Sun, 17 Dec 2023 15:33:50 +0100
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 2018 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 <stdio.h> #include <stdlib.h> #include <string.h> #include <cx/buffer.h> #include <cx/utils.h> #include <cx/basic_mempool.h> #include <cx/hash_map.h> #include "utils.h" #include "session.h" #include "resource.h" #include "methods.h" DavSession* dav_session_new(DavContext *context, char *base_url) { if(!base_url) { return NULL; } cxstring url = cx_str(base_url); if(url.length == 0) { return NULL; } DavSession *sn = malloc(sizeof(DavSession)); memset(sn, 0, sizeof(DavSession)); sn->mp = cxBasicMempoolCreate(DAV_SESSION_MEMPOOL_SIZE); sn->pathcache = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE); sn->key = NULL; sn->errorstr = NULL; sn->error = DAV_OK; sn->flags = 0; dav_session_set_baseurl(sn, base_url); sn->handle = curl_easy_init(); curl_easy_setopt(sn->handle, CURLOPT_FOLLOWLOCATION, 1L); // lock manager is created on-demand sn->locks = NULL; // set proxy DavProxy *proxy = cx_strprefix(url, CX_STR("https")) ? context->https_proxy : context->http_proxy; if (proxy->url) { curl_easy_setopt(sn->handle, CURLOPT_PROXY, proxy->url); if (proxy->username) { curl_easy_setopt(sn->handle, CURLOPT_PROXYUSERNAME, proxy->username); if (proxy->password) { curl_easy_setopt(sn->handle, CURLOPT_PROXYPASSWORD, proxy->password); } else { // TODO: prompt } } if(proxy->no_proxy) { curl_easy_setopt(sn->handle, CURLOPT_NOPROXY, proxy->no_proxy); } } // set url #if LIBCURL_VERSION_NUM >= 0x072D00 curl_easy_setopt(sn->handle, CURLOPT_DEFAULT_PROTOCOL, "http"); #endif curl_easy_setopt(sn->handle, CURLOPT_URL, base_url); // add to context cxListAdd(context->sessions, sn); sn->context = context; return sn; } DavSession* dav_session_new_auth( DavContext *context, char *base_url, char *user, char *password) { DavSession *sn = dav_session_new(context, base_url); if(!sn) { return NULL; } dav_session_set_auth(sn, user, password); return sn; } void dav_session_set_auth(DavSession *sn, const char *user, const char *password) { if(user && password) { dav_session_set_auth_s(sn, cx_str(user), cx_str(password)); } } void dav_session_set_auth_s(DavSession *sn, cxstring user, cxstring password) { if(user.length > 0 && password.length > 0) { size_t upwdlen = user.length + password.length + 2; char *upwdbuf = malloc(upwdlen); snprintf(upwdbuf, upwdlen, "%.*s:%.*s", (int)user.length, user.ptr, (int)password.length, password.ptr); curl_easy_setopt(sn->handle, CURLOPT_USERPWD, upwdbuf); free(upwdbuf); } } void dav_session_set_baseurl(DavSession *sn, char *base_url) { const CxAllocator *a = sn->mp->allocator; if(sn->base_url) { cxFree(a, sn->base_url); } cxstring url = cx_str(base_url); if(url.ptr[url.length - 1] == '/') { cxmutstr url_m = cx_strdup_a(a, cx_str(base_url)); sn->base_url = url_m.ptr; } else { char *url_str = cxMalloc(a, url.length + 2); memcpy(url_str, base_url, url.length); url_str[url.length] = '/'; url_str[url.length + 1] = '\0'; sn->base_url = url_str; } } void dav_session_enable_encryption(DavSession *sn, DavKey *key, int flags) { sn->key = key; // TODO: review sanity if(flags != 0) { sn->flags |= flags; } else { sn->flags |= DAV_SESSION_ENCRYPT_CONTENT; } } void dav_session_set_authcallback(DavSession *sn, dav_auth_func func, void *userdata) { sn->auth_prompt = func; sn->authprompt_userdata = userdata; } void dav_session_set_progresscallback(DavSession *sn, dav_progress_func get, dav_progress_func put, void *userdata) { sn->get_progress = get; sn->put_progress = put; sn->progress_userdata = userdata; } CURLcode dav_session_curl_perform(DavSession *sn, long *status) { return dav_session_curl_perform_buf(sn, NULL, NULL, status); } CURLcode dav_session_curl_perform_buf(DavSession *sn, CxBuffer *request, CxBuffer *response, long *status) { CURLcode ret = curl_easy_perform(sn->handle); long http_status; curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status); if(ret == CURLE_OK) { if(sn->logfunc) { char *log_method; char *log_url; curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &log_url); curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_METHOD , &log_method); char *log_reqbody = NULL; size_t log_reqbodylen = 0; char *log_rpbody = NULL; size_t log_rpbodylen = 0; if(request) { log_reqbody = request->space; log_reqbodylen = request->size; } if(response) { log_rpbody = response->space; log_rpbodylen = response->size; } sn->logfunc(sn, log_method, log_url, log_reqbody, log_reqbodylen, http_status, log_rpbody, log_rpbodylen); } if(http_status == 401 && sn->auth_prompt) { if(!sn->auth_prompt(sn, sn->authprompt_userdata)) { if(request) { cxBufferSeek(request, 0, SEEK_SET); } if(response) { cxBufferSeek(response, 0, SEEK_SET); } ret = curl_easy_perform(sn->handle); curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status); } } } if(status) { *status = http_status; } return ret; } int dav_session_get_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { DavResource *res = clientp; DavSession *sn = res->session; if(sn->get_progress) { sn->get_progress(res, (int64_t)dltotal, (int64_t)dlnow, sn->progress_userdata); } return 0; } int dav_session_put_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { DavResource *res = clientp; DavSession *sn = res->session; if(sn->put_progress) { sn->put_progress(res, (int64_t)ultotal, (int64_t)ulnow, sn->progress_userdata); } return 0; } void dav_session_set_error(DavSession *sn, CURLcode c, int status) { if(status > 0) { switch(status) { default: { switch(c) { default: sn->error = DAV_ERROR; } break; } case 401: sn->error = DAV_UNAUTHORIZED; break; case 403: sn->error = DAV_FORBIDDEN; break; case 404: sn->error = DAV_NOT_FOUND; break; case 405: sn->error = DAV_METHOD_NOT_ALLOWED; break; case 407: sn->error = DAV_PROXY_AUTH_REQUIRED; break; case 409: sn->error = DAV_CONFLICT; break; case 412: sn->error = DAV_PRECONDITION_FAILED; break; case 413: sn->error = DAV_REQUEST_ENTITY_TOO_LARGE; break; case 414: sn->error = DAV_REQUEST_URL_TOO_LONG; break; case 423: sn->error = DAV_LOCKED; break; case 511: sn->error = DAV_NET_AUTH_REQUIRED; break; } } else { switch(c) { case CURLE_UNSUPPORTED_PROTOCOL: sn->error = DAV_UNSUPPORTED_PROTOCOL; break; case CURLE_COULDNT_RESOLVE_PROXY: sn->error = DAV_COULDNT_RESOLVE_PROXY; break; case CURLE_COULDNT_RESOLVE_HOST: sn->error = DAV_COULDNT_RESOLVE_HOST; break; case CURLE_COULDNT_CONNECT: sn->error = DAV_COULDNT_CONNECT; break; case CURLE_OPERATION_TIMEDOUT: sn->error = DAV_TIMEOUT; break; case CURLE_SSL_CONNECT_ERROR: case CURLE_PEER_FAILED_VERIFICATION: case CURLE_SSL_ENGINE_NOTFOUND: case CURLE_SSL_ENGINE_SETFAILED: case CURLE_SSL_CERTPROBLEM: case CURLE_SSL_CIPHER: //#ifndef CURLE_SSL_CACERT // case CURLE_SSL_CACERT: //#endif case CURLE_SSL_CACERT_BADFILE: case CURLE_SSL_SHUTDOWN_FAILED: case CURLE_SSL_CRL_BADFILE: case CURLE_SSL_ISSUER_ERROR: sn->error = DAV_SSL_ERROR; break; default: sn->error = DAV_ERROR; break; } } if(c != CURLE_OK) { dav_session_set_errstr(sn, curl_easy_strerror(c)); } else { dav_session_set_errstr(sn, NULL); } } void dav_session_set_errstr(DavSession *sn, const char *str) { if(sn->errorstr) { dav_session_free(sn, sn->errorstr); } char *errstr = NULL; if(str) { errstr = dav_session_strdup(sn, str); } sn->errorstr = errstr; } void dav_session_destroy(DavSession *sn) { // remove session from context CxList *sessions = sn->context->sessions; ssize_t i = cxListFind(sessions, sn); if(i >= 0) { cxListRemove(sessions, i); } else { printf("Error: session not found in ctx->sessions\n"); dav_session_destructor(sn); } } void dav_session_destructor(DavSession *sn) { cxMempoolDestroy(sn->mp); curl_easy_cleanup(sn->handle); free(sn); } void* dav_session_malloc(DavSession *sn, size_t size) { return cxMalloc(sn->mp->allocator, size); } void* dav_session_calloc(DavSession *sn, size_t nelm, size_t size) { return cxCalloc(sn->mp->allocator, nelm, size); } void* dav_session_realloc(DavSession *sn, void *ptr, size_t size) { return cxRealloc(sn->mp->allocator, ptr, size); } void dav_session_free(DavSession *sn, void *ptr) { cxFree(sn->mp->allocator, ptr); } char* dav_session_strdup(DavSession *sn, const char *str) { return cx_strdup_a(sn->mp->allocator, cx_str((char*)str)).ptr; } char* dav_session_create_plain_href(DavSession *sn, const char *path) { if(!DAV_ENCRYPT_NAME(sn) && !DAV_DECRYPT_NAME(sn)) { // non encrypted file names char *url = util_path_to_url(sn, path); char *href = dav_session_strdup(sn, util_url_path(url)); free(url); return href; } else { return NULL; } } char* dav_session_get_href(DavSession *sn, const char *path) { if(DAV_DECRYPT_NAME(sn) || DAV_ENCRYPT_NAME(sn)) { cxstring p = cx_str(path); CxBuffer href; CxBuffer pbuf; cxBufferInit(&href, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); cxBufferInit(&pbuf, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); int start = 0; int begin = 0; // check path cache char *cp = strdup(path); //printf("cp: %s\n", cp); while(strlen(cp) > 1) { char *cached = cxMapGet(sn->pathcache, cx_hash_key_str(cp)); if(cached) { start = strlen(cp); begin = start; cxBufferPutString(&href, cached); break; } else { // check, if the parent path is cached char *f = cp; cp = util_parent_path(cp); free(f); } } free(cp); if(href.pos == 0) { // if there are no cached elements we have to add the base url path // to the href buffer cxBufferPutString(&href, util_url_path(sn->base_url)); } // create resource for name lookup cxmutstr rp = cx_strdup(cx_strn(path, start)); DavResource *root = dav_resource_new(sn, rp.ptr); free(rp.ptr); resource_set_href(root, cx_strn(href.space, href.pos)); // create request buffer for propfind requests CxBuffer *rqbuf = create_basic_propfind_request(); cxstring remaining = cx_strsubs(p, start); CxStrtokCtx elms = cx_strtok(remaining, CX_STR("/"), INT_MAX); DavResource *res = root; cxBufferPutString(&pbuf, res->path); // iterate over all remaining path elements cxstring elm; while(cx_strtok_next(&elms, &elm)) { if(elm.length > 0) { //printf("elm: %.*s\n", elm.length, elm.ptr); DavResource *child = dav_find_child(sn, res, rqbuf, elm.ptr); // if necessary add a path separator if(pbuf.space[pbuf.pos-1] != '/') { if(href.space[href.pos-1] != '/') { cxBufferPut(&href, '/'); } cxBufferPut(&pbuf, '/'); } // add last path/href to the cache cxstring pp = cx_strn(pbuf.space, pbuf.size); cxstring hh = cx_strn(href.space, href.size); dav_session_cache_path(sn, pp, hh); cxBufferWrite(elm.ptr, 1, elm.length, &pbuf); if(child) { // href is already URL encoded, so don't encode again cxBufferPutString(&href, util_resource_name(child->href)); res = child; } else if(DAV_ENCRYPT_NAME(sn)) { char *random_name = util_random_str(); cxBufferPutString(&href, random_name); free(random_name); } else { // path is not URL encoded, so we have to do this here cxstring resname = cx_str(util_resource_name((const char*)path)); // the name of collections ends with // a trailing slash, which MUST NOT be encoded if(resname.ptr[resname.length-1] == '/') { char *esc = curl_easy_escape(sn->handle, resname.ptr, resname.length-1); cxBufferWrite(esc, 1, strlen(esc), &href); cxBufferPut(&href, '/'); curl_free(esc); } else { char *esc = curl_easy_escape(sn->handle, resname.ptr, resname.length); cxBufferWrite(esc, 1, strlen(esc), &href); curl_free(esc); } } } } // if necessary add a path separator if(p.ptr[p.length-1] == '/') { if(href.space[href.pos-1] != '/') { cxBufferPut(&href, '/'); } cxBufferPut(&pbuf, '/'); } // add the final path to the cache cxstring pp = cx_strn(pbuf.space, pbuf.size); cxstring hh = cx_strn(href.space, href.size); dav_session_cache_path(sn, pp, hh); cxmutstr href_str = cx_strdup_a( sn->mp->allocator, cx_strn(href.space, href.size)); // cleanup dav_resource_free_all(root); cxBufferFree(rqbuf); cxBufferDestroy(&pbuf); cxBufferDestroy(&href); return href_str.ptr; } else { return dav_session_create_plain_href(sn, path); } } DavResource* dav_find_child(DavSession *sn, DavResource *res, CxBuffer *rqbuf, const char *name) { if(res && !dav_propfind(sn, res, rqbuf)) { DavResource *child = res->children; while(child) { if(!strcmp(child->name, name)) { return child; } child = child->next; } } return NULL; } void dav_session_cache_path(DavSession *sn, cxstring path, cxstring href) { CxHashKey path_key = cx_hash_key(path.ptr, path.length); char *elm = cxMapGet(sn->pathcache, path_key); if(!elm) { cxmutstr href_s = cx_strdup_a(sn->mp->allocator, href); cxMapPut(sn->pathcache, path_key, href_s.ptr); } } DavLock* dav_create_lock(DavSession *sn, const char *token, char *timeout) { DavLock *lock = dav_session_malloc(sn, sizeof(DavLock)); lock->path = NULL; lock->token = dav_session_strdup(sn, token); // TODO: timeout return lock; } void dav_destroy_lock(DavSession *sn, DavLock *lock) { dav_session_free(sn, lock->token); if(lock->path) { dav_session_free(sn, lock->path); } dav_session_free(sn, lock); } static int dav_lock_cmp(void const *left, void const *right) { const DavLock *l = left; const DavLock *r = right; return strcmp(l->path, r->path); } static int create_lock_manager(DavSession *sn) { // create lock manager DavLockManager *locks = cxMalloc(sn->mp->allocator, sizeof(DavLockManager)); locks->resource_locks = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, 16); locks->collection_locks = cxLinkedListCreate(sn->mp->allocator, dav_lock_cmp, CX_STORE_POINTERS); sn->locks = locks; return 0; } static DavLockManager* get_lock_manager(DavSession *sn) { DavLockManager *locks = sn->locks; if(!locks) { if(create_lock_manager(sn)) { return NULL; } locks = sn->locks; } return locks; } int dav_add_resource_lock(DavSession *sn, const char *path, DavLock *lock) { DavLockManager *locks = get_lock_manager(sn); if(!locks) { return -1; } CxHashKey path_key = cx_hash_key_str(path); if(cxMapGet(locks->resource_locks, path_key)) { return -1; } cxMapPut(locks->resource_locks, path_key, lock); return 0; } int dav_add_collection_lock(DavSession *sn, const char *path, DavLock *lock) { DavLockManager *locks = get_lock_manager(sn); if(!locks) { return -1; } lock->path = dav_session_strdup(sn, path); cxListAdd(locks->collection_locks, lock); cxListSort(locks->collection_locks); return 0; } DavLock* dav_get_lock(DavSession *sn, const char *path) { DavLockManager *locks = get_lock_manager(sn); if(!locks) { return NULL; } cxstring p = cx_str(path); DavLock *lock = cxMapGet(locks->resource_locks, cx_hash_key(p.ptr, p.length)); if(lock) { return lock; } CxIterator i = cxListIterator(locks->collection_locks); cx_foreach(DavLock*, col_lock, i) { int cmd = strcmp(path, col_lock->path); if(cmd == 0) { return col_lock; } else if(cx_strprefix(p, cx_str(col_lock->path))) { return col_lock; } else if(cmd > 0) { break; } } return NULL; } void dav_remove_lock(DavSession *sn, const char *path, DavLock *lock) { DavLockManager *locks = get_lock_manager(sn); if(!locks) { return; } if(cxMapRemoveAndGet(locks->resource_locks, cx_hash_key_str(path))) { return; } CxMutIterator i = cxListMutIterator(locks->collection_locks); int rm = 0; cx_foreach(DavLock* , cl, i) { if(rm) { break; } if(cl == lock) { cxIteratorFlagRemoval(i); rm = 1; } } }