Parse Content-Range with missing right hand side

Fixes #144
This commit is contained in:
Justine Tunney 2021-04-01 18:51:12 -07:00
parent 7abca1531f
commit 83abd68029
6 changed files with 59 additions and 26 deletions

View file

@ -26,7 +26,7 @@
*/ */
ssize_t ParseContentLength(const char *s, size_t n) { ssize_t ParseContentLength(const char *s, size_t n) {
int i, r = 0; int i, r = 0;
if (!n) return -1; if (!n) return 0;
for (i = 0; i < n; ++i) { for (i = 0; i < n; ++i) {
if (!isdigit(s[i])) return -1; if (!isdigit(s[i])) return -1;
if (__builtin_mul_overflow(r, 10, &r)) return -1; if (__builtin_mul_overflow(r, 10, &r)) return -1;

View file

@ -16,6 +16,7 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/macros.internal.h"
#include "libc/str/str.h" #include "libc/str/str.h"
#include "net/http/http.h" #include "net/http/http.h"
@ -48,14 +49,18 @@ bool ParseHttpRange(const char *p, size_t n, long resourcelength,
} }
if (n && *p == '-') { if (n && *p == '-') {
++p, --n; ++p, --n;
length = 0; if (!n) {
while (n && '0' <= *p && *p <= '9') { length = MAX(start, resourcelength) - start;
if (__builtin_mul_overflow(length, 10, &length)) return false; } else {
if (__builtin_add_overflow(length, *p - '0', &length)) return false; length = 0;
++p, --n; while (n && '0' <= *p && *p <= '9') {
if (__builtin_mul_overflow(length, 10, &length)) return false;
if (__builtin_add_overflow(length, *p - '0', &length)) return false;
++p, --n;
}
if (__builtin_add_overflow(length, 1, &length)) return false;
if (__builtin_sub_overflow(length, start, &length)) return false;
} }
if (__builtin_add_overflow(length, 1, &length)) return false;
if (__builtin_sub_overflow(length, start, &length)) return false;
} else if (__builtin_sub_overflow(resourcelength, start, &length)) { } else if (__builtin_sub_overflow(resourcelength, start, &length)) {
return false; return false;
} }

View file

@ -87,6 +87,8 @@ int ParseHttpRequest(struct HttpRequest *r, const char *p, size_t n) {
return ebadmsg(); return ebadmsg();
} }
break; break;
} else if (!('A' <= c && c <= 'Z')) {
return ebadmsg();
} }
if (++r->i == n) break; if (++r->i == n) break;
c = p[r->i] & 0xff; c = p[r->i] & 0xff;

View file

