1/* 2 * libwebsockets - small server side websockets and web server implementation 3 * 4 * Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com> 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to 8 * deal in the Software without restriction, including without limitation the 9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 * sell copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 * IN THE SOFTWARE. 23 */ 24 25#if !defined (LWS_PLUGIN_STATIC) 26#if !defined(LWS_DLL) 27#define LWS_DLL 28#endif 29#if !defined(LWS_INTERNAL) 30#define LWS_INTERNAL 31#endif 32#include <libwebsockets.h> 33#endif 34 35#include <stdlib.h> 36#include <string.h> 37#include <fcntl.h> 38#include <sys/types.h> 39#include <sys/stat.h> 40#include <dirent.h> 41#ifdef WIN32 42#include <io.h> 43#endif 44#include <stdio.h> 45#include <errno.h> 46 47struct dir_entry { 48 lws_list_ptr next; /* sorted by mtime */ 49 char user[32]; 50 unsigned long long size; 51 time_t mtime; 52}; 53/* filename follows */ 54 55#define lp_to_dir_entry(p, _n) lws_list_ptr_container(p, struct dir_entry, _n) 56 57struct pss_deaddrop; 58 59struct vhd_deaddrop { 60 struct lws_context *context; 61 struct lws_vhost *vh; 62 const struct lws_protocols *protocol; 63 64 struct pss_deaddrop *pss_head; 65 66 const char *upload_dir; 67 68 struct lwsac *lwsac_head; 69 struct dir_entry *dire_head; 70 int filelist_version; 71 72 unsigned long long max_size; 73}; 74 75struct pss_deaddrop { 76 struct lws_spa *spa; 77 struct vhd_deaddrop *vhd; 78 struct lws *wsi; 79 char result[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE]; 80 char filename[256]; 81 char user[32]; 82 unsigned long long file_length; 83 lws_filefd_type fd; 84 int response_code; 85 86 struct pss_deaddrop *pss_list; 87 88 struct lwsac *lwsac_head; 89 struct dir_entry *dire; 90 int filelist_version; 91 92 uint8_t completed:1; 93 uint8_t sent_headers:1; 94 uint8_t sent_body:1; 95 uint8_t first:1; 96}; 97 98static const char * const param_names[] = { 99 "text", 100 "send", 101 "file", 102 "upload", 103}; 104 105enum enum_param_names { 106 EPN_TEXT, 107 EPN_SEND, 108 EPN_FILE, 109 EPN_UPLOAD, 110}; 111 112static int 113de_mtime_sort(lws_list_ptr a, lws_list_ptr b) 114{ 115 struct dir_entry *p1 = lp_to_dir_entry(a, next), 116 *p2 = lp_to_dir_entry(b, next); 117 118 return (int)(p2->mtime - p1->mtime); 119} 120 121static void 122start_sending_dir(struct pss_deaddrop *pss) 123{ 124 if (pss->vhd->lwsac_head) 125 lwsac_reference(pss->vhd->lwsac_head); 126 pss->lwsac_head = pss->vhd->lwsac_head; 127 pss->dire = pss->vhd->dire_head; 128 pss->filelist_version = pss->vhd->filelist_version; 129 pss->first = 1; 130} 131 132static int 133scan_upload_dir(struct vhd_deaddrop *vhd) 134{ 135 char filepath[256], subdir[3][128], *p; 136 struct lwsac *lwsac_head = NULL; 137 lws_list_ptr sorted_head = NULL; 138 int i, sp = 0, found = 0; 139 struct dir_entry *dire; 140 struct dirent *de; 141 size_t initial, m; 142 struct stat s; 143 DIR *dir[3]; 144 145 initial = strlen(vhd->upload_dir) + 1; 146 lws_strncpy(subdir[sp], vhd->upload_dir, sizeof(subdir[sp])); 147 dir[sp] = opendir(vhd->upload_dir); 148 if (!dir[sp]) { 149 lwsl_err("%s: Unable to walk upload dir '%s'\n", __func__, 150 vhd->upload_dir); 151 return -1; 152 } 153 154 do { 155 de = readdir(dir[sp]); 156 if (!de) { 157 closedir(dir[sp]); 158#if !defined(__COVERITY__) 159 if (!sp) 160#endif 161 break; 162#if !defined(__COVERITY__) 163 sp--; 164 continue; 165#endif 166 } 167 168 p = filepath; 169 170 for (i = 0; i <= sp; i++) 171 p += lws_snprintf(p, lws_ptr_diff_size_t((filepath + sizeof(filepath)), p), 172 "%s/", subdir[i]); 173 174 lws_snprintf(p, lws_ptr_diff_size_t((filepath + sizeof(filepath)), p), "%s", 175 de->d_name); 176 177 /* ignore temp files */ 178 if (de->d_name[strlen(de->d_name) - 1] == '~') 179 continue; 180#if defined(__COVERITY__) 181 s.st_size = 0; 182 s.st_mtime = 0; 183#else 184 /* coverity[toctou] */ 185 if (stat(filepath, &s)) 186 continue; 187 188 if (S_ISDIR(s.st_mode)) { 189 if (!strcmp(de->d_name, ".") || 190 !strcmp(de->d_name, "..")) 191 continue; 192 sp++; 193 if (sp == LWS_ARRAY_SIZE(dir)) { 194 lwsl_err("%s: Skipping too-deep subdir %s\n", 195 __func__, filepath); 196 sp--; 197 continue; 198 } 199 lws_strncpy(subdir[sp], de->d_name, sizeof(subdir[sp])); 200 dir[sp] = opendir(filepath); 201 if (!dir[sp]) { 202 lwsl_err("%s: Unable to open subdir '%s'\n", 203 __func__, filepath); 204 goto bail; 205 } 206 continue; 207 } 208#endif 209 210 m = strlen(filepath + initial) + 1; 211 dire = lwsac_use(&lwsac_head, sizeof(*dire) + m, 0); 212 if (!dire) { 213 lwsac_free(&lwsac_head); 214 215 goto bail; 216 } 217 218 dire->next = NULL; 219 dire->size = (unsigned long long)s.st_size; 220 dire->mtime = s.st_mtime; 221 dire->user[0] = '\0'; 222#if !defined(__COVERITY__) 223 if (sp) 224 lws_strncpy(dire->user, subdir[1], sizeof(dire->user)); 225#endif 226 227 found++; 228 229 memcpy(&dire[1], filepath + initial, m); 230 231 lws_list_ptr_insert(&sorted_head, &dire->next, de_mtime_sort); 232 } while (1); 233 234 /* the old lwsac continues to live while someone else is consuming it */ 235 if (vhd->lwsac_head) 236 lwsac_detach(&vhd->lwsac_head); 237 238 /* we replace it with the fresh one */ 239 vhd->lwsac_head = lwsac_head; 240 if (sorted_head) 241 vhd->dire_head = lp_to_dir_entry(sorted_head, next); 242 else 243 vhd->dire_head = NULL; 244 245 vhd->filelist_version++; 246 247 lwsl_info("%s: found %d\n", __func__, found); 248 249 lws_start_foreach_llp(struct pss_deaddrop **, ppss, vhd->pss_head) { 250 start_sending_dir(*ppss); 251 lws_callback_on_writable((*ppss)->wsi); 252 } lws_end_foreach_llp(ppss, pss_list); 253 254 return 0; 255 256bail: 257 while (sp >= 0) 258 closedir(dir[sp--]); 259 260 return -1; 261} 262 263static int 264file_upload_cb(void *data, const char *name, const char *filename, 265 char *buf, int _len, enum lws_spa_fileupload_states state) 266{ 267 struct pss_deaddrop *pss = (struct pss_deaddrop *)data; 268 char filename2[256]; 269 size_t len = (size_t)_len; 270 int n; 271 272 (void)n; 273 274 switch (state) { 275 case LWS_UFS_OPEN: 276 lws_urldecode(filename2, filename, sizeof(filename2) - 1); 277 lws_filename_purify_inplace(filename2); 278 if (pss->user[0]) { 279 lws_filename_purify_inplace(pss->user); 280 lws_snprintf(pss->filename, sizeof(pss->filename), 281 "%s/%s", pss->vhd->upload_dir, pss->user); 282 if (mkdir(pss->filename 283#if !defined(WIN32) 284 , 0700 285#endif 286 ) < 0) 287 lwsl_debug("%s: mkdir failed\n", __func__); 288 lws_snprintf(pss->filename, sizeof(pss->filename), 289 "%s/%s/%s~", pss->vhd->upload_dir, 290 pss->user, filename2); 291 } else 292 lws_snprintf(pss->filename, sizeof(pss->filename), 293 "%s/%s~", pss->vhd->upload_dir, filename2); 294 lwsl_notice("%s: filename '%s'\n", __func__, pss->filename); 295 296 pss->fd = (lws_filefd_type)(long long)lws_open(pss->filename, 297 O_CREAT | O_TRUNC | O_RDWR, 0600); 298 if (pss->fd == LWS_INVALID_FILE) { 299 pss->response_code = HTTP_STATUS_INTERNAL_SERVER_ERROR; 300 lwsl_err("%s: unable to open %s (errno %d)\n", __func__, 301 pss->filename, errno); 302 return -1; 303 } 304 break; 305 306 case LWS_UFS_FINAL_CONTENT: 307 case LWS_UFS_CONTENT: 308 if (len) { 309 pss->file_length += (unsigned int)len; 310 311 /* if the file length is too big, drop it */ 312 if (pss->file_length > pss->vhd->max_size) { 313 pss->response_code = 314 HTTP_STATUS_REQ_ENTITY_TOO_LARGE; 315 close((int)(lws_intptr_t)pss->fd); 316 pss->fd = LWS_INVALID_FILE; 317 unlink(pss->filename); 318 319 return -1; 320 } 321 322 if (pss->fd != LWS_INVALID_FILE) { 323 n = (int)write((int)(lws_intptr_t)pss->fd, buf, (unsigned int)len); 324 lwsl_debug("%s: write %d says %d\n", __func__, 325 (int)len, n); 326 lws_set_timeout(pss->wsi, PENDING_TIMEOUT_HTTP_CONTENT, 30); 327 } 328 } 329 if (state == LWS_UFS_CONTENT) 330 break; 331 332 if (pss->fd != LWS_INVALID_FILE) 333 close((int)(lws_intptr_t)pss->fd); 334 335 /* the temp filename without the ~ */ 336 lws_strncpy(filename2, pss->filename, sizeof(filename2)); 337 filename2[strlen(filename2) - 1] = '\0'; 338 if (rename(pss->filename, filename2) < 0) 339 lwsl_err("%s: unable to rename\n", __func__); 340 341 pss->fd = LWS_INVALID_FILE; 342 pss->response_code = HTTP_STATUS_OK; 343 scan_upload_dir(pss->vhd); 344 345 break; 346 case LWS_UFS_CLOSE: 347 break; 348 } 349 350 return 0; 351} 352 353/* 354 * returns length in bytes 355 */ 356 357static int 358format_result(struct pss_deaddrop *pss) 359{ 360 unsigned char *p, *start, *end; 361 362 p = (unsigned char *)pss->result + LWS_PRE; 363 start = p; 364 end = p + sizeof(pss->result) - LWS_PRE - 1; 365 366 p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), 367 "<!DOCTYPE html><html lang=\"en\"><head>" 368 "<meta charset=utf-8 http-equiv=\"Content-Language\" " 369 "content=\"en\"/>" 370 "</head>"); 371 p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), "</body></html>"); 372 373 return (int)lws_ptr_diff(p, start); 374} 375 376static int 377callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason, 378 void *user, void *in, size_t len) 379{ 380 struct vhd_deaddrop *vhd = (struct vhd_deaddrop *) 381 lws_protocol_vh_priv_get(lws_get_vhost(wsi), 382 lws_get_protocol(wsi)); 383 struct pss_deaddrop *pss = (struct pss_deaddrop *)user; 384 uint8_t buf[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE], 385 *start = &buf[LWS_PRE], *p = start, 386 *end = &buf[sizeof(buf) - 1]; 387 char fname[256], *wp; 388 const char *cp; 389 int n, m, was; 390 391 switch (reason) { 392 393 case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */ 394 lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), 395 lws_get_protocol(wsi), 396 sizeof(struct vhd_deaddrop)); 397 398 vhd = (struct vhd_deaddrop *) 399 lws_protocol_vh_priv_get(lws_get_vhost(wsi), 400 lws_get_protocol(wsi)); 401 if (!vhd) 402 return 0; 403 404 vhd->context = lws_get_context(wsi); 405 vhd->vh = lws_get_vhost(wsi); 406 vhd->protocol = lws_get_protocol(wsi); 407 vhd->max_size = 20 * 1024 * 1024; /* default without pvo */ 408 409 if (!lws_pvo_get_str(in, "max-size", &cp)) 410 vhd->max_size = (unsigned long long)atoll(cp); 411 if (lws_pvo_get_str(in, "upload-dir", &vhd->upload_dir)) { 412 lwsl_warn("%s: requires 'upload-dir' pvo\n", __func__); 413 return 0; 414 } 415 416 scan_upload_dir(vhd); 417 418 lwsl_notice(" deaddrop: vh %s, upload dir %s, max size %llu\n", 419 lws_get_vhost_name(vhd->vh), vhd->upload_dir, 420 vhd->max_size); 421 break; 422 423 case LWS_CALLBACK_PROTOCOL_DESTROY: 424 if (vhd) 425 lwsac_free(&vhd->lwsac_head); 426 break; 427 428 /* WS-related */ 429 430 case LWS_CALLBACK_ESTABLISHED: 431 pss->vhd = vhd; 432 pss->wsi = wsi; 433 /* add ourselves to the list of live pss held in the vhd */ 434 pss->pss_list = vhd->pss_head; 435 vhd->pss_head = pss; 436 437 m = lws_hdr_copy(wsi, pss->user, sizeof(pss->user), 438 WSI_TOKEN_HTTP_AUTHORIZATION); 439 if (m > 0) 440 lwsl_info("%s: basic auth user: %s\n", 441 __func__, pss->user); 442 else 443 pss->user[0] = '\0'; 444 445 start_sending_dir(pss); 446 lws_callback_on_writable(wsi); 447 return 0; 448 449 case LWS_CALLBACK_CLOSED: 450 if (pss->lwsac_head) 451 lwsac_unreference(&pss->lwsac_head); 452 /* remove our closing pss from the list of live pss */ 453 lws_start_foreach_llp(struct pss_deaddrop **, 454 ppss, vhd->pss_head) { 455 if (*ppss == pss) { 456 *ppss = pss->pss_list; 457 break; 458 } 459 } lws_end_foreach_llp(ppss, pss_list); 460 return 0; 461 462 case LWS_CALLBACK_RECEIVE: 463 /* we get this kind of thing {"del":"agreen/no-entry.svg"} */ 464 if (!pss || len < 10) 465 break; 466 467 if (strncmp((const char *)in, "{\"del\":\"", 8)) 468 break; 469 470 cp = strchr((const char *)in, '/'); 471 if (cp) { 472 n = (int)(((void *)cp - in)) - 8; 473 474 if ((int)strlen(pss->user) != n || 475 memcmp(pss->user, ((const char *)in) + 8, (unsigned int)n)) { 476 lwsl_notice("%s: del: auth mismatch " 477 " '%s' '%s' (%d)\n", 478 __func__, pss->user, 479 ((const char *)in) + 8, n); 480 break; 481 } 482 } 483 484 lws_strncpy(fname, ((const char *)in) + 8, sizeof(fname)); 485 lws_filename_purify_inplace(fname); 486 wp = strchr((const char *)fname, '\"'); 487 if (wp) 488 *wp = '\0'; 489 490 lws_snprintf((char *)buf, sizeof(buf), "%s/%s", vhd->upload_dir, 491 fname); 492 493 lwsl_notice("%s: del: path %s\n", __func__, (const char *)buf); 494 495 if (unlink((const char *)buf) < 0) 496 lwsl_err("%s: unlink %s failed\n", __func__, 497 (const char *)buf); 498 499 scan_upload_dir(vhd); 500 break; 501 502 case LWS_CALLBACK_SERVER_WRITEABLE: 503 if (pss->lwsac_head && !pss->dire) 504 return 0; 505 506 was = 0; 507 if (pss->first) { 508 p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), 509 "{\"max_size\":%llu, \"files\": [", 510 vhd->max_size); 511 was = 1; 512 } 513 514 m = 5; 515 while (m-- && pss->dire) { 516 p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), 517 "%c{\"name\":\"%s\", " 518 "\"size\":%llu," 519 "\"mtime\":%llu," 520 "\"yours\":%d}", 521 pss->first ? ' ' : ',', 522 (const char *)&pss->dire[1], 523 pss->dire->size, 524 (unsigned long long)pss->dire->mtime, 525 !strcmp(pss->user, pss->dire->user) && 526 pss->user[0]); 527 pss->first = 0; 528 pss->dire = lp_to_dir_entry(pss->dire->next, next); 529 } 530 531 if (!pss->dire) { 532 p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), 533 "]}"); 534 if (pss->lwsac_head) { 535 lwsac_unreference(&pss->lwsac_head); 536 pss->lwsac_head = NULL; 537 } 538 } 539 540 n = lws_write(wsi, start, lws_ptr_diff_size_t(p, start), 541 (enum lws_write_protocol)lws_write_ws_flags(LWS_WRITE_TEXT, was, 542 !pss->dire)); 543 if (n < 0) { 544 lwsl_notice("%s: ws write failed\n", __func__); 545 return 1; 546 } 547 if (pss->dire) { 548 lws_callback_on_writable(wsi); 549 550 return 0; 551 } 552 553 /* ie, we finished */ 554 555 if (pss->filelist_version != pss->vhd->filelist_version) { 556 lwsl_info("%s: restart send\n", __func__); 557 /* what we just sent is already out of date */ 558 start_sending_dir(pss); 559 lws_callback_on_writable(wsi); 560 } 561 562 return 0; 563 564 /* POST-related */ 565 566 case LWS_CALLBACK_HTTP_BODY: 567 568 /* create the POST argument parser if not already existing */ 569 if (!pss->spa) { 570 pss->vhd = vhd; 571 pss->wsi = wsi; 572 pss->spa = lws_spa_create(wsi, param_names, 573 LWS_ARRAY_SIZE(param_names), 574 1024, file_upload_cb, pss); 575 if (!pss->spa) 576 return -1; 577 578 pss->filename[0] = '\0'; 579 pss->file_length = 0; 580 /* catchall */ 581 pss->response_code = HTTP_STATUS_SERVICE_UNAVAILABLE; 582 583 m = lws_hdr_copy(wsi, pss->user, sizeof(pss->user), 584 WSI_TOKEN_HTTP_AUTHORIZATION); 585 if (m > 0) 586 lwsl_info("basic auth user: %s\n", pss->user); 587 else 588 pss->user[0] = '\0'; 589 } 590 591 /* let it parse the POST data */ 592 if (lws_spa_process(pss->spa, in, (int)len)) { 593 lwsl_notice("spa saw a problem\n"); 594 /* some problem happened */ 595 lws_spa_finalize(pss->spa); 596 597 pss->completed = 1; 598 lws_callback_on_writable(wsi); 599 } 600 break; 601 602 case LWS_CALLBACK_HTTP_BODY_COMPLETION: 603 /* call to inform no more payload data coming */ 604 lws_spa_finalize(pss->spa); 605 606 pss->completed = 1; 607 lws_callback_on_writable(wsi); 608 break; 609 610 case LWS_CALLBACK_HTTP_WRITEABLE: 611 if (!pss->completed) 612 break; 613 614 p = (unsigned char *)pss->result + LWS_PRE; 615 start = p; 616 end = p + sizeof(pss->result) - LWS_PRE - 1; 617 618 if (!pss->sent_headers) { 619 n = format_result(pss); 620 621 if (lws_add_http_header_status(wsi, 622 (unsigned int)pss->response_code, 623 &p, end)) 624 goto bail; 625 626 if (lws_add_http_header_by_token(wsi, 627 WSI_TOKEN_HTTP_CONTENT_TYPE, 628 (unsigned char *)"text/html", 9, 629 &p, end)) 630 goto bail; 631 if (lws_add_http_header_content_length(wsi, (lws_filepos_t)n, &p, end)) 632 goto bail; 633 if (lws_finalize_http_header(wsi, &p, end)) 634 goto bail; 635 636 /* first send the headers ... */ 637 n = lws_write(wsi, start, lws_ptr_diff_size_t(p, start), 638 LWS_WRITE_HTTP_HEADERS | 639 LWS_WRITE_H2_STREAM_END); 640 if (n < 0) 641 goto bail; 642 643 pss->sent_headers = 1; 644 lws_callback_on_writable(wsi); 645 break; 646 } 647 648 if (!pss->sent_body) { 649 n = format_result(pss); 650 n = lws_write(wsi, (unsigned char *)start, (unsigned int)n, 651 LWS_WRITE_HTTP_FINAL); 652 653 pss->sent_body = 1; 654 if (n < 0) { 655 lwsl_err("%s: writing body failed\n", __func__); 656 return 1; 657 } 658 goto try_to_reuse; 659 } 660 break; 661 662 case LWS_CALLBACK_HTTP_DROP_PROTOCOL: 663 /* called when our wsi user_space is going to be destroyed */ 664 if (pss->spa) { 665 lws_spa_destroy(pss->spa); 666 pss->spa = NULL; 667 } 668 break; 669 670 default: 671 break; 672 } 673 674 return 0; 675 676bail: 677 678 return 1; 679 680try_to_reuse: 681 if (lws_http_transaction_completed(wsi)) 682 return -1; 683 684 return 0; 685} 686 687#define LWS_PLUGIN_PROTOCOL_DEADDROP \ 688 { \ 689 "lws-deaddrop", \ 690 callback_deaddrop, \ 691 sizeof(struct pss_deaddrop), \ 692 1024, \ 693 0, NULL, 0 \ 694 } 695 696#if !defined (LWS_PLUGIN_STATIC) 697 698LWS_VISIBLE const struct lws_protocols deaddrop_protocols[] = { 699 LWS_PLUGIN_PROTOCOL_DEADDROP 700}; 701 702LWS_VISIBLE const lws_plugin_protocol_t deaddrop = { 703 .hdr = { 704 "deaddrop", 705 "lws_protocol_plugin", 706 LWS_BUILD_HASH, 707 LWS_PLUGIN_API_MAGIC 708 }, 709 710 .protocols = deaddrop_protocols, 711 .count_protocols = LWS_ARRAY_SIZE(deaddrop_protocols), 712 .extensions = NULL, 713 .count_extensions = 0, 714}; 715 716#endif 717