int http_chunk_append_mem(server *srv, connection *con, const char * mem, size_t len) { chunkqueue *cq; if (!con) return -1; cq = con->write_queue; if (len == 0) { if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { chunkqueue_append_mem(cq, "0\r\n\r\n", 5 + 1); } else { chunkqueue_append_mem(cq, "", 1); } return 0; } if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { http_chunk_append_len(srv, con, len - 1); } chunkqueue_append_mem(cq, mem, len); if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { chunkqueue_append_mem(cq, "\r\n", 2 + 1); } return 0; }
int http_chunk_append_file(server * srv, connection * con, buffer * fn, off_t offset, off_t len) { chunkqueue *cq; if (!con) return -1; cq = con->write_queue; if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { http_chunk_append_len(srv, con, len); } chunkqueue_append_file(cq, fn, offset, len); if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED && len > 0) { chunkqueue_append_mem(cq, "\r\n", 2 + 1); } return 0; }
void http_chunk_close(server *srv, connection *con) { UNUSED(srv); force_assert(NULL != con); if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("0\r\n\r\n")); } }
void chunkqueue_steal(chunkqueue *dest, chunkqueue *src, off_t len) { while (len > 0) { chunk *c = src->first; off_t clen = 0, use; if (NULL == c) break; switch (c->type) { case MEM_CHUNK: clen = buffer_string_length(c->mem); break; case FILE_CHUNK: clen = c->file.length; break; } force_assert(clen >= c->offset); clen -= c->offset; use = len >= clen ? clen : len; src->bytes_out += use; dest->bytes_in += use; len -= use; if (0 == clen) { /* drop empty chunk */ src->first = c->next; if (c == src->last) src->last = NULL; chunkqueue_push_unused_chunk(src, c); continue; } if (use == clen) { /* move complete chunk */ src->first = c->next; if (c == src->last) src->last = NULL; chunkqueue_append_chunk(dest, c); continue; } /* partial chunk with length "use" */ switch (c->type) { case MEM_CHUNK: chunkqueue_append_mem(dest, c->mem->ptr + c->offset, use); break; case FILE_CHUNK: /* tempfile flag is in "last" chunk after the split */ chunkqueue_append_file(dest, c->file.name, c->file.start + c->offset, use); break; } c->offset += use; force_assert(0 == len); } }
static void http_chunk_append_file_fd_range(server *srv, connection *con, buffer *fn, int fd, off_t offset, off_t len) { chunkqueue *cq = con->write_queue; if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { http_chunk_append_len(srv, con, (uintmax_t)len); } chunkqueue_append_file_fd(cq, fn, fd, offset, len); if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { chunkqueue_append_mem(cq, CONST_STR_LEN("\r\n")); } }
static int http_chunk_append_data(server *srv, connection *con, buffer *b, const char * mem, size_t len) { chunkqueue * const cq = con->write_queue; chunk *c = cq->last; if (0 == len) return 0; /* current usage does not append_mem or append_buffer after appending * file, so not checking if users of this interface have appended large * (references to) files to chunkqueue, which would not be in memory */ /*(allow slightly larger mem use if FDEVENT_STREAM_RESPONSE_BUFMIN * to reduce creation of temp files when backend producer will be * blocked until more data is sent to network to client)*/ if ((c && c->type == FILE_CHUNK && c->file.is_temp) || cq->bytes_in - cq->bytes_out + len > 1024 * ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) ? 128 : 64)) { return http_chunk_append_to_tempfile(srv, con, b ? b->ptr : mem, len); } /* not appending to prior mem chunk just in case using openssl * and need to resubmit same args as prior call to openssl (required?)*/ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { http_chunk_append_len(srv, con, len); } /*(chunkqueue_append_buffer() might steal buffer contents)*/ b ? chunkqueue_append_buffer(cq, b) : chunkqueue_append_mem(cq, mem, len); if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { chunkqueue_append_mem(cq, CONST_STR_LEN("\r\n")); } return 0; }
int http_chunk_append_buffer(server *srv, connection *con, buffer *mem) { chunkqueue *cq; if (!con) return -1; cq = con->write_queue; if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { http_chunk_append_len(srv, con, mem->used - 1); } chunkqueue_append_buffer(cq, mem); if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED && mem->used > 0) { chunkqueue_append_mem(cq, "\r\n", 2 + 1); } return 0; }
int http_chunk_append_file(server *srv, connection *con, buffer *fn, off_t offset, off_t len) { chunkqueue *cq; if (!con) return -1; Cdbg(DBE, "enter http_chunk_append_file..with fd=[%d], fn=[%s], offset=[%d], len=[%d]", con->fd, fn->ptr, offset, len); cq = con->write_queue; if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { http_chunk_append_len(srv, con, len); } chunkqueue_append_file(cq, fn, offset, len); if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED && len > 0) { chunkqueue_append_mem(cq, "\r\n", 2 + 1); } Cdbg(DBE, "leave"); return 0; }
/** * move the content of chunk to another chunkqueue. return total bytes copied/stolen. */ off_t chunkqueue_steal_chunk(chunkqueue *cq, chunk *c) { /* we are copying the whole buffer, just steal it */ off_t total = 0; buffer *b, btmp; if (!cq) return 0; if (chunk_is_done(c)) return 0; switch (c->type) { case MEM_CHUNK: total = c->mem->used - c->offset - 1; if (c->offset == 0) { b = chunkqueue_get_append_buffer(cq); btmp = *b; *b = *(c->mem); *(c->mem) = btmp; } else { chunkqueue_append_mem(cq, c->mem->ptr + c->offset, total); chunk_set_done(c); } break; case FILE_CHUNK: total = c->file.length - c->offset; if (c->file.is_temp) { chunkqueue_steal_tempfile(cq, c); } else { chunkqueue_append_file(cq, c->file.name, c->file.start + c->offset, c->file.length - c->offset); chunk_set_done(c); } break; case UNUSED_CHUNK: return 0; } return total; }
/** * walk through the content array * * content = { "<pre>", { file = "/content" } , "</pre>" } * * header["Content-Type"] = "text/html" * * return 200 */ static int magnet_attach_content(server *srv, connection *con, lua_State *L, int lighty_table_ndx) { force_assert(lua_istable(L, lighty_table_ndx)); lua_getfield(L, lighty_table_ndx, "content"); /* lighty.content */ if (lua_istable(L, -1)) { int i; /* content is found, and is a table */ for (i = 1; ; i++) { lua_rawgeti(L, -1, i); /* -1 is the value and should be the value ... aka a table */ if (lua_isstring(L, -1)) { const_buffer data = magnet_checkconstbuffer(L, -1); chunkqueue_append_mem(con->write_queue, data.ptr, data.len); } else if (lua_istable(L, -1)) { lua_getfield(L, -1, "filename"); lua_getfield(L, -2, "length"); lua_getfield(L, -3, "offset"); if (lua_isstring(L, -3)) { /* filename has to be a string */ buffer *fn; stat_cache_entry *sce; handler_t res; fn = magnet_checkbuffer(L, -3); res = stat_cache_get_entry(srv, con, fn, &sce); if (HANDLER_GO_ON == res) { off_t off = (off_t) luaL_optinteger(L, -1, 0); off_t len = (off_t) luaL_optinteger(L, -2, (lua_Integer) sce->st.st_size); if (off < 0) { buffer_free(fn); return luaL_error(L, "offset for '%s' is negative", lua_tostring(L, -3)); } if (len < off) { buffer_free(fn); return luaL_error(L, "offset > length for '%s'", lua_tostring(L, -3)); } chunkqueue_append_file(con->write_queue, fn, off, len - off); } buffer_free(fn); } else { return luaL_error(L, "content[%d] is a table and requires the field \"filename\"", i); } lua_pop(L, 3); } else if (lua_isnil(L, -1)) { /* end of list */ lua_pop(L, 1); break; } else { return luaL_error(L, "content[%d] is neither a string nor a table: ", i); } lua_pop(L, 1); /* pop the content[...] entry value */ } } else { return luaL_error(L, "lighty.content has to be a table"); } lua_pop(L, 1); /* pop lighty.content */ return 0; }
/** * walk through the content array * * content = { "<pre>", { file = "/content" } , "</pre>" } * * header["Content-Type"] = "text/html" * * return 200 */ static int magnet_attach_content(server *srv, connection *con, plugin_data *p, lua_State *L) { UNUSED(p); /** * get the environment of the function */ assert(lua_isfunction(L, -1)); lua_getfenv(L, -1); /* -1 is the function */ lua_getfield(L, -1, "lighty"); /* lighty.* from the env */ assert(lua_istable(L, -1)); lua_getfield(L, -1, "content"); /* lighty.content */ if (lua_istable(L, -1)) { int i; /* header is found, and is a table */ for (i = 1; ; i++) { lua_rawgeti(L, -1, i); /* -1 is the value and should be the value ... aka a table */ if (lua_isstring(L, -1)) { size_t s_len = 0; const char *s = lua_tolstring(L, -1, &s_len); chunkqueue_append_mem(con->write_queue, s, s_len + 1); } else if (lua_istable(L, -1)) { lua_getfield(L, -1, "filename"); lua_getfield(L, -2, "length"); lua_getfield(L, -3, "offset"); if (lua_isstring(L, -3)) { /* filename has to be a string */ buffer *fn = buffer_init(); stat_cache_entry *sce; buffer_copy_string(fn, lua_tostring(L, -3)); if (HANDLER_GO_ON == stat_cache_get_entry(srv, con, fn, &sce)) { off_t off = 0; off_t len = 0; if (lua_isnumber(L, -1)) { off = lua_tonumber(L, -1); } if (lua_isnumber(L, -2)) { len = lua_tonumber(L, -2); } else { len = sce->st.st_size; } if (off < 0) { return luaL_error(L, "offset for '%s' is negative", fn->ptr); } if (len < off) { return luaL_error(L, "offset > length for '%s'", fn->ptr); } chunkqueue_append_file(con->write_queue, fn, off, len - off); } buffer_free(fn); } else { lua_pop(L, 3 + 2); /* correct the stack */ return luaL_error(L, "content[%d] is a table and requires the field \"filename\"", i); } lua_pop(L, 3); } else if (lua_isnil(L, -1)) { /* oops, end of list */ lua_pop(L, 1); break; } else { lua_pop(L, 4); return luaL_error(L, "content[%d] is neither a string nor a table: ", i); } lua_pop(L, 1); /* pop the content[...] table */ } } else { return luaL_error(L, "lighty.content has to be a table"); } lua_pop(L, 1); /* pop the header-table */ lua_pop(L, 1); /* pop the lighty-table */ lua_pop(L, 1); /* php the function env */ return 0; }
static int connection_handle_write_prepare(server *srv, connection *con) { if (con->mode == DIRECT) { /* static files */ switch(con->request.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 noone else catched it * * */ if ((!con->http_status || con->http_status == 200) && !buffer_string_is_empty(con->uri.path) && con->uri.path->ptr[0] != '*') { response_header_insert(srv, con, CONST_STR_LEN("Allow"), CONST_STR_LEN("OPTIONS, GET, HEAD, POST")); con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED; con->parsed_response &= ~HTTP_CONTENT_LENGTH; con->http_status = 200; con->file_finished = 1; chunkqueue_reset(con->write_queue); } break; default: if (0 == con->http_status) { con->http_status = 501; } break; } } if (con->http_status == 0) { con->http_status = 403; } switch(con->http_status) { case 204: /* class: header only */ case 205: case 304: /* disable chunked encoding again as we have no body */ con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED; con->parsed_response &= ~HTTP_CONTENT_LENGTH; chunkqueue_reset(con->write_queue); con->file_finished = 1; break; default: /* class: header + body */ if (con->mode != DIRECT) break; /* only custom body for 4xx and 5xx */ if (con->http_status < 400 || con->http_status >= 600) break; con->file_finished = 0; connection_handle_errdoc_init(srv, con); /* try to send static errorfile */ if (!buffer_string_is_empty(con->conf.errorfile_prefix)) { stat_cache_entry *sce = NULL; buffer_copy_buffer(con->physical.path, con->conf.errorfile_prefix); buffer_append_int(con->physical.path, con->http_status); buffer_append_string_len(con->physical.path, CONST_STR_LEN(".html")); if (0 == http_chunk_append_file(srv, con, con->physical.path)) { con->file_finished = 1; if (HANDLER_ERROR != stat_cache_get_entry(srv, con, con->physical.path, &sce)) { response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type)); } } } if (!con->file_finished) { buffer *b; buffer_reset(con->physical.path); con->file_finished = 1; b = buffer_init(); /* build default error-page */ buffer_copy_string_len(b, CONST_STR_LEN( "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n" "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" " <head>\n" " <title>")); buffer_append_int(b, con->http_status); buffer_append_string_len(b, CONST_STR_LEN(" - ")); buffer_append_string(b, get_http_status_name(con->http_status)); buffer_append_string_len(b, CONST_STR_LEN( "</title>\n" " </head>\n" " <body>\n" " <h1>")); buffer_append_int(b, con->http_status); buffer_append_string_len(b, CONST_STR_LEN(" - ")); buffer_append_string(b, get_http_status_name(con->http_status)); buffer_append_string_len(b, CONST_STR_LEN("</h1>\n" " </body>\n" "</html>\n" )); (void)http_chunk_append_buffer(srv, con, b); buffer_free(b); response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); } break; } /* Allow filter plugins to change response headers before they are written. */ switch(plugins_call_handle_response_start(srv, con)) { case HANDLER_GO_ON: case HANDLER_FINISHED: break; default: log_error_write(srv, __FILE__, __LINE__, "s", "response_start plugin failed"); return -1; } if (con->file_finished) { /* we have all the content and chunked encoding is not used, set a content-length */ if ((!(con->parsed_response & HTTP_CONTENT_LENGTH)) && (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0) { off_t qlen = chunkqueue_length(con->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 ((con->http_status >= 100 && con->http_status < 200) || con->http_status == 204 || con->http_status == 304) { data_string *ds; /* no Content-Body, no Content-Length */ if (NULL != (ds = (data_string*) array_get_element(con->response.headers, "Content-Length"))) { buffer_reset(ds->value); /* Headers with empty values are ignored for output */ } } else if (qlen > 0 || con->request.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 */ buffer_copy_int(srv->tmp_buf, qlen); response_header_overwrite(srv, con, CONST_STR_LEN("Content-Length"), CONST_BUF_LEN(srv->tmp_buf)); } } } 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) */ if (((con->parsed_response & HTTP_CONTENT_LENGTH) == 0) && ((con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0)) { if (con->request.http_version == HTTP_VERSION_1_1) { off_t qlen = chunkqueue_length(con->write_queue); con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; if (qlen) { /* create initial Transfer-Encoding: chunked segment */ buffer *b = srv->tmp_chunk_len; buffer_string_set_length(b, 0); buffer_append_uint_hex(b, (uintmax_t)qlen); buffer_append_string_len(b, CONST_STR_LEN("\r\n")); chunkqueue_prepend_buffer(con->write_queue, b); chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("\r\n")); } } else { con->keep_alive = 0; } } /** * if the backend sent a Connection: close, follow the wish * * NOTE: if the backend sent Connection: Keep-Alive, but no Content-Length, we * will close the connection. That's fine. We can always decide the close * the connection * * FIXME: to be nice we should remove the Connection: ... */ if (con->parsed_response & HTTP_CONNECTION) { /* a subrequest disable keep-alive although the client wanted it */ if (con->keep_alive && !con->response.keep_alive) { con->keep_alive = 0; } } } if (con->request.http_method == HTTP_METHOD_HEAD) { /** * a HEAD request has the same as a GET * without the content */ con->file_finished = 1; chunkqueue_reset(con->write_queue); con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED; } http_response_write_header(srv, con); return 0; }