UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2020 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 32 #include "../daemon/session.h" 33 #include "../daemon/protocol.h" 34 #include "../util/platform.h" 35 36 #include <cx/string.h> 37 #include <cx/hash_map.h> 38 39 #include "multistatus.h" 40 41 #include "operation.h" 42 #include "xml.h" 43 44 #define MULTISTATUS_BUFFER_LENGTH 2048 45 46 Multistatus* multistatus_response(Session *sn, Request *rq) { 47 Multistatus *ms = pool_malloc(sn->pool, sizeof(Multistatus)); 48 if(!ms) { 49 return NULL; 50 } 51 ZERO(ms, sizeof(Multistatus)); 52 ms->response.addresource = multistatus_addresource; 53 ms->sn = sn; 54 ms->rq = rq; 55 ms->namespaces = cxHashMapCreate(pool_allocator(sn->pool), CX_STORE_POINTERS, 8); 56 ms->proppatch = FALSE; 57 if(!ms->namespaces) { 58 return NULL; 59 } 60 if(cxMapPut(ms->namespaces, cx_hash_key_str("D"), webdav_dav_namespace())) { 61 return NULL; 62 } 63 return ms; 64 } 65 66 static int send_xml_root(Multistatus *ms, Writer *out) { 67 writer_put_lit(out, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" 68 "<D:multistatus"); 69 70 // write the namespaces definitions 71 // key is the namespace prefix 72 // the map always contains the "DAV:" namespace with the prefix "D" 73 CxIterator i = cxMapIterator(ms->namespaces); 74 cx_foreach(CxMapEntry*, entry, i) { 75 WSNamespace *ns = entry->value; 76 writer_put_lit(out, " xmlns:"); 77 writer_put (out, entry->key->data, entry->key->len); 78 writer_put_lit(out, "=\""); 79 writer_put_str(out, (char*)ns->href); 80 writer_put_lit(out, "\""); 81 } 82 83 writer_put_lit(out, ">\n"); 84 85 return out->error; 86 } 87 88 static void send_nsdef(WSNamespace *ns, Writer *out) { 89 writer_put_lit(out, " xmlns:"); 90 writer_put_str(out, (char*)ns->prefix); 91 writer_put_lit(out, "=\""); 92 writer_put_str(out, (char*)ns->href); 93 writer_putc (out, '\"'); 94 } 95 96 97 // html escape 98 /* 99 static void send_string_escaped(Writer *out, cxmutstr str) { 100 char *begin = str.ptr; 101 char *end = begin; 102 char *escape = NULL; 103 int esclen; 104 for(size_t i=0;i<str.length;i++) { 105 char c = str.ptr[i]; 106 end = str.ptr + i; 107 switch(c) { 108 case '"': { 109 escape = "&quot;"; 110 esclen = 6; 111 break; 112 } 113 case '&': { 114 escape = "&amp;"; 115 esclen = 5; 116 break; 117 } 118 case '\'': { 119 escape = "&apos;"; 120 esclen = 6; 121 break; 122 } 123 case '<': { 124 escape = "&lt;"; 125 esclen = 4; 126 break; 127 } 128 case '>': { 129 escape = "&gt;"; 130 esclen = 4; 131 break; 132 } 133 default: continue; 134 } 135 ptrdiff_t len = end - begin; 136 if(len > 0) { 137 writer_put(out, begin, len); 138 begin = end + 1; 139 } 140 writer_put(out, escape, esclen); 141 } 142 ptrdiff_t len = end - begin; 143 if(len > 0) { 144 writer_put(out, begin, len + 1); 145 begin = end + 1; 146 } 147 } 148 */ 149 150 static const char hex_ch[] = "0123456789ABCDEF"; 151 152 static void send_string_escaped(Writer *out, cxmutstr str) { 153 char *begin = str.ptr; 154 char *end = begin; 155 156 char escape[4]; 157 escape[0] = '%'; 158 159 for(size_t i=0;i<str.length;i++) { 160 unsigned char c = (unsigned char)str.ptr[i]; 161 end = str.ptr + i; 162 163 // check if the character must be escaped 164 if( (c >= 'A' && c <= 'Z') || 165 (c >= 'a' && c <= 'z') || 166 (c >= '/' && c <= '9') || 167 (strchr("_.\\-~", c) != NULL)) 168 { 169 continue; 170 } 171 172 // convert char to hex number, escape[0] always contains '%' 173 escape[1] = hex_ch[(c >> 4) & 0x0F]; 174 escape[2] = hex_ch[c & 0x0F]; 175 176 // write previous unescaped chars, if available 177 ptrdiff_t len = end - begin; 178 if(len > 0) { 179 writer_put(out, begin, len); 180 } 181 182 // write escaped char 183 writer_put(out, escape, 3); 184 185 // begin next chunk 186 begin = end + 1; 187 } 188 ptrdiff_t len = str.ptr + str.length - begin; 189 if(len > 0) { 190 writer_put(out, begin, len); 191 begin = end + 1; 192 } 193 } 194 195 static int send_property( 196 Multistatus *ms, 197 WebdavProperty *property, 198 WebdavNSList *nsdef, 199 WSBool writeContent, 200 Writer *out) 201 { 202 // write: "<prefix:name" 203 writer_putc (out, '<'); 204 writer_put_str(out, (char*)property->namespace->prefix); 205 writer_putc (out, ':'); 206 writer_put_str(out, (char*)property->name); 207 208 // send additional namespace definitions required for the value 209 WebdavNSList *def = nsdef; 210 while(def) { 211 send_nsdef(def->namespace, out); 212 def = def->next; 213 } 214 215 // send xml lang attribute 216 if(property->lang) { 217 writer_put_lit(out, " xml:lang=\""); 218 writer_put_str(out, (char*)property->lang); 219 writer_putc (out, '\"'); 220 } 221 222 // end property tag and write content 223 if(writeContent) { 224 writer_putc(out, '>'); 225 226 // content 227 switch(property->vtype) { 228 case WS_VALUE_NO_TYPE: break; 229 case WS_VALUE_XML_NODE: { 230 wsxml_write_nodes_without_nsdef( 231 ms->sn->pool, 232 out, 233 property->value.node); 234 break; 235 } 236 case WS_VALUE_XML_DATA: { 237 // only write data, data->namespaces is already handled 238 writer_put( 239 out, 240 property->value.data.data, 241 property->value.data.length); 242 break; 243 } 244 case WS_VALUE_TEXT: { 245 // asume the text is already escaped 246 writer_put( 247 out, 248 property->value.text.str, 249 property->value.text.length); 250 break; 251 } 252 } 253 254 // end tag 255 writer_put_lit(out, "</"); 256 writer_put_str(out, (char*)property->namespace->prefix); 257 writer_putc (out, ':'); 258 writer_put_str(out, (char*)property->name); 259 writer_putc (out, '>'); 260 } else { 261 writer_put_lit(out, "/>"); 262 } 263 264 return out->error; 265 } 266 267 static int send_response_tag(Multistatus *ms, MSResponse *rp, Writer *out) { 268 writer_put_lit(out, " <D:response>\n" 269 " <D:href>"); 270 send_string_escaped(out, cx_mutstr(rp->resource.href)); 271 writer_put_lit(out, "</D:href>\n"); 272 273 WSBool writeContent = ms->proppatch ? FALSE : TRUE; 274 275 if(rp->plist_begin) { 276 writer_put_lit(out, " <D:propstat>\n" 277 " <D:prop>\n"); 278 // send properties 279 PropertyOkList *p = rp->plist_begin; 280 while(p) { 281 writer_put_lit(out, " "); 282 if(send_property(ms, p->property, p->nsdef, writeContent, out)) { 283 return out->error; 284 } 285 writer_put_lit(out, "\n"); 286 p = p->next; 287 } 288 289 writer_put_lit(out, " </D:prop>\n" 290 " <D:status>HTTP/1.1 200 OK</D:status>\n" 291 " </D:propstat>\n"); 292 } 293 294 // send error properties 295 PropertyErrorList *error = rp->errors; 296 while(error) { 297 writer_put_lit(out, " <D:propstat>\n" 298 " <D:prop>\n"); 299 300 WebdavPList *errprop = error->begin; 301 while(errprop) { 302 writer_put_lit(out, " "); 303 if(send_property(ms, errprop->property, NULL, FALSE, out)) { 304 return out->error; 305 } 306 writer_putc(out, '\n'); 307 errprop = errprop->next; 308 } 309 310 char statuscode[8]; 311 int sclen = snprintf(statuscode, 8, "%d ", error->status); 312 if(sclen > 4) { 313 statuscode[0] = '5'; 314 statuscode[1] = '0'; 315 statuscode[2] = '0'; 316 statuscode[3] = ' '; 317 sclen = 4; 318 } 319 writer_put_lit(out, " </D:prop>\n" 320 " <D:status>HTTP/1.1 "); 321 writer_put(out, statuscode, sclen); 322 const char *status_msg = protocol_status_message(error->status); 323 if(status_msg) { 324 writer_put(out, status_msg, strlen(status_msg)); 325 } else { 326 writer_put_lit(out, "Server Error"); 327 } 328 writer_put_lit(out, "</D:status>\n" 329 " </D:propstat>\n"); 330 331 332 error = error->next; 333 } 334 335 // end response tag 336 writer_put_lit(out, " </D:response>\n"); 337 338 return out->error; 339 } 340 341 int multistatus_send(Multistatus *ms, SYS_NETFD net) { 342 // make sure every resource is closed 343 if(ms->current && !ms->current->resource.isclosed) { 344 if(msresponse_close((WebdavResource*)ms->current)) { 345 return 1; 346 } 347 } 348 349 // start http response 350 protocol_status(ms->sn, ms->rq, 207, NULL); 351 if(protocol_start_response(ms->sn, ms->rq)) { 352 return 1; 353 } 354 355 char buffer[MULTISTATUS_BUFFER_LENGTH]; 356 // create a writer, that flushes the buffer when it is filled 357 Writer writer; 358 Writer *out = &writer; 359 writer_init(out, net, buffer, MULTISTATUS_BUFFER_LENGTH); 360 361 // send the xml root element with namespace defs 362 if(send_xml_root(ms, out)) { 363 return 1; 364 } 365 366 // send response tags 367 MSResponse *response = ms->first; 368 while(response) { 369 if(send_response_tag(ms, response, out)) { 370 return 1; 371 } 372 response = response->next; 373 } 374 375 // end multistatus 376 writer_put_lit(out, "</D:multistatus>\n"); 377 378 //printf("\n\n"); 379 //fflush(stdout); 380 381 writer_flush(out); 382 383 return 0; 384 } 385 386 WebdavResource * multistatus_addresource( 387 WebdavResponse *response, 388 const char *path) 389 { 390 Multistatus *ms = (Multistatus*)response; 391 MSResponse *res = pool_malloc(ms->sn->pool, sizeof(MSResponse)); 392 if(!res) { 393 return NULL; 394 } 395 ZERO(res, sizeof(MSResponse)); 396 397 // set href 398 res->resource.href = pool_strdup(ms->sn->pool, path); 399 if(!res->resource.href) { 400 return NULL; 401 } 402 403 res->resource.err = 0; 404 405 // add resource funcs 406 res->resource.addproperty = msresponse_addproperty; 407 res->resource.close = msresponse_close; 408 409 res->properties = cxHashMapCreate(pool_allocator(ms->sn->pool), CX_STORE_POINTERS, 32); 410 if(!res->properties) { 411 return NULL; 412 } 413 414 res->multistatus = ms; 415 res->errors = NULL; 416 res->resource.isclosed = 0; 417 res->closing = 0; 418 419 // add new resource to the resource list 420 if(ms->current) { 421 // before adding a new resource, the current resource must be closed 422 if(!ms->current->resource.isclosed) { 423 msresponse_close((WebdavResource*)ms->current); 424 } 425 ms->current->next = res; 426 } else { 427 ms->first = res; 428 } 429 ms->current = res; 430 431 return (WebdavResource*)res; 432 } 433 434 static int oklist_add( 435 pool_handle_t *pool, 436 PropertyOkList **begin, 437 PropertyOkList **end, 438 WebdavProperty *property, 439 WebdavNSList *nsdef) 440 { 441 PropertyOkList *newelm = pool_malloc(pool, sizeof(PropertyOkList)); 442 if(!newelm) { 443 return 1; 444 } 445 newelm->property = property; 446 newelm->nsdef = nsdef; 447 newelm->next = NULL; 448 if(*end) { 449 (*end)->next = newelm; 450 } else { 451 *begin = newelm; 452 } 453 *end = newelm; 454 return 0; 455 } 456 457 /* 458 * should only be called from msresponse_addproperty 459 * 460 * Adds a property to the error list with the specified statuscode 461 */ 462 static int msresponse_addproperror( 463 MSResponse *response, 464 WebdavProperty *property, 465 int statuscode) 466 { 467 pool_handle_t *pool = response->multistatus->sn->pool; 468 469 response->resource.err++; 470 471 // MSResponse contains a list of properties for each status code 472 // at first find the list for this status code 473 PropertyErrorList *errlist = NULL; 474 PropertyErrorList *list = response->errors; 475 PropertyErrorList *last = NULL; 476 while(list) { 477 if(list->status == statuscode) { 478 errlist = list; 479 break; 480 } 481 last = list; 482 list = list->next; 483 } 484 485 if(!errlist) { 486 // no list available for this statuscode 487 PropertyErrorList *newelm = pool_malloc(pool, 488 sizeof(PropertyErrorList)); 489 if(!newelm) { 490 return 1; 491 } 492 newelm->begin = NULL; 493 newelm->end = NULL; 494 newelm->next = NULL; 495 newelm->status = statuscode; 496 497 if(last) { 498 last->next = newelm; 499 } else { 500 response->errors = newelm; 501 } 502 errlist = newelm; 503 } 504 505 // we have the list -> add the new element 506 if(webdav_plist_add(pool, &errlist->begin, &errlist->end, property)) { 507 return 1; 508 } 509 return 0; 510 } 511 512 static CxHashKey ms_property_key( 513 CxAllocator *a, 514 const xmlChar *href, 515 const char *property_name) 516 { 517 cxmutstr key_data = cx_strcat_a(a, 3, cx_str((const char*)href), (cxstring){ "\0", 1 }, cx_str(property_name)); 518 return cx_hash_key_bytes((unsigned char*)key_data.ptr, key_data.length); 519 } 520 521 int msresponse_addproperty( 522 WebdavResource *res, 523 WebdavProperty *property, 524 int status) 525 { 526 MSResponse *response = (MSResponse*)res; 527 Session *sn = response->multistatus->sn; 528 if(response->resource.isclosed) { 529 log_ereport( 530 LOG_WARN, 531 "%s", 532 "webdav: cannot add property to closed response tag"); 533 return 0; 534 } 535 536 // some WebdavProperty checks to make sure nothing explodes 537 if(!property->namespace || !property->namespace->href) { 538 // error: namespace is required 539 log_ereport( 540 LOG_FAILURE, 541 "%s", 542 "webdav: property ''%s'' has no namespace", 543 property->name); 544 return 1; 545 } 546 547 // check if the property was already added to the resource 548 CxAllocator *a = pool_allocator(sn->pool); 549 CxHashKey key = ms_property_key(a, property->namespace->href, property->name); 550 if(cxMapGet(response->properties, key)) { 551 cxFree(a, (void*)key.data); 552 return 0; 553 } 554 if(cxMapPut(response->properties, key, property)) { 555 return 1; // OOM 556 } 557 cxFree(a, (void*)key.data); 558 559 // list of namespace definitions for this property 560 WebdavNSList *nsdef_begin = NULL; 561 WebdavNSList *nsdef_end = NULL; 562 563 // add namespace of this property to the namespace map 564 // the namespace map will be used for global namespace definitions 565 if(property->namespace->prefix) { 566 WSNamespace *ns = cxMapGet( 567 response->multistatus->namespaces, 568 cx_hash_key_str((const char*)property->namespace->prefix)); 569 if(!ns) { 570 // prefix is not in use -> we can add the namespace to the ns map 571 int err = cxMapPut( 572 response->multistatus->namespaces, 573 cx_hash_key_str((const char*)property->namespace->prefix), 574 property->namespace); 575 if(err) { 576 return 1; // OOM 577 } 578 } else if( 579 strcmp((const char*)property->namespace->href, 580 (const char*)ns->href)) 581 { 582 // global namespace != local namespace 583 // therefore we need a namespace definition in this element 584 585 // ns-prefix != property-prefix -> add ns to nsdef 586 if(webdav_nslist_add( 587 sn->pool, 588 &nsdef_begin, 589 &nsdef_end, 590 property->namespace)) 591 { 592 return 1; // OOM 593 } 594 } 595 } 596 597 if(response->multistatus->proppatch && response->errors) { 598 // in a proppatch request all operations must succeed 599 // if we have an error, the property update status code must be 600 // 424 Failed Dependency 601 status = 424; 602 } 603 604 // error properties will be added to a separate list 605 if(status != 200) { 606 return msresponse_addproperror(response, property, status); 607 } 608 609 // add all namespaces used by this property to the nsdef list 610 WebdavNSList *nslist = NULL; 611 if(property->vtype == WS_VALUE_XML_NODE) { 612 // iterate over xml tree and collect all namespaces 613 int err = 0; 614 nslist = wsxml_get_required_namespaces( 615 response->multistatus->sn->pool, 616 property->value.node, 617 &err); 618 if(err) { 619 return 1; // OOM 620 } 621 } else if(property->vtype == WS_VALUE_XML_DATA) { 622 // xml data contains a list of all used namespaces 623 nslist = property->value.data.namespaces; 624 } // other value types don't contain xml namespaces 625 626 while(nslist) { 627 // only add the namespace to the definitions list, if it isn't a 628 // property namespace, because the prop ns is already added 629 // to the element's def list or global definitions list 630 if(strcmp( 631 (const char*)nslist->namespace->prefix, 632 (const char*)property->namespace->prefix)) 633 { 634 // ns-prefix != property-prefix -> add ns to nsdef 635 if(webdav_nslist_add( 636 sn->pool, 637 &nsdef_begin, 638 &nsdef_end, 639 nslist->namespace)) 640 { 641 return 1; // OOM 642 } 643 } 644 nslist = nslist->next; 645 } 646 647 // add property to the list 648 if(oklist_add( 649 sn->pool, 650 &response->plist_begin, 651 &response->plist_end, 652 property, 653 nsdef_begin)) 654 { 655 return 1; 656 } 657 return 0; 658 } 659 660 int msresponse_close(WebdavResource *res) { 661 MSResponse *response = (MSResponse*)res; 662 if(response->closing) { 663 return 0; // close already in progress 664 } 665 response->closing = TRUE; 666 Multistatus *ms = response->multistatus; 667 668 int ret = REQ_PROCEED; 669 670 // PROPFIND: 671 // response_close will execute propfind_do of all remaining backends 672 // after that we will have all available properties 673 WebdavOperation *op = ms->response.op; 674 if(op->response_close(op, res)) { 675 ret = REQ_ABORTED; 676 } 677 678 // add missing properties with status code 404 679 CxAllocator *a = pool_allocator(ms->sn->pool); 680 WebdavPList *pl = ms->response.op->reqprops; 681 while(pl) { 682 CxHashKey key = ms_property_key(a, pl->property->namespace->href, pl->property->name); 683 if(!cxMapGet(response->properties, key)) { 684 // property was not added to this response 685 if(ms->proppatch) { 686 if(msresponse_addproperty(res, pl->property, 424)) { 687 ret = REQ_ABORTED; 688 break; 689 } 690 } else { 691 if(msresponse_addproperty(res, pl->property, 404)) { 692 ret = REQ_ABORTED; 693 break; 694 } 695 } 696 } 697 698 pl = pl->next; 699 } 700 701 if(ms->proppatch && response->errors) { 702 // a proppatch response must succeed entirely 703 // if we have a single error prop, move all props with status 200 704 // to the error list 705 PropertyOkList *elm = response->plist_begin; 706 PropertyOkList *nextelm; 707 while(elm) { 708 if(msresponse_addproperty(res, elm->property, 424)) { 709 return 1; 710 } 711 nextelm = elm->next; 712 pool_free(response->multistatus->sn->pool, elm); 713 elm = nextelm; 714 } 715 response->plist_begin = NULL; 716 response->plist_end = NULL; 717 } 718 719 // we don't need the properties anymore 720 cxMapDestroy(response->properties); 721 722 response->resource.isclosed = TRUE; 723 return ret; 724 } 725