diff options
Diffstat (limited to 'networking/httpd.c')
-rw-r--r-- | networking/httpd.c | 57 |
1 files changed, 54 insertions, 3 deletions
diff --git a/networking/httpd.c b/networking/httpd.c index 94f7297ad..0297cf9ec 100644 --- a/networking/httpd.c +++ b/networking/httpd.c | |||
@@ -215,6 +215,19 @@ | |||
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: | 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: | ||
218 | //config:config FEATURE_HTTPD_LAST_MODIFIED | 231 | //config:config FEATURE_HTTPD_LAST_MODIFIED |
219 | //config: bool "Add Last-Modified header to response" | 232 | //config: bool "Add Last-Modified header to response" |
220 | //config: default y | 233 | //config: default y |
@@ -328,6 +341,7 @@ enum { | |||
328 | HTTP_OK = 200, | 341 | HTTP_OK = 200, |
329 | HTTP_PARTIAL_CONTENT = 206, | 342 | HTTP_PARTIAL_CONTENT = 206, |
330 | HTTP_MOVED_TEMPORARILY = 302, | 343 | HTTP_MOVED_TEMPORARILY = 302, |
344 | HTTP_NOT_MODIFIED = 304, | ||
331 | HTTP_BAD_REQUEST = 400, /* malformed syntax */ | 345 | HTTP_BAD_REQUEST = 400, /* malformed syntax */ |
332 | HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */ | 346 | HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */ |
333 | HTTP_NOT_FOUND = 404, | 347 | HTTP_NOT_FOUND = 404, |
@@ -345,7 +359,6 @@ enum { | |||
345 | HTTP_NO_CONTENT = 204, | 359 | HTTP_NO_CONTENT = 204, |
346 | HTTP_MULTIPLE_CHOICES = 300, | 360 | HTTP_MULTIPLE_CHOICES = 300, |
347 | HTTP_MOVED_PERMANENTLY = 301, | 361 | HTTP_MOVED_PERMANENTLY = 301, |
348 | HTTP_NOT_MODIFIED = 304, | ||
349 | HTTP_PAYMENT_REQUIRED = 402, | 362 | HTTP_PAYMENT_REQUIRED = 402, |
350 | HTTP_BAD_GATEWAY = 502, | 363 | HTTP_BAD_GATEWAY = 502, |
351 | HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */ | 364 | HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */ |
@@ -358,6 +371,9 @@ static const uint16_t http_response_type[] ALIGN2 = { | |||
358 | HTTP_PARTIAL_CONTENT, | 371 | HTTP_PARTIAL_CONTENT, |
359 | #endif | 372 | #endif |
360 | HTTP_MOVED_TEMPORARILY, | 373 | HTTP_MOVED_TEMPORARILY, |
374 | #if ENABLE_FEATURE_HTTPD_ETAG | ||
375 | HTTP_NOT_MODIFIED, | ||
376 | #endif | ||
361 | HTTP_REQUEST_TIMEOUT, | 377 | HTTP_REQUEST_TIMEOUT, |
362 | HTTP_NOT_IMPLEMENTED, | 378 | HTTP_NOT_IMPLEMENTED, |
363 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH | 379 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
@@ -374,7 +390,6 @@ static const uint16_t http_response_type[] ALIGN2 = { | |||
374 | HTTP_NO_CONTENT, | 390 | HTTP_NO_CONTENT, |
375 | HTTP_MULTIPLE_CHOICES, | 391 | HTTP_MULTIPLE_CHOICES, |
376 | HTTP_MOVED_PERMANENTLY, | 392 | HTTP_MOVED_PERMANENTLY, |
377 | HTTP_NOT_MODIFIED, | ||
378 | HTTP_BAD_GATEWAY, | 393 | HTTP_BAD_GATEWAY, |
379 | HTTP_SERVICE_UNAVAILABLE, | 394 | HTTP_SERVICE_UNAVAILABLE, |
380 | #endif | 395 | #endif |
@@ -389,6 +404,9 @@ static const struct { | |||
389 | { "Partial Content", NULL }, | 404 | { "Partial Content", NULL }, |
390 | #endif | 405 | #endif |
391 | { "Found", NULL }, | 406 | { "Found", NULL }, |
407 | #if ENABLE_FEATURE_HTTPD_ETAG | ||
408 | { "Not Modified" }, | ||
409 | #endif | ||
392 | { "Request Timeout", "No request appeared within 60 seconds" }, | 410 | { "Request Timeout", "No request appeared within 60 seconds" }, |
393 | { "Not Implemented", "The requested method is not recognized" }, | 411 | { "Not Implemented", "The requested method is not recognized" }, |
394 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH | 412 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
@@ -405,7 +423,6 @@ static const struct { | |||
405 | { "No Content" }, | 423 | { "No Content" }, |
406 | { "Multiple Choices" }, | 424 | { "Multiple Choices" }, |
407 | { "Moved Permanently" }, | 425 | { "Moved Permanently" }, |
408 | { "Not Modified" }, | ||
409 | { "Bad Gateway", "" }, | 426 | { "Bad Gateway", "" }, |
410 | { "Service Unavailable", "" }, | 427 | { "Service Unavailable", "" }, |
411 | #endif | 428 | #endif |
@@ -419,6 +436,9 @@ struct globals { | |||
419 | smallint content_gzip; | 436 | smallint content_gzip; |
420 | #endif | 437 | #endif |
421 | time_t last_mod; | 438 | time_t last_mod; |
439 | #if ENABLE_FEATURE_HTTPD_ETAG | ||
440 | char *if_none_match; | ||
441 | #endif | ||
422 | char *rmt_ip_str; /* for $REMOTE_ADDR and $REMOTE_PORT */ | 442 | char *rmt_ip_str; /* for $REMOTE_ADDR and $REMOTE_PORT */ |
423 | const char *bind_addr_or_port; | 443 | const char *bind_addr_or_port; |
424 | 444 | ||
@@ -453,6 +473,9 @@ struct globals { | |||
453 | #define sizeof_hdr_buf COMMON_BUFSIZE | 473 | #define sizeof_hdr_buf COMMON_BUFSIZE |
454 | char *hdr_ptr; | 474 | char *hdr_ptr; |
455 | int hdr_cnt; | 475 | int hdr_cnt; |
476 | #if ENABLE_FEATURE_HTTPD_ETAG | ||
477 | char etag[sizeof("'%llx-%llx'") + 2 * sizeof(long long)*3]; | ||
478 | #endif | ||
456 | #if ENABLE_FEATURE_HTTPD_ERROR_PAGES | 479 | #if ENABLE_FEATURE_HTTPD_ERROR_PAGES |
457 | const char *http_error_page[ARRAY_SIZE(http_response_type)]; | 480 | const char *http_error_page[ARRAY_SIZE(http_response_type)]; |
458 | #endif | 481 | #endif |
@@ -1208,6 +1231,10 @@ static void send_headers(unsigned responseNum) | |||
1208 | #if ENABLE_FEATURE_HTTPD_LAST_MODIFIED | 1231 | #if ENABLE_FEATURE_HTTPD_LAST_MODIFIED |
1209 | "Last-Modified: %s\r\n" | 1232 | "Last-Modified: %s\r\n" |
1210 | #endif | 1233 | #endif |
1234 | #if ENABLE_FEATURE_HTTPD_ETAG | ||
1235 | "ETag: %s\r\n" | ||
1236 | #endif | ||
1237 | |||
1211 | /* Because of 4.4 (5), we can forgo sending of "Content-Length" | 1238 | /* Because of 4.4 (5), we can forgo sending of "Content-Length" |
1212 | * since we close connection afterwards, but it helps clients | 1239 | * since we close connection afterwards, but it helps clients |
1213 | * to e.g. estimate download times, show progress bars etc. | 1240 | * to e.g. estimate download times, show progress bars etc. |
@@ -1218,6 +1245,9 @@ static void send_headers(unsigned responseNum) | |||
1218 | #if ENABLE_FEATURE_HTTPD_LAST_MODIFIED | 1245 | #if ENABLE_FEATURE_HTTPD_LAST_MODIFIED |
1219 | date_str, | 1246 | date_str, |
1220 | #endif | 1247 | #endif |
1248 | #if ENABLE_FEATURE_HTTPD_ETAG | ||
1249 | G.etag, | ||
1250 | #endif | ||
1221 | file_size | 1251 | file_size |
1222 | ); | 1252 | ); |
1223 | } | 1253 | } |
@@ -1730,6 +1760,7 @@ static NOINLINE void send_file_and_exit(const char *url, int what) | |||
1730 | } | 1760 | } |
1731 | } else { | 1761 | } else { |
1732 | fd = open(url, O_RDONLY); | 1762 | fd = open(url, O_RDONLY); |
1763 | /* file_size and last_mod are already populated */ | ||
1733 | } | 1764 | } |
1734 | if (fd < 0) { | 1765 | if (fd < 0) { |
1735 | if (DEBUG) | 1766 | if (DEBUG) |
@@ -1741,6 +1772,19 @@ static NOINLINE void send_file_and_exit(const char *url, int what) | |||
1741 | send_headers_and_exit(HTTP_NOT_FOUND); | 1772 | send_headers_and_exit(HTTP_NOT_FOUND); |
1742 | log_and_exit(); | 1773 | log_and_exit(); |
1743 | } | 1774 | } |
1775 | #if ENABLE_FEATURE_HTTPD_ETAG | ||
1776 | /* ETag is "hex(last_mod)-hex(file_size)" e.g. "5e132e20-417" */ | ||
1777 | sprintf(G.etag, "\"%llx-%llx\"", (unsigned long long)last_mod, (unsigned long long)file_size); | ||
1778 | |||
1779 | if (G.if_none_match) { | ||
1780 | if (DEBUG) | ||
1781 | bb_perror_msg("If-None-Match and file's ETag are: '%s' '%s'\n", G.if_none_match, G.etag); | ||
1782 | /* Weak ETag comparision. | ||
1783 | * If-None-Match may have many ETags but they are quoted so we can use simple substring search */ | ||
1784 | if (strstr(G.if_none_match, G.etag)) | ||
1785 | send_headers_and_exit(HTTP_NOT_MODIFIED); | ||
1786 | } | ||
1787 | #endif | ||
1744 | /* If you want to know about EPIPE below | 1788 | /* If you want to know about EPIPE below |
1745 | * (happens if you abort downloads from local httpd): */ | 1789 | * (happens if you abort downloads from local httpd): */ |
1746 | signal(SIGPIPE, SIG_IGN); | 1790 | signal(SIGPIPE, SIG_IGN); |
@@ -2480,6 +2524,13 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
2480 | continue; | 2524 | continue; |
2481 | } | 2525 | } |
2482 | #endif | 2526 | #endif |
2527 | #if ENABLE_FEATURE_HTTPD_ETAG | ||
2528 | if (STRNCASECMP(iobuf, "If-None-Match:") == 0) { | ||
2529 | free(G.if_none_match); | ||
2530 | G.if_none_match = xstrdup(skip_whitespace(iobuf + sizeof("If-None-Match:") - 1)); | ||
2531 | continue; | ||
2532 | } | ||
2533 | #endif | ||
2483 | #if ENABLE_FEATURE_HTTPD_CGI | 2534 | #if ENABLE_FEATURE_HTTPD_CGI |
2484 | if (cgi_type != CGI_NONE) { | 2535 | if (cgi_type != CGI_NONE) { |
2485 | bool ct = (STRNCASECMP(iobuf, "Content-Type:") == 0); | 2536 | bool ct = (STRNCASECMP(iobuf, "Content-Type:") == 0); |