libidav/utils.c

Thu, 21 Dec 2017 19:48:27 +0100

author
Mike Becker <universe@uap-core.de>
date
Thu, 21 Dec 2017 19:48:27 +0100
changeset 359
bacb54502b24
parent 349
0b4ecadaf3f9
child 372
2e15ff88a0ab
permissions
-rw-r--r--

davql: allow ANYWHERE keyword in SELECT statements

This may seem pointless, but users might want to be explicit about this and the grammar is more consistent.

This commit also adds some no-ops to the functions body of the SET parser, because some day the grammar might allow more clauses after the WHERE clause.

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2016 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 <ucx/string.h>
#include <ucx/buffer.h>
#include <ucx/utils.h>
#include <libxml/tree.h>
#include <curl/curl.h>

#ifdef _WIN32
#include <conio.h>
#define getpasswordchar() getch()
#else
#include <termios.h>
#define getpasswordchar() getchar()
#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(sstr_t 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
    sstr_t date = sstrtrim(sstr(iso8601str));

    sstr_t time = sstrchr(date, 'T');
    if(time.length == 0) {
        return 0;
    }
    date.length = time.ptr - date.ptr;
    time.ptr++; time.length--;
    
    sstr_t tzinfo;
    if((tzinfo = sstrchr(time, 'Z')).length > 0 ||
        (tzinfo = sstrchr(time, '+')).length > 0 ||
        (tzinfo = sstrchr(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
    sstr_t frac;
    if((frac = sstrchr(time, '.')).length > 0 ||
        (frac = sstrchr(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(!sstrcmp(tzinfo, S("Z"))) {
#ifdef __FreeBSD__
        return timegm(&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);
#ifdef __FreeBSD__
            return timegm(&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(char *v) {
    if(v[0] == 'T' || v[0] == 't') {
        return 1;
    }
    return 0;
}

int util_strtoint(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;
    }
}

char* util_url_base(char *url) {
    sstr_t u = sstr(url);
    int len = u.length;
    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;
    int i = 0;
    for(i=0;i<len;i++) {
        c = url[i];
        if(c == '/') {
            slashcount++;
            if(slashcount == slmax) {
                i++;
                break;
            }
        }
    } 
    sstr_t server = sstrsubsl(u, 0, i);
    server = sstrdup(server);
    return server.ptr;
}

char* util_url_path(char *url) { 
    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;
            }
        }
    } 
    return path;
}

char* util_url_decode(DavSession *sn, 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) {
    
    sstr_t sbuffer = sstrn(buffer, size*nitems);
    
    UcxMap *map = (UcxMap*) data;
    
    // if we get a status line, clear the map and exit
    if(sstrprefix(sbuffer, S("HTTP/"))) {
        ucx_map_free_content(map, free);
        ucx_map_clear(map);
        return size*nitems;
    }
    
    // if we get the terminating CRLF, just exit
    if(!sstrcmp(sbuffer, S("\r\n"))) {
        return 2;
    }
    
    sstr_t key = sbuffer;
    sstr_t value = sstrchr(sbuffer, ':');
    
    if(value.length == 0) {
        return 0; // invalid header line
    }
    
    key.length = value.ptr - key.ptr;
    value.ptr++; value.length--;
    
    key = sstrlower(sstrtrim(key));
    value = sstrdup(sstrtrim(value));
        
    ucx_map_sstr_put(map, key, value.ptr);
    
    free(key.ptr);
    
    return sbuffer.length;
}

void util_capture_header(CURL *handle, UcxMap* 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);
    }
}

char* util_resource_name(char *url) {
    int si = 0;
    int osi = 0;
    int i = 0;
    int p = 0;
    char c;
    while((c = url[i]) != 0) {
        if(c == '/') {
            osi = si;
            si = i;
            p = 1;
        }
        i++;
    }
    
    char *name = url + si + p;
    if(name[0] == 0) {
        name = url + osi + p;
        if(name[0] == 0) {
            return url;
        }
    }
    
    return name;
}

int util_mkdir(char *path, mode_t mode) {
#ifdef _WIN32
    return mkdir(path);
#else
    return mkdir(path, mode);
#endif
}

char* util_concat_path(char *url_base, char *p) {
    sstr_t base = sstr(url_base);
    sstr_t path;
    if(p) {
        path = sstr(p);
    } else {
        path = sstrn("", 0);
    }
    
    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;
        }
    }
    
    sstr_t url;
    if(add_separator) {
        url = sstrcat(3, base, sstr("/"), path);
    } else {
        url = sstrcat(2, base, path);
    }
    
    return url.ptr;
}

