Skip to content

Commit 5c977a0

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

File tree

7 files changed

+286
-83
lines changed

7 files changed

+286
-83
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: 83 additions & 10 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,22 @@ 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 = 0;
2327+
2328+
if (io->len < sizeof(buf)) {
2329+
to_read = sizeof(buf) - io->len;
2330+
}
2331+
2332+
if (left > 0 && to_read > (size_t) left) {
2333+
to_read = left;
2334+
}
23242335

2325-
if (nc->send_iobuf.len >= NS_MAX_HTTP_SEND_IOBUF) {
2326-
/* 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) {
2336+
if (to_read == 0) {
2337+
/* Rate limiting. send_iobuf is too full, wait until it's drained. */
2338+
} else if (dp->sent<dp->cl &&(n = fread(buf, 1, to_read, dp->fp))> 0) {
23282339
ns_send(nc, buf, n);
2340+
dp->sent += n;
23292341
} else {
23302342
fclose(dp->fp);
23312343
NS_FREE(dp);
@@ -2613,24 +2625,85 @@ static void handle_ssi_request(struct ns_connection *nc, const char *path,
26132625
}
26142626
#endif /* NS_DISABLE_SSI */
26152627

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

26202646
if ((dp = (struct proto_data_http *) NS_CALLOC(1, sizeof(*dp))) == NULL) {
26212647
send_http_error(nc, 500, "Server Error"); /* LCOV_EXCL_LINE */
26222648
} else if ((dp->fp = fopen(path, "rb")) == NULL) {
26232649
NS_FREE(dp);
2650+
nc->proto_data = NULL;
26242651
send_http_error(nc, 500, "Server Error");
26252652
} else if (has_suffix(path, opts->ssi_suffix)) {
26262653
handle_ssi_request(nc, path, opts);
26272654
} else {
2655+
char etag[50], current_time[50], last_modified[50], range[50];
2656+
time_t t = time(NULL);
2657+
int64_t r1 = 0, r2 = 0, cl = st->st_size;
2658+
struct ns_str *range_hdr = ns_get_http_header(hm, "Range");
2659+
int n, status_code = 200;
2660+
const char *status_message = "OK";
2661+
2662+
/* Handle Range header */
2663+
range[0] = '\0';
2664+
if (range_hdr != NULL &&
2665+
(n = parse_range_header(range_hdr->p, &r1, &r2)) > 0 && r1 >= 0 &&
2666+
r2 >= 0) {
2667+
/* If range is specified like "400-", set second limit to content len */
2668+
if (n == 1) {
2669+
r2 = cl - 1;
2670+
}
2671+
if (r1 > r2 || r2 >= cl) {
2672+
status_code = 416;
2673+
status_message = "Requested range not satisfiable";
2674+
cl = 0;
2675+
snprintf(range, sizeof(range),
2676+
"Content-Range: bytes */%" INT64_FMT "\r\n",
2677+
(int64_t) st->st_size);
2678+
} else {
2679+
status_code = 206;
2680+
status_message = "Partial Content";
2681+
cl = r2 - r1 + 1;
2682+
snprintf(range, sizeof(range), "Content-Range: bytes %" INT64_FMT
2683+
"-%" INT64_FMT "/%" INT64_FMT "\r\n",
2684+
r1, r1 + cl - 1, (int64_t) st->st_size);
2685+
fseeko(dp->fp, r1, SEEK_SET);
2686+
}
2687+
}
2688+
2689+
construct_etag(etag, sizeof(etag), st);
2690+
gmt_time_string(current_time, sizeof(current_time), &t);
2691+
gmt_time_string(last_modified, sizeof(last_modified), &st->st_mtime);
26282692
ns_printf(nc,
2629-
"HTTP/1.1 200 OK\r\n"
2693+
"HTTP/1.1 %d %s\r\n"
2694+
"Date: %s\r\n"
2695+
"Last-Modified: %s\r\n"
2696+
"Accept-Ranges: bytes\r\n"
26302697
"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);
2698+
"Content-Length: %" INT64_FMT
2699+
"\r\n"
2700+
"%s"
2701+
"Etag: %s\r\n"
2702+
"\r\n",
2703+
status_code, status_message, current_time, last_modified,
2704+
get_mime_type(path, "text/plain"), cl, range, etag);
26332705
nc->proto_data = (void *) dp;
2706+
dp->cl = cl;
26342707
transfer_file_data(nc);
26352708
}
26362709
}
@@ -3321,7 +3394,7 @@ void ns_serve_http(struct ns_connection *nc, struct http_message *hm,
33213394
send_http_error(nc, 403, NULL);
33223395
}
33233396
} else {
3324-
ns_send_http_file(nc, path, &st, &opts);
3397+
ns_send_http_file(nc, path, &st, hm, &opts);
33253398
}
33263399
}
33273400

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: 83 additions & 10 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,22 @@ 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 = 0;
449+
450+
if (io->len < sizeof(buf)) {
451+
to_read = sizeof(buf) - io->len;
452+
}
453+
454+
if (left > 0 && to_read > (size_t) left) {
455+
to_read = left;
456+
}
446457

