From f43101753558a772b8317409cf542b3e3631cd13 Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Fri, 21 Sep 2007 22:35:18 +0000 Subject: [PATCH] httpd: optional support for partial downloads --- networking/Config.in | 9 ++++ networking/httpd.c | 119 +++++++++++++++++++++++++++++++++++++------ shell/ash.c | 17 ++++++- 3 files changed, 128 insertions(+), 17 deletions(-) diff --git a/networking/Config.in b/networking/Config.in index d687dbed9..030b1c0de 100644 --- a/networking/Config.in +++ b/networking/Config.in @@ -83,6 +83,15 @@ config HTTPD help Serve web pages via an HTTP server. +config FEATURE_HTTPD_RANGES + bool "Support 'Ranges:' header" + default n + depends on HTTPD + help + Makes httpd emit "Accept-Ranges: bytes" header and understand + "Range: bytes=NNN-[MMM]" header. Allows for resuming interrupted + downloads, seeking in multimedia players etc. + config FEATURE_HTTPD_USE_SENDFILE bool "Use sendfile system call" default n diff --git a/networking/httpd.c b/networking/httpd.c index a8e5df98c..057416040 100644 --- a/networking/httpd.c +++ b/networking/httpd.c @@ -138,6 +138,7 @@ typedef struct Htaccess_IP { enum { HTTP_OK = 200, + HTTP_PARTIAL_CONTENT = 206, HTTP_MOVED_TEMPORARILY = 302, HTTP_BAD_REQUEST = 400, /* malformed syntax */ HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */ @@ -165,6 +166,9 @@ enum { static const uint16_t http_response_type[] ALIGN2 = { HTTP_OK, +#if ENABLE_FEATURE_HTTPD_RANGES + HTTP_PARTIAL_CONTENT, +#endif HTTP_MOVED_TEMPORARILY, HTTP_REQUEST_TIMEOUT, HTTP_NOT_IMPLEMENTED, @@ -192,7 +196,10 @@ static const struct { const char *info; } http_response[ARRAY_SIZE(http_response_type)] = { { "OK", NULL }, - { "Found", "Directories must end with a slash" }, /* ?? */ +#if ENABLE_FEATURE_HTTPD_RANGES + { "Partial Content", NULL }, +#endif + { "Found", NULL }, { "Request Timeout", "No request appeared within 60 seconds" }, { "Not Implemented", "The requested method is not recognized" }, #if ENABLE_FEATURE_HTTPD_BASIC_AUTH @@ -214,13 +221,13 @@ static const struct { #endif }; + struct globals { int verbose; /* must be int (used by getopt32) */ smallint flg_deny_all; unsigned rmt_ip; /* used for IP-based allow/deny rules */ time_t last_mod; - off_t ContentLength; /* -1 - unknown */ char *rmt_ip_str; /* for $REMOTE_ADDR and $REMOTE_PORT */ const char *bind_addr_or_port; @@ -237,6 +244,13 @@ struct globals { USE_FEATURE_HTTPD_CGI(char *referer;) USE_FEATURE_HTTPD_CGI(char *user_agent;) + off_t file_size; /* -1 - unknown */ +#if ENABLE_FEATURE_HTTPD_RANGES + off_t range_start; + off_t range_end; + off_t range_len; +#endif + #if ENABLE_FEATURE_HTTPD_BASIC_AUTH Htaccess *g_auth; /* config user:password lines */ #endif @@ -264,13 +278,18 @@ struct globals { #define home_httpd (G.home_httpd ) #define found_mime_type (G.found_mime_type ) #define found_moved_temporarily (G.found_moved_temporarily) -#define ContentLength (G.ContentLength ) #define last_mod (G.last_mod ) #define ip_a_d (G.ip_a_d ) #define g_realm (G.g_realm ) #define remoteuser (G.remoteuser ) #define referer (G.referer ) #define user_agent (G.user_agent ) +#define file_size (G.file_size ) +#if ENABLE_FEATURE_HTTPD_RANGES +#define range_start (G.range_start ) +#define range_end (G.range_end ) +#define range_len (G.range_len ) +#endif #define rmt_ip_str (G.rmt_ip_str ) #define g_auth (G.g_auth ) #define mime_a (G.mime_a ) @@ -283,9 +302,17 @@ struct globals { PTR_TO_GLOBALS = xzalloc(sizeof(G)); \ USE_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \ bind_addr_or_port = "80"; \ - ContentLength = -1; \ + file_size = -1; \ } while (0) +#if !ENABLE_FEATURE_HTTPD_RANGES +enum { + range_start = 0, + range_end = MAXINT(off_t) - 1, + range_len = MAXINT(off_t), +}; +#endif + #define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1) @@ -921,10 +948,26 @@ static void send_headers(int responseNum) } #endif - if (ContentLength != -1) { /* file */ + if (file_size != -1) { /* file */ strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&last_mod)); - len += sprintf(iobuf + len, "Last-Modified: %s\r\n%s %"OFF_FMT"d\r\n", - tmp_str, "Content-length:", ContentLength); +#if ENABLE_FEATURE_HTTPD_RANGES + if (responseNum == HTTP_PARTIAL_CONTENT) { + len += sprintf(iobuf + len, "Content-Range: bytes %"OFF_FMT"d-%"OFF_FMT"d/%"OFF_FMT"d\r\n", + range_start, + range_end, + file_size); + file_size = range_end - range_start + 1; + } +#endif + len += sprintf(iobuf + len, +#if ENABLE_FEATURE_HTTPD_RANGES + "Accept-Ranges: bytes\r\n" +#endif + "Last-Modified: %s\r\n%s %"OFF_FMT"d\r\n", + tmp_str, + "Content-length:", + file_size + ); } iobuf[len++] = '\r'; iobuf[len++] = '\n'; @@ -1373,7 +1416,7 @@ static void send_file_and_exit(const char *url, int headers) const char *try_suffix; ssize_t count; #if ENABLE_FEATURE_HTTPD_USE_SENDFILE - off_t offset = 0; + off_t offset; #endif suffix = strrchr(url, '.'); @@ -1415,6 +1458,26 @@ static void send_file_and_exit(const char *url, int headers) if (headers) send_headers_and_exit(HTTP_NOT_FOUND); } +#if ENABLE_FEATURE_HTTPD_RANGES + if (!headers) + range_start = 0; /* err pages and ranges don't mix */ + range_len = MAXINT(off_t); + if (range_start) { + if (!range_end) { + range_end = file_size - 1; + } + if (range_end < range_start + || lseek(f, range_start, SEEK_SET) != range_start + ) { + lseek(f, 0, SEEK_SET); + range_start = 0; + } else { + range_len = range_end - range_start + 1; + send_headers(HTTP_PARTIAL_CONTENT); + headers = 0; + } + } +#endif if (headers) send_headers(HTTP_OK); @@ -1424,24 +1487,32 @@ static void send_file_and_exit(const char *url, int headers) signal(SIGPIPE, SIG_IGN); #if ENABLE_FEATURE_HTTPD_USE_SENDFILE + offset = range_start; do { - /* byte count (3rd arg) is rounded down to 64k */ - count = sendfile(1, f, &offset, MAXINT(ssize_t) - 0xffff); + /* sz is rounded down to 64k */ + ssize_t sz = MAXINT(ssize_t) - 0xffff; + USE_FEATURE_HTTPD_RANGES(if (sz > range_len) sz = range_len;) + count = sendfile(1, f, &offset, sz); if (count < 0) { - if (offset == 0) + if (offset == range_start) goto fallback; goto fin; } - } while (count > 0); + USE_FEATURE_HTTPD_RANGES(range_len -= sz;) + } while (count > 0 && range_len); log_and_exit(); fallback: #endif while ((count = safe_read(f, iobuf, IOBUF_SIZE)) > 0) { - ssize_t n = count; - count = full_write(1, iobuf, count); + ssize_t n; + USE_FEATURE_HTTPD_RANGES(if (count > range_len) count = range_len;) + n = full_write(1, iobuf, count); if (count != n) break; + USE_FEATURE_HTTPD_RANGES(range_len -= count;) + if (!range_len) + break; } #if ENABLE_FEATURE_HTTPD_USE_SENDFILE fin: @@ -1781,6 +1852,23 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) credentials = checkPerm(urlcopy, tptr); } #endif /* FEATURE_HTTPD_BASIC_AUTH */ +#if ENABLE_FEATURE_HTTPD_RANGES + if (STRNCASECMP(iobuf, "Range:") == 0) { + // We know only bytes=NNN-[MMM] + char *s = skip_whitespace(iobuf + sizeof("Range:")-1); + if (strncmp(s, "bytes=", 6) == 0) { + s += sizeof("bytes=")-1; + range_start = BB_STRTOOFF(s, &s, 10); + if (s[0] != '-' || range_start < 0) { + range_start = 0; + } else if (s[1]) { + range_end = BB_STRTOOFF(s+1, NULL, 10); + if (errno || range_end < range_start) + range_start = 0; + } + } + } +#endif } /* while extra header reading */ } @@ -1833,8 +1921,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) if (urlp[-1] == '/') strcpy(urlp, "index.html"); if (stat(tptr, &sb) == 0) { - /* It's a dir URL and there is index.html */ - ContentLength = sb.st_size; + file_size = sb.st_size; last_mod = sb.st_mtime; } #if ENABLE_FEATURE_HTTPD_CGI diff --git a/shell/ash.c b/shell/ash.c index 167232c33..492474110 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -2494,6 +2494,7 @@ pwdcmd(int argc, char **argv) #define DQSYNTAX 1 /* in double quotes */ #define SQSYNTAX 2 /* in single quotes */ #define ARISYNTAX 3 /* in arithmetic */ +#define PSSYNTAX 4 /* prompt */ #if ENABLE_ASH_OPTIMIZE_FOR_SIZE #define USE_SIT_FUNCTION @@ -9886,6 +9887,9 @@ readtoken1(int firstc, int syntax, char *eofmark, int striptabs) smallint dblquote; smallint oldstyle; smallint prevsyntax; /* syntax before arithmetic */ +#if ENABLE_ASH_EXPAND_PRMT + smallint pssyntax; /* we are expanding a prompt string */ +#endif int varnest; /* levels of variables expansion */ int arinest; /* levels of arithmetic expansion */ int parenlevel; /* levels of parens in arithmetic */ @@ -9910,6 +9914,11 @@ readtoken1(int firstc, int syntax, char *eofmark, int striptabs) dblquote = (syntax == DQSYNTAX); oldstyle = 0; prevsyntax = 0; +#if ENABLE_ASH_EXPAND_PRMT + pssyntax = (syntax == PSSYNTAX); + if (pssyntax) + syntax = DQSYNTAX; +#endif varnest = 0; arinest = 0; parenlevel = 0; @@ -9948,6 +9957,12 @@ readtoken1(int firstc, int syntax, char *eofmark, int striptabs) if (doprompt) setprompt(2); } else { +#if ENABLE_ASH_EXPAND_PRMT + if (c == '$' && pssyntax) { + USTPUTC(CTLESC, out); + USTPUTC('\\', out); + } +#endif if (dblquote && c != '\\' && c != '`' && c != '$' && ( @@ -10780,7 +10795,7 @@ expandstr(const char *ps) /* XXX Fix (char *) cast. */ setinputstring((char *)ps); - readtoken1(pgetc(), DQSYNTAX, nullstr, 0); + readtoken1(pgetc(), PSSYNTAX, nullstr, 0); popfile(); n.narg.type = NARG;