/* * 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. */ #define LWS_DLL #define LWS_INTERNAL #include #include #include #include struct fobj { struct fobj *next; const char *name, *uri, *icon, *date; time_t m; unsigned long size; }; struct per_session_data__tbl_dir { struct fobj base; char strings[64 * 1024]; char reldir[256]; char *p; const char *dir; #if UV_VERSION_MAJOR > 0 uv_fs_event_t *event_req; #endif struct lws *wsi; }; #if UV_VERSION_MAJOR > 0 static void mon_cb(uv_fs_event_t *handle, const char *filename, int events, int status) { struct per_session_data__tbl_dir *pss = handle->data; //lwsl_notice("%s\n", __func__); if (pss && pss->wsi) lws_callback_on_writable(pss->wsi); } static void lws_uv_close_cb(uv_handle_t *handle) { free(handle); } static void lws_protocol_dir_kill_monitor(struct per_session_data__tbl_dir *pss) { if (!pss->event_req) return; pss->wsi = NULL; pss->event_req->data = NULL; uv_fs_event_stop(pss->event_req); uv_close((uv_handle_t *)pss->event_req, lws_uv_close_cb); pss->event_req = NULL; } #endif static int scan_dir(struct lws *wsi, struct per_session_data__tbl_dir *pss) { /* uuh travis... */ #if UV_VERSION_MAJOR > 0 uv_loop_t *loop = lws_uv_getloop(lws_get_context(wsi), 0); char *end = &(pss->strings[sizeof(pss->strings) - 1]); struct fobj *prev = &pss->base; char path[512], da[200]; const char *icon; uv_dirent_t dent; struct fobj *f; struct stat st; struct tm *tm; int ret = 0, n; uv_fs_t req; lws_protocol_dir_kill_monitor(pss); lws_snprintf(path, sizeof(path) - 1, "%s/%s", pss->dir, pss->reldir); //lwsl_notice("path = %s\n", path); pss->event_req = malloc(sizeof(*pss->event_req)); if (!pss->event_req) return 2; pss->wsi = wsi; pss->event_req->data = pss; uv_fs_event_init(lws_uv_getloop(lws_get_context(wsi), 0), pss->event_req); // The recursive flag watches subdirectories too. n = uv_fs_event_start(pss->event_req, mon_cb, path, UV_FS_EVENT_RECURSIVE); //lwsl_notice("monitoring %s (%d)\n", path, n); if (!uv_fs_scandir(loop, &req, path, 0, NULL)) { lwsl_err("Scandir on %s failed\n", path); return 2; } pss->p = pss->strings; while (uv_fs_scandir_next(&req, &dent) != UV_EOF) { lws_snprintf(path, sizeof(path) - 1, "%s/%s/%s", pss->dir, pss->reldir, dent.name); if (stat(path, &st)) { lwsl_info("unable to stat %s\n", path); continue; } f = malloc(sizeof(*f)); f->next = NULL; f->name = pss->p; n = lws_snprintf(pss->p, end - pss->p, "%s", dent.name); pss->p += n + 1; f->uri = NULL; if ((S_IFMT & st.st_mode) == S_IFDIR) { n = lws_snprintf(pss->p, end - pss->p, "=%s/%s", pss->reldir, dent.name); f->uri = pss->p; } if (lws_get_mimetype(dent.name, NULL)) { n = lws_snprintf(pss->p, end - pss->p, "./serve/%s/%s", pss->reldir, dent.name); f->uri = pss->p; } if (f->uri) pss->p += n + 1; if (end - pss->p < 100) { free(f); break; } icon = " "; if ((S_IFMT & st.st_mode) == S_IFDIR) icon = "📂"; f->icon = pss->p; n = lws_snprintf(pss->p, end - pss->p, "%s", icon); pss->p += n + 1; f->date = pss->p; tm = gmtime(&st.st_mtime); strftime(da, sizeof(da), "%Y-%b-%d %H:%M:%S %z", tm); n = lws_snprintf(pss->p, end - pss->p, "%s", da); pss->p += n + 1; f->size = st.st_size; f->m = st.st_mtime; prev->next = f; prev = f; } uv_fs_req_cleanup(&req); return ret; #else return 0; #endif } static void free_scan_dir(struct per_session_data__tbl_dir *pss) { struct fobj *f = pss->base.next, *f1; while (f) { f1 = f->next; free(f); f = f1; } pss->base.next = NULL; } static int callback_lws_table_dirlisting(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__tbl_dir *pss = (struct per_session_data__tbl_dir *)user; char j[LWS_PRE + 16384], *p = j + LWS_PRE, *start = p, *q, *q1, *w, *end = j + sizeof(j) - LWS_PRE, e[384], s[384], s1[384]; const struct lws_protocol_vhost_options *pmo; struct fobj *f; int n, first = 1; switch (reason) { case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */ break; case LWS_CALLBACK_ESTABLISHED: lwsl_debug("LWS_CALLBACK_ESTABLISHED\n"); /* * send client the lwsgt table layout */ start = "{\"cols\":[" " {\"name\": \"Date\"}," " {\"name\": \"Size\", \"align\": \"right\"}," " {\"name\": \"Icon\"}," " {\"name\": \"Name\", \"href\": \"uri\"}," " {\"name\": \"uri\", \"hide\": \"1\" }" " ]" "}"; if (lws_write(wsi, (unsigned char *)start, strlen(start), LWS_WRITE_TEXT) < 0) return -1; /* send a view update next */ lws_callback_on_writable(wsi); break; case LWS_CALLBACK_RECEIVE: if (len > sizeof(pss->reldir) - 1) len = sizeof(pss->reldir) - 1; if (!strstr(in, "..") && !strchr(in, '~')) lws_strncpy(pss->reldir, in, len + 1); else len = 0; pss->reldir[len] = '\0'; if (pss->reldir[0] == '/' && !pss->reldir[1]) pss->reldir[0] = '\0'; lwsl_info("%s\n", pss->reldir); lws_callback_on_writable(wsi); break; case LWS_CALLBACK_SERVER_WRITEABLE: if (scan_dir(wsi, pss)) return 1; p += lws_snprintf(p, end - p, "{\"breadcrumbs\":["); q = pss->reldir; if (!q[0]) p += lws_snprintf(p, end - p, "{\"name\":\"top\"}"); while (*q) { q1 = strchr(q, '/'); if (!q1) { if (first) strcpy(s, "top1"); else strcpy(s, q); s1[0] = '\0'; q += strlen(q); } else { n = lws_ptr_diff(q1, q); if (n > (int)sizeof(s) - 1) n = sizeof(s) - 1; if (first) { strcpy(s1, "/"); strcpy(s, "top"); } else { lws_strncpy(s, q, n + 1); n = lws_ptr_diff(q1, pss->reldir); if (n > (int)sizeof(s1) - 1) n = sizeof(s1) - 1; lws_strncpy(s1, pss->reldir, n + 1); } q = q1 + 1; } if (!first) p += lws_snprintf(p, end - p, ","); else first = 0; p += lws_snprintf(p, end - p, "{\"name\":\"%s\"", lws_json_purify(e, s, sizeof(e), NULL)); if (*q) { w = s1; while (w[0] == '/' && w[1] == '/') w++; p += lws_snprintf(p, end - p, ",\"url\":\"%s\"", lws_json_purify(e, w, sizeof(e), NULL)); } p += lws_snprintf(p, end - p, "}"); if (!q1) break; } p += lws_snprintf(p, end - p, "],\"data\":["); f = pss->base.next; while (f) { /* format in JSON */ p += lws_snprintf(p, end - p, "{\"Icon\":\"%s\",", lws_json_purify(e, f->icon, sizeof(e), NULL)); p += lws_snprintf(p, end - p, " \"Date\":\"%s\",", lws_json_purify(e, f->date, sizeof(e), NULL)); p += lws_snprintf(p, end - p, " \"Size\":\"%ld\",", f->size); if (f->uri) p += lws_snprintf(p, end - p, " \"uri\":\"%s\",", lws_json_purify(e, f->uri, sizeof(e), NULL)); p += lws_snprintf(p, end - p, " \"Name\":\"%s\"}", lws_json_purify(e, f->name, sizeof(e), NULL)); f = f->next; if (f) p += lws_snprintf(p, end - p, ","); } p += lws_snprintf(p, end - p, "]}"); free_scan_dir(pss); if (lws_write(wsi, (unsigned char *)start, p - start, LWS_WRITE_TEXT) < 0) return -1; break; case LWS_CALLBACK_HTTP_PMO: /* find the per-mount options we're interested in */ lwsl_debug("LWS_CALLBACK_HTTP_PMO\n"); pmo = (struct lws_protocol_vhost_options *)in; while (pmo) { if (!strcmp(pmo->name, "dir")) /* path to list files */ pss->dir = pmo->value; pmo = pmo->next; } if (!pss->dir[0]) { lwsl_err("dirlisting: \"dir\" pmo missing\n"); return 1; } break; case LWS_CALLBACK_HTTP_DROP_PROTOCOL: //lwsl_notice("LWS_CALLBACK_HTTP_DROP_PROTOCOL\n"); #if UV_VERSION_MAJOR > 0 lws_protocol_dir_kill_monitor(pss); #endif break; default: return 0; } return 0; } static const struct lws_protocols protocols[] = { { "protocol-lws-table-dirlisting", callback_lws_table_dirlisting, sizeof(struct per_session_data__tbl_dir), 0, }, }; LWS_VISIBLE int init_protocol_lws_table_dirlisting(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_lws_table_dirlisting(struct lws_context *context) { return 0; }