blob: 9431a0fe38d0adfe03f4b96ddab9ebdf1a98b357 [file] [log] [blame]
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
*
* 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 <libwebsockets.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#ifdef WIN32
#include <io.h>
#endif
#include <stdio.h>
#include <errno.h>
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,
"<!DOCTYPE html><html lang=\"en\"><head>"
"<meta charset=utf-8 http-equiv=\"Content-Language\" "
"content=\"en\"/>"
"</head>");
p += lws_snprintf((char *)p, end - p, "</body></html>");
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