/* * libwebsockets - small server side websockets and web server implementation * * Copyright (C) 2010 - 2019 Andy Green * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #if !defined (LWS_PLUGIN_STATIC) #define LWS_DLL #define LWS_INTERNAL #include #endif #include #include #include #include #include #include #ifdef WIN32 #include #endif #include #include struct dir_entry { lws_list_ptr next; /* sorted by mtime */ char user[32]; unsigned long long size; time_t mtime; }; /* filename follows */ #define lp_to_dir_entry(p, _n) lws_list_ptr_container(p, struct dir_entry, _n) struct pss_deaddrop; struct vhd_deaddrop { struct lws_context *context; struct lws_vhost *vh; const struct lws_protocols *protocol; struct pss_deaddrop *pss_head; const char *upload_dir; struct lwsac *lwsac_head; struct dir_entry *dire_head; int filelist_version; unsigned long long max_size; }; struct pss_deaddrop { struct lws_spa *spa; struct vhd_deaddrop *vhd; struct lws *wsi; char result[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE]; char filename[256]; char user[32]; unsigned long long file_length; lws_filefd_type fd; int response_code; struct pss_deaddrop *pss_list; struct lwsac *lwsac_head; struct dir_entry *dire; int filelist_version; uint8_t completed:1; uint8_t sent_headers:1; uint8_t sent_body:1; uint8_t first:1; }; static const char * const param_names[] = { "text", "send", "file", "upload", }; enum enum_param_names { EPN_TEXT, EPN_SEND, EPN_FILE, EPN_UPLOAD, }; static int de_mtime_sort(lws_list_ptr a, lws_list_ptr b) { struct dir_entry *p1 = lp_to_dir_entry(a, next), *p2 = lp_to_dir_entry(b, next); return (int)(p2->mtime - p1->mtime); } static void start_sending_dir(struct pss_deaddrop *pss) { if (pss->vhd->lwsac_head) lwsac_reference(pss->vhd->lwsac_head); pss->lwsac_head = pss->vhd->lwsac_head; pss->dire = pss->vhd->dire_head; pss->filelist_version = pss->vhd->filelist_version; pss->first = 1; } static int scan_upload_dir(struct vhd_deaddrop *vhd) { char filepath[256], subdir[3][128], *p; int m, sp = 0, initial, found = 0; struct lwsac *lwsac_head = NULL; lws_list_ptr sorted_head = NULL; struct dir_entry *dire; struct dirent *de; struct stat s; DIR *dir[3]; initial = strlen(vhd->upload_dir) + 1; lws_strncpy(subdir[sp], vhd->upload_dir, sizeof(subdir[sp])); dir[sp] = opendir(vhd->upload_dir); if (!dir[sp]) { lwsl_err("%s: Unable to walk upload dir '%s'\n", __func__, vhd->upload_dir); return -1; } do { de = readdir(dir[sp]); if (!de) { closedir(dir[sp]); #if !defined(__COVERITY__) if (!sp) #endif break; #if !defined(__COVERITY__) sp--; continue; #endif } p = filepath; for (m = 0; m <= sp; m++) p += lws_snprintf(p, (filepath + sizeof(filepath)) - p, "%s/", subdir[m]); lws_snprintf(p, (filepath + sizeof(filepath)) - p, "%s", de->d_name); /* ignore temp files */ if (de->d_name[strlen(de->d_name) - 1] == '~') continue; #if defined(__COVERITY__) s.st_size = 0; s.st_mtime = 0; #else /* coverity[toctou] */ if (stat(filepath, &s)) continue; if (S_ISDIR(s.st_mode)) { if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) continue; sp++; if (sp == LWS_ARRAY_SIZE(dir)) { lwsl_err("%s: Skipping too-deep subdir %s\n", __func__, filepath); sp--; continue; } lws_strncpy(subdir[sp], de->d_name, sizeof(subdir[sp])); dir[sp] = opendir(filepath); if (!dir[sp]) { lwsl_err("%s: Unable to open subdir '%s'\n", __func__, filepath); goto bail; } continue; } #endif m = strlen(filepath + initial) + 1; dire = lwsac_use(&lwsac_head, sizeof(*dire) + m, 0); if (!dire) { lwsac_free(&lwsac_head); goto bail; } dire->next = NULL; dire->size = s.st_size; dire->mtime = s.st_mtime; dire->user[0] = '\0'; #if !defined(__COVERITY__) if (sp) lws_strncpy(dire->user, subdir[1], sizeof(dire->user)); #endif found++; memcpy(&dire[1], filepath + initial, m); lws_list_ptr_insert(&sorted_head, &dire->next, de_mtime_sort); } while (1); /* the old lwsac continues to live while someone else is consuming it */ if (vhd->lwsac_head) lwsac_detach(&vhd->lwsac_head); /* we replace it with the fresh one */ vhd->lwsac_head = lwsac_head; if (sorted_head) vhd->dire_head = lp_to_dir_entry(sorted_head, next); else vhd->dire_head = NULL; vhd->filelist_version++; lwsl_info("%s: found %d\n", __func__, found); lws_start_foreach_llp(struct pss_deaddrop **, ppss, vhd->pss_head) { start_sending_dir(*ppss); lws_callback_on_writable((*ppss)->wsi); } lws_end_foreach_llp(ppss, pss_list); return 0; bail: while (sp >= 0) closedir(dir[sp--]); return -1; } static int file_upload_cb(void *data, const char *name, const char *filename, char *buf, int len, enum lws_spa_fileupload_states state) { struct pss_deaddrop *pss = (struct pss_deaddrop *)data; char filename2[256]; int n; (void)n; switch (state) { case LWS_UFS_OPEN: lws_urldecode(filename2, filename, sizeof(filename2) - 1); lws_filename_purify_inplace(filename2); if (pss->user[0]) { lws_filename_purify_inplace(pss->user); lws_snprintf(pss->filename, sizeof(pss->filename), "%s/%s", pss->vhd->upload_dir, pss->user); if (mkdir(pss->filename #if !defined(WIN32) , 0700 #endif ) < 0) lwsl_debug("%s: mkdir failed\n", __func__); lws_snprintf(pss->filename, sizeof(pss->filename), "%s/%s/%s~", pss->vhd->upload_dir, pss->user, filename2); } else lws_snprintf(pss->filename, sizeof(pss->filename), "%s/%s~", pss->vhd->upload_dir, filename2); lwsl_notice("%s: filename '%s'\n", __func__, pss->filename); pss->fd = (lws_filefd_type)(long long)lws_open(pss->filename, O_CREAT | O_TRUNC | O_RDWR, 0600); if (pss->fd == LWS_INVALID_FILE) { pss->response_code = HTTP_STATUS_INTERNAL_SERVER_ERROR; lwsl_err("%s: unable to open %s (errno %d)\n", __func__, pss->filename, errno); return -1; } break; case LWS_UFS_FINAL_CONTENT: case LWS_UFS_CONTENT: if (len) { pss->file_length += len; /* if the file length is too big, drop it */ if (pss->file_length > pss->vhd->max_size) { pss->response_code = HTTP_STATUS_REQ_ENTITY_TOO_LARGE; close((int)(long long)pss->fd); pss->fd = LWS_INVALID_FILE; unlink(pss->filename); return -1; } if (pss->fd != LWS_INVALID_FILE) { n = write((int)(long long)pss->fd, buf, len); lwsl_debug("%s: write %d says %d\n", __func__, len, n); lws_set_timeout(pss->wsi, PENDING_TIMEOUT_HTTP_CONTENT, 30); } } if (state == LWS_UFS_CONTENT) break; if (pss->fd != LWS_INVALID_FILE) close((int)(long long)pss->fd); /* the temp filename without the ~ */ lws_strncpy(filename2, pss->filename, sizeof(filename2)); filename2[strlen(filename2) - 1] = '\0'; if (rename(pss->filename, filename2) < 0) lwsl_err("%s: unable to rename\n", __func__); pss->fd = LWS_INVALID_FILE; pss->response_code = HTTP_STATUS_OK; scan_upload_dir(pss->vhd); break; case LWS_UFS_CLOSE: break; } return 0; } /* * returns length in bytes */ static int format_result(struct pss_deaddrop *pss) { unsigned char *p, *start, *end; p = (unsigned char *)pss->result + LWS_PRE; start = p; end = p + sizeof(pss->result) - LWS_PRE - 1; p += lws_snprintf((char *)p, end -p, "" "" ""); p += lws_snprintf((char *)p, end - p, ""); return (int)lws_ptr_diff(p, start); } static int callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct vhd_deaddrop *vhd = (struct vhd_deaddrop *) lws_protocol_vh_priv_get(lws_get_vhost(wsi), lws_get_protocol(wsi)); struct pss_deaddrop *pss = (struct pss_deaddrop *)user; uint8_t buf[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE], *start = &buf[LWS_PRE], *p = start, *end = &buf[sizeof(buf) - LWS_PRE - 1]; char fname[256], *wp; const char *cp; int n, m, was; switch (reason) { case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */ lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), lws_get_protocol(wsi), sizeof(struct vhd_deaddrop)); vhd = (struct vhd_deaddrop *) lws_protocol_vh_priv_get(lws_get_vhost(wsi), lws_get_protocol(wsi)); vhd->context = lws_get_context(wsi); vhd->vh = lws_get_vhost(wsi); vhd->protocol = lws_get_protocol(wsi); vhd->max_size = 20 * 1024 * 1024; /* default without pvo */ if (!lws_pvo_get_str(in, "max-size", &cp)) vhd->max_size = atoll(cp); if (lws_pvo_get_str(in, "upload-dir", &vhd->upload_dir)) { lwsl_err("%s: requires 'upload-dir' pvo\n", __func__); return -1; } scan_upload_dir(vhd); lwsl_notice(" deaddrop: vh %s, upload dir %s, max size %llu\n", lws_get_vhost_name(vhd->vh), vhd->upload_dir, vhd->max_size); break; case LWS_CALLBACK_PROTOCOL_DESTROY: lwsac_free(&vhd->lwsac_head); break; /* WS-related */ case LWS_CALLBACK_ESTABLISHED: pss->vhd = vhd; pss->wsi = wsi; /* add ourselves to the list of live pss held in the vhd */ pss->pss_list = vhd->pss_head; vhd->pss_head = pss; m = lws_hdr_copy(wsi, pss->user, sizeof(pss->user), WSI_TOKEN_HTTP_AUTHORIZATION); if (m > 0) lwsl_info("%s: basic auth user: %s\n", __func__, pss->user); else pss->user[0] = '\0'; start_sending_dir(pss); lws_callback_on_writable(wsi); return 0; case LWS_CALLBACK_CLOSED: if (pss->lwsac_head) lwsac_unreference(&pss->lwsac_head); /* remove our closing pss from the list of live pss */ lws_start_foreach_llp(struct pss_deaddrop **, ppss, vhd->pss_head) { if (*ppss == pss) { *ppss = pss->pss_list; break; } } lws_end_foreach_llp(ppss, pss_list); return 0; case LWS_CALLBACK_RECEIVE: /* we get this kind of thing {"del":"agreen/no-entry.svg"} */ if (!pss || len < 10) break; if (strncmp((const char *)in, "{\"del\":\"", 8)) break; cp = strchr((const char *)in, '/'); if (cp) { n = ((void *)cp - in) - 8; if ((int)strlen(pss->user) != n || memcmp(pss->user, ((const char *)in) + 8, n)) { lwsl_notice("%s: del: auth mismatch " " '%s' '%s' (%d)\n", __func__, pss->user, ((const char *)in) + 8, n); break; } } lws_strncpy(fname, ((const char *)in) + 8, sizeof(fname)); lws_filename_purify_inplace(fname); wp = strchr((const char *)fname, '\"'); if (wp) *wp = '\0'; lws_snprintf((char *)buf, sizeof(buf), "%s/%s", vhd->upload_dir, fname); lwsl_notice("%s: del: path %s\n", __func__, (const char *)buf); if (unlink((const char *)buf) < 0) lwsl_err("%s: unlink %s failed\n", __func__, (const char *)buf); scan_upload_dir(vhd); break; case LWS_CALLBACK_SERVER_WRITEABLE: if (pss->lwsac_head && !pss->dire) return 0; was = 0; if (pss->first) { p += lws_snprintf((char *)p, lws_ptr_diff(end, p), "{\"max_size\":%llu, \"files\": [", vhd->max_size); was = 1; } m = 5; while (m-- && pss->dire) { p += lws_snprintf((char *)p, lws_ptr_diff(end, p), "%c{\"name\":\"%s\", " "\"size\":%llu," "\"mtime\":%llu," "\"yours\":%d}", pss->first ? ' ' : ',', (const char *)&pss->dire[1], pss->dire->size, (unsigned long long)pss->dire->mtime, !strcmp(pss->user, pss->dire->user) && pss->user[0]); pss->first = 0; pss->dire = lp_to_dir_entry(pss->dire->next, next); } if (!pss->dire) { p += lws_snprintf((char *)p, lws_ptr_diff(end, p), "]}"); if (pss->lwsac_head) { lwsac_unreference(&pss->lwsac_head); pss->lwsac_head = NULL; } } n = lws_write(wsi, start, lws_ptr_diff(p, start), lws_write_ws_flags(LWS_WRITE_TEXT, was, !pss->dire)); if (n < 0) { lwsl_notice("%s: ws write failed\n", __func__); return 1; } if (pss->dire) { lws_callback_on_writable(wsi); return 0; } /* ie, we finished */ if (pss->filelist_version != pss->vhd->filelist_version) { lwsl_info("%s: restart send\n", __func__); /* what we just sent is already out of date */ start_sending_dir(pss); lws_callback_on_writable(wsi); } return 0; /* POST-related */ case LWS_CALLBACK_HTTP_BODY: /* create the POST argument parser if not already existing */ if (!pss->spa) { pss->vhd = vhd; pss->wsi = wsi; pss->spa = lws_spa_create(wsi, param_names, LWS_ARRAY_SIZE(param_names), 1024, file_upload_cb, pss); if (!pss->spa) return -1; pss->filename[0] = '\0'; pss->file_length = 0; /* catchall */ pss->response_code = HTTP_STATUS_SERVICE_UNAVAILABLE; m = lws_hdr_copy(wsi, pss->user, sizeof(pss->user), WSI_TOKEN_HTTP_AUTHORIZATION); if (m > 0) lwsl_info("basic auth user: %s\n", pss->user); else pss->user[0] = '\0'; } /* let it parse the POST data */ if (lws_spa_process(pss->spa, in, (int)len)) { lwsl_notice("spa saw a problem\n"); /* some problem happened */ lws_spa_finalize(pss->spa); pss->completed = 1; lws_callback_on_writable(wsi); } break; case LWS_CALLBACK_HTTP_BODY_COMPLETION: /* call to inform no more payload data coming */ lws_spa_finalize(pss->spa); pss->completed = 1; lws_callback_on_writable(wsi); break; case LWS_CALLBACK_HTTP_WRITEABLE: if (!pss->completed) break; p = (unsigned char *)pss->result + LWS_PRE; start = p; end = p + sizeof(pss->result) - LWS_PRE - 1; if (!pss->sent_headers) { n = format_result(pss); if (lws_add_http_header_status(wsi, pss->response_code, &p, end)) goto bail; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"text/html", 9, &p, end)) goto bail; if (lws_add_http_header_content_length(wsi, n, &p, end)) goto bail; if (lws_finalize_http_header(wsi, &p, end)) goto bail; /* first send the headers ... */ n = lws_write(wsi, start, lws_ptr_diff(p, start), LWS_WRITE_HTTP_HEADERS | LWS_WRITE_H2_STREAM_END); if (n < 0) goto bail; pss->sent_headers = 1; lws_callback_on_writable(wsi); break; } if (!pss->sent_body) { n = format_result(pss); n = lws_write(wsi, (unsigned char *)start, n, LWS_WRITE_HTTP_FINAL); pss->sent_body = 1; if (n < 0) { lwsl_err("%s: writing body failed\n", __func__); return 1; } goto try_to_reuse; } break; case LWS_CALLBACK_HTTP_DROP_PROTOCOL: /* called when our wsi user_space is going to be destroyed */ if (pss->spa) { lws_spa_destroy(pss->spa); pss->spa = NULL; } break; default: break; } return 0; bail: return 1; try_to_reuse: if (lws_http_transaction_completed(wsi)) return -1; return 0; } #define LWS_PLUGIN_PROTOCOL_DEADDROP \ { \ "lws-deaddrop", \ callback_deaddrop, \ sizeof(struct pss_deaddrop), \ 1024, \ 0, NULL, 0 \ } #if !defined (LWS_PLUGIN_STATIC) static const struct lws_protocols protocols[] = { LWS_PLUGIN_PROTOCOL_DEADDROP }; LWS_VISIBLE int init_protocol_deaddrop(struct lws_context *context, struct lws_plugin_capability *c) { if (c->api_magic != LWS_PLUGIN_API_MAGIC) { lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC, c->api_magic); return 1; } c->protocols = protocols; c->count_protocols = LWS_ARRAY_SIZE(protocols); c->extensions = NULL; c->count_extensions = 0; return 0; } LWS_VISIBLE int destroy_protocol_deaddrop(struct lws_context *context) { return 0; } #endif