added etag and conditional request implementation from Open Web Server

Sat, 17 Oct 2015 22:24:38 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 17 Oct 2015 22:24:38 +0200
changeset 102
136a76e293b5
parent 101
7fbcdbad0baa
child 103
d3b514e2ddbd

added etag and conditional request implementation from Open Web Server

src/server/daemon/http.c file | annotate | diff | comparison | revisions
src/server/daemon/http.h file | annotate | diff | comparison | revisions
src/server/daemon/httprequest.c file | annotate | diff | comparison | revisions
src/server/daemon/httprequest.h file | annotate | diff | comparison | revisions
src/server/daemon/objs.mk file | annotate | diff | comparison | revisions
src/server/daemon/protocol.h file | annotate | diff | comparison | revisions
src/server/public/nsapi.h file | annotate | diff | comparison | revisions
src/server/safs/service.c file | annotate | diff | comparison | revisions
src/server/util/util.c file | annotate | diff | comparison | revisions
--- /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 <stdio.h>
+#include <stdlib.h>
+
+#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);
+}
+
+
--- /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 */
+
--- 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
--- 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 {
--- 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
--- 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 */
 
--- 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);
--- 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);
--- 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);
+}
+

mercurial