Skip to content

Commit e028532

Browse files
committed
deaddrop: Authenticated users only can delete files they uploaded
Also adds inotify for Linux server (which is most likely platform) Co-developed-by: Gemini 2.5 Pro
1 parent 5090346 commit e028532

2 files changed

Lines changed: 137 additions & 54 deletions

File tree

plugins/deaddrop/assets/deaddrop.js

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@
117117

118118
fetch("upload/" + lws_urlencode(displayName), {
119119
method: "POST",
120-
body: formData
120+
body: formData,
121+
credentials: "same-origin" /* Tells browser to send auth header */
121122
})
122123
.then((e) => { /* this just means we got a response code */
123124
var us = e.url.split("/"), ul = us[us.length - 1], n;
@@ -149,8 +150,16 @@
149150
}
150151

151152
function do_upload(file) {
152-
var formData = new FormData();
153-
formData.append("file", file);
153+
var formData = new FormData(),
154+
displayName = file.name;
155+
156+
if (!username) { // Do not allow unauthenticated file uploads
157+
alert("You must be logged in to upload files.");
158+
return;
159+
}
160+
161+
// The server is authoritative for the filename, we send the original.
162+
formData.append("file", file, displayName);
154163
_do_upload(formData, file.name, file.size);
155164
}
156165

@@ -180,7 +189,7 @@
180189
ts = d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' +
181190
pad(d.getDate()) + '_' + pad(d.getHours()) + '-' +
182191
pad(d.getMinutes()) + '-' + pad(d.getSeconds()),
183-
generated_filename, // to be created below
192+
generated_filename,
184193
formData = new FormData(), blob;
185194

186195
e.preventDefault();
@@ -191,8 +200,8 @@
191200
}
192201
clear_errors();
193202

194-
// Filename now prefixed with username
195-
generated_filename = username + '_' + ts + '.txt';
203+
// Server is authoritative for prefixing, just generate a unique name
204+
generated_filename = ts + '.txt';
196205

197206
blob = new Blob([content.value], { type: "text/plain" });
198207
formData.append("file", blob, generated_filename);
@@ -226,30 +235,27 @@
226235
upl_text.disabled = !content.value.length;
227236
}
228237

