diff --git a/src/connections.c b/src/connections.c index 055247df..0bb889a2 100644 --- a/src/connections.c +++ b/src/connections.c @@ -13,7 +13,6 @@ #include "request.h" #include "response.h" #include "network.h" -#include "http_chunk.h" #include "stat_cache.h" #include "plugin.h" @@ -258,269 +257,6 @@ static void connection_handle_response_end_state(request_st * const r, connectio } } -__attribute_cold__ -static void connection_handle_errdoc_init(request_st * const r) { - /* modules that produce headers required with error response should - * typically also produce an error document. Make an exception for - * mod_auth WWW-Authenticate response header. */ - buffer *www_auth = NULL; - if (401 == r->http_status) { - const buffer *vb = http_header_response_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("WWW-Authenticate")); - if (NULL != vb) www_auth = buffer_init_buffer(vb); - } - - buffer_reset(&r->physical.path); - r->resp_htags = 0; - array_reset_data_strings(&r->resp_headers); - http_response_body_clear(r, 0); - - if (NULL != www_auth) { - http_header_response_set(r, HTTP_HEADER_OTHER, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(www_auth)); - buffer_free(www_auth); - } -} - -__attribute_cold__ -static void connection_handle_errdoc(request_st * const r) { - if (NULL == r->handler_module - ? r->error_handler_saved_status >= 65535 - : (!r->conf.error_intercept||r->error_handler_saved_status)) - return; - - connection_handle_errdoc_init(r); - r->resp_body_finished = 1; - - /* try to send static errorfile */ - if (!buffer_string_is_empty(r->conf.errorfile_prefix)) { - buffer_copy_buffer(&r->physical.path, r->conf.errorfile_prefix); - buffer_append_int(&r->physical.path, r->http_status); - buffer_append_string_len(&r->physical.path, CONST_STR_LEN(".html")); - if (0 == http_chunk_append_file(r, &r->physical.path)) { - stat_cache_entry *sce = stat_cache_get_entry(&r->physical.path); - const buffer *content_type = (NULL != sce) - ? stat_cache_content_type_get(sce, r) - : NULL; - if (content_type) - http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, - CONST_STR_LEN("Content-Type"), - CONST_BUF_LEN(content_type)); - return; - } - } - - /* build default error-page */ - buffer_reset(&r->physical.path); - buffer * const b = r->tmp_buf; - buffer_copy_string_len(b, CONST_STR_LEN( - "\n" - "\n" - "\n" - " \n" - " ")); - http_status_append(b, r->http_status); - buffer_append_string_len(b, CONST_STR_LEN( - "\n" - " \n" - " \n" - "

")); - http_status_append(b, r->http_status); - buffer_append_string_len(b, CONST_STR_LEN( - "

