Skip to content

Commit 212a5da

Browse files
committed
Add Range: header support (#237)
1 parent ea3017a commit 212a5da

File tree

7 files changed

+274
-79
lines changed

7 files changed

+274
-79
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
![](http://img.shields.io/coveralls/cesanta/fossa/master.svg "Coverage Status")
55
![](https://img.shields.io/badge/license-GPL_2-green.svg "License")
66

7-
Fossa is a
8-
http://cesanta.com/fossa.shtml[multi-protocol networking library] written in C.
7+
[Fossa]((http://cesanta.com/fossa.shtml)) is a
8+
multi-protocol networking library written in C.
99
It provides easy to use event-driven interface that allows to implement
1010
network protocols or scalable network applications with little effort.
1111
Fossa helps developers to manage the complexity of network programming

fossa.c

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1890,7 +1890,9 @@ int json_emit(char *buf, int buf_len, const char *fmt, ...) {
18901890

18911891

18921892
struct proto_data_http {
1893-
FILE *fp; /* Opened file */
1893+
FILE *fp; /* Opened file. */
1894+
int64_t cl; /* Content-Length. How many bytes to send. */
1895+
int64_t sent; /* How many bytes have been already sent. */
18941896
};
18951897

18961898
#define MIME_ENTRY(_ext, _type) \
@@ -2320,12 +2322,18 @@ static void transfer_file_data(struct ns_connection *nc) {
23202322
struct proto_data_http *dp = (struct proto_data_http *) nc->proto_data;
23212323
struct iobuf *io = &nc->send_iobuf;
23222324
char buf[NS_MAX_HTTP_SEND_IOBUF];
2323-
size_t n;
2325+
int64_t left = dp->cl - dp->sent;
2326+
size_t n = 0, to_read = sizeof(buf) - io->len;
2327+
2328+
if (left > 0 && to_read > (size_t) left) {
2329+
to_read = left;
2330+
}
23242331

23252332
if (nc->send_iobuf.len >= NS_MAX_HTTP_SEND_IOBUF) {
23262333
/* If output buffer is too big, do nothing until it's drained */
2327-
} else if ((n = fread(buf, 1, sizeof(buf) - io->len, dp->fp)) > 0) {
2334+
} else if (dp->sent<dp->cl &&(n = fread(buf, 1, to_read, dp->fp))> 0) {
23282335
ns_send(nc, buf, n);
2336+
dp->sent += n;
23292337
} else {
23302338
fclose(dp->fp);
23312339
NS_FREE(dp);
@@ -2613,24 +2621,85 @@ static void handle_ssi_request(struct ns_connection *nc, const char *path,
26132621
}
26142622
#endif /* NS_DISABLE_SSI */
26152623

2624+
static void construct_etag(char *buf, size_t buf_len, const ns_stat_t *st) {
2625+
snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"", (unsigned long) st->st_mtime,
2626+
(int64_t) st->st_size);
2627+
}
2628+
2629+
static void gmt_time_string(char *buf, size_t buf_len, time_t *t) {
2630+
strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t));
2631+
}
2632+
2633+
static int parse_range_header(const char *header, int64_t *a, int64_t *b) {
2634+
return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
2635+
}
2636+
26162637
void ns_send_http_file(struct ns_connection *nc, const char *path,
2617-
ns_stat_t *st, struct ns_serve_http_opts *opts) {
2638+
ns_stat_t *st, struct http_message *hm,
2639+
struct ns_serve_http_opts *opts) {
26182640
struct proto_data_http *dp;
26192641

26202642
if ((dp = (struct proto_data_http *) NS_CALLOC(1, sizeof(*dp))) == NULL) {
26212643
send_http_error(nc, 500, "Server Error"); /* LCOV_EXCL_LINE */
26222644
} else if ((dp->fp = fopen(path, "rb")) == NULL) {
26232645
NS_FREE(dp);
2646+
nc->proto_data = NULL;
26242647
send_http_error(nc, 500, "Server Error");
26252648
} else if (has_suffix(path, opts->ssi_suffix)) {
26262649
handle_ssi_request(nc, path, opts);
26272650
} else {
2651+
char etag[50], current_time[50], last_modified[50], range[50];
2652+
time_t t = time(NULL);
2653+
int64_t r1 = 0, r2 = 0, cl = st->st_size;
2654+
struct ns_str *range_hdr = ns_get_http_header(hm, "Range");
2655+
int n, status_code = 200;
2656+
const char *status_message = "OK";
2657+
2658+
/* Handle Range header */
2659+
range[0] = '\0';
2660+
if (range_hdr != NULL &&
2661+
(n = parse_range_header(range_hdr->p, &r1, &r2)) > 0 && r1 >= 0 &&
2662+
r2 >= 0) {
2663+
/* If range is specified like "400-", set second limit to content len */
2664+
if (n == 1) {
2665+
r2 = cl - 1;
2666+
}
2667+
if (r1 > r2 || r2 >= cl) {
2668+
status_code = 416;
2669+
status_message = "Requested range not satisfiable";
2670+
cl = 0;
2671+
snprintf(range, sizeof(range),
2672+
"Content-Range: bytes */%" INT64_FMT "\r\n",
2673+
(int64_t) st->st_size);
2674+
} else {
2675+
status_code = 206;
2676+
status_message = "Partial Content";
2677+
cl = r2 - r1 + 1;
2678+
snprintf(range, sizeof(range), "Content-Range: bytes %" INT64_FMT
2679+
"-%" INT64_FMT "/%" INT64_FMT "\r\n",
2680+
r1, r1 + cl - 1, (int64_t) st->st_size);
2681+
fseeko(dp->fp, r1, SEEK_SET);
2682+
}
2683+
}
2684+
2685+
construct_etag(etag, sizeof(etag), st);
2686+
gmt_time_string(current_time, sizeof(current_time), &t);
2687+
gmt_time_string(last_modified, sizeof(last_modified), &st->st_mtime);
26282688
ns_printf(nc,
2629-
"HTTP/1.1 200 OK\r\n"
2689+
"HTTP/1.1 %d %s\r\n"
2690+
"Date: %s\r\n"
2691+
"Last-Modified: %s\r\n"
2692+
"Accept-Ranges: bytes\r\n"
26302693
"Content-Type: %s\r\n"
2631-
"Content-Length: %lu\r\n\r\n",
2632-
get_mime_type(path, "text/plain"), (unsigned long) st->st_size);
2694+
"Content-Length: %" INT64_FMT
2695+
"\r\n"
2696+
"%s"
2697+
"Etag: %s\r\n"
2698+
"\r\n",
2699+
status_code, status_message, current_time, last_modified,
2700+
get_mime_type(path, "text/plain"), cl, range, etag);
26332701
nc->proto_data = (void *) dp;
2702+
dp->cl = cl;
26342703
transfer_file_data(nc);
26352704
}
26362705
}
@@ -3321,7 +3390,7 @@ void ns_serve_http(struct ns_connection *nc, struct http_message *hm,
33213390
send_http_error(nc, 403, NULL);
33223391
}
33233392
} else {
3324-
ns_send_http_file(nc, path, &st, &opts);
3393+
ns_send_http_file(nc, path, &st, hm, &opts);
33253394
}
33263395
}
33273396

fossa.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@
9696
#define to64(x) _atoi64(x)
9797
#define popen(x, y) _popen((x), (y))
9898
#define pclose(x) _pclose(x)
99+
#if defined(_MSC_VER) && _MSC_VER >= 1400
100+
#define fseeko(x, y, z) _fseeki64((x), (y), (z))
101+
#else
102+
#define fseeko(x, y, z) fseek((x), (y), (z))
103+
#endif
99104
typedef int socklen_t;
100105
typedef unsigned char uint8_t;
101106
typedef unsigned int uint32_t;
@@ -104,6 +109,7 @@ typedef unsigned __int64 uint64_t;
104109
typedef __int64 int64_t;
105110
typedef SOCKET sock_t;
106111
typedef uint32_t in_addr_t;
112+
#define INT64_FMT "I64d"
107113
#ifdef __MINGW32__
108114
typedef struct stat ns_stat_t;
109115
#else
@@ -117,6 +123,7 @@ typedef struct _stati64 ns_stat_t;
117123
#include <dirent.h>
118124
#include <errno.h>
119125
#include <fcntl.h>
126+
#include <inttypes.h>
120127
#include <netdb.h>
121128
#include <pthread.h>
122129
#include <stdarg.h>
@@ -131,6 +138,7 @@ typedef struct _stati64 ns_stat_t;
131138
#ifdef __APPLE__
132139
int64_t strtoll(const char* str, char** endptr, int base);
133140
#endif
141+
#define INT64_FMT PRId64
134142
#define to64(x) strtoll(x, NULL, 10)
135143
typedef int sock_t;
136144
typedef struct stat ns_stat_t;

src/common.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@
9696
#define to64(x) _atoi64(x)
9797
#define popen(x, y) _popen((x), (y))
9898
#define pclose(x) _pclose(x)
99+
#if defined(_MSC_VER) && _MSC_VER >= 1400
100+
#define fseeko(x, y, z) _fseeki64((x), (y), (z))
101+
#else
102+
#define fseeko(x, y, z) fseek((x), (y), (z))
103+
#endif
99104
typedef int socklen_t;
100105
typedef unsigned char uint8_t;
101106
typedef unsigned int uint32_t;
@@ -104,6 +109,7 @@ typedef unsigned __int64 uint64_t;
104109
typedef __int64 int64_t;
105110
typedef SOCKET sock_t;
106111
typedef uint32_t in_addr_t;
112+
#define INT64_FMT "I64d"
107113
#ifdef __MINGW32__
108114
typedef struct stat ns_stat_t;
109115
#else
@@ -117,6 +123,7 @@ typedef struct _stati64 ns_stat_t;
117123
#include <dirent.h>
118124
#include <errno.h>
119125
#include <fcntl.h>
126+
#include <inttypes.h>
120127
#include <netdb.h>
121128
#include <pthread.h>
122129
#include <stdarg.h>
@@ -131,6 +138,7 @@ typedef struct _stati64 ns_stat_t;
131138
#ifdef __APPLE__
132139
int64_t strtoll(const char* str, char** endptr, int base);
133140
#endif
141+
#define INT64_FMT PRId64
134142
#define to64(x) strtoll(x, NULL, 10)
135143
typedef int sock_t;
136144
typedef struct stat ns_stat_t;

src/http.c

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
#include "internal.h"
1313

1414
struct proto_data_http {
15-
FILE *fp; /* Opened file */
15+
FILE *fp; /* Opened file. */
16+
int64_t cl; /* Content-Length. How many bytes to send. */
17+
int64_t sent; /* How many bytes have been already sent. */
1618
};
1719

1820
#define MIME_ENTRY(_ext, _type) \
@@ -442,12 +444,18 @@ static void transfer_file_data(struct ns_connection *nc) {
442444
struct proto_data_http *dp = (struct proto_data_http *) nc->proto_data;
443445
struct iobuf *io = &nc->send_iobuf;
444446
char buf[NS_MAX_HTTP_SEND_IOBUF];
445-
size_t n;
447+
int64_t left = dp->cl - dp->sent;
448+
size_t n = 0, to_read = sizeof(buf) - io->len;
449+
450+
if (left > 0 && to_read > (size_t) left) {
451+
to_read = left;
452+
}
446453

447454
if (nc->send_iobuf.len >= NS_MAX_HTTP_SEND_IOBUF) {
448455
/* If output buffer is too big, do nothing until it's drained */
449-
} else if ((n = fread(buf, 1, sizeof(buf) - io->len, dp->fp)) > 0) {
456+
} else if (dp->sent<dp->cl &&(n = fread(buf, 1, to_read, dp->fp))> 0) {
450457
ns_send(nc, buf, n);
458+
dp->sent += n;
451459
} else {
452460
fclose(dp->fp);
453461
NS_FREE(dp);
@@ -735,24 +743,85 @@ static void handle_ssi_request(struct ns_connection *nc, const char *path,
735743
}
736744
#endif /* NS_DISABLE_SSI */
737745

746+
static void construct_etag(char *buf, size_t buf_len, const ns_stat_t *st) {
747+
snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"", (unsigned long) st->st_mtime,
748+
(int64_t) st->st_size);
749+
}
750+
751+
static void gmt_time_string(char *buf, size_t buf_len, time_t *t) {
752+
strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t));
753+
}
754+
755+
static int parse_range_header(const char *header, int64_t *a, int64_t *b) {
756+
return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
757+
}
758+
738759
void ns_send_http_file(struct ns_connection *nc, const char *path,
739-
ns_stat_t *st, struct ns_serve_http_opts *opts) {
760+
ns_stat_t *st, struct http_message *hm,
761+
struct ns_serve_http_opts *opts) {
740762
struct proto_data_http *dp;
741763

742764
if ((dp = (struct proto_data_http *) NS_CALLOC(1, sizeof(*dp))) == NULL) {
743765
send_http_error(nc, 500, "Server Error"); /* LCOV_EXCL_LINE */
744766
} else if ((dp->fp = fopen(path, "rb")) == NULL) {
745767
NS_FREE(dp);
768+
nc->proto_data = NULL;
746769
send_http_error(nc, 500, "Server Error");
747770
} else if (has_suffix(path, opts->ssi_suffix)) {
748771
handle_ssi_request(nc, path, opts);
749772
} else {
773+
char etag[50], current_time[50], last_modified[50], range[50];
774+
time_t t = time(NULL);
775+
int64_t r1 = 0, r2 = 0, cl = st->st_size;
776+
struct ns_str *range_hdr = ns_get_http_header(hm, "Range");
777+
int n, status_code = 200;
778+
const char *status_message = "OK";
779+
780+
/* Handle Range header */
781+
range[0] = '\0';
782+
if (range_hdr != NULL &&
783+
(n = parse_range_header(range_hdr->p, &r1, &r2)) > 0 && r1 >= 0 &&
784+
r2 >= 0) {
785+
/* If range is specified like "400-", set second limit to content len */
786+
if (n == 1) {
787+
r2 = cl - 1;
788+
}
789+
if (r1 > r2 || r2 >= cl) {
790+
status_code = 416;
791+
status_message = "Requested range not satisfiable";
792+
cl = 0;
793+
snprintf(range, sizeof(range),
794+
"Content-Range: bytes */%" INT64_FMT "\r\n",
795+
(int64_t) st->st_size);
796+
} else {
797+
status_code = 206;
798+
status_message = "Partial Content";
799+
cl = r2 - r1 + 1;
800+
snprintf(range, sizeof(range), "Content-Range: bytes %" INT64_FMT
801+
"-%" INT64_FMT "/%" INT64_FMT "\r\n",
802+
r1, r1 + cl - 1, (int64_t) st->st_size);
803+
fseeko(dp->fp, r1, SEEK_SET);
804+
}
805+
}
806+
807+
construct_etag(etag, sizeof(etag), st);
808+
gmt_time_string(current_time, sizeof(current_time), &t);
809+
gmt_time_string(last_modified, sizeof(last_modified), &st->st_mtime);
750810
ns_printf(nc,
751-
"HTTP/1.1 200 OK\r\n"
811+
"HTTP/1.1 %d %s\r\n"
812+
"Date: %s\r\n"
813+
"Last-Modified: %s\r\n"
814+
"Accept-Ranges: bytes\r\n"
752815
"Content-Type: %s\r\n"
753-
"Content-Length: %lu\r\n\r\n",
754-
get_mime_type(path, "text/plain"), (unsigned long) st->st_size);
816+
"Content-Length: %" INT64_FMT
817+
"\r\n"
818+
"%s"
819+
"Etag: %s\r\n"
820+
"\r\n",
821+
status_code, status_message, current_time, last_modified,
822+
get_mime_type(path, "text/plain"), cl, range, etag);
755823
nc->proto_data = (void *) dp;
824+
dp->cl = cl;
756825
transfer_file_data(nc);
757826
}
758827
}
@@ -1443,7 +1512,7 @@ void ns_serve_http(struct ns_connection *nc, struct http_message *hm,
14431512
send_http_error(nc, 403, NULL);
14441513
}
14451514
} else {
1446-
ns_send_http_file(nc, path, &st, &opts);
1515+
ns_send_http_file(nc, path, &st, hm, &opts);
14471516
}
14481517
}
14491518

test/data/range.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Faith of consciousness is freedom
2+
Faith of feeling is weakness
3+
Faith of body is stupidity.
4+
Love of consciousness evokes the same in response
5+
Love of feeling evokes the opposite
6+
Love of body depends only on type and polarity.
7+
Hope of consciousness is strength
8+
Hope of feelings is slavery
9+
Hope of body is disease.

0 commit comments

Comments
 (0)