libidav/utils.c

Sun, 17 Dec 2023 15:33:50 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 17 Dec 2023 15:33:50 +0100
changeset 800
30d484806c2b
parent 795
05647e862a17
child 808
f21be698def9
permissions
-rw-r--r--

fix faulty string to int conversion utilities

Probably it was expected that errno is set to EINVAL when illegal characters are encountered. But this is not standard and does not happen on every system, allowing illegal strings to be parsed as valid integers.

/*
 * 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) {
    if (str == NULL || *str == '\0') return 0;
    char *end;
    errno = 0;
    uint64_t val = strtoull(str, &end, 0);
    if(errno == 0 && *end == '\0') {
        *value = val;
        return 1;
    } else {
        return 0;
    }
}

int util_strtoint(const char *str, int64_t *value) {
    if (str == NULL || *str == '\0') return 0;
    char *end;
    errno = 0;
    int64_t val = strtoll(str, &end, 0);
    if(errno == 0 && *end == '\0') {
        *value = val;
        return 1;
    } else {
        return 0;
    }
}

int util_szstrtouint(const char *str, uint64_t *value) {
    if (str == NULL || *str == '\0') return 0;
    char *end;
    errno = 0;
    size_t len = strlen(str);
    uint64_t val = strtoull(str, &end, 0);
    if(errno != 0) {
        return 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;
    }
}

cxstring 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;
                }
            }
        }
    }
    return cx_strsubsl(url, 0, i);
}

char* util_url_base(const char *url) {
    return cx_strdup(util_url_base_s(cx_str(url))).ptr;
}

#ifdef _WIN32
#define strncasecmp _strnicmp
#endif

const char* util_url_path(const char *url) {
    return util_url_path_s(cx_str(url)).ptr;
}

cxstring util_url_path_s(cxstring url) {
    cxstring path = { "", 0 };
    int slashcount = 0;
    int slmax;
    if(url.length > 7 && !strncasecmp(url.ptr, "http://", 7)) {
        slmax = 3;
    } else if(url.length > 8 && !strncasecmp(url.ptr, "https://", 8)) {
        slmax = 3;
    } else {
        slmax = 1;
    }
    char c;
    for(int i=0;i<url.length;i++) {
        c = url.ptr[i];
        if(c == '/') {
            slashcount++;
            if(slashcount == slmax) {
                path = cx_strsubs(url, i);
                break;
            }
        }
    }
    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(url_base);
    cxstring path;
    if(p) {
        path = cx_str((char*)p);
    } else {
        path = CX_STR("");
    }
    
    return util_concat_path_s(base, path).ptr;
}

cxmutstr util_concat_path_s(cxstring base, cxstring path) {
    if(!path.ptr) {
        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;
}

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