@@ -5,8 +5,11 @@ import openURLMiddleware from '../openURLMiddleware';
55
66jest . mock ( 'open' ) ;
77
8- function createMockRequest ( method : string , body : object ) : http . IncomingMessage {
9- const bodyStr = JSON . stringify ( body ) ;
8+ function createMockRequest (
9+ method : string ,
10+ body ?: object ,
11+ ) : http . IncomingMessage {
12+ const bodyStr = body == null ? '' : JSON . stringify ( body ) ;
1013 const readable = new Readable ( ) ;
1114 readable . push ( bodyStr ) ;
1215 readable . push ( null ) ;
@@ -21,97 +24,82 @@ function createMockRequest(method: string, body: object): http.IncomingMessage {
2124 } ) as unknown as http . IncomingMessage ;
2225}
2326
24- describe ( 'openURLMiddleware' , ( ) => {
25- let res : jest . Mocked < http . ServerResponse > ;
26- let next : jest . Mock ;
27-
28- beforeEach ( ( ) => {
29- res = {
30- writeHead : jest . fn ( ) ,
31- end : jest . fn ( ) ,
27+ type MiddlewareResponse = {
28+ body ?: string ;
29+ next : jest . Mock ;
30+ statusCode ?: number ;
31+ } ;
32+
33+ function callOpenURLMiddleware (
34+ body ?: object ,
35+ method = 'POST' ,
36+ ) : Promise < MiddlewareResponse > {
37+ return new Promise ( ( resolve , reject ) => {
38+ const response : MiddlewareResponse = {
39+ next : jest . fn ( ( error ?: Error ) => {
40+ if ( error ) {
41+ reject ( error ) ;
42+ return ;
43+ }
44+
45+ resolve ( response ) ;
46+ } ) ,
47+ } ;
48+
49+ const res = {
50+ writeHead : jest . fn ( ( statusCode : number ) => {
51+ response . statusCode = statusCode ;
52+ } ) ,
53+ end : jest . fn ( ( message ?: string ) => {
54+ response . body = message ;
55+ resolve ( response ) ;
56+ } ) ,
3257 setHeader : jest . fn ( ) ,
3358 } as any ;
3459
35- next = jest . fn ( ) ;
60+ openURLMiddleware ( createMockRequest ( method , body ) , res , response . next ) ;
61+ } ) ;
62+ }
63+
64+ describe ( 'openURLMiddleware' , ( ) => {
65+ beforeEach ( ( ) => {
3666 jest . clearAllMocks ( ) ;
3767 } ) ;
3868
3969 afterEach ( ( ) => {
4070 jest . restoreAllMocks ( ) ;
4171 } ) ;
4272
43- test ( 'should return 400 for non-string URL' , ( done ) => {
44- const req = createMockRequest ( 'POST' , { url : 123 } ) ;
45-
46- res . end = jest . fn ( ( ) => {
47- try {
48- expect ( open ) . not . toHaveBeenCalled ( ) ;
49- expect ( res . writeHead ) . toHaveBeenCalledWith ( 400 ) ;
50- expect ( res . end ) . toHaveBeenCalledWith ( 'URL must be a string' ) ;
51- done ( ) ;
52- } catch ( error ) {
53- done ( error ) ;
54- }
55- } ) as any ;
56-
57- openURLMiddleware ( req , res , next ) ;
58- } ) ;
73+ test . each ( [
74+ 'https://reactnative.dev/docs/tutorial' ,
75+ 'https://reactnative.dev/docs/fast-refresh' ,
76+ 'https://x.com/reactnative' ,
77+ ] ) ( 'should open React Native welcome screen URL %s' , async ( url ) => {
78+ const response = await callOpenURLMiddleware ( { url} ) ;
5979
60- // CVE-2025-11953
61- test ( 'should reject malicious URL with invalid hostname' , ( done ) => {
62- const maliciousUrl = 'https://www.$(calc.exe).com/foo' ;
63- const req = createMockRequest ( 'POST' , { url : maliciousUrl } ) ;
64-
65- res . end = jest . fn ( ( ) => {
66- try {
67- expect ( open ) . not . toHaveBeenCalled ( ) ;
68- expect ( res . writeHead ) . toHaveBeenCalledWith ( 400 ) ;
69- expect ( res . end ) . toHaveBeenCalledWith ( 'Invalid URL' ) ;
70- done ( ) ;
71- } catch ( error ) {
72- done ( error ) ;
73- }
74- } ) as any ;
75-
76- openURLMiddleware ( req , res , next ) ;
80+ expect ( open ) . toHaveBeenCalledWith ( url ) ;
81+ expect ( response . statusCode ) . toBe ( 200 ) ;
82+ expect ( response . next ) . not . toHaveBeenCalled ( ) ;
7783 } ) ;
7884
79- // CVE-2025-11953
80- test ( 'should reject URL with Windows pipe separator' , ( done ) => {
81- const maliciousUrl = 'https://evil.com?|calc.exe' ;
82- const req = createMockRequest ( 'POST' , { url : maliciousUrl } ) ;
83-
84- res . end = jest . fn ( ( ) => {
85- try {
86- expect ( open ) . not . toHaveBeenCalled ( ) ;
87- expect ( res . writeHead ) . toHaveBeenCalledWith ( 400 ) ;
88- expect ( res . end ) . toHaveBeenCalledWith ( 'Invalid URL' ) ;
89- done ( ) ;
90- } catch ( error ) {
91- done ( error ) ;
92- }
93- } ) as any ;
94-
95- openURLMiddleware ( req , res , next ) ;
85+ test ( 'should return 400 for non-string URL' , async ( ) => {
86+ const response = await callOpenURLMiddleware ( { url : 123 } ) ;
87+
88+ expect ( open ) . not . toHaveBeenCalled ( ) ;
89+ expect ( response . statusCode ) . toBe ( 400 ) ;
90+ expect ( response . body ) . toBe ( 'URL must be a string' ) ;
9691 } ) ;
9792
9893 // CVE-2025-11953
99- test ( 'should reject URL with Windows command exfiltration' , ( done ) => {
100- // Encodes to reveal %BETA% env var
101- const maliciousUrl = 'https://example.com/?a=%¾TA%' ;
102- const req = createMockRequest ( 'POST' , { url : maliciousUrl } ) ;
103-
104- res . end = jest . fn ( ( ) => {
105- try {
106- expect ( open ) . not . toHaveBeenCalled ( ) ;
107- expect ( res . writeHead ) . toHaveBeenCalledWith ( 400 ) ;
108- expect ( res . end ) . toHaveBeenCalledWith ( 'Invalid URL' ) ;
109- done ( ) ;
110- } catch ( error ) {
111- done ( error ) ;
112- }
113- } ) as any ;
114-
115- openURLMiddleware ( req , res , next ) ;
94+ test . each ( [
95+ [ 'malicious URL with invalid hostname' , 'https://www.$(calc.exe).com/foo' ] ,
96+ [ 'URL with Windows pipe separator' , 'https://evil.com?|calc.exe' ] ,
97+ [ 'URL with Windows command exfiltration' , 'https://example.com/?a=%¾TA%' ] ,
98+ ] ) ( 'should reject %s' , async ( _name , url ) => {
99+ const response = await callOpenURLMiddleware ( { url} ) ;
100+
101+ expect ( open ) . not . toHaveBeenCalled ( ) ;
102+ expect ( response . statusCode ) . toBe ( 400 ) ;
103+ expect ( response . body ) . toBe ( 'Invalid URL' ) ;
116104 } ) ;
117105} ) ;
0 commit comments