tlsd 2.18.1 → 2.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scaffold/static/index.html +1 -0
- package/tlsd.js +49 -20
package/package.json
CHANGED
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
const uploadURL = '/';
|
|
46
46
|
xhr.open( 'PUT', uploadURL, true );
|
|
47
47
|
xhr.setRequestHeader( 'Content-Type', file.type || 'application/octet-stream' );
|
|
48
|
+
xhr.setRequestHeader( 'X-Filename', file.name );
|
|
48
49
|
xhr.onload = () => {
|
|
49
50
|
if( xhr.status === 200 ) {
|
|
50
51
|
let result = JSON.parse( xhr.responseText );
|
package/tlsd.js
CHANGED
|
@@ -20,6 +20,15 @@ const { D, V, I, W, E } = L;
|
|
|
20
20
|
const UPLOAD_DIR = process.env.UPLOAD_DIR || "/tmp/tlsd-put-files/";
|
|
21
21
|
const UPLOAD_MAX_BYTES = toInt( process.env.UPLOAD_MAX_BYTES ) || ( 200 * 1024 * 1024 );
|
|
22
22
|
|
|
23
|
+
function sanitize_upload_filename( raw ) {
|
|
24
|
+
if( ! raw || typeof raw !== "string" ) return "file";
|
|
25
|
+
const basename = path.basename( raw.trim() );
|
|
26
|
+
const sanitized = basename.replace( /[\\/:*?"<>|\x00-\x1f]/g, "_" ).replace( /\.{2,}/g, "." );
|
|
27
|
+
// Limit to the LAST 40 chars of the sanitized filename
|
|
28
|
+
const truncated = sanitized.slice( -40 );
|
|
29
|
+
return truncated || "file";
|
|
30
|
+
}
|
|
31
|
+
|
|
23
32
|
// DOS protection configuration
|
|
24
33
|
const MAX_RPC_MESSAGE_SIZE = toInt( process.env.MAX_RPC_MESSAGE_SIZE ) || ( 1024 * 1024 ); // 1MB default
|
|
25
34
|
const MAX_WS_CONNECTIONS = toInt( process.env.MAX_WS_CONNECTIONS ) || 1000;
|
|
@@ -184,7 +193,7 @@ function make_cookie_setters( res ) {
|
|
|
184
193
|
// 4) Falls back to legacy directory loader at "root/rpc"
|
|
185
194
|
// If loading succeeds, passes the message to the handler.
|
|
186
195
|
// context may be one of:
|
|
187
|
-
// { transport_type: "WS", connection: {
|
|
196
|
+
// { transport_type: "WS", connection: { name, socket, send, cookies, resource }, ... }
|
|
188
197
|
// { transport_type: "GET", connection: { req, res } }
|
|
189
198
|
// { transport_type: "POST", connection: { req, res } }
|
|
190
199
|
// Additionally, context.upload_dir is set to the server's upload directory.
|
|
@@ -420,36 +429,47 @@ function csrf_protection( root ) {
|
|
|
420
429
|
return;
|
|
421
430
|
}
|
|
422
431
|
|
|
423
|
-
//
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
if( ! cookieToken || ! headerToken || cookieToken !== headerToken ) {
|
|
428
|
-
W( "CSRF token validation failed for " + method + " " + url );
|
|
429
|
-
res.writeHead( 403, {
|
|
430
|
-
"Content-Type": "application/json",
|
|
431
|
-
"Cache-Control": "no-store",
|
|
432
|
-
} );
|
|
433
|
-
res.write( o2j( { error: "CSRF token validation failed" } ) );
|
|
434
|
-
res.end();
|
|
432
|
+
// In dev mode, skip CSRF validation for PUT (e.g. drag-drop file uploads)
|
|
433
|
+
if( dev_mode && is_put ) {
|
|
434
|
+
next();
|
|
435
435
|
return;
|
|
436
436
|
}
|
|
437
437
|
|
|
438
|
-
|
|
438
|
+
const cookieToken = cookies.csrf_token;
|
|
439
|
+
const headerToken = req.headers[ "x-csrf-token" ];
|
|
440
|
+
|
|
441
|
+
// Origin/Referer validation
|
|
439
442
|
const origin = req.headers[ "origin" ];
|
|
440
443
|
const referer = req.headers[ "referer" ];
|
|
441
444
|
let originValid = false;
|
|
442
445
|
|
|
443
446
|
if( origin ) {
|
|
444
|
-
// Check if origin matches host (with protocol)
|
|
445
447
|
const expectedOrigin = ( dev_mode ? "http://" : "https://" ) + host;
|
|
446
448
|
originValid = origin === expectedOrigin;
|
|
447
449
|
} else if( referer ) {
|
|
448
|
-
// Fall back to Referer if Origin is missing
|
|
449
450
|
const expectedReferer = ( dev_mode ? "http://" : "https://" ) + host;
|
|
450
451
|
originValid = referer.startsWith( expectedReferer );
|
|
451
452
|
}
|
|
452
453
|
|
|
454
|
+
// For PUT (e.g. file uploads), cookie + Origin/Referer is sufficient.
|
|
455
|
+
// Same-origin XHR sends these automatically; no client-side token handling needed.
|
|
456
|
+
if( is_put && ! is_rpc && cookieToken && originValid ) {
|
|
457
|
+
next();
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Validate CSRF token (double-submit cookie pattern)
|
|
462
|
+
if( ! cookieToken || ! headerToken || cookieToken !== headerToken ) {
|
|
463
|
+
W( "CSRF token validation failed for " + method + " " + url );
|
|
464
|
+
res.writeHead( 403, {
|
|
465
|
+
"Content-Type": "application/json",
|
|
466
|
+
"Cache-Control": "no-store",
|
|
467
|
+
} );
|
|
468
|
+
res.write( o2j( { error: "CSRF token validation failed" } ) );
|
|
469
|
+
res.end();
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
453
473
|
if( ! originValid ) {
|
|
454
474
|
W( "CSRF Origin/Referer validation failed for " + method + " " + url + " origin: " + origin + " referer: " + referer );
|
|
455
475
|
res.writeHead( 403, {
|
|
@@ -582,8 +602,17 @@ function put_handler( req, res, next ) {
|
|
|
582
602
|
fs.mkdirSync( local_path, { recursive: true } );
|
|
583
603
|
|
|
584
604
|
// generate random hash to store file under locally
|
|
585
|
-
const hash = sha1( "" + ( Date.now() + Math.random() ) );
|
|
586
|
-
|
|
605
|
+
const hash = sha1( "" + ( Date.now() + Math.random() ) ).slice( 0, 16 ); // only use first 16 chars of the hash
|
|
606
|
+
let raw_filename = req.headers[ "X-Filename" ];
|
|
607
|
+
if( ! raw_filename ) {
|
|
608
|
+
// take the filename from the last part of the URL
|
|
609
|
+
raw_filename = req.url.split( "/" ).pop();
|
|
610
|
+
// remove any query string
|
|
611
|
+
raw_filename = raw_filename.split( "?" ).shift();
|
|
612
|
+
}
|
|
613
|
+
const sanitized_filename = sanitize_upload_filename( raw_filename );
|
|
614
|
+
const final_filename = hash + "_" + sanitized_filename;
|
|
615
|
+
local_path += "/" + final_filename;
|
|
587
616
|
|
|
588
617
|
D( "PUT: " + local_path );
|
|
589
618
|
|
|
@@ -618,7 +647,7 @@ function put_handler( req, res, next ) {
|
|
|
618
647
|
if( responded ) return;
|
|
619
648
|
I( "PUT: " + local_path );
|
|
620
649
|
res.writeHead( 200, { "Content-Type": "application/json" } );
|
|
621
|
-
res.write( o2j( { hash } ) );
|
|
650
|
+
res.write( o2j( { hash, filename: final_filename } ) );
|
|
622
651
|
res.end();
|
|
623
652
|
} );
|
|
624
653
|
|
|
@@ -795,7 +824,7 @@ function ws_attach( server, msg_handler ) {
|
|
|
795
824
|
socket.send( o2j( msg ) );
|
|
796
825
|
};
|
|
797
826
|
|
|
798
|
-
const conn = { name, socket, send, cookies };
|
|
827
|
+
const conn = { name, socket, send, cookies, resource: wsreq.resource };
|
|
799
828
|
|
|
800
829
|
socket.on( "error", function( err ) {
|
|
801
830
|
E( "WS: error", err.stack || err );
|