UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2024 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 "davcontroller.h" 30 #include "window.h" 31 32 #include <cx/printf.h> 33 34 #include "config.h" 35 #include "upload.h" 36 #include "download.h" 37 38 #include "system.h" 39 #include "common/context.h" 40 41 #include <libidav/config.h> 42 #include <libidav/utils.h> 43 44 DavBrowser* davbrowser_create(UiObject *toplevel) { 45 DavBrowser *doc = ui_document_new(sizeof(DavBrowser)); 46 UiContext *ctx = ui_document_context(doc); 47 CxMempool *mp = ui_cx_mempool(ctx); 48 doc->window = toplevel; 49 doc->ctx = ctx; 50 51 doc->navigation_stack = cxLinkedListCreateSimple(CX_STORE_POINTERS); 52 doc->navstack_enabled = true; 53 doc->navstack_pos = 0; 54 55 doc->dav_queue = ui_threadpool_create(1); 56 cxMempoolRegister(mp, doc->dav_queue, (cx_destructor_func)ui_threadpool_destroy); 57 58 doc->res_open_inprogress = cxHashMapCreate(ctx->allocator, CX_STORE_POINTERS, 4); 59 60 doc->path = ui_string_new(ctx, "path"); 61 doc->resources = ui_list_new(ctx, "reslist"); 62 63 return doc; 64 } 65 66 67 void davbrowser_set_collection(UiObject *ui, DavBrowser *browser, DavResource *collection) { 68 if (browser->current) { 69 dav_resource_free_all(browser->current); 70 } 71 ui_list_clear(browser->resources); 72 73 browser->current = collection; 74 browser->res_counter++; 75 for (DavResource *res = collection->children; res; res = res->next) { 76 ui_list_append(browser->resources, res); 77 } 78 79 browser->resources->update(browser->resources, 0); 80 81 ui_set_group(ui->ctx, APP_STATE_BROWSER_SESSION); 82 } 83 84 // ------------------------------ davbrowser_connect2repo ------------------------------ 85 86 typedef struct DavConnect2Repo { 87 UiObject *ui; 88 DavBrowser *browser; 89 90 DavCfgRepository *repo; 91 char *path; 92 93 UiString *password; 94 } DavConnect2Repo; 95 96 static void dialog_secretstore_decrypt(UiEvent *event, void *data) { 97 DavConnect2Repo *c2r = event->window; 98 99 if(event->intval == 4) { 100 char *pw = ui_get(c2r->password); 101 102 PwdStore *secrets = get_pwdstore(); 103 pwdstore_setpassword(secrets, pw); 104 if(pwdstore_decrypt(secrets)) { 105 ui_dialog(c2r->ui, .title = "Error", .content = "Cannot decrypt Secret Store", .closebutton_label = "OK"); 106 } else { 107 davbrowser_connect2repo(c2r->ui, c2r->browser, c2r->repo, c2r->path); 108 } 109 } 110 free(c2r->path); 111 if(!c2r->repo->node) { 112 dav_repository_free(get_config(), c2r->repo); 113 } 114 115 ui_close(event->obj); 116 } 117 118 int davbrowser_connect2repo(UiObject *ui, DavBrowser *browser, DavCfgRepository *repo, const char *path) { 119 char *user = NULL; 120 char *password = NULL; 121 char *password_free = NULL; 122 if (repo->user.value.ptr && repo->password.value.ptr) { 123 user = repo->user.value.ptr; 124 cxmutstr decodedpw = dav_repository_get_decodedpassword(repo); 125 password = decodedpw.ptr; 126 password_free = decodedpw.ptr; 127 } else { 128 PwdStore *secrets = get_pwdstore(); 129 const char *credentials = NULL; 130 if(repo->stored_user.value.ptr) { 131 if(pwdstore_has_id(secrets, repo->stored_user.value.ptr)) { 132 credentials = repo->stored_user.value.ptr; 133 } 134 } else { 135 credentials = get_location_credentials(repo, path); 136 } 137 138 if(credentials) { 139 if(!secrets->isdecrypted) { 140 UiObject *obj = ui_dialog_window(ui, 141 .title = "Authentication", 142 .lbutton1 = "Cancel", 143 .rbutton4 = "Connect", 144 .default_button = 4, 145 .show_closebutton = UI_OFF, 146 .onclick = dialog_secretstore_decrypt); 147 148 DavConnect2Repo *c2r = ui_malloc(obj->ctx, sizeof(DavConnect2Repo)); 149 c2r->ui = ui; 150 c2r->browser = browser; 151 c2r->repo = repo; 152 c2r->path = path ? strdup(path) : NULL; 153 c2r->password = ui_string_new(obj->ctx, NULL); 154 obj->window = c2r; 155 156 ui_grid(obj, .margin = 20, .columnspacing = 12, .rowspacing = 16) { 157 ui_llabel(obj, .label = "Decrypt Secret Store", .colspan = 2); 158 ui_newline(obj); 159 160 ui_llabel(obj, .label = "Password"); 161 ui_passwordfield(obj, .value = c2r->password, .hexpand = TRUE); 162 } 163 164 ui_show(obj); 165 166 return 1; 167 } 168 169 if(!get_stored_credentials(credentials, &user, &password)) { 170 fprintf(stderr, "Error: failed to get user/password for credentials %s\n", credentials); 171 } 172 } 173 } 174 175 DavSession *sn = dav_session_new(application_dav_context(), repo->url.value.ptr); 176 if (user && password) { 177 dav_session_set_auth(sn, user, password); 178 } 179 free(password_free); 180 181 sn->flags = dav_repository_get_flags(repo); 182 sn->key = dav_context_get_key(application_dav_context(), repo->default_key.value.ptr); 183 curl_easy_setopt(sn->handle, CURLOPT_SSLVERSION, repo->ssl_version); 184 if(repo->cert.value.ptr) { 185 curl_easy_setopt(sn->handle, CURLOPT_CAINFO, repo->cert.value.ptr); 186 } 187 if(!repo->verification.value) { 188 curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYPEER, 0); 189 curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYHOST, 0); 190 } 191 192 193 browser->sn = sn; 194 if (repo->name.value.length > 0) { 195 browser->repo_base = cx_strdup(cx_strn(repo->name.value.ptr, repo->name.value.length)).ptr; 196 } else { 197 browser->repo_base = cx_strdup(cx_strn(repo->url.value.ptr, repo->url.value.length)).ptr; 198 } 199 200 char *dav_path = util_concat_path(browser->repo_base, path); 201 ui_set(browser->path, dav_path); 202 free(dav_path); 203 204 SessionAuthData *auth = cxMalloc(sn->mp->allocator, sizeof(SessionAuthData)); 205 auth->obj = ui; 206 auth->cond = ui_condvar_create(); 207 auth->sn = sn; 208 auth->user = repo->user.value.ptr ? cx_strdup_a(sn->mp->allocator, cx_strcast(repo->user.value)).ptr : NULL; 209 auth->password = NULL; 210 dav_session_set_authcallback(sn, jobthr_davbrowser_auth, auth); 211 212 davbrowser_query_path(ui, browser, path); 213 214 return 0; 215 } 216 217 // ------------------------------ davbrowser_auth ------------------------------ 218 219 static int davbrowser_auth_dialog(void *data) { 220 SessionAuthData *auth = data; 221 auth_dialog(auth); 222 return 0; 223 } 224 225 void davbrowser_auth_set_user_pwd(SessionAuthData *auth, const char *user, const char *password) { 226 dav_session_free(auth->sn, auth->user); 227 dav_session_free(auth->sn, auth->password); 228 auth->user = user ? dav_session_strdup(auth->sn, user) : NULL; 229 auth->password = password ? dav_session_strdup(auth->sn, password) : NULL; 230 } 231 232 int jobthr_davbrowser_auth(DavSession *sn, void *data) { 233 SessionAuthData *auth = data; 234 235 ui_call_mainthread(davbrowser_auth_dialog, auth); 236 ui_condvar_wait(auth->cond); 237 238 if(auth->cond->intdata) { 239 dav_session_set_auth(sn, auth->user, auth->password); 240 } 241 dav_session_free(auth->sn, auth->password); 242 auth->password = NULL; 243 244 return 0; 245 } 246 247 248 // ------------------------------ davbrowser_query_path ------------------------------ 249 250 typedef struct DavBrowserQueryPath { 251 UiThreadpool *pool; 252 DavBrowser *browser; 253 char *path; 254 DavResource *result; 255 } DavBrowserQueryPath; 256 257 static int browser_query_path(void *data) { 258 DavBrowserQueryPath *query = data; 259 DavSession *sn = query->browser->sn; 260 261 DavResource *res = dav_query(sn, "select `idav:crypto-name`,`idav:crypto-key`,D:lockdiscovery from %s with depth = 1 order by iscollection desc, name", query->path); 262 query->result = res; 263 264 return 0; 265 } 266 267 static void browser_query_finished(UiEvent *event, void *data) { 268 DavBrowserQueryPath *query = data; 269 DavBrowser *browser = event->document; 270 271 if (query->pool == browser->dav_queue) { 272 if (query->result) { 273 davbrowser_set_collection(event->obj, browser, query->result); 274 } else { 275 cxmutstr error = cx_asprintf("Error %d", query->browser->sn->error); 276 ui_dialog(event->obj, .title = "Error", .content = error.ptr, .closebutton_label = "OK"); 277 } 278 279 window_progress(event->window, 0); 280 } else { 281 // operation aborted 282 if (query->result) { 283 dav_resource_free_all(query->result); 284 } 285 } 286 287 free(query->path); 288 free(query); 289 } 290 291 void davbrowser_query_path(UiObject *ui, DavBrowser *browser, const char *path) { 292 if (!browser->sn) { 293 // TODO: error 294 return; 295 } 296 297 // for comparison, we need the current base_url/repo_name + path 298 size_t len = path ? strlen(path) : 0; 299 if (len == 1 && *path == '/') { 300 path = ""; 301 } 302 303 // check if the new path is a prefix of the current path 304 // if not, we have to set the pathbar string to the new path 305 char *full_path = util_concat_path(browser->repo_base, path); 306 char *full_path_col = util_concat_path(full_path, "/"); 307 char *current_path = ui_get(browser->path); 308 cxstring cpath = cx_str(current_path); 309 cxstring newc = cx_str(full_path_col); 310 if (!cx_strprefix(cpath, newc)) { 311 ui_set(browser->path, full_path); 312 } 313 free(full_path); 314 free(full_path_col); 315 316 DavBrowserQueryPath *query = malloc(sizeof(DavBrowserQueryPath)); 317 query->pool = browser->dav_queue; 318 query->browser = browser; 319 query->path = strdup(path); 320 query->result = NULL; 321 ui_threadpool_job(browser->dav_queue, ui, browser_query_path, query, browser_query_finished, query); 322 323 window_progress(ui->window, 1); 324 325 davbrowser_add2navstack(browser, browser->repo_base, path); 326 } 327 328 void davbrowser_query_url(UiObject *ui, DavBrowser *browser, const char *url) { 329 if (browser->repo_base) { 330 cxstring base = cx_str(browser->repo_base); 331 cxstring newurl = cx_str(url); 332 333 if (cx_strprefix(newurl, base)) { 334 const char *path = url + base.length; 335 davbrowser_query_path(ui, browser, path); 336 return; 337 } 338 } 339 340 char *path = NULL; 341 DavCfgRepository *repo = dav_config_url2repo(get_config(), url, &path); 342 343 int ret = davbrowser_connect2repo(ui, browser, repo, path); 344 free(path); 345 if(ret) { 346 return; 347 } 348 349 if (!repo->node) { 350 dav_repository_free(get_config(), repo); 351 } 352 } 353 354 void davbrowser_open_resource(UiObject *ui, DavBrowser *browser, DavResource *res, const char *contenttype) { 355 DavResourceViewType type = DAV_RESOURCE_VIEW_PROPERTIES; 356 if(!contenttype) { 357 contenttype = res->contenttype; 358 } 359 360 if(res->iscollection) { 361 // default type 362 } else if(contenttype) { 363 cxstring ctype = cx_str(contenttype); 364 if(cx_strprefix(ctype, CX_STR("text/"))) { 365 type = DAV_RESOURCE_VIEW_TEXT; 366 } else if(cx_strprefix(ctype, CX_STR("image/"))) { 367 type = DAV_RESOURCE_VIEW_IMAGE; 368 } else if(cx_strprefix(ctype, CX_STR("application/"))) { 369 if(cx_strsuffix(ctype, CX_STR("json"))) { 370 type = DAV_RESOURCE_VIEW_TEXT; 371 } else if(cx_strsuffix(ctype, CX_STR("/xml"))) { 372 type = DAV_RESOURCE_VIEW_TEXT; 373 } else if(cx_strsuffix(ctype, CX_STR("+xml"))) { 374 type = DAV_RESOURCE_VIEW_TEXT; 375 } else if(cx_strsuffix(ctype, CX_STR("/xml"))) { 376 type = DAV_RESOURCE_VIEW_TEXT; 377 } 378 } 379 } else { 380 cxstring path = cx_str(res->path); 381 if(cx_strsuffix(path, CX_STR(".png"))) { 382 type = DAV_RESOURCE_VIEW_IMAGE; 383 } else if(cx_strsuffix(path, CX_STR(".jpg"))) { 384 type = DAV_RESOURCE_VIEW_IMAGE; 385 } else if(cx_strsuffix(path, CX_STR(".jpeg"))) { 386 type = DAV_RESOURCE_VIEW_IMAGE; 387 } else if(cx_strsuffix(path, CX_STR(".tif"))) { 388 type = DAV_RESOURCE_VIEW_IMAGE; 389 } else if(cx_strsuffix(path, CX_STR(".tiff"))) { 390 type = DAV_RESOURCE_VIEW_IMAGE; 391 } else if(cx_strsuffix(path, CX_STR(".webp"))) { 392 type = DAV_RESOURCE_VIEW_IMAGE; 393 } else if(cx_strsuffix(path, CX_STR(".bmp"))) { 394 type = DAV_RESOURCE_VIEW_IMAGE; 395 } else if(cx_strsuffix(path, CX_STR(".gif"))) { 396 type = DAV_RESOURCE_VIEW_IMAGE; 397 } else if(cx_strsuffix(path, CX_STR(".txt"))) { 398 type = DAV_RESOURCE_VIEW_TEXT; 399 } else if(cx_strsuffix(path, CX_STR(".md"))) { 400 type = DAV_RESOURCE_VIEW_TEXT; 401 } else if(cx_strsuffix(path, CX_STR(".xml"))) { 402 type = DAV_RESOURCE_VIEW_TEXT; 403 } 404 } 405 406 resourceviewer_new(browser, res->path, type); 407 } 408 409 void davbrowser_add2navstack(DavBrowser *browser, const char *base, const char *path) { 410 if (browser->navstack_enabled) { 411 for (int i = 0; i < browser->navstack_pos; i++) { 412 char *nav_url = cxListAt(browser->navigation_stack, 0); 413 cxListRemove(browser->navigation_stack, 0); 414 free(nav_url); 415 } 416 browser->navstack_pos = 0; 417 418 char *nav_url = util_concat_path(base, path); 419 cxListInsert(browser->navigation_stack, 0, nav_url); 420 421 if (cxListSize(browser->navigation_stack) > DAVBROWSER_MAX_NAVLIST) { 422 char *nav = cxListAt(browser->navigation_stack, cxListSize(browser->navigation_stack) - 1); 423 free(nav); 424 cxListRemove(browser->navigation_stack, cxListSize(browser->navigation_stack) - 1); 425 } 426 } 427 } 428 429 void davbrowser_navigation_parent(UiObject *ui, DavBrowser *browser) { 430 if(browser->current) { 431 char *parent = util_parent_path(browser->current->path); 432 if(strlen(parent) > 0) { 433 davbrowser_query_path(ui, browser, parent); 434 } 435 free(parent); 436 } 437 } 438 439 void davbrowser_navigation_back(UiObject *ui, DavBrowser *browser) { 440 if (browser->navstack_pos+1 < cxListSize(browser->navigation_stack)) { 441 browser->navstack_pos++; 442 char *nav_url = cxListAt(browser->navigation_stack, browser->navstack_pos); 443 browser->navstack_enabled = false; 444 davbrowser_query_url(ui, browser, nav_url); 445 browser->navstack_enabled = true; 446 ui_set(browser->path, nav_url); 447 } 448 } 449 450 void davbrowser_navigation_forward(UiObject *ui, DavBrowser *browser) { 451 if (browser->navstack_pos > 0) { 452 browser->navstack_pos--; 453 char *nav_url = cxListAt(browser->navigation_stack, browser->navstack_pos); 454 browser->navstack_enabled = false; 455 davbrowser_query_url(ui, browser, nav_url); 456 browser->navstack_enabled = true; 457 ui_set(browser->path, nav_url); 458 } 459 } 460 461 462 void davbrowser_upload_files(UiObject *ui, DavBrowser *browser, UiFileList files) { 463 if (!browser->sn) { 464 return; // TODO: error msg 465 } 466 467 cxmutstr wtitle = cx_asprintf("Upload to: %s", ui_get(browser->path)); 468 UiObject *dialog = ui_simple_window(wtitle.ptr, NULL); 469 free(wtitle.ptr); 470 471 DavFileUpload *upload = dav_upload_create(browser, dialog, files); 472 transfer_window_init(dialog, action_upload_cancel); 473 dav_upload_start(upload); 474 application_register_transfer(&upload->trans); 475 } 476 477 void davbrowser_download(UiObject *ui, DavBrowser *browser, DavResource *reslist, const char *local_path) { 478 cxmutstr wtitle = cx_asprintf("Download to: %s", local_path); 479 UiObject *dialog = ui_simple_window(wtitle.ptr, NULL); 480 free(wtitle.ptr); 481 482 DavFileDownload *download = dav_download_create(browser, dialog, reslist, local_path); 483 transfer_window_init(dialog, action_download_cancel); 484 dav_download_start(download); 485 application_register_transfer(&download->trans); 486 } 487 488 489 // ------------------------------------- Path Operation (DELETE, MKCOL) ------------------------------------- 490 491 enum DavPathOpType { 492 DAV_PATH_OP_DELETE = 0, 493 DAV_PATH_OP_CREATE 494 }; 495 496 typedef struct DavPathOp { 497 UiObject *ui; 498 DavBrowser *browser; 499 500 // operation type (delete, mkcol, ...) 501 enum DavPathOpType op; 502 // clone of the browser's DavSession 503 DavSession *sn; 504 // path array 505 char **path; 506 // browser->resources indices 507 size_t *list_indices; 508 // number of path/list_indices elements 509 size_t nelm; 510 // path is collection 511 DavBool iscollection; 512 513 // browser->current ptr when the PathOp started 514 // used in combination with collection_ctn to check if the browser list changed 515 DavResource *collection; 516 int64_t collection_ctn; 517 } DavPathOp; 518 519 typedef struct DavPathOpResult { 520 UiObject *ui; 521 DavBrowser *browser; 522 DavResource *collection; 523 int64_t collection_ctn; 524 enum DavPathOpType op; 525 DavBool iscollection; 526 527 char *path; 528 int res_index; 529 int result; 530 char *errormsg; 531 532 time_t result_lastmodified; 533 uint64_t result_contentlength; 534 char *result_contenttype; 535 } DavPathOpResult; 536 537 typedef struct DavRenameOp { 538 UiObject *ui; 539 DavBrowser *browser; 540 541 // clone of the browser's DavSession 542 DavSession *sn; 543 char *path; 544 char *newname; 545 int result; 546 char *errormsg; 547 548 // browser->resources index 549 size_t index; 550 551 // browser->current ptr when the PathOp started 552 // used in combination with collection_ctn to check if the browser list changed 553 DavResource *collection; 554 int64_t collection_ctn; 555 } DavRenameOp; 556 557 static int uithr_pathop_delete_error(void *data) { 558 DavPathOpResult *result = data; 559 560 cxmutstr msg = cx_asprintf("Cannot delete resource %s", result->path); 561 ui_dialog(result->ui, .title = "Error", .content = msg.ptr, .button1_label = "OK"); 562 free(msg.ptr); 563 564 if (result->errormsg) { 565 free(result->errormsg); 566 } 567 free(result->path); 568 free(result); 569 return 0; 570 } 571 572 static int uithr_pathop_delete_sucess(void *data) { 573 DavPathOpResult *result = data; 574 575 if (result->browser->current == result->collection && result->browser->res_counter == result->collection_ctn) { 576 ui_list_remove(result->browser->resources, result->res_index); 577 result->browser->resources->update(result->browser->resources, 0); 578 } 579 580 free(result->path); 581 free(result); 582 return 0; 583 } 584 585 static int uithr_pathop_create_resource_error(void *data) { 586 DavPathOpResult *result = data; 587 588 cxmutstr msg = cx_asprintf("Cannot create %s %s", result->iscollection ? "collection" : "resource", result->path); 589 ui_dialog(result->ui, .title = "Error", .content = msg.ptr, .button1_label = "OK"); 590 free(msg.ptr); 591 592 if (result->errormsg) { 593 free(result->errormsg); 594 } 595 free(result->path); 596 free(result); 597 return 0; 598 } 599 600 static int uithr_pathop_create_resource_sucess(void *data) { 601 DavPathOpResult *result = data; 602 603 if (result->browser->current == result->collection && result->browser->res_counter == result->collection_ctn) { 604 DavResource *res = dav_resource_new(result->browser->sn, result->path); 605 res->iscollection = result->iscollection; 606 res->lastmodified = result->result_lastmodified; 607 res->contentlength = result->result_contentlength; 608 res->contenttype = result->result_contenttype ? dav_session_strdup(res->session, result->result_contenttype) : NULL; 609 // TODO: add the resource at the correct position or sort the list after append 610 ui_list_append(result->browser->resources, res); 611 result->browser->resources->update(result->browser->resources, 0); 612 } 613 614 free(result->path); 615 free(result->result_contenttype); 616 free(result); 617 return 0; 618 } 619 620 static int jobthr_path_op(void *data) { 621 DavPathOp *op = data; 622 623 for (int i = op->nelm-1; i >= 0; i--) { 624 if (op->path[i]) { 625 DavResource *res = dav_resource_new(op->sn, op->path[i]); 626 627 DavPathOpResult *result = malloc(sizeof(DavPathOpResult)); 628 result->ui = op->ui; 629 result->browser = op->browser; 630 result->collection = op->collection; 631 result->collection_ctn = op->collection_ctn; 632 result->op = op->op; 633 result->path = strdup(res->path); 634 result->result = 0; 635 result->res_index = op->list_indices[i]; 636 result->errormsg = NULL; 637 result->iscollection = op->iscollection; 638 result->result_lastmodified = 0; 639 result->result_contentlength = 0; 640 result->result_contenttype = NULL; 641 642 if (op->op == DAV_PATH_OP_DELETE) { 643 ui_threadfunc result_callback = uithr_pathop_delete_sucess; 644 if (dav_delete(res)) { 645 result->errormsg = op->sn->errorstr ? strdup(op->sn->errorstr) : NULL; 646 result_callback = uithr_pathop_delete_error; 647 } 648 ui_call_mainthread(result_callback, result); 649 } else if (op->op == DAV_PATH_OP_CREATE) { 650 res->iscollection = op->iscollection; 651 ui_threadfunc result_callback = uithr_pathop_create_resource_sucess; 652 if (dav_create(res)) { 653 result->errormsg = op->sn->errorstr ? strdup(op->sn->errorstr) : NULL; 654 result_callback = uithr_pathop_create_resource_error; 655 } else { 656 // try to load some basic resource properties 657 // we don't care about the result, if it fails, 658 // we just don't have the new properties 659 dav_load_prop(res, NULL, 0); 660 result->result_lastmodified = res->lastmodified; 661 result->result_contentlength = res->contentlength; 662 result->result_contenttype = res->contenttype ? strdup(res->contenttype) : NULL; 663 } 664 ui_call_mainthread(result_callback, result); 665 } 666 667 dav_resource_free(res); 668 free(op->path[i]); 669 } 670 } 671 672 dav_session_destroy(op->sn); 673 free(op->path); 674 free(op->list_indices); 675 free(op); 676 677 return 0; 678 } 679 680 void davbrowser_delete(UiObject *ui, DavBrowser *browser, UiListSelection selection) { 681 DavPathOp *op = malloc(sizeof(DavPathOp)); 682 op->ui = ui; 683 op->browser = browser; 684 op->op = DAV_PATH_OP_DELETE; 685 op->sn = dav_session_clone(browser->sn); 686 op->path = calloc(selection.count, sizeof(char*)); 687 op->list_indices = calloc(selection.count, sizeof(size_t)); 688 op->nelm = selection.count; 689 690 op->collection = browser->current; 691 op->collection_ctn = browser->res_counter; 692 693 for (int i = 0; i < selection.count; i++) { 694 DavResource *res = ui_list_get(browser->resources, selection.rows[i]); 695 if (res) { 696 op->path[i] = strdup(res->path); 697 op->list_indices[i] = selection.rows[i]; 698 } 699 } 700 701 ui_job(ui, jobthr_path_op, op, NULL, NULL); 702 } 703 704 void davbrowser_create_resource(UiObject *ui, DavBrowser *browser, const char *name, DavBool iscollection) { 705 DavPathOp *op = malloc(sizeof(DavPathOp)); 706 op->ui = ui; 707 op->browser = browser; 708 op->op = DAV_PATH_OP_CREATE; 709 op->sn = dav_session_clone(browser->sn); 710 op->path = calloc(1, sizeof(char*)); 711 op->list_indices = calloc(1, sizeof(size_t)); 712 op->nelm = 1; 713 op->iscollection = iscollection; 714 715 op->path[0] = util_concat_path(browser->current->path, name); 716 717 op->collection = browser->current; 718 op->collection_ctn = browser->res_counter; 719 720 ui_job(ui, jobthr_path_op, op, NULL, NULL); 721 } 722 723 void davbrowser_mkcol(UiObject *ui, DavBrowser *browser, const char *name) { 724 davbrowser_create_resource(ui, browser, name, TRUE); 725 } 726 727 void davbrowser_newfile(UiObject *ui, DavBrowser *browser, const char *name) { 728 davbrowser_create_resource(ui, browser, name, FALSE); 729 } 730 731 732 733 734 static int jobthr_rename(void *data) { 735 DavRenameOp *op = data; 736 737 DavResource *res = dav_get(op->sn, op->path, NULL); 738 if(!res) { 739 fprintf(stderr, "Error: Cannot get resource %s\n", op->path); 740 op->result = 1; 741 return 0; 742 } 743 744 char *cryptoname = dav_get_string_property_ns(res, DAV_NS, "crypto-name"); 745 char *cryptokey = dav_get_string_property_ns(res, DAV_NS, "crypto-key"); 746 if(cryptoname && cryptokey) { 747 // encrypted resource, the name is stored in the crypto-name property 748 // these properties are only loaded if encryption is enabled for 749 // this session 750 DavKey *key = dav_context_get_key(op->sn->context, cryptokey); 751 if(!key) { 752 cxmutstr error = cx_asprintf_a(op->sn->mp->allocator, "Cannot rename resource: crypto key %s not found.", cryptokey); 753 op->errormsg = error.ptr; 754 op->result = 1; 755 return 0; 756 } 757 758 // check if a resource with this name already exists 759 char *parent = util_parent_path(res->path); 760 char *newpath = util_concat_path(parent, op->newname); 761 DavResource *testres = dav_resource_new(op->sn, newpath); 762 if(dav_exists(testres)) { 763 cxmutstr error = cx_asprintf_a(op->sn->mp->allocator, "A resource with the name %s already exists.", op->newname); 764 op->errormsg = error.ptr; 765 op->result = 1; 766 } else { 767 char *crname = aes_encrypt(op->newname, strlen(op->newname), key); 768 dav_set_string_property_ns(res, DAV_NS, "crypto-name", crname); 769 free(crname); 770 if(dav_store(res)) { 771 op->result = 1; 772 } 773 } 774 free(parent); 775 free(newpath); 776 } else { 777 // rename the resource by changing the url mapping with MOVE 778 char *parent = util_parent_path(res->href); 779 char *new_href = util_concat_path(parent, op->newname); 780 char *dest = util_get_url(op->sn, new_href); 781 free(parent); 782 free(new_href); 783 if(dav_moveto(res, dest, false)) { 784 op->result = 1; 785 } 786 free(dest); 787 } 788 789 if(op->result && !op->errormsg) { 790 cxmutstr error = cx_asprintf_a(op->sn->mp->allocator, "Error: %d", op->sn->error); 791 op->errormsg = error.ptr; 792 } 793 794 return 0; 795 } 796 797 static void uithr_rename_finished(UiEvent *event, void *data) { 798 DavRenameOp *op = data; 799 800 if(!op->result) { 801 // update name in the browser list 802 if (op->browser->current == op->collection && op->browser->res_counter == op->collection_ctn) { 803 DavResource *res = ui_list_get(op->browser->resources, op->index); 804 805 char *parent = util_parent_path(res->path); 806 char *newpath = util_concat_path(parent, op->newname); 807 dav_session_free(res->session, res->path); 808 dav_session_free(res->session, res->name); 809 res->path = dav_session_strdup(res->session, newpath); 810 res->name = dav_session_strdup(res->session, op->newname); 811 op->browser->resources->update(op->browser->resources, 0); 812 free(parent); 813 free(newpath); 814 } 815 } else { 816 // error 817 ui_dialog(op->ui, .title = "Error", .content = op->errormsg, .closebutton_label = "OK"); 818 } 819 dav_session_destroy(op->sn); 820 } 821 822 static void action_resource_rename(UiEvent *event, void *data) { 823 DavRenameOp *op = data; 824 if(event->intval == 1) { 825 char *newname = event->eventdata; 826 if(!newname || strlen(newname) == 0) { 827 ui_dialog(op->ui, .title = "Error", .content = "No name specified", .closebutton_label = "OK"); 828 dav_session_destroy(op->sn); 829 return; 830 } 831 832 char *s = strchr(newname, '/'); 833 if(s) { 834 ui_dialog(op->ui, .title = "Error", .content = "Character ''/'' is not allowed", .closebutton_label = "OK"); 835 dav_session_destroy(op->sn); 836 return; 837 } 838 839 op->newname = dav_session_strdup(op->sn, newname); 840 841 ui_job(op->ui, jobthr_rename, op, uithr_rename_finished, op); 842 return; 843 } 844 dav_session_destroy(op->sn); 845 } 846 847 void davbrowser_rename(UiObject *ui, DavBrowser *browser, UiListSelection selection) { 848 DavSession *sn = dav_session_clone(browser->sn); 849 850 DavResource *res = ui_list_get(browser->resources, selection.rows[0]); 851 852 DavRenameOp *rename = dav_session_malloc(sn, sizeof(DavRenameOp)); 853 memset(rename, 0, sizeof(DavRenameOp)); 854 rename->browser = browser; 855 rename->ui = ui; 856 rename->sn = sn; 857 rename->path = dav_session_strdup(sn, res->path); 858 rename->index = selection.rows[0]; 859 860 rename->collection = browser->current; 861 rename->collection_ctn = browser->res_counter; 862 863 ui_dialog(ui, 864 .title = "Rename", 865 .content = res->name, 866 .input = TRUE, 867 .input_value = res->name, 868 .result = action_resource_rename, 869 .resultdata = rename, 870 .button1_label = "Rename", 871 .closebutton_label = "Cancel"); 872 } 873 874 875 876 DavResourceViewer* dav_resourceviewer_create(UiObject *toplevel, DavSession *sn, const char *path, DavResourceViewType type) { 877 DavResourceViewer *doc = ui_document_new(sizeof(DavResourceViewer)); 878 UiContext *ctx = ui_document_context(doc); 879 CxMempool *mp = ui_cx_mempool(ctx); 880 doc->obj = toplevel; 881 doc->ctx = ctx; 882 883 doc->sn = dav_session_clone(sn); 884 doc->dav_queue = ui_threadpool_create(1); 885 cxMempoolRegister(mp, doc->dav_queue, (cx_destructor_func)ui_threadpool_destroy); 886 doc->path = strdup(path); 887 doc->type = type; 888 889 doc->tabview = ui_int_new(ctx, "tabview"); 890 doc->loading = ui_int_new(ctx, "loading"); 891 doc->message = ui_string_new(ctx, "message"); 892 893 doc->text = ui_text_new(ctx, "text"); 894 doc->image = ui_generic_new(ctx, "image"); 895 896 doc->properties = ui_list_new(ctx, "properties"); 897 898 doc->info_url = ui_string_new(ctx, "info_url"); 899 doc->info_name = ui_string_new(ctx, "info_name"); 900 doc->info_type = ui_string_new(ctx, "info_type"); 901 doc->info_encrypted = ui_string_new(ctx, "info_encrypted"); 902 doc->info_etag = ui_string_new(ctx, "info_etag"); 903 doc->info_size = ui_string_new(ctx, "info_size"); 904 905 doc->property_type = ui_int_new(ctx, NULL); 906 doc->property_ns = ui_string_new(ctx, NULL); 907 doc->property_name = ui_string_new(ctx, NULL); 908 doc->property_nsdef = ui_string_new(ctx, NULL); 909 doc->property_value = ui_text_new(ctx, NULL); 910 doc->property_errormsg = ui_string_new(ctx, NULL); 911 912 return doc; 913 } 914 915 static char* gen_tmp_download_filename(const char *name) { 916 char *dir = ui_getappdir(); 917 918 unsigned char rd[8]; 919 memset(rd, 0, 8); 920 dav_rand_bytes(rd, 8); 921 char *hex = util_hexstr(rd, 8); 922 cxmutstr tmp = cx_asprintf("%sdownload-%s-%s", dir, hex, name); 923 return tmp.ptr; 924 } 925 926 static int jobthr_resourceviewer_load(void *data) { 927 DavResourceViewer *doc = data; 928 929 DavResource *res = dav_resource_new(doc->sn, doc->path); 930 doc->error = dav_load(res); 931 if(!doc->error) { 932 doc->current = res; 933 934 if(res->contentlength < DAV_RESOURCEVIEWER_PREVIEW_MAX_SIZE) { 935 if(doc->type == DAV_RESOURCE_VIEW_TEXT) { 936 doc->text_content = cxBufferCreate(NULL, res->contentlength, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); 937 int err = dav_get_content(res, doc->text_content, (dav_write_func)cxBufferWrite); 938 cxBufferPut(doc->text_content, 0); 939 if(err) { 940 doc->error = err; 941 doc->message_str = "Cannot load content"; // TODO: better message 942 } 943 } else if(doc->type == DAV_RESOURCE_VIEW_IMAGE) { 944 char *tmp = gen_tmp_download_filename(res->name); 945 FILE *f = sys_fopen(tmp, "wb"); 946 if(f) { 947 int err = dav_get_content(res, f, (dav_write_func)fwrite); 948 if(!err) { 949 doc->tmp_file = tmp; 950 } else { 951 free(tmp); 952 } 953 fclose(f); 954 } else { 955 free(tmp); 956 } 957 } 958 } else { 959 // TODO: add file too large message 960 } 961 } else { 962 doc->message_str = "Cannot load properties"; // TODO: better message 963 dav_resource_free(res); 964 } 965 966 return 0; 967 } 968 969 static void resourceviewer_set_info(DavResourceViewer *doc) { 970 DavResource *res = doc->current; 971 if(!res) { 972 return; 973 } 974 975 char *url = util_concat_path(res->session->base_url, res->href); 976 ui_set(doc->info_url, url); 977 free(url); 978 979 ui_set(doc->info_name, res->name); 980 981 if(res->iscollection) { 982 ui_set(doc->info_type, "Collection"); 983 } else { 984 if(res->contenttype) { 985 cxmutstr type = cx_asprintf("Resource (%s)", res->contenttype); 986 ui_set(doc->info_type, type.ptr); 987 free(type.ptr); 988 } else { 989 ui_set(doc->info_type, "Resource"); 990 } 991 } 992 993 char *keyprop = dav_get_string_property_ns( 994 res, 995 DAV_NS, 996 "crypto-key"); 997 if(keyprop) { 998 cxmutstr info_encrypted = cx_asprintf("Yes Key: %s", keyprop); 999 ui_set(doc->info_encrypted, info_encrypted.ptr); 1000 free(info_encrypted.ptr); 1001 } else { 1002 ui_set(doc->info_encrypted, "No"); 1003 } 1004 1005 char *etag = dav_get_string_property_ns( 1006 res, 1007 "DAV:", 1008 "getetag"); 1009 ui_set(doc->info_etag, etag); 1010 1011 if(res->contentlength > 0) { 1012 char *sz = util_size_str(FALSE, res->contentlength); 1013 cxmutstr size_str = cx_asprintf("%s (%" PRIu64 " bytes)", sz, res->contentlength); 1014 ui_set(doc->info_size, size_str.ptr); 1015 free(size_str.ptr); 1016 } else { 1017 ui_set(doc->info_size, "0"); 1018 } 1019 } 1020 1021 static void resourceviewer_update_proplist(DavResourceViewer *doc) { 1022 DavResource *res = doc->current; 1023 if(!res) { 1024 return; 1025 } 1026 1027 size_t count = 0; 1028 DavPropName *properties = dav_get_property_names(res, &count); 1029 for(int i=0;i<count;i++) { 1030 DavPropertyList *prop = ui_malloc(doc->ctx, sizeof(DavPropertyList)); 1031 prop->ns = properties[i].ns ? ui_strdup(doc->ctx, properties[i].ns) : NULL; 1032 prop->name = ui_strdup(doc->ctx, properties[i].name); 1033 prop->value_simplified = NULL; 1034 prop->value_full = NULL; 1035 prop->update = FALSE; 1036 prop->isnew = FALSE; 1037 1038 DavXmlNode *xval = dav_get_property_ns(res, prop->ns, prop->name); 1039 prop->xml = xval; 1040 if(xval) { 1041 if(dav_xml_isstring(xval)) { 1042 char *value = dav_xml_getstring(xval); 1043 if(value) { 1044 prop->value_simplified = NULL; 1045 prop->value_full = ui_strdup(doc->ctx, value); 1046 } 1047 } else { 1048 DavXmlNode *x = xval->type == DAV_XML_ELEMENT ? xval : dav_xml_nextelm(xval); 1049 cxmutstr value = cx_asprintf_a(ui_allocator(doc->ctx), "<%s>...</%s>", x->name, x->name); 1050 prop->value_simplified = value.ptr; 1051 } 1052 } 1053 1054 ui_list_append(doc->properties, prop); 1055 } 1056 doc->properties->update(doc->properties, 0); 1057 } 1058 1059 static void resourceviewer_load_finished(UiEvent *event, void *data) { 1060 DavResourceViewer *doc = data; 1061 1062 if(doc->window_closed) { 1063 dav_resourceviewer_destroy(doc); 1064 return; 1065 } 1066 1067 resourceviewer_set_info(doc); 1068 resourceviewer_update_proplist(doc); 1069 1070 if(doc->type == DAV_RESOURCE_VIEW_TEXT) { 1071 ui_set(doc->text, doc->text_content->space); 1072 } else if(doc->type == DAV_RESOURCE_VIEW_IMAGE) { 1073 ui_image_load_file(doc->image, doc->tmp_file); 1074 } 1075 1076 ui_set(doc->tabview, 1); 1077 1078 doc->loaded = TRUE; 1079 } 1080 1081 void dav_resourceviewer_load(UiObject *ui, DavResourceViewer *res) { 1082 ui_set(res->loading, 1); 1083 ui_set(res->message, "Loading..."); 1084 ui_set(res->tabview, 0); 1085 1086 ui_job(ui, jobthr_resourceviewer_load, res, resourceviewer_load_finished, res); 1087 } 1088 1089 1090 typedef struct ResourceViewerUploadFile { 1091 UiObject *ui; 1092 DavSession *sn; 1093 char *path; 1094 cxmutstr text; 1095 int error; 1096 } ResourceViewerUploadFile; 1097 1098 static int jobthr_upload_text(void *data) { 1099 ResourceViewerUploadFile *upload = data; 1100 1101 DavResource *res = dav_resource_new(upload->sn, upload->path); 1102 dav_set_content_data(res, upload->text.ptr, upload->text.length); 1103 upload->error = dav_store(res); 1104 dav_resource_free(res); 1105 1106 return 0; 1107 } 1108 1109 static void uithr_upload_text_finished(UiEvent *event, void *data) { 1110 ResourceViewerUploadFile *upload = data; 1111 ui_object_unref(event->obj); 1112 1113 if(upload->error) { 1114 cxmutstr errormsg = cx_asprintf("Upload failed: %d", upload->sn->error); // TODO: add full error message 1115 ui_dialog(event->obj, .title = "Error", .content = errormsg.ptr, .closebutton_label = "OK"); 1116 free(errormsg.ptr); 1117 ui_set_group(event->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); 1118 } 1119 1120 free(upload->text.ptr); 1121 free(upload); 1122 } 1123 1124 static int jobthr_store_properties(void *data) { 1125 DavResourceViewer *res = data; 1126 res->error = dav_store(res->current); 1127 return 0; 1128 } 1129 1130 static void uithr_store_properties_finished(UiEvent *event, void *data) { 1131 DavResourceViewer *res = data; 1132 ui_object_unref(event->obj); 1133 if(res->error) { 1134 cxmutstr errormsg = cx_asprintf("Proppatch failed: %d", res->sn->error); // TODO: add full error message 1135 ui_dialog(event->obj, .title = "Error", .content = errormsg.ptr, .closebutton_label = "OK"); 1136 free(errormsg.ptr); 1137 ui_set_group(event->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); 1138 res->properties_modified = TRUE; 1139 } else { 1140 CxList *properties = res->properties->data; 1141 CxIterator i = cxListIterator(properties); 1142 cx_foreach(DavPropertyList *, prop, i) { 1143 prop->update = FALSE; 1144 prop->isnew = FALSE; 1145 } 1146 } 1147 } 1148 1149 void dav_resourceviewer_save(UiObject *ui, DavResourceViewer *res) { 1150 if(res->type == DAV_RESOURCE_VIEW_TEXT) { 1151 ResourceViewerUploadFile *upload = malloc(sizeof(ResourceViewerUploadFile)); 1152 upload->ui = ui; 1153 upload->sn = res->sn; 1154 upload->path = res->current->path; 1155 char *text = ui_get(res->text); 1156 upload->text = cx_strdup(cx_str(text)); 1157 ui_object_ref(res->obj); 1158 ui_threadpool_job(res->dav_queue, ui, jobthr_upload_text, upload, uithr_upload_text_finished, upload); 1159 } 1160 1161 if(res->properties_modified) { 1162 CxList *properties = res->properties->data; 1163 CxIterator i = cxListIterator(properties); 1164 cx_foreach(DavPropertyList *, prop, i) { 1165 if(prop->update) { 1166 if(prop->value_full) { 1167 // text 1168 dav_set_string_property_ns(res->current, prop->ns, prop->name, prop->value_full); 1169 } else { 1170 // xml 1171 dav_set_property_ns(res->current, prop->ns, prop->name, prop->xml); 1172 } 1173 } 1174 } 1175 1176 ui_object_ref(res->obj); 1177 ui_threadpool_job(res->dav_queue, ui, jobthr_store_properties, res, uithr_store_properties_finished, res); 1178 } 1179 1180 ui_unset_group(ui->ctx, RESOURCEVIEWER_STATE_MODIFIED); 1181 } 1182 1183 void dav_resourceviewer_destroy(DavResourceViewer *res) { 1184 1185 } 1186 1187 void dav_resourceviewer_property_remove(DavResourceViewer *res, DavPropertyList *prop) { 1188 if(!prop->isnew) { 1189 dav_remove_property_ns(res->current, prop->ns, prop->name); 1190 ui_set_group(res->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); 1191 res->properties_modified = TRUE; 1192 } 1193 1194 CxList *properties = res->properties->data; 1195 cxListFindRemove(properties, prop); 1196 ui_free(res->ctx, prop->ns); 1197 ui_free(res->ctx, prop->name); 1198 ui_free(res->ctx, prop->value_simplified); 1199 ui_free(res->ctx, prop->value_full); 1200 // prop->xml freed by DavSession 1201 ui_free(res->ctx, prop); 1202 ui_list_update(res->properties); 1203 } 1204 1205 void dav_resourceviewer_property_update_text(DavResourceViewer *res, DavPropertyList *prop, const char *text) { 1206 ui_free(res->ctx, prop->value_simplified); 1207 ui_free(res->ctx, prop->value_full); 1208 prop->xml = NULL; 1209 prop->value_full = ui_strdup(res->ctx, text); 1210 prop->value_simplified = NULL; 1211 prop->update = TRUE; 1212 1213 ui_set_group(res->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); 1214 res->properties_modified = TRUE; 1215 ui_list_update(res->properties); 1216 } 1217 1218 void dav_resourceviewer_property_update_xml(DavResourceViewer *res, DavPropertyList *prop, DavXmlNode *xml) { 1219 1220 } 1221 1222 void dav_resourceviewer_property_add_text(DavResourceViewer *res, const char *ns, const char *name, const char *text) { 1223 DavPropertyList *prop = ui_malloc(res->ctx, sizeof(DavPropertyList)); 1224 prop->ns = ui_strdup(res->ctx, ns); 1225 prop->name = ui_strdup(res->ctx, name); 1226 prop->value_simplified = NULL; 1227 prop->value_full = ui_strdup(res->ctx, text); 1228 prop->xml = NULL; 1229 prop->isnew = TRUE; 1230 prop->update = TRUE; 1231 1232 ui_list_append(res->properties, prop); 1233 ui_set_group(res->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); 1234 res->properties_modified = TRUE; 1235 ui_list_update(res->properties); 1236 } 1237 1238 void dav_resourceviewer_property_add_xml(DavResourceViewer *res, const char *ns, const char *name, const char *nsdef, DavXmlNode *xml) { 1239 1240 } 1241 1242 1243 1244 uint64_t dav_transfer_speed(TransferProgress *progress, time_t current) { 1245 size_t bytes = progress->transferred_bytes - progress->speedtest_bytes; 1246 time_t t = current - progress->speedtest_start; 1247 1248 progress->speedtest_start = current; 1249 progress->speedtest_bytes = progress->transferred_bytes; 1250 1251 return bytes/t; 1252 } 1253