2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 2018 Olaf Wintermann. All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
33 #include <ucx/buffer.h>
34 #include <ucx/utils.h>
41 DavSession* dav_session_new(DavContext *context, char *base_url) {
45 sstr_t url = sstr(base_url);
49 DavSession *sn = malloc(sizeof(DavSession));
50 memset(sn, 0, sizeof(DavSession));
51 sn->mp = ucx_mempool_new(DAV_SESSION_MEMPOOL_SIZE);
52 sn->pathcache = ucx_map_new_a(sn->mp->allocator, DAV_PATH_CACHE_SIZE);
58 dav_session_set_baseurl(sn, base_url);
60 sn->handle = curl_easy_init();
61 curl_easy_setopt(sn->handle, CURLOPT_FOLLOWLOCATION, 1L);
63 // create lock manager
64 DavLockManager *locks = ucx_mempool_malloc(sn->mp, sizeof(DavLockManager));
65 locks->resource_locks = ucx_map_new_a(sn->mp->allocator, 16);
66 locks->collection_locks = NULL;
70 DavProxy *proxy = sstrprefix(url, S("https")) ? context->https_proxy
71 : context->http_proxy;
74 curl_easy_setopt(sn->handle, CURLOPT_PROXY, proxy->url);
75 if (proxy->username) {
76 curl_easy_setopt(sn->handle, CURLOPT_PROXYUSERNAME,
78 if (proxy->password) {
79 curl_easy_setopt(sn->handle, CURLOPT_PROXYPASSWORD,
86 curl_easy_setopt(sn->handle, CURLOPT_NOPROXY,
92 #if LIBCURL_VERSION_NUM >= 0x072D00
93 curl_easy_setopt(sn->handle, CURLOPT_DEFAULT_PROTOCOL, "http");
95 curl_easy_setopt(sn->handle, CURLOPT_URL, base_url);
98 context->sessions = ucx_list_append(context->sessions, sn);
99 sn->context = context;
104 DavSession* dav_session_new_auth(
110 DavSession *sn = dav_session_new(context, base_url);
114 dav_session_set_auth(sn, user, password);
118 void dav_session_set_auth(DavSession *sn, char *user, char *password) {
119 if(user && password) {
120 size_t ulen = strlen(user);
121 size_t plen = strlen(password);
122 size_t upwdlen = ulen + plen + 2;
123 char *upwdbuf = malloc(upwdlen);
124 snprintf(upwdbuf, upwdlen, "%s:%s", user, password);
125 curl_easy_setopt(sn->handle, CURLOPT_USERPWD, upwdbuf);
130 void dav_session_set_baseurl(DavSession *sn, char *base_url) {
132 ucx_mempool_free(sn->mp, sn->base_url);
135 sstr_t url = sstr(base_url);
136 if(url.ptr[url.length - 1] == '/') {
137 sstr_t url = sstrdup_a(sn->mp->allocator, sstr(base_url));
138 sn->base_url = url.ptr;
140 char *url_str = ucx_mempool_malloc(sn->mp, url.length + 2);
141 memcpy(url_str, base_url, url.length);
142 url_str[url.length] = '/';
143 url_str[url.length + 1] = '\0';
144 sn->base_url = url_str;
148 void dav_session_enable_encryption(DavSession *sn, DavKey *key, int flags) {
150 // TODO: review sanity
154 sn->flags |= DAV_SESSION_ENCRYPT_CONTENT;
158 void dav_session_set_authcallback(DavSession *sn, dav_auth_func func, void *userdata) {
159 sn->auth_prompt = func;
160 sn->authprompt_userdata = userdata;
163 void dav_session_set_progresscallback(DavSession *sn, dav_progress_func get, dav_progress_func put, void *userdata) {
164 sn->get_progress = get;
165 sn->put_progress = put;
166 sn->progress_userdata = userdata;
169 CURLcode dav_session_curl_perform(DavSession *sn, long *status) {
170 return dav_session_curl_perform_buf(sn, NULL, NULL, status);
173 CURLcode dav_session_curl_perform_buf(DavSession *sn, UcxBuffer *request, UcxBuffer *response, long *status) {
174 CURLcode ret = curl_easy_perform(sn->handle);
176 curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status);
177 if(ret == CURLE_OK && http_status == 401 && sn->auth_prompt) {
178 if(!sn->auth_prompt(sn, sn->authprompt_userdata)) {
180 ucx_buffer_seek(request, 0, SEEK_SET);
183 ucx_buffer_seek(response, 0, SEEK_SET);
185 ret = curl_easy_perform(sn->handle);
186 curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status);
190 *status = http_status;
195 int dav_session_get_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
196 DavResource *res = clientp;
197 DavSession *sn = res->session;
198 if(sn->get_progress) {
199 sn->get_progress(res, (int64_t)dltotal, (int64_t)dlnow, sn->progress_userdata);
204 int dav_session_put_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
205 DavResource *res = clientp;
206 DavSession *sn = res->session;
207 if(sn->put_progress) {
208 sn->put_progress(res, (int64_t)ultotal, (int64_t)ulnow, sn->progress_userdata);
213 void dav_session_set_error(DavSession *sn, CURLcode c, int status) {
218 default: sn->error = DAV_ERROR;
222 case 401: sn->error = DAV_UNAUTHORIZED; break;
223 case 403: sn->error = DAV_FORBIDDEN; break;
224 case 404: sn->error = DAV_NOT_FOUND; break;
225 case 405: sn->error = DAV_METHOD_NOT_ALLOWED; break;
226 case 407: sn->error = DAV_PROXY_AUTH_REQUIRED; break;
227 case 409: sn->error = DAV_CONFLICT; break;
228 case 412: sn->error = DAV_PRECONDITION_FAILED; break;
229 case 413: sn->error = DAV_REQUEST_ENTITY_TOO_LARGE; break;
230 case 414: sn->error = DAV_REQUEST_URL_TOO_LONG; break;
231 case 423: sn->error = DAV_LOCKED; break;
232 case 511: sn->error = DAV_NET_AUTH_REQUIRED; break;
236 case CURLE_UNSUPPORTED_PROTOCOL: sn->error = DAV_UNSUPPORTED_PROTOCOL; break;
237 case CURLE_COULDNT_RESOLVE_PROXY: sn->error = DAV_COULDNT_RESOLVE_PROXY; break;
238 case CURLE_COULDNT_RESOLVE_HOST: sn->error = DAV_COULDNT_RESOLVE_HOST; break;
239 case CURLE_COULDNT_CONNECT: sn->error = DAV_COULDNT_CONNECT; break;
240 case CURLE_OPERATION_TIMEDOUT: sn->error = DAV_TIMEOUT; break;
241 case CURLE_SSL_CONNECT_ERROR:
242 case CURLE_PEER_FAILED_VERIFICATION:
243 case CURLE_SSL_ENGINE_NOTFOUND:
244 case CURLE_SSL_ENGINE_SETFAILED:
245 case CURLE_SSL_CERTPROBLEM:
246 case CURLE_SSL_CIPHER:
247 #ifndef CURLE_SSL_CACERT
248 case CURLE_SSL_CACERT:
250 case CURLE_SSL_CACERT_BADFILE:
251 case CURLE_SSL_SHUTDOWN_FAILED:
252 case CURLE_SSL_CRL_BADFILE:
253 case CURLE_SSL_ISSUER_ERROR: sn->error = DAV_SSL_ERROR; break;
254 default: sn->error = DAV_ERROR; break;
258 dav_session_set_errstr(sn, curl_easy_strerror(c));
260 dav_session_set_errstr(sn, NULL);
264 void dav_session_set_errstr(DavSession *sn, const char *str) {
266 dav_session_free(sn, sn->errorstr);
270 errstr = dav_session_strdup(sn, str);
272 sn->errorstr = errstr;
275 void dav_session_destroy(DavSession *sn) {
276 // remove session from context
277 UcxList *sessions = sn->context->sessions;
278 ssize_t i = ucx_list_find(sessions, sn, ucx_cmp_ptr, NULL);
280 UcxList *elm = ucx_list_get(sessions, i);
282 sn->context->sessions = ucx_list_remove(sessions, elm);
286 ucx_mempool_destroy(sn->mp);
287 curl_easy_cleanup(sn->handle);
292 void* dav_session_malloc(DavSession *sn, size_t size) {
293 return ucx_mempool_malloc(sn->mp, size);
296 void* dav_session_calloc(DavSession *sn, size_t nelm, size_t size) {
297 return ucx_mempool_calloc(sn->mp, nelm, size);
300 void* dav_session_realloc(DavSession *sn, void *ptr, size_t size) {
301 return ucx_mempool_realloc(sn->mp, ptr, size);
304 void dav_session_free(DavSession *sn, void *ptr) {
305 ucx_mempool_free(sn->mp, ptr);
308 char* dav_session_strdup(DavSession *sn, const char *str) {
309 return sstrdup_a(sn->mp->allocator, sstr((char*)str)).ptr;
313 char* dav_session_create_plain_href(DavSession *sn, char *path) {
314 if(!DAV_ENCRYPT_NAME(sn) && !DAV_DECRYPT_NAME(sn)) {
315 // non encrypted file names
316 char *url = util_path_to_url(sn, path);
317 char *href = dav_session_strdup(sn, util_url_path(url));
325 char* dav_session_get_href(DavSession *sn, char *path) {
326 if(DAV_DECRYPT_NAME(sn) || DAV_ENCRYPT_NAME(sn)) {
327 sstr_t p = sstr(path);
328 UcxBuffer *href = ucx_buffer_new(NULL, 256, UCX_BUFFER_AUTOEXTEND);
329 UcxBuffer *pbuf = ucx_buffer_new(NULL, 256, UCX_BUFFER_AUTOEXTEND);
334 char *cp = strdup(path);
335 //printf("cp: %s\n", cp);
336 while(strlen(cp) > 1) {
337 char *cached = ucx_map_cstr_get(sn->pathcache, cp);
341 ucx_buffer_puts(href, cached);
344 // check, if the parent path is cached
346 cp = util_parent_path(cp);
352 // if there are no cached elements we have to add the base url path
353 // to the href buffer
354 ucx_buffer_puts(href, util_url_path(sn->base_url));
357 // create resource for name lookup
358 sstr_t rp = sstrdup(sstrn(path, start));
359 DavResource *root = dav_resource_new(sn, rp.ptr);
361 resource_set_href(root, sstrn(href->space, href->pos));
363 // create request buffer for propfind requests
364 UcxBuffer *rqbuf = create_basic_propfind_request();
366 sstr_t remaining = sstrsubs(p, start);
368 sstr_t *elms = sstrsplit(remaining, S("/"), &nelm);
369 DavResource *res = root;
370 ucx_buffer_puts(pbuf, res->path);
371 // iterate over all remaining path elements
372 for(int i=0;i<nelm;i++) {
373 sstr_t elm = elms[i];
375 //printf("elm: %.*s\n", elm.length, elm.ptr);
376 DavResource *child = dav_find_child(sn, res, rqbuf, elm.ptr);
378 // if necessary add a path separator
379 if(pbuf->space[pbuf->pos-1] != '/') {
380 if(href->space[href->pos-1] != '/') {
381 ucx_buffer_putc(href, '/');
383 ucx_buffer_putc(pbuf, '/');
385 // add last path/href to the cache
386 sstr_t pp = sstrn(pbuf->space, pbuf->size);
387 sstr_t hh = sstrn(href->space, href->size);
388 dav_session_cache_path(sn, pp, hh);
390 ucx_buffer_write(elm.ptr, 1, elm.length, pbuf);
392 // href is already URL encoded, so don't encode again
393 ucx_buffer_puts(href, util_resource_name(child->href));
395 } else if(DAV_ENCRYPT_NAME(sn)) {
396 char *random_name = util_random_str();
397 ucx_buffer_puts(href, random_name);
400 // path is not URL encoded, so we have to do this here
401 scstr_t resname = scstr(util_resource_name(path));
402 // the name of collections ends with
403 // a trailing slash, which MUST NOT be encoded
404 if(resname.ptr[resname.length-1] == '/') {
405 char *esc = curl_easy_escape(sn->handle,
406 resname.ptr, resname.length-1);
407 ucx_buffer_write(esc, 1, strlen(esc), href);
408 ucx_buffer_putc(href, '/');
411 char *esc = curl_easy_escape(sn->handle,
412 resname.ptr, resname.length);
413 ucx_buffer_write(esc, 1, strlen(esc), href);
424 // if necessary add a path separator
425 if(p.ptr[p.length-1] == '/') {
426 if(href->space[href->pos-1] != '/') {
427 ucx_buffer_putc(href, '/');
429 ucx_buffer_putc(pbuf, '/');
431 // add the final path to the cache
432 sstr_t pp = sstrn(pbuf->space, pbuf->size);
433 sstr_t hh = sstrn(href->space, href->size);
434 dav_session_cache_path(sn, pp, hh);
436 sstr_t href_str = sstrdup_a(
442 dav_resource_free_all(root);
443 ucx_buffer_free(rqbuf);
444 ucx_buffer_free(pbuf);
445 ucx_buffer_free(href);
449 return dav_session_create_plain_href(sn, path);
453 DavResource* dav_find_child(DavSession *sn, DavResource *res, UcxBuffer *rqbuf, char *name) {
454 if(res && !dav_propfind(sn, res, rqbuf)) {
455 DavResource *child = res->children;
457 if(!strcmp(child->name, name)) {
466 void dav_session_cache_path(DavSession *sn, sstr_t path, sstr_t href) {
467 char *elm = ucx_map_sstr_get(sn->pathcache, path);
469 href = sstrdup_a(sn->mp->allocator, href);
470 ucx_map_sstr_put(sn->pathcache, path, href.ptr);
475 DavLock* dav_create_lock(DavSession *sn, char *token, char *timeout) {
476 DavLock *lock = dav_session_malloc(sn, sizeof(DavLock));
478 lock->token = dav_session_strdup(sn, token);
485 void dav_destroy_lock(DavSession *sn, DavLock *lock) {
486 dav_session_free(sn, lock->token);
488 dav_session_free(sn, lock->path);
490 dav_session_free(sn, lock);
493 int dav_add_resource_lock(DavSession *sn, char *path, DavLock *lock) {
494 DavLockManager *locks = sn->locks;
495 if(ucx_map_cstr_get(locks->resource_locks, path)) {
499 ucx_map_cstr_put(locks->resource_locks, path, lock);
503 static void insert_lock(DavSession *sn, UcxList *elm, UcxList *newelm) {
504 UcxList *next = elm->next;
513 int dav_add_collection_lock(DavSession *sn, char *path, DavLock *lock) {
514 DavLockManager *locks = sn->locks;
515 if(!locks->collection_locks) {
516 locks->collection_locks = ucx_list_append_a(
520 lock->path = dav_session_strdup(sn, path);
524 UcxList *elm = locks->collection_locks;
526 DavLock *l = elm->data;
527 int cmp = strcmp(path, l->path);
529 UcxList *newelm = ucx_list_append_a(sn->mp->allocator, NULL, lock);
530 lock->path = dav_session_strdup(sn, path);
531 insert_lock(sn, elm, newelm);
532 } else if(cmp == 0) {
539 UcxList *newelm = ucx_list_append_a(sn->mp->allocator, NULL, lock);
540 lock->path = dav_session_strdup(sn, path);
541 ucx_list_concat(elm, newelm);
549 DavLock* dav_get_lock(DavSession *sn, char *path) {
550 DavLockManager *locks = sn->locks;
552 DavLock *lock = ucx_map_cstr_get(locks->resource_locks, path);
557 sstr_t p = sstr(path);
558 UCX_FOREACH(elm, locks->collection_locks) {
559 DavLock *cl = elm->data;
560 int cmd = strcmp(path, cl->path);
563 } else if(sstrprefix(p, sstr(cl->path))) {
573 void dav_remove_lock(DavSession *sn, char *path, DavLock *lock) {
574 DavLockManager *locks = sn->locks;
576 if(ucx_map_cstr_remove(locks->resource_locks, path)) {
581 UCX_FOREACH(elm, locks->collection_locks) {
582 DavLock *cl = elm->data;
590 locks->collection_locks = ucx_list_remove_a(
592 locks->collection_locks,