Beispiel #1
0
static void mainvr_connection_upgrade(liVRequest *vr, liStream *backend_drain, liStream *backend_source) {
	liConnection* con = li_connection_from_vrequest(vr);
	LI_FORCE_ASSERT(NULL != con);

	if (con->response_headers_sent || NULL != con->out.source) {
		li_connection_error(con);
		return;
	}
	if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
		VR_DEBUG(vr, "%s", "connection upgrade: write response headers");
	}
	con->response_headers_sent = TRUE;
	con->info.keep_alive = FALSE;
	li_response_send_headers(vr, con->out.out, NULL, TRUE);
	con->state = LI_CON_STATE_UPGRADED;
	vr->response.transfer_encoding = 0;
	li_connection_update_io_wait(con);

	li_stream_disconnect_dest(&con->in);
	con->in.out->is_closed = FALSE;

	li_stream_connect(&con->in, backend_drain);
	li_stream_connect(backend_source, &con->out);

	li_vrequest_reset(con->mainvr, TRUE);

	if (NULL != con->in.source) {
		li_chunkqueue_steal_all(con->out.out, backend_drain->out);
	}
	con->info.out_queue_length = con->out.out->length;

	li_stream_notify(&con->out);
	li_stream_notify(&con->in);
}
Beispiel #2
0
/* io -> ssl crypted in */
static void stream_crypt_drain_cb(liStream *stream, liStreamEvent event) {
	liGnuTLSFilter *f = LI_CONTAINER_OF(stream, liGnuTLSFilter, crypt_drain);
	switch (event) {
	case LI_STREAM_NEW_DATA:
		if (!stream->out->is_closed && NULL != stream->source) {
			li_chunkqueue_steal_all(stream->out, stream->source->out);
			stream->out->is_closed = stream->out->is_closed || stream->source->out->is_closed;
			li_stream_notify(stream); /* tell plain_source to do SSL_read */
		}
		if (stream->out->is_closed) {
			li_stream_disconnect(stream);
		}
		break;
	case LI_STREAM_NEW_CQLIMIT:
		break;
	case LI_STREAM_CONNECTED_DEST: /* plain_source */
		break;
	case LI_STREAM_CONNECTED_SOURCE: /* io in */
		break;
	case LI_STREAM_DISCONNECTED_DEST: /* plain_source */
		if (!stream->out->is_closed || 0 != stream->out->length) {
			f_abort_gnutls(f); /* didn't read everything */
		}
		break;
	case LI_STREAM_DISCONNECTED_SOURCE: /* io in disconnect */
		if (!stream->out->is_closed) {
			f_abort_gnutls(f); /* conn aborted */
		}
		break;
	case LI_STREAM_DESTROY:
		f_release(f);
		break;
	}
}
Beispiel #3
0
/* ssl (plain) -> app */
static void stream_plain_source_cb(liStream *stream, liStreamEvent event) {
	liGnuTLSFilter *f = LI_CONTAINER_OF(stream, liGnuTLSFilter, plain_source);
	switch (event) {
	case LI_STREAM_NEW_DATA:
		do_gnutls_read(f);
		if (f->write_wants_read) do_gnutls_write(f);
		li_stream_notify(stream);
		break;
	case LI_STREAM_NEW_CQLIMIT:
		break;
	case LI_STREAM_CONNECTED_DEST: /* app */
		break;
	case LI_STREAM_CONNECTED_SOURCE: /* crypt_drain */
		break;
	case LI_STREAM_DISCONNECTED_DEST: /* app */
		if (!stream->out->is_closed || 0 != stream->out->length) {
			f_abort_gnutls(f); /* didn't read everything */
		}
		break;
	case LI_STREAM_DISCONNECTED_SOURCE: /* crypt_drain */
		if (!stream->out->is_closed) {
			f_abort_gnutls(f); /* didn't get everything */
		}
		break;
	case LI_STREAM_DESTROY:
		f_release(f);
		break;
	}
}
/* flush current tempfile chunk. ignores out->is_closed. */
static void bod_flush(bod_state *state) {
	liChunkQueue *out = state->stream.out;
	if (NULL != out && NULL != state->tempfile && state->write_pos > state->flush_pos) {
		li_chunkqueue_append_chunkfile(out, state->tempfile, state->flush_pos, state->write_pos - state->flush_pos);
		state->flush_pos = state->write_pos;
		li_stream_notify(&state->stream);
	}
}
/* stop buffering, forward everyting */
static void bod_stop(bod_state *state) {
	bod_close(state);
	if (NULL != state->stream.source && !state->stream.out->is_closed) {
		li_chunkqueue_steal_all(state->stream.out, state->stream.source->out);
		if (state->stream.source->out->is_closed) {
			state->stream.out->is_closed = TRUE;
			li_stream_disconnect(&state->stream);
		}
		li_stream_notify(&state->stream);
	}
	state->vr = NULL;
}
Beispiel #6
0
static void f_close_with_alert(liGnuTLSFilter *f, int r) {
	if (f->closing || f->aborted) return;

	if (GNUTLS_E_SUCCESS != gnutls_alert_send_appropriate(f->session, r)) {
		f_abort_gnutls(f);
		return;
	}

	/* push alert to io */
	li_stream_notify(&f->crypt_source);
	f->plain_source.out->is_closed = TRUE;
	f->crypt_drain.out->is_closed = TRUE;
	f_close_gnutls(f);
}
static void stream_http_response_data(liStreamHttpResponse* shr) {
	if (NULL == shr->stream.source) return;

	if (!shr->response_headers_finished) {
		switch (li_http_response_parse(shr->vr, &shr->parse_response_ctx)) {
		case LI_HANDLER_GO_ON:
			check_response_header(shr);
			if (NULL == shr->stream.source) return;
			break;
		case LI_HANDLER_ERROR:
			VR_ERROR(shr->vr, "%s", "Parsing response header failed");
			li_vrequest_error(shr->vr);
			return;
		case LI_HANDLER_WAIT_FOR_EVENT:
			if (shr->stream.source->out->is_closed) {
				VR_ERROR(shr->vr, "%s", "Parsing response header failed (eos)");
				li_vrequest_error(shr->vr);
			}
			return;
		default:
			return;
		}
	}

	if (shr->transfer_encoding_chunked) {
		if (!li_filter_chunked_decode(shr->vr, shr->stream.out, shr->stream.source->out, &shr->chunked_decode_state)) {
			if (NULL != shr->vr) {
				VR_ERROR(shr->vr, "%s", "Decoding chunks failed");
				li_vrequest_error(shr->vr);
			} else {
				li_stream_reset(&shr->stream);
			}
		}
		if (shr->stream.source->out->is_closed) {
			li_stream_disconnect(&shr->stream);
		}
	} else {
		li_chunkqueue_steal_all(shr->stream.out, shr->stream.source->out);
		if (shr->stream.source->out->is_closed) {
			shr->stream.out->is_closed = TRUE;
			li_stream_disconnect(&shr->stream);
		}
	}
	li_stream_notify(&shr->stream);
}
Beispiel #8
0
/* http response header/data -> tcp/ssl */
static void _connection_http_out_cb(liStream *stream, liStreamEvent event) {
	liConnection *con = LI_CONTAINER_OF(stream, liConnection, out);

	liChunkQueue *raw_out = stream->out, *out;
	liVRequest *vr = con->mainvr;

	switch (event) {
	case LI_STREAM_NEW_DATA:
		/* handle below */
		break;
	case LI_STREAM_CONNECTED_SOURCE:
		/* also handle data immediately */
		break;
	case LI_STREAM_DISCONNECTED_SOURCE:
		if (!con->out_has_all_data) li_connection_error(con);
		return;
	case LI_STREAM_DISCONNECTED_DEST:
		if (!raw_out->is_closed || 0 != raw_out->length || NULL == con->con_sock.raw_out) {
			li_connection_error(con);
		} else {
			connection_close(con);
		}
		return;
	case LI_STREAM_DESTROY:
		con->info.resp = NULL;
		li_job_later(&con->wrk->loop.jobqueue, &con->job_reset);
		return;
	default:
		return;
	}

	out = (NULL != stream->source) ? stream->source->out : NULL;

	/* keep raw_out->is_closed = FALSE for keep-alive requests; instead set con->out_has_all_data = TRUE */

	if (LI_CON_STATE_HANDLE_MAINVR <= con->state) {
		if (NULL == stream->source) {
			if (LI_CON_STATE_HANDLE_MAINVR == con->state) {
				/* wait for vrequest to connect the stream as signal that the headers are ready */
				return;
			}
		}
		if (!con->response_headers_sent) {
			if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
				VR_DEBUG(vr, "%s", "write response headers");
			}
			con->response_headers_sent = TRUE;
			if (0 == CORE_OPTION(LI_CORE_OPTION_MAX_KEEP_ALIVE_IDLE).number) {
				con->info.keep_alive = FALSE;
			}
			li_response_send_headers(vr, raw_out, out, FALSE);
		}

		if (!con->out_has_all_data && !raw_out->is_closed && NULL != out) {
			if (vr->response.transfer_encoding & LI_HTTP_TRANSFER_ENCODING_CHUNKED) {
				li_filter_chunked_encode(vr, raw_out, out);
			} else {
				li_chunkqueue_steal_all(raw_out, out);
			}
		}
		if (raw_out->is_closed || NULL == out || out->is_closed) {
			con->out_has_all_data = TRUE;
			raw_out->is_closed = FALSE;
		}
		if (con->out_has_all_data) {
			if (con->state < LI_CON_STATE_WRITE) {
				con->state = LI_CON_STATE_WRITE;
				li_connection_update_io_wait(con);
			}
			if (NULL != out) {
				out = NULL;
				li_stream_disconnect(stream);
			}
		}
		con->info.out_queue_length = raw_out->length;
	}

	li_stream_notify(stream);
}
Beispiel #9
0
/* tcp/ssl -> http "parser" */
static void _connection_http_in_cb(liStream *stream, liStreamEvent event) {
	liConnection *con = LI_CONTAINER_OF(stream, liConnection, in);
	liChunkQueue *raw_in, *in;
	liVRequest *vr = con->mainvr;

	switch (event) {
	case LI_STREAM_NEW_DATA:
		/* handle below */
		break;
	case LI_STREAM_DISCONNECTED_SOURCE:
		connection_close(con);
		return;
	case LI_STREAM_DESTROY:
		con->info.req = NULL;
		li_job_later(&con->wrk->loop.jobqueue, &con->job_reset);
		return;
	default:
		return;
	}

	if (NULL == stream->source) return;

	/* raw_in never gets closed normally - if we receive EOF from the client it means it cancelled the request */
	raw_in = stream->source->out;
	if (raw_in->is_closed) {
		connection_close(con);
		return;
	}

	/* always close "in" after request body end. reopen it on keep-alive */
	in = con->in.out;

	if (0 == raw_in->length) return; /* no (new) data */

	if (LI_CON_STATE_UPGRADED == con->state) {
		li_chunkqueue_steal_all(in, raw_in);
		li_stream_notify(stream);
		return;
	}

	if (con->state == LI_CON_STATE_KEEP_ALIVE) {
		/* stop keep alive timeout watchers */
		if (con->keep_alive_data.link) {
			g_queue_delete_link(&con->wrk->keep_alive_queue, con->keep_alive_data.link);
			con->keep_alive_data.link = NULL;
		}
		con->keep_alive_data.timeout = 0;
		li_event_stop(&con->keep_alive_data.watcher);

		con->keep_alive_requests++;
		/* disable keep alive if limit is reached */
		if (con->keep_alive_requests == CORE_OPTION(LI_CORE_OPTION_MAX_KEEP_ALIVE_REQUESTS).number)
			con->info.keep_alive = FALSE;

		/* reopen stream for request body */
		li_chunkqueue_reset(in);
		/* reset stuff from keep-alive and record timestamp */
		li_vrequest_start(con->mainvr);

		con->state = LI_CON_STATE_READ_REQUEST_HEADER;

		/* put back in io timeout queue */
		li_connection_update_io_wait(con);
	} else if (con->state == LI_CON_STATE_REQUEST_START) {
		con->state = LI_CON_STATE_READ_REQUEST_HEADER;
		li_connection_update_io_wait(con);
	}

	if (con->state == LI_CON_STATE_READ_REQUEST_HEADER) {
		liHandlerResult res;

		if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
			VR_DEBUG(vr, "%s", "reading request header");
		}

		res = li_http_request_parse(vr, &con->req_parser_ctx);

		/* max uri length 8 kilobytes */
		/* TODO: check this and similar in request_parse and response_parse */
		if (vr->request.uri.raw->len > 8*1024) {
			VR_INFO(vr,
				"request uri too large. limit: 8kb, received: %s",
				li_counter_format(vr->request.uri.raw->len, COUNTER_BYTES, vr->wrk->tmp_str)->str
			);

			con->info.keep_alive = FALSE;
			vr->response.http_status = 414; /* Request-URI Too Large */
			con->state = LI_CON_STATE_WRITE;
			li_connection_update_io_wait(con);
			li_stream_again(&con->out);
			return;
		}

		switch(res) {
		case LI_HANDLER_GO_ON:
			break; /* go on */
		case LI_HANDLER_WAIT_FOR_EVENT:
			return;
		case LI_HANDLER_ERROR:
		case LI_HANDLER_COMEBACK: /* unexpected */
			/* unparsable header */
			if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
				VR_DEBUG(vr, "%s", "parsing header failed");
			}

			con->wrk->stats.requests++;
			con->info.keep_alive = FALSE;
			/* set status 400 if not already set to e.g. 413 */
			if (vr->response.http_status == 0)
				vr->response.http_status = 400;
			con->state = LI_CON_STATE_WRITE;
			li_connection_update_io_wait(con);
			li_stream_again(&con->out);
			return;
		}

		con->wrk->stats.requests++;

		/* headers ready */
		if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
			VR_DEBUG(vr, "%s", "validating request header");
		}
		if (!li_request_validate_header(con)) {
			/* set status 400 if not already set */
			if (vr->response.http_status == 0)
				vr->response.http_status = 400;
			con->state = LI_CON_STATE_WRITE;
			con->info.keep_alive = FALSE;
			li_connection_update_io_wait(con);
			li_stream_again(&con->out);
			return;
		}

		/* When does a client ask for 100 Continue? probably not while trying to ddos us
		 * as post content probably goes to a dynamic backend anyway, we don't
		 * care about the rare cases we could determine that we don't want a request at all
		 * before sending it to a backend - so just send the stupid header
		 */
		if (con->expect_100_cont) {
			if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
				VR_DEBUG(vr, "%s", "send 100 Continue");
			}
			li_chunkqueue_append_mem(con->out.out, CONST_STR_LEN("HTTP/1.1 100 Continue\r\n\r\n"));
			con->expect_100_cont = FALSE;

			li_stream_notify(&con->out);
		}

		con->state = LI_CON_STATE_HANDLE_MAINVR;
		li_connection_update_io_wait(con);
		li_action_enter(vr, con->srv->mainaction);

		li_vrequest_handle_request_headers(vr);
	}

	if (con->state != LI_CON_STATE_READ_REQUEST_HEADER && !in->is_closed) {
		goffset newbytes = 0;

		if (-1 == vr->request.content_length) {
			if (!in->is_closed) {
				if (!li_filter_chunked_decode(vr, in, raw_in, &con->in_chunked_decode_state)) {
					if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
						VR_DEBUG(vr, "%s", "failed decoding chunked request body");
					}
					li_connection_error(con);
					return;
				}
				if (in->is_closed) vr->request.content_length = in->bytes_in;
				newbytes = 1; /* always notify */
			}
		} else {
			if (in->bytes_in < vr->request.content_length) {
				newbytes = li_chunkqueue_steal_len(in, raw_in, vr->request.content_length - in->bytes_in);
			}
			if (in->bytes_in == vr->request.content_length) {
				in->is_closed = TRUE;
			}
		}
		if (newbytes > 0 || in->is_closed) {
			li_stream_notify(&con->in);
		}
	}
}
static void bod_handle_data(bod_state *state) {
	liChunkQueue *out = state->stream.out;
	liChunkQueue *in;

	if (out->is_closed) {
		li_stream_disconnect(&state->stream);
		bod_close(state);
		return;
	}

	in = (state->stream.source != NULL) ? state->stream.source->out : NULL;
	if (NULL == in) goto out;

	if (NULL == state->vr) {
		li_chunkqueue_steal_all(out, in);
		goto out;
	}

	while (in->length > 0) {
		liChunk *c = li_chunkqueue_first_chunk(in);
		liChunkIter ci;
		off_t length, data_len;
		char *data = NULL;
		GError *err;

		assert(UNUSED_CHUNK != c->type);
		switch (c->type) {
		case UNUSED_CHUNK:
			/* shouldn't happen anyway, but stealing it is ok here too */
		case FILE_CHUNK:
			if (state->split_on_file_chunks) {
				bod_close(state);
			} else {
				bod_flush(state);
			}
			li_chunkqueue_steal_chunk(out, in);
			break;
		case STRING_CHUNK:
		case MEM_CHUNK:
		case BUFFER_CHUNK:
			if (!bod_open(state)) return;

			length = li_chunk_length(c);
			ci = li_chunkqueue_iter(in);

			err = NULL;
			if (LI_HANDLER_GO_ON != li_chunkiter_read(ci, 0, length, &data, &data_len, &err)) {
				if (NULL != err) {
					VR_ERROR(state->vr, "%s", err->message);
					g_error_free(err);
				}
				bod_error(state);
				return;
			}

			while ( data_len > 0 ) {
				ssize_t r;

				r = pwrite(state->tempfile->fd, data, data_len, state->write_pos);

				if (r < 0) {
					switch (errno) {
					case EINTR: continue;
					default: break;
					}

					VR_ERROR(state->vr, "pwrite failed: %s", g_strerror(errno));
					bod_stop(state); /* write failures are not critical */
					return;
				}

				data += r;
				data_len -= r;
				state->write_pos += r;
			}

			li_chunkqueue_skip(in, length);

			break;
		}
	}

	bod_autoflush(state);

out:
	if (NULL == in || in->is_closed) {
		out->is_closed = TRUE;
		bod_close(state); /* close/flush ignores out->is_closed */
		li_stream_notify(&state->stream); /* if no flush happened we still notify */
	}
}