xref: /lighttpd1.4/src/mod_status.c (revision 49e9f0ac)
1 #include "first.h"
2 
3 #include "base.h"
4 #include "connections.h"
5 #include "fdevent.h"
6 #include "http_header.h"
7 #include "log.h"
8 
9 #include "plugin.h"
10 
11 #include <sys/types.h>
12 
13 #include <fcntl.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <errno.h>
17 #include <time.h>
18 #include <stdio.h>
19 
20 typedef struct {
21 	buffer *config_url;
22 	buffer *status_url;
23 	buffer *statistics_url;
24 
25 	int     sort;
26 } plugin_config;
27 
28 typedef struct {
29 	PLUGIN_DATA;
30 
31 	double traffic_out;
32 	double requests;
33 
34 	double mod_5s_traffic_out[5];
35 	double mod_5s_requests[5];
36 	size_t mod_5s_ndx;
37 
38 	double rel_traffic_out;
39 	double rel_requests;
40 
41 	double abs_traffic_out;
42 	double abs_requests;
43 
44 	double bytes_written;
45 
46 	buffer *module_list;
47 
48 	plugin_config **config_storage;
49 
50 	plugin_config conf;
51 } plugin_data;
52 
53 INIT_FUNC(mod_status_init) {
54 	plugin_data *p;
55 	size_t i;
56 
57 	p = calloc(1, sizeof(*p));
58 
59 	p->traffic_out = p->requests = 0;
60 	p->rel_traffic_out = p->rel_requests = 0;
61 	p->abs_traffic_out = p->abs_requests = 0;
62 	p->bytes_written = 0;
63 	p->module_list = buffer_init();
64 
65 	for (i = 0; i < 5; i++) {
66 		p->mod_5s_traffic_out[i] = p->mod_5s_requests[i] = 0;
67 	}
68 
69 	return p;
70 }
71 
72 FREE_FUNC(mod_status_free) {
73 	plugin_data *p = p_d;
74 
75 	UNUSED(srv);
76 
77 	if (!p) return HANDLER_GO_ON;
78 
79 	buffer_free(p->module_list);
80 
81 	if (p->config_storage) {
82 		size_t i;
83 		for (i = 0; i < srv->config_context->used; i++) {
84 			plugin_config *s = p->config_storage[i];
85 			if (NULL == s) continue;
86 
87 			buffer_free(s->status_url);
88 			buffer_free(s->statistics_url);
89 			buffer_free(s->config_url);
90 
91 			free(s);
92 		}
93 		free(p->config_storage);
94 	}
95 
96 
97 	free(p);
98 
99 	return HANDLER_GO_ON;
100 }
101 
102 SETDEFAULTS_FUNC(mod_status_set_defaults) {
103 	plugin_data *p = p_d;
104 	size_t i;
105 
106 	config_values_t cv[] = {
107 		{ "status.status-url",           NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
108 		{ "status.config-url",           NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
109 		{ "status.enable-sort",          NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },
110 		{ "status.statistics-url",       NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
111 		{ NULL,                          NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
112 	};
113 
114 	if (!p) return HANDLER_ERROR;
115 
116 	p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *));
117 
118 	for (i = 0; i < srv->config_context->used; i++) {
119 		data_config const* config = (data_config const*)srv->config_context->data[i];
120 		plugin_config *s;
121 
122 		s = calloc(1, sizeof(plugin_config));
123 		s->config_url    = buffer_init();
124 		s->status_url    = buffer_init();
125 		s->sort          = 1;
126 		s->statistics_url    = buffer_init();
127 
128 		cv[0].destination = s->status_url;
129 		cv[1].destination = s->config_url;
130 		cv[2].destination = &(s->sort);
131 		cv[3].destination = s->statistics_url;
132 
133 		p->config_storage[i] = s;
134 
135 		if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
136 			return HANDLER_ERROR;
137 		}
138 	}
139 
140 	return HANDLER_GO_ON;
141 }
142 
143 
144 
145 static int mod_status_row_append(buffer *b, const char *key, const char *value) {
146 	buffer_append_string_len(b, CONST_STR_LEN("   <tr>\n"));
147 	buffer_append_string_len(b, CONST_STR_LEN("    <td><b>"));
148 	buffer_append_string(b, key);
149 	buffer_append_string_len(b, CONST_STR_LEN("</b></td>\n"));
150 	buffer_append_string_len(b, CONST_STR_LEN("    <td>"));
151 	buffer_append_string(b, value);
152 	buffer_append_string_len(b, CONST_STR_LEN("</td>\n"));
153 	buffer_append_string_len(b, CONST_STR_LEN("   </tr>\n"));
154 
155 	return 0;
156 }
157 
158 static int mod_status_header_append(buffer *b, const char *key) {
159 	buffer_append_string_len(b, CONST_STR_LEN("   <tr>\n"));
160 	buffer_append_string_len(b, CONST_STR_LEN("    <th colspan=\"2\">"));
161 	buffer_append_string(b, key);
162 	buffer_append_string_len(b, CONST_STR_LEN("</th>\n"));
163 	buffer_append_string_len(b, CONST_STR_LEN("   </tr>\n"));
164 
165 	return 0;
166 }
167 
168 static int mod_status_header_append_sort(buffer *b, void *p_d, const char* key) {
169 	plugin_data *p = p_d;
170 
171 	if (p->conf.sort) {
172 		buffer_append_string_len(b, CONST_STR_LEN("<th class=\"status\"><a href=\"#\" class=\"sortheader\" onclick=\"resort(this);return false;\">"));
173 		buffer_append_string(b, key);
174 		buffer_append_string_len(b, CONST_STR_LEN("<span class=\"sortarrow\">:</span></a></th>\n"));
175 	} else {
176 		buffer_append_string_len(b, CONST_STR_LEN("<th class=\"status\">"));
177 		buffer_append_string(b, key);
178 		buffer_append_string_len(b, CONST_STR_LEN("</th>\n"));
179 	}
180 
181 	return 0;
182 }
183 
184 static int mod_status_get_multiplier(double *avg, char *multiplier, int size) {
185 	*multiplier = ' ';
186 
187 	if (*avg > size) { *avg /= size; *multiplier = 'k'; }
188 	if (*avg > size) { *avg /= size; *multiplier = 'M'; }
189 	if (*avg > size) { *avg /= size; *multiplier = 'G'; }
190 	if (*avg > size) { *avg /= size; *multiplier = 'T'; }
191 	if (*avg > size) { *avg /= size; *multiplier = 'P'; }
192 	if (*avg > size) { *avg /= size; *multiplier = 'E'; }
193 	if (*avg > size) { *avg /= size; *multiplier = 'Z'; }
194 	if (*avg > size) { *avg /= size; *multiplier = 'Y'; }
195 
196 	return 0;
197 }
198 
199 static handler_t mod_status_handle_server_status_html(server *srv, connection *con, void *p_d) {
200 	plugin_data *p = p_d;
201 	buffer *b = chunkqueue_append_buffer_open(con->write_queue);
202 	size_t j;
203 	double avg;
204 	char multiplier = '\0';
205 	char buf[32];
206 	time_t ts;
207 
208 	int days, hours, mins, seconds;
209 
210 	/*(CON_STATE_CLOSE must be last state in enum connection_state_t)*/
211 	int cstates[CON_STATE_CLOSE+3];
212 	memset(cstates, 0, sizeof(cstates));
213 
214 	buffer_copy_string_len(b, CONST_STR_LEN(
215 				 "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
216 				 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
217 				 "         \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
218 				 "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
219 				 " <head>\n"
220 				 "  <title>Status</title>\n"
221 
222 				   "  <style type=\"text/css\">\n"
223 				   "    table.status { border: black solid thin; }\n"
224 				   "    td { white-space: nowrap; }\n"
225 				   "    td.int { background-color: #f0f0f0; text-align: right }\n"
226 				   "    td.string { background-color: #f0f0f0; text-align: left }\n"
227 				   "    th.status { background-color: black; color: white; font-weight: bold; }\n"
228 				   "    a.sortheader { background-color: black; color: white; font-weight: bold; text-decoration: none; display: block; }\n"
229 				   "    span.sortarrow { color: white; text-decoration: none; }\n"
230 				   "  </style>\n"));
231 
232 	if (!buffer_string_is_empty(con->uri.query) && 0 == memcmp(con->uri.query->ptr, CONST_STR_LEN("refresh="))) {
233 		/* Note: Refresh is an historical, but non-standard HTTP header
234 		 * References (meta http-equiv="refresh" use is deprecated):
235 		 *   https://www.w3.org/TR/WCAG10-HTML-TECHS/#meta-element
236 		 *   https://www.w3.org/TR/WCAG10-CORE-TECHS/#auto-page-refresh
237 		 *   https://www.w3.org/QA/Tips/reback
238 		 */
239 		const long refresh = strtol(con->uri.query->ptr+sizeof("refresh=")-1, NULL, 10);
240 		if (refresh > 0) {
241 			buffer_append_string_len(b, CONST_STR_LEN("<meta http-equiv=\"refresh\" content=\""));
242 			buffer_append_int(b, refresh < 604800 ? refresh : 604800);
243 			buffer_append_string_len(b, CONST_STR_LEN("\">\n"));
244 		}
245 	}
246 
247 	if (p->conf.sort) {
248 		buffer_append_string_len(b, CONST_STR_LEN(
249 					   "<script type=\"text/javascript\">\n"
250 					   "// <!--\n"
251 					   "var sort_column;\n"
252 					   "var prev_span = null;\n"
253 
254 					   "function get_inner_text(el) {\n"
255 					   " if((typeof el == 'string')||(typeof el == 'undefined'))\n"
256 					   "  return el;\n"
257 					   " if(el.innerText)\n"
258 					   "  return el.innerText;\n"
259 					   " else {\n"
260 					   "  var str = \"\";\n"
261 					   "  var cs = el.childNodes;\n"
262 					   "  var l = cs.length;\n"
263 					   "  for (i=0;i<l;i++) {\n"
264 					   "   if (cs[i].nodeType==1) str += get_inner_text(cs[i]);\n"
265 					   "   else if (cs[i].nodeType==3) str += cs[i].nodeValue;\n"
266 					   "  }\n"
267 					   " }\n"
268 					   " return str;\n"
269 					   "}\n"
270 
271 					   "function sortfn(a,b) {\n"
272 					   " var at = get_inner_text(a.cells[sort_column]);\n"
273 					   " var bt = get_inner_text(b.cells[sort_column]);\n"
274 					   " if (a.cells[sort_column].className == 'int') {\n"
275 					   "  return parseInt(at)-parseInt(bt);\n"
276 					   " } else {\n"
277 					   "  aa = at.toLowerCase();\n"
278 					   "  bb = bt.toLowerCase();\n"
279 					   "  if (aa==bb) return 0;\n"
280 					   "  else if (aa<bb) return -1;\n"
281 					   "  else return 1;\n"
282 					   " }\n"
283 					   "}\n"
284 
285 					   "function resort(lnk) {\n"
286 					   " var span = lnk.childNodes[1];\n"
287 					   " var table = lnk.parentNode.parentNode.parentNode.parentNode;\n"
288 					   " var rows = new Array();\n"
289 					   " for (j=1;j<table.rows.length;j++)\n"
290 					   "  rows[j-1] = table.rows[j];\n"
291 					   " sort_column = lnk.parentNode.cellIndex;\n"
292 					   " rows.sort(sortfn);\n"
293 
294 					   " if (prev_span != null) prev_span.innerHTML = '';\n"
295 					   " if (span.getAttribute('sortdir')=='down') {\n"
296 					   "  span.innerHTML = '&uarr;';\n"
297 					   "  span.setAttribute('sortdir','up');\n"
298 					   "  rows.reverse();\n"
299 					   " } else {\n"
300 					   "  span.innerHTML = '&darr;';\n"
301 					   "  span.setAttribute('sortdir','down');\n"
302 					   " }\n"
303 					   " for (i=0;i<rows.length;i++)\n"
304 					   "  table.tBodies[0].appendChild(rows[i]);\n"
305 					   " prev_span = span;\n"
306 					   "}\n"
307 					   "// -->\n"
308 					   "</script>\n"));
309 	}
310 
311 	buffer_append_string_len(b, CONST_STR_LEN(
312 				 " </head>\n"
313 				 " <body>\n"));
314 
315 
316 
317 	/* connection listing */
318 	buffer_append_string_len(b, CONST_STR_LEN("<h1>Server-Status ("));
319 	buffer_append_string_buffer(b, con->conf.server_tag);
320 	buffer_append_string_len(b, CONST_STR_LEN(")</h1>"));
321 
322 	buffer_append_string_len(b, CONST_STR_LEN("<table summary=\"status\" class=\"status\">"));
323 	buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Hostname</td><td class=\"string\">"));
324 	buffer_append_string_buffer(b, con->uri.authority);
325 	buffer_append_string_len(b, CONST_STR_LEN(" ("));
326 	buffer_append_string_buffer(b, con->server_name);
327 	buffer_append_string_len(b, CONST_STR_LEN(")</td></tr>\n"));
328 	buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Uptime</td><td class=\"string\">"));
329 
330 	ts = srv->cur_ts - srv->startup_ts;
331 
332 	days = ts / (60 * 60 * 24);
333 	ts %= (60 * 60 * 24);
334 
335 	hours = ts / (60 * 60);
336 	ts %= (60 * 60);
337 
338 	mins = ts / (60);
339 	ts %= (60);
340 
341 	seconds = ts;
342 
343 	if (days) {
344 		buffer_append_int(b, days);
345 		buffer_append_string_len(b, CONST_STR_LEN(" days "));
346 	}
347 
348 	if (hours) {
349 		buffer_append_int(b, hours);
350 		buffer_append_string_len(b, CONST_STR_LEN(" hours "));
351 	}
352 
353 	if (mins) {
354 		buffer_append_int(b, mins);
355 		buffer_append_string_len(b, CONST_STR_LEN(" min "));
356 	}
357 
358 	buffer_append_int(b, seconds);
359 	buffer_append_string_len(b, CONST_STR_LEN(" s"));
360 
361 	buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"));
362 	buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Started at</td><td class=\"string\">"));
363 
364 	ts = srv->startup_ts;
365 
366 	strftime(buf, sizeof(buf) - 1, "%Y-%m-%d %H:%M:%S", localtime(&ts));
367 	buffer_append_string(b, buf);
368 	buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"));
369 
370 
371 	buffer_append_string_len(b, CONST_STR_LEN("<tr><th colspan=\"2\">absolute (since start)</th></tr>\n"));
372 
373 	buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Requests</td><td class=\"string\">"));
374 	avg = p->abs_requests;
375 
376 	mod_status_get_multiplier(&avg, &multiplier, 1000);
377 
378 	buffer_append_int(b, avg);
379 	buffer_append_string_len(b, CONST_STR_LEN(" "));
380 	if (multiplier)	buffer_append_string_len(b, &multiplier, 1);
381 	buffer_append_string_len(b, CONST_STR_LEN("req</td></tr>\n"));
382 
383 	buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Traffic</td><td class=\"string\">"));
384 	avg = p->abs_traffic_out;
385 
386 	mod_status_get_multiplier(&avg, &multiplier, 1024);
387 
388 	snprintf(buf, sizeof(buf), "%.2f", avg);
389 	buffer_append_string(b, buf);
390 	buffer_append_string_len(b, CONST_STR_LEN(" "));
391 	if (multiplier)	buffer_append_string_len(b, &multiplier, 1);
392 	buffer_append_string_len(b, CONST_STR_LEN("byte</td></tr>\n"));
393 
394 
395 
396 	buffer_append_string_len(b, CONST_STR_LEN("<tr><th colspan=\"2\">average (since start)</th></tr>\n"));
397 
398 	buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Requests</td><td class=\"string\">"));
399 	avg = p->abs_requests / (srv->cur_ts - srv->startup_ts);
400 
401 	mod_status_get_multiplier(&avg, &multiplier, 1000);
402 
403 	buffer_append_int(b, avg);
404 	buffer_append_string_len(b, CONST_STR_LEN(" "));
405 	if (multiplier)	buffer_append_string_len(b, &multiplier, 1);
406 	buffer_append_string_len(b, CONST_STR_LEN("req/s</td></tr>\n"));
407 
408 	buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Traffic</td><td class=\"string\">"));
409 	avg = p->abs_traffic_out / (srv->cur_ts - srv->startup_ts);
410 
411 	mod_status_get_multiplier(&avg, &multiplier, 1024);
412 
413 	snprintf(buf, sizeof(buf), "%.2f", avg);
414 	buffer_append_string(b, buf);
415 	buffer_append_string_len(b, CONST_STR_LEN(" "));
416 	if (multiplier)	buffer_append_string_len(b, &multiplier, 1);
417 	buffer_append_string_len(b, CONST_STR_LEN("byte/s</td></tr>\n"));
418 
419 
420 
421 	buffer_append_string_len(b, CONST_STR_LEN("<tr><th colspan=\"2\">average (5s sliding average)</th></tr>\n"));
422 	for (j = 0, avg = 0; j < 5; j++) {
423 		avg += p->mod_5s_requests[j];
424 	}
425 
426 	avg /= 5;
427 
428 	buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Requests</td><td class=\"string\">"));
429 
430 	mod_status_get_multiplier(&avg, &multiplier, 1000);
431 
432 	buffer_append_int(b, avg);
433 	buffer_append_string_len(b, CONST_STR_LEN(" "));
434 	if (multiplier)	buffer_append_string_len(b, &multiplier, 1);
435 
436 	buffer_append_string_len(b, CONST_STR_LEN("req/s</td></tr>\n"));
437 
438 	for (j = 0, avg = 0; j < 5; j++) {
439 		avg += p->mod_5s_traffic_out[j];
440 	}
441 
442 	avg /= 5;
443 
444 	buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Traffic</td><td class=\"string\">"));
445 
446 	mod_status_get_multiplier(&avg, &multiplier, 1024);
447 
448 	snprintf(buf, sizeof(buf), "%.2f", avg);
449 	buffer_append_string(b, buf);
450 	buffer_append_string_len(b, CONST_STR_LEN(" "));
451 	if (multiplier)	buffer_append_string_len(b, &multiplier, 1);
452 	buffer_append_string_len(b, CONST_STR_LEN("byte/s</td></tr>\n"));
453 
454 	buffer_append_string_len(b, CONST_STR_LEN("</table>\n"));
455 
456 	buffer_append_string_len(b, CONST_STR_LEN("<hr />\n<pre>\n"));
457 
458 	buffer_append_string_len(b, CONST_STR_LEN("<b>"));
459 	buffer_append_int(b, srv->conns->used);
460 	buffer_append_string_len(b, CONST_STR_LEN(" connections</b>\n"));
461 
462 	for (j = 0; j < srv->conns->used; j++) {
463 		connection *c = srv->conns->ptr[j];
464 		const char *state;
465 
466 		if (CON_STATE_READ == c->state && !buffer_string_is_empty(c->request.orig_uri)) {
467 			state = "k";
468 			++cstates[CON_STATE_CLOSE+2];
469 		} else {
470 			state = connection_get_short_state(c->state);
471 			++cstates[(c->state <= CON_STATE_CLOSE ? c->state : CON_STATE_CLOSE+1)];
472 		}
473 
474 		buffer_append_string_len(b, state, 1);
475 
476 		if (((j + 1) % 50) == 0) {
477 			buffer_append_string_len(b, CONST_STR_LEN("\n"));
478 		}
479 	}
480 	buffer_append_string_len(b, CONST_STR_LEN("\n\n<table>\n"));
481 	buffer_append_string_len(b, CONST_STR_LEN("<tr><td style=\"text-align:right\">"));
482 	buffer_append_int(b, cstates[CON_STATE_CLOSE+2]);
483 	buffer_append_string_len(b, CONST_STR_LEN("<td>&nbsp;&nbsp;k = keep-alive</td></tr>\n"));
484 	for (j = 0; j < CON_STATE_CLOSE+2; ++j) {
485 		/*(skip "unknown" state if there are none; there should not be any unknown)*/
486 		if (0 == cstates[j] && j == CON_STATE_CLOSE+1) continue;
487 		buffer_append_string_len(b, CONST_STR_LEN("<tr><td style=\"text-align:right\">"));
488 		buffer_append_int(b, cstates[j]);
489 		buffer_append_string_len(b, CONST_STR_LEN("</td><td>&nbsp;&nbsp;"));
490 		buffer_append_string_len(b, connection_get_short_state(j), 1);
491 		buffer_append_string_len(b, CONST_STR_LEN(" = "));
492 		buffer_append_string(b, connection_get_state(j));
493 		buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"));
494 	}
495 	buffer_append_string_len(b, CONST_STR_LEN("</table>"));
496 
497 	buffer_append_string_len(b, CONST_STR_LEN("\n</pre><hr />\n<h2>Connections</h2>\n"));
498 
499 	buffer_append_string_len(b, CONST_STR_LEN("<table summary=\"status\" class=\"status\">\n"));
500 	buffer_append_string_len(b, CONST_STR_LEN("<tr>"));
501 	mod_status_header_append_sort(b, p_d, "Client IP");
502 	mod_status_header_append_sort(b, p_d, "Read");
503 	mod_status_header_append_sort(b, p_d, "Written");
504 	mod_status_header_append_sort(b, p_d, "State");
505 	mod_status_header_append_sort(b, p_d, "Time");
506 	mod_status_header_append_sort(b, p_d, "Host");
507 	mod_status_header_append_sort(b, p_d, "URI");
508 	mod_status_header_append_sort(b, p_d, "File");
509 	buffer_append_string_len(b, CONST_STR_LEN("</tr>\n"));
510 
511 	for (j = 0; j < srv->conns->used; j++) {
512 		connection *c = srv->conns->ptr[j];
513 
514 		buffer_append_string_len(b, CONST_STR_LEN("<tr><td class=\"string\">"));
515 
516 		buffer_append_string_buffer(b, c->dst_addr_buf);
517 
518 		buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"int\">"));
519 
520 		if (c->request.content_length) {
521 			buffer_append_int(b, c->request_content_queue->bytes_in);
522 			buffer_append_string_len(b, CONST_STR_LEN("/"));
523 			buffer_append_int(b, c->request.content_length);
524 		} else {
525 			buffer_append_string_len(b, CONST_STR_LEN("0/0"));
526 		}
527 
528 		buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"int\">"));
529 
530 		buffer_append_int(b, c->write_queue->bytes_out);
531 		buffer_append_string_len(b, CONST_STR_LEN("/"));
532 		buffer_append_int(b, c->write_queue->bytes_out + chunkqueue_length(c->write_queue));
533 
534 		buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
535 
536 		if (CON_STATE_READ == c->state && !buffer_string_is_empty(c->request.orig_uri)) {
537 			buffer_append_string_len(b, CONST_STR_LEN("keep-alive"));
538 		} else {
539 			buffer_append_string(b, connection_get_state(c->state));
540 		}
541 
542 		buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"int\">"));
543 
544 		buffer_append_int(b, srv->cur_ts - c->request_start);
545 
546 		buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
547 
548 		if (buffer_string_is_empty(c->server_name)) {
549 			buffer_append_string_buffer(b, c->uri.authority);
550 		}
551 		else {
552 			buffer_append_string_buffer(b, c->server_name);
553 		}
554 
555 		buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
556 
557 		if (!buffer_string_is_empty(c->uri.path)) {
558 			buffer_append_string_encoded(b, CONST_BUF_LEN(c->uri.path), ENCODING_HTML);
559 		}
560 
561 		if (!buffer_string_is_empty(c->uri.query)) {
562 			buffer_append_string_len(b, CONST_STR_LEN("?"));
563 			buffer_append_string_encoded(b, CONST_BUF_LEN(c->uri.query), ENCODING_HTML);
564 		}
565 
566 		if (!buffer_string_is_empty(c->request.orig_uri)) {
567 			buffer_append_string_len(b, CONST_STR_LEN(" ("));
568 			buffer_append_string_encoded(b, CONST_BUF_LEN(c->request.orig_uri), ENCODING_HTML);
569 			buffer_append_string_len(b, CONST_STR_LEN(")"));
570 		}
571 		buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
572 
573 		buffer_append_string_buffer(b, c->physical.path);
574 
575 		buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"));
576 	}
577 
578 
579 	buffer_append_string_len(b, CONST_STR_LEN(
580 		      "</table>\n"));
581 
582 
583 	buffer_append_string_len(b, CONST_STR_LEN(
584 		      " </body>\n"
585 		      "</html>\n"
586 		      ));
587 
588 	chunkqueue_append_buffer_commit(con->write_queue);
589 
590 	http_header_response_set(con, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
591 
592 	return 0;
593 }
594 
595 
596 static handler_t mod_status_handle_server_status_text(server *srv, connection *con, void *p_d) {
597 	plugin_data *p = p_d;
598 	buffer *b = chunkqueue_append_buffer_open(con->write_queue);
599 	double avg;
600 	time_t ts;
601 	char buf[32];
602 	unsigned int k;
603 	unsigned int l;
604 
605 	/* output total number of requests */
606 	buffer_append_string_len(b, CONST_STR_LEN("Total Accesses: "));
607 	avg = p->abs_requests;
608 	snprintf(buf, sizeof(buf) - 1, "%.0f", avg);
609 	buffer_append_string(b, buf);
610 	buffer_append_string_len(b, CONST_STR_LEN("\n"));
611 
612 	/* output total traffic out in kbytes */
613 	buffer_append_string_len(b, CONST_STR_LEN("Total kBytes: "));
614 	avg = p->abs_traffic_out / 1024;
615 	snprintf(buf, sizeof(buf) - 1, "%.0f", avg);
616 	buffer_append_string(b, buf);
617 	buffer_append_string_len(b, CONST_STR_LEN("\n"));
618 
619 	/* output uptime */
620 	buffer_append_string_len(b, CONST_STR_LEN("Uptime: "));
621 	ts = srv->cur_ts - srv->startup_ts;
622 	buffer_append_int(b, ts);
623 	buffer_append_string_len(b, CONST_STR_LEN("\n"));
624 
625 	/* output busy servers */
626 	buffer_append_string_len(b, CONST_STR_LEN("BusyServers: "));
627 	buffer_append_int(b, srv->conns->used);
628 	buffer_append_string_len(b, CONST_STR_LEN("\n"));
629 
630 	buffer_append_string_len(b, CONST_STR_LEN("IdleServers: "));
631 	buffer_append_int(b, srv->conns->size - srv->conns->used);
632 	buffer_append_string_len(b, CONST_STR_LEN("\n"));
633 
634 	/* output scoreboard */
635 	buffer_append_string_len(b, CONST_STR_LEN("Scoreboard: "));
636 	for (k = 0; k < srv->conns->used; k++) {
637 		connection *c = srv->conns->ptr[k];
638 		const char *state =
639 		  (CON_STATE_READ == c->state && !buffer_string_is_empty(c->request.orig_uri))
640 		    ? "k"
641 		    : connection_get_short_state(c->state);
642 		buffer_append_string_len(b, state, 1);
643 	}
644 	for (l = 0; l < srv->conns->size - srv->conns->used; l++) {
645 		buffer_append_string_len(b, CONST_STR_LEN("_"));
646 	}
647 	buffer_append_string_len(b, CONST_STR_LEN("\n"));
648 
649 	chunkqueue_append_buffer_commit(con->write_queue);
650 
651 	/* set text/plain output */
652 	http_header_response_set(con, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain"));
653 
654 	return 0;
655 }
656 
657 
658 static handler_t mod_status_handle_server_status_json(server *srv, connection *con, void *p_d) {
659 	plugin_data *p = p_d;
660 	buffer *b = chunkqueue_append_buffer_open(con->write_queue);
661 	double avg;
662 	time_t ts;
663 	char buf[32];
664 	size_t j;
665 	unsigned int jsonp = 0;
666 
667 	if (buffer_string_length(con->uri.query) >= sizeof("jsonp=")-1
668 	   && 0 == memcmp(con->uri.query->ptr, CONST_STR_LEN("jsonp="))) {
669 		/* not a full parse of query string for multiple parameters,
670 		* not URL-decoding param and not XML-encoding (XSS protection),
671 		* so simply ensure that json function name isalnum() or '_' */
672 		const char *f = con->uri.query->ptr + sizeof("jsonp=")-1;
673 		int len = 0;
674 		while (light_isalnum(f[len]) || f[len] == '_') ++len;
675 		if (0 != len && light_isalpha(f[0]) && f[len] == '\0') {
676 			buffer_append_string_len(b, f, len);
677 			buffer_append_string_len(b, CONST_STR_LEN("("));
678 			jsonp = 1;
679 		}
680 	}
681 
682 	/* output total number of requests */
683 	buffer_append_string_len(b, CONST_STR_LEN("{\n\t\"RequestsTotal\": "));
684 	avg = p->abs_requests;
685 	snprintf(buf, sizeof(buf) - 1, "%.0f", avg);
686 	buffer_append_string(b, buf);
687 	buffer_append_string_len(b, CONST_STR_LEN(",\n"));
688 
689 	/* output total traffic out in kbytes */
690 	buffer_append_string_len(b, CONST_STR_LEN("\t\"TrafficTotal\": "));
691 	avg = p->abs_traffic_out / 1024;
692 	snprintf(buf, sizeof(buf) - 1, "%.0f", avg);
693 	buffer_append_string(b, buf);
694 	buffer_append_string_len(b, CONST_STR_LEN(",\n"));
695 
696 	/* output uptime */
697 	buffer_append_string_len(b, CONST_STR_LEN("\t\"Uptime\": "));
698 	ts = srv->cur_ts - srv->startup_ts;
699 	buffer_append_int(b, ts);
700 	buffer_append_string_len(b, CONST_STR_LEN(",\n"));
701 
702 	/* output busy servers */
703 	buffer_append_string_len(b, CONST_STR_LEN("\t\"BusyServers\": "));
704 	buffer_append_int(b, srv->conns->used);
705 	buffer_append_string_len(b, CONST_STR_LEN(",\n"));
706 
707 	buffer_append_string_len(b, CONST_STR_LEN("\t\"IdleServers\": "));
708 	buffer_append_int(b, srv->conns->size - srv->conns->used);
709 	buffer_append_string_len(b, CONST_STR_LEN(",\n"));
710 
711 	for (j = 0, avg = 0; j < 5; j++) {
712 		avg += p->mod_5s_requests[j];
713 	}
714 
715 	avg /= 5;
716 
717 	buffer_append_string_len(b, CONST_STR_LEN("\t\"RequestAverage5s\":"));
718 	buffer_append_int(b, avg);
719 	buffer_append_string_len(b, CONST_STR_LEN(",\n"));
720 
721 	for (j = 0, avg = 0; j < 5; j++) {
722 		avg += p->mod_5s_traffic_out[j];
723 	}
724 
725 	avg /= 5;
726 
727 	buffer_append_string_len(b, CONST_STR_LEN("\t\"TrafficAverage5s\":"));
728 	buffer_append_int(b, avg / 1024); /* kbps */
729 	buffer_append_string_len(b, CONST_STR_LEN("\n}"));
730 
731 	if (jsonp) buffer_append_string_len(b, CONST_STR_LEN(");"));
732 
733 	chunkqueue_append_buffer_commit(con->write_queue);
734 
735 	/* set text/plain output */
736 	http_header_response_set(con, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/javascript"));
737 
738 	return 0;
739 }
740 
741 
742 static handler_t mod_status_handle_server_statistics(server *srv, connection *con, void *p_d) {
743 	buffer *b;
744 	size_t i;
745 	array *st = srv->status;
746 	UNUSED(p_d);
747 
748 	if (0 == st->used) {
749 		/* we have nothing to send */
750 		con->http_status = 204;
751 		con->file_finished = 1;
752 
753 		return HANDLER_FINISHED;
754 	}
755 
756 	b = chunkqueue_append_buffer_open(con->write_queue);
757 	for (i = 0; i < st->used; i++) {
758 		size_t ndx = st->sorted[i];
759 
760 		buffer_append_string_buffer(b, st->data[ndx]->key);
761 		buffer_append_string_len(b, CONST_STR_LEN(": "));
762 		buffer_append_int(b, ((data_integer *)(st->data[ndx]))->value);
763 		buffer_append_string_len(b, CONST_STR_LEN("\n"));
764 	}
765 	chunkqueue_append_buffer_commit(con->write_queue);
766 
767 	http_header_response_set(con, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain"));
768 
769 	con->http_status = 200;
770 	con->file_finished = 1;
771 
772 	return HANDLER_FINISHED;
773 }
774 
775 
776 static handler_t mod_status_handle_server_status(server *srv, connection *con, void *p_d) {
777 
778 	if (buffer_is_equal_string(con->uri.query, CONST_STR_LEN("auto"))) {
779 		mod_status_handle_server_status_text(srv, con, p_d);
780 	} else if (buffer_string_length(con->uri.query) >= sizeof("json")-1
781 		   && 0 == memcmp(con->uri.query->ptr, CONST_STR_LEN("json"))) {
782 		mod_status_handle_server_status_json(srv, con, p_d);
783 	} else {
784 		mod_status_handle_server_status_html(srv, con, p_d);
785 	}
786 
787 	con->http_status = 200;
788 	con->file_finished = 1;
789 
790 	return HANDLER_FINISHED;
791 }
792 
793 
794 static handler_t mod_status_handle_server_config(server *srv, connection *con, void *p_d) {
795 	plugin_data *p = p_d;
796 	buffer *b = chunkqueue_append_buffer_open(con->write_queue);
797 	buffer *m = p->module_list;
798 	size_t i;
799 
800 	buffer_copy_string_len(b, CONST_STR_LEN(
801 			   "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
802 			   "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
803 			   "         \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
804 			   "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
805 			   " <head>\n"
806 			   "  <title>Status</title>\n"
807 			   " </head>\n"
808 			   " <body>\n"
809 			   "  <h1>"));
810 	buffer_append_string_buffer(b, con->conf.server_tag);
811 	buffer_append_string_len(b, CONST_STR_LEN(
812 			   "</h1>\n"
813 			   "  <table summary=\"status\" border=\"1\">\n"));
814 
815 	mod_status_header_append(b, "Server-Features");
816 #ifdef HAVE_PCRE_H
817 	mod_status_row_append(b, "RegEx Conditionals", "enabled");
818 #else
819 	mod_status_row_append(b, "RegEx Conditionals", "disabled - pcre missing");
820 #endif
821 	mod_status_header_append(b, "Network Engine");
822 
823 	mod_status_row_append(b, "fd-Event-Handler", srv->srvconf.event_handler->ptr);
824 
825 	mod_status_header_append(b, "Config-File-Settings");
826 
827 	for (i = 0; i < srv->plugins.used; i++) {
828 		plugin **ps = srv->plugins.ptr;
829 
830 		plugin *pl = ps[i];
831 
832 		if (i == 0) {
833 			buffer_copy_buffer(m, pl->name);
834 		} else {
835 			buffer_append_string_len(m, CONST_STR_LEN("<br />"));
836 			buffer_append_string_buffer(m, pl->name);
837 		}
838 	}
839 
840 	mod_status_row_append(b, "Loaded Modules", m->ptr);
841 
842 	buffer_append_string_len(b, CONST_STR_LEN("  </table>\n"));
843 
844 	buffer_append_string_len(b, CONST_STR_LEN(
845 		      " </body>\n"
846 		      "</html>\n"
847 		      ));
848 
849 	chunkqueue_append_buffer_commit(con->write_queue);
850 
851 	http_header_response_set(con, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
852 
853 	con->http_status = 200;
854 	con->file_finished = 1;
855 
856 	return HANDLER_FINISHED;
857 }
858 
859 #define PATCH(x) \
860 	p->conf.x = s->x;
861 static int mod_status_patch_connection(server *srv, connection *con, plugin_data *p) {
862 	size_t i, j;
863 	plugin_config *s = p->config_storage[0];
864 
865 	PATCH(status_url);
866 	PATCH(config_url);
867 	PATCH(sort);
868 	PATCH(statistics_url);
869 
870 	/* skip the first, the global context */
871 	for (i = 1; i < srv->config_context->used; i++) {
872 		data_config *dc = (data_config *)srv->config_context->data[i];
873 		s = p->config_storage[i];
874 
875 		/* condition didn't match */
876 		if (!config_check_cond(srv, con, dc)) continue;
877 
878 		/* merge config */
879 		for (j = 0; j < dc->value->used; j++) {
880 			data_unset *du = dc->value->data[j];
881 
882 			if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.status-url"))) {
883 				PATCH(status_url);
884 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.config-url"))) {
885 				PATCH(config_url);
886 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.enable-sort"))) {
887 				PATCH(sort);
888 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.statistics-url"))) {
889 				PATCH(statistics_url);
890 			}
891 		}
892 	}
893 
894 	return 0;
895 }
896 
897 static handler_t mod_status_handler(server *srv, connection *con, void *p_d) {
898 	plugin_data *p = p_d;
899 
900 	if (con->mode != DIRECT) return HANDLER_GO_ON;
901 
902 	mod_status_patch_connection(srv, con, p);
903 
904 	if (!buffer_string_is_empty(p->conf.status_url) &&
905 	    buffer_is_equal(p->conf.status_url, con->uri.path)) {
906 		return mod_status_handle_server_status(srv, con, p_d);
907 	} else if (!buffer_string_is_empty(p->conf.config_url) &&
908 	    buffer_is_equal(p->conf.config_url, con->uri.path)) {
909 		return mod_status_handle_server_config(srv, con, p_d);
910 	} else if (!buffer_string_is_empty(p->conf.statistics_url) &&
911 	    buffer_is_equal(p->conf.statistics_url, con->uri.path)) {
912 		return mod_status_handle_server_statistics(srv, con, p_d);
913 	}
914 
915 	return HANDLER_GO_ON;
916 }
917 
918 TRIGGER_FUNC(mod_status_trigger) {
919 	plugin_data *p = p_d;
920 	size_t i;
921 
922 	/* check all connections */
923 	for (i = 0; i < srv->conns->used; i++) {
924 		connection *c = srv->conns->ptr[i];
925 
926 		p->bytes_written += c->bytes_written_cur_second;
927 	}
928 
929 	/* a sliding average */
930 	p->mod_5s_traffic_out[p->mod_5s_ndx] = p->bytes_written;
931 	p->mod_5s_requests   [p->mod_5s_ndx] = p->requests;
932 
933 	p->mod_5s_ndx = (p->mod_5s_ndx+1) % 5;
934 
935 	p->abs_traffic_out += p->bytes_written;
936 	p->rel_traffic_out += p->bytes_written;
937 
938 	p->bytes_written = 0;
939 
940 	/* reset storage - second */
941 	p->traffic_out = 0;
942 	p->requests    = 0;
943 
944 	return HANDLER_GO_ON;
945 }
946 
947 REQUESTDONE_FUNC(mod_status_account) {
948 	plugin_data *p = p_d;
949 
950 	UNUSED(srv);
951 
952 	p->requests++;
953 	p->rel_requests++;
954 	p->abs_requests++;
955 
956 	p->bytes_written += con->bytes_written_cur_second;
957 
958 	return HANDLER_GO_ON;
959 }
960 
961 int mod_status_plugin_init(plugin *p);
962 int mod_status_plugin_init(plugin *p) {
963 	p->version     = LIGHTTPD_VERSION_ID;
964 	p->name        = buffer_init_string("status");
965 
966 	p->init        = mod_status_init;
967 	p->cleanup     = mod_status_free;
968 	p->set_defaults= mod_status_set_defaults;
969 
970 	p->handle_uri_clean    = mod_status_handler;
971 	p->handle_trigger      = mod_status_trigger;
972 	p->handle_request_done = mod_status_account;
973 
974 	p->data        = NULL;
975 
976 	return 0;
977 }
978