Thu, 12 Dec 2024 16:46:43 +0100
fix compatibility with older libcurl versions
/* * 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/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 dav_context_add_session(context, 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; } DavSession* dav_session_clone(DavSession *sn) { CURL *newhandle = curl_easy_duphandle(sn->handle); DavSession *newsn = malloc(sizeof(DavSession)); memset(newsn, 0, sizeof(DavSession)); newsn->mp = cxBasicMempoolCreate(DAV_SESSION_MEMPOOL_SIZE); newsn->pathcache = cxHashMapCreate(newsn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE); newsn->key = sn->key; newsn->errorstr = NULL; newsn->error = DAV_OK; newsn->flags = 0; newsn->handle = newhandle; newsn->base_url = cx_strdup_a(newsn->mp->allocator, cx_str(sn->base_url)).ptr; newsn->auth_prompt = sn->auth_prompt; newsn->authprompt_userdata = sn->authprompt_userdata; newsn->logfunc = sn->logfunc; newsn->get_progress = sn->get_progress; newsn->put_progress = sn->put_progress; newsn->progress_userdata = sn->progress_userdata; // add to context dav_context_add_session(sn->context, newsn); newsn->context = sn->context; return newsn; } 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); #if LIBCURL_VERSION_NUM >= 0x074800 curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_METHOD , &log_method); #else long opt_upload = 0; curl_easy_getinfo(sn->handle, CURLOPT_UPLOAD, &opt_upload); char *opt_custom = NULL; curl_easy_getinfo(sn->handle, CURLOPT_CUSTOMREQUEST, &opt_custom); if(opt_custom) { log_method = opt_custom; } else if(opt_upload) { log_method = "PUT"; } else { log_method = "GET"; } #endif 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 if (dav_context_remove_session(sn->context, sn)) { fprintf(stderr, "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; } cxListFindRemove(locks->collection_locks, lock); }