add existing code (build system, libs, initial mizucp code)
[mizunara.git] / libidav / session.c
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 <ucx/buffer.h>
34 #include <ucx/utils.h>
35
36 #include "utils.h"
37 #include "session.h"
38 #include "resource.h"
39 #include "methods.h"
40
41 DavSession* dav_session_new(DavContext *context, char *base_url) {
42     if(!base_url) {
43         return NULL;
44     }
45     sstr_t url = sstr(base_url);
46     if(url.length == 0) {
47         return NULL;
48     }
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);
53     sn->key = NULL;
54     sn->errorstr = NULL;
55     sn->error = DAV_OK;
56     sn->flags = 0;
57     
58     dav_session_set_baseurl(sn, base_url);
59     
60     sn->handle = curl_easy_init();
61     curl_easy_setopt(sn->handle, CURLOPT_FOLLOWLOCATION, 1L);
62     
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;
67     sn->locks = locks;
68
69     // set proxy
70     DavProxy *proxy = sstrprefix(url, S("https")) ? context->https_proxy
71                                                   : context->http_proxy;
72     
73     if (proxy->url) {
74         curl_easy_setopt(sn->handle, CURLOPT_PROXY, proxy->url);
75         if (proxy->username) {
76             curl_easy_setopt(sn->handle, CURLOPT_PROXYUSERNAME,
77                 proxy->username);
78             if (proxy->password) {
79                 curl_easy_setopt(sn->handle, CURLOPT_PROXYPASSWORD,
80                     proxy->password);
81             } else {
82                 // TODO: prompt
83             }
84         }
85         if(proxy->no_proxy) {
86             curl_easy_setopt(sn->handle, CURLOPT_NOPROXY,
87                 proxy->no_proxy);
88         }
89     }
90     
91     // set url
92 #if LIBCURL_VERSION_NUM >= 0x072D00
93     curl_easy_setopt(sn->handle, CURLOPT_DEFAULT_PROTOCOL, "http");
94 #endif
95     curl_easy_setopt(sn->handle, CURLOPT_URL, base_url);
96     
97     // add to context
98     context->sessions = ucx_list_append(context->sessions, sn);
99     sn->context = context;
100     
101     return sn;
102 }
103
104 DavSession* dav_session_new_auth(
105         DavContext *context,
106         char *base_url,
107         char *user,
108         char *password)
109 {
110     DavSession *sn = dav_session_new(context, base_url);
111     if(!sn) {
112         return NULL;
113     }
114     dav_session_set_auth(sn, user, password);
115     return sn;
116 }
117
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);
126         free(upwdbuf);
127     }
128 }
129
130 void dav_session_set_baseurl(DavSession *sn, char *base_url) {
131     if(sn->base_url) {
132         ucx_mempool_free(sn->mp, sn->base_url);
133     }
134     
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;
139     } else {
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;
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, UcxBuffer *request, UcxBuffer *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 && http_status == 401 && sn->auth_prompt) {
178         if(!sn->auth_prompt(sn, sn->authprompt_userdata)) {
179             if(request) {
180                 ucx_buffer_seek(request, 0, SEEK_SET);
181             }
182             if(response) {
183                 ucx_buffer_seek(response, 0, SEEK_SET);
184             }
185             ret = curl_easy_perform(sn->handle);
186             curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status);
187         }
188     }
189     if(status) {
190         *status = http_status;
191     }
192     return ret;
193 }
194
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);
200     }
201     return 0;
202 }
203
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);
209     }
210     return 0;
211 }
212
213 void dav_session_set_error(DavSession *sn, CURLcode c, int status) {
214     if(status > 0) {
215         switch(status) {
216             default: {
217                 switch(c) {
218                     default: sn->error = DAV_ERROR;
219                 }
220                 break;
221             }
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;
233         }
234     } else {
235         switch(c) {
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:
249 #endif
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;
255         }
256     }
257     if(c != CURLE_OK) {
258         dav_session_set_errstr(sn, curl_easy_strerror(c));
259     } else {
260         dav_session_set_errstr(sn, NULL);
261     }
262 }
263
264 void dav_session_set_errstr(DavSession *sn, const char *str) {
265     if(sn->errorstr) {
266         dav_session_free(sn, sn->errorstr);
267     }
268     char *errstr = NULL;
269     if(str) {
270         errstr = dav_session_strdup(sn, str);
271     }
272     sn->errorstr = errstr;
273 }
274
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);
279     if(i >= 0)  {
280         UcxList *elm = ucx_list_get(sessions, i);
281         if(elm) {
282             sn->context->sessions = ucx_list_remove(sessions, elm);
283         }
284     }
285     
286     ucx_mempool_destroy(sn->mp);
287     curl_easy_cleanup(sn->handle);
288     free(sn);
289 }
290
291
292 void* dav_session_malloc(DavSession *sn, size_t size) {
293     return ucx_mempool_malloc(sn->mp, size);
294 }
295
296 void* dav_session_calloc(DavSession *sn, size_t nelm, size_t size) {
297     return ucx_mempool_calloc(sn->mp, nelm, size);
298 }
299
300 void* dav_session_realloc(DavSession *sn, void *ptr, size_t size) {
301     return ucx_mempool_realloc(sn->mp, ptr, size);
302 }
303
304 void  dav_session_free(DavSession *sn, void *ptr) {
305     ucx_mempool_free(sn->mp, ptr);
306 }
307
308 char* dav_session_strdup(DavSession *sn, const char *str) {
309     return sstrdup_a(sn->mp->allocator, sstr((char*)str)).ptr;
310 }
311
312
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));
318         free(url);
319         return href;
320     } else {
321         return NULL;
322     }
323 }
324
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);
330         int start = 0;
331         int begin = 0;
332         
333         // check path cache
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);
338             if(cached) {
339                 start = strlen(cp);
340                 begin = start;
341                 ucx_buffer_puts(href, cached);
342                 break;
343             } else {
344                 // check, if the parent path is cached
345                 char *f = cp;
346                 cp = util_parent_path(cp);
347                 free(f);
348             }
349         }
350         free(cp);
351         if(href->pos == 0) {
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));
355         }
356         
357         // create resource for name lookup
358         sstr_t rp = sstrdup(sstrn(path, start));
359         DavResource *root = dav_resource_new(sn, rp.ptr);
360         free(rp.ptr);
361         resource_set_href(root, sstrn(href->space, href->pos));
362         
363         // create request buffer for propfind requests
364         UcxBuffer *rqbuf = create_basic_propfind_request();
365         
366         sstr_t remaining = sstrsubs(p, start);
367         ssize_t nelm = 0;
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];
374             if(elm.length > 0) {
375                 //printf("elm: %.*s\n", elm.length, elm.ptr);
376                 DavResource *child = dav_find_child(sn, res, rqbuf, elm.ptr);
377                 
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, '/');
382                     }
383                     ucx_buffer_putc(pbuf, '/');
384                 }
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);
389                 
390                 ucx_buffer_write(elm.ptr, 1, elm.length, pbuf);
391                 if(child) {
392                     // href is already URL encoded, so don't encode again
393                     ucx_buffer_puts(href, util_resource_name(child->href));
394                     res = child;
395                 } else if(DAV_ENCRYPT_NAME(sn)) {
396                     char *random_name = util_random_str();
397                     ucx_buffer_puts(href, random_name);
398                     free(random_name);
399                 } else {
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, '/');
409                         curl_free(esc);
410                     } else  {
411                         char *esc = curl_easy_escape(sn->handle,
412                                 resname.ptr, resname.length);
413                         ucx_buffer_write(esc, 1, strlen(esc), href);
414                         curl_free(esc);
415                     }
416                 }
417             }
418             
419             // cleanup
420             free(elm.ptr);
421         }
422         free(elms);
423         
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, '/');
428             }
429             ucx_buffer_putc(pbuf, '/');
430         }
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);
435         
436         sstr_t href_str = sstrdup_a(
437                 sn->mp->allocator,
438                 sstrn(href->space,
439                 href->size));
440         
441         // cleanup
442         dav_resource_free_all(root);
443         ucx_buffer_free(rqbuf);
444         ucx_buffer_free(pbuf);
445         ucx_buffer_free(href);
446         
447         return href_str.ptr;
448     } else {
449         return dav_session_create_plain_href(sn, path);
450     }
451 }
452
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;
456         while(child) {
457             if(!strcmp(child->name, name)) {
458                 return child;
459             }
460             child = child->next;
461         }
462     }
463     return NULL;
464 }
465
466 void dav_session_cache_path(DavSession *sn, sstr_t path, sstr_t href) {
467     char *elm = ucx_map_sstr_get(sn->pathcache, path);
468     if(!elm) {
469         href = sstrdup_a(sn->mp->allocator, href);
470         ucx_map_sstr_put(sn->pathcache, path, href.ptr);
471     }
472 }
473
474
475 DavLock* dav_create_lock(DavSession *sn, char *token, char *timeout) {
476     DavLock *lock = dav_session_malloc(sn, sizeof(DavLock));
477     lock->path = NULL;
478     lock->token = dav_session_strdup(sn, token);
479     
480     // TODO: timeout
481     
482     return lock;
483 }
484
485 void dav_destroy_lock(DavSession *sn, DavLock *lock) {
486     dav_session_free(sn, lock->token);
487     if(lock->path) {
488         dav_session_free(sn, lock->path);
489     }
490     dav_session_free(sn, lock);
491 }
492
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)) {
496         return -1;
497     }
498     
499     ucx_map_cstr_put(locks->resource_locks, path, lock);
500     return 0;
501 }
502
503 static void insert_lock(DavSession *sn, UcxList *elm, UcxList *newelm) {
504     UcxList *next = elm->next;
505     if(next) {
506         next->prev = newelm;
507         newelm->next = next;
508     }
509     newelm->prev = elm;
510     elm->next = newelm;
511 }
512
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(
517                 sn->mp->allocator,
518                 NULL,
519                 lock);
520         lock->path = dav_session_strdup(sn, path);
521         return 0;
522     }
523     
524     UcxList *elm = locks->collection_locks;
525     for(;;) {
526         DavLock *l = elm->data;
527         int cmp = strcmp(path, l->path);
528         if(cmp > 0) {
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) {
533             return -1;
534         }
535         
536         if(elm->next) {
537             elm = elm->next;
538         } else {
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);
542             break;
543         }
544     }
545     
546     return 0;
547 }
548
549 DavLock* dav_get_lock(DavSession *sn, char *path) {
550     DavLockManager *locks = sn->locks;
551     
552     DavLock *lock = ucx_map_cstr_get(locks->resource_locks, path);
553     if(lock) {
554         return lock;
555     }
556     
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);
561         if(cmd == 0) {
562             return cl;
563         } else if(sstrprefix(p, sstr(cl->path)))  {
564             return cl;
565         } else if(cmd > 0) {
566             break;
567         }
568     }
569     
570     return NULL;
571 }
572
573 void dav_remove_lock(DavSession *sn, char *path, DavLock *lock) {
574     DavLockManager *locks = sn->locks;
575     
576     if(ucx_map_cstr_remove(locks->resource_locks, path)) {
577         return;
578     }
579     
580     UcxList *rm = NULL;
581     UCX_FOREACH(elm, locks->collection_locks) {
582         DavLock *cl = elm->data;
583         if(cl == lock) {
584             rm = elm;
585             break;
586         }
587     }
588     
589     if(rm) {
590         locks->collection_locks = ucx_list_remove_a(
591                 sn->mp->allocator,
592                 locks->collection_locks,
593                 rm);
594     }
595 }