diff --git a/mongoose.c b/mongoose.c index 7fe04d48..2742d7f1 100644 --- a/mongoose.c +++ b/mongoose.c @@ -901,6 +901,26 @@ static const char *guess_content_type(const char *filename) { return "text/plain; charset=utf-8"; } +static int getrange(struct mg_str *s, int64_t *a, int64_t *b) { + int i, numparsed = 0; + LOG(LL_INFO, ("%.*s", (int) s->len, s->ptr)); + for (i = 0; i + 6 < (int) s->len; i++) { + if (memcmp(&s->ptr[i], "bytes=", 6) == 0) { + struct mg_str p = mg_str_n(s->ptr + i + 6, s->len - i - 6); + if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++; + *a = mg_to64(p); + // LOG(LL_INFO, ("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed)); + while (p.len && p.ptr[0] >= '0' && p.ptr[0] <= '9') p.ptr++, p.len--; + if (p.len && p.ptr[0] == '-') p.ptr++, p.len--; + *b = mg_to64(p); + if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++; + // LOG(LL_INFO, ("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed)); + break; + } + } + return numparsed; +} + void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, const char *path, const char *mime, const char *hdrs) { struct mg_str *inm = mg_http_get_header(hm, "If-None-Match"); @@ -917,10 +937,42 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, fclose(fp); mg_printf(c, "HTTP/1.1 304 Not Modified\r\nContent-Length: 0\r\n\r\n"); } else { + int n, status = 200; + char range[70] = ""; + int64_t r1 = 0, r2 = 0, cl = st.st_size; + + // Handle Range header + struct mg_str *rh = mg_http_get_header(hm, "Range"); + if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0 && r1 >= 0 && r2 >= 0) { + // If range is specified like "400-", set second limit to content len + if (n == 1) r2 = cl - 1; + if (r1 > r2 || r2 >= cl) { + status = 416; + cl = 0; + snprintf(range, sizeof(range), + "Content-Range: bytes */" MG_INT64_FMT "\r\n", + (int64_t) st.st_size); + } else { + status = 206; + cl = r2 - r1 + 1; + snprintf(range, sizeof(range), + "Content-Range: bytes " MG_INT64_FMT "-" MG_INT64_FMT + "/" MG_INT64_FMT "\r\n", + r1, r1 + cl - 1, (int64_t) st.st_size); +#if _FILE_OFFSET_BITS == 64 || _POSIX_C_SOURCE >= 200112L || \ + _XOPEN_SOURCE >= 600 + fseeko(fp, r1, SEEK_SET); +#else + fseek(fp, (long) r1, SEEK_SET); +#endif + } + } + mg_printf(c, - "HTTP/1.1 200 OK\r\nContent-Type: %s\r\n" - "Etag: %s\r\nContent-Length: " MG_INT64_FMT "\r\n%s\r\n", - mime, etag, (int64_t) st.st_size, hdrs ? hdrs : ""); + "HTTP/1.1 %d %s\r\nContent-Type: %s\r\n" + "Etag: %s\r\nContent-Length: " MG_INT64_FMT "\r\n%s%s\r\n", + status, mg_http_status_code_str(status), mime, etag, cl, range, + hdrs ? hdrs : ""); if (mg_vcasecmp(&hm->method, "HEAD") == 0) { fclose(fp); } else { diff --git a/src/http.c b/src/http.c index df5ff515..bc92b191 100644 --- a/src/http.c +++ b/src/http.c @@ -479,6 +479,26 @@ static const char *guess_content_type(const char *filename) { return "text/plain; charset=utf-8"; } +static int getrange(struct mg_str *s, int64_t *a, int64_t *b) { + int i, numparsed = 0; + LOG(LL_INFO, ("%.*s", (int) s->len, s->ptr)); + for (i = 0; i + 6 < (int) s->len; i++) { + if (memcmp(&s->ptr[i], "bytes=", 6) == 0) { + struct mg_str p = mg_str_n(s->ptr + i + 6, s->len - i - 6); + if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++; + *a = mg_to64(p); + // LOG(LL_INFO, ("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed)); + while (p.len && p.ptr[0] >= '0' && p.ptr[0] <= '9') p.ptr++, p.len--; + if (p.len && p.ptr[0] == '-') p.ptr++, p.len--; + *b = mg_to64(p); + if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++; + // LOG(LL_INFO, ("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed)); + break; + } + } + return numparsed; +} + void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, const char *path, const char *mime, const char *hdrs) { struct mg_str *inm = mg_http_get_header(hm, "If-None-Match"); @@ -495,10 +515,42 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, fclose(fp); mg_printf(c, "HTTP/1.1 304 Not Modified\r\nContent-Length: 0\r\n\r\n"); } else { + int n, status = 200; + char range[70] = ""; + int64_t r1 = 0, r2 = 0, cl = st.st_size; + + // Handle Range header + struct mg_str *rh = mg_http_get_header(hm, "Range"); + if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0 && r1 >= 0 && r2 >= 0) { + // If range is specified like "400-", set second limit to content len + if (n == 1) r2 = cl - 1; + if (r1 > r2 || r2 >= cl) { + status = 416; + cl = 0; + snprintf(range, sizeof(range), + "Content-Range: bytes */" MG_INT64_FMT "\r\n", + (int64_t) st.st_size); + } else { + status = 206; + cl = r2 - r1 + 1; + snprintf(range, sizeof(range), + "Content-Range: bytes " MG_INT64_FMT "-" MG_INT64_FMT + "/" MG_INT64_FMT "\r\n", + r1, r1 + cl - 1, (int64_t) st.st_size); +#if _FILE_OFFSET_BITS == 64 || _POSIX_C_SOURCE >= 200112L || \ + _XOPEN_SOURCE >= 600 + fseeko(fp, r1, SEEK_SET); +#else + fseek(fp, (long) r1, SEEK_SET); +#endif + } + } + mg_printf(c, - "HTTP/1.1 200 OK\r\nContent-Type: %s\r\n" - "Etag: %s\r\nContent-Length: " MG_INT64_FMT "\r\n%s\r\n", - mime, etag, (int64_t) st.st_size, hdrs ? hdrs : ""); + "HTTP/1.1 %d %s\r\nContent-Type: %s\r\n" + "Etag: %s\r\nContent-Length: " MG_INT64_FMT "\r\n%s%s\r\n", + status, mg_http_status_code_str(status), mime, etag, cl, range, + hdrs ? hdrs : ""); if (mg_vcasecmp(&hm->method, "HEAD") == 0) { fclose(fp); } else { diff --git a/test/unit_test.c b/test/unit_test.c index b8be7b6d..4c89bd60 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -868,6 +868,15 @@ static void test_http_parse(void) { } } +static void ehr(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { + if (ev == MG_EV_HTTP_MSG) { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + struct mg_http_serve_opts opts = {"./test/data", NULL, NULL}; + mg_http_serve_dir(c, hm, &opts); + } + (void) fn_data; +} + static void test_http_range(void) { struct mg_mgr mgr; const char *url = "http://127.0.0.1:12349"; @@ -875,40 +884,39 @@ static void test_http_range(void) { char buf[FETCH_BUF_SIZE]; mg_mgr_init(&mgr); - mg_http_listen(&mgr, url, eh1, NULL); + mg_http_listen(&mgr, url, ehr, NULL); ASSERT(fetch(&mgr, buf, url, "GET /range.txt HTTP/1.0\n\n") == 200); ASSERT(mg_http_parse(buf, strlen(buf), &hm) > 0); - LOG(LL_INFO, ("----%d\n[%s]", (int) hm.body.len, buf)); ASSERT(hm.body.len == 312); - // ASSERT(strlen(buf) == 312); -#if 0 - ASSERT(fetch(&mgr, buf, url, "%s", - "GET /data/range.txt HTTP/1.0\nRange: bytes=5-10\n\n") == 206); - ASSERT(strcmp(buf, "\r\n of co") == 0); - ASSERT_STREQ_NZ(buf, "HTTP/1.1 206 Partial Content"); - ASSERT(strstr(buf, "Content-Length: 6\r\n") != 0); - ASSERT(strstr(buf, "Content-Range: bytes 5-10/312\r\n") != 0); - ASSERT_STREQ(buf + strlen(buf) - 8, "\r\n of co"); + fetch(&mgr, buf, url, "%s", "GET /range.txt HTTP/1.0\nRange: bytes=5-10\n\n"); + ASSERT(mg_http_parse(buf, strlen(buf), &hm) > 0); + ASSERT(mg_strcmp(hm.uri, mg_str("206")) == 0); + ASSERT(mg_strcmp(hm.proto, mg_str("Partial Content")) == 0); + ASSERT(mg_strcmp(hm.body, mg_str(" of co")) == 0); + ASSERT(mg_strcmp(*mg_http_get_header(&hm, "Content-Range"), + mg_str("bytes 5-10/312")) == 0); - /* Fetch till EOF */ - fetch_http(buf, "%s", "GET /data/range.txt HTTP/1.0\nRange: bytes=300-\n\n"); - ASSERT_STREQ_NZ(buf, "HTTP/1.1 206 Partial Content"); - ASSERT(strstr(buf, "Content-Length: 12\r\n") != 0); - ASSERT(strstr(buf, "Content-Range: bytes 300-311/312\r\n") != 0); - ASSERT_STREQ(buf + strlen(buf) - 14, "\r\nis disease.\n"); + // Fetch till EOF + fetch(&mgr, buf, url, "%s", "GET /range.txt HTTP/1.0\nRange: bytes=300-\n\n"); + ASSERT(mg_http_parse(buf, strlen(buf), &hm) > 0); + ASSERT(mg_strcmp(hm.uri, mg_str("206")) == 0); + ASSERT(mg_strcmp(hm.body, mg_str("is disease.\n")) == 0); + // LOG(LL_INFO, ("----%d\n[%s]", (int) hm.body.len, buf)); - /* Fetch past EOF, must trigger 416 response */ - fetch_http(buf, "%s", "GET /data/range.txt HTTP/1.0\nRange: bytes=1000-\n\n"); - ASSERT_STREQ_NZ(buf, "HTTP/1.1 416"); - ASSERT(strstr(buf, "Content-Length: 0\r\n") != 0); - ASSERT(strstr(buf, "Content-Range: bytes */312\r\n") != 0); + // Fetch past EOF, must trigger 416 response + fetch(&mgr, buf, url, "%s", "GET /range.txt HTTP/1.0\nRange: bytes=999-\n\n"); + ASSERT(mg_http_parse(buf, strlen(buf), &hm) > 0); + ASSERT(mg_strcmp(hm.uri, mg_str("416")) == 0); + ASSERT(hm.body.len == 0); + ASSERT(mg_strcmp(*mg_http_get_header(&hm, "Content-Range"), + mg_str("bytes */312")) == 0); - /* Request range past EOF, must trigger 416 response */ - fetch_http(buf, "%s", "GET /data/range.txt HTTP/1.0\nRange: bytes=0-312\n\n"); - ASSERT_STREQ_NZ(buf, "HTTP/1.1 416"); -#endif + fetch(&mgr, buf, url, "%s", + "GET /range.txt HTTP/1.0\nRange: bytes=0-312\n\n"); + ASSERT(mg_http_parse(buf, strlen(buf), &hm) > 0); + ASSERT(mg_strcmp(hm.uri, mg_str("416")) == 0); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL);