libidav/utils.c

changeset 1
b5bb7b3cd597
child 2
fbdfaacc4182
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/utils.c	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,1219 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 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.
+ */
+
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <cx/string.h>
+#include <cx/buffer.h>
+#include <cx/utils.h>
+#include <cx/printf.h>
+#include <libxml/tree.h>
+#include <curl/curl.h>
+
+#ifdef _WIN32
+#include <conio.h>
+#define getpasswordchar() getch()
+#define IS_PATH_SEPARATOR(c) (c == '/' || c == '\\')
+#define PATH_SEPARATOR '\\'
+#else
+#include <unistd.h>
+#include <spawn.h>
+#include <sys/wait.h>
+#include <termios.h>
+#define getpasswordchar() getchar()
+#define IS_PATH_SEPARATOR(c) (c == '/')
+#define PATH_SEPARATOR '/'
+#endif
+
+#include "webdav.h"
+#include "utils.h"
+#include "crypto.h"
+#include "session.h"
+
+/*
+#include <openssl/hmac.h>
+#include <openssl/evp.h>
+#include <openssl/bio.h>
+#include <openssl/buffer.h>
+#include <openssl/rand.h>
+*/
+
+static size_t extractval(cxstring str, char *result, char delim) {
+    size_t n = 0;
+    for(size_t i = 0; i < str.length ; i++) {
+        if(isdigit(str.ptr[i])) {
+            result[n++] = str.ptr[i];
+        } else if(str.ptr[i] != delim) {
+            return 0;
+        }
+    }
+    result[n] = '\0';
+    return n;
+}
+
+static time_t parse_iso8601(char *iso8601str) {
+
+    // safety
+    if(!iso8601str) {
+        return 0;
+    }
+    
+    // local vars
+    struct tm tparts;
+    memset(&tparts, 0, sizeof(struct tm));
+    long val;
+    char conv[16];
+    
+    // work on the trimmed string
+    cxstring date = cx_strtrim(cx_str(iso8601str));
+
+    cxstring time = cx_strchr(date, 'T');
+    if(time.length == 0) {
+        return 0;
+    }
+    date.length = time.ptr - date.ptr;
+    time.ptr++; time.length--;
+    
+    cxstring tzinfo;
+    if((tzinfo = cx_strchr(time, 'Z')).length > 0 ||
+        (tzinfo = cx_strchr(time, '+')).length > 0 ||
+        (tzinfo = cx_strchr(time, '-')).length > 0) {
+        
+        time.length = tzinfo.ptr - time.ptr;
+    }
+
+    // parse date
+    if((date.length != 8 && date.length != 10)
+            || extractval(date, conv , '-') != 8) {
+        return 0;
+    }
+    val = atol(conv);
+    if(val < 19000000L) {
+        return 0;
+    }
+    tparts.tm_mday = val % 100;
+    tparts.tm_mon = (val % 10000) / 100 - 1;
+    tparts.tm_year = val / 10000 - 1900;
+    
+    // parse time and skip possible fractional seconds
+    cxstring frac;
+    if((frac = cx_strchr(time, '.')).length > 0 ||
+        (frac = cx_strchr(time, ',')).length > 0) {
+        time.length = frac.ptr - time.ptr;
+    }
+    if((time.length != 6 && time.length != 8)
+            || extractval(time, conv , ':') != 6) {
+        return 0;
+    }
+    val = atol(conv);
+    tparts.tm_sec = val % 100;
+    tparts.tm_min = (val % 10000) / 100;
+    tparts.tm_hour = val / 10000;
+
+
+    // parse time zone (if any)
+    if(tzinfo.length == 0) {
+        // local time
+        tparts.tm_isdst = -1;
+        return mktime(&tparts);
+    } else if(!cx_strcmp(tzinfo, cx_str("Z"))) {
+#if defined(__FreeBSD__)
+        return timegm(&tparts);
+#elif defined(_WIN32)
+        return _mkgmtime(&tparts);
+#else
+        return mktime(&tparts) - timezone;
+#endif
+    } else if(tzinfo.ptr[0] == '+' || tzinfo.ptr[0] == '-') {
+        int sign = (tzinfo.ptr[0] == '+') ? -1 : 1;
+
+        if(tzinfo.length > 6) {
+            return 0;
+        } else {
+            tzinfo.ptr++; tzinfo.length--;
+            extractval(tzinfo, conv, ':');
+            val = atol(conv);
+            val = 60 * (val / 100) + (val % 100);
+#if defined(__FreeBSD__)
+            return timegm(&tparts) + (time_t) (60 * val * sign);
+#elif defined(_WIN32)
+            return _mkgmtime(&tparts) + (time_t)(60 * val * sign);
+#else
+            return mktime(&tparts) - timezone + (time_t) (60 * val * sign);            
+#endif
+        }
+    } else {
+        return 0;
+    }
+}
+
+
+time_t util_parse_creationdate(char *str) {
+    // parse a ISO-8601 date (rfc-3339)
+    // example: 2012-11-29T21:35:35Z
+    if(!str) {
+        return 0;
+    }
+    
+    return parse_iso8601(str);
+}
+
+time_t util_parse_lastmodified(char *str) {
+    // parse a rfc-1123 date
+    // example: Thu, 29 Nov 2012 21:35:35 GMT
+    if(!str) {
+        return 0;
+    } else {
+        time_t result = curl_getdate(str, NULL);
+        if(result == -1) {
+            // fall back to the ISO-8601 format (e.g. Microsoft Sharepoint
+            // illegally uses this format for lastmodified, but also some
+            // users might want to give an ISO-8601 date)
+            return util_parse_creationdate(str);
+        } else {
+            return result;
+        }
+    }
+}
+
+int util_getboolean(const char *v) {
+    if(v[0] == 'T' || v[0] == 't') {
+        return 1;
+    }
+    return 0;
+}
+
+int util_strtouint(const char *str, uint64_t *value) {
+    char *end;
+    errno = 0;
+    uint64_t val = strtoull(str, &end, 0);
+    if(errno == 0) {
+        *value = val;
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+int util_strtoint(const char *str, int64_t *value) {
+    char *end;
+    errno = 0;
+    int64_t val = strtoll(str, &end, 0);
+    if(errno == 0) {
+        *value = val;
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+int util_szstrtouint(const char *str, uint64_t *value) {
+    char *end;
+    errno = 0;
+    size_t len = strlen(str);
+    uint64_t val = strtoull(str, &end, 0);
+    if(end == str+len) {
+        *value = val;
+        return 1;
+    } else if(end == str+len-1) {
+        uint64_t mul = 1;
+        switch(end[0]) {
+            case 'k':
+            case 'K': mul = 1024; break;
+            case 'm':
+            case 'M': mul = 1024*1024; break;
+            case 'g':
+            case 'G': mul = 1024*1024*1024; break;
+            default: return 0;
+        }
+        
+        uint64_t result = 0;
+        if(util_uint_mul(val, mul, &result)) {
+            return 0;
+        }
+        *value = result;
+        return 1;
+    }
+    return 0;
+}
+
+int util_uint_mul(uint64_t a, uint64_t b, uint64_t *result) {
+    if(a == 0 || b == 0) {
+        *result = 0;
+        return 0;
+    }
+    uint64_t r = a * b;
+    if(r / b == a) {
+        *result = r;
+        return 0;
+    } else {
+        *result = 0;
+        return 1;
+    }
+}
+
+char* util_url_base_s(cxstring url) {
+    size_t i = 0;
+    if(url.length > 0) {
+        int slmax;
+        if(cx_strprefix(url, cx_str("http://"))) {
+            slmax = 3;
+        } else if(cx_strprefix(url, cx_str("https://"))) {
+            slmax = 3;
+        } else {
+            slmax = 1;
+        }
+        int slashcount = 0;
+        for(i=0;i<url.length;i++) {
+            if(url.ptr[i] == '/') {
+                slashcount++;
+                if(slashcount == slmax) {
+                    i++;
+                    break;
+                }
+            }
+        }
+    }
+    cxstring server = cx_strsubsl(url, 0, i);
+    return cx_strdup(server).ptr;
+}
+
+char* util_url_base(char *url) {
+    return util_url_base_s(cx_str(url));
+}
+
+#ifdef _WIN32
+#define strncasecmp _strnicmp
+#endif
+
+const char* util_url_path(const char *url) {
+    const char *path = NULL;
+    size_t len = strlen(url);
+    int slashcount = 0;
+    int slmax;
+    if(len > 7 && !strncasecmp(url, "http://", 7)) {
+        slmax = 3;
+    } else if(len > 8 && !strncasecmp(url, "https://", 8)) {
+        slmax = 3;
+    } else {
+        slmax = 1;
+    }
+    char c;
+    for(int i=0;i<len;i++) {
+        c = url[i];
+        if(c == '/') {
+            slashcount++;
+            if(slashcount == slmax) {
+                path = url + i;
+                break;
+            }
+        }
+    } 
+    if(!path) {
+        path = url + len; // empty string
+    }
+    return path;
+}
+
+char* util_url_decode(DavSession *sn, const char *url) {
+    char *unesc = curl_easy_unescape(sn->handle, url, strlen(url), NULL);
+    char *ret = strdup(unesc);
+    curl_free(unesc);
+    return ret;
+}
+
+static size_t util_header_callback(char *buffer, size_t size,
+        size_t nitems, void *data) {
+    
+    cxstring sbuffer = cx_strn(buffer, size*nitems);
+    
+    CxMap *map = (CxMap*) data;
+    
+    // if we get a status line, clear the map and exit
+    if(cx_strprefix(sbuffer, cx_str("HTTP/"))) {
+        // TODO: use new map destructor   ucx_map_free_content(map, free);
+        cxMapClear(map);
+        return size*nitems;
+    }
+    
+    // if we get the terminating CRLF, just exit
+    if(!cx_strcmp(sbuffer, cx_str("\r\n"))) {
+        return 2;
+    }
+    
+    cxstring key = sbuffer;
+    cxstring value = cx_strchr(sbuffer, ':');
+    
+    if(value.length == 0) {
+        return 0; // invalid header line
+    }
+    
+    key.length = value.ptr - key.ptr;
+    value.ptr++; value.length--;
+    
+    cxmutstr key_cp = cx_strdup(cx_strtrim(key));
+    cx_strlower(key_cp);
+    cxmutstr value_cp = cx_strdup(cx_strtrim(value));
+        
+    cxMapPut(map, cx_hash_key(key_cp.ptr, key_cp.length), value_cp.ptr);
+    
+    free(key_cp.ptr);
+    
+    return sbuffer.length;
+}
+
+int util_path_isrelated(const char *path1, const char *path2) {
+    cxstring p1 = cx_str(path1);
+    cxstring p2 = cx_str(path2);
+    
+    if(IS_PATH_SEPARATOR(p1.ptr[p1.length-1])) {
+        p1.length--;
+    }
+    if(IS_PATH_SEPARATOR(p2.ptr[p2.length-1])) {
+        p2.length--;
+    }
+    
+    if(p2.length < p1.length) {
+        return 0;
+    }
+    
+    if(!cx_strcmp(p1, p2)) {
+        return 1;
+    }
+    
+    if(cx_strprefix(p2, p1)) {
+        if(IS_PATH_SEPARATOR(p2.ptr[p1.length])) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+#ifdef _WIN32
+int util_path_isabsolut(const char *path) {
+    if(strlen(path) < 3) {
+        return 0;
+    }
+    
+    // check if first char is A-Z or a-z
+    char c = path[0];
+    if(!((c >= 65 && c <= 90) || (c >= 97 && c <= 122))) {
+        return 0;
+    }
+    
+    if(path[1] == ':' && path[2] == '\\') {
+        return 1;
+    }
+    return 0;
+}
+#else
+int util_path_isabsolut(const char *path) {
+    return path[0] == '/';
+}
+#endif
+
+char* util_path_normalize(const char *path) {
+    size_t len = strlen(path);
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, len+1, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    if(path[0] == '/') {
+        cxBufferPut(&buf, '/');
+    }
+    
+    int add_separator = 0;
+    int seg_start = 0;
+    for(int i=0;i<=len;i++) {
+        char c = path[i];
+        if(IS_PATH_SEPARATOR(c) || c == '\0') {
+            const char *seg_ptr = path+seg_start;
+            int seg_len = i - seg_start;
+            if(IS_PATH_SEPARATOR(seg_ptr[0])) {
+                seg_ptr++;
+                seg_len--;
+            }
+            
+            if(seg_len > 0) {
+                cxstring seg = cx_strn(seg_ptr, seg_len);
+                if(!cx_strcmp(seg, CX_STR(".."))) {
+                    for(int j=buf.pos;j>=0;j--) {
+                        char t = j < buf.pos ? buf.space[j] : 0;
+                        if(IS_PATH_SEPARATOR(t) || j == 0) {
+                            buf.pos = j;
+                            buf.size = j;
+                            buf.space[j] = 0;
+                            add_separator = IS_PATH_SEPARATOR(t) ? 1 : 0;
+                            break;
+                        }
+                    }
+                } else if(!cx_strcmp(seg, CX_STR("."))) {
+                    // ignore
+                } else {
+                    if(add_separator) {
+                        cxBufferPut(&buf, PATH_SEPARATOR);
+                    }
+                    cxBufferWrite(seg_ptr, 1, seg_len, &buf);
+                    add_separator = 1;
+                }
+            }
+            
+            seg_start = i;
+        }
+    }
+    
+    cxBufferPut(&buf, 0);
+    
+    return buf.space;
+}
+
+static char* create_relative_path(const char *abspath, const char *base) {
+    size_t path_len = strlen(abspath);
+    size_t base_len = strlen(base);
+    
+    if(IS_PATH_SEPARATOR(abspath[path_len-1])) {
+        path_len--;
+    }
+    if(IS_PATH_SEPARATOR(base[base_len-1])) {
+        base_len--;
+    }
+    // get base parent
+    for(int i=base_len-1;i>=0;i--) {
+        if(IS_PATH_SEPARATOR(base[i])) {
+            base_len = i+1;
+            break;
+        }
+    }
+    
+    size_t max = path_len > base_len ? base_len : path_len;
+    
+    // get prefix of abspath and base
+    // this dir is the root of the link
+    size_t i;
+    size_t last_dir = 0;
+    for(i=0;i<max;i++) {
+        char c = abspath[i];
+        if(c != base[i]) {
+            break;
+        } else if(IS_PATH_SEPARATOR(c)) {
+            last_dir = i;
+        }
+    }
+    
+    char *ret = NULL;
+    CxBuffer out;
+    if(last_dir+1 < base_len) {
+        // base is deeper than the link root, we have to go backwards
+        int dircount = 0;
+        for(int i=last_dir+1;i<base_len;i++) {
+            if(IS_PATH_SEPARATOR(base[i])) {
+                dircount++;
+            }
+        }
+        
+        cxBufferInit(&out, NULL, dircount*3+path_len-last_dir, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+        
+        for(int i=0;i<dircount;i++) {
+            cxBufferPutString(&out, "../");
+        }
+    } else {
+        cxBufferInit(&out, NULL, path_len - last_dir, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    }
+    
+    cxBufferPutString(&out, abspath + last_dir + 1);
+    cxBufferPut(&out, 0);
+    
+    return out.space;
+}
+
+#ifdef _WIN32
+char* util_create_relative_path(const char *abspath, const char *base) {
+    char *abspath_converted = strdup(abspath);
+    char *base_converted = strdup(base);
+    size_t abs_len = strlen(abspath_converted);
+    size_t base_len = strlen(base_converted);
+    
+    for(int i=0;i<abs_len;i++) {
+        if(abspath_converted[i] == '\\') {
+            abspath_converted[i] = '/';
+        }
+    }
+    for(int i=0;i<base_len;i++) {
+        if(base_converted[i] == '\\') {
+            base_converted[i] = '/';
+        }
+    }
+    
+    char *ret = create_relative_path(abspath_converted, base_converted);
+    free(abspath_converted);
+    free(base_converted);
+    return ret;
+}
+#else
+char* util_create_relative_path(const char *abspath, const char *base) {
+    return create_relative_path(abspath, base);
+}
+#endif
+
+
+void util_capture_header(CURL *handle, CxMap* map) {
+    if(map) {
+        curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, util_header_callback);
+        curl_easy_setopt(handle, CURLOPT_HEADERDATA, map);
+    } else {
+        curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, NULL);
+        curl_easy_setopt(handle, CURLOPT_HEADERDATA, NULL);
+    }
+}
+
+const char* util_resource_name(const char *url) {
+    cxstring urlstr = cx_str(url);
+    if(urlstr.ptr[urlstr.length-1] == '/') {
+        urlstr.length--;
+    }
+    cxstring resname = cx_strrchr(urlstr, '/');
+    if(resname.length > 1) {
+        return resname.ptr+1;
+    } else {
+        return url;
+    }
+}
+
+int util_mkdir(char *path, mode_t mode) {
+#ifdef _WIN32
+    return mkdir(path);
+#else
+    return mkdir(path, mode);
+#endif
+}
+
+char* util_concat_path(const char *url_base, const char *p) {
+    cxstring base = cx_str((char*)url_base);
+    cxstring path;
+    if(p) {
+        path = cx_str((char*)p);
+    } else {
+        path = CX_STR("");
+    }
+    
+    int add_separator = 0;
+    if(base.length != 0 && base.ptr[base.length-1] == '/') {
+        if(path.ptr[0] == '/') {
+            base.length--;
+        }
+    } else {
+        if(path.length == 0 || path.ptr[0] != '/') {
+            add_separator = 1;
+        }
+    }
+    
+    cxmutstr url;
+    if(add_separator) {
+        url = cx_strcat(3, base, CX_STR("/"), path);
+    } else {
+        url = cx_strcat(2, base, path);
+    }
+    
+    return url.ptr;
+}
+
+char* util_get_url(DavSession *sn, const char *href) {
+    cxstring base = cx_str(sn->base_url);
+    cxstring href_str = cx_str(href);
+    
+    const char *base_path = util_url_path(sn->base_url);
+    base.length -= strlen(base_path);
+    
+    cxmutstr url = cx_strcat(2, base, href_str);
+    return url.ptr;
+}
+
+void util_set_url(DavSession *sn, const char *href) {
+    char *url = util_get_url(sn, href);
+    curl_easy_setopt(sn->handle, CURLOPT_URL, url);
+    free(url);
+}
+
+char* util_path_to_url(DavSession *sn, const char *path) {
+    CxBuffer url;
+    cxBufferInit(&url, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    // add base url
+    cxBufferWrite(sn->base_url, 1, strlen(sn->base_url), &url);
+    // remove trailing slash
+    cxBufferSeek(&url, -1, SEEK_CUR);
+    
+    cxstring p = cx_str(path);
+    
+    CxStrtokCtx tkctx = cx_strtok(p, CX_STR("/"), INT_MAX);
+    cxstring node;
+    while(cx_strtok_next(&tkctx, &node)) {
+        if(node.length > 0) {
+            char *esc = curl_easy_escape(sn->handle, node.ptr, node.length);
+            cxBufferPut(&url, '/');
+            cxBufferWrite(esc, 1, strlen(esc), &url);
+            curl_free(esc);
+        }
+    }
+    
+    if(path[p.length-1] == '/') {
+        cxBufferPut(&url, '/');
+    }
+    cxBufferPut(&url, 0);
+    
+    return url.space;
+}
+
+char* util_parent_path(const char *path) {
+    const char *name = util_resource_name(path);
+    size_t namelen = strlen(name);
+    size_t pathlen = strlen(path);
+    size_t parentlen = pathlen - namelen;
+    char *parent = malloc(parentlen + 1);
+    memcpy(parent, path, parentlen);
+    parent[parentlen] = '\0';
+    return parent;
+}
+
+char* util_size_str(DavBool iscollection, uint64_t contentlength) {
+    char *str = malloc(16);
+    uint64_t size = contentlength;
+    
+    if(iscollection) {
+        str[0] = '\0'; // currently no information for collections
+    } else if(size < 0x400) {
+        snprintf(str, 16, "%" PRIu64 " bytes", size);
+    } else if(size < 0x100000) {
+        float s = (float)size/0x400;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(size < 0x2800 && diff != 0) {
+            // size < 10 KiB
+            snprintf(str, 16, "%.1f KiB", s);
+        } else {
+            snprintf(str, 16, "%.0f KiB", s);
+        }
+    } else if(size < 0x40000000) {
+        float s = (float)size/0x100000;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(size < 0xa00000 && diff != 0) {
+            // size < 10 MiB
+            snprintf(str, 16, "%.1f MiB", s);
+        } else {
+            size /= 0x100000;
+            snprintf(str, 16, "%.0f MiB", s);
+        }
+    } else if(size < 0x1000000000ULL) {
+        float s = (float)size/0x40000000;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(size < 0x280000000 && diff != 0) {
+            // size < 10 GiB
+            snprintf(str, 16, "%.1f GiB", s);
+        } else {
+            size /= 0x40000000;
+            snprintf(str, 16, "%.0f GiB", s);
+        }
+    } else {
+        size /= 1024;
+        float s = (float)size/0x40000000;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(size < 0x280000000 && diff != 0) {
+            // size < 10 TiB
+            snprintf(str, 16, "%.1f TiB", s);
+        } else {
+            size /= 0x40000000;
+            snprintf(str, 16, "%.0f TiB", s);
+        }
+    }
+    return str;
+}
+
+char* util_date_str(time_t tm) {
+    struct tm t;
+    struct tm n;
+    time_t now = time(NULL);
+#ifdef _WIN32
+    memcpy(&t, localtime(&tm), sizeof(struct tm));
+    memcpy(&n, localtime(&now), sizeof(struct tm));
+#else
+    localtime_r(&tm, &t);
+    localtime_r(&now, &n);
+#endif /* _WIN32 */
+    char *str = malloc(16);
+    if(t.tm_year == n.tm_year) {
+        strftime(str, 16, "%b %d %H:%M", &t);
+    } else {
+        strftime(str, 16, "%b %d  %Y", &t);
+    }
+    return str;
+}
+
+
+char* util_xml_get_text(const xmlNode *elm) {
+    xmlNode *node = elm->children;
+    while(node) {
+        if(node->type == XML_TEXT_NODE) {
+            return (char*)node->content;
+        }
+        node = node->next;
+    }
+    return NULL;
+}
+
+
+char* util_base64decode(const char *in) {
+    int len = 0;
+    return util_base64decode_len(in, &len);
+}
+
+#define WHITESPACE 64
+#define EQUALS     65
+#define INVALID    66
+static char b64dectable[] = {
+    66,66,66,66,66,66,66,66,66,66,64,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,62,66,66,66,63,52,53,
+    54,55,56,57,58,59,60,61,66,66,66,65,66,66,66, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+    10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,66,66,66,66,66,66,26,27,28,
+    29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+    66,66,66,66,66,66
+};
+char* util_base64decode_len(const char* in, int *outlen) {
+    /* code is mostly from wikibooks */
+    
+    if(!in) {
+        *outlen = 0;
+        return NULL;
+    }
+    
+    size_t inlen = strlen(in);
+    size_t bufsize = (inlen*3) / 4;
+    char *outbuf = malloc(bufsize+1);
+    *outlen = -1;
+    
+    unsigned char *out = (unsigned char*)outbuf;
+    
+    const char *end = in + inlen;
+    char iter = 0;
+    uint32_t buf = 0;
+    size_t len = 0;
+    
+    while (in < end) {
+        unsigned char c = b64dectable[*in++];
+        
+        switch (c) {
+            case WHITESPACE: continue; /* skip whitespace */
+            case INVALID: {
+                  /* invalid input */
+                outbuf[0] = 0;
+                return outbuf;
+            }
+            case EQUALS: {
+                /* pad character, end of data */
+                in = end;
+                continue;
+            }
+            default: {
+                buf = buf << 6 | c;
+                iter++; // increment the number of iteration
+                /* If the buffer is full, split it into bytes */
+                if (iter == 4) {
+                    if ((len += 3) > bufsize) {
+                        /* buffer overflow */
+                        outbuf[0] = 0;
+                        return outbuf;
+                    }
+                    *(out++) = (buf >> 16) & 255;
+                    *(out++) = (buf >> 8) & 255;
+                    *(out++) = buf & 255;
+                    buf = 0; iter = 0;
+
+                }
+            }
+        }
+    }
+   
+    if (iter == 3) {
+        if ((len += 2) > bufsize) {
+            /* buffer overflow */
+            outbuf[0] = 0;
+            return outbuf;
+        }
+        *(out++) = (buf >> 10) & 255;
+        *(out++) = (buf >> 2) & 255;
+    }
+    else if (iter == 2) {
+        if (++len > bufsize) {
+            /* buffer overflow */
+            outbuf[0] = 0;
+            return outbuf;
+        }
+        *(out++) = (buf >> 4) & 255;
+    }
+
+    *outlen = len; /* modify to reflect the actual output size */
+    outbuf[len] = 0;
+    return outbuf;
+}
+
+
+static char* b64enctable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+char* util_base64encode(const char *in, size_t len) {
+    // calculate length of base64 output and create buffer
+    size_t outlen = 4 * ((len + 2) / 3);
+    int pad = len % 3;
+    
+    char *out = malloc(outlen + 1);
+    out[outlen] = 0;
+    size_t pos = 0;
+    
+    // encode blocks of 3 bytes
+    size_t i;
+    size_t blockend = len - pad;
+    for(i=0;i<blockend;i++) {
+        unsigned char b1 = in[i++];
+        unsigned char b2 = in[i++];
+        unsigned char b3 = in[i];
+        uint32_t inb = b1 << 16 | (b2 << 8) | b3;
+        out[pos++] = b64enctable[(inb >> 18) & 63];
+        out[pos++] = b64enctable[(inb >> 12) & 63];
+        out[pos++] = b64enctable[(inb >> 6) & 63];
+        out[pos++] = b64enctable[(inb) & 63];
+    }
+    
+    // encode last bytes
+    if(pad > 0) {
+        char p[3] = {0, 0, 0};
+        for(int j=0;i<len;i++) {
+            p[j++] = in[i];
+        }
+        unsigned char b1 = p[0];
+        unsigned char b2 = p[1];
+        unsigned char b3 = p[2];
+        uint32_t inb = (b1 << 16) | (b2 << 8) | b3;
+        out[pos++] = b64enctable[(inb >> 18) & 63];
+        out[pos++] = b64enctable[(inb >> 12) & 63];
+        out[pos++] = b64enctable[(inb >> 6) & 63];
+        out[pos++] = b64enctable[(inb) & 63];
+        for(int k=outlen-1;k>=outlen-(3-pad);k--) {
+            out[k] = '=';
+        }
+    }
+    
+    return out;
+}
+
+char* util_encrypt_str(DavSession *sn, const char *str, const char *key) {
+    DavKey *k = dav_context_get_key(sn->context, key);
+    if(!k) {
+        sn->error = DAV_ERROR;
+        cxmutstr err = cx_asprintf("Key %s not found", key);
+        dav_session_set_errstr(sn, err.ptr);
+        free(err.ptr);
+        return NULL;
+    }
+    
+    return util_encrypt_str_k(sn, str, k);
+}
+
+char* util_encrypt_str_k(DavSession *sn, const char *str, DavKey *key) {
+    char *enc_str = aes_encrypt(str, strlen(str), key);
+    char *ret_str = dav_session_strdup(sn, enc_str);
+    free(enc_str);
+    return ret_str;
+}
+
+char* util_decrypt_str(DavSession *sn, const char *str, const char *key) {
+    DavKey *k = dav_context_get_key(sn->context, key);
+    if(!k) {
+        sn->error = DAV_ERROR;
+        cxmutstr err = cx_asprintf("Key %s not found", key);
+        dav_session_set_errstr(sn, err.ptr);
+        free(err.ptr);
+        return NULL;
+    }
+    
+    return util_decrypt_str_k(sn, str, k);
+}
+
+char* util_decrypt_str_k(DavSession *sn, const char *str, DavKey *key) {
+    size_t len = 0;
+    char *dec_str = aes_decrypt(str, &len, key);
+    char *ret_str = dav_session_strdup(sn, dec_str);
+    free(dec_str);
+    return ret_str;
+}
+
+char* util_random_str() {
+    unsigned char *str = malloc(25);
+    str[24] = '\0';
+    
+    cxstring t = CX_STR(
+            "01234567890"
+            "abcdefghijklmnopqrstuvwxyz"
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+    const unsigned char *table = (const unsigned char*)t.ptr;
+    
+#ifdef DAV_USE_OPENSSL
+    RAND_bytes(str, 24);
+#else
+    dav_rand_bytes(str, 24);
+#endif
+    for(int i=0;i<24;i++) {
+        int c = str[i] % t.length;
+        str[i] = table[c];
+    }
+    
+    return (char*)str;
+}
+
+/*
+ * gets a substring from 0 to the appearance of the token
+ * tokens are separated by space
+ * sets sub to the substring and returns the remaining string
+ */
+// TODO: remove if it isn't used
+/*
+sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub) {  
+    int i;
+    int token_start = -1;
+    int token_end = -1;
+    for(i=0;i<=str.length;i++) {
+        int c;
+        if(i == str.length) {
+            c = ' ';
+        } else {
+            c = str.ptr[i];
+        }
+        if(c < 33) {
+            if(token_start != -1) {
+                token_end = i;
+                size_t len = token_end - token_start;
+                sstr_t tk = sstrsubsl(str, token_start, len);
+                //printf("token: {%.*s}\n", token.length, token.ptr);
+                if(!sstrcmp(tk, token)) {
+                    *sub = sstrtrim(sstrsubsl(str, 0, token_start));
+                    break;
+                }
+                token_start = -1;
+                token_end = -1;
+            }
+        } else {
+            if(token_start == -1) {
+                token_start = i;
+            }
+        }
+    }
+    
+    if(i < str.length) {
+        return sstrtrim(sstrsubs(str, i));
+    } else {
+        str.ptr = NULL;
+        str.length = 0;
+        return str;
+    }
+}
+*/
+
+cxmutstr util_readline(FILE *stream) {
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    
+    int c;
+    while((c = fgetc(stream)) != EOF) {
+        if(c == '\n') {
+            break;
+        }
+        cxBufferPut(&buf, c);
+    }
+    
+    cxmutstr str = cx_strdup(cx_strtrim(cx_strn(buf.space, buf.size)));
+    cxBufferDestroy(&buf);
+    return str;
+}
+
+char* util_password_input(char *prompt) {
+    fprintf(stderr, "%s", prompt);
+    fflush(stderr);
+    
+#ifndef _WIN32
+    // hide terminal input
+    struct termios oflags, nflags;
+    if(isatty(fileno(stdin))) {
+        tcgetattr(fileno(stdin), &oflags);
+        nflags = oflags;
+        nflags.c_lflag &= ~ECHO;
+        nflags.c_lflag |= ECHONL;
+        if (tcsetattr(fileno(stdin), TCSANOW, &nflags) != 0) {
+            perror("tcsetattr");
+        }
+    }
+    
+#endif
+    
+    // read password input
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    int c = 0;
+    while((c = getpasswordchar()) != EOF) {
+        if(c == '\n' || c == '\r') {
+            break;
+        }
+        cxBufferPut(&buf, c);
+    }
+    cxBufferPut(&buf, 0);
+    fflush(stdin);
+    
+#ifndef _WIN32
+    // restore terminal settings
+    if (isatty(fileno(stdin)) && tcsetattr(fileno(stdin), TCSANOW, &oflags) != 0) {
+        perror("tcsetattr");
+    }
+#endif
+    
+    return buf.space;
+}
+
+int util_exec_command(char *command, CxBuffer *outbuf) {
+#ifdef _WIN32
+    fprintf(stderr, "util_exec_command unsupported\n");
+    return 1;
+#else
+    
+    int pout[2];
+    if(pipe(pout)) {
+        perror("pipe");
+        return 1;
+    }
+    
+    int ret = 0;
+    
+    // close stdin and stderr, use pipe for stdout
+    posix_spawn_file_actions_t actions;
+    posix_spawn_file_actions_init(&actions);
+    posix_spawn_file_actions_addclose(&actions, 0);
+    posix_spawn_file_actions_adddup2(&actions, pout[1], 1);
+    posix_spawn_file_actions_addclose(&actions, 2);
+    
+    char *args[4];
+    args[0] = "sh";
+    args[1] = "-c";
+    args[2] = command;
+    args[3] = NULL;
+    
+    pid_t pid; // child pid
+    ret = posix_spawn(&pid, "/bin/sh", &actions, NULL, args, NULL);
+    
+    close(pout[1]);
+    
+    if(!ret) {
+        ssize_t r;
+        char buf[1024];
+        while((r = read(pout[0], buf, 1024)) > 0) {
+            cxBufferWrite(buf, 1, r, outbuf);
+        }
+    }
+    
+    // wait for child process
+    ret = 1;
+    waitpid(pid, &ret, 0);
+    
+    posix_spawn_file_actions_destroy(&actions);
+    close(pout[0]);
+    
+    return ret;
+#endif
+}
+
+char* util_hexstr(const unsigned char *data, size_t len) {
+    size_t buflen = 2*len + 4;
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, buflen + 1, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    for(int i=0;i<len;i++) {
+        cx_bprintf(&buf, "%02x", data[i]);
+    }
+    cxBufferPut(&buf, 0);
+    return buf.space;
+}
+
+void util_remove_trailing_pathseparator(char *path) {
+    size_t len = strlen(path);
+    if(len < 2) {
+        return;
+    }
+    
+    if(path[len-1] == '/') {
+        path[len-1] = '\0';
+    }
+}
+
+char* util_file_hash(const char *path) {
+    FILE *in = fopen(path, "r");
+    if(!in) {
+        return NULL;
+    }
+    
+    DAV_SHA_CTX *sha = dav_hash_init();
+    char *buf = malloc(16384);
+    
+    size_t r;
+    while((r = fread(buf, 1, 16384, in)) > 0) {
+        dav_hash_update(sha, buf, r);
+    }
+    
+    unsigned char hash[DAV_SHA256_DIGEST_LENGTH];
+    dav_hash_final(sha, hash);
+    free(buf);
+    fclose(in);
+    
+    return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH);    
+}
+

mercurial