fix errors detected by Coverity Scan
[lighttpd.git] / src / mod_accesslog.c
blob45039ce05a8fb79fc140968cf19ec75dcf20dc6b
1 #include "first.h"
3 #include "base.h"
4 #include "log.h"
5 #include "buffer.h"
7 #include "plugin.h"
9 #include "inet_ntop_cache.h"
11 #include "sys-socket.h"
13 #include <sys/types.h>
14 #include <sys/stat.h>
16 #include <ctype.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <fcntl.h>
20 #include <unistd.h>
21 #include <errno.h>
22 #include <time.h>
24 #include <stdio.h>
26 #ifdef HAVE_SYSLOG_H
27 # include <syslog.h>
28 #endif
30 typedef struct {
31 char key;
32 enum {
33 FORMAT_UNSET,
34 FORMAT_UNSUPPORTED,
35 FORMAT_PERCENT,
36 FORMAT_REMOTE_HOST,
37 FORMAT_REMOTE_IDENT,
38 FORMAT_REMOTE_USER,
39 FORMAT_TIMESTAMP,
40 FORMAT_REQUEST_LINE,
41 FORMAT_STATUS,
42 FORMAT_BYTES_OUT_NO_HEADER,
43 FORMAT_HEADER,
45 FORMAT_REMOTE_ADDR,
46 FORMAT_LOCAL_ADDR,
47 FORMAT_COOKIE,
48 FORMAT_TIME_USED_MS,
49 FORMAT_ENV,
50 FORMAT_FILENAME,
51 FORMAT_REQUEST_PROTOCOL,
52 FORMAT_REQUEST_METHOD,
53 FORMAT_SERVER_PORT,
54 FORMAT_QUERY_STRING,
55 FORMAT_TIME_USED,
56 FORMAT_URL,
57 FORMAT_SERVER_NAME,
58 FORMAT_HTTP_HOST,
59 FORMAT_CONNECTION_STATUS,
60 FORMAT_BYTES_IN,
61 FORMAT_BYTES_OUT,
63 FORMAT_RESPONSE_HEADER
64 } type;
65 } format_mapping;
67 /**
70 * "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
74 static const format_mapping fmap[] =
76 { '%', FORMAT_PERCENT },
77 { 'h', FORMAT_REMOTE_HOST },
78 { 'l', FORMAT_REMOTE_IDENT },
79 { 'u', FORMAT_REMOTE_USER },
80 { 't', FORMAT_TIMESTAMP },
81 { 'r', FORMAT_REQUEST_LINE },
82 { 's', FORMAT_STATUS },
83 { 'b', FORMAT_BYTES_OUT_NO_HEADER },
84 { 'i', FORMAT_HEADER },
86 { 'a', FORMAT_REMOTE_ADDR },
87 { 'A', FORMAT_LOCAL_ADDR },
88 { 'B', FORMAT_BYTES_OUT_NO_HEADER },
89 { 'C', FORMAT_COOKIE },
90 { 'D', FORMAT_TIME_USED_MS },
91 { 'e', FORMAT_ENV },
92 { 'f', FORMAT_FILENAME },
93 { 'H', FORMAT_REQUEST_PROTOCOL },
94 { 'm', FORMAT_REQUEST_METHOD },
95 { 'n', FORMAT_UNSUPPORTED }, /* we have no notes */
96 { 'p', FORMAT_SERVER_PORT },
97 { 'P', FORMAT_UNSUPPORTED }, /* we are only one process */
98 { 'q', FORMAT_QUERY_STRING },
99 { 'T', FORMAT_TIME_USED },
100 { 'U', FORMAT_URL }, /* w/o querystring */
101 { 'v', FORMAT_SERVER_NAME },
102 { 'V', FORMAT_HTTP_HOST },
103 { 'X', FORMAT_CONNECTION_STATUS },
104 { 'I', FORMAT_BYTES_IN },
105 { 'O', FORMAT_BYTES_OUT },
107 { 'o', FORMAT_RESPONSE_HEADER },
109 { '\0', FORMAT_UNSET }
113 typedef struct {
114 enum { FIELD_UNSET, FIELD_STRING, FIELD_FORMAT } type;
116 buffer *string;
117 int field;
118 } format_field;
120 typedef struct {
121 format_field **ptr;
123 size_t used;
124 size_t size;
125 } format_fields;
127 typedef struct {
128 buffer *access_logfile;
129 int log_access_fd;
130 buffer *access_logbuffer; /* each logfile has a separate buffer */
132 unsigned short use_syslog; /* syslog has global buffer */
133 unsigned short syslog_level;
135 buffer *format;
137 time_t last_generated_accesslog_ts;
138 time_t *last_generated_accesslog_ts_ptr;
140 buffer *ts_accesslog_str;
141 buffer *ts_accesslog_fmt_str;
142 unsigned short append_tz_offset;
144 format_fields *parsed_format;
145 } plugin_config;
147 typedef struct {
148 PLUGIN_DATA;
150 plugin_config **config_storage;
151 plugin_config conf;
153 buffer *syslog_logbuffer; /* syslog has global buffer. no caching, always written directly */
154 } plugin_data;
156 INIT_FUNC(mod_accesslog_init) {
157 plugin_data *p;
159 p = calloc(1, sizeof(*p));
160 p->syslog_logbuffer = buffer_init();
162 return p;
165 static void accesslog_write_all(server *srv, const buffer *filename, int fd, const void* buf, size_t count) {
166 if (-1 == write_all(fd, buf, count)) {
167 log_error_write(srv, __FILE__, __LINE__, "sbs",
168 "writing access log entry failed:", filename, strerror(errno));
172 static void accesslog_append_escaped(buffer *dest, buffer *str) {
173 char *ptr, *start, *end;
175 /* replaces non-printable chars with \xHH where HH is the hex representation of the byte */
176 /* exceptions: " => \", \ => \\, whitespace chars => \n \t etc. */
177 if (buffer_string_is_empty(str)) return;
178 buffer_string_prepare_append(dest, buffer_string_length(str));
180 for (ptr = start = str->ptr, end = str->ptr + buffer_string_length(str); ptr < end; ptr++) {
181 unsigned char const c = (unsigned char) *ptr;
182 if (c >= ' ' && c <= '~' && c != '"' && c != '\\') {
183 /* nothing to change, add later as one block */
184 } else {
185 /* copy previous part */
186 if (start < ptr) {
187 buffer_append_string_len(dest, start, ptr - start);
189 start = ptr + 1;
191 switch (c) {
192 case '"':
193 BUFFER_APPEND_STRING_CONST(dest, "\\\"");
194 break;
195 case '\\':
196 BUFFER_APPEND_STRING_CONST(dest, "\\\\");
197 break;
198 case '\b':
199 BUFFER_APPEND_STRING_CONST(dest, "\\b");
200 break;
201 case '\n':
202 BUFFER_APPEND_STRING_CONST(dest, "\\n");
203 break;
204 case '\r':
205 BUFFER_APPEND_STRING_CONST(dest, "\\r");
206 break;
207 case '\t':
208 BUFFER_APPEND_STRING_CONST(dest, "\\t");
209 break;
210 case '\v':
211 BUFFER_APPEND_STRING_CONST(dest, "\\v");
212 break;
213 default: {
214 /* non printable char => \xHH */
215 char hh[5] = {'\\','x',0,0,0};
216 char h = c / 16;
217 hh[2] = (h > 9) ? (h - 10 + 'A') : (h + '0');
218 h = c % 16;
219 hh[3] = (h > 9) ? (h - 10 + 'A') : (h + '0');
220 buffer_append_string_len(dest, &hh[0], 4);
222 break;
227 if (start < end) {
228 buffer_append_string_len(dest, start, end - start);
232 static int accesslog_parse_format(server *srv, format_fields *fields, buffer *format) {
233 size_t i, j, k = 0, start = 0;
235 if (buffer_is_empty(format)) return -1;
237 for (i = 0; i < buffer_string_length(format); i++) {
238 switch(format->ptr[i]) {
239 case '%':
240 if (i > 0 && start != i) {
241 /* copy the string before this % */
242 if (fields->size == 0) {
243 fields->size = 16;
244 fields->used = 0;
245 fields->ptr = malloc(fields->size * sizeof(format_field * ));
246 } else if (fields->used == fields->size) {
247 fields->size += 16;
248 fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_field * ));
251 fields->ptr[fields->used] = malloc(sizeof(format_field));
252 fields->ptr[fields->used]->type = FIELD_STRING;
253 fields->ptr[fields->used]->string = buffer_init();
255 buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + start, i - start);
257 fields->used++;
260 /* we need a new field */
262 if (fields->size == 0) {
263 fields->size = 16;
264 fields->used = 0;
265 fields->ptr = malloc(fields->size * sizeof(format_field * ));
266 } else if (fields->used == fields->size) {
267 fields->size += 16;
268 fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_field * ));
271 /* search for the terminating command */
272 switch (format->ptr[i+1]) {
273 case '>':
274 case '<':
275 /* after the } has to be a character */
276 if (format->ptr[i+2] == '\0') {
277 log_error_write(srv, __FILE__, __LINE__, "s", "%< and %> have to be followed by a format-specifier");
278 return -1;
282 for (j = 0; fmap[j].key != '\0'; j++) {
283 if (fmap[j].key != format->ptr[i+2]) continue;
285 /* found key */
287 fields->ptr[fields->used] = malloc(sizeof(format_field));
288 fields->ptr[fields->used]->type = FIELD_FORMAT;
289 fields->ptr[fields->used]->field = fmap[j].type;
290 fields->ptr[fields->used]->string = NULL;
292 fields->used++;
294 break;
297 if (fmap[j].key == '\0') {
298 log_error_write(srv, __FILE__, __LINE__, "s", "%< and %> have to be followed by a valid format-specifier");
299 return -1;
302 start = i + 3;
303 i = start - 1; /* skip the string */
305 break;
306 case '{':
307 /* go forward to } */
309 for (k = i+2; k < buffer_string_length(format); k++) {
310 if (format->ptr[k] == '}') break;
313 if (k == buffer_string_length(format)) {
314 log_error_write(srv, __FILE__, __LINE__, "s", "%{ has to be terminated by a }");
315 return -1;
318 /* after the } has to be a character */
319 if (format->ptr[k+1] == '\0') {
320 log_error_write(srv, __FILE__, __LINE__, "s", "%{...} has to be followed by a format-specifier");
321 return -1;
324 if (k == i + 2) {
325 log_error_write(srv, __FILE__, __LINE__, "s", "%{...} has to be contain a string");
326 return -1;
329 for (j = 0; fmap[j].key != '\0'; j++) {
330 if (fmap[j].key != format->ptr[k+1]) continue;
332 /* found key */
334 fields->ptr[fields->used] = malloc(sizeof(format_field));
335 fields->ptr[fields->used]->type = FIELD_FORMAT;
336 fields->ptr[fields->used]->field = fmap[j].type;
337 fields->ptr[fields->used]->string = buffer_init();
339 buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + i + 2, k - (i + 2));
341 fields->used++;
343 break;
346 if (fmap[j].key == '\0') {
347 log_error_write(srv, __FILE__, __LINE__, "s", "%{...} has to be followed by a valid format-specifier");
348 return -1;
351 start = k + 2;
352 i = start - 1; /* skip the string */
354 break;
355 default:
356 /* after the % has to be a character */
357 if (format->ptr[i+1] == '\0') {
358 log_error_write(srv, __FILE__, __LINE__, "s", "% has to be followed by a format-specifier");
359 return -1;
362 for (j = 0; fmap[j].key != '\0'; j++) {
363 if (fmap[j].key != format->ptr[i+1]) continue;
365 /* found key */
367 fields->ptr[fields->used] = malloc(sizeof(format_field));
368 fields->ptr[fields->used]->type = FIELD_FORMAT;
369 fields->ptr[fields->used]->field = fmap[j].type;
370 fields->ptr[fields->used]->string = NULL;
372 fields->used++;
374 break;
377 if (fmap[j].key == '\0') {
378 log_error_write(srv, __FILE__, __LINE__, "s", "% has to be followed by a valid format-specifier");
379 return -1;
382 start = i + 2;
383 i = start - 1; /* skip the string */
385 break;
388 break;
392 if (start < i) {
393 /* copy the string */
394 if (fields->size == 0) {
395 fields->size = 16;
396 fields->used = 0;
397 fields->ptr = malloc(fields->size * sizeof(format_field * ));
398 } else if (fields->used == fields->size) {
399 fields->size += 16;
400 fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_field * ));
403 fields->ptr[fields->used] = malloc(sizeof(format_field));
404 fields->ptr[fields->used]->type = FIELD_STRING;
405 fields->ptr[fields->used]->string = buffer_init();
407 buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + start, i - start);
409 fields->used++;
412 return 0;
415 FREE_FUNC(mod_accesslog_free) {
416 plugin_data *p = p_d;
417 size_t i;
419 if (!p) return HANDLER_GO_ON;
421 if (p->config_storage) {
423 for (i = 0; i < srv->config_context->used; i++) {
424 plugin_config *s = p->config_storage[i];
426 if (NULL == s) continue;
428 if (!buffer_string_is_empty(s->access_logbuffer)) {
429 if (s->log_access_fd != -1) {
430 accesslog_write_all(srv, s->access_logfile, s->log_access_fd, CONST_BUF_LEN(s->access_logbuffer));
434 if (s->log_access_fd != -1) close(s->log_access_fd);
436 buffer_free(s->ts_accesslog_str);
437 buffer_free(s->ts_accesslog_fmt_str);
438 buffer_free(s->access_logbuffer);
439 buffer_free(s->format);
440 buffer_free(s->access_logfile);
442 if (s->parsed_format) {
443 size_t j;
444 for (j = 0; j < s->parsed_format->used; j++) {
445 if (s->parsed_format->ptr[j]->string) buffer_free(s->parsed_format->ptr[j]->string);
446 free(s->parsed_format->ptr[j]);
448 free(s->parsed_format->ptr);
449 free(s->parsed_format);
452 free(s);
455 free(p->config_storage);
458 if (p->syslog_logbuffer) buffer_free(p->syslog_logbuffer);
459 free(p);
461 return HANDLER_GO_ON;
464 SETDEFAULTS_FUNC(log_access_open) {
465 plugin_data *p = p_d;
466 size_t i = 0;
468 config_values_t cv[] = {
469 { "accesslog.filename", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
470 { "accesslog.use-syslog", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },
471 { "accesslog.format", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
472 { "accesslog.syslog-level", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION },
473 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
476 if (!p) return HANDLER_ERROR;
478 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
480 for (i = 0; i < srv->config_context->used; i++) {
481 data_config const* config = (data_config const*)srv->config_context->data[i];
482 plugin_config *s;
484 s = calloc(1, sizeof(plugin_config));
485 s->access_logfile = buffer_init();
486 s->format = buffer_init();
487 s->access_logbuffer = buffer_init();
488 s->ts_accesslog_str = buffer_init();
489 s->ts_accesslog_fmt_str = buffer_init();
490 s->log_access_fd = -1;
491 s->last_generated_accesslog_ts = 0;
492 s->last_generated_accesslog_ts_ptr = &(s->last_generated_accesslog_ts);
493 s->syslog_level = LOG_INFO;
496 cv[0].destination = s->access_logfile;
497 cv[1].destination = &(s->use_syslog);
498 cv[2].destination = s->format;
499 cv[3].destination = &(s->syslog_level);
501 p->config_storage[i] = s;
503 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
504 return HANDLER_ERROR;
507 if (i == 0 && buffer_string_is_empty(s->format)) {
508 /* set a default logfile string */
510 buffer_copy_string_len(s->format, CONST_STR_LEN("%h %V %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""));
513 /* parse */
515 if (!buffer_is_empty(s->format)) {
516 size_t j, count;
518 s->parsed_format = calloc(1, sizeof(*(s->parsed_format)));
520 if (-1 == accesslog_parse_format(srv, s->parsed_format, s->format)) {
522 log_error_write(srv, __FILE__, __LINE__, "sb",
523 "parsing accesslog-definition failed:", s->format);
525 return HANDLER_ERROR;
528 /* make sure they didn't try to send the timestamp in twice...
529 * also, save the format string in a different variable (this
530 * will save a few conditionals later)
532 count = 0;
533 for (j = 0; j < s->parsed_format->used; j++) {
534 if (FIELD_FORMAT == s->parsed_format->ptr[j]->type) {
535 if (FORMAT_TIMESTAMP == s->parsed_format->ptr[j]->field) {
536 if (!buffer_string_is_empty(s->parsed_format->ptr[j]->string)) {
537 buffer_copy_string(s->ts_accesslog_fmt_str, s->parsed_format->ptr[j]->string->ptr);
540 if (++count > 1) {
541 log_error_write(srv, __FILE__, __LINE__, "sb",
542 "you may not use the timestamp twice in the same access log:", s->format);
544 return HANDLER_ERROR;
550 #if 0
551 /* debugging */
552 for (j = 0; j < s->parsed_format->used; j++) {
553 switch (s->parsed_format->ptr[j]->type) {
554 case FIELD_FORMAT:
555 log_error_write(srv, __FILE__, __LINE__, "ssds",
556 "config:", "format", s->parsed_format->ptr[j]->field,
557 s->parsed_format->ptr[j]->string ?
558 s->parsed_format->ptr[j]->string->ptr : "" );
559 break;
560 case FIELD_STRING:
561 log_error_write(srv, __FILE__, __LINE__, "ssbs", "config:", "string '", s->parsed_format->ptr[j]->string, "'");
562 break;
563 default:
564 break;
567 #endif
570 s->append_tz_offset = 0;
571 if (buffer_string_is_empty(s->ts_accesslog_fmt_str)) {
572 #if defined(HAVE_STRUCT_TM_GMTOFF)
573 BUFFER_COPY_STRING_CONST(s->ts_accesslog_fmt_str, "[%d/%b/%Y:%H:%M:%S ");
574 s->append_tz_offset = 1;
575 #else
576 BUFFER_COPY_STRING_CONST(s->ts_accesslog_fmt_str, "[%d/%b/%Y:%H:%M:%S +0000]");
577 #endif
580 if (s->use_syslog) {
581 /* ignore the next checks */
582 continue;
585 if (buffer_string_is_empty(s->access_logfile)) continue;
587 if (srv->srvconf.preflight_check) continue;
589 if (-1 == (s->log_access_fd = open_logfile_or_pipe(srv, s->access_logfile->ptr)))
590 return HANDLER_ERROR;
594 return HANDLER_GO_ON;
597 SIGHUP_FUNC(log_access_cycle) {
598 plugin_data *p = p_d;
599 size_t i;
601 if (!p->config_storage) return HANDLER_GO_ON;
603 for (i = 0; i < srv->config_context->used; i++) {
604 plugin_config *s = p->config_storage[i];
606 if (!buffer_string_is_empty(s->access_logbuffer)) {
607 if (s->log_access_fd != -1) {
608 accesslog_write_all(srv, s->access_logfile, s->log_access_fd, CONST_BUF_LEN(s->access_logbuffer));
611 buffer_reset(s->access_logbuffer);
614 if (s->use_syslog == 0
615 && !buffer_string_is_empty(s->access_logfile)
616 && s->access_logfile->ptr[0] != '|') {
618 if (-1 != s->log_access_fd) close(s->log_access_fd);
620 if (-1 == (s->log_access_fd =
621 open(s->access_logfile->ptr, O_APPEND | O_WRONLY | O_CREAT | O_LARGEFILE, 0644))) {
623 log_error_write(srv, __FILE__, __LINE__, "ss", "cycling access-log failed:", strerror(errno));
625 return HANDLER_ERROR;
627 fd_close_on_exec(s->log_access_fd);
631 return HANDLER_GO_ON;
634 #define PATCH(x) \
635 p->conf.x = s->x;
636 static int mod_accesslog_patch_connection(server *srv, connection *con, plugin_data *p) {
637 size_t i, j;
638 plugin_config *s = p->config_storage[0];
640 PATCH(access_logfile);
641 PATCH(format);
642 PATCH(log_access_fd);
643 PATCH(last_generated_accesslog_ts_ptr);
644 PATCH(access_logbuffer);
645 PATCH(ts_accesslog_str);
646 PATCH(ts_accesslog_fmt_str);
647 PATCH(append_tz_offset);
648 PATCH(parsed_format);
649 PATCH(use_syslog);
650 PATCH(syslog_level);
652 /* skip the first, the global context */
653 for (i = 1; i < srv->config_context->used; i++) {
654 data_config *dc = (data_config *)srv->config_context->data[i];
655 s = p->config_storage[i];
657 /* condition didn't match */
658 if (!config_check_cond(srv, con, dc)) continue;
660 /* merge config */
661 for (j = 0; j < dc->value->used; j++) {
662 data_unset *du = dc->value->data[j];
664 if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.filename"))) {
665 PATCH(access_logfile);
666 PATCH(log_access_fd);
667 PATCH(access_logbuffer);
668 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.format"))) {
669 PATCH(format);
670 PATCH(parsed_format);
671 PATCH(last_generated_accesslog_ts_ptr);
672 PATCH(ts_accesslog_str);
673 PATCH(ts_accesslog_fmt_str);
674 PATCH(append_tz_offset);
675 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.use-syslog"))) {
676 PATCH(use_syslog);
677 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.syslog-level"))) {
678 PATCH(syslog_level);
683 return 0;
685 #undef PATCH
687 REQUESTDONE_FUNC(log_access_write) {
688 plugin_data *p = p_d;
689 buffer *b;
690 size_t j;
692 int newts = 0;
693 data_string *ds;
695 mod_accesslog_patch_connection(srv, con, p);
697 /* No output device, nothing to do */
698 if (!p->conf.use_syslog && p->conf.log_access_fd == -1) return HANDLER_GO_ON;
700 if (p->conf.use_syslog) {
701 b = p->syslog_logbuffer;
702 } else {
703 b = p->conf.access_logbuffer;
706 if (buffer_is_empty(b)) {
707 buffer_string_set_length(b, 0);
710 for (j = 0; j < p->conf.parsed_format->used; j++) {
711 switch(p->conf.parsed_format->ptr[j]->type) {
712 case FIELD_STRING:
713 buffer_append_string_buffer(b, p->conf.parsed_format->ptr[j]->string);
714 break;
715 case FIELD_FORMAT:
716 switch(p->conf.parsed_format->ptr[j]->field) {
717 case FORMAT_TIMESTAMP:
719 /* cache the generated timestamp */
720 if (srv->cur_ts != *(p->conf.last_generated_accesslog_ts_ptr)) {
721 struct tm tm;
722 #if defined(HAVE_STRUCT_TM_GMTOFF)
723 long scd, hrs, min;
724 #endif
726 buffer_string_prepare_copy(p->conf.ts_accesslog_str, 255);
727 #if defined(HAVE_STRUCT_TM_GMTOFF)
728 # ifdef HAVE_LOCALTIME_R
729 localtime_r(&(srv->cur_ts), &tm);
730 buffer_append_strftime(p->conf.ts_accesslog_str, p->conf.ts_accesslog_fmt_str->ptr, &tm);
731 # else /* HAVE_LOCALTIME_R */
732 buffer_append_strftime(p->conf.ts_accesslog_str, p->conf.ts_accesslog_fmt_str->ptr, localtime(&(srv->cur_ts)));
733 # endif /* HAVE_LOCALTIME_R */
735 if (p->conf.append_tz_offset) {
736 buffer_append_string_len(p->conf.ts_accesslog_str, tm.tm_gmtoff >= 0 ? "+" : "-", 1);
738 scd = labs(tm.tm_gmtoff);
739 hrs = scd / 3600;
740 min = (scd % 3600) / 60;
742 /* hours */
743 if (hrs < 10) buffer_append_string_len(p->conf.ts_accesslog_str, CONST_STR_LEN("0"));
744 buffer_append_int(p->conf.ts_accesslog_str, hrs);
746 if (min < 10) buffer_append_string_len(p->conf.ts_accesslog_str, CONST_STR_LEN("0"));
747 buffer_append_int(p->conf.ts_accesslog_str, min);
748 buffer_append_string_len(p->conf.ts_accesslog_str, CONST_STR_LEN("]"));
750 #else /* HAVE_STRUCT_TM_GMTOFF */
751 # ifdef HAVE_GMTIME_R
752 gmtime_r(&(srv->cur_ts), &tm);
753 buffer_append_strftime(p->conf.ts_accesslog_str, p->conf.ts_accesslog_fmt_str->ptr, &tm);
754 # else /* HAVE_GMTIME_R */
755 buffer_append_strftime(p->conf.ts_accesslog_str, p->conf.ts_accesslog_fmt_str->ptr, gmtime(&(srv->cur_ts)));
756 # endif /* HAVE_GMTIME_R */
757 #endif /* HAVE_STRUCT_TM_GMTOFF */
759 *(p->conf.last_generated_accesslog_ts_ptr) = srv->cur_ts;
760 newts = 1;
763 buffer_append_string_buffer(b, p->conf.ts_accesslog_str);
765 break;
766 case FORMAT_REMOTE_HOST:
768 /* handle inet_ntop cache */
770 buffer_append_string(b, inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
772 break;
773 case FORMAT_REMOTE_IDENT:
774 /* ident */
775 buffer_append_string_len(b, CONST_STR_LEN("-"));
776 break;
777 case FORMAT_REMOTE_USER:
778 if (NULL != (ds = (data_string *)array_get_element(con->environment, "REMOTE_USER")) && !buffer_string_is_empty(ds->value)) {
779 accesslog_append_escaped(b, ds->value);
780 } else {
781 buffer_append_string_len(b, CONST_STR_LEN("-"));
783 break;
784 case FORMAT_REQUEST_LINE:
785 if (!buffer_string_is_empty(con->request.request_line)) {
786 accesslog_append_escaped(b, con->request.request_line);
788 break;
789 case FORMAT_STATUS:
790 buffer_append_int(b, con->http_status);
791 break;
793 case FORMAT_BYTES_OUT_NO_HEADER:
794 if (con->bytes_written > 0) {
795 buffer_append_int(b,
796 con->bytes_written - con->bytes_header <= 0 ? 0 : con->bytes_written - con->bytes_header);
797 } else {
798 buffer_append_string_len(b, CONST_STR_LEN("-"));
800 break;
801 case FORMAT_HEADER:
802 if (NULL != (ds = (data_string *)array_get_element(con->request.headers, p->conf.parsed_format->ptr[j]->string->ptr))) {
803 accesslog_append_escaped(b, ds->value);
804 } else {
805 buffer_append_string_len(b, CONST_STR_LEN("-"));
807 break;
808 case FORMAT_RESPONSE_HEADER:
809 if (NULL != (ds = (data_string *)array_get_element(con->response.headers, p->conf.parsed_format->ptr[j]->string->ptr))) {
810 accesslog_append_escaped(b, ds->value);
811 } else {
812 buffer_append_string_len(b, CONST_STR_LEN("-"));
814 break;
815 case FORMAT_ENV:
816 if (NULL != (ds = (data_string *)array_get_element(con->environment, p->conf.parsed_format->ptr[j]->string->ptr))) {
817 accesslog_append_escaped(b, ds->value);
818 } else {
819 buffer_append_string_len(b, CONST_STR_LEN("-"));
821 break;
822 case FORMAT_FILENAME:
823 if (!buffer_string_is_empty(con->physical.path)) {
824 buffer_append_string_buffer(b, con->physical.path);
825 } else {
826 buffer_append_string_len(b, CONST_STR_LEN("-"));
828 break;
829 case FORMAT_BYTES_OUT:
830 if (con->bytes_written > 0) {
831 buffer_append_int(b, con->bytes_written);
832 } else {
833 buffer_append_string_len(b, CONST_STR_LEN("-"));
835 break;
836 case FORMAT_BYTES_IN:
837 if (con->bytes_read > 0) {
838 buffer_append_int(b, con->bytes_read);
839 } else {
840 buffer_append_string_len(b, CONST_STR_LEN("-"));
842 break;
843 case FORMAT_TIME_USED:
844 buffer_append_int(b, srv->cur_ts - con->request_start);
845 break;
846 case FORMAT_SERVER_NAME:
847 if (!buffer_string_is_empty(con->server_name)) {
848 buffer_append_string_buffer(b, con->server_name);
849 } else {
850 buffer_append_string_len(b, CONST_STR_LEN("-"));
852 break;
853 case FORMAT_HTTP_HOST:
854 if (!buffer_string_is_empty(con->uri.authority)) {
855 accesslog_append_escaped(b, con->uri.authority);
856 } else {
857 buffer_append_string_len(b, CONST_STR_LEN("-"));
859 break;
860 case FORMAT_REQUEST_PROTOCOL:
861 buffer_append_string_len(b,
862 con->request.http_version == HTTP_VERSION_1_1 ? "HTTP/1.1" : "HTTP/1.0", 8);
863 break;
864 case FORMAT_REQUEST_METHOD:
865 buffer_append_string(b, get_http_method_name(con->request.http_method));
866 break;
867 case FORMAT_PERCENT:
868 buffer_append_string_len(b, CONST_STR_LEN("%"));
869 break;
870 case FORMAT_SERVER_PORT:
872 const char *colon;
873 buffer *srvtoken = ((server_socket*)(con->srv_socket))->srv_token;
874 if (srvtoken->ptr[0] == '[') {
875 colon = strstr(srvtoken->ptr, "]:");
876 } else {
877 colon = strchr(srvtoken->ptr, ':');
879 if (colon) {
880 buffer_append_string(b, colon+1);
881 } else {
882 buffer_append_int(b, srv->srvconf.port);
885 break;
886 case FORMAT_QUERY_STRING:
887 accesslog_append_escaped(b, con->uri.query);
888 break;
889 case FORMAT_URL:
890 accesslog_append_escaped(b, con->uri.path_raw);
891 break;
892 case FORMAT_CONNECTION_STATUS:
893 if (con->state == CON_STATE_RESPONSE_END) {
894 if (0 == con->keep_alive) {
895 buffer_append_string_len(b, CONST_STR_LEN("-"));
896 } else {
897 buffer_append_string_len(b, CONST_STR_LEN("+"));
899 } else { /* CON_STATE_ERROR */
900 buffer_append_string_len(b, CONST_STR_LEN("X"));
902 break;
903 default:
905 { 'a', FORMAT_REMOTE_ADDR },
906 { 'A', FORMAT_LOCAL_ADDR },
907 { 'C', FORMAT_COOKIE },
908 { 'D', FORMAT_TIME_USED_MS },
911 break;
913 break;
914 default:
915 break;
919 if (p->conf.use_syslog) { /* syslog doesn't cache */
920 #ifdef HAVE_SYSLOG_H
921 if (!buffer_string_is_empty(b)) {
922 /*(syslog appends a \n on its own)*/
923 syslog(p->conf.syslog_level, "%s", b->ptr);
924 buffer_reset(b);
926 #endif
927 return HANDLER_GO_ON;
930 buffer_append_string_len(b, CONST_STR_LEN("\n"));
932 if ((!buffer_string_is_empty(p->conf.access_logfile) && p->conf.access_logfile->ptr[0] == '|') || /* pipes don't cache */
933 newts ||
934 buffer_string_length(b) >= BUFFER_MAX_REUSE_SIZE) {
935 if (p->conf.log_access_fd >= 0) {
936 accesslog_write_all(srv, p->conf.access_logfile, p->conf.log_access_fd, CONST_BUF_LEN(b));
938 buffer_reset(b);
941 return HANDLER_GO_ON;
945 int mod_accesslog_plugin_init(plugin *p);
946 int mod_accesslog_plugin_init(plugin *p) {
947 p->version = LIGHTTPD_VERSION_ID;
948 p->name = buffer_init_string("accesslog");
950 p->init = mod_accesslog_init;
951 p->set_defaults= log_access_open;
952 p->cleanup = mod_accesslog_free;
954 p->handle_request_done = log_access_write;
955 p->handle_sighup = log_access_cycle;
957 p->data = NULL;
959 return 0;