@@ -3,14 +3,13 @@ import { mkdir, writeFile } from 'node:fs/promises';
33import type { TidewaveConfig } from '../../core' ;
44import type { TidewaveHandler , TidewaveNext , TidewaveRequest , TidewaveResponse } from '../types' ;
55import { magicByteType } from '../magic-bytes' ;
6+ import { checkOrigin } from '../security' ;
67
78const MAX_UPLOAD_SIZE = 200_000_000 ;
89const ALLOWED_UPLOAD_CONTENT_TYPES = [ 'image/png' , 'image/jpeg' , 'video/webm' ] as const ;
910const ALLOWED_UPLOAD_TYPES = [ 'screenshot' , 'recording' ] as const ;
1011const ALLOWED_UPLOAD_EXTENSIONS = [ '.png' , '.jpg' , '.jpeg' , '.webm' ] as const ;
1112const INVALID_UPLOAD = 'Bad Request: missing or invalid file parameter' ;
12- const INVALID_UPLOAD_ORIGIN =
13- "For security reasons, this page only allows connections from the application's own origin." ;
1413
1514class InvalidUploadError extends Error { }
1615
@@ -36,7 +35,7 @@ export function createHandleUpload(config: TidewaveConfig): TidewaveHandler {
3635 return ;
3736 }
3837
39- if ( ! requireSameOrigin ( req , res , config ) ) return ;
38+ if ( ! checkOrigin ( req , res , config ) ) return ;
4039 if ( uploadTooLarge ( req ) ) {
4140 badRequest ( res ) ;
4241 return ;
@@ -88,58 +87,6 @@ export function createHandleUpload(config: TidewaveConfig): TidewaveHandler {
8887 } ;
8988}
9089
91- function requireSameOrigin (
92- req : TidewaveRequest ,
93- res : TidewaveResponse ,
94- config : TidewaveConfig ,
95- ) : boolean {
96- const origin = firstHeaderValue ( req . headers . origin ) ;
97- if ( ! origin ) return true ;
98-
99- if ( allowedOriginHosts ( config ) . includes ( originHost ( origin ) ) ) return true ;
100-
101- console . warn ( INVALID_UPLOAD_ORIGIN ) ;
102- res . statusCode = 403 ;
103- res . end ( INVALID_UPLOAD_ORIGIN ) ;
104- return false ;
105- }
106-
107- function originHost ( origin : string ) : string {
108- try {
109- return new URL ( origin ) . hostname . toLowerCase ( ) ;
110- } catch {
111- return '' ;
112- }
113- }
114-
115- function allowedOriginHosts ( config : TidewaveConfig ) : string [ ] {
116- // Do not derive this from the request Host header. Host is client-controlled,
117- // while configured origin hosts avoid accepting DNS rebinding requests.
118- return ( config . allowedOrigins || [ ] ) . map ( originOrHostToHost ) . filter ( host => host . length > 0 ) ;
119- }
120-
121- function originOrHostToHost ( originOrHost : string ) : string {
122- if ( originOrHost . startsWith ( '//' ) ) {
123- return originHost ( `http:${ originOrHost } ` ) ;
124- }
125-
126- if ( / ^ [ A - Z a - z ] [ A - Z a - z 0 - 9 + . - ] * : \/ \/ / . test ( originOrHost ) ) {
127- return originHost ( originOrHost ) ;
128- }
129-
130- if ( originOrHost . startsWith ( '[' ) ) {
131- const end = originOrHost . indexOf ( ']' ) ;
132- return end === - 1 ? originOrHost . toLowerCase ( ) : originOrHost . slice ( 1 , end ) . toLowerCase ( ) ;
133- }
134-
135- try {
136- const parsed = new URL ( `http://${ originOrHost } ` ) ;
137- return parsed . hostname . toLowerCase ( ) ;
138- } catch {
139- return originOrHost . toLowerCase ( ) ;
140- }
141- }
142-
14390function uploadTooLarge ( req : TidewaveRequest ) : boolean {
14491 const contentLength = firstHeaderValue ( req . headers [ 'content-length' ] ) ;
14592 if ( ! contentLength ) return false ;
0 commit comments