447-
if (nc->send_iobuf.len >= NS_MAX_HTTP_SEND_IOBUF) {
448-
/* 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) {
458+
if (to_read == 0) {
459+
/* Rate limiting. send_iobuf is too full, wait until it's drained. */
460+
} else if (dp->sent<dp->cl &&(n = fread(buf, 1, to_read, dp->fp))> 0) {
450461
ns_send(nc, buf, n);
462+
dp->sent += n;
451463
} else {
452464
fclose(dp->fp);
453465
NS_FREE(dp);
@@ -735,24 +747,85 @@ static void handle_ssi_request(struct ns_connection *nc, const char *path,
735747
}
736748
#endif /* NS_DISABLE_SSI */
737749

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

742768
if ((dp = (struct proto_data_http *) NS_CALLOC(1, sizeof(*dp))) == NULL) {
743769
send_http_error(nc, 500, "Server Error"); /* LCOV_EXCL_LINE */
744770
} else if ((dp->fp = fopen(path, "rb")) == NULL) {
745771
NS_FREE(dp);
772+
nc->proto_data = NULL;
746773
send_http_error(nc, 500, "Server Error");
747774
} else if (has_suffix(path, opts->ssi_suffix)) {
748775
handle_ssi_request(nc, path, opts);
749776
} else {
777+
char etag[50], current_time[50], last_modified[50], range[50];
778+
time_t t = time(NULL);
779+
int64_t r1 = 0, r2 = 0, cl = st->st_size;
780+
struct ns_str *range_hdr = ns_get_http_header(hm, "Range");
781+
int n, status_code = 200;
782+
const char *status_message = "OK";
783+
784+
/* Handle Range header */
785+
range[0] = '\0';
786+
if (range_hdr != NULL &&
787+
(n = parse_range_header(range_hdr->p, &r1, &r2)) > 0 && r1 >= 0 &&
788+
r2 >= 0) {
789+
/* If range is specified like "400-", set second limit to content len */
790+
if (n == 1) {
791+
r2 = cl - 1;
792+
}
793+
if (r1 > r2 || r2 >= cl) {
794+
status_code = 416;
795+
status_message = "Requested range not satisfiable";
796+
cl = 0;
797+
snprintf(range, sizeof(range),
798+
"Content-Range: bytes */%" INT64_FMT "\r\n",
799+
(int64_t) st->st_size);
800+
} else {
801+
status_code = 206;
802+
status_message = "Partial Content";
803+
cl = r2 - r1 + 1;
804+
snprintf(range, sizeof(range), "Content-Range: bytes %" INT64_FMT
805+
"-%" INT64_FMT "/%" INT64_FMT "\r\n",
806+
r1, r1 + cl - 1, (int64_t) st->st_size);
807+
fseeko(dp->fp, r1, SEEK_SET);
808+
}
809+
}
810+
811+
construct_etag(etag, sizeof(etag), st);
812+
gmt_time_string(current_time, sizeof(current_time), &t);
813+
gmt_time_string(last_modified, sizeof(last_modified), &st->st_mtime);
750814
ns_printf(nc,
751-
"HTTP/1.1 200 OK\r\n"
815+
"HTTP/1.1 %d %s\r\n"
816+
"Date: %s\r\n"
817+
"Last-Modified: %s\r\n"
818+
"Accept-Ranges: bytes\r\n"
752819
"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);
820+
"Content-Length: %" INT64_FMT
821+
"\r\n"
822+
"%s"
823+
"Etag: %s\r\n"
824+
"\r\n",
825+
status_code, status_message, current_time, last_modified,
826+
get_mime_type(path, "text/plain"), cl, range, etag);
755827
nc->proto_data = (void *) dp;
828+
dp->cl = cl;
756829
transfer_file_data(nc);
757830
}
758831
}
@@ -1443,7 +1516,7 @@ void ns_serve_http(struct ns_connection *nc, struct http_message *hm,
14431516
send_http_error(nc, 403, NULL);
14441517
}
14451518
} else {
1446-
ns_send_http_file(nc, path, &st, &opts);
1519+
ns_send_http_file(nc, path, &st, hm, &opts);
14471520
}
14481521
}
14491522

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)