229-
function get_appropriate_ws_url(extra_url)
230-
{
231-
var pcol;
232-
var u = document.URL;
238+
function get_appropriate_ws_url(extra_url) {
239+
var pcol,
240+
url = new URL(document.URL);
233241

234-
/*
235-
* We open the websocket encrypted if this page came on an
236-
* https:// url itself, otherwise unencrypted
237-
*/
238-
239-
if (u.substring(0, 5) === "https") {
242+
if (url.protocol === "https:") {
240243
pcol = "wss://";
241-
u = u.substr(8);
242244
} else {
243245
pcol = "ws://";
244-
if (u.substring(0, 4) === "http")
245-
u = u.substr(7);
246246
}
247247

248-
u = u.split("/");
249-
250-
/* + "/xxx" bit is for IE10 workaround */
248+
var path = url.pathname;
249+
/*
250+
* If the path looks like it has a filename (eg, contains a '.'),
251+
* then get its parent directory. Otherwise, use the path as-is.
252+
* This makes it robust for vhost paths like /.../docrepo/ vs
253+
* /.../docrepo/index.html
254+
*/
255+
if (path.split('/').pop().indexOf('.') !== -1)
256+
path = path.substring(0, path.lastIndexOf('/') + 1);
251257

252-
return pcol + u[0] + "/" + extra_url;
258+
return pcol + url.host + path + extra_url;
253259
}
254260

255261
function new_ws(urlpath, protocol)
@@ -287,7 +293,6 @@
287293
dd.classList.remove("noconn");
288294
da.classList.remove("disa");
289295
};
290-
291296
ws.onmessage = function got_packet(msg) {
292297
var j = JSON.parse(msg.data), s = "", n,
293298
t = document.getElementById("dd-list");
@@ -302,11 +307,14 @@
302307
for (n = 0; n < j.files.length; n++) {
303308
var fullName = j.files[n].name;
304309
var displayName = fullName;
305-
var isOwner = username &&
306-
fullName.startsWith(username + "_");
310+
/*
311+
* The server is the single source of truth.
312+
* We trust the "yours" flag it sends us.
313+
*/
314+
var isOwner = j.files[n].yours;
307315

308316
// Strip username prefix for display if owner
309-
if (isOwner)
317+
if (isOwner === 1)
310318
displayName = fullName.substring(
311319
username.length + 1);
312320

@@ -317,7 +325,7 @@
317325
date.toDateString() + " " +
318326
date.toLocaleTimeString() + "</td><td>";
319327

320-
// Only show delete button if authenticated and owner
328+
/* Only show delete button if the server said we are the owner */
321329
if (isOwner)
322330
s += "<img id=\"d" + n +
323331
"\" class=\"delbtn\" file=\"" +

plugins/deaddrop/protocol_lws_deaddrop.c

Lines changed: 101 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,14 @@
4141
#ifdef WIN32
4242
#include <io.h>
4343
#endif
44-
#if !defined(WIN32)
44+
#if defined(__linux__)
4545
#include <limits.h>
4646
#endif
4747
#include <stdio.h>
4848
#include <errno.h>
49+
#if defined(__linux__)
50+
#include <sys/inotify.h>
51+
#endif
4952

5053
struct dir_entry {
5154
lws_list_ptr next; /* sorted by mtime */
@@ -73,6 +76,10 @@ struct vhd_deaddrop {
7376
int filelist_version;
7477

7578
unsigned long long max_size;
79+
80+
#if defined(__linux__)
81+
int inotify_fd;
82+
#endif
7683
};
7784

7885
struct pss_deaddrop {
@@ -81,7 +88,7 @@ struct pss_deaddrop {
8188
struct lws *wsi;
8289
char result[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE];
8390
char filename[256];
84-
char user[32];
91+
char user[64];
8592
unsigned long long file_length;
8693
lws_filefd_type fd;
8794
int response_code;
@@ -220,6 +227,9 @@ static int
220227
file_upload_cb(void *data, const char *name, const char *filename,
221228
char *buf, int _len, enum lws_spa_fileupload_states state)
222229
{
230+
lwsl_warn("%s: entered, state %d, pss->user: '%s'\n", __func__,
231+
state, ((struct pss_deaddrop *)data)->user);
232+
223233
struct pss_deaddrop *pss = (struct pss_deaddrop *)data;
224234
char filename2[256];
225235
size_t len = (size_t)_len;
@@ -229,19 +239,19 @@ file_upload_cb(void *data, const char *name, const char *filename,
229239

230240
switch (state) {
231241
case LWS_UFS_OPEN:
232-
/* Require an authenticated user to upload */
242+
/* REQUIRE an authenticated user on the upload POST itself */
233243
if (!pss->user[0]) {
234244
pss->response_code = HTTP_STATUS_FORBIDDEN;
235-
lwsl_warn("%s: unauthenticated upload forbidden\n",
236-
__func__);
245+
lwsl_wsi_warn(pss->wsi, "%s: no authenticated user (pss %p)\n", __func__, pss);
237246
return -1;
238247
}
239248

240249
lws_urldecode(filename2, filename, sizeof(filename2) - 1);
241250
lws_filename_purify_inplace(filename2);
242251
lws_filename_purify_inplace(pss->user);
243252

244-
/* New filename format: upload_dir/user_originalfilename~ */
253+
/* Server is authoritative: construct filename from authenticated
254+
* user and the base filename from the request. */
245255
lws_snprintf(pss->filename, sizeof(pss->filename),
246256
"%s/%s_%s~", pss->vhd->upload_dir,
247257
pss->user, filename2);
@@ -338,17 +348,48 @@ callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason,
338348
uint8_t buf[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE],
339349
*start = &buf[LWS_PRE], *p = start,
340350
*end = &buf[sizeof(buf) - 1];
341-
#if !defined(WIN32)
351+
#if defined(__linux__)
342352
char path[512], resolved_path[PATH_MAX];
343353
#else
344354
char path[512];
345355
#endif
346356
char fname[256], *wp;
347357
const char *cp;
348-
int n, m, was;
358+
int n, m, was, uri_len;
359+
char *uri_ptr;
360+
#if defined(__linux__)
361+
char ev_buf[1024];
362+
#endif
349363

350364
switch (reason) {
351365

366+
case LWS_CALLBACK_HTTP:
367+
{
368+
int meth;
369+
// pss->user[0] = '\0';
370+
371+
m = lws_hdr_copy(wsi, pss->user, sizeof(pss->user),
372+
WSI_TOKEN_HTTP_AUTHORIZATION);
373+
374+
if (m > 0)
375+
lwsl_wsi_warn(wsi, "%s: upload auth user (pss %p): '%s'\n",
376+
__func__, (void *)pss, pss->user);
377+
else
378+
lwsl_wsi_warn(wsi, "%s: HTTP: no auth (%d)\n", __func__, m);
379+
380+
381+
meth = lws_http_get_uri_and_method(wsi, &uri_ptr, &uri_len);
382+
if (meth != LWSHUMETH_POST || !uri_ptr)
383+
break;
384+
if (!strstr(uri_ptr, "/upload/"))
385+
break;
386+
387+
pss->vhd = vhd;
388+
pss->wsi = wsi;
389+
390+
break;
391+
}
392+
352393
case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */
353394
lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
354395
lws_get_protocol(wsi),
@@ -359,6 +400,9 @@ callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason,
359400
lws_get_protocol(wsi));
360401
if (!vhd)
361402
return 0;
403+
#if defined(__linux__)
404+
vhd->inotify_fd = -1;
405+
#endif
362406

363407
vhd->context = lws_get_context(wsi);
364408
vhd->vh = lws_get_vhost(wsi);
@@ -372,6 +416,27 @@ callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason,
372416
return 0;
373417
}
374418

419+
#if defined(__linux__)
420+
/*
421+
* Set up inotify on the upload dir and adopt it into the
422+
* lws event loop on our vhost, so we can be told about
423+
* external changes to the dir contents
424+
*/
425+
vhd->inotify_fd = inotify_init1(IN_NONBLOCK);
426+
if (vhd->inotify_fd >= 0) {
427+
if (inotify_add_watch(vhd->inotify_fd, vhd->upload_dir,
428+
IN_CLOSE_WRITE | IN_DELETE |
429+
IN_MOVED_FROM | IN_MOVED_TO) >= 0)
430+
lws_adopt_descriptor_vhost(vhd->vh,
431+
LWS_ADOPT_RAW_FILE_DESC,
432+
(lws_sock_file_fd_type)vhd->inotify_fd,
433+
vhd->protocol->name, NULL);
434+
else
435+
lwsl_err("%s: inotify_add_watch failed\n",
436+
__func__);
437+
}
438+
#endif
439+
375440
scan_upload_dir(vhd);
376441

377442
lwsl_notice(" deaddrop: vh %s, upload dir %s, max size %llu\n",
@@ -380,8 +445,22 @@ callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason,
380445
break;
381446

382447
case LWS_CALLBACK_PROTOCOL_DESTROY:
383-
if (vhd)
448+
if (vhd) {
384449
lwsac_free(&vhd->lwsac_head);
450+
#if defined(__linux__)
451+
if (vhd->inotify_fd != -1)
452+
close(vhd->inotify_fd);
453+
#endif
454+
}
455+
break;
456+
457+
case LWS_CALLBACK_RAW_RX_FILE:
458+
#if defined(__linux__)
459+
/* inotify has told us something changed in the upload dir */
460+
n = (int)read(lws_get_socket_fd(wsi), ev_buf, sizeof(ev_buf));
461+
lwsl_info("%s: inotify event (%d), rescanning upload dir\n", __func__, n);
462+
scan_upload_dir(vhd);
463+
#endif
385464
break;
386465

387466
/* WS-related */
@@ -395,11 +474,12 @@ callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason,
395474

396475
m = lws_hdr_copy(wsi, pss->user, sizeof(pss->user),
397476
WSI_TOKEN_HTTP_AUTHORIZATION);
477+
398478
if (m > 0)
399-
lwsl_info("%s: basic auth user: %s\n",
400-
__func__, pss->user);
479+
lwsl_wsi_warn(wsi, "%s: upload auth user (pss %p): '%s'\n",
480+
__func__, (void *)pss, pss->user);
401481
else
402-
pss->user[0] = '\0';
482+
lwsl_wsi_warn(wsi, "%s: HTTP: no auth (%d)\n", __func__, m);
403483

404484
start_sending_dir(pss);
405485
lws_callback_on_writable(wsi);
@@ -455,7 +535,7 @@ callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason,
455535
lws_snprintf(path, sizeof(path), "%s/%s", vhd->upload_dir,
456536
fname);
457537

458-
#if !defined(WIN32)
538+
#if defined(__linux__)
459539
if (!realpath(path, resolved_path)) {
460540
lwsl_warn("%s: delete: realpath failed %s\n", __func__, path);
461541
break;
@@ -492,6 +572,13 @@ callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason,
492572

493573
m = 5;
494574
while (m-- && pss->dire) {
575+
int is_yours = !strcmp(pss->user, pss->dire->user) &&
576+
pss->user[0];
577+
578+
lwsl_warn("[Deaddrop Debug] File: '%s', Owner: '%s', WS User: '%s', Is Yours: %d\n",
579+
(const char *)&pss->dire[1], pss->dire->user,
580+
pss->user, is_yours);
581+
495582
p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p),
496583
"%c{\"name\":\"%s\", "
497584
"\"size\":%llu,"
@@ -500,9 +587,7 @@ callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason,
500587
pss->first ? ' ' : ',',
501588
(const char *)&pss->dire[1],
502589
pss->dire->size,
503-
(unsigned long long)pss->dire->mtime,
504-
!strcmp(pss->user, pss->dire->user) &&
505-
pss->user[0]);
590+
(unsigned long long)pss->dire->mtime, is_yours);
506591
pss->first = 0;
507592
pss->dire = lp_to_dir_entry(pss->dire->next, next);
508593
}
@@ -546,8 +631,6 @@ callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason,
546631

547632
/* create the POST argument parser if not already existing */
548633
if (!pss->spa) {
549-
pss->vhd = vhd;
550-
pss->wsi = wsi;
551634
pss->spa = lws_spa_create(wsi, param_names,
552635
LWS_ARRAY_SIZE(param_names),
553636
1024, file_upload_cb, pss);
@@ -556,15 +639,7 @@ callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason,
556639

557640
pss->filename[0] = '\0';
558641
pss->file_length = 0;
559-
/* catchall */
560642
pss->response_code = HTTP_STATUS_SERVICE_UNAVAILABLE;
561-
562-
m = lws_hdr_copy(wsi, pss->user, sizeof(pss->user),
563-
WSI_TOKEN_HTTP_AUTHORIZATION);
564-
if (m > 0)
565-
lwsl_info("basic auth user: %s\n", pss->user);
566-
else
567-
pss->user[0] = '\0';
568643
}
569644

570645
/* let it parse the POST data */

0 commit comments

Comments
 (0)