--- /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); +} +