@ -20,7 +20,7 @@
#include "net/http/http.h" #include "net/http/http.h"
TEST(ParseContentLength, test) { TEST(ParseContentLength, test) {
EXPECT_EQ(-1, ParseContentLength("", 0)); EXPECT_EQ(0, ParseContentLength("", 0));
EXPECT_EQ(-1, ParseContentLength("-1", 2)); EXPECT_EQ(-1, ParseContentLength("-1", 2));
EXPECT_EQ(-1, ParseContentLength("-2", 2)); EXPECT_EQ(-1, ParseContentLength("-2", 2));
EXPECT_EQ(0, ParseContentLength("0", 1)); EXPECT_EQ(0, ParseContentLength("0", 1));

View file

@ -52,6 +52,14 @@ TEST(ParseHttpRange, testOffset) {
EXPECT_EQ(10, length); EXPECT_EQ(10, length);
} }
TEST(ParseHttpRange, testEmptySecond) {
long start, length;
const char *s = "bytes=0-";
EXPECT_TRUE(ParseHttpRange(s, strlen(s), 100, &start, &length));
EXPECT_EQ(0, start);
EXPECT_EQ(100, length);
}
TEST(ParseHttpRange, testToEnd) { TEST(ParseHttpRange, testToEnd) {
long start, length; long start, length;
const char *s = "bytes=40"; const char *s = "bytes=40";

View file

@ -463,7 +463,7 @@ static void ProgramRedirect(int code, const char *src, const char *dst) {
} else { } else {
i = redirects.n; i = redirects.n;
redirects.p = xrealloc(redirects.p, (i + 1) * sizeof(*redirects.p)); redirects.p = xrealloc(redirects.p, (i + 1) * sizeof(*redirects.p));
for (j = i; j > 0; --j) { for (j = i; j; --j) {
if (CompareSlices(r.path, r.pathlen, redirects.p[j - 1].path, if (CompareSlices(r.path, r.pathlen, redirects.p[j - 1].path,
redirects.p[j - 1].pathlen) < 0) { redirects.p[j - 1].pathlen) < 0) {
redirects.p[j] = redirects.p[j - 1]; redirects.p[j] = redirects.p[j - 1];
@ -526,6 +526,10 @@ static const char *GetContentType2(const char *path, size_t n) {
} }
static const char *GetContentType(struct Asset *a, const char *path, size_t n) { static const char *GetContentType(struct Asset *a, const char *path, size_t n) {
const char *r;
if (a->file && (r = GetContentType2(a->file->path, strlen(a->file->path)))) {
return r;
}
return firstnonnull( return firstnonnull(
GetContentType2(path, n), GetContentType2(path, n),
firstnonnull(GetContentType2(ZIP_LFILE_NAME(zmap + a->lf), firstnonnull(GetContentType2(ZIP_LFILE_NAME(zmap + a->lf),
@ -1114,6 +1118,19 @@ static void ParseRequestUri(void) {
u.data = inbuf.p + msg.uri.a; u.data = inbuf.p + msg.uri.a;
u.size = msg.uri.b - msg.uri.a; u.size = msg.uri.b - msg.uri.a;
memset(&request, 0, sizeof(request)); memset(&request, 0, sizeof(request));
if (u.size > 8 && !memcmp(u.data, "http", 4)) {
/*
* convert http://www.foo.com/index.html -> /www.foo.com/index.html
*/
if (u.data[4] == ':' && u.data[5] == '/' && u.data[6] == '/') {
u.data += 6;
u.size -= 6;
} else if (u.data[4] == 's' && u.data[5] == ':' && u.data[6] == '/' &&
u.data[7] == '/') {
u.data += 7;
u.size -= 7;
}
}
u.q = u.p = FreeLater(xmalloc(u.size * 2)); u.q = u.p = FreeLater(xmalloc(u.size * 2));
ParsePath(&u, &request.path); ParsePath(&u, &request.path);
if (u.c == '?') ParseParams(&u, &request.params); if (u.c == '?') ParseParams(&u, &request.params);
@ -1201,7 +1218,7 @@ static char *ServeError(int code, const char *reason) {
contentlength = reasonlen + 2; contentlength = reasonlen + 2;
stpcpy(stpcpy(content, reason), "\r\n"); stpcpy(stpcpy(content, reason), "\r\n");
WARNF("%s %s %`'.*s %d %s", clientaddrstr, kHttpMethod[msg.method], WARNF("%s %s %`'.*s %d %s", clientaddrstr, kHttpMethod[msg.method],
request.path.n, request.path.p, code, reason); msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a, code, reason);
return p; return p;
} }
@ -1233,9 +1250,10 @@ static char *AppendContentLength(char *p, size_t n) {
static char *AppendContentRange(char *p, long rangestart, long rangelength, static char *AppendContentRange(char *p, long rangestart, long rangelength,
long contentlength) { long contentlength) {
long endrange; long endrange;
CHECK_GE(rangestart + rangelength, rangestart); CHECK_GT(rangelength, 0);
CHECK_GT(rangestart + rangelength, rangestart);
CHECK_LE(rangestart + rangelength, contentlength); CHECK_LE(rangestart + rangelength, contentlength);
if (__builtin_add_overflow(rangestart, rangelength, &endrange)) abort(); endrange = rangestart + rangelength - 1;
p = AppendHeaderName(p, "Content-Range"); p = AppendHeaderName(p, "Content-Range");
p = stpcpy(p, "bytes "); p = stpcpy(p, "bytes ");
p += uint64toarray_radix10(rangestart, p); p += uint64toarray_radix10(rangestart, p);
@ -1361,6 +1379,7 @@ static char *ServeAsset(struct Asset *a, const char *path, size_t pathlen) {
if (ParseHttpRange(inbuf.p + msg.headers[kHttpRange].a, if (ParseHttpRange(inbuf.p + msg.headers[kHttpRange].a,
msg.headers[kHttpRange].b - msg.headers[kHttpRange].a, msg.headers[kHttpRange].b - msg.headers[kHttpRange].a,
contentlength, &rangestart, &rangelength)) { contentlength, &rangestart, &rangelength)) {
LOGF("rangestart = %ld rangelength = %ld", rangestart, rangelength);
p = SetStatus(206, "Partial Content"); p = SetStatus(206, "Partial Content");
p = AppendContentRange(p, rangestart, rangelength, contentlength); p = AppendContentRange(p, rangestart, rangelength, contentlength);
content = AddRange(content, rangestart, rangelength); content = AddRange(content, rangestart, rangelength);
@ -2139,7 +2158,7 @@ Content-Length: 0\r\n\
static void LogClose(const char *reason) { static void LogClose(const char *reason) {
if (amtread) { if (amtread) {
WARNF("%s %s with %,ld bytes unprocessed", clientaddrstr, reason); WARNF("%s %s with %,ld bytes unprocessed", clientaddrstr, reason, amtread);
} else { } else {
DEBUGF("%s %s", clientaddrstr, reason); DEBUGF("%s %s", clientaddrstr, reason);
} }
@ -2301,25 +2320,22 @@ static char *HandleMessage(void) {
if (httpversion > 101) { if (httpversion > 101) {
return ServeError(505, "HTTP Version Not Supported"); return ServeError(505, "HTTP Version Not Supported");
} }
if (msg.method > kHttpOptions || if (HasHeader(kHttpExpect) && !HeaderEquals(kHttpExpect, "100-continue")) {
return ServeError(417, "Expectation Failed");
}
if (msg.method == kHttpConnect ||
(HasHeader(kHttpTransferEncoding) && (HasHeader(kHttpTransferEncoding) &&
!HeaderEquals(kHttpTransferEncoding, "identity"))) { !HeaderEquals(kHttpTransferEncoding, "identity"))) {
return ServeError(501, "Not Implemented"); return ServeError(501, "Not Implemented");
} }
if (HasHeader(kHttpExpect) && !HeaderEquals(kHttpExpect, "100-continue")) { if (!HasHeader(kHttpContentLength) &&
return ServeError(417, "Expectation Failed"); (msg.method == kHttpPost || msg.method == kHttpPut)) {
return ServeError(411, "Length Required");
} }
if ((cl = ParseContentLength(inbuf.p + msg.headers[kHttpContentLength].a, if ((cl = ParseContentLength(inbuf.p + msg.headers[kHttpContentLength].a,
msg.headers[kHttpContentLength].b - msg.headers[kHttpContentLength].b -
msg.headers[kHttpContentLength].a)) == -1) { msg.headers[kHttpContentLength].a)) == -1) {
if (HasHeader(kHttpContentLength)) { return ServeError(400, "Bad Request");
return ServeError(400, "Bad Request");
} else if (msg.method != kHttpGet && msg.method != kHttpHead &&
msg.method != kHttpDelete && msg.method != kHttpOptions) {
return ServeError(411, "Length Required");
} else {
cl = 0;
}
} }
need = hdrsize + cl; /* synchronization is possible */ need = hdrsize + cl; /* synchronization is possible */
if (need > inbuf.n) { if (need > inbuf.n) {
@ -2363,7 +2379,7 @@ static char *HandleMessage(void) {
return ServeServerOptions(); return ServeServerOptions();
} }
if (!IsAcceptableHttpRequestPath(request.path.p, request.path.n)) { if (!IsAcceptableHttpRequestPath(request.path.p, request.path.n)) {
WARNF("%s could not parse request request %`'.*s", clientaddrstr, WARNF("%s could not parse request %`'.*s", clientaddrstr,
msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a); msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a);
connectionclose = true; connectionclose = true;
return ServeError(400, "Bad Request"); return ServeError(400, "Bad Request");
@ -2399,7 +2415,9 @@ static bool HandleRequest(void) {
p = HandleMessage(); p = HandleMessage();
} else { } else {
httpversion = 101; httpversion = 101;
connectionclose = true;
p = ServeError(400, "Bad Request"); p = ServeError(400, "Bad Request");
DEBUGF("%s received garbage %`'.*s", clientaddrstr, amtread, inbuf.p);
} }
if (!msgsize) { if (!msgsize) {
amtread = 0; amtread = 0;