mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-16 00:00:07 +01:00

Thanks to Fabrice Gilot for reporting the problem that led to uncovering this. Due to a misunderstanding of the return value of snprintf (it is not truncated according to the max size passed in) in several places relying on snprintf to truncate the length overflows are possible. This patch wraps snprintf with a new lws_snprintf() which does truncate its length to allow the buffer limiting scheme to work properly. All users should update with these fixes.
395 lines
9 KiB
C
395 lines
9 KiB
C
/*
|
|
* ws protocol handler plugin for dirlisting "generic table" demo
|
|
*
|
|
* Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public
|
|
* License as published by the Free Software Foundation:
|
|
* version 2.1 of the License.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301 USA
|
|
*/
|
|
|
|
#define LWS_DLL
|
|
#define LWS_INTERNAL
|
|
#include "../lib/libwebsockets.h"
|
|
|
|
#include <string.h>
|
|
#include <uv.h>
|
|
|
|
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, '~'))
|
|
strncpy(pss->reldir, in, len);
|
|
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 = q1 - q;
|
|
if (n > sizeof(s) - 1)
|
|
n = sizeof(s) - 1;
|
|
if (first) {
|
|
strcpy(s1, "/");
|
|
strcpy(s, "top");
|
|
} else {
|
|
strncpy(s, q, n);
|
|
s[n] = '\0';
|
|
|
|
n = q1 - pss->reldir;
|
|
if (n > sizeof(s1) - 1)
|
|
n = sizeof(s1) - 1;
|
|
strncpy(s1, pss->reldir, n);
|
|
s1[n] = '\0';
|
|
}
|
|
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)));
|
|
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)));
|
|
}
|
|
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)));
|
|
p += lws_snprintf(p, end - p, " \"Date\":\"%s\",",
|
|
lws_json_purify(e, f->date, sizeof(e)));
|
|
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)));
|
|
p += lws_snprintf(p, end - p, " \"Name\":\"%s\"}",
|
|
lws_json_purify(e, f->name, sizeof(e)));
|
|
|
|
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_EXTERN 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 = ARRAY_SIZE(protocols);
|
|
c->extensions = NULL;
|
|
c->count_extensions = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
LWS_EXTERN LWS_VISIBLE int
|
|
destroy_protocol_lws_table_dirlisting(struct lws_context *context)
|
|
{
|
|
return 0;
|
|
}
|