# HG changeset patch # User Olaf Wintermann # Date 1445113478 -7200 # Node ID 136a76e293b5924b0cfb7f9e589029a0256c440f # Parent 7fbcdbad0baaae03a77a45f1b383618609669834 added etag and conditional request implementation from Open Web Server diff -r 7fbcdbad0baa -r 136a76e293b5 src/server/daemon/http.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/daemon/http.c Sat Oct 17 22:24:38 2015 +0200 @@ -0,0 +1,230 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * + * THE BSD LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "http.h" +#include "protocol.h" + +#include "../util/pblock.h" +#include "../util/pool.h" +#include "../util/util.h" + + +static int http_etag = 1; + +/* --------------------------- http_format_etag --------------------------- */ + +NSAPI_PUBLIC void http_format_etag(Session *sn, Request *rq, char *etagp, int etaglen, off_t size, time_t mtime) +{ + /* + * Heuristic for weak/strong validator: if the request was made within + * a second of the last-modified date, then we send a weak Etag. + */ + if (rq->req_start - mtime <= 1) { + /* Send a weak Etag */ + *etagp++ = 'W'; + *etagp++ = '/'; + etaglen -= 2; + } + + /* + * Create an Etag out of the metadata (size, mtime) to obtain Etag + * uniqueness. + */ + util_snprintf(etagp, etaglen, "\"%x-%x\"", (int)size, (int)mtime); +} + + +/* --------------------------- http_weaken_etag --------------------------- */ + +NSAPI_PUBLIC void http_weaken_etag(Session *sn, Request *rq) +{ + /* Replace any existing strong Etag with a weak Etag */ + pb_param *pp = pblock_findkey(pb_key_etag, rq->srvhdrs); + if (pp) { + if (pp->value[0] != 'W' || pp->value[1] != '/') { + char *weak = (char *) pool_malloc(sn->pool, 2 + strlen(pp->value) + 1); + + weak[0] = 'W'; + weak[1] = '/'; + strcpy(weak + 2, pp->value); + + pool_free(sn->pool, pp->value); + pp->value = weak; + } + } +} + + +/* ------------------------------ match_etag ------------------------------ */ + +NSAPI_PUBLIC int http_match_etag(const char *header, const char *etag, int strong) +{ + if (!etag) + return 0; /* mismatch */ + + if (header[0] == '*') + return 1; /* match */ + + if (etag[0] == 'W' && etag[1] == '/') { + /* Weak Etags never match when using the strong validator */ + if (strong) + return 0; /* mismatch */ + + etag += 2; + } + + /* Look for Etag in header */ + const char *found = strstr(header, etag); + if (!found) + return 0; /* mismatch */ + + /* Weak Etags never match when using the strong validator */ + if (strong && found >= header + 2 && found[-2] == 'W' && found[-1] == '/') + return 0; /* mismatch */ + + return 1; /* match */ +} + + +/* ----------------------- http_check_preconditions ----------------------- */ + +NSAPI_PUBLIC int http_check_preconditions(Session *sn, Request *rq, struct tm *mtm, const char *etag) +{ + char *header; + + /* If-modified-since */ + header = pblock_findkeyval(pb_key_if_modified_since, rq->headers); + if (header) { + if (mtm && util_later_than(mtm, header)) { + protocol_status(sn, rq, PROTOCOL_NOT_MODIFIED, NULL); + return REQ_ABORTED; + } + } + + /* If-unmodified-since */ + header = pblock_findkeyval(pb_key_if_unmodified_since, rq->headers); + if (header) { + if (mtm && !util_later_than(mtm, header)) { + //PRTime temptime; + //PRStatus status = PR_ParseTimeString(header, PR_TRUE, &temptime); + //if (status == PR_SUCCESS) { + // http_status(sn, rq, PROTOCOL_PRECONDITION_FAIL, NULL); + // return REQ_ABORTED; + //} + } + } + + /* If-none-match */ + header = pblock_findkeyval(pb_key_if_none_match, rq->headers); + if (header) { + /* If the If-none-match header matches the current Etag... */ + if (ISMGET(rq) || ISMHEAD(rq)) { + if (http_match_etag(header, etag, PR_FALSE)) { + protocol_status(sn, rq, PROTOCOL_NOT_MODIFIED, NULL); + return REQ_ABORTED; + } + } else { + if (http_match_etag(header, etag, PR_TRUE)) { + protocol_status(sn, rq, PROTOCOL_PRECONDITION_FAIL, NULL); + return REQ_ABORTED; + } + } + } + + /* If-match */ + header = pblock_findkeyval(pb_key_if_match, rq->headers); + if (header) { + /* If the If-match header matches the current Etag... */ + if (!http_match_etag(header, etag, PR_TRUE)) { + protocol_status(sn, rq, PROTOCOL_PRECONDITION_FAIL, NULL); + return REQ_ABORTED; + } + } + + return REQ_PROCEED; +} + + +/* ---------------------------- http_set_finfo ---------------------------- */ + +static inline int set_finfo(Session *sn, Request *rq, off_t size, time_t mtime) +{ + struct tm mtms; + struct tm *mtm = system_gmtime(&mtime, &mtms); + pb_param *pp; + + /* Insert Last-modified */ + if (mtm) { + pp = pblock_key_param_create(rq->srvhdrs, pb_key_last_modified, NULL, + HTTP_DATE_LEN); + if (!pp || !pp->value) + return REQ_ABORTED; + strftime(pp->value, HTTP_DATE_LEN, HTTP_DATE_FMT, mtm); + pblock_kpinsert(pb_key_last_modified, pp, rq->srvhdrs); + } + + /* Insert Content-length */ + const int content_length_size = 21; + pp = pblock_key_param_create(rq->srvhdrs, pb_key_content_length, NULL, + content_length_size); + if (!pp || !pp->value) + return REQ_ABORTED; + snprintf(pp->value, content_length_size, "%lld", (long long)size); + pblock_kpinsert(pb_key_content_length, pp, rq->srvhdrs); + + char *etag; + if (http_etag) { + /* Insert Etag */ + pp = pblock_key_param_create(rq->srvhdrs, pb_key_etag, NULL, MAX_ETAG); + if (!pp || !pp->value) + return REQ_ABORTED; + http_format_etag(sn, rq, pp->value, MAX_ETAG, size, mtime); + pblock_kpinsert(pb_key_etag, pp, rq->srvhdrs); + etag = pp->value; + } else { + etag = NULL; + } + + /* Check If-modified-since, etc. */ + return http_check_preconditions(sn, rq, mtm, etag); +} + +NSAPI_PUBLIC int http_set_finfo(Session *sn, Request *rq, struct stat *finfo) +{ + return set_finfo(sn, rq, finfo->st_size, finfo->st_mtime); +} + + diff -r 7fbcdbad0baa -r 136a76e293b5 src/server/daemon/http.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/daemon/http.h Sat Oct 17 22:24:38 2015 +0200 @@ -0,0 +1,46 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef HTTP_H +#define HTTP_H + +#include "../public/nsapi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_ETAG 80 + + +#ifdef __cplusplus +} +#endif + +#endif /* HTTP_H */ + diff -r 7fbcdbad0baa -r 136a76e293b5 src/server/daemon/httprequest.c --- a/src/server/daemon/httprequest.c Sat Oct 17 21:17:34 2015 +0200 +++ b/src/server/daemon/httprequest.c Sat Oct 17 22:24:38 2015 +0200 @@ -54,6 +54,8 @@ hd->alloc = 16; req->headers = hd; + + req->req_start = time(NULL); } void http_request_cleanup(HTTPRequest *req) { @@ -98,6 +100,7 @@ if(rq == NULL) { /* TODO: error */ } + rq->rq.req_start = request->req_start; rq->phase = NSAPIAuthTrans; // fill session structure diff -r 7fbcdbad0baa -r 136a76e293b5 src/server/daemon/httprequest.h --- a/src/server/daemon/httprequest.h Sat Oct 17 21:17:34 2015 +0200 +++ b/src/server/daemon/httprequest.h Sat Oct 17 22:24:38 2015 +0200 @@ -53,6 +53,7 @@ sstr_t httpv; HeaderArray *headers; netbuf *netbuf; + time_t req_start; }; struct _header { diff -r 7fbcdbad0baa -r 136a76e293b5 src/server/daemon/objs.mk --- a/src/server/daemon/objs.mk Sat Oct 17 21:17:34 2015 +0200 +++ b/src/server/daemon/objs.mk Sat Oct 17 22:24:38 2015 +0200 @@ -37,6 +37,7 @@ DAEMONOBJ += httprequest.o DAEMONOBJ += main.o DAEMONOBJ += protocol.o +DAEMONOBJ += http.o DAEMONOBJ += request.o DAEMONOBJ += session.o DAEMONOBJ += sessionhandler.o diff -r 7fbcdbad0baa -r 136a76e293b5 src/server/daemon/protocol.h --- a/src/server/daemon/protocol.h Sat Oct 17 21:17:34 2015 +0200 +++ b/src/server/daemon/protocol.h Sat Oct 17 22:24:38 2015 +0200 @@ -26,8 +26,8 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef HTTP_H -#define HTTP_H +#ifndef PROTOCOL_H +#define PROTOCOL_H #include "../public/nsapi.h" #include "../util/io.h" @@ -54,5 +54,5 @@ } #endif -#endif /* HTTP_H */ +#endif /* PROTOCOL_H */ diff -r 7fbcdbad0baa -r 136a76e293b5 src/server/public/nsapi.h --- a/src/server/public/nsapi.h Sat Oct 17 21:17:34 2015 +0200 +++ b/src/server/public/nsapi.h Sat Oct 17 22:24:38 2015 +0200 @@ -1376,6 +1376,12 @@ #define protocol_uri2url http_uri2url #define protocol_uri2url_dynamic http_uri2url_dynamic + +NSAPI_PUBLIC void http_format_etag(Session *sn, Request *rq, char *etagp, int etaglen, off_t size, time_t mtime); +NSAPI_PUBLIC int http_check_preconditions(Session *sn, Request *rq, struct tm *mtm, const char *etag); +NSAPI_PUBLIC int http_set_finfo(Session *sn, Request *rq, struct stat *finfo); + + typedef void (*thrstartfunc)(void *); SYS_THREAD INTsysthread_start(int prio, int stksz, thrstartfunc fn, void *arg); NSAPI_PUBLIC void INTsysthread_sleep(int milliseconds); diff -r 7fbcdbad0baa -r 136a76e293b5 src/server/safs/service.c --- a/src/server/safs/service.c Sat Oct 17 21:17:34 2015 +0200 +++ b/src/server/safs/service.c Sat Oct 17 22:24:38 2015 +0200 @@ -81,14 +81,14 @@ protocol_status(sn, rq, 302, NULL); http_start_response(sn, rq); vfs_close(fd); - return fd; + return NULL; } - - // add content-length header - char contentLength[32]; - int len = snprintf(contentLength, 32, "%jd", s->st_size); - - pblock_kvinsert(pb_key_content_length, contentLength, len, rq->srvhdrs); + + // sets last-modified, content-length and checks conditions + if(http_set_finfo(sn, rq, s) != REQ_PROCEED) { + vfs_close(fd); + return NULL; + } // start response protocol_status(sn, rq, 200, NULL); diff -r 7fbcdbad0baa -r 136a76e293b5 src/server/util/util.c --- a/src/server/util/util.c Sat Oct 17 21:17:34 2015 +0200 +++ b/src/server/util/util.c Sat Oct 17 22:24:38 2015 +0200 @@ -529,7 +529,7 @@ } -// new - code in parts from params.cpp +// new - code from params.cpp NSAPI_PUBLIC pblock* util_parse_param(pool_handle_t *pool, char *query) { pblock *pb = pblock_create_pool(pool, 32); if(!pb) { @@ -600,3 +600,307 @@ return newstring; } + + + +/* ---------------------------- util_mstr2num ----------------------------- */ + +static const int MSTR2NUM_HT_MASK = 0xf; + +static const struct { + unsigned ucmstr; // Uppercase 3 character month string in a machine word + int mnum; // 0-based month number for this month string +} MSTR2NUM_HT[MSTR2NUM_HT_MASK + 1] = { + { 'A' << 16 | 'P' << 8 | 'R', 3 }, + { 'S' << 16 | 'E' << 8 | 'P', 8 }, + { 'M' << 16 | 'A' << 8 | 'Y', 4 }, + { 0, -1 }, + { 'M' << 16 | 'A' << 8 | 'R', 2 }, + { 'F' << 16 | 'E' << 8 | 'B', 1 }, + { 0, -1 }, + { 'D' << 16 | 'E' << 8 | 'C', 11 }, + { 'O' << 16 | 'C' << 8 | 'T', 9 }, + { 'J' << 16 | 'U' << 8 | 'N', 5 }, + { 0, -1 }, + { 'A' << 16 | 'U' << 8 | 'G', 7 }, + { 'J' << 16 | 'A' << 8 | 'N', 0 }, + { 'J' << 16 | 'U' << 8 | 'L', 6 }, + { 0, -1 }, + { 'N' << 16 | 'O' << 8 | 'V', 10 } +}; + +static inline int _mstr2num(const char *s) +{ + const unsigned char *mstr = (const unsigned char *) s; + + /* + * We compute ucmstr (an uppercase 3 character month string stored in a + * machine word) and hash (a perfect hash based on the last 2 characters + * of the 3 character uppercase month string) from the input string s. + * Note that each character from the input string is masked by 0xdf; in + * ASCII, this has the effect of converting alphabetic characters to + * uppercase while 1. not changing any nonalphabetic characters into + * alphabetic characters and 2. leaving any nul characters unchanged. + * + * The hash value is used as an index into the MSTR2NUM_HT[] hash table. + * If the ucmstr at that index matches our computed ucmstr, the mnum at + * that index is the 0-based month number corresponding to the input + * string. + * + * Note that we never read past the end of the input string and always + * return -1 if the input string doesn't begin with a valid 3 character + * month string. + */ + + unsigned char ucmstr0 = mstr[0] & 0xdf; + unsigned ucmstr = ucmstr0 << 16; + if (ucmstr0 != '\0') { + unsigned char ucmstr1 = mstr[1] & 0xdf; + ucmstr |= ucmstr1 << 8; + if (ucmstr1 != '\0') { + unsigned char ucmstr2 = mstr[2] & 0xdf; + ucmstr |= ucmstr2; + + unsigned hash = (ucmstr1 >> 2) ^ (ucmstr2 << 1); + + int i = hash & MSTR2NUM_HT_MASK; + + if (MSTR2NUM_HT[i].ucmstr == ucmstr) + return MSTR2NUM_HT[i].mnum; + } + } + + return -1; +} + +NSAPI_PUBLIC int util_mstr2num(const char *s) +{ + return _mstr2num(s); +} + + +/* ------------------------- util_str_time_equal -------------------------- */ + +/* + * Function to compare if two time strings are equal + * + * Acceptable date formats: + * Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 + * Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 + * Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format + * + * Return 0 if equal, -1 if not equal. + */ + +static inline const char * _parse_day_month(const char *p, int *day, int *month) +{ + *day = 0; + + if (*p == ',') { + // Parse day and month: ", 06 Nov", ", 06-Nov" + p++; + if (*p == ' ') + p++; + while (*p >= '0' && *p <= '9') + *day = *day * 10 + (*p++ - '0'); + if (*p == ' ' || *p == '-') + p++; + *month = _mstr2num(p); + if (*month != -1) + p += 3; + } else { + // Parse month and day: " Nov 6" + if (*p == ' ') + p++; + *month = _mstr2num(p); + if (*month != -1) + p += 3; + while (*p == ' ') + p++; + while (*p >= '0' && *p <= '9') + *day = *day * 10 + (*p++ - '0'); + } + + return p; +} + +static inline void _parse_year_time(const char *p, int *year, const char ** time) +{ + int _year = 0; + + if (*p == '-') { + // Parse year and time: "-94 08:49:37" + p++; + while (*p >= '0' && *p <= '9') + _year = _year * 10 + (*p++ - '0'); + if (_year < 70) { + _year += 2000; + } else { + _year += 1900; + } + if (*p == ' ') + p++; + *time = p; + } else { + // Parse year and time or time and year + if (*p == ' ') + p++; + if (p[0] && p[1] && p[2] == ':') { + // Parse time and year: "08:49:37 1994" + *time = p; + p += 3; + while (*p && *p != ' ') + p++; + if (*p == ' ') + p++; + while (*p >= '0' && *p <= '9') + _year = _year * 10 + (*p++ - '0'); + } else { + // Parse year and time: "1994 08:49:37" + while (*p >= '0' && *p <= '9') + _year = _year * 10 + (*p++ - '0'); + if (*p == ' ') + p++; + *time = p; + } + } + + *year = _year; +} + +NSAPI_PUBLIC int util_str_time_equal(const char *t1, const char *t2) +{ + // Skip leading whitespace and day of week + while (isspace(*t1)) + t1++; + while (isalpha(*t1)) + t1++; + while (isspace(*t2)) + t2++; + while (isalpha(*t2)) + t2++; + + // Day and month: ", 06 Nov", ", 06-Nov", or " Nov 6" + int day1; + int month1; + t1 = _parse_day_month(t1, &day1, &month1); + int day2; + int month2; + t2 = _parse_day_month(t2, &day2, &month2); + if (day1 != day2) + return -1; + if (month1 != month2) + return -1; + + // Year and time: " 1994 08:49:37", "-94 08:49:37", or " 08:49:37 1994" + int year1; + const char *time1; + _parse_year_time(t1, &year1, &time1); + int year2; + const char *time2; + _parse_year_time(t2, &year2, &time2); + if (year1 != year2) + return -1; + while (*time1 && *time1 != ' ' && *time1 == *time2) { + time1++; + time2++; + } + if (*time2 && *time2 != ' ') + return -1; + + return 0; +} + + +/* --------------------------- util_later_than ---------------------------- */ + +static int _time_compare(const struct tm *lms, const char *ims) +{ + while (isspace(*ims)) + ims++; + while (isalpha(*ims)) + ims++; + + int day; + int month; + ims = _parse_day_month(ims, &day, &month); + if (month == -1) + return 1; + + int year; + const char *time; + _parse_year_time(ims, &year, &time); + + int rv; + + rv = (lms->tm_year + 1900) - year; + if (rv) + return rv; + + rv = lms->tm_mon - month; + if (rv) + return rv; + + rv = lms->tm_mday - day; + if (rv) + return rv; + + const char *p = time; + + int hour = 0; + while (*p >= '0' && *p <= '9') + hour = hour * 10 + (*p++ - '0'); + if (*p == ':') + p++; + + rv = lms->tm_hour - hour; + if (rv) + return rv; + + int minutes = 0; + while (*p >= '0' && *p <= '9') + minutes = minutes * 10 + (*p++ - '0'); + if (*p == ':') + p++; + + rv = lms->tm_min - minutes; + if (rv) + return rv; + + int seconds = 0; + while (*p >= '0' && *p <= '9') + seconds = seconds * 10 + (*p++ - '0'); + if (*p == ':') + p++; + + rv = lms->tm_sec - seconds; + if (rv) + return rv; + + return 0; +} + +NSAPI_PUBLIC int util_later_than(const struct tm *lms, const char *ims) +{ + /* + * Returns 0 if lms later than ims + * 0 if ims is malformed + * 1 if ims later than lms + * 1 if equal + */ + + return _time_compare(lms, ims) <= 0; +} + +NSAPI_PUBLIC int util_time_equal(const struct tm *lms, const char *ims) +{ + return _time_compare(lms, ims) == 0; +} + + +NSAPI_PUBLIC struct tm * +util_gmtime(const time_t *clock, struct tm *res) +{ + return gmtime_r(clock, res); +} +