Esempio n. 1
0
void li_http_header_get_all(GString *dest, liHttpHeaders *headers, const gchar *key, size_t keylen) {
	GList *l;
	g_string_truncate(dest, 0);

	for (l = li_http_header_find_first(headers, key, keylen); l; l = li_http_header_find_next(l, key, keylen)) {
		liHttpHeader *h = (liHttpHeader*) l->data;
		if (dest->len) g_string_append_len(dest, CONST_STR_LEN(", "));
		g_string_append_len(dest, &h->data->str[h->keylen+2], h->data->len - (h->keylen + 2));
	}
}
Esempio n. 2
0
gboolean li_http_header_is(liHttpHeaders *headers, const gchar *key, size_t keylen, const gchar *val, size_t valuelen) {
	GList *l;
	UNUSED(valuelen);

	for (l = li_http_header_find_first(headers, key, keylen); l; l = li_http_header_find_next(l, key, keylen)) {
		liHttpHeader *h = (liHttpHeader*) l->data;
		if (h->data->len - (h->keylen + 2) != valuelen) continue;
		if (0 == g_ascii_strcasecmp( &h->data->str[h->keylen+2], val )) return TRUE;
	}
	return FALSE;
}
Esempio n. 3
0
gboolean li_http_header_remove(liHttpHeaders *headers, const gchar *key, size_t keylen) {
	GList *l, *lp = NULL;
	gboolean res = FALSE;

	for (l = li_http_header_find_first(headers, key, keylen); l; l = li_http_header_find_next(l, key, keylen)) {
		if (lp) {
			li_http_header_remove_link(headers, lp);
			res = TRUE;
			lp = NULL;
		}
		lp = l;
	}
	if (lp) {
		li_http_header_remove_link(headers, lp);
		res = TRUE;
		lp = NULL;
	}
	return res;
}
static void check_response_header(liStreamHttpResponse* shr) {
	liResponse *resp = &shr->vr->response;
	GList *l;

	shr->transfer_encoding_chunked = FALSE;

	/* Transfer-Encoding: chunked */
	l = li_http_header_find_first(resp->headers, CONST_STR_LEN("transfer-encoding"));
	if (l) {
		for ( ; l ; l = li_http_header_find_next(l, CONST_STR_LEN("transfer-encoding")) ) {
			liHttpHeader *hh = (liHttpHeader*) l->data;
			if (0 == g_ascii_strcasecmp( LI_HEADER_VALUE(hh), "identity" )) {
				/* ignore */
				continue;
			} if (0 == g_ascii_strcasecmp( LI_HEADER_VALUE(hh), "chunked" )) {
				if (shr->transfer_encoding_chunked) {
					VR_ERROR(shr->vr, "%s", "Response is chunked encoded twice");
					li_vrequest_error(shr->vr);
					return;
				}
				shr->transfer_encoding_chunked = TRUE;
			} else {
				VR_ERROR(shr->vr, "Response has unsupported Transfer-Encoding: %s", LI_HEADER_VALUE(hh));
				li_vrequest_error(shr->vr);
				return;
			}
		}
		li_http_header_remove(resp->headers, CONST_STR_LEN("transfer-encoding"));
		/* any non trivial transfer-encoding overwrites content-length */
		if (shr->transfer_encoding_chunked) {
			li_http_header_remove(resp->headers, CONST_STR_LEN("content-length"));
		}
	}

	/* Upgrade: */
	l = li_http_header_find_first(resp->headers, CONST_STR_LEN("upgrade"));
	if (l) {
		gboolean have_connection_upgrade = FALSE;
		liHttpHeaderTokenizer header_tokenizer;
		GString *token;
		if (101 != resp->http_status) {
			VR_ERROR(shr->vr, "Upgrade but status is %i instead of 101 'Switching Protocols'", resp->http_status);
			li_vrequest_error(shr->vr);
			return;
		}
		if (shr->transfer_encoding_chunked) {
			VR_ERROR(shr->vr, "%s", "Upgrade with Transfer-Encoding: chunked");
			li_vrequest_error(shr->vr);
			return;
		}
		/* requires Connection: Upgrade header */
		token = g_string_sized_new(15);
		li_http_header_tokenizer_start(&header_tokenizer, resp->headers, CONST_STR_LEN("Connection"));
		while (li_http_header_tokenizer_next(&header_tokenizer, token)) {
			VR_ERROR(shr->vr, "Parsing header '%s'", ((liHttpHeader*)header_tokenizer.cur->data)->data->str);
			VR_ERROR(shr->vr, "Connection token '%s'", token->str);
			if (0 == g_ascii_strcasecmp(token->str, "Upgrade")) {
				have_connection_upgrade = TRUE;
				break;
			}
		}
		g_string_free(token, TRUE); token = NULL;
		if (!have_connection_upgrade) {
			VR_ERROR(shr->vr, "%s", "Upgrade without Connection: Upgrade Transfer");
			li_vrequest_error(shr->vr);
			return;
		}
		shr->response_headers_finished = TRUE;
		shr->vr->backend_drain->out->is_closed = FALSE;
		{
			/* li_vrequest_connection_upgrade releases vr->backend_drain; keep our own reference */
			liStream *backend_drain = shr->vr->backend_drain;
			shr->vr->backend_drain = NULL;
			li_vrequest_connection_upgrade(shr->vr, backend_drain, &shr->stream);
			li_stream_release(backend_drain);
		}
		return;
	}

	shr->response_headers_finished = TRUE;
	li_vrequest_indirect_headers_ready(shr->vr);

	return;
}
Esempio n. 5
0
gboolean li_request_validate_header(liConnection *con) {
	liRequest *req = &con->mainvr->request;
	liHttpHeader *hh;
	GList *l;

	if (con->info.is_ssl) {
		g_string_append_len(req->uri.scheme, CONST_STR_LEN("https"));
	} else {
		g_string_append_len(req->uri.scheme, CONST_STR_LEN("http"));
	}

	switch (req->http_version) {
	case LI_HTTP_VERSION_1_0:
		if (!li_http_header_is(req->headers, CONST_STR_LEN("connection"), CONST_STR_LEN("keep-alive")))
			con->info.keep_alive = FALSE;
		break;
	case LI_HTTP_VERSION_1_1:
		if (li_http_header_is(req->headers, CONST_STR_LEN("connection"), CONST_STR_LEN("close")))
			con->info.keep_alive = FALSE;
		break;
	case LI_HTTP_VERSION_UNSET:
		bad_request(con, 505); /* Version not Supported */
		return FALSE;
	}

	if (req->uri.raw->len == 0) {
		bad_request(con, 400); /* bad request */
		return FALSE;
	}

	/* get hostname */
	l = li_http_header_find_first(req->headers, CONST_STR_LEN("host"));
	if (NULL != l) {
		if (NULL != li_http_header_find_next(l, CONST_STR_LEN("host"))) {
			/* more than one "host" header */
			bad_request(con, 400); /* bad request */
			return FALSE;
		}

		hh = (liHttpHeader*) l->data;
		g_string_append_len(req->uri.authority, LI_HEADER_VALUE_LEN(hh));
		/* check header after we parsed the url, as it may override uri.authority */
	}

	/* Need hostname in HTTP/1.1 */
	if (req->uri.authority->len == 0 && req->http_version == LI_HTTP_VERSION_1_1) {
		bad_request(con, 400); /* bad request */
		return FALSE;
	}

	/* may override hostname */
	if (!request_parse_url(con->mainvr)) {
		bad_request(con, 400); /* bad request */
		return FALSE;
	}

	if (req->uri.host->len == 0 && req->uri.authority->len != 0) {
		if (!li_parse_hostname(&req->uri)) {
			bad_request(con, 400); /* bad request */
			return FALSE;
		}
	}

	/* remove trailing dots from hostname */
	{
		guint i = req->uri.host->len;
		while (i > 0 && req->uri.host->str[i-1] == '.') i--;
		g_string_truncate(req->uri.host, i);
	}

	/* content-length */
	hh = li_http_header_lookup(req->headers, CONST_STR_LEN("content-length"));
	if (hh) {
		const gchar *val = LI_HEADER_VALUE(hh);
		gint64 r;
		char *err;

		r = g_ascii_strtoll(val, &err, 10);
		if (*err != '\0') {
			_VR_DEBUG(con->srv, con->mainvr, "content-length is not a number: %s (Status: 400)", err);
			bad_request(con, 400); /* bad request */
			return FALSE;
		}

		/**
			* negative content-length is not supported
			* and is a bad request
			*/
		if (r < 0) {
			bad_request(con, 400); /* bad request */
			return FALSE;
		}

		/**
			* check if we had a over- or underrun in the string conversion
			*/
		if (r == G_MININT64 || r == G_MAXINT64) {
			if (errno == ERANGE) {
				bad_request(con, 413); /* Request Entity Too Large */
				return FALSE;
			}
		}

		con->mainvr->request.content_length = r;
	}

	/* Expect: 100-continue */
	l = li_http_header_find_first(req->headers, CONST_STR_LEN("expect"));
	if (l) {
		gboolean expect_100_cont = FALSE;

		for ( ; l ; l = li_http_header_find_next(l, CONST_STR_LEN("expect")) ) {
			hh = (liHttpHeader*) l->data;
			if (0 == g_ascii_strcasecmp( LI_HEADER_VALUE(hh), "100-continue" )) {
				expect_100_cont = TRUE;
			} else {
				/* we only support 100-continue */
				bad_request(con, 417); /* Expectation Failed */
				return FALSE;
			}
		}

		if (expect_100_cont && req->http_version == LI_HTTP_VERSION_1_0) {
			/* only HTTP/1.1 clients can send us this header */
			bad_request(con, 417); /* Expectation Failed */
			return FALSE;
		}
		con->expect_100_cont = expect_100_cont;
	}

	/* TODO: headers:
	 * - If-Modified-Since (different duplicate check)
	 * - If-None-Match (different duplicate check)
	 * - Range (duplicate check)
	 */

	switch(con->mainvr->request.http_method) {
	case LI_HTTP_METHOD_GET:
	case LI_HTTP_METHOD_HEAD:
		/* content-length is forbidden for those */
		if (con->mainvr->request.content_length > 0) {
			VR_ERROR(con->mainvr, "%s", "GET/HEAD with content-length -> 400");

			bad_request(con, 400); /* bad request */
			return FALSE;
		}
		con->mainvr->request.content_length = 0;
		break;
	case LI_HTTP_METHOD_POST:
		/* content-length is required for them */
		if (con->mainvr->request.content_length == -1) {
			/* content-length is missing */
			VR_ERROR(con->mainvr, "%s", "POST-request, but content-length missing -> 411");

			bad_request(con, 411); /* Length Required */
			return FALSE;
		}
		break;
	default:
		if (con->mainvr->request.content_length == -1)
			con->mainvr->request.content_length = 0;
		/* the may have a content-length */
		break;
	}

	return TRUE;
}
Esempio n. 6
0
static liHandlerResult cache_etag_handle(liVRequest *vr, gpointer param, gpointer *context) {
	cache_etag_context *ctx = (cache_etag_context*) param;
	cache_etag_file *cfile = (cache_etag_file*) *context;
	GList *etag_entry;
	liHttpHeader *etag;
	struct stat st;
	GString *tmp_str = vr->wrk->tmp_str;
	liHandlerResult res;
	int err, fd;

	if (!cfile) {
		if (vr->request.http_method != LI_HTTP_METHOD_GET) return LI_HANDLER_GO_ON;

		LI_VREQUEST_WAIT_FOR_RESPONSE_HEADERS(vr);

		if (vr->response.http_status != 200) return LI_HANDLER_GO_ON;

		/* Don't cache static files if filter list is empty */
		if (NULL == vr->filters_out_first && vr->backend_source->out->is_closed && 0 == vr->backend_source->out->mem_usage)
			return LI_HANDLER_GO_ON;

		etag_entry = li_http_header_find_first(vr->response.headers, CONST_STR_LEN("etag"));
		if (!etag_entry) return LI_HANDLER_GO_ON; /* no etag -> no caching */
		if (li_http_header_find_next(etag_entry, CONST_STR_LEN("etag"))) {
			VR_ERROR(vr, "%s", "duplicate etag header in response, will not cache it");
			return LI_HANDLER_GO_ON;
		}
		etag = (liHttpHeader*) etag_entry->data;

		cfile = cache_etag_file_create(createFileName(vr, ctx->path, etag));
		*context = cfile;
	}

	res = li_stat_cache_get(vr, cfile->filename, &st, &err, &fd);
	if (res == LI_HANDLER_WAIT_FOR_EVENT)
		return res;

	if (res == LI_HANDLER_GO_ON) {
		liFilter *f;
		if (!S_ISREG(st.st_mode)) {
			VR_ERROR(vr, "Unexpected file type for cache file '%s' (mode %o)", cfile->filename->str, (unsigned int) st.st_mode);
			close(fd);
			return LI_HANDLER_GO_ON; /* no caching */
		}
		cfile->hit_fd = fd;
#ifdef FD_CLOEXEC
		fcntl(cfile->hit_fd, F_SETFD, FD_CLOEXEC);
#endif
		if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
			VR_DEBUG(vr, "cache hit for '%s'", vr->request.uri.path->str);
		}
		cfile->hit_length = st.st_size;
		g_string_truncate(tmp_str, 0);
		li_string_append_int(tmp_str, st.st_size);
		li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Length"), GSTR_LEN(tmp_str));
		f = li_vrequest_add_filter_out(vr, cache_etag_filter_hit, NULL, NULL, NULL);
		if (NULL != f) {
			li_chunkqueue_append_file_fd(f->out, NULL, 0, cfile->hit_length, cfile->hit_fd);
			f->out->is_closed = TRUE;
			cfile->hit_fd = -1;
		}
		cache_etag_file_free(cfile);

		*context = NULL;
		return LI_HANDLER_GO_ON;
	}

	if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
		VR_DEBUG(vr, "cache miss for '%s'", vr->request.uri.path->str);
	}

	if (!cache_etag_file_start(vr, cfile)) {
		cache_etag_file_free(cfile);
		*context = NULL;
		return LI_HANDLER_GO_ON; /* no caching */
	}

	li_vrequest_add_filter_out(vr, cache_etag_filter_miss, cache_etag_filter_free, NULL, cfile);
	*context = NULL;

	return LI_HANDLER_GO_ON;
}