diff options
Diffstat (limited to 'networking/httpd.c')
-rw-r--r-- | networking/httpd.c | 192 |
1 files changed, 151 insertions, 41 deletions
diff --git a/networking/httpd.c b/networking/httpd.c index 5d2d2681c..19cc08fcb 100644 --- a/networking/httpd.c +++ b/networking/httpd.c | |||
@@ -214,6 +214,44 @@ | |||
214 | //config: help | 214 | //config: help |
215 | //config: Makes httpd send files using GZIP content encoding if the | 215 | //config: Makes httpd send files using GZIP content encoding if the |
216 | //config: client supports it and a pre-compressed <file>.gz exists. | 216 | //config: client supports it and a pre-compressed <file>.gz exists. |
217 | //config: | ||
218 | //config:config FEATURE_HTTPD_ETAG | ||
219 | //config: bool "Support caching via ETag header" | ||
220 | //config: default y | ||
221 | //config: depends on HTTPD | ||
222 | //config: help | ||
223 | //config: If server responds with ETag then next time client (browser) | ||
224 | //config: resend it via If-None-Match header. | ||
225 | //config: Then httpd will check if file wasn't modified and if not, | ||
226 | //config: return 304 Not Modified status code. | ||
227 | //config: The ETag value is constructed from last modification date | ||
228 | //config: in unix epoch, and size: "hex(last_mod)-hex(file_size)". | ||
229 | //config: It's not completely reliable as hash functions but fair enough. | ||
230 | //config: | ||
231 | //config:config FEATURE_HTTPD_LAST_MODIFIED | ||
232 | //config: bool "Add Last-Modified header to response" | ||
233 | //config: default y | ||
234 | //config: depends on HTTPD | ||
235 | //config: help | ||
236 | //config: The Last-Modified header is used for cache validation. | ||
237 | //config: The client sends last seen mtime to server in If-Modified-Since. | ||
238 | //config: Both headers MUST be an RFC 1123 formatted, which is hard to parse. | ||
239 | //config: Use ETag header instead. | ||
240 | //config: | ||
241 | //config:config FEATURE_HTTPD_DATE | ||
242 | //config: bool "Add Date header to response" | ||
243 | //config: default y | ||
244 | //config: depends on HTTPD | ||
245 | //config: help | ||
246 | //config: RFC2616 says that server MUST add Date header to response. | ||
247 | //config: But it is almost useless and can be omitted. | ||
248 | //config: | ||
249 | //config:config FEATURE_HTTPD_ACL_IP | ||
250 | //config: bool "ACL IP" | ||
251 | //config: default y | ||
252 | //config: depends on HTTPD | ||
253 | //config: help | ||
254 | //config: Support IP deny/allow rules | ||
217 | 255 | ||
218 | //applet:IF_HTTPD(APPLET(httpd, BB_DIR_USR_SBIN, BB_SUID_DROP)) | 256 | //applet:IF_HTTPD(APPLET(httpd, BB_DIR_USR_SBIN, BB_SUID_DROP)) |
219 | 257 | ||
@@ -278,7 +316,7 @@ static void send_REQUEST_TIMEOUT_and_exit(int sig) NORETURN; | |||
278 | 316 | ||
279 | static const char DEFAULT_PATH_HTTPD_CONF[] ALIGN1 = "/etc"; | 317 | static const char DEFAULT_PATH_HTTPD_CONF[] ALIGN1 = "/etc"; |
280 | static const char HTTPD_CONF[] ALIGN1 = "httpd.conf"; | 318 | static const char HTTPD_CONF[] ALIGN1 = "httpd.conf"; |
281 | static const char HTTP_200[] ALIGN1 = "HTTP/1.0 200 OK\r\n"; | 319 | static const char HTTP_200[] ALIGN1 = "HTTP/1.1 200 OK\r\n"; |
282 | static const char index_html[] ALIGN1 = "index.html"; | 320 | static const char index_html[] ALIGN1 = "index.html"; |
283 | 321 | ||
284 | typedef struct has_next_ptr { | 322 | typedef struct has_next_ptr { |
@@ -292,6 +330,7 @@ typedef struct Htaccess { | |||
292 | char before_colon[1]; /* really bigger, must be last */ | 330 | char before_colon[1]; /* really bigger, must be last */ |
293 | } Htaccess; | 331 | } Htaccess; |
294 | 332 | ||
333 | #if ENABLE_FEATURE_HTTPD_ACL_IP | ||
295 | /* Must have "next" as a first member */ | 334 | /* Must have "next" as a first member */ |
296 | typedef struct Htaccess_IP { | 335 | typedef struct Htaccess_IP { |
297 | struct Htaccess_IP *next; | 336 | struct Htaccess_IP *next; |
@@ -299,6 +338,7 @@ typedef struct Htaccess_IP { | |||
299 | unsigned mask; | 338 | unsigned mask; |
300 | int allow_deny; | 339 | int allow_deny; |
301 | } Htaccess_IP; | 340 | } Htaccess_IP; |
341 | #endif | ||
302 | 342 | ||
303 | /* Must have "next" as a first member */ | 343 | /* Must have "next" as a first member */ |
304 | typedef struct Htaccess_Proxy { | 344 | typedef struct Htaccess_Proxy { |
@@ -319,6 +359,7 @@ enum { | |||
319 | HTTP_OK = 200, | 359 | HTTP_OK = 200, |
320 | HTTP_PARTIAL_CONTENT = 206, | 360 | HTTP_PARTIAL_CONTENT = 206, |
321 | HTTP_MOVED_TEMPORARILY = 302, | 361 | HTTP_MOVED_TEMPORARILY = 302, |
362 | HTTP_NOT_MODIFIED = 304, | ||
322 | HTTP_BAD_REQUEST = 400, /* malformed syntax */ | 363 | HTTP_BAD_REQUEST = 400, /* malformed syntax */ |
323 | HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */ | 364 | HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */ |
324 | HTTP_NOT_FOUND = 404, | 365 | HTTP_NOT_FOUND = 404, |
@@ -336,7 +377,6 @@ enum { | |||
336 | HTTP_NO_CONTENT = 204, | 377 | HTTP_NO_CONTENT = 204, |
337 | HTTP_MULTIPLE_CHOICES = 300, | 378 | HTTP_MULTIPLE_CHOICES = 300, |
338 | HTTP_MOVED_PERMANENTLY = 301, | 379 | HTTP_MOVED_PERMANENTLY = 301, |
339 | HTTP_NOT_MODIFIED = 304, | ||
340 | HTTP_PAYMENT_REQUIRED = 402, | 380 | HTTP_PAYMENT_REQUIRED = 402, |
341 | HTTP_BAD_GATEWAY = 502, | 381 | HTTP_BAD_GATEWAY = 502, |
342 | HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */ | 382 | HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */ |
@@ -349,6 +389,9 @@ static const uint16_t http_response_type[] ALIGN2 = { | |||
349 | HTTP_PARTIAL_CONTENT, | 389 | HTTP_PARTIAL_CONTENT, |
350 | #endif | 390 | #endif |
351 | HTTP_MOVED_TEMPORARILY, | 391 | HTTP_MOVED_TEMPORARILY, |
392 | #if ENABLE_FEATURE_HTTPD_ETAG | ||
393 | HTTP_NOT_MODIFIED, | ||
394 | #endif | ||
352 | HTTP_REQUEST_TIMEOUT, | 395 | HTTP_REQUEST_TIMEOUT, |
353 | HTTP_NOT_IMPLEMENTED, | 396 | HTTP_NOT_IMPLEMENTED, |
354 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH | 397 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
@@ -365,7 +408,6 @@ static const uint16_t http_response_type[] ALIGN2 = { | |||
365 | HTTP_NO_CONTENT, | 408 | HTTP_NO_CONTENT, |
366 | HTTP_MULTIPLE_CHOICES, | 409 | HTTP_MULTIPLE_CHOICES, |
367 | HTTP_MOVED_PERMANENTLY, | 410 | HTTP_MOVED_PERMANENTLY, |
368 | HTTP_NOT_MODIFIED, | ||
369 | HTTP_BAD_GATEWAY, | 411 | HTTP_BAD_GATEWAY, |
370 | HTTP_SERVICE_UNAVAILABLE, | 412 | HTTP_SERVICE_UNAVAILABLE, |
371 | #endif | 413 | #endif |
@@ -380,6 +422,9 @@ static const struct { | |||
380 | { "Partial Content", NULL }, | 422 | { "Partial Content", NULL }, |
381 | #endif | 423 | #endif |
382 | { "Found", NULL }, | 424 | { "Found", NULL }, |
425 | #if ENABLE_FEATURE_HTTPD_ETAG | ||
426 | { "Not Modified" }, | ||
427 | #endif | ||
383 | { "Request Timeout", "No request appeared within 60 seconds" }, | 428 | { "Request Timeout", "No request appeared within 60 seconds" }, |
384 | { "Not Implemented", "The requested method is not recognized" }, | 429 | { "Not Implemented", "The requested method is not recognized" }, |
385 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH | 430 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
@@ -396,7 +441,6 @@ static const struct { | |||
396 | { "No Content" }, | 441 | { "No Content" }, |
397 | { "Multiple Choices" }, | 442 | { "Multiple Choices" }, |
398 | { "Moved Permanently" }, | 443 | { "Moved Permanently" }, |
399 | { "Not Modified" }, | ||
400 | { "Bad Gateway", "" }, | 444 | { "Bad Gateway", "" }, |
401 | { "Service Unavailable", "" }, | 445 | { "Service Unavailable", "" }, |
402 | #endif | 446 | #endif |
@@ -410,6 +454,9 @@ struct globals { | |||
410 | smallint content_gzip; | 454 | smallint content_gzip; |
411 | #endif | 455 | #endif |
412 | time_t last_mod; | 456 | time_t last_mod; |
457 | #if ENABLE_FEATURE_HTTPD_ETAG | ||
458 | char *if_none_match; | ||
459 | #endif | ||
413 | char *rmt_ip_str; /* for $REMOTE_ADDR and $REMOTE_PORT */ | 460 | char *rmt_ip_str; /* for $REMOTE_ADDR and $REMOTE_PORT */ |
414 | const char *bind_addr_or_port; | 461 | const char *bind_addr_or_port; |
415 | 462 | ||
@@ -420,7 +467,9 @@ struct globals { | |||
420 | 467 | ||
421 | const char *found_mime_type; | 468 | const char *found_mime_type; |
422 | const char *found_moved_temporarily; | 469 | const char *found_moved_temporarily; |
470 | #if ENABLE_FEATURE_HTTPD_ACL_IP | ||
423 | Htaccess_IP *ip_a_d; /* config allow/deny lines */ | 471 | Htaccess_IP *ip_a_d; /* config allow/deny lines */ |
472 | #endif | ||
424 | 473 | ||
425 | IF_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm;) | 474 | IF_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm;) |
426 | IF_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;) | 475 | IF_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;) |
@@ -444,6 +493,9 @@ struct globals { | |||
444 | #define sizeof_hdr_buf COMMON_BUFSIZE | 493 | #define sizeof_hdr_buf COMMON_BUFSIZE |
445 | char *hdr_ptr; | 494 | char *hdr_ptr; |
446 | int hdr_cnt; | 495 | int hdr_cnt; |
496 | #if ENABLE_FEATURE_HTTPD_ETAG | ||
497 | char etag[sizeof("'%llx-%llx'") + 2 * sizeof(long long)*3]; | ||
498 | #endif | ||
447 | #if ENABLE_FEATURE_HTTPD_ERROR_PAGES | 499 | #if ENABLE_FEATURE_HTTPD_ERROR_PAGES |
448 | const char *http_error_page[ARRAY_SIZE(http_response_type)]; | 500 | const char *http_error_page[ARRAY_SIZE(http_response_type)]; |
449 | #endif | 501 | #endif |
@@ -467,7 +519,6 @@ struct globals { | |||
467 | #define found_mime_type (G.found_mime_type ) | 519 | #define found_mime_type (G.found_mime_type ) |
468 | #define found_moved_temporarily (G.found_moved_temporarily) | 520 | #define found_moved_temporarily (G.found_moved_temporarily) |
469 | #define last_mod (G.last_mod ) | 521 | #define last_mod (G.last_mod ) |
470 | #define ip_a_d (G.ip_a_d ) | ||
471 | #define g_realm (G.g_realm ) | 522 | #define g_realm (G.g_realm ) |
472 | #define remoteuser (G.remoteuser ) | 523 | #define remoteuser (G.remoteuser ) |
473 | #define file_size (G.file_size ) | 524 | #define file_size (G.file_size ) |
@@ -528,11 +579,14 @@ static ALWAYS_INLINE void free_Htaccess_list(Htaccess **pptr) | |||
528 | free_llist((has_next_ptr**)pptr); | 579 | free_llist((has_next_ptr**)pptr); |
529 | } | 580 | } |
530 | 581 | ||
582 | #if ENABLE_FEATURE_HTTPD_ACL_IP | ||
531 | static ALWAYS_INLINE void free_Htaccess_IP_list(Htaccess_IP **pptr) | 583 | static ALWAYS_INLINE void free_Htaccess_IP_list(Htaccess_IP **pptr) |
532 | { | 584 | { |
533 | free_llist((has_next_ptr**)pptr); | 585 | free_llist((has_next_ptr**)pptr); |
534 | } | 586 | } |
587 | #endif | ||
535 | 588 | ||
589 | #if ENABLE_FEATURE_HTTPD_ACL_IP | ||
536 | /* Returns presumed mask width in bits or < 0 on error. | 590 | /* Returns presumed mask width in bits or < 0 on error. |
537 | * Updates strp, stores IP at provided pointer */ | 591 | * Updates strp, stores IP at provided pointer */ |
538 | static int scan_ip(const char **strp, unsigned *ipp, unsigned char endc) | 592 | static int scan_ip(const char **strp, unsigned *ipp, unsigned char endc) |
@@ -617,6 +671,7 @@ static int scan_ip_mask(const char *str, unsigned *ipp, unsigned *maskp) | |||
617 | *maskp = (uint32_t)(~mask); | 671 | *maskp = (uint32_t)(~mask); |
618 | return 0; | 672 | return 0; |
619 | } | 673 | } |
674 | #endif | ||
620 | 675 | ||
621 | /* | 676 | /* |
622 | * Parse configuration file into in-memory linked list. | 677 | * Parse configuration file into in-memory linked list. |
@@ -646,7 +701,9 @@ static void parse_conf(const char *path, int flag) | |||
646 | char buf[160]; | 701 | char buf[160]; |
647 | 702 | ||
648 | /* discard old rules */ | 703 | /* discard old rules */ |
649 | free_Htaccess_IP_list(&ip_a_d); | 704 | #if ENABLE_FEATURE_HTTPD_ACL_IP |
705 | free_Htaccess_IP_list(&G.ip_a_d); | ||
706 | #endif | ||
650 | flg_deny_all = 0; | 707 | flg_deny_all = 0; |
651 | /* retain previous auth and mime config only for subdir parse */ | 708 | /* retain previous auth and mime config only for subdir parse */ |
652 | if (flag != SUBDIR_PARSE) { | 709 | if (flag != SUBDIR_PARSE) { |
@@ -763,6 +820,7 @@ static void parse_conf(const char *path, int flag) | |||
763 | continue; | 820 | continue; |
764 | } | 821 | } |
765 | 822 | ||
823 | #if ENABLE_FEATURE_HTTPD_ACL_IP | ||
766 | if (ch == 'A' || ch == 'D') { | 824 | if (ch == 'A' || ch == 'D') { |
767 | Htaccess_IP *pip; | 825 | Htaccess_IP *pip; |
768 | 826 | ||
@@ -784,13 +842,13 @@ static void parse_conf(const char *path, int flag) | |||
784 | pip->allow_deny = ch; | 842 | pip->allow_deny = ch; |
785 | if (ch == 'D') { | 843 | if (ch == 'D') { |
786 | /* Deny:from_IP - prepend */ | 844 | /* Deny:from_IP - prepend */ |
787 | pip->next = ip_a_d; | 845 | pip->next = G.ip_a_d; |
788 | ip_a_d = pip; | 846 | G.ip_a_d = pip; |
789 | } else { | 847 | } else { |
790 | /* A:from_IP - append (thus all D's precedes A's) */ | 848 | /* A:from_IP - append (thus all D's precedes A's) */ |
791 | Htaccess_IP *prev_IP = ip_a_d; | 849 | Htaccess_IP *prev_IP = G.ip_a_d; |
792 | if (prev_IP == NULL) { | 850 | if (prev_IP == NULL) { |
793 | ip_a_d = pip; | 851 | G.ip_a_d = pip; |
794 | } else { | 852 | } else { |
795 | while (prev_IP->next) | 853 | while (prev_IP->next) |
796 | prev_IP = prev_IP->next; | 854 | prev_IP = prev_IP->next; |
@@ -799,6 +857,7 @@ static void parse_conf(const char *path, int flag) | |||
799 | } | 857 | } |
800 | continue; | 858 | continue; |
801 | } | 859 | } |
860 | #endif | ||
802 | 861 | ||
803 | #if ENABLE_FEATURE_HTTPD_ERROR_PAGES | 862 | #if ENABLE_FEATURE_HTTPD_ERROR_PAGES |
804 | if (flag == FIRST_PARSE && ch == 'E') { | 863 | if (flag == FIRST_PARSE && ch == 'E') { |
@@ -1059,11 +1118,12 @@ static void log_and_exit(void) | |||
1059 | */ | 1118 | */ |
1060 | static void send_headers(unsigned responseNum) | 1119 | static void send_headers(unsigned responseNum) |
1061 | { | 1120 | { |
1121 | #if ENABLE_FEATURE_HTTPD_DATE || ENABLE_FEATURE_HTTPD_LAST_MODIFIED | ||
1062 | static const char RFC1123FMT[] ALIGN1 = "%a, %d %b %Y %H:%M:%S GMT"; | 1122 | static const char RFC1123FMT[] ALIGN1 = "%a, %d %b %Y %H:%M:%S GMT"; |
1063 | /* Fixed size 29-byte string. Example: Sun, 06 Nov 1994 08:49:37 GMT */ | 1123 | /* Fixed size 29-byte string. Example: Sun, 06 Nov 1994 08:49:37 GMT */ |
1064 | char date_str[40]; /* using a bit larger buffer to paranoia reasons */ | 1124 | char date_str[40]; /* using a bit larger buffer to paranoia reasons */ |
1065 | |||
1066 | struct tm tm; | 1125 | struct tm tm; |
1126 | #endif | ||
1067 | const char *responseString = ""; | 1127 | const char *responseString = ""; |
1068 | const char *infoString = NULL; | 1128 | const char *infoString = NULL; |
1069 | #if ENABLE_FEATURE_HTTPD_ERROR_PAGES | 1129 | #if ENABLE_FEATURE_HTTPD_ERROR_PAGES |
@@ -1071,7 +1131,6 @@ static void send_headers(unsigned responseNum) | |||
1071 | #endif | 1131 | #endif |
1072 | unsigned len; | 1132 | unsigned len; |
1073 | unsigned i; | 1133 | unsigned i; |
1074 | time_t timer = time(NULL); | ||
1075 | 1134 | ||
1076 | for (i = 0; i < ARRAY_SIZE(http_response_type); i++) { | 1135 | for (i = 0; i < ARRAY_SIZE(http_response_type); i++) { |
1077 | if (http_response_type[i] == responseNum) { | 1136 | if (http_response_type[i] == responseNum) { |
@@ -1092,15 +1151,24 @@ static void send_headers(unsigned responseNum) | |||
1092 | * always fit into those kbytes. | 1151 | * always fit into those kbytes. |
1093 | */ | 1152 | */ |
1094 | 1153 | ||
1095 | strftime(date_str, sizeof(date_str), RFC1123FMT, gmtime_r(&timer, &tm)); | 1154 | { |
1096 | /* ^^^ using gmtime_r() instead of gmtime() to not use static data */ | 1155 | #if ENABLE_FEATURE_HTTPD_DATE |
1097 | len = sprintf(iobuf, | 1156 | time_t timer = time(NULL); |
1098 | "HTTP/1.0 %u %s\r\n" | 1157 | strftime(date_str, sizeof(date_str), RFC1123FMT, gmtime_r(&timer, &tm)); |
1158 | /* ^^^ using gmtime_r() instead of gmtime() to not use static data */ | ||
1159 | #endif | ||
1160 | len = sprintf(iobuf, | ||
1161 | "HTTP/1.1 %u %s\r\n" | ||
1162 | #if ENABLE_FEATURE_HTTPD_DATE | ||
1099 | "Date: %s\r\n" | 1163 | "Date: %s\r\n" |
1164 | #endif | ||
1100 | "Connection: close\r\n", | 1165 | "Connection: close\r\n", |
1101 | responseNum, responseString, | 1166 | responseNum, responseString |
1102 | date_str | 1167 | #if ENABLE_FEATURE_HTTPD_DATE |
1103 | ); | 1168 | ,date_str |
1169 | #endif | ||
1170 | ); | ||
1171 | } | ||
1104 | 1172 | ||
1105 | if (responseNum != HTTP_OK || found_mime_type) { | 1173 | if (responseNum != HTTP_OK || found_mime_type) { |
1106 | len += sprintf(iobuf + len, | 1174 | len += sprintf(iobuf + len, |
@@ -1120,7 +1188,7 @@ static void send_headers(unsigned responseNum) | |||
1120 | #endif | 1188 | #endif |
1121 | if (responseNum == HTTP_MOVED_TEMPORARILY) { | 1189 | if (responseNum == HTTP_MOVED_TEMPORARILY) { |
1122 | /* Responding to "GET /dir" with | 1190 | /* Responding to "GET /dir" with |
1123 | * "HTTP/1.0 302 Found" "Location: /dir/" | 1191 | * "HTTP/1.1 302 Found" "Location: /dir/" |
1124 | * - IOW, asking them to repeat with a slash. | 1192 | * - IOW, asking them to repeat with a slash. |
1125 | * Here, overflow IS possible, can't use sprintf: | 1193 | * Here, overflow IS possible, can't use sprintf: |
1126 | * mkdir test | 1194 | * mkdir test |
@@ -1152,7 +1220,9 @@ static void send_headers(unsigned responseNum) | |||
1152 | #endif | 1220 | #endif |
1153 | 1221 | ||
1154 | if (file_size != -1) { /* file */ | 1222 | if (file_size != -1) { /* file */ |
1223 | #if ENABLE_FEATURE_HTTPD_LAST_MODIFIED | ||
1155 | strftime(date_str, sizeof(date_str), RFC1123FMT, gmtime_r(&last_mod, &tm)); | 1224 | strftime(date_str, sizeof(date_str), RFC1123FMT, gmtime_r(&last_mod, &tm)); |
1225 | #endif | ||
1156 | #if ENABLE_FEATURE_HTTPD_RANGES | 1226 | #if ENABLE_FEATURE_HTTPD_RANGES |
1157 | if (responseNum == HTTP_PARTIAL_CONTENT) { | 1227 | if (responseNum == HTTP_PARTIAL_CONTENT) { |
1158 | len += sprintf(iobuf + len, | 1228 | len += sprintf(iobuf + len, |
@@ -1197,7 +1267,13 @@ static void send_headers(unsigned responseNum) | |||
1197 | #if ENABLE_FEATURE_HTTPD_RANGES | 1267 | #if ENABLE_FEATURE_HTTPD_RANGES |
1198 | "Accept-Ranges: bytes\r\n" | 1268 | "Accept-Ranges: bytes\r\n" |
1199 | #endif | 1269 | #endif |
1270 | #if ENABLE_FEATURE_HTTPD_LAST_MODIFIED | ||
1200 | "Last-Modified: %s\r\n" | 1271 | "Last-Modified: %s\r\n" |
1272 | #endif | ||
1273 | #if ENABLE_FEATURE_HTTPD_ETAG | ||
1274 | "ETag: %s\r\n" | ||
1275 | #endif | ||
1276 | |||
1201 | /* Because of 4.4 (5), we can forgo sending of "Content-Length" | 1277 | /* Because of 4.4 (5), we can forgo sending of "Content-Length" |
1202 | * since we close connection afterwards, but it helps clients | 1278 | * since we close connection afterwards, but it helps clients |
1203 | * to e.g. estimate download times, show progress bars etc. | 1279 | * to e.g. estimate download times, show progress bars etc. |
@@ -1205,7 +1281,12 @@ static void send_headers(unsigned responseNum) | |||
1205 | * but de-facto standard is to send it (see comment below). | 1281 | * but de-facto standard is to send it (see comment below). |
1206 | */ | 1282 | */ |
1207 | "Content-Length: %"OFF_FMT"u\r\n", | 1283 | "Content-Length: %"OFF_FMT"u\r\n", |
1284 | #if ENABLE_FEATURE_HTTPD_LAST_MODIFIED | ||
1208 | date_str, | 1285 | date_str, |
1286 | #endif | ||
1287 | #if ENABLE_FEATURE_HTTPD_ETAG | ||
1288 | G.etag, | ||
1289 | #endif | ||
1209 | file_size | 1290 | file_size |
1210 | ); | 1291 | ); |
1211 | } | 1292 | } |
@@ -1444,7 +1525,7 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post | |||
1444 | count = safe_read(fromCgi_rd, rbuf + out_cnt, IOBUF_SIZE - 8); | 1525 | count = safe_read(fromCgi_rd, rbuf + out_cnt, IOBUF_SIZE - 8); |
1445 | if (count <= 0) { | 1526 | if (count <= 0) { |
1446 | /* eof (or error) and there was no "HTTP", | 1527 | /* eof (or error) and there was no "HTTP", |
1447 | * send "HTTP/1.0 200 OK\r\n", then send received data */ | 1528 | * send "HTTP/1.1 200 OK\r\n", then send received data */ |
1448 | if (out_cnt) { | 1529 | if (out_cnt) { |
1449 | full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1); | 1530 | full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1); |
1450 | full_write(STDOUT_FILENO, rbuf, out_cnt); | 1531 | full_write(STDOUT_FILENO, rbuf, out_cnt); |
@@ -1455,10 +1536,10 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post | |||
1455 | count = 0; | 1536 | count = 0; |
1456 | /* "Status" header format is: "Status: 302 Redirected\r\n" */ | 1537 | /* "Status" header format is: "Status: 302 Redirected\r\n" */ |
1457 | if (out_cnt >= 8 && memcmp(rbuf, "Status: ", 8) == 0) { | 1538 | if (out_cnt >= 8 && memcmp(rbuf, "Status: ", 8) == 0) { |
1458 | /* send "HTTP/1.0 " */ | 1539 | /* send "HTTP/1.1 " */ |
1459 | if (full_write(STDOUT_FILENO, HTTP_200, 9) != 9) | 1540 | if (full_write(STDOUT_FILENO, HTTP_200, 9) != 9) |
1460 | break; | 1541 | break; |
1461 | /* skip "Status: " (including space, sending "HTTP/1.0 NNN" is wrong) */ | 1542 | /* skip "Status: " (including space, sending "HTTP/1.1 NNN" is wrong) */ |
1462 | rbuf += 8; | 1543 | rbuf += 8; |
1463 | count = out_cnt - 8; | 1544 | count = out_cnt - 8; |
1464 | out_cnt = -1; /* buffering off */ | 1545 | out_cnt = -1; /* buffering off */ |
@@ -1474,7 +1555,7 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post | |||
1474 | full_write(s, "Content-type: text/plain\r\n\r\n", 28); | 1555 | full_write(s, "Content-type: text/plain\r\n\r\n", 28); |
1475 | } | 1556 | } |
1476 | * Counter-example of valid CGI without Content-type: | 1557 | * Counter-example of valid CGI without Content-type: |
1477 | * echo -en "HTTP/1.0 302 Found\r\n" | 1558 | * echo -en "HTTP/1.1 302 Found\r\n" |
1478 | * echo -en "Location: http://www.busybox.net\r\n" | 1559 | * echo -en "Location: http://www.busybox.net\r\n" |
1479 | * echo -en "\r\n" | 1560 | * echo -en "\r\n" |
1480 | */ | 1561 | */ |
@@ -1581,7 +1662,7 @@ static void send_cgi_and_exit( | |||
1581 | /* (Older versions of bbox seem to do some decoding) */ | 1662 | /* (Older versions of bbox seem to do some decoding) */ |
1582 | setenv1("QUERY_STRING", g_query); | 1663 | setenv1("QUERY_STRING", g_query); |
1583 | putenv((char*)"SERVER_SOFTWARE=busybox httpd/"BB_VER); | 1664 | putenv((char*)"SERVER_SOFTWARE=busybox httpd/"BB_VER); |
1584 | putenv((char*)"SERVER_PROTOCOL=HTTP/1.0"); | 1665 | putenv((char*)"SERVER_PROTOCOL=HTTP/1.1"); |
1585 | putenv((char*)"GATEWAY_INTERFACE=CGI/1.1"); | 1666 | putenv((char*)"GATEWAY_INTERFACE=CGI/1.1"); |
1586 | /* Having _separate_ variables for IP and port defeats | 1667 | /* Having _separate_ variables for IP and port defeats |
1587 | * the purpose of having socket abstraction. Which "port" | 1668 | * the purpose of having socket abstraction. Which "port" |
@@ -1732,6 +1813,7 @@ static NOINLINE void send_file_and_exit(const char *url, int what) | |||
1732 | } | 1813 | } |
1733 | } else { | 1814 | } else { |
1734 | fd = open(url, O_RDONLY); | 1815 | fd = open(url, O_RDONLY); |
1816 | /* file_size and last_mod are already populated */ | ||
1735 | } | 1817 | } |
1736 | if (fd < 0) { | 1818 | if (fd < 0) { |
1737 | if (DEBUG) | 1819 | if (DEBUG) |
@@ -1743,6 +1825,19 @@ static NOINLINE void send_file_and_exit(const char *url, int what) | |||
1743 | send_headers_and_exit(HTTP_NOT_FOUND); | 1825 | send_headers_and_exit(HTTP_NOT_FOUND); |
1744 | log_and_exit(); | 1826 | log_and_exit(); |
1745 | } | 1827 | } |
1828 | #if ENABLE_FEATURE_HTTPD_ETAG | ||
1829 | /* ETag is "hex(last_mod)-hex(file_size)" e.g. "5e132e20-417" */ | ||
1830 | sprintf(G.etag, "\"%llx-%llx\"", (unsigned long long)last_mod, (unsigned long long)file_size); | ||
1831 | |||
1832 | if (G.if_none_match) { | ||
1833 | if (DEBUG) | ||
1834 | bb_perror_msg("If-None-Match and file's ETag are: '%s' '%s'\n", G.if_none_match, G.etag); | ||
1835 | /* Weak ETag comparision. | ||
1836 | * If-None-Match may have many ETags but they are quoted so we can use simple substring search */ | ||
1837 | if (strstr(G.if_none_match, G.etag)) | ||
1838 | send_headers_and_exit(HTTP_NOT_MODIFIED); | ||
1839 | } | ||
1840 | #endif | ||
1746 | /* If you want to know about EPIPE below | 1841 | /* If you want to know about EPIPE below |
1747 | * (happens if you abort downloads from local httpd): */ | 1842 | * (happens if you abort downloads from local httpd): */ |
1748 | signal(SIGPIPE, SIG_IGN); | 1843 | signal(SIGPIPE, SIG_IGN); |
@@ -1878,11 +1973,12 @@ static NOINLINE void send_file_and_exit(const char *url, int what) | |||
1878 | log_and_exit(); | 1973 | log_and_exit(); |
1879 | } | 1974 | } |
1880 | 1975 | ||
1976 | #if ENABLE_FEATURE_HTTPD_ACL_IP | ||
1881 | static void if_ip_denied_send_HTTP_FORBIDDEN_and_exit(unsigned remote_ip) | 1977 | static void if_ip_denied_send_HTTP_FORBIDDEN_and_exit(unsigned remote_ip) |
1882 | { | 1978 | { |
1883 | Htaccess_IP *cur; | 1979 | Htaccess_IP *cur; |
1884 | 1980 | ||
1885 | for (cur = ip_a_d; cur; cur = cur->next) { | 1981 | for (cur = G.ip_a_d; cur; cur = cur->next) { |
1886 | #if DEBUG | 1982 | #if DEBUG |
1887 | fprintf(stderr, | 1983 | fprintf(stderr, |
1888 | "checkPermIP: '%s' ? '%u.%u.%u.%u/%u.%u.%u.%u'\n", | 1984 | "checkPermIP: '%s' ? '%u.%u.%u.%u/%u.%u.%u.%u'\n", |
@@ -1907,6 +2003,9 @@ static void if_ip_denied_send_HTTP_FORBIDDEN_and_exit(unsigned remote_ip) | |||
1907 | if (flg_deny_all) /* depends on whether we saw "D:*" */ | 2003 | if (flg_deny_all) /* depends on whether we saw "D:*" */ |
1908 | send_headers_and_exit(HTTP_FORBIDDEN); | 2004 | send_headers_and_exit(HTTP_FORBIDDEN); |
1909 | } | 2005 | } |
2006 | #else | ||
2007 | # define if_ip_denied_send_HTTP_FORBIDDEN_and_exit(arg) ((void)0) | ||
2008 | #endif | ||
1910 | 2009 | ||
1911 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH | 2010 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
1912 | 2011 | ||
@@ -2160,7 +2259,9 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
2160 | char *urlcopy; | 2259 | char *urlcopy; |
2161 | char *urlp; | 2260 | char *urlp; |
2162 | char *tptr; | 2261 | char *tptr; |
2262 | #if ENABLE_FEATURE_HTTPD_ACL_IP | ||
2163 | unsigned remote_ip; | 2263 | unsigned remote_ip; |
2264 | #endif | ||
2164 | #if ENABLE_FEATURE_HTTPD_CGI | 2265 | #if ENABLE_FEATURE_HTTPD_CGI |
2165 | unsigned total_headers_len; | 2266 | unsigned total_headers_len; |
2166 | #endif | 2267 | #endif |
@@ -2182,18 +2283,30 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
2182 | * (IOW, server process doesn't need to waste 8k) */ | 2283 | * (IOW, server process doesn't need to waste 8k) */ |
2183 | iobuf = xmalloc(IOBUF_SIZE); | 2284 | iobuf = xmalloc(IOBUF_SIZE); |
2184 | 2285 | ||
2286 | if (ENABLE_FEATURE_HTTPD_CGI || DEBUG || verbose) { | ||
2287 | /* NB: can be NULL (user runs httpd -i by hand?) */ | ||
2288 | rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr->u.sa); | ||
2289 | } | ||
2290 | if (verbose) { | ||
2291 | /* this trick makes -v logging much simpler */ | ||
2292 | if (rmt_ip_str) | ||
2293 | applet_name = rmt_ip_str; | ||
2294 | if (verbose > 2) | ||
2295 | bb_simple_error_msg("connected"); | ||
2296 | } | ||
2297 | #if ENABLE_FEATURE_HTTPD_ACL_IP | ||
2185 | remote_ip = 0; | 2298 | remote_ip = 0; |
2186 | if (fromAddr->u.sa.sa_family == AF_INET) { | 2299 | if (fromAddr->u.sa.sa_family == AF_INET) { |
2187 | remote_ip = ntohl(fromAddr->u.sin.sin_addr.s_addr); | 2300 | remote_ip = ntohl(fromAddr->u.sin.sin_addr.s_addr); |
2188 | } | 2301 | } |
2189 | #if ENABLE_FEATURE_IPV6 | 2302 | # if ENABLE_FEATURE_IPV6 |
2190 | # if !ENABLE_PLATFORM_MINGW32 | 2303 | # if !ENABLE_PLATFORM_MINGW32 |
2191 | if (fromAddr->u.sa.sa_family == AF_INET6 | 2304 | if (fromAddr->u.sa.sa_family == AF_INET6 |
2192 | && fromAddr->u.sin6.sin6_addr.s6_addr32[0] == 0 | 2305 | && fromAddr->u.sin6.sin6_addr.s6_addr32[0] == 0 |
2193 | && fromAddr->u.sin6.sin6_addr.s6_addr32[1] == 0 | 2306 | && fromAddr->u.sin6.sin6_addr.s6_addr32[1] == 0 |
2194 | && ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[2]) == 0xffff) | 2307 | && ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[2]) == 0xffff) |
2195 | remote_ip = ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[3]); | 2308 | remote_ip = ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[3]); |
2196 | # else | 2309 | # else |
2197 | if (fromAddr->u.sa.sa_family == AF_INET6 | 2310 | if (fromAddr->u.sa.sa_family == AF_INET6 |
2198 | && fromAddr->u.sin6.sin6_addr.s6_words[0] == 0 | 2311 | && fromAddr->u.sin6.sin6_addr.s6_words[0] == 0 |
2199 | && fromAddr->u.sin6.sin6_addr.s6_words[1] == 0 | 2312 | && fromAddr->u.sin6.sin6_addr.s6_words[1] == 0 |
@@ -2201,20 +2314,10 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
2201 | && fromAddr->u.sin6.sin6_addr.s6_words[3] == 0 | 2314 | && fromAddr->u.sin6.sin6_addr.s6_words[3] == 0 |
2202 | && ntohl(*(uint32_t *)(fromAddr->u.sin6.sin6_addr.s6_words+4)) == 0xffff) | 2315 | && ntohl(*(uint32_t *)(fromAddr->u.sin6.sin6_addr.s6_words+4)) == 0xffff) |
2203 | remote_ip = ntohl(*(uint32_t *)(fromAddr->u.sin6.sin6_addr.s6_words+6)); | 2316 | remote_ip = ntohl(*(uint32_t *)(fromAddr->u.sin6.sin6_addr.s6_words+6)); |
2317 | # endif | ||
2204 | # endif | 2318 | # endif |
2205 | #endif | ||
2206 | if (ENABLE_FEATURE_HTTPD_CGI || DEBUG || verbose) { | ||
2207 | /* NB: can be NULL (user runs httpd -i by hand?) */ | ||
2208 | rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr->u.sa); | ||
2209 | } | ||
2210 | if (verbose) { | ||
2211 | /* this trick makes -v logging much simpler */ | ||
2212 | if (rmt_ip_str) | ||
2213 | applet_name = rmt_ip_str; | ||
2214 | if (verbose > 2) | ||
2215 | bb_simple_error_msg("connected"); | ||
2216 | } | ||
2217 | if_ip_denied_send_HTTP_FORBIDDEN_and_exit(remote_ip); | 2319 | if_ip_denied_send_HTTP_FORBIDDEN_and_exit(remote_ip); |
2320 | #endif | ||
2218 | 2321 | ||
2219 | #ifdef SIGALRM | 2322 | #ifdef SIGALRM |
2220 | /* Install timeout handler. get_line() needs it. */ | 2323 | /* Install timeout handler. get_line() needs it. */ |
@@ -2512,6 +2615,13 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
2512 | continue; | 2615 | continue; |
2513 | } | 2616 | } |
2514 | #endif | 2617 | #endif |
2618 | #if ENABLE_FEATURE_HTTPD_ETAG | ||
2619 | if (STRNCASECMP(iobuf, "If-None-Match:") == 0) { | ||
2620 | free(G.if_none_match); | ||
2621 | G.if_none_match = xstrdup(skip_whitespace(iobuf + sizeof("If-None-Match:") - 1)); | ||
2622 | continue; | ||
2623 | } | ||
2624 | #endif | ||
2515 | #if ENABLE_FEATURE_HTTPD_CGI | 2625 | #if ENABLE_FEATURE_HTTPD_CGI |
2516 | if (cgi_type != CGI_NONE) { | 2626 | if (cgi_type != CGI_NONE) { |
2517 | bool ct = (STRNCASECMP(iobuf, "Content-Type:") == 0); | 2627 | bool ct = (STRNCASECMP(iobuf, "Content-Type:") == 0); |