char* util_get_url(DavSession *sn, char *href) {
    sstr_t base = sstr(sn->base_url);
    sstr_t href_str = sstr(href);
    
    char *base_path = util_url_path(sn->base_url);
    base.length -= strlen(base_path);
    
    sstr_t url = sstrcat(2, base, href_str);
    return url.ptr;
}

void util_set_url(DavSession *sn, 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, char *path) {
    char *space = malloc(256);
    UcxBuffer *url = ucx_buffer_new(space, 256, UCX_BUFFER_AUTOEXTEND);
    
    // add base url
    ucx_buffer_write(sn->base_url, 1, strlen(sn->base_url), url);
    // remove trailing slash
    ucx_buffer_seek(url, -1, SEEK_CUR);
    
    sstr_t p = sstr(path);
    ssize_t ntk = 0;
    sstr_t *tks = sstrsplit(p, S("/"), &ntk);
    
    for(int i=0;i<ntk;i++) {
        sstr_t node = tks[i];
        if(node.length > 0) {
            char *esc = curl_easy_escape(sn->handle, node.ptr, node.length);
            ucx_buffer_putc(url, '/');
            ucx_buffer_write(esc, 1, strlen(esc), url);
            curl_free(esc);
        }
        free(node.ptr);
    }
    free(tks);
    if(path[p.length-1] == '/') {
        ucx_buffer_putc(url, '/');
    }
    ucx_buffer_putc(url, 0);
    
    space = url->space;
    ucx_buffer_free(url);
    
    return space;
}

char* util_parent_path(char *path) {
    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_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(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(char* in, int *outlen) {
    /* code is mostly from wikibooks */
    
    size_t inlen = strlen(in);
    size_t bufsize = (inlen*3) / 4;
    char *outbuf = malloc(bufsize+1);
    *outlen = -1;
    
    unsigned char *out = (unsigned char*)outbuf;
    
    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(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, char *str, char *key) {
    DavKey *k = dav_context_get_key(sn->context, key);
    if(!k) {
        sn->error = DAV_ERROR;
        sstr_t err = ucx_sprintf("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, 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, char *str, char *key) {
    DavKey *k = dav_context_get_key(sn->context, key);
    if(!k) {
        sn->error = DAV_ERROR;
        sstr_t err = ucx_sprintf("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, 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';
    
    sstr_t t = S(
            "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
 */
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;
    }
}

sstr_t util_readline(FILE *stream) {
    UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND);
    
    int c;
    while((c = fgetc(stream)) != '\n') {
        ucx_buffer_putc(buf, c);
    }
    
    sstr_t str = sstrdup(sstrtrim(sstrn(buf->space, buf->size)));
    ucx_buffer_free(buf);
    return str;
}

char* util_password_input(char *prompt) {
    fprintf(stderr, "%s", prompt);
    fflush(stderr);
    
#ifndef _WIN32
    // hide terminal input
    struct termios oflags, nflags;
    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
    UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND);
    int c = 0;
    while((c = getpasswordchar()) != EOF) {
        if(c == '\n' || c == '\r') {
            break;
        }
        ucx_buffer_putc(buf, c);
    }
    ucx_buffer_putc(buf, 0);
    fflush(stdin);
    
#ifndef _WIN32
    // restore terminal settings
    if (tcsetattr(fileno(stdin), TCSANOW, &oflags) != 0) {
        perror("tcsetattr");
    }
#endif
    
    char *str = buf->space;
    free(buf); // only free the UcxBuffer struct
    return str;
}


char* util_hexstr(unsigned char *data, size_t len) {
    size_t buflen = 2*len + 4;
    UcxBuffer *buf = ucx_buffer_new(malloc(buflen), buflen + 1, 0);
    for(int i=0;i<len;i++) {
        ucx_bprintf(buf, "%x", data[i]);
    }
    ucx_buffer_putc(buf, 0);
    char *str = buf->space;
    ucx_buffer_free(buf);
    return str;
}

mercurial