\n" - " \n" - "\n")); - (void)http_chunk_append_mem(r, CONST_BUF_LEN(b)); - - http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, - CONST_STR_LEN("Content-Type"), - CONST_STR_LEN("text/html")); -} - -__attribute_cold__ -static void -connection_merge_trailers (request_st * const r) -{ - /* attempt to merge trailers into headers; header not yet sent by caller */ - if (buffer_string_is_empty(&r->gw_dechunk->b)) return; - const int done = r->gw_dechunk->done; - if (!done) return; /* XXX: !done; could scan for '\n' and send only those */ - - /* do not include trailers if success status (when response was read from - * backend) subsequently changed to error status. http_chunk could add the - * trailers, but such actions are better on a different code layer than in - * http_chunk.c */ - if (done < 400 && r->http_status >= 400) return; - - /* XXX: trailers passed through; no sanity check currently done - * https://tools.ietf.org/html/rfc7230#section-4.1.2 - * - * Not checking for disallowed fields - * Not handling (deprecated) line wrapping - * Not strictly checking fields - */ - const char *k = strchr(r->gw_dechunk->b.ptr, '\n'); /*(skip final chunk)*/ - if (NULL == k) return; /*(should not happen)*/ - ++k; - for (const char *v, *e; (e = strchr(k, '\n')); k = e+1) { - v = memchr(k, ':', (size_t)(e - k)); - if (NULL == v || v == k || *k == ' ' || *k == '\t') continue; - uint32_t klen = (uint32_t)(v - k); - do { ++v; } while (*v == ' ' || *v == '\t'); - if (*v == '\r' || *v == '\n') continue; - enum http_header_e id = http_header_hkey_get(k, klen); - http_header_response_insert(r, id, k, klen, v, (size_t)(e - v)); - } - http_header_response_unset(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Trailer")); - buffer_clear(&r->gw_dechunk->b); -} - -static int connection_handle_write_prepare(request_st * const r) { - if (NULL == r->handler_module) { - /* static files */ - switch(r->http_method) { - case HTTP_METHOD_GET: - case HTTP_METHOD_POST: - case HTTP_METHOD_HEAD: - break; - case HTTP_METHOD_OPTIONS: - /* - * 400 is coming from the request-parser BEFORE uri.path is set - * 403 is from the response handler when no module handled request - * - * */ - if ((!r->http_status || r->http_status == 200) - && !buffer_string_is_empty(&r->uri.path) - && r->uri.path.ptr[0] != '*') { - http_response_body_clear(r, 0); - http_header_response_append(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Allow"), CONST_STR_LEN("OPTIONS, GET, HEAD, POST")); - r->http_status = 200; - r->resp_body_finished = 1; - - } - break; - default: - if (0 == r->http_status) { - r->http_status = 501; - } - break; - } - } - - if (r->http_status == 0) { - r->http_status = 403; - } - - switch(r->http_status) { - case 204: /* class: header only */ - case 205: - case 304: - /* disable chunked encoding again as we have no body */ - http_response_body_clear(r, 1); - r->resp_body_finished = 1; - break; - default: /* class: header + body */ - /* only custom body for 4xx and 5xx */ - if (r->http_status >= 400 && r->http_status < 600) - connection_handle_errdoc(r); - break; - } - - if (r->gw_dechunk) - connection_merge_trailers(r); - - /* Allow filter plugins to change response headers before they are written. */ - switch(plugins_call_handle_response_start(r)) { - case HANDLER_GO_ON: - case HANDLER_FINISHED: - break; - default: - log_error(r->conf.errh, __FILE__, __LINE__, - "response_start plugin failed"); - return -1; - } - - if (r->resp_body_finished) { - /* we have all the content and chunked encoding is not used, set a content-length */ - - if (!(r->resp_htags & (HTTP_HEADER_CONTENT_LENGTH|HTTP_HEADER_TRANSFER_ENCODING))) { - off_t qlen = chunkqueue_length(r->write_queue); - - /** - * The Content-Length header only can be sent if we have content: - * - HEAD doesn't have a content-body (but have a content-length) - * - 1xx, 204 and 304 don't have a content-body (RFC 2616 Section 4.3) - * - * Otherwise generate a Content-Length header as chunked encoding is not - * available - */ - if ((r->http_status >= 100 && r->http_status < 200) || - r->http_status == 204 || - r->http_status == 304) { - /* no Content-Body, no Content-Length */ - http_header_response_unset(r, HTTP_HEADER_CONTENT_LENGTH, CONST_STR_LEN("Content-Length")); - } else if (qlen > 0) { - buffer * const tb = r->tmp_buf; - buffer_clear(tb); - buffer_append_int(tb, qlen); - http_header_response_set(r, HTTP_HEADER_CONTENT_LENGTH, - CONST_STR_LEN("Content-Length"), - CONST_BUF_LEN(tb)); - } else if (r->http_method != HTTP_METHOD_HEAD) { - /* qlen = 0 is important for Redirects (301, ...) as they MAY have - * a content. Browsers are waiting for a Content otherwise - */ - http_header_response_set(r, HTTP_HEADER_CONTENT_LENGTH, - CONST_STR_LEN("Content-Length"), - CONST_STR_LEN("0")); - } - } - } else { - /** - * the file isn't finished yet, but we have all headers - * - * to get keep-alive we either need: - * - Content-Length: ... (HTTP/1.0 and HTTP/1.0) or - * - Transfer-Encoding: chunked (HTTP/1.1) - * - Upgrade: ... (lighttpd then acts as transparent proxy) - */ - - if (!(r->resp_htags & (HTTP_HEADER_CONTENT_LENGTH|HTTP_HEADER_TRANSFER_ENCODING|HTTP_HEADER_UPGRADE))) { - if (r->http_method == HTTP_METHOD_CONNECT - && r->http_status == 200) { - /*(no transfer-encoding if successful CONNECT)*/ - } else if (r->http_version == HTTP_VERSION_1_1) { - off_t qlen = chunkqueue_length(r->write_queue); - r->resp_send_chunked = 1; - if (qlen) { - /* create initial Transfer-Encoding: chunked segment */ - buffer * const b = chunkqueue_prepend_buffer_open(r->write_queue); - buffer_append_uint_hex(b, (uintmax_t)qlen); - buffer_append_string_len(b, CONST_STR_LEN("\r\n")); - chunkqueue_prepend_buffer_commit(r->write_queue); - chunkqueue_append_mem(r->write_queue, CONST_STR_LEN("\r\n")); - } - http_header_response_append(r, HTTP_HEADER_TRANSFER_ENCODING, CONST_STR_LEN("Transfer-Encoding"), CONST_STR_LEN("chunked")); - } else { - r->keep_alive = 0; - } - } - } - - if (r->http_method == HTTP_METHOD_HEAD) { - /** - * a HEAD request has the same as a GET - * without the content - */ - http_response_body_clear(r, 1); - r->resp_body_finished = 1; - } - - http_response_write_header(r); - - return 0; -} - - static off_t connection_write_throttle (connection * const con, off_t max_bytes) { @@ -1210,118 +946,6 @@ connection *connection_accepted(server *srv, server_socket *srv_socket, sock_add } -static int connection_handle_request(request_st * const r) { - const plugin *p = r->handler_module; - int rc; - if (NULL != p - || ((rc = http_response_prepare(r)) == HANDLER_GO_ON - && NULL != (p = r->handler_module))) - rc = p->handle_subrequest(r, p->data); - switch (rc) { - case HANDLER_WAIT_FOR_EVENT: - if (!r->resp_body_finished && (!r->resp_body_started || 0 == r->conf.stream_response_body)) { - break; /* come back here */ - } - /* response headers received from backend; fall through to start response */ - /* fall through */ - case HANDLER_GO_ON: /*(HANDLER_FINISHED if request not handled)*/ - case HANDLER_FINISHED: - if (r->http_status == 0) r->http_status = 200; - if (r->error_handler_saved_status > 0) { - r->http_method = r->error_handler_saved_method; - } - if (NULL == r->handler_module || r->conf.error_intercept) { - if (r->error_handler_saved_status) { - const int subreq_status = r->http_status; - if (r->error_handler_saved_status > 0) { - r->http_status = r->error_handler_saved_status; - } else if (r->http_status == 404 || r->http_status == 403) { - /* error-handler-404 is a 404 */ - r->http_status = -r->error_handler_saved_status; - } else { - /* error-handler-404 is back and has generated content */ - /* if Status: was set, take it otherwise use 200 */ - } - if (200 <= subreq_status && subreq_status <= 299) { - /*(flag value to indicate that error handler succeeded) - *(for (NULL == r->handler_module))*/ - r->error_handler_saved_status = 65535; /* >= 1000 */ - } - } else if (r->http_status >= 400) { - const buffer *error_handler = NULL; - if (!buffer_string_is_empty(r->conf.error_handler)) { - error_handler = r->conf.error_handler; - } else if ((r->http_status == 404 || r->http_status == 403) - && !buffer_string_is_empty(r->conf.error_handler_404)) { - error_handler = r->conf.error_handler_404; - } - - if (error_handler) { - /* call error-handler */ - - /* set REDIRECT_STATUS to save current HTTP status code - * for access by dynamic handlers - * https://redmine.lighttpd.net/issues/1828 */ - buffer * const tb = r->tmp_buf; - buffer_clear(tb); - buffer_append_int(tb, r->http_status); - http_header_env_set(r, CONST_STR_LEN("REDIRECT_STATUS"), CONST_BUF_LEN(tb)); - - if (error_handler == r->conf.error_handler) { - plugins_call_handle_request_reset(r); - - if (r->reqbody_length) { - if (r->reqbody_length != r->reqbody_queue->bytes_in) { - r->keep_alive = 0; - } - r->reqbody_length = 0; - chunkqueue_reset(r->reqbody_queue); - } - - r->con->is_writable = 1; - r->resp_body_finished = 0; - r->resp_body_started = 0; - - r->error_handler_saved_status = r->http_status; - r->error_handler_saved_method = r->http_method; - - r->http_method = HTTP_METHOD_GET; - } else { /*(preserve behavior for server.error-handler-404)*/ - r->error_handler_saved_status = -r->http_status; /*(negative to flag old behavior)*/ - } - - if (r->http_version == HTTP_VERSION_UNSET) r->http_version = HTTP_VERSION_1_0; - - buffer_copy_buffer(&r->target, error_handler); - connection_handle_errdoc_init(r); - r->http_status = 0; /*(after connection_handle_errdoc_init())*/ - http_response_comeback(r); - return 1; - } - } - } - - /* we have something to send, go on */ - connection_set_state(r, CON_STATE_RESPONSE_START); - break; - case HANDLER_WAIT_FOR_FD: - connection_fdwaitqueue_append(r->con); - break; - case HANDLER_COMEBACK: - http_response_comeback(r); - return 1; - /*case HANDLER_ERROR:*/ - default: - /* something went wrong */ - connection_set_state(r, CON_STATE_ERROR); - break; - } - - return 0; -} - - - static void connection_state_machine_loop (request_st * const r, connection * const con) { @@ -1355,24 +979,27 @@ connection_state_machine_loop (request_st * const r, connection * const con) /* fall through */ case CON_STATE_READ_POST: case CON_STATE_HANDLE_REQUEST: - if (connection_handle_request(r)) { + switch (http_response_handler(r)) { + case HANDLER_GO_ON:/*CON_STATE_RESPONSE_START occurred;transient*/ + case HANDLER_FINISHED: + break; + case HANDLER_WAIT_FOR_EVENT: + return; + case HANDLER_COMEBACK: /* redo loop; will not match r->state */ ostate = CON_STATE_CONNECT; - break; - } - - if (r->state == CON_STATE_HANDLE_REQUEST - && ostate == CON_STATE_READ_POST) { - ostate = CON_STATE_HANDLE_REQUEST; - } - - if (r->state != CON_STATE_RESPONSE_START) break; - /* fall through */ - case CON_STATE_RESPONSE_START: /* transient */ - if (-1 == connection_handle_write_prepare(r)) { + continue; + case HANDLER_WAIT_FOR_FD: + connection_fdwaitqueue_append(con); + return; + /*case HANDLER_ERROR:*/ + default: connection_set_state(r, CON_STATE_ERROR); - break; + continue; } + /* fall through */ + /*case CON_STATE_RESPONSE_START:*//*occurred;transient*/ + http_response_write_header(r); connection_set_state(r, CON_STATE_WRITE); /* fall through */ case CON_STATE_WRITE: diff --git a/src/response.c b/src/response.c index e5bc2d16..0d0cb6d2 100644 --- a/src/response.c +++ b/src/response.c @@ -9,6 +9,7 @@ #include "log.h" #include "stat_cache.h" #include "chunk.h" +#include "http_chunk.h" #include "plugin.h" @@ -45,7 +46,10 @@ static int http_response_omit_header(request_st * const r, const data_string * c return 0; } -int http_response_write_header(request_st * const r) { + +void +http_response_write_header (request_st * const r) +{ chunkqueue * const cq = r->write_queue; buffer * const b = chunkqueue_prepend_buffer_open(cq); @@ -153,9 +157,9 @@ int http_response_write_header(request_st * const r) { } chunkqueue_prepend_buffer_commit(cq); - return 0; } + static handler_t http_response_physical_path_check(request_st * const r) { stat_cache_entry *sce = stat_cache_get_entry(&r->physical.path); @@ -340,7 +344,14 @@ static handler_t http_response_config (request_st * const r) { return HANDLER_GO_ON; } -handler_t http_response_prepare(request_st * const r) { + +__attribute_cold__ +static handler_t http_response_comeback (request_st * const r); + + +static handler_t +http_response_prepare (request_st * const r) +{ handler_t rc; do { @@ -617,7 +628,9 @@ handler_t http_response_prepare(request_st * const r) { return rc; } -handler_t http_response_comeback (request_st * const r) + +__attribute_cold__ +static handler_t http_response_comeback (request_st * const r) { if (NULL != r->handler_module || !buffer_is_empty(&r->physical.path)) return HANDLER_GO_ON; @@ -646,3 +659,396 @@ handler_t http_response_comeback (request_st * const r) return http_status_set_error_close(r, status); } } + + +__attribute_cold__ +static void +http_response_errdoc_init (request_st * const r) +{ + /* modules that produce headers required with error response should + * typically also produce an error document. Make an exception for + * mod_auth WWW-Authenticate response header. */ + buffer *www_auth = NULL; + if (401 == r->http_status) { + const buffer * const vb = + http_header_response_get(r, HTTP_HEADER_OTHER, + CONST_STR_LEN("WWW-Authenticate")); + if (NULL != vb) www_auth = buffer_init_buffer(vb); + } + + buffer_reset(&r->physical.path); + r->resp_htags = 0; + array_reset_data_strings(&r->resp_headers); + http_response_body_clear(r, 0); + + if (NULL != www_auth) { + http_header_response_set(r, HTTP_HEADER_OTHER, + CONST_STR_LEN("WWW-Authenticate"), + CONST_BUF_LEN(www_auth)); + buffer_free(www_auth); + } +} + + +__attribute_cold__ +static void +http_response_static_errdoc (request_st * const r) +{ + if (NULL == r->handler_module + ? r->error_handler_saved_status >= 65535 + : (!r->conf.error_intercept || r->error_handler_saved_status)) + return; + + http_response_errdoc_init(r); + r->resp_body_finished = 1; + + /* try to send static errorfile */ + if (!buffer_string_is_empty(r->conf.errorfile_prefix)) { + buffer_copy_buffer(&r->physical.path, r->conf.errorfile_prefix); + buffer_append_int(&r->physical.path, r->http_status); + buffer_append_string_len(&r->physical.path, CONST_STR_LEN(".html")); + if (0 == http_chunk_append_file(r, &r->physical.path)) { + stat_cache_entry *sce = stat_cache_get_entry(&r->physical.path); + const buffer *content_type = (NULL != sce) + ? stat_cache_content_type_get(sce, r) + : NULL; + if (content_type) + http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, + CONST_STR_LEN("Content-Type"), + CONST_BUF_LEN(content_type)); + return; + } + } + + /* build default error-page */ + buffer_reset(&r->physical.path); + buffer * const b = r->tmp_buf; + buffer_copy_string_len(b, CONST_STR_LEN( + "\n" + "\n" + "\n" + " \n" + " ")); + http_status_append(b, r->http_status); + buffer_append_string_len(b, CONST_STR_LEN( + "\n" + " \n" + " \n" + "

")); + http_status_append(b, r->http_status); + buffer_append_string_len(b, CONST_STR_LEN( + "

\n" + " \n" + "\n")); + (void)http_chunk_append_mem(r, CONST_BUF_LEN(b)); + + http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, + CONST_STR_LEN("Content-Type"), + CONST_STR_LEN("text/html")); +} + + +__attribute_cold__ +static void +http_response_merge_trailers (request_st * const r) +{ + /* attempt to merge trailers into headers; header not yet sent by caller */ + if (buffer_string_is_empty(&r->gw_dechunk->b)) return; + const int done = r->gw_dechunk->done; + if (!done) return; /* XXX: !done; could scan for '\n' and send only those */ + + /* do not include trailers if success status (when response was read from + * backend) subsequently changed to error status. http_chunk could add the + * trailers, but such actions are better on a different code layer than in + * http_chunk.c */ + if (done < 400 && r->http_status >= 400) return; + + /* XXX: trailers passed through; no sanity check currently done + * https://tools.ietf.org/html/rfc7230#section-4.1.2 + * + * Not checking for disallowed fields + * Not handling (deprecated) line wrapping + * Not strictly checking fields + */ + const char *k = strchr(r->gw_dechunk->b.ptr, '\n'); /*(skip final chunk)*/ + if (NULL == k) return; /*(should not happen)*/ + ++k; + for (const char *v, *e; (e = strchr(k, '\n')); k = e+1) { + v = memchr(k, ':', (size_t)(e - k)); + if (NULL == v || v == k || *k == ' ' || *k == '\t') continue; + uint32_t klen = (uint32_t)(v - k); + do { ++v; } while (*v == ' ' || *v == '\t'); + if (*v == '\r' || *v == '\n') continue; + enum http_header_e id = http_header_hkey_get(k, klen); + http_header_response_insert(r, id, k, klen, v, (size_t)(e - v)); + } + http_header_response_unset(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Trailer")); + buffer_clear(&r->gw_dechunk->b); +} + + +static handler_t +http_response_write_prepare(request_st * const r) +{ + if (NULL == r->handler_module) { + /* static files */ + switch(r->http_method) { + case HTTP_METHOD_GET: + case HTTP_METHOD_POST: + case HTTP_METHOD_HEAD: + break; + case HTTP_METHOD_OPTIONS: + if ((!r->http_status || r->http_status == 200) + && !buffer_string_is_empty(&r->uri.path) + && r->uri.path.ptr[0] != '*') { + http_response_body_clear(r, 0); + http_header_response_append(r, HTTP_HEADER_OTHER, + CONST_STR_LEN("Allow"), + CONST_STR_LEN("OPTIONS, GET, HEAD, POST")); + r->http_status = 200; + r->resp_body_finished = 1; + } + break; + default: + if (r->http_status == 0) + r->http_status = 501; + break; + } + } + + if (r->http_status == 0) + r->http_status = 403; + + switch (r->http_status) { + case 204: /* class: header only */ + case 205: + case 304: + /* disable chunked encoding again as we have no body */ + http_response_body_clear(r, 1); + r->resp_body_finished = 1; + break; + default: /* class: header + body */ + /* only custom body for 4xx and 5xx */ + if (r->http_status >= 400 && r->http_status < 600) + http_response_static_errdoc(r); + break; + } + + if (r->gw_dechunk) + http_response_merge_trailers(r); + + /* Allow filter plugins to change response headers */ + switch (plugins_call_handle_response_start(r)) { + case HANDLER_GO_ON: + case HANDLER_FINISHED: + break; + default: + log_error(r->conf.errh, __FILE__, __LINE__, + "response_start plugin failed"); + return HANDLER_ERROR; + } + + if (r->resp_body_finished) { + /* set content-length if length is known and not already set */ + if (!(r->resp_htags + & (HTTP_HEADER_CONTENT_LENGTH | HTTP_HEADER_TRANSFER_ENCODING))) { + off_t qlen = chunkqueue_length(r->write_queue); + + /** + * The Content-Length header can only be sent if we have content: + * - HEAD does not have a content-body (but can have content-length) + * - 1xx, 204 and 304 does not have a content-body + * (RFC 2616 Section 4.3) + * + * Otherwise generate a Content-Length header + * (if chunked encoding is not available) + */ + if ( r->http_status < 200 + || r->http_status == 204 + || r->http_status == 304) { + /* no Content-Body, no Content-Length */ + http_header_response_unset(r, HTTP_HEADER_CONTENT_LENGTH, + CONST_STR_LEN("Content-Length")); + } + else if (qlen > 0) { + buffer * const tb = r->tmp_buf; + buffer_clear(tb); + buffer_append_int(tb, qlen); + http_header_response_set(r, HTTP_HEADER_CONTENT_LENGTH, + CONST_STR_LEN("Content-Length"), + CONST_BUF_LEN(tb)); + } + else if (r->http_method != HTTP_METHOD_HEAD) { + /* Content-Length: 0 is important for Redirects (301, ...) as + * there might be content. */ + http_header_response_set(r, HTTP_HEADER_CONTENT_LENGTH, + CONST_STR_LEN("Content-Length"), + CONST_STR_LEN("0")); + } + } + } + else { + /** + * response is not yet finished, but we have all headers + * + * keep-alive requires one of: + * - Content-Length: ... (HTTP/1.0 and HTTP/1.0) + * - Transfer-Encoding: chunked (HTTP/1.1) + * - Upgrade: ... (lighttpd then acts as transparent proxy) + */ + + if (!(r->resp_htags + & (HTTP_HEADER_CONTENT_LENGTH + |HTTP_HEADER_TRANSFER_ENCODING + |HTTP_HEADER_UPGRADE))) { + if (r->http_method == HTTP_METHOD_CONNECT && r->http_status == 200){ + /*(no transfer-encoding if successful CONNECT)*/ + } + else if (r->http_version == HTTP_VERSION_1_1) { + off_t qlen = chunkqueue_length(r->write_queue); + r->resp_send_chunked = 1; + if (qlen) { + /* create initial Transfer-Encoding: chunked segment */ + buffer * const b = + chunkqueue_prepend_buffer_open(r->write_queue); + buffer_append_uint_hex(b, (uintmax_t)qlen); + buffer_append_string_len(b, CONST_STR_LEN("\r\n")); + chunkqueue_prepend_buffer_commit(r->write_queue); + chunkqueue_append_mem(r->write_queue,CONST_STR_LEN("\r\n")); + } + http_header_response_append(r, HTTP_HEADER_TRANSFER_ENCODING, + CONST_STR_LEN("Transfer-Encoding"), + CONST_STR_LEN("chunked")); + } + else { + r->keep_alive = 0; + } + } + } + + if (r->http_method == HTTP_METHOD_HEAD) { + /* HEAD request is like a GET, but without the content */ + http_response_body_clear(r, 1); + r->resp_body_finished = 1; + } + + return HANDLER_GO_ON; +} + + +__attribute_cold__ +static handler_t +http_response_call_error_handler (request_st * const r, const buffer * const error_handler) +{ + /* call error-handler */ + + /* set REDIRECT_STATUS to save current HTTP status code + * for access by dynamic handlers + * https://redmine.lighttpd.net/issues/1828 */ + buffer * const tb = r->tmp_buf; + buffer_clear(tb); + buffer_append_int(tb, r->http_status); + http_header_env_set(r, CONST_STR_LEN("REDIRECT_STATUS"), CONST_BUF_LEN(tb)); + + if (error_handler == r->conf.error_handler) { + plugins_call_handle_request_reset(r); + + if (r->reqbody_length) { + if (r->reqbody_length != r->reqbody_queue->bytes_in) + r->keep_alive = 0; + r->reqbody_length = 0; + chunkqueue_reset(r->reqbody_queue); + } + + r->con->is_writable = 1; + r->resp_body_finished = 0; + r->resp_body_started = 0; + + r->error_handler_saved_status = r->http_status; + r->error_handler_saved_method = r->http_method; + + r->http_method = HTTP_METHOD_GET; + } + else { /*(preserve behavior for server.error-handler-404)*/ + /*(negative to flag old behavior)*/ + r->error_handler_saved_status = -r->http_status; + } + + if (r->http_version == HTTP_VERSION_UNSET) + r->http_version = HTTP_VERSION_1_0; + + buffer_copy_buffer(&r->target, error_handler); + http_response_errdoc_init(r); + r->http_status = 0; /*(after http_response_errdoc_init())*/ + http_response_comeback(r); + return HANDLER_COMEBACK; +} + + +handler_t +http_response_handler (request_st * const r) +{ + const plugin *p = r->handler_module; + int rc; + if (NULL != p + || ((rc = http_response_prepare(r)) == HANDLER_GO_ON + && NULL != (p = r->handler_module))) + rc = p->handle_subrequest(r, p->data); + + switch (rc) { + case HANDLER_WAIT_FOR_EVENT: + if (!r->resp_body_finished + && (!r->resp_body_started || 0 == r->conf.stream_response_body)) + return HANDLER_WAIT_FOR_EVENT; /* come back here */ + /* response headers received from backend; start response */ + __attribute_fallthrough__ + case HANDLER_GO_ON: + case HANDLER_FINISHED: /*(HANDLER_FINISHED if request not handled)*/ + if (r->http_status == 0) r->http_status = 200; + if (r->error_handler_saved_status > 0) + r->http_method = r->error_handler_saved_method; + if (NULL == r->handler_module || r->conf.error_intercept) { + if (r->error_handler_saved_status) { + const int subreq_status = r->http_status; + if (r->error_handler_saved_status > 0) + r->http_status = r->error_handler_saved_status; + else if (r->http_status == 404 || r->http_status == 403) + /* error-handler-404 is a 404 */ + r->http_status = -r->error_handler_saved_status; + else { + /* error-handler-404 is back and has generated content */ + /* if Status: was set, take it otherwise use 200 */ + } + if (200 <= subreq_status && subreq_status <= 299) { + /*(flag value to indicate that error handler succeeded) + *(for (NULL == r->handler_module))*/ + r->error_handler_saved_status = 65535; /* >= 1000 */ + } + } + else if (r->http_status >= 400) { + const buffer *error_handler = NULL; + if (!buffer_string_is_empty(r->conf.error_handler)) + error_handler = r->conf.error_handler; + else if ((r->http_status == 404 || r->http_status == 403) + && !buffer_string_is_empty(r->conf.error_handler_404)) + error_handler = r->conf.error_handler_404; + + if (error_handler) + return http_response_call_error_handler(r, error_handler); + } + } + + /* we have something to send; go on */ + /*(CON_STATE_RESPONSE_START; transient state)*/ + return http_response_write_prepare(r); + case HANDLER_WAIT_FOR_FD: + return HANDLER_WAIT_FOR_FD; + case HANDLER_COMEBACK: + http_response_comeback(r); + return HANDLER_COMEBACK; + /*case HANDLER_ERROR:*/ + default: + return HANDLER_ERROR; /* something went wrong */ + } +} diff --git a/src/response.h b/src/response.h index aee5ebca..72832975 100644 --- a/src/response.h +++ b/src/response.h @@ -9,7 +9,6 @@ #include int http_response_parse(server *srv, request_st *r); -int http_response_write_header(request_st *r); typedef struct http_cgi_opts_t { int authorizer; @@ -43,10 +42,6 @@ int http_cgi_headers(request_st *r, http_cgi_opts *opts, http_cgi_header_append_ handler_t http_response_parse_headers(request_st *r, http_response_opts *opts, buffer *hdrs); handler_t http_response_read(request_st *r, http_response_opts *opts, buffer *b, fdnode *fdn); -handler_t http_response_prepare(request_st *r); - -__attribute_cold__ -handler_t http_response_comeback(request_st *r); __attribute_cold__ handler_t http_response_reqbody_read_error(request_st *r, int http_status); @@ -59,6 +54,8 @@ void http_response_send_file (request_st *r, buffer *path); void http_response_backend_done (request_st *r); void http_response_backend_error (request_st *r); void http_response_upgrade_read_body_unknown(request_st *r); +void http_response_write_header(request_st *r); +handler_t http_response_handler(request_st *r); __attribute_cold__ void strftime_cache_reset(void);