static handler_t mod_status_handle_server_statistics(server *srv, connection *con, void *p_d) { plugin_data *p = p_d; buffer *b = p->module_list; size_t i; array *st = srv->status; if (0 == st->used) { /* we have nothing to send */ con->http_status = 204; con->file_finished = 1; return HANDLER_FINISHED; } b = chunkqueue_get_append_buffer(con->write_queue); for (i = 0; i < st->used; i++) { size_t ndx = st->sorted[i]; buffer_append_string_buffer(b, st->data[ndx]->key); buffer_append_string(b, ": "); buffer_append_long(b, ((data_integer *)(st->data[ndx]))->value); buffer_append_string(b, "\n"); } response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain")); con->http_status = 200; con->file_finished = 1; return HANDLER_FINISHED; }
static handler_t mod_status_handle_server_statistics(server *srv, connection *con, void *p_d) { buffer *b; size_t i; array *st = srv->status; UNUSED(p_d); if (0 == st->used) { /* we have nothing to send */ con->http_status = 204; con->file_finished = 1; return HANDLER_FINISHED; } b = buffer_init(); for (i = 0; i < st->used; i++) { size_t ndx = st->sorted[i]; buffer_append_string_buffer(b, st->data[ndx]->key); buffer_append_string_len(b, CONST_STR_LEN(": ")); buffer_append_int(b, ((data_integer *)(st->data[ndx]))->value); buffer_append_string_len(b, CONST_STR_LEN("\n")); } chunkqueue_append_buffer(con->write_queue, b); buffer_free(b); response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain")); con->http_status = 200; con->file_finished = 1; return HANDLER_FINISHED; }
static handler_t mod_status_handle_server_status_text(server *srv, connection *con, void *p_d) { plugin_data *p = p_d; buffer *b = buffer_init(); double avg; time_t ts; char buf[32]; unsigned int k; unsigned int l; /* output total number of requests */ buffer_append_string_len(b, CONST_STR_LEN("Total Accesses: ")); avg = p->abs_requests; snprintf(buf, sizeof(buf) - 1, "%.0f", avg); buffer_append_string(b, buf); buffer_append_string_len(b, CONST_STR_LEN("\n")); /* output total traffic out in kbytes */ buffer_append_string_len(b, CONST_STR_LEN("Total kBytes: ")); avg = p->abs_traffic_out / 1024; snprintf(buf, sizeof(buf) - 1, "%.0f", avg); buffer_append_string(b, buf); buffer_append_string_len(b, CONST_STR_LEN("\n")); /* output uptime */ buffer_append_string_len(b, CONST_STR_LEN("Uptime: ")); ts = srv->cur_ts - srv->startup_ts; buffer_append_int(b, ts); buffer_append_string_len(b, CONST_STR_LEN("\n")); /* output busy servers */ buffer_append_string_len(b, CONST_STR_LEN("BusyServers: ")); buffer_append_int(b, srv->conns->used); buffer_append_string_len(b, CONST_STR_LEN("\n")); buffer_append_string_len(b, CONST_STR_LEN("IdleServers: ")); buffer_append_int(b, srv->conns->size - srv->conns->used); buffer_append_string_len(b, CONST_STR_LEN("\n")); /* output scoreboard */ buffer_append_string_len(b, CONST_STR_LEN("Scoreboard: ")); for (k = 0; k < srv->conns->used; k++) { connection *c = srv->conns->ptr[k]; const char *state = connection_get_short_state(c->state); buffer_append_string_len(b, state, 1); } for (l = 0; l < srv->conns->size - srv->conns->used; l++) { buffer_append_string_len(b, CONST_STR_LEN("_")); } buffer_append_string_len(b, CONST_STR_LEN("\n")); chunkqueue_append_buffer(con->write_queue, b); buffer_free(b); /* set text/plain output */ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain")); return 0; }
static int magnet_copy_response_header(server * srv, connection * con, plugin_data * p, lua_State * L) { UNUSED(p); /** * get the environment of the function */ lua_getfenv(L, -1); /* -1 is the function */ /* * lighty.header */ lua_getfield(L, -1, "lighty"); /* lighty.* from the env */ assert(lua_istable(L, -1)); lua_getfield(L, -1, "header"); /* lighty.header */ if (lua_istable(L, -1)) { /* * header is found, and is a table */ lua_pushnil(L); while (lua_next(L, -2) != 0) { if (lua_isstring(L, -1) && lua_isstring(L, -2)) { const char *key, *val; size_t key_len, val_len; key = lua_tolstring(L, -2, &key_len); val = lua_tolstring(L, -1, &val_len); response_header_overwrite(srv, con, key, key_len, val, val_len); } lua_pop(L, 1); } } lua_pop(L, 1); /* pop the header-table */ lua_pop(L, 1); /* pop the lighty-env */ lua_pop(L, 1); /* pop the function env */ return 0; }
static handler_t mod_status_handle_server_status_text(server *srv, connection *con, void *p_d) { plugin_data *p = p_d; buffer *b; double avg; time_t ts; char buf[32]; b = chunkqueue_get_append_buffer(con->write_queue); /* output total number of requests */ BUFFER_APPEND_STRING_CONST(b, "Total Accesses: "); avg = p->abs_requests; snprintf(buf, sizeof(buf) - 1, "%.0f", avg); buffer_append_string(b, buf); BUFFER_APPEND_STRING_CONST(b, "\n"); /* output total traffic out in kbytes */ BUFFER_APPEND_STRING_CONST(b, "Total kBytes: "); avg = p->abs_traffic_out / 1024; snprintf(buf, sizeof(buf) - 1, "%.0f", avg); buffer_append_string(b, buf); BUFFER_APPEND_STRING_CONST(b, "\n"); /* output uptime */ BUFFER_APPEND_STRING_CONST(b, "Uptime: "); ts = srv->cur_ts - srv->startup_ts; buffer_append_long(b, ts); BUFFER_APPEND_STRING_CONST(b, "\n"); /* output busy servers */ BUFFER_APPEND_STRING_CONST(b, "BusyServers: "); buffer_append_long(b, srv->conns->used); BUFFER_APPEND_STRING_CONST(b, "\n"); /* set text/plain output */ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain")); return 0; }
static int magnet_copy_response_header(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, "header"); /* lighty.header */ if (lua_istable(L, -1)) { /* header is found, and is a table */ lua_pushnil(L); while (lua_next(L, -2) != 0) { if (lua_isstring(L, -1) && lua_isstring(L, -2)) { const_buffer key = magnet_checkconstbuffer(L, -2); const_buffer val = magnet_checkconstbuffer(L, -1); response_header_overwrite(srv, con, key.ptr, key.len, val.ptr, val.len); } lua_pop(L, 1); } } lua_pop(L, 1); /* pop lighty.header */ return 0; }
static handler_t mod_status_handle_server_config(server *srv, connection *con, void *p_d) { plugin_data *p = p_d; buffer *b, *m = p->module_list; size_t i; struct ev_map { fdevent_handler_t et; const char *name; } event_handlers[] = { /* - poll is most reliable * - select works everywhere * - linux-* are experimental */ #ifdef USE_POLL { FDEVENT_HANDLER_POLL, "poll" }, #endif #ifdef USE_SELECT { FDEVENT_HANDLER_SELECT, "select" }, #endif #ifdef USE_LINUX_EPOLL { FDEVENT_HANDLER_LINUX_SYSEPOLL, "linux-sysepoll" }, #endif #ifdef USE_LINUX_SIGIO { FDEVENT_HANDLER_LINUX_RTSIG, "linux-rtsig" }, #endif #ifdef USE_SOLARIS_DEVPOLL { FDEVENT_HANDLER_SOLARIS_DEVPOLL,"solaris-devpoll" }, #endif #ifdef USE_FREEBSD_KQUEUE { FDEVENT_HANDLER_FREEBSD_KQUEUE, "freebsd-kqueue" }, #endif { FDEVENT_HANDLER_UNSET, NULL } }; b = chunkqueue_get_append_buffer(con->write_queue); BUFFER_COPY_STRING_CONST(b, "<?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>Status</title>\n" " </head>\n" " <body>\n" " <h1>" PACKAGE_NAME " " PACKAGE_VERSION "</h1>\n" " <table summary=\"status\" border=\"1\">\n"); mod_status_header_append(b, "Server-Features"); #ifdef HAVE_PCRE_H mod_status_row_append(b, "RegEx Conditionals", "enabled"); #else mod_status_row_append(b, "RegEx Conditionals", "disabled - pcre missing"); #endif mod_status_header_append(b, "Network Engine"); for (i = 0; event_handlers[i].name; i++) { if (event_handlers[i].et == srv->event_handler) { mod_status_row_append(b, "fd-Event-Handler", event_handlers[i].name); break; } } mod_status_header_append(b, "Config-File-Settings"); for (i = 0; i < srv->plugins.used; i++) { plugin **ps = srv->plugins.ptr; plugin *pl = ps[i]; if (i == 0) { buffer_copy_string_buffer(m, pl->name); } else { BUFFER_APPEND_STRING_CONST(m, "<br />"); buffer_append_string_buffer(m, pl->name); } } mod_status_row_append(b, "Loaded Modules", m->ptr); BUFFER_APPEND_STRING_CONST(b, " </table>\n"); BUFFER_APPEND_STRING_CONST(b, " </body>\n" "</html>\n" ); response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); con->http_status = 200; con->file_finished = 1; return HANDLER_FINISHED; }
static handler_t mod_status_handle_server_status_html(server *srv, connection *con, void *p_d) { plugin_data *p = p_d; buffer *b; size_t j; double avg; char multiplier = '\0'; char buf[32]; time_t ts; int days, hours, mins, seconds; b = chunkqueue_get_append_buffer(con->write_queue); BUFFER_COPY_STRING_CONST(b, "<?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>Status</title>\n"); BUFFER_APPEND_STRING_CONST(b, " <style type=\"text/css\">\n" " table.status { border: black solid thin; }\n" " td.int { background-color: #f0f0f0; text-align: right }\n" " td.string { background-color: #f0f0f0; text-align: left }\n" " th.status { background-color: black; color: white; font-weight: bold; }\n" " a.sortheader { background-color: black; color: white; font-weight: bold; text-decoration: none; display: block; }\n" " span.sortarrow { color: white; text-decoration: none; }\n" " </style>\n"); if (p->conf.sort) { BUFFER_APPEND_STRING_CONST(b, "<script type=\"text/javascript\">\n" "// <!--\n" "var sort_column;\n" "var prev_span = null;\n"); BUFFER_APPEND_STRING_CONST(b, "function get_inner_text(el) {\n" " if((typeof el == 'string')||(typeof el == 'undefined'))\n" " return el;\n" " if(el.innerText)\n" " return el.innerText;\n" " else {\n" " var str = \"\";\n" " var cs = el.childNodes;\n" " var l = cs.length;\n" " for (i=0;i<l;i++) {\n" " if (cs[i].nodeType==1) str += get_inner_text(cs[i]);\n" " else if (cs[i].nodeType==3) str += cs[i].nodeValue;\n" " }\n" " }\n" " return str;\n" "}\n"); BUFFER_APPEND_STRING_CONST(b, "function sortfn(a,b) {\n" " var at = get_inner_text(a.cells[sort_column]);\n" " var bt = get_inner_text(b.cells[sort_column]);\n" " if (a.cells[sort_column].className == 'int') {\n" " return parseInt(at)-parseInt(bt);\n" " } else {\n" " aa = at.toLowerCase();\n" " bb = bt.toLowerCase();\n" " if (aa==bb) return 0;\n" " else if (aa<bb) return -1;\n" " else return 1;\n" " }\n" "}\n"); BUFFER_APPEND_STRING_CONST(b, "function resort(lnk) {\n" " var span = lnk.childNodes[1];\n" " var table = lnk.parentNode.parentNode.parentNode.parentNode;\n" " var rows = new Array();\n" " for (j=1;j<table.rows.length;j++)\n" " rows[j-1] = table.rows[j];\n" " sort_column = lnk.parentNode.cellIndex;\n" " rows.sort(sortfn);\n"); BUFFER_APPEND_STRING_CONST(b, " if (prev_span != null) prev_span.innerHTML = '';\n" " if (span.getAttribute('sortdir')=='down') {\n" " span.innerHTML = '↑';\n" " span.setAttribute('sortdir','up');\n" " rows.reverse();\n" " } else {\n" " span.innerHTML = '↓';\n" " span.setAttribute('sortdir','down');\n" " }\n" " for (i=0;i<rows.length;i++)\n" " table.tBodies[0].appendChild(rows[i]);\n" " prev_span = span;\n" "}\n" "// -->\n" "</script>\n"); } BUFFER_APPEND_STRING_CONST(b, " </head>\n" " <body>\n"); /* connection listing */ BUFFER_APPEND_STRING_CONST(b, "<h1>Server-Status</h1>"); BUFFER_APPEND_STRING_CONST(b, "<table summary=\"status\" class=\"status\">"); BUFFER_APPEND_STRING_CONST(b, "<tr><td>Hostname</td><td class=\"string\">"); buffer_append_string_buffer(b, con->uri.authority); BUFFER_APPEND_STRING_CONST(b, " ("); buffer_append_string_buffer(b, con->server_name); BUFFER_APPEND_STRING_CONST(b, ")</td></tr>\n"); BUFFER_APPEND_STRING_CONST(b, "<tr><td>Uptime</td><td class=\"string\">"); ts = srv->cur_ts - srv->startup_ts; days = ts / (60 * 60 * 24); ts %= (60 * 60 * 24); hours = ts / (60 * 60); ts %= (60 * 60); mins = ts / (60); ts %= (60); seconds = ts; if (days) { buffer_append_long(b, days); BUFFER_APPEND_STRING_CONST(b, " days "); } if (hours) { buffer_append_long(b, hours); BUFFER_APPEND_STRING_CONST(b, " hours "); } if (mins) { buffer_append_long(b, mins); BUFFER_APPEND_STRING_CONST(b, " min "); } buffer_append_long(b, seconds); BUFFER_APPEND_STRING_CONST(b, " s"); BUFFER_APPEND_STRING_CONST(b, "</td></tr>\n"); BUFFER_APPEND_STRING_CONST(b, "<tr><td>Started at</td><td class=\"string\">"); ts = srv->startup_ts; strftime(buf, sizeof(buf) - 1, "%Y-%m-%d %H:%M:%S", localtime(&ts)); buffer_append_string(b, buf); BUFFER_APPEND_STRING_CONST(b, "</td></tr>\n"); BUFFER_APPEND_STRING_CONST(b, "<tr><th colspan=\"2\">absolute (since start)</th></tr>\n"); BUFFER_APPEND_STRING_CONST(b, "<tr><td>Requests</td><td class=\"string\">"); avg = p->abs_requests; mod_status_get_multiplier(&avg, &multiplier, 1000); buffer_append_long(b, avg); BUFFER_APPEND_STRING_CONST(b, " "); if (multiplier) buffer_append_string_len(b, &multiplier, 1); BUFFER_APPEND_STRING_CONST(b, "req</td></tr>\n"); BUFFER_APPEND_STRING_CONST(b, "<tr><td>Traffic</td><td class=\"string\">"); avg = p->abs_traffic_out; mod_status_get_multiplier(&avg, &multiplier, 1024); sprintf(buf, "%.2f", avg); buffer_append_string(b, buf); BUFFER_APPEND_STRING_CONST(b, " "); if (multiplier) buffer_append_string_len(b, &multiplier, 1); BUFFER_APPEND_STRING_CONST(b, "byte</td></tr>\n"); BUFFER_APPEND_STRING_CONST(b, "<tr><th colspan=\"2\">average (since start)</th></tr>\n"); BUFFER_APPEND_STRING_CONST(b, "<tr><td>Requests</td><td class=\"string\">"); avg = p->abs_requests / (srv->cur_ts - srv->startup_ts); mod_status_get_multiplier(&avg, &multiplier, 1000); buffer_append_long(b, avg); BUFFER_APPEND_STRING_CONST(b, " "); if (multiplier) buffer_append_string_len(b, &multiplier, 1); BUFFER_APPEND_STRING_CONST(b, "req/s</td></tr>\n"); BUFFER_APPEND_STRING_CONST(b, "<tr><td>Traffic</td><td class=\"string\">"); avg = p->abs_traffic_out / (srv->cur_ts - srv->startup_ts); mod_status_get_multiplier(&avg, &multiplier, 1024); sprintf(buf, "%.2f", avg); buffer_append_string(b, buf); BUFFER_APPEND_STRING_CONST(b, " "); if (multiplier) buffer_append_string_len(b, &multiplier, 1); BUFFER_APPEND_STRING_CONST(b, "byte/s</td></tr>\n"); BUFFER_APPEND_STRING_CONST(b, "<tr><th colspan=\"2\">average (5s sliding average)</th></tr>\n"); for (j = 0, avg = 0; j < 5; j++) { avg += p->mod_5s_requests[j]; } avg /= 5; BUFFER_APPEND_STRING_CONST(b, "<tr><td>Requests</td><td class=\"string\">"); mod_status_get_multiplier(&avg, &multiplier, 1000); buffer_append_long(b, avg); BUFFER_APPEND_STRING_CONST(b, " "); if (multiplier) buffer_append_string_len(b, &multiplier, 1); BUFFER_APPEND_STRING_CONST(b, "req/s</td></tr>\n"); for (j = 0, avg = 0; j < 5; j++) { avg += p->mod_5s_traffic_out[j]; } avg /= 5; BUFFER_APPEND_STRING_CONST(b, "<tr><td>Traffic</td><td class=\"string\">"); mod_status_get_multiplier(&avg, &multiplier, 1024); sprintf(buf, "%.2f", avg); buffer_append_string(b, buf); BUFFER_APPEND_STRING_CONST(b, " "); if (multiplier) buffer_append_string_len(b, &multiplier, 1); BUFFER_APPEND_STRING_CONST(b, "byte/s</td></tr>\n"); BUFFER_APPEND_STRING_CONST(b, "</table>\n"); BUFFER_APPEND_STRING_CONST(b, "<hr />\n<pre><b>legend</b>\n"); BUFFER_APPEND_STRING_CONST(b, ". = connect, C = close, E = hard error\n"); BUFFER_APPEND_STRING_CONST(b, "r = read, R = read-POST, W = write, h = handle-request\n"); BUFFER_APPEND_STRING_CONST(b, "q = request-start, Q = request-end\n"); BUFFER_APPEND_STRING_CONST(b, "s = response-start, S = response-end\n"); BUFFER_APPEND_STRING_CONST(b, "<b>"); buffer_append_long(b, srv->conns->used); BUFFER_APPEND_STRING_CONST(b, " connections</b>\n"); for (j = 0; j < srv->conns->used; j++) { connection *c = srv->conns->ptr[j]; const char *state = connection_get_short_state(c->state); buffer_append_string_len(b, state, 1); if (((j + 1) % 50) == 0) { BUFFER_APPEND_STRING_CONST(b, "\n"); } } BUFFER_APPEND_STRING_CONST(b, "\n</pre><hr />\n<h2>Connections</h2>\n"); BUFFER_APPEND_STRING_CONST(b, "<table summary=\"status\" class=\"status\">\n"); BUFFER_APPEND_STRING_CONST(b, "<tr>"); mod_status_header_append_sort(b, p_d, "Client IP"); mod_status_header_append_sort(b, p_d, "Read"); mod_status_header_append_sort(b, p_d, "Written"); mod_status_header_append_sort(b, p_d, "State"); mod_status_header_append_sort(b, p_d, "Time"); mod_status_header_append_sort(b, p_d, "Host"); mod_status_header_append_sort(b, p_d, "URI"); mod_status_header_append_sort(b, p_d, "File"); BUFFER_APPEND_STRING_CONST(b, "</tr>\n"); for (j = 0; j < srv->conns->used; j++) { connection *c = srv->conns->ptr[j]; BUFFER_APPEND_STRING_CONST(b, "<tr><td class=\"string\">"); buffer_append_string(b, inet_ntop_cache_get_ip(srv, &(c->dst_addr))); BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"int\">"); if (con->request.content_length) { buffer_append_long(b, c->request_content_queue->bytes_in); BUFFER_APPEND_STRING_CONST(b, "/"); buffer_append_long(b, c->request.content_length); } else { BUFFER_APPEND_STRING_CONST(b, "0/0"); } BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"int\">"); buffer_append_off_t(b, chunkqueue_written(c->write_queue)); BUFFER_APPEND_STRING_CONST(b, "/"); buffer_append_off_t(b, chunkqueue_length(c->write_queue)); BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">"); buffer_append_string(b, connection_get_state(c->state)); BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"int\">"); buffer_append_long(b, srv->cur_ts - c->request_start); BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">"); if (buffer_is_empty(c->server_name)) { buffer_append_string_buffer(b, c->uri.authority); } else { buffer_append_string_buffer(b, c->server_name); } BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">"); if (!buffer_is_empty(c->uri.path)) { buffer_append_string_encoded(b, CONST_BUF_LEN(c->uri.path), ENCODING_HTML); } BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">"); buffer_append_string_buffer(b, c->physical.path); BUFFER_APPEND_STRING_CONST(b, "</td></tr>\n"); } BUFFER_APPEND_STRING_CONST(b, "</table>\n"); BUFFER_APPEND_STRING_CONST(b, " </body>\n" "</html>\n" ); response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); return 0; }
int cache_parse_lua(server *srv, connection *con, plugin_data *p, buffer *fn) { lua_State *L; int ret = -1; buffer *b; b = buffer_init(); /* push the lua file to the interpreter and see what happends */ L = luaL_newstate(); luaL_openlibs(L); /* register functions */ lua_register(L, "md5", f_crypto_md5); lua_register(L, "file_mtime", f_file_mtime); lua_register(L, "file_isreg", f_file_isreg); lua_register(L, "file_isdir", f_file_isreg); lua_register(L, "dir_files", f_dir_files); #ifdef USE_MEMCACHED lua_pushlightuserdata(L, p->conf.memc); lua_pushcclosure(L, f_memcache_get_long, 1); lua_setglobal(L, "memcache_get_long"); lua_pushlightuserdata(L, p->conf.memc); lua_pushcclosure(L, f_memcache_get_string, 1); lua_setglobal(L, "memcache_get_string"); lua_pushlightuserdata(L, p->conf.memc); lua_pushcclosure(L, f_memcache_exists, 1); lua_setglobal(L, "memcache_exists"); #endif /* register CGI environment */ lua_newtable(L); { int header_tbl = lua_gettop(L); c_to_lua_push(L, header_tbl, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri)); c_to_lua_push(L, header_tbl, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path)); c_to_lua_push(L, header_tbl, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(con->physical.path)); c_to_lua_push(L, header_tbl, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.basedir)); if (!buffer_string_is_empty(con->request.pathinfo)) { c_to_lua_push(L, header_tbl, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo)); } c_to_lua_push(L, header_tbl, CONST_STR_LEN("CWD"), CONST_BUF_LEN(p->basedir)); c_to_lua_push(L, header_tbl, CONST_STR_LEN("BASEURL"), CONST_BUF_LEN(p->baseurl)); } lua_setglobal(L, "request"); /* register GET parameter */ lua_newtable(L); { int get_tbl = lua_gettop(L); buffer_copy_buffer(b, con->uri.query); cache_export_get_params(L, get_tbl, b); buffer_reset(b); } lua_setglobal(L, "get"); /* 2 default constants */ lua_pushinteger(L, 0); lua_setglobal(L, "CACHE_HIT"); lua_pushinteger(L, 1); lua_setglobal(L, "CACHE_MISS"); /* load lua program */ ret = luaL_loadfile(L, fn->ptr); if (0 != ret) { log_error_write(srv, __FILE__, __LINE__, "sbsS", "failed loading cml_lua script", fn, ":", lua_tostring(L, -1)); goto error; } if (lua_pcall(L, 0, 1, 0)) { log_error_write(srv, __FILE__, __LINE__, "sbsS", "failed running cml_lua script", fn, ":", lua_tostring(L, -1)); goto error; } /* get return value */ ret = (int)lua_tointeger(L, -1); lua_pop(L, 1); /* fetch the data from lua */ lua_to_c_get_string(L, "trigger_handler", p->trigger_handler); if (0 == lua_to_c_get_string(L, "output_contenttype", b)) { response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(b)); } if (ret == 0) { /* up to now it is a cache-hit, check if all files exist */ int curelem; time_t mtime = 0; if (!lua_to_c_is_table(L, "output_include")) { log_error_write(srv, __FILE__, __LINE__, "s", "output_include is missing or not a table"); ret = -1; goto error; } lua_getglobal(L, "output_include"); curelem = lua_gettop(L); /* HOW-TO build a etag ? * as we don't just have one file we have to take the stat() * from all base files, merge them and build the etag from * it later. * * The mtime of the content is the mtime of the freshest base file * * */ lua_pushnil(L); /* first key */ while (lua_next(L, curelem) != 0) { /* key' is at index -2 and value' at index -1 */ if (lua_isstring(L, -1)) { const char *s = lua_tostring(L, -1); struct stat st; int fd; /* the file is relative, make it absolute */ if (s[0] != '/') { buffer_copy_buffer(b, p->basedir); buffer_append_string(b, lua_tostring(L, -1)); } else { buffer_copy_string(b, lua_tostring(L, -1)); } fd = stat_cache_open_rdonly_fstat(srv, con, b, &st); if (fd < 0) { /* stat failed */ switch(errno) { case ENOENT: /* a file is missing, call the handler to generate it */ if (!buffer_string_is_empty(p->trigger_handler)) { ret = 1; /* cache-miss */ log_error_write(srv, __FILE__, __LINE__, "s", "a file is missing, calling handler"); break; } else { /* handler not set -> 500 */ ret = -1; log_error_write(srv, __FILE__, __LINE__, "s", "a file missing and no handler set"); break; } break; default: break; } } else { chunkqueue_append_file_fd(con->write_queue, b, fd, 0, st.st_size); if (st.st_mtime > mtime) mtime = st.st_mtime; } } else { /* not a string */ ret = -1; log_error_write(srv, __FILE__, __LINE__, "s", "not a string"); break; } lua_pop(L, 1); /* removes value'; keeps key' for next iteration */ } lua_settop(L, curelem - 1); if (ret == 0) { data_string *ds; char timebuf[sizeof("Sat, 23 Jul 2005 21:20:01 GMT")]; con->file_finished = 1; ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"); if (0 == mtime) mtime = time(NULL); /* default last-modified to now */ /* no Last-Modified specified */ if (NULL == ds) { strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&mtime)); response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), timebuf, sizeof(timebuf) - 1); ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"); force_assert(NULL != ds); } if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, ds->value)) { /* ok, the client already has our content, * no need to send it again */ chunkqueue_reset(con->write_queue); ret = 0; /* cache-hit */ } } else { chunkqueue_reset(con->write_queue); } } if (ret == 1 && !buffer_string_is_empty(p->trigger_handler)) { /* cache-miss */ buffer_copy_buffer(con->uri.path, p->baseurl); buffer_append_string_buffer(con->uri.path, p->trigger_handler); buffer_copy_buffer(con->physical.path, p->basedir); buffer_append_string_buffer(con->physical.path, p->trigger_handler); chunkqueue_reset(con->write_queue); } error: lua_close(L); buffer_free(b); return ret /* cache-error */; }
static int http_list_directory(server *srv, connection *con, plugin_data *p, buffer *dir) { DIR *dp; buffer *out; struct dirent *dent; struct stat st; char *path, *path_file; size_t i; int hide_dotfiles = p->conf.hide_dot_files; dirls_list_t dirs, files, *list; dirls_entry_t *tmp; char sizebuf[sizeof("999.9K")]; char datebuf[sizeof("2005-Jan-01 22:23:24")]; size_t k; const char *content_type; long name_max; #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR) char attrval[128]; int attrlen; #endif #ifdef HAVE_LOCALTIME_R struct tm tm; #endif if (buffer_string_is_empty(dir)) return -1; i = buffer_string_length(dir); #ifdef HAVE_PATHCONF if (0 >= (name_max = pathconf(dir->ptr, _PC_NAME_MAX))) { /* some broken fs (fuse) return 0 instead of -1 */ #ifdef NAME_MAX name_max = NAME_MAX; #else name_max = 255; /* stupid default */ #endif } #elif defined __WIN32 name_max = FILENAME_MAX; #else name_max = NAME_MAX; #endif path = malloc(i + name_max + 1); force_assert(NULL != path); memcpy(path, dir->ptr, i+1); path_file = path + i; if (NULL == (dp = opendir(path))) { log_error_write(srv, __FILE__, __LINE__, "sbs", "opendir failed:", dir, strerror(errno)); free(path); return -1; } dirs.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE); force_assert(dirs.ent); dirs.size = DIRLIST_BLOB_SIZE; dirs.used = 0; files.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE); force_assert(files.ent); files.size = DIRLIST_BLOB_SIZE; files.used = 0; while ((dent = readdir(dp)) != NULL) { unsigned short exclude_match = 0; if (dent->d_name[0] == '.') { if (hide_dotfiles) continue; if (dent->d_name[1] == '\0') continue; if (dent->d_name[1] == '.' && dent->d_name[2] == '\0') continue; } if (p->conf.hide_readme_file && !buffer_string_is_empty(p->conf.show_readme)) { if (strcmp(dent->d_name, p->conf.show_readme->ptr) == 0) continue; } if (p->conf.hide_header_file && !buffer_string_is_empty(p->conf.show_header)) { if (strcmp(dent->d_name, p->conf.show_header->ptr) == 0) continue; } /* compare d_name against excludes array * elements, skipping any that match. */ #ifdef HAVE_PCRE_H for(i = 0; i < p->conf.excludes->used; i++) { int n; #define N 10 int ovec[N * 3]; pcre *regex = p->conf.excludes->ptr[i]->regex; if ((n = pcre_exec(regex, NULL, dent->d_name, strlen(dent->d_name), 0, 0, ovec, 3 * N)) < 0) { if (n != PCRE_ERROR_NOMATCH) { log_error_write(srv, __FILE__, __LINE__, "sd", "execution error while matching:", n); /* aborting would require a lot of manual cleanup here. * skip instead (to not leak names that break pcre matching) */ exclude_match = 1; break; } } else { exclude_match = 1; break; } } if (exclude_match) { continue; } #endif i = strlen(dent->d_name); /* NOTE: the manual says, d_name is never more than NAME_MAX * so this should actually not be a buffer-overflow-risk */ if (i > (size_t)name_max) continue; memcpy(path_file, dent->d_name, i + 1); if (stat(path, &st) != 0) continue; list = &files; if (S_ISDIR(st.st_mode)) list = &dirs; if (list->used == list->size) { list->size += DIRLIST_BLOB_SIZE; list->ent = (dirls_entry_t**) realloc(list->ent, sizeof(dirls_entry_t*) * list->size); force_assert(list->ent); } tmp = (dirls_entry_t*) malloc(sizeof(dirls_entry_t) + 1 + i); tmp->mtime = st.st_mtime; tmp->size = st.st_size; tmp->namelen = i; memcpy(DIRLIST_ENT_NAME(tmp), dent->d_name, i + 1); list->ent[list->used++] = tmp; } closedir(dp); if (dirs.used) http_dirls_sort(dirs.ent, dirs.used); if (files.used) http_dirls_sort(files.ent, files.used); out = buffer_init(); http_list_directory_header(srv, con, p, out); /* directories */ for (i = 0; i < dirs.used; i++) { tmp = dirs.ent[i]; #ifdef HAVE_LOCALTIME_R localtime_r(&(tmp->mtime), &tm); strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm); #else strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime))); #endif buffer_append_string_len(out, CONST_STR_LEN("<tr class=\"d\"><td class=\"n\"><a href=\"")); buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART); buffer_append_string_len(out, CONST_STR_LEN("/\">")); buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML); buffer_append_string_len(out, CONST_STR_LEN("</a>/</td><td class=\"m\">")); buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1); buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">- </td><td class=\"t\">Directory</td></tr>\n")); free(tmp); } /* files */ for (i = 0; i < files.used; i++) { tmp = files.ent[i]; content_type = NULL; #if defined(HAVE_XATTR) if (con->conf.use_xattr) { memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1); attrlen = sizeof(attrval) - 1; if (attr_get(path, srv->srvconf.xattr_name->ptr, attrval, &attrlen, 0) == 0) { attrval[attrlen] = '\0'; content_type = attrval; } } #elif defined(HAVE_EXTATTR) if (con->conf.use_xattr) { memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1); if(-1 != (attrlen = extattr_get_file(path, EXTATTR_NAMESPACE_USER, srv->srvconf.xattr_name->ptr, attrval, sizeof(attrval)-1))) { attrval[attrlen] = '\0'; content_type = attrval; } } #endif if (content_type == NULL) { content_type = "application/octet-stream"; for (k = 0; k < con->conf.mimetypes->used; k++) { data_string *ds = (data_string *)con->conf.mimetypes->data[k]; size_t ct_len; if (buffer_is_empty(ds->key)) continue; ct_len = buffer_string_length(ds->key); if (tmp->namelen < ct_len) continue; if (0 == strncasecmp(DIRLIST_ENT_NAME(tmp) + tmp->namelen - ct_len, ds->key->ptr, ct_len)) { content_type = ds->value->ptr; break; } } } #ifdef HAVE_LOCALTIME_R localtime_r(&(tmp->mtime), &tm); strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm); #else strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime))); #endif http_list_directory_sizefmt(sizebuf, sizeof(sizebuf), tmp->size); buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\"")); buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART); buffer_append_string_len(out, CONST_STR_LEN("\">")); buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML); buffer_append_string_len(out, CONST_STR_LEN("</a></td><td class=\"m\">")); buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1); buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">")); buffer_append_string(out, sizebuf); buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"t\">")); buffer_append_string(out, content_type); buffer_append_string_len(out, CONST_STR_LEN("</td></tr>\n")); free(tmp); } free(files.ent); free(dirs.ent); free(path); http_list_directory_footer(srv, con, p, out); /* Insert possible charset to Content-Type */ if (buffer_string_is_empty(p->conf.encoding)) { response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); } else { buffer_copy_string_len(p->content_charset, CONST_STR_LEN("text/html; charset=")); buffer_append_string_buffer(p->content_charset, p->conf.encoding); response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->content_charset)); } con->file_finished = 1; chunkqueue_append_buffer(con->write_queue, out); buffer_free(out); return 0; }
/* 0: everything ok, -1: error, -2: con closed */ static int connection_handle_read(server *srv, connection *con) { int len; buffer *b; int toread, read_offset; if (con->conf.is_ssl) { return connection_handle_read_ssl(srv, con); } b = (NULL != con->read_queue->last) ? con->read_queue->last->mem : NULL; /* default size for chunks is 4kb; only use bigger chunks if FIONREAD tells * us more than 4kb is available * if FIONREAD doesn't signal a big chunk we fill the previous buffer * if it has >= 1kb free */ #if defined(__WIN32) if (NULL == b || b->size - b->used < 1024) { b = chunkqueue_get_append_buffer(con->read_queue); buffer_prepare_copy(b, 4 * 1024); } read_offset = (b->used == 0) ? 0 : b->used - 1; len = recv(con->fd, b->ptr + read_offset, b->size - 1 - read_offset, 0); #else #ifdef HAVE_LIBMTCP /* toread = MAX_READ_LIMIT; */ if (mtcp_socket_ioctl(srv->mctx, con->fd, FIONREAD, &toread) || toread == 0 || toread <= 4*1024) { #else if (ioctl(con->fd, FIONREAD, &toread) || toread == 0 || toread <= 4*1024) { #endif if (NULL == b || b->size - b->used < 1024) { b = chunkqueue_get_append_buffer(con->read_queue); buffer_prepare_copy(b, 4 * 1024); } } else { if (toread > MAX_READ_LIMIT) toread = MAX_READ_LIMIT; b = chunkqueue_get_append_buffer(con->read_queue); buffer_prepare_copy(b, toread + 1); } read_offset = (b->used == 0) ? 0 : b->used - 1; #ifdef HAVE_LIBMTCP len = mtcp_read(srv->mctx, con->fd, b->ptr + read_offset, b->size - 1 - read_offset); #else len = read(con->fd, b->ptr + read_offset, b->size - 1 - read_offset); #endif #endif if (len < 0) { con->is_readable = 0; if (errno == EAGAIN) return 0; if (errno == EINTR) { /* we have been interrupted before we could read */ con->is_readable = 1; return 0; } if (errno != ECONNRESET) { /* expected for keep-alive */ log_error_write(srv, __FILE__, __LINE__, "ssd", "connection closed - read failed: ", strerror(errno), errno); } connection_set_state(srv, con, CON_STATE_ERROR); return -1; } else if (len == 0) { con->is_readable = 0; /* the other end close the connection -> KEEP-ALIVE */ /* pipelining */ return -2; } else if ((size_t)len < b->size - 1) { /* we got less then expected, wait for the next fd-event */ con->is_readable = 0; } if (b->used > 0) b->used--; b->used += len; b->ptr[b->used++] = '\0'; con->bytes_read += len; #if 0 dump_packet(b->ptr, len); #endif 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: case HTTP_METHOD_PUT: case HTTP_METHOD_PATCH: case HTTP_METHOD_MKCOL: case HTTP_METHOD_DELETE: case HTTP_METHOD_COPY: case HTTP_METHOD_MOVE: case HTTP_METHOD_PROPFIND: case HTTP_METHOD_PROPPATCH: case HTTP_METHOD_LOCK: case HTTP_METHOD_UNLOCK: 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) && con->uri.path->used && 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: switch(con->http_status) { case 400: /* bad request */ case 401: /* authorization required */ case 414: /* overload request header */ case 505: /* unknown protocol */ case 207: /* this was webdav */ break; default: con->http_status = 501; break; } 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; buffer_reset(con->physical.path); /* try to send static errorfile */ if (!buffer_is_empty(con->conf.errorfile_prefix)) { stat_cache_entry *sce = NULL; buffer_copy_string_buffer(con->physical.path, con->conf.errorfile_prefix); buffer_append_long(con->physical.path, con->http_status); buffer_append_string_len(con->physical.path, CONST_STR_LEN(".html")); if (HANDLER_ERROR != stat_cache_get_entry(srv, con, con->physical.path, &sce)) { con->file_finished = 1; http_chunk_append_file(srv, con, con->physical.path, 0, sce->st.st_size); 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 = chunkqueue_get_append_buffer(con->write_queue); /* 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_long(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_long(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" )); response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); } break; } 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_off_t(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)) { 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; } static int connection_handle_write(server *srv, connection *con) { switch(network_write_chunkqueue(srv, con, con->write_queue, MAX_WRITE_LIMIT)) { case 0: con->write_request_ts = srv->cur_ts; if (con->file_finished) { connection_set_state(srv, con, CON_STATE_RESPONSE_END); joblist_append(srv, con); } break; case -1: /* error on our side */ log_error_write(srv, __FILE__, __LINE__, "sd", "connection closed: write failed on fd", con->fd); connection_set_state(srv, con, CON_STATE_ERROR); joblist_append(srv, con); break; case -2: /* remote close */ connection_set_state(srv, con, CON_STATE_ERROR); joblist_append(srv, con); break; case 1: con->write_request_ts = srv->cur_ts; con->is_writable = 0; /* not finished yet -> WRITE */ break; } return 0; } connection *connection_init(server *srv) { connection *con; UNUSED(srv); con = calloc(1, sizeof(*con)); con->fd = 0; con->ndx = -1; con->fde_ndx = -1; con->bytes_written = 0; con->bytes_read = 0; con->bytes_header = 0; con->loops_per_request = 0; #define CLEAN(x) \ con->x = buffer_init(); CLEAN(request.uri); CLEAN(request.request_line); CLEAN(request.request); CLEAN(request.pathinfo); CLEAN(request.orig_uri); CLEAN(uri.scheme); CLEAN(uri.authority); CLEAN(uri.path); CLEAN(uri.path_raw); CLEAN(uri.query); CLEAN(physical.doc_root); CLEAN(physical.path); CLEAN(physical.basedir); CLEAN(physical.rel_path); CLEAN(physical.etag); CLEAN(parse_request); CLEAN(authed_user); CLEAN(server_name); CLEAN(error_handler); CLEAN(dst_addr_buf); #if defined USE_OPENSSL && ! defined OPENSSL_NO_TLSEXT CLEAN(tlsext_server_name); #endif #undef CLEAN con->write_queue = chunkqueue_init(); con->read_queue = chunkqueue_init(); con->request_content_queue = chunkqueue_init(); chunkqueue_set_tempdirs(con->request_content_queue, srv->srvconf.upload_tempdirs); con->request.headers = array_init(); con->response.headers = array_init(); con->environment = array_init(); /* init plugin specific connection structures */ con->plugin_ctx = calloc(1, (srv->plugins.used + 1) * sizeof(void *)); con->cond_cache = calloc(srv->config_context->used, sizeof(cond_cache_t)); config_setup_connection(srv, con); return con; }
static int http_response_parse_range(server *srv, connection *con, plugin_data *p) { int multipart = 0; char *boundary = "fkj49sn38dcn3"; data_string *ds; stat_cache_entry *sce = NULL; buffer *content_type = NULL; buffer *range = NULL; http_req_range *ranges, *r; if (NULL != (ds = (data_string *)array_get_element(con->request.headers, CONST_STR_LEN("Range")))) { range = ds->value; } else { /* we don't have a Range header */ return -1; } if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) { SEGFAULT("stat_cache_get_entry(%s) returned %d", SAFE_BUF_STR(con->physical.path), HANDLER_ERROR); } con->response.content_length = 0; if (NULL != (ds = (data_string *)array_get_element(con->response.headers, CONST_STR_LEN("Content-Type")))) { content_type = ds->value; } /* start the range-header parser * bytes=<num> */ ranges = p->ranges; http_request_range_reset(ranges); switch (http_request_range_parse(range, ranges)) { case PARSE_ERROR: return -1; /* no range valid Range Header */ case PARSE_SUCCESS: break; default: TRACE("%s", "foobar"); return -1; } if (ranges->next) { multipart = 1; } /* patch the '-1' */ for (r = ranges; r; r = r->next) { if (r->start == -1) { /* -<end> * * the last <end> bytes */ r->start = sce->st.st_size - r->end; r->end = sce->st.st_size - 1; } if (r->end == -1) { /* <start>- * all but the first <start> bytes */ r->end = sce->st.st_size - 1; } if (r->end > sce->st.st_size - 1) { /* RFC 2616 - 14.35.1 * * if last-byte-pos not present or > size-of-file * take the size-of-file * * */ r->end = sce->st.st_size - 1; } if (r->start > sce->st.st_size - 1) { /* RFC 2616 - 14.35.1 * * if first-byte-pos > file-size, 416 */ con->http_status = 416; return -1; } if (r->start > r->end) { /* RFC 2616 - 14.35.1 * * if last-byte-pos is present, it has to be >= first-byte-pos * * invalid ranges have to be handle as no Range specified * */ return -1; } } if (r) { /* we ran into an range violation */ return -1; } if (multipart) { buffer *b; for (r = ranges; r; r = r->next) { /* write boundary-header */ b = chunkqueue_get_append_buffer(con->send); buffer_copy_string_len(b, CONST_STR_LEN("\r\n--")); buffer_append_string(b, boundary); /* write Content-Range */ buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Range: bytes ")); buffer_append_off_t(b, r->start); buffer_append_string_len(b, CONST_STR_LEN("-")); buffer_append_off_t(b, r->end); buffer_append_string_len(b, CONST_STR_LEN("/")); buffer_append_off_t(b, sce->st.st_size); buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Type: ")); buffer_append_string_buffer(b, content_type); /* write END-OF-HEADER */ buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n")); con->response.content_length += b->used - 1; con->send->bytes_in += b->used - 1; chunkqueue_append_file(con->send, con->physical.path, r->start, r->end - r->start + 1); con->response.content_length += r->end - r->start + 1; con->send->bytes_in += r->end - r->start + 1; } /* add boundary end */ b = chunkqueue_get_append_buffer(con->send); buffer_copy_string_len(b, "\r\n--", 4); buffer_append_string(b, boundary); buffer_append_string_len(b, "--\r\n", 4); con->response.content_length += b->used - 1; con->send->bytes_in += b->used - 1; /* set header-fields */ buffer_copy_string_len(p->range_buf, CONST_STR_LEN("multipart/byteranges; boundary=")); buffer_append_string(p->range_buf, boundary); /* overwrite content-type */ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->range_buf)); } else { r = ranges; chunkqueue_append_file(con->send, con->physical.path, r->start, r->end - r->start + 1); con->response.content_length += r->end - r->start + 1; con->send->bytes_in += r->end - r->start + 1; buffer_copy_string_len(p->range_buf, CONST_STR_LEN("bytes ")); buffer_append_off_t(p->range_buf, r->start); buffer_append_string_len(p->range_buf, CONST_STR_LEN("-")); buffer_append_off_t(p->range_buf, r->end); buffer_append_string_len(p->range_buf, CONST_STR_LEN("/")); buffer_append_off_t(p->range_buf, sce->st.st_size); response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(p->range_buf)); } /* ok, the file is set-up */ return 0; }
static int mod_ssi_handle_request(server *srv, connection *con, plugin_data *p) { stream s; #ifdef HAVE_PCRE_H int i, n; #define N 10 int ovec[N * 3]; #endif stat_cache_entry *sce = NULL; /* get a stream to the file */ array_reset(p->ssi_vars); array_reset(p->ssi_cgi_env); buffer_copy_string_len(p->timefmt, CONST_STR_LEN("%a, %d %b %Y %H:%M:%S %Z")); p->sizefmt = 0; build_ssi_cgi_vars(srv, con, p); p->if_is_false = 0; /* Reset the modified time of included files */ include_file_last_mtime = 0; if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) { log_error_write(srv, __FILE__, __LINE__, "SB", "stat_cache_get_entry failed: ", con->physical.path); return -1; } if (-1 == stream_open(&s, con->physical.path)) { log_error_write(srv, __FILE__, __LINE__, "sb", "stream-open: ", con->physical.path); return -1; } /** * <!--#element attribute=value attribute=value ... --> * * config DONE * errmsg -- missing * sizefmt DONE * timefmt DONE * echo DONE * var DONE * encoding -- missing * exec DONE * cgi -- never * cmd DONE * fsize DONE * file DONE * virtual DONE * flastmod DONE * file DONE * virtual DONE * include DONE * file DONE * virtual DONE * printenv DONE * set DONE * var DONE * value DONE * * if DONE * elif DONE * else DONE * endif DONE * * * expressions * AND, OR DONE * comp DONE * ${...} -- missing * $... DONE * '...' DONE * ( ... ) DONE * * * * ** all DONE ** * DATE_GMT * The current date in Greenwich Mean Time. * DATE_LOCAL * The current date in the local time zone. * DOCUMENT_NAME * The filename (excluding directories) of the document requested by the user. * DOCUMENT_URI * The (%-decoded) URL path of the document requested by the user. Note that in the case of nested include files, this is not then URL for the current document. * LAST_MODIFIED * The last modification date of the document requested by the user. * USER_NAME * Contains the owner of the file which included it. * */ #ifdef HAVE_PCRE_H for (i = 0; (n = pcre_exec(p->ssi_regex, NULL, s.start, s.size, i, 0, ovec, N * 3)) > 0; i = ovec[1]) { const char **l; /* take everything from last offset to current match pos */ if (!p->if_is_false) chunkqueue_append_file(con->write_queue, con->physical.path, i, ovec[0] - i); pcre_get_substring_list(s.start, ovec, n, &l); process_ssi_stmt(srv, con, p, l, n, sce); pcre_free_substring_list(l); } switch(n) { case PCRE_ERROR_NOMATCH: /* copy everything/the rest */ chunkqueue_append_file(con->write_queue, con->physical.path, i, s.size - i); break; default: log_error_write(srv, __FILE__, __LINE__, "sd", "execution error while matching: ", n); break; } #endif stream_close(&s); con->file_started = 1; con->file_finished = 1; con->mode = p->id; if (p->conf.content_type->used <= 1) { response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); } else { response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->conf.content_type)); } { /* Generate "ETag" & "Last-Modified" headers */ time_t lm_time = 0; buffer *mtime = NULL; etag_mutate(con->physical.etag, sce->etag); response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag)); if (sce->st.st_mtime > include_file_last_mtime) lm_time = sce->st.st_mtime; else lm_time = include_file_last_mtime; mtime = strftime_cache_get(srv, lm_time); response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime)); } /* Reset the modified time of included files */ include_file_last_mtime = 0; /* reset physical.path */ buffer_reset(con->physical.path); return 0; }
static int http_response_parse_range(server *srv, connection *con, plugin_data *p) { int multipart = 0; int error; off_t start, end; const char *s, *minus; char *boundary = "fkj49sn38dcn3"; data_string *ds; stat_cache_entry *sce = NULL; buffer *content_type = NULL; if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) { SEGFAULT(); } start = 0; end = sce->st.st_size - 1; con->response.content_length = 0; if (NULL != (ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) { content_type = ds->value; } for (s = con->request.http_range, error = 0; !error && *s && NULL != (minus = strchr(s, '-')); ) { char *err; off_t la, le; if (s == minus) { /* -<stop> */ le = strtoll(s, &err, 10); if (le == 0) { /* RFC 2616 - 14.35.1 */ con->http_status = 416; error = 1; } else if (*err == '\0') { /* end */ s = err; end = sce->st.st_size - 1; start = sce->st.st_size + le; } else if (*err == ',') { multipart = 1; s = err + 1; end = sce->st.st_size - 1; start = sce->st.st_size + le; } else { error = 1; } } else if (*(minus+1) == '\0' || *(minus+1) == ',') { /* <start>- */ la = strtoll(s, &err, 10); if (err == minus) { /* ok */ if (*(err + 1) == '\0') { s = err + 1; end = sce->st.st_size - 1; start = la; } else if (*(err + 1) == ',') { multipart = 1; s = err + 2; end = sce->st.st_size - 1; start = la; } else { error = 1; } } else { /* error */ error = 1; } } else { /* <start>-<stop> */ la = strtoll(s, &err, 10); if (err == minus) { le = strtoll(minus+1, &err, 10); /* RFC 2616 - 14.35.1 */ if (la > le) { error = 1; } if (*err == '\0') { /* ok, end*/ s = err; end = le; start = la; } else if (*err == ',') { multipart = 1; s = err + 1; end = le; start = la; } else { /* error */ error = 1; } } else { /* error */ error = 1; } } if (!error) { if (start < 0) start = 0; /* RFC 2616 - 14.35.1 */ if (end > sce->st.st_size - 1) end = sce->st.st_size - 1; if (start > sce->st.st_size - 1) { error = 1; con->http_status = 416; } } if (!error) { if (multipart) { /* write boundary-header */ buffer *b; b = chunkqueue_get_append_buffer(con->write_queue); buffer_copy_string_len(b, CONST_STR_LEN("\r\n--")); buffer_append_string(b, boundary); /* write Content-Range */ buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Range: bytes ")); buffer_append_off_t(b, start); buffer_append_string_len(b, CONST_STR_LEN("-")); buffer_append_off_t(b, end); buffer_append_string_len(b, CONST_STR_LEN("/")); buffer_append_off_t(b, sce->st.st_size); buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Type: ")); buffer_append_string_buffer(b, content_type); /* write END-OF-HEADER */ buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n")); con->response.content_length += b->used - 1; } chunkqueue_append_file(con->write_queue, con->physical.path, start, end - start + 1); con->response.content_length += end - start + 1; } } /* something went wrong */ if (error) return -1; if (multipart) { /* add boundary end */ buffer *b; b = chunkqueue_get_append_buffer(con->write_queue); buffer_copy_string_len(b, "\r\n--", 4); buffer_append_string(b, boundary); buffer_append_string_len(b, "--\r\n", 4); con->response.content_length += b->used - 1; /* set header-fields */ buffer_copy_string_len(p->range_buf, CONST_STR_LEN("multipart/byteranges; boundary=")); buffer_append_string(p->range_buf, boundary); /* overwrite content-type */ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->range_buf)); } else { /* add Content-Range-header */ buffer_copy_string_len(p->range_buf, CONST_STR_LEN("bytes ")); buffer_append_off_t(p->range_buf, start); buffer_append_string_len(p->range_buf, CONST_STR_LEN("-")); buffer_append_off_t(p->range_buf, end); buffer_append_string_len(p->range_buf, CONST_STR_LEN("/")); buffer_append_off_t(p->range_buf, sce->st.st_size); response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(p->range_buf)); } /* ok, the file is set-up */ return 0; }
void http_response_send_file (server *srv, connection *con, buffer *path) { stat_cache_entry *sce = NULL; buffer *mtime = NULL; data_string *ds; int allow_caching = (0 == con->http_status || 200 == con->http_status); if (HANDLER_ERROR == stat_cache_get_entry(srv, con, path, &sce)) { con->http_status = (errno == ENOENT) ? 404 : 403; log_error_write(srv, __FILE__, __LINE__, "sbsb", "not a regular file:", con->uri.path, "->", path); return; } /* we only handline regular files */ #ifdef HAVE_LSTAT if ((sce->is_symlink == 1) && !con->conf.follow_symlink) { con->http_status = 403; if (con->conf.log_request_handling) { log_error_write(srv, __FILE__, __LINE__, "s", "-- access denied due symlink restriction"); log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", path); } return; } #endif if (!S_ISREG(sce->st.st_mode)) { con->http_status = 403; if (con->conf.log_file_not_found) { log_error_write(srv, __FILE__, __LINE__, "sbsb", "not a regular file:", con->uri.path, "->", sce->name); } return; } /* mod_compress might set several data directly, don't overwrite them */ /* set response content-type, if not set already */ if (NULL == array_get_element(con->response.headers, "Content-Type")) { if (buffer_string_is_empty(sce->content_type)) { /* we are setting application/octet-stream, but also announce that * this header field might change in the seconds few requests * * This should fix the aggressive caching of FF and the script download * seen by the first installations */ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream")); allow_caching = 0; } else { response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type)); } } if (con->conf.range_requests) { response_header_overwrite(srv, con, CONST_STR_LEN("Accept-Ranges"), CONST_STR_LEN("bytes")); } if (allow_caching) { if (con->etag_flags != 0 && !buffer_string_is_empty(sce->etag)) { if (NULL == array_get_element(con->response.headers, "ETag")) { /* generate e-tag */ etag_mutate(con->physical.etag, sce->etag); response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag)); } } /* prepare header */ if (NULL == (ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"))) { mtime = strftime_cache_get(srv, sce->st.st_mtime); response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime)); } else { mtime = ds->value; } if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) { return; } } if (con->request.http_range && con->conf.range_requests && (200 == con->http_status || 0 == con->http_status) && NULL == array_get_element(con->response.headers, "Content-Encoding")) { int do_range_request = 1; /* check if we have a conditional GET */ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If-Range"))) { /* if the value is the same as our ETag, we do a Range-request, * otherwise a full 200 */ if (ds->value->ptr[0] == '"') { /** * client wants a ETag */ if (!con->physical.etag) { do_range_request = 0; } else if (!buffer_is_equal(ds->value, con->physical.etag)) { do_range_request = 0; } } else if (!mtime) { /** * we don't have a Last-Modified and can match the If-Range: * * sending all */ do_range_request = 0; } else if (!buffer_is_equal(ds->value, mtime)) { do_range_request = 0; } } if (do_range_request) { /* content prepared, I'm done */ con->file_finished = 1; if (0 == http_response_parse_range(srv, con, path, sce)) { con->http_status = 206; } return; } } /* if we are still here, prepare body */ /* we add it here for all requests * the HEAD request will drop it afterwards again */ if (0 == sce->st.st_size || 0 == http_chunk_append_file(srv, con, path)) { con->http_status = 200; con->file_finished = 1; } else { con->http_status = 403; } }
int http_response_write_header(server *srv, connection *con) { buffer *b; size_t i; int have_date = 0; int have_server = 0; b = chunkqueue_get_prepend_buffer(con->write_queue); if (con->request.http_version == HTTP_VERSION_1_1) { buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.1 ")); } else { buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.0 ")); } buffer_append_long(b, con->http_status); buffer_append_string_len(b, CONST_STR_LEN(" ")); buffer_append_string(b, get_http_status_name(con->http_status)); /* disable keep-alive if requested */ if (con->request_count > con->conf.max_keep_alive_requests || 0 == con->conf.max_keep_alive_idle) { con->keep_alive = 0; } else { con->keep_alive_idle = con->conf.max_keep_alive_idle; } if (con->request.http_version != HTTP_VERSION_1_1 || con->keep_alive == 0) { if (con->keep_alive) { response_header_overwrite(srv, con, CONST_STR_LEN("Connection"), CONST_STR_LEN("keep-alive")); } else { response_header_overwrite(srv, con, CONST_STR_LEN("Connection"), CONST_STR_LEN("close")); } } if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { response_header_overwrite(srv, con, CONST_STR_LEN("Transfer-Encoding"), CONST_STR_LEN("chunked")); } /* add all headers */ for (i = 0; i < con->response.headers->used; i++) { data_string *ds; ds = (data_string *)con->response.headers->data[i]; if (ds->value->used && ds->key->used && 0 != strncasecmp(ds->key->ptr, CONST_STR_LEN("X-LIGHTTPD-")) && 0 != strncasecmp(ds->key->ptr, CONST_STR_LEN("X-Sendfile"))) { if (0 == strcasecmp(ds->key->ptr, "Date")) have_date = 1; if (0 == strcasecmp(ds->key->ptr, "Server")) have_server = 1; if (0 == strcasecmp(ds->key->ptr, "Content-Encoding") && 304 == con->http_status) continue; buffer_append_string_len(b, CONST_STR_LEN("\r\n")); buffer_append_string_buffer(b, ds->key); buffer_append_string_len(b, CONST_STR_LEN(": ")); #if 0 /** * the value might contain newlines, encode them with at least one white-space */ buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_HTTP_HEADER); #else buffer_append_string_buffer(b, ds->value); #endif } } if (!have_date) { /* HTTP/1.1 requires a Date: header */ buffer_append_string_len(b, CONST_STR_LEN("\r\nDate: ")); /* cache the generated timestamp */ if (srv->cur_ts != srv->last_generated_date_ts) { buffer_prepare_copy(srv->ts_date_str, 255); strftime(srv->ts_date_str->ptr, srv->ts_date_str->size - 1, "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(srv->cur_ts))); srv->ts_date_str->used = strlen(srv->ts_date_str->ptr) + 1; srv->last_generated_date_ts = srv->cur_ts; } buffer_append_string_buffer(b, srv->ts_date_str); } if (!have_server) { if (buffer_is_empty(con->conf.server_tag)) { buffer_append_string_len(b, CONST_STR_LEN("\r\nServer: " PACKAGE_DESC)); } else if (con->conf.server_tag->used > 1) { buffer_append_string_len(b, CONST_STR_LEN("\r\nServer: ")); buffer_append_string_encoded(b, CONST_BUF_LEN(con->conf.server_tag), ENCODING_HTTP_HEADER); } } //- Jerry add 20110923 #if EMBEDDED_EANBLE char * ddns_host_n = nvram_get_ddns_host_name(); if(ddns_host_n){ buffer_append_string_len(b, CONST_STR_LEN("\r\nDDNS: ")); buffer_append_string(b, ddns_host_n); }else #endif buffer_append_string_len(b, CONST_STR_LEN("\r\nDDNS: ")); buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n")); con->bytes_header = b->used - 1; if (con->conf.log_response_header) { log_error_write(srv, __FILE__, __LINE__, "sSb", "Response-Header:", "\n", b); } 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; }