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)); } }
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; }
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; }
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; }
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; }