summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--networking/httpd.c57
1 files changed, 54 insertions, 3 deletions
diff --git a/networking/httpd.c b/networking/httpd.c
index 94f7297..0297cf9 100644
--- a/networking/httpd.c
+++ b/networking/httpd.c
@@ -215,6 +215,19 @@
//config: Makes httpd send files using GZIP content encoding if the
//config: client supports it and a pre-compressed <file>.gz exists.
//config:
+//config:config FEATURE_HTTPD_ETAG
+//config: bool "Support caching via ETag header"
+//config: default y
+//config: depends on HTTPD
+//config: help
+//config: If server responds with ETag then next time client (browser)
+//config: resend it via If-None-Match header.
+//config: Then httpd will check if file wasn't modified and if not,
+//config: return 304 Not Modified status code.
+//config: The ETag value is constructed from last modification date
+//config: in unix epoch, and size: "hex(last_mod)-hex(file_size)".
+//config: It's not completely reliable as hash functions but fair enough.
+//config:
//config:config FEATURE_HTTPD_LAST_MODIFIED
//config: bool "Add Last-Modified header to response"
//config: default y
@@ -328,6 +341,7 @@ enum {
HTTP_OK = 200,
HTTP_PARTIAL_CONTENT = 206,
HTTP_MOVED_TEMPORARILY = 302,
+ HTTP_NOT_MODIFIED = 304,
HTTP_BAD_REQUEST = 400, /* malformed syntax */
HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */
HTTP_NOT_FOUND = 404,
@@ -345,7 +359,6 @@ enum {
HTTP_NO_CONTENT = 204,
HTTP_MULTIPLE_CHOICES = 300,
HTTP_MOVED_PERMANENTLY = 301,
- HTTP_NOT_MODIFIED = 304,
HTTP_PAYMENT_REQUIRED = 402,
HTTP_BAD_GATEWAY = 502,
HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */
@@ -358,6 +371,9 @@ static const uint16_t http_response_type[] ALIGN2 = {
HTTP_PARTIAL_CONTENT,
#endif
HTTP_MOVED_TEMPORARILY,
+#if ENABLE_FEATURE_HTTPD_ETAG
+ HTTP_NOT_MODIFIED,
+#endif
HTTP_REQUEST_TIMEOUT,
HTTP_NOT_IMPLEMENTED,
#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
@@ -374,7 +390,6 @@ static const uint16_t http_response_type[] ALIGN2 = {
HTTP_NO_CONTENT,
HTTP_MULTIPLE_CHOICES,
HTTP_MOVED_PERMANENTLY,
- HTTP_NOT_MODIFIED,
HTTP_BAD_GATEWAY,
HTTP_SERVICE_UNAVAILABLE,
#endif
@@ -389,6 +404,9 @@ static const struct {
{ "Partial Content", NULL },
#endif
{ "Found", NULL },
+#if ENABLE_FEATURE_HTTPD_ETAG
+ { "Not Modified" },
+#endif
{ "Request Timeout", "No request appeared within 60 seconds" },
{ "Not Implemented", "The requested method is not recognized" },
#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
@@ -405,7 +423,6 @@ static const struct {
{ "No Content" },
{ "Multiple Choices" },
{ "Moved Permanently" },
- { "Not Modified" },
{ "Bad Gateway", "" },
{ "Service Unavailable", "" },
#endif
@@ -419,6 +436,9 @@ struct globals {
smallint content_gzip;
#endif
time_t last_mod;
+#if ENABLE_FEATURE_HTTPD_ETAG
+ char *if_none_match;
+#endif
char *rmt_ip_str; /* for $REMOTE_ADDR and $REMOTE_PORT */
const char *bind_addr_or_port;
@@ -453,6 +473,9 @@ struct globals {
#define sizeof_hdr_buf COMMON_BUFSIZE
char *hdr_ptr;
int hdr_cnt;
+#if ENABLE_FEATURE_HTTPD_ETAG
+ char etag[sizeof("'%llx-%llx'") + 2 * sizeof(long long)*3];
+#endif
#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
const char *http_error_page[ARRAY_SIZE(http_response_type)];
#endif
@@ -1208,6 +1231,10 @@ static void send_headers(unsigned responseNum)
#if ENABLE_FEATURE_HTTPD_LAST_MODIFIED
"Last-Modified: %s\r\n"
#endif
+#if ENABLE_FEATURE_HTTPD_ETAG
+ "ETag: %s\r\n"
+#endif
+
/* Because of 4.4 (5), we can forgo sending of "Content-Length"
* since we close connection afterwards, but it helps clients
* to e.g. estimate download times, show progress bars etc.
@@ -1218,6 +1245,9 @@ static void send_headers(unsigned responseNum)
#if ENABLE_FEATURE_HTTPD_LAST_MODIFIED
date_str,
#endif
+#if ENABLE_FEATURE_HTTPD_ETAG
+ G.etag,
+#endif
file_size
);
}
@@ -1730,6 +1760,7 @@ static NOINLINE void send_file_and_exit(const char *url, int what)
}
} else {
fd = open(url, O_RDONLY);
+ /* file_size and last_mod are already populated */
}
if (fd < 0) {
if (DEBUG)
@@ -1741,6 +1772,19 @@ static NOINLINE void send_file_and_exit(const char *url, int what)
send_headers_and_exit(HTTP_NOT_FOUND);
log_and_exit();
}
+#if ENABLE_FEATURE_HTTPD_ETAG
+ /* ETag is "hex(last_mod)-hex(file_size)" e.g. "5e132e20-417" */
+ sprintf(G.etag, "\"%llx-%llx\"", (unsigned long long)last_mod, (unsigned long long)file_size);
+
+ if (G.if_none_match) {
+ if (DEBUG)
+ bb_perror_msg("If-None-Match and file's ETag are: '%s' '%s'\n", G.if_none_match, G.etag);
+ /* Weak ETag comparision.
+ * If-None-Match may have many ETags but they are quoted so we can use simple substring search */
+ if (strstr(G.if_none_match, G.etag))
+ send_headers_and_exit(HTTP_NOT_MODIFIED);
+ }
+#endif
/* If you want to know about EPIPE below
* (happens if you abort downloads from local httpd): */
signal(SIGPIPE, SIG_IGN);
@@ -2480,6 +2524,13 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
continue;
}
#endif
+#if ENABLE_FEATURE_HTTPD_ETAG
+ if (STRNCASECMP(iobuf, "If-None-Match:") == 0) {
+ free(G.if_none_match);
+ G.if_none_match = xstrdup(skip_whitespace(iobuf + sizeof("If-None-Match:") - 1));
+ continue;
+ }
+#endif
#if ENABLE_FEATURE_HTTPD_CGI
if (cgi_type != CGI_NONE) {
bool ct = (STRNCASECMP(iobuf, "Content-Type:") == 0);