|
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 #include <libxml/tree.h> |
|
33 |
|
34 #include "utils.h" |
|
35 #include "webdav.h" |
|
36 #include "session.h" |
|
37 #include "methods.h" |
|
38 #include <cx/buffer.h> |
|
39 #include <cx/utils.h> |
|
40 #include <cx/linked_list.h> |
|
41 #include <cx/hash_map.h> |
|
42 #include <cx/compare.h> |
|
43 #include "davqlparser.h" |
|
44 #include "davqlexec.h" |
|
45 |
|
46 |
|
47 DavContext* dav_context_new(void) { |
|
48 // initialize |
|
49 DavContext *context = calloc(1, sizeof(DavContext)); |
|
50 if(!context) { |
|
51 return NULL; |
|
52 } |
|
53 context->sessions = cxLinkedListCreate(cxDefaultAllocator, cx_cmp_intptr, CX_STORE_POINTERS); |
|
54 context->sessions->destructor_data = dav_session_destructor; |
|
55 context->http_proxy = calloc(1, sizeof(DavProxy)); |
|
56 if(!context->http_proxy) { |
|
57 dav_context_destroy(context); |
|
58 return NULL; |
|
59 } |
|
60 context->https_proxy = calloc(1, sizeof(DavProxy)); |
|
61 if(!context->https_proxy) { |
|
62 dav_context_destroy(context); |
|
63 return NULL; |
|
64 } |
|
65 context->namespaces = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); |
|
66 if(!context->namespaces) { |
|
67 dav_context_destroy(context); |
|
68 return NULL; |
|
69 } |
|
70 context->namespaceinfo = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); |
|
71 if(!context->namespaceinfo) { |
|
72 dav_context_destroy(context); |
|
73 } |
|
74 context->keys = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); |
|
75 if(!context->keys) { |
|
76 dav_context_destroy(context); |
|
77 return NULL; |
|
78 } |
|
79 |
|
80 // add DAV: namespace |
|
81 if(dav_add_namespace(context, "D", "DAV:")) { |
|
82 dav_context_destroy(context); |
|
83 return NULL; |
|
84 } |
|
85 |
|
86 |
|
87 // add idav namespace |
|
88 if(dav_add_namespace(context, "idav", DAV_NS)) { |
|
89 dav_context_destroy(context); |
|
90 return NULL; |
|
91 } |
|
92 |
|
93 // add idavprops namespace |
|
94 if(dav_add_namespace(context, "idavprops", DAV_PROPS_NS)) { |
|
95 dav_context_destroy(context); |
|
96 return NULL; |
|
97 } |
|
98 |
|
99 return context; |
|
100 } |
|
101 |
|
102 void dav_context_destroy(DavContext *ctx) { |
|
103 // destroy all sessions assoziated with this context |
|
104 // ctx->sessions destructor must be dav_session_destructor |
|
105 cxListDestroy(ctx->sessions); |
|
106 |
|
107 if(ctx->http_proxy) { |
|
108 free(ctx->http_proxy); |
|
109 } |
|
110 if(ctx->https_proxy) { |
|
111 free(ctx->https_proxy); |
|
112 } |
|
113 |
|
114 if(ctx->namespaces) { |
|
115 CxIterator i = cxMapIteratorValues(ctx->namespaces); |
|
116 cx_foreach(DavNamespace*, ns, i) { |
|
117 if(!ns) continue; |
|
118 if(ns->prefix) { |
|
119 free(ns->prefix); |
|
120 } |
|
121 if(ns->name) { |
|
122 free(ns->name); |
|
123 } |
|
124 free(ns); |
|
125 } |
|
126 cxMapDestroy(ctx->namespaces); |
|
127 } |
|
128 if(ctx->namespaceinfo) { |
|
129 // TODO: implement |
|
130 } |
|
131 if(ctx->keys) { |
|
132 CxIterator i = cxMapIteratorValues(ctx->keys); |
|
133 cx_foreach(DavKey*, key, i) { |
|
134 if(!key) continue; |
|
135 if(key->name) { |
|
136 free(key->name); |
|
137 } |
|
138 if(key->data) { |
|
139 free(key->data); |
|
140 } |
|
141 free(key); |
|
142 } |
|
143 cxMapDestroy(ctx->keys); |
|
144 } |
|
145 |
|
146 free(ctx); |
|
147 } |
|
148 |
|
149 void dav_context_add_key(DavContext *context, DavKey *key) { |
|
150 cxMapPut(context->keys, cx_hash_key_str(key->name), key); |
|
151 } |
|
152 |
|
153 DavKey* dav_context_get_key(DavContext *context, const char *name) { |
|
154 if(name) { |
|
155 return cxMapGet(context->keys, cx_hash_key_str(name)); |
|
156 } |
|
157 return NULL; |
|
158 } |
|
159 |
|
160 int dav_add_namespace(DavContext *context, const char *prefix, const char *name) { |
|
161 DavNamespace *namespace = malloc(sizeof(DavNamespace)); |
|
162 if(!namespace) { |
|
163 return 1; |
|
164 } |
|
165 |
|
166 char *p = strdup(prefix); |
|
167 char *n = strdup(name); |
|
168 |
|
169 int err = 0; |
|
170 if(p && n) { |
|
171 namespace->prefix = p; |
|
172 namespace->name = n; |
|
173 err = cxMapPut(context->namespaces, cx_hash_key_str(prefix), namespace); |
|
174 } |
|
175 |
|
176 if(err) { |
|
177 free(namespace); |
|
178 if(p) free(p); |
|
179 if(n) free(n); |
|
180 } |
|
181 |
|
182 return err; |
|
183 } |
|
184 |
|
185 DavNamespace* dav_get_namespace(DavContext *context, const char *prefix) { |
|
186 return cxMapGet(context->namespaces, cx_hash_key_str(prefix)); |
|
187 } |
|
188 |
|
189 DavNamespace* dav_get_namespace_s(DavContext *context, cxstring prefix) { |
|
190 return cxMapGet(context->namespaces, cx_hash_key(prefix.ptr, prefix.length)); |
|
191 } |
|
192 |
|
193 int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt) { |
|
194 CxHashKey hkey = cx_hash_key_str(ns); |
|
195 DavNSInfo *info = cxMapGet(context->namespaceinfo, hkey); |
|
196 if(!info) { |
|
197 info = calloc(1, sizeof(DavNSInfo)); |
|
198 info->encrypt = encrypt; |
|
199 cxMapPut(context->namespaceinfo, hkey, info); |
|
200 } else { |
|
201 info->encrypt = encrypt; |
|
202 } |
|
203 return 0; |
|
204 } |
|
205 |
|
206 int dav_namespace_is_encrypted(DavContext *context, const char *ns) { |
|
207 DavNSInfo *info = cxMapGet(context->namespaceinfo, cx_hash_key_str(ns)); |
|
208 if(info) { |
|
209 return info->encrypt; |
|
210 } |
|
211 return 0; |
|
212 } |
|
213 |
|
214 void dav_get_property_namespace_str( |
|
215 DavContext *ctx, |
|
216 char *prefixed_name, |
|
217 char **ns, |
|
218 char **name) |
|
219 { |
|
220 // TODO: rewrite using dav_get_property_ns |
|
221 |
|
222 char *pname = strchr(prefixed_name, ':'); |
|
223 char *pns = "DAV:"; |
|
224 if(pname) { |
|
225 DavNamespace *ns = dav_get_namespace_s( |
|
226 ctx, |
|
227 cx_strn(prefixed_name, pname-prefixed_name)); |
|
228 if(ns) { |
|
229 pns = ns->name; |
|
230 pname++; |
|
231 } else { |
|
232 pns = NULL; |
|
233 pname = NULL; |
|
234 } |
|
235 } else { |
|
236 pname = prefixed_name; |
|
237 } |
|
238 *ns = pns; |
|
239 *name = pname; |
|
240 } |
|
241 |
|
242 DavNamespace* dav_get_property_namespace( |
|
243 DavContext *ctx, |
|
244 char *prefixed_name, |
|
245 char **name) |
|
246 { |
|
247 char *pname = strchr(prefixed_name, ':'); |
|
248 if(pname) { |
|
249 DavNamespace *ns = dav_get_namespace_s( |
|
250 ctx, |
|
251 cx_strn(prefixed_name, pname-prefixed_name)); |
|
252 if(ns) { |
|
253 *name = pname +1; |
|
254 return ns; |
|
255 } else { |
|
256 *name = NULL; |
|
257 return NULL; |
|
258 } |
|
259 } else { |
|
260 *name = prefixed_name; |
|
261 return dav_get_namespace_s(ctx, cx_str("D")); |
|
262 } |
|
263 } |
|
264 |
|
265 // TODO: add sstr_t version of dav_get_property_ns |
|
266 |
|
267 void dav_set_effective_href(DavSession *sn, DavResource *resource) { |
|
268 char *eff_url; |
|
269 curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &eff_url); |
|
270 if(eff_url) { |
|
271 const char *href = util_url_path(eff_url); |
|
272 if(strcmp(href, resource->href)) { |
|
273 dav_session_free(sn, resource->href); |
|
274 resource->href = dav_session_strdup(sn, href); |
|
275 } |
|
276 } |
|
277 } |
|
278 |
|
279 DavResource* dav_get(DavSession *sn, char *path, const char *properties) { |
|
280 CURL *handle = sn->handle; |
|
281 DavResource *resource = dav_resource_new(sn, path); |
|
282 util_set_url(sn, dav_resource_get_href(resource)); |
|
283 |
|
284 CxList *proplist = NULL; |
|
285 if(properties) { |
|
286 proplist = parse_properties_string(sn->context, cx_str(properties)); |
|
287 } |
|
288 CxBuffer *rqbuf = create_propfind_request(sn, proplist, "propfind", 0); |
|
289 CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); |
|
290 |
|
291 //fwrite(rqbuf->space, 1, rqbuf->size, stdout); |
|
292 //printf("\n"); |
|
293 |
|
294 CURLcode ret = do_propfind_request(sn, rqbuf, rpbuf); |
|
295 long status = 0; |
|
296 curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status); |
|
297 if(ret == CURLE_OK && status == 207) { |
|
298 dav_set_effective_href(sn, resource); |
|
299 |
|
300 //printf("response\n%s\n", rpbuf->space); |
|
301 // TODO: use PropfindParser |
|
302 resource = parse_propfind_response(sn, resource, rpbuf); |
|
303 resource->exists = 1; |
|
304 sn->error = DAV_OK; |
|
305 } else { |
|
306 dav_session_set_error(sn, ret, status); |
|
307 dav_resource_free(resource); |
|
308 resource = NULL; |
|
309 } |
|
310 |
|
311 cxBufferFree(rqbuf); |
|
312 cxBufferFree(rpbuf); |
|
313 |
|
314 if(proplist) { |
|
315 CxIterator i = cxListIterator(proplist); |
|
316 cx_foreach(DavProperty*, p, i) { |
|
317 free(p->name); |
|
318 } |
|
319 cxListDestroy(proplist); |
|
320 } |
|
321 |
|
322 return resource; |
|
323 } |
|
324 |
|
325 |
|
326 int dav_propfind(DavSession *sn, DavResource *root, CxBuffer *rqbuf) { |
|
327 // clean resource properties |
|
328 DavResourceData *data = root->data; |
|
329 cxMapClear(data->properties); // TODO: free existing content |
|
330 |
|
331 CURL *handle = sn->handle; |
|
332 util_set_url(sn, dav_resource_get_href(root)); |
|
333 |
|
334 CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); |
|
335 DavResource *resource = root; |
|
336 CURLcode ret = do_propfind_request(sn, rqbuf, rpbuf); |
|
337 long status = 0; |
|
338 long error = 0; |
|
339 curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status); |
|
340 if(ret == CURLE_OK && status == 207) { |
|
341 //printf("response\n%s\n", rpbuf->space); |
|
342 dav_set_effective_href(sn, resource); |
|
343 // TODO: use PropfindParser |
|
344 resource = parse_propfind_response(sn, resource, rpbuf); |
|
345 sn->error = DAV_OK; |
|
346 root->exists = 1; |
|
347 } else { |
|
348 dav_session_set_error(sn, ret, status); |
|
349 error = 1; |
|
350 } |
|
351 cxBufferFree(rpbuf); |
|
352 return error; |
|
353 } |
|
354 |
|
355 CxList* parse_properties_string(DavContext *context, cxstring str) { |
|
356 CxList *proplist = cxLinkedListCreateSimple(sizeof(DavProperty)); |
|
357 |
|
358 CxStrtokCtx tok = cx_strtok(str, cx_str(","), INT_MAX); |
|
359 cxstring s; |
|
360 while(cx_strtok_next(&tok, &s)) { |
|
361 cxstring nsname = cx_strchr(s, ':'); |
|
362 if(nsname.length > 0) { |
|
363 cxstring nspre = cx_strsubsl(s, 0, nsname.ptr - s.ptr); |
|
364 nsname.ptr++; |
|
365 nsname.length--; |
|
366 |
|
367 DavProperty dp; |
|
368 cxstring pre = cx_strtrim(nspre); |
|
369 dp.ns = dav_get_namespace_s(context, pre); |
|
370 dp.name = cx_strdup(nsname).ptr; |
|
371 dp.value = NULL; |
|
372 if(dp.ns && dp.name) { |
|
373 cxListAdd(proplist, &dp); |
|
374 } else { |
|
375 free(dp.name); |
|
376 } |
|
377 } |
|
378 } |
|
379 |
|
380 return proplist; |
|
381 } |
|
382 |
|
383 DavResource* dav_query(DavSession *sn, char *query, ...) { |
|
384 DavQLStatement *stmt = dav_parse_statement(cx_str(query)); |
|
385 if(!stmt) { |
|
386 sn->error = DAV_ERROR; |
|
387 return NULL; |
|
388 } |
|
389 if(stmt->errorcode != 0) { |
|
390 sn->error = DAV_QL_ERROR; |
|
391 dav_free_statement(stmt); |
|
392 return NULL; |
|
393 } |
|
394 |
|
395 va_list ap; |
|
396 va_start(ap, query); |
|
397 DavResult result = dav_statement_execv(sn, stmt, ap); |
|
398 va_end(ap); |
|
399 |
|
400 dav_free_statement(stmt); |
|
401 |
|
402 if(result.status == -1) { |
|
403 if(result.result) { |
|
404 dav_resource_free(result.result); |
|
405 result.result = NULL; |
|
406 } |
|
407 } |
|
408 |
|
409 return result.result; |
|
410 } |
|
411 |
|
412 |
|
413 |
|
414 |
|
415 void dav_verbose_log( |
|
416 DavSession *sn, |
|
417 const char *method, |
|
418 const char *url, |
|
419 const char *request_body, |
|
420 size_t request_bodylen, |
|
421 int status, |
|
422 const char *response_body, |
|
423 size_t response_bodylen) |
|
424 { |
|
425 fprintf(stderr, "# method: %s url: %s status: %d\n", method, url, status); |
|
426 fprintf(stderr, "# request len: %d\n%.*s\n", (int)request_bodylen, (int)request_bodylen, request_body); |
|
427 fprintf(stderr, "# response len: %d\n%.*s\n", (int)response_bodylen, (int)response_bodylen, response_body); |
|
428 } |