libidav/session.c

changeset 1
b5bb7b3cd597
child 2
fbdfaacc4182
equal deleted inserted replaced
0:2483f517c562 1:b5bb7b3cd597
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 2018 Olaf Wintermann. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
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.
15 *
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.
27 */
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include <cx/buffer.h>
34 #include <cx/utils.h>
35 #include <cx/mempool.h>
36 #include <cx/hash_map.h>
37
38 #include "utils.h"
39 #include "session.h"
40 #include "resource.h"
41 #include "methods.h"
42
43 DavSession* dav_session_new(DavContext *context, char *base_url) {
44 if(!base_url) {
45 return NULL;
46 }
47 cxstring url = cx_str(base_url);
48 if(url.length == 0) {
49 return NULL;
50 }
51 DavSession *sn = malloc(sizeof(DavSession));
52 memset(sn, 0, sizeof(DavSession));
53 sn->mp = cxMempoolCreate(DAV_SESSION_MEMPOOL_SIZE, NULL);
54 sn->pathcache = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE);
55 sn->key = NULL;
56 sn->errorstr = NULL;
57 sn->error = DAV_OK;
58 sn->flags = 0;
59
60 dav_session_set_baseurl(sn, base_url);
61
62 sn->handle = curl_easy_init();
63 curl_easy_setopt(sn->handle, CURLOPT_FOLLOWLOCATION, 1L);
64
65 // lock manager is created on-demand
66 sn->locks = NULL;
67
68 // set proxy
69 DavProxy *proxy = cx_strprefix(url, CX_STR("https")) ? context->https_proxy
70 : context->http_proxy;
71
72 if (proxy->url) {
73 curl_easy_setopt(sn->handle, CURLOPT_PROXY, proxy->url);
74 if (proxy->username) {
75 curl_easy_setopt(sn->handle, CURLOPT_PROXYUSERNAME,
76 proxy->username);
77 if (proxy->password) {
78 curl_easy_setopt(sn->handle, CURLOPT_PROXYPASSWORD,
79 proxy->password);
80 } else {
81 // TODO: prompt
82 }
83 }
84 if(proxy->no_proxy) {
85 curl_easy_setopt(sn->handle, CURLOPT_NOPROXY,
86 proxy->no_proxy);
87 }
88 }
89
90 // set url
91 #if LIBCURL_VERSION_NUM >= 0x072D00
92 curl_easy_setopt(sn->handle, CURLOPT_DEFAULT_PROTOCOL, "http");
93 #endif
94 curl_easy_setopt(sn->handle, CURLOPT_URL, base_url);
95
96 // add to context
97 cxListAdd(context->sessions, sn);
98 sn->context = context;
99
100 return sn;
101 }
102
103 DavSession* dav_session_new_auth(
104 DavContext *context,
105 char *base_url,
106 char *user,
107 char *password)
108 {
109 DavSession *sn = dav_session_new(context, base_url);
110 if(!sn) {
111 return NULL;
112 }
113 dav_session_set_auth(sn, user, password);
114 return sn;
115 }
116
117 void dav_session_set_auth(DavSession *sn, char *user, char *password) {
118 if(user && password) {
119 size_t ulen = strlen(user);
120 size_t plen = strlen(password);
121 size_t upwdlen = ulen + plen + 2;
122 char *upwdbuf = malloc(upwdlen);
123 snprintf(upwdbuf, upwdlen, "%s:%s", user, password);
124 curl_easy_setopt(sn->handle, CURLOPT_USERPWD, upwdbuf);
125 free(upwdbuf);
126 }
127 }
128
129 void dav_session_set_baseurl(DavSession *sn, char *base_url) {
130 const CxAllocator *a = sn->mp->allocator;
131 if(sn->base_url) {
132 cxFree(a, sn->base_url);
133 }
134
135 cxstring url = cx_str(base_url);
136 if(url.ptr[url.length - 1] == '/') {
137 cxmutstr url_m = cx_strdup_a(a, cx_str(base_url));
138 sn->base_url = url_m.ptr;
139 } else {
140 char *url_str = cxMalloc(a, 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;
145 }
146 }
147
148 void dav_session_enable_encryption(DavSession *sn, DavKey *key, int flags) {
149 sn->key = key;
150 // TODO: review sanity
151 if(flags != 0) {
152 sn->flags |= flags;
153 } else {
154 sn->flags |= DAV_SESSION_ENCRYPT_CONTENT;
155 }
156 }
157
158 void dav_session_set_authcallback(DavSession *sn, dav_auth_func func, void *userdata) {
159 sn->auth_prompt = func;
160 sn->authprompt_userdata = userdata;
161 }
162
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;
167 }
168
169 CURLcode dav_session_curl_perform(DavSession *sn, long *status) {
170 return dav_session_curl_perform_buf(sn, NULL, NULL, status);
171 }
172
173 CURLcode dav_session_curl_perform_buf(DavSession *sn, CxBuffer *request, CxBuffer *response, long *status) {
174 CURLcode ret = curl_easy_perform(sn->handle);
175 long http_status;
176 curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status);
177 if(ret == CURLE_OK) {
178 if(sn->logfunc) {
179 char *log_method;
180 char *log_url;
181 curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &log_url);
182 curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_METHOD , &log_method);
183 char *log_reqbody = NULL;
184 size_t log_reqbodylen = 0;
185 char *log_rpbody = NULL;
186 size_t log_rpbodylen = 0;
187 if(request) {
188 log_reqbody = request->space;
189 log_reqbodylen = request->size;
190 }
191 if(response) {
192 log_rpbody = response->space;
193 log_rpbodylen = response->size;
194 }
195 sn->logfunc(sn, log_method, log_url, log_reqbody, log_reqbodylen, http_status, log_rpbody, log_rpbodylen);
196 }
197
198 if(http_status == 401 && sn->auth_prompt) {
199 if(!sn->auth_prompt(sn, sn->authprompt_userdata)) {
200 if(request) {
201 cxBufferSeek(request, 0, SEEK_SET);
202 }
203 if(response) {
204 cxBufferSeek(response, 0, SEEK_SET);
205 }
206 ret = curl_easy_perform(sn->handle);
207 curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status);
208 }
209 }
210
211 }
212
213 if(status) {
214 *status = http_status;
215 }
216 return ret;
217 }
218
219 int dav_session_get_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
220 DavResource *res = clientp;
221 DavSession *sn = res->session;
222 if(sn->get_progress) {
223 sn->get_progress(res, (int64_t)dltotal, (int64_t)dlnow, sn->progress_userdata);
224 }
225 return 0;
226 }
227
228 int dav_session_put_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
229 DavResource *res = clientp;
230 DavSession *sn = res->session;
231 if(sn->put_progress) {
232 sn->put_progress(res, (int64_t)ultotal, (int64_t)ulnow, sn->progress_userdata);
233 }
234 return 0;
235 }
236
237 void dav_session_set_error(DavSession *sn, CURLcode c, int status) {
238 if(status > 0) {
239 switch(status) {
240 default: {
241 switch(c) {
242 default: sn->error = DAV_ERROR;
243 }
244 break;
245 }
246 case 401: sn->error = DAV_UNAUTHORIZED; break;
247 case 403: sn->error = DAV_FORBIDDEN; break;
248 case 404: sn->error = DAV_NOT_FOUND; break;
249 case 405: sn->error = DAV_METHOD_NOT_ALLOWED; break;
250 case 407: sn->error = DAV_PROXY_AUTH_REQUIRED; break;
251 case 409: sn->error = DAV_CONFLICT; break;
252 case 412: sn->error = DAV_PRECONDITION_FAILED; break;
253 case 413: sn->error = DAV_REQUEST_ENTITY_TOO_LARGE; break;
254 case 414: sn->error = DAV_REQUEST_URL_TOO_LONG; break;
255 case 423: sn->error = DAV_LOCKED; break;
256 case 511: sn->error = DAV_NET_AUTH_REQUIRED; break;
257 }
258 } else {
259 switch(c) {
260 case CURLE_UNSUPPORTED_PROTOCOL: sn->error = DAV_UNSUPPORTED_PROTOCOL; break;
261 case CURLE_COULDNT_RESOLVE_PROXY: sn->error = DAV_COULDNT_RESOLVE_PROXY; break;
262 case CURLE_COULDNT_RESOLVE_HOST: sn->error = DAV_COULDNT_RESOLVE_HOST; break;
263 case CURLE_COULDNT_CONNECT: sn->error = DAV_COULDNT_CONNECT; break;
264 case CURLE_OPERATION_TIMEDOUT: sn->error = DAV_TIMEOUT; break;
265 case CURLE_SSL_CONNECT_ERROR:
266 case CURLE_PEER_FAILED_VERIFICATION:
267 case CURLE_SSL_ENGINE_NOTFOUND:
268 case CURLE_SSL_ENGINE_SETFAILED:
269 case CURLE_SSL_CERTPROBLEM:
270 case CURLE_SSL_CIPHER:
271 //#ifndef CURLE_SSL_CACERT
272 // case CURLE_SSL_CACERT:
273 //#endif
274 case CURLE_SSL_CACERT_BADFILE:
275 case CURLE_SSL_SHUTDOWN_FAILED:
276 case CURLE_SSL_CRL_BADFILE:
277 case CURLE_SSL_ISSUER_ERROR: sn->error = DAV_SSL_ERROR; break;
278 default: sn->error = DAV_ERROR; break;
279 }
280 }
281 if(c != CURLE_OK) {
282 dav_session_set_errstr(sn, curl_easy_strerror(c));
283 } else {
284 dav_session_set_errstr(sn, NULL);
285 }
286 }
287
288 void dav_session_set_errstr(DavSession *sn, const char *str) {
289 if(sn->errorstr) {
290 dav_session_free(sn, sn->errorstr);
291 }
292 char *errstr = NULL;
293 if(str) {
294 errstr = dav_session_strdup(sn, str);
295 }
296 sn->errorstr = errstr;
297 }
298
299 void dav_session_destroy(DavSession *sn) {
300 // remove session from context
301 CxList *sessions = sn->context->sessions;
302 ssize_t i = cxListFind(sessions, sn);
303 if(i >= 0) {
304 cxListRemove(sessions, i);
305 } else {
306 printf("Error: session not found in ctx->sessions\n");
307 dav_session_destructor(sn);
308 }
309 }
310
311 void dav_session_destructor(DavSession *sn) {
312 cxMempoolDestroy(sn->mp);
313 curl_easy_cleanup(sn->handle);
314 free(sn);
315 }
316
317
318 void* dav_session_malloc(DavSession *sn, size_t size) {
319 return cxMalloc(sn->mp->allocator, size);
320 }
321
322 void* dav_session_calloc(DavSession *sn, size_t nelm, size_t size) {
323 return cxCalloc(sn->mp->allocator, nelm, size);
324 }
325
326 void* dav_session_realloc(DavSession *sn, void *ptr, size_t size) {
327 return cxRealloc(sn->mp->allocator, ptr, size);
328 }
329
330 void dav_session_free(DavSession *sn, void *ptr) {
331 cxFree(sn->mp->allocator, ptr);
332 }
333
334 char* dav_session_strdup(DavSession *sn, const char *str) {
335 return cx_strdup_a(sn->mp->allocator, cx_str((char*)str)).ptr;
336 }
337
338
339 char* dav_session_create_plain_href(DavSession *sn, const char *path) {
340 if(!DAV_ENCRYPT_NAME(sn) && !DAV_DECRYPT_NAME(sn)) {
341 // non encrypted file names
342 char *url = util_path_to_url(sn, path);
343 char *href = dav_session_strdup(sn, util_url_path(url));
344 free(url);
345 return href;
346 } else {
347 return NULL;
348 }
349 }
350
351 char* dav_session_get_href(DavSession *sn, const char *path) {
352 if(DAV_DECRYPT_NAME(sn) || DAV_ENCRYPT_NAME(sn)) {
353 cxstring p = cx_str(path);
354 CxBuffer href;
355 CxBuffer pbuf;
356 cxBufferInit(&href, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
357 cxBufferInit(&pbuf, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
358
359 int start = 0;
360 int begin = 0;
361
362 // check path cache
363 char *cp = strdup(path);
364 //printf("cp: %s\n", cp);
365 while(strlen(cp) > 1) {
366 char *cached = cxMapGet(sn->pathcache, cx_hash_key_str(cp));
367 if(cached) {
368 start = strlen(cp);
369 begin = start;
370 cxBufferPutString(&href, cached);
371 break;
372 } else {
373 // check, if the parent path is cached
374 char *f = cp;
375 cp = util_parent_path(cp);
376 free(f);
377 }
378 }
379 free(cp);
380 if(href.pos == 0) {
381 // if there are no cached elements we have to add the base url path
382 // to the href buffer
383 cxBufferPutString(&href, util_url_path(sn->base_url));
384 }
385
386 // create resource for name lookup
387 cxmutstr rp = cx_strdup(cx_strn(path, start));
388 DavResource *root = dav_resource_new(sn, rp.ptr);
389 free(rp.ptr);
390 resource_set_href(root, cx_strn(href.space, href.pos));
391
392 // create request buffer for propfind requests
393 CxBuffer *rqbuf = create_basic_propfind_request();
394
395 cxstring remaining = cx_strsubs(p, start);
396 CxStrtokCtx elms = cx_strtok(remaining, CX_STR("/"), INT_MAX);
397 DavResource *res = root;
398 cxBufferPutString(&pbuf, res->path);
399 // iterate over all remaining path elements
400 cxstring elm;
401 while(cx_strtok_next(&elms, &elm)) {
402 if(elm.length > 0) {
403 //printf("elm: %.*s\n", elm.length, elm.ptr);
404 DavResource *child = dav_find_child(sn, res, rqbuf, elm.ptr);
405
406 // if necessary add a path separator
407 if(pbuf.space[pbuf.pos-1] != '/') {
408 if(href.space[href.pos-1] != '/') {
409 cxBufferPut(&href, '/');
410 }
411 cxBufferPut(&pbuf, '/');
412 }
413 // add last path/href to the cache
414 cxstring pp = cx_strn(pbuf.space, pbuf.size);
415 cxstring hh = cx_strn(href.space, href.size);
416 dav_session_cache_path(sn, pp, hh);
417
418 cxBufferWrite(elm.ptr, 1, elm.length, &pbuf);
419 if(child) {
420 // href is already URL encoded, so don't encode again
421 cxBufferPutString(&href, util_resource_name(child->href));
422 res = child;
423 } else if(DAV_ENCRYPT_NAME(sn)) {
424 char *random_name = util_random_str();
425 cxBufferPutString(&href, random_name);
426 free(random_name);
427 } else {
428 // path is not URL encoded, so we have to do this here
429 cxstring resname = cx_str(util_resource_name((const char*)path));
430 // the name of collections ends with
431 // a trailing slash, which MUST NOT be encoded
432 if(resname.ptr[resname.length-1] == '/') {
433 char *esc = curl_easy_escape(sn->handle,
434 resname.ptr, resname.length-1);
435 cxBufferWrite(esc, 1, strlen(esc), &href);
436 cxBufferPut(&href, '/');
437 curl_free(esc);
438 } else {
439 char *esc = curl_easy_escape(sn->handle,
440 resname.ptr, resname.length);
441 cxBufferWrite(esc, 1, strlen(esc), &href);
442 curl_free(esc);
443 }
444 }
445 }
446 }
447
448 // if necessary add a path separator
449 if(p.ptr[p.length-1] == '/') {
450 if(href.space[href.pos-1] != '/') {
451 cxBufferPut(&href, '/');
452 }
453 cxBufferPut(&pbuf, '/');
454 }
455 // add the final path to the cache
456 cxstring pp = cx_strn(pbuf.space, pbuf.size);
457 cxstring hh = cx_strn(href.space, href.size);
458 dav_session_cache_path(sn, pp, hh);
459
460 cxmutstr href_str = cx_strdup_a(
461 sn->mp->allocator,
462 cx_strn(href.space, href.size));
463
464 // cleanup
465 dav_resource_free_all(root);
466 cxBufferFree(rqbuf);
467
468 cxBufferDestroy(&pbuf);
469 cxBufferDestroy(&href);
470
471 return href_str.ptr;
472 } else {
473 return dav_session_create_plain_href(sn, path);
474 }
475 }
476
477 DavResource* dav_find_child(DavSession *sn, DavResource *res, CxBuffer *rqbuf, const char *name) {
478 if(res && !dav_propfind(sn, res, rqbuf)) {
479 DavResource *child = res->children;
480 while(child) {
481 if(!strcmp(child->name, name)) {
482 return child;
483 }
484 child = child->next;
485 }
486 }
487 return NULL;
488 }
489
490 void dav_session_cache_path(DavSession *sn, cxstring path, cxstring href) {
491 CxHashKey path_key = cx_hash_key(path.ptr, path.length);
492 char *elm = cxMapGet(sn->pathcache, path_key);
493 if(!elm) {
494 cxmutstr href_s = cx_strdup_a(sn->mp->allocator, href);
495 cxMapPut(sn->pathcache, path_key, href_s.ptr);
496 }
497 }
498
499
500 DavLock* dav_create_lock(DavSession *sn, const char *token, char *timeout) {
501 DavLock *lock = dav_session_malloc(sn, sizeof(DavLock));
502 lock->path = NULL;
503 lock->token = dav_session_strdup(sn, token);
504
505 // TODO: timeout
506
507 return lock;
508 }
509
510 void dav_destroy_lock(DavSession *sn, DavLock *lock) {
511 dav_session_free(sn, lock->token);
512 if(lock->path) {
513 dav_session_free(sn, lock->path);
514 }
515 dav_session_free(sn, lock);
516 }
517
518
519 static int dav_lock_cmp(void const *left, void const *right) {
520 const DavLock *l = left;
521 const DavLock *r = right;
522 return strcmp(l->path, r->path);
523 }
524
525 static int create_lock_manager(DavSession *sn) {
526 // create lock manager
527 DavLockManager *locks = cxMalloc(sn->mp->allocator, sizeof(DavLockManager));
528 locks->resource_locks = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, 16);
529 locks->collection_locks = cxLinkedListCreate(sn->mp->allocator, dav_lock_cmp, CX_STORE_POINTERS);
530 sn->locks = locks;
531 return 0;
532 }
533
534 static DavLockManager* get_lock_manager(DavSession *sn) {
535 DavLockManager *locks = sn->locks;
536 if(!locks) {
537 if(create_lock_manager(sn)) {
538 return NULL;
539 }
540 locks = sn->locks;
541 }
542 return locks;
543 }
544
545 int dav_add_resource_lock(DavSession *sn, const char *path, DavLock *lock) {
546 DavLockManager *locks = get_lock_manager(sn);
547 if(!locks) {
548 return -1;
549 }
550
551 CxHashKey path_key = cx_hash_key_str(path);
552 if(cxMapGet(locks->resource_locks, path_key)) {
553 return -1;
554 }
555
556 cxMapPut(locks->resource_locks, path_key, lock);
557 return 0;
558 }
559
560 int dav_add_collection_lock(DavSession *sn, const char *path, DavLock *lock) {
561 DavLockManager *locks = get_lock_manager(sn);
562 if(!locks) {
563 return -1;
564 }
565
566 lock->path = dav_session_strdup(sn, path);
567 cxListAdd(locks->collection_locks, lock);
568 cxListSort(locks->collection_locks);
569
570 return 0;
571 }
572
573 DavLock* dav_get_lock(DavSession *sn, const char *path) {
574 DavLockManager *locks = get_lock_manager(sn);
575 if(!locks) {
576 return NULL;
577 }
578
579 cxstring p = cx_str(path);
580
581 DavLock *lock = cxMapGet(locks->resource_locks, cx_hash_key(p.ptr, p.length));
582 if(lock) {
583 return lock;
584 }
585
586 CxIterator i = cxListIterator(locks->collection_locks);
587 cx_foreach(DavLock*, col_lock, i) {
588 int cmd = strcmp(path, col_lock->path);
589 if(cmd == 0) {
590 return col_lock;
591 } else if(cx_strprefix(p, cx_str(col_lock->path))) {
592 return col_lock;
593 } else if(cmd > 0) {
594 break;
595 }
596 }
597
598 return NULL;
599 }
600
601 void dav_remove_lock(DavSession *sn, const char *path, DavLock *lock) {
602 DavLockManager *locks = get_lock_manager(sn);
603 if(!locks) {
604 return;
605 }
606
607 if(cxMapRemoveAndGet(locks->resource_locks, cx_hash_key_str(path))) {
608 return;
609 }
610
611 CxMutIterator i = cxListMutIterator(locks->collection_locks);
612 int rm = 0;
613 cx_foreach(DavLock* , cl, i) {
614 if(rm) {
615 break;
616 }
617 if(cl == lock) {
618 cxIteratorFlagRemoval(i);
619 rm = 1;
620 }
621 }
622 }

mercurial