1
1
'use strict' ;
2
2
const {
3
- ArrayPrototypePush,
4
- Promise,
3
+ ObjectPrototypeHasOwnProperty,
5
4
PromisePrototypeThen,
6
- PromiseResolve,
7
5
SafeMap,
8
6
StringPrototypeEndsWith,
9
7
StringPrototypeSlice,
10
8
StringPrototypeStartsWith,
11
9
} = primordials ;
12
10
const {
13
- Buffer : {
14
- concat : BufferConcat
15
- }
11
+ Buffer : { concat : BufferConcat } ,
16
12
} = require ( 'buffer' ) ;
17
13
const {
18
14
ERR_NETWORK_IMPORT_DISALLOWED ,
19
15
ERR_NETWORK_IMPORT_BAD_RESPONSE ,
16
+ ERR_MODULE_NOT_FOUND ,
20
17
} = require ( 'internal/errors' ) . codes ;
21
18
const { URL } = require ( 'internal/url' ) ;
22
19
const net = require ( 'net' ) ;
23
20
const path = require ( 'path' ) ;
24
-
21
+ const { once } = require ( 'events' ) ;
22
+ const { compose } = require ( 'stream' ) ;
25
23
/**
26
24
* @typedef CacheEntry
27
25
* @property {Promise<string> | string } resolvedHREF
@@ -33,6 +31,9 @@ const path = require('path');
33
31
* Only for GET requests, other requests would need new Map
34
32
* HTTP cache semantics keep diff caches
35
33
*
34
+ * It caches either the promise or the cache entry since import.meta.url needs
35
+ * the value synchronously for the response location after all redirects.
36
+ *
36
37
* Maps HREF to pending cache entry
37
38
* @type {Map<string, Promise<CacheEntry> | CacheEntry> }
38
39
*/
@@ -48,23 +49,23 @@ let HTTPSAgent;
48
49
function HTTPSGet ( url , opts ) {
49
50
const https = require ( 'https' ) ; // [1]
50
51
HTTPSAgent ??= new https . Agent ( { // [2]
51
- keepAlive : true
52
+ keepAlive : true ,
52
53
} ) ;
53
54
return https . get ( url , {
54
55
agent : HTTPSAgent ,
55
- ...opts
56
+ ...opts ,
56
57
} ) ;
57
58
}
58
59
59
60
let HTTPAgent ;
60
61
function HTTPGet ( url , opts ) {
61
62
const http = require ( 'http' ) ; // [1]
62
63
HTTPAgent ??= new http . Agent ( { // [2]
63
- keepAlive : true
64
+ keepAlive : true ,
64
65
} ) ;
65
66
return http . get ( url , {
66
67
agent : HTTPAgent ,
67
- ...opts
68
+ ...opts ,
68
69
} ) ;
69
70
}
70
71
@@ -99,118 +100,79 @@ function fetchWithRedirects(parsed) {
99
100
return existing ;
100
101
}
101
102
const handler = parsed . protocol === 'http:' ? HTTPGet : HTTPSGet ;
102
- const result = new Promise ( ( fulfill , reject ) => {
103
+ const result = ( async ( ) => {
103
104
const req = handler ( parsed , {
104
- headers : {
105
- Accept : '*/*'
106
- }
107
- } )
108
- . on ( 'error' , reject )
109
- . on ( 'response' , ( res ) => {
110
- function dispose ( ) {
111
- req . destroy ( ) ;
112
- res . destroy ( ) ;
113
- }
114
- if ( res . statusCode >= 300 && res . statusCode <= 303 ) {
115
- if ( res . headers . location ) {
116
- dispose ( ) ;
117
- try {
118
- const location = new URL ( res . headers . location , parsed ) ;
119
- if ( location . protocol !== 'http:' &&
120
- location . protocol !== 'https:' ) {
121
- reject ( new ERR_NETWORK_IMPORT_DISALLOWED (
122
- res . headers . location ,
123
- parsed . href ,
124
- 'cannot redirect to non-network location' ) ) ;
125
- return ;
126
- }
127
- return PromisePrototypeThen (
128
- PromiseResolve ( fetchWithRedirects ( location ) ) ,
129
- ( entry ) => {
130
- cacheForGET . set ( parsed . href , entry ) ;
131
- fulfill ( entry ) ;
132
- } ) ;
133
- } catch ( e ) {
134
- dispose ( ) ;
135
- reject ( e ) ;
136
- }
105
+ headers : { Accept : '*/*' } ,
106
+ } ) ;
107
+ // Note that `once` is used here to handle `error` and that it hits the
108
+ // `finally` on network error/timeout.
109
+ const { 0 : res } = await once ( req , 'response' ) ;
110
+ try {
111
+ const isRedirect = res . statusCode >= 300 && res . statusCode <= 303 ;
112
+ const hasLocation = ObjectPrototypeHasOwnProperty ( res . headers , 'location' ) ;
113
+ if ( isRedirect && hasLocation ) {
114
+ const location = new URL ( res . headers . location , parsed ) ;
115
+ if ( location . protocol !== 'http:' && location . protocol !== 'https:' ) {
116
+ throw new ERR_NETWORK_IMPORT_DISALLOWED (
117
+ res . headers . location ,
118
+ parsed . href ,
119
+ 'cannot redirect to non-network location'
120
+ ) ;
137
121
}
122
+ const entry = await fetchWithRedirects ( location ) ;
123
+ cacheForGET . set ( parsed . href , entry ) ;
124
+ return entry ;
125
+ }
126
+ if ( res . statusCode === 404 ) {
127
+ const err = new ERR_MODULE_NOT_FOUND ( parsed . href , null ) ;
128
+ err . message = `Cannot find module '${ parsed . href } ', HTTP 404` ;
129
+ throw err ;
138
130
}
139
131
if ( res . statusCode > 303 || res . statusCode < 200 ) {
140
- dispose ( ) ;
141
- reject (
142
- new ERR_NETWORK_IMPORT_BAD_RESPONSE (
143
- parsed . href ,
144
- 'HTTP response returned status code of ' + res . statusCode ) ) ;
145
- return ;
132
+ throw new ERR_NETWORK_IMPORT_DISALLOWED (
133
+ res . headers . location ,
134
+ parsed . href ,
135
+ 'cannot redirect to non-network location' ) ;
146
136
}
147
137
const { headers } = res ;
148
138
const contentType = headers [ 'content-type' ] ;
149
139
if ( ! contentType ) {
150
- dispose ( ) ;
151
- reject ( new ERR_NETWORK_IMPORT_BAD_RESPONSE (
140
+ throw new ERR_NETWORK_IMPORT_BAD_RESPONSE (
152
141
parsed . href ,
153
- ' the \ 'Content-Type\ ' header is required' ) ) ;
154
- return ;
142
+ " the 'Content-Type' header is required"
143
+ ) ;
155
144
}
156
145
/**
157
146
* @type {CacheEntry }
158
147
*/
159
148
const entry = {
160
149
resolvedHREF : parsed . href ,
161
150
headers : {
162
- 'content-type' : res . headers [ 'content-type' ]
151
+ 'content-type' : res . headers [ 'content-type' ] ,
163
152
} ,
164
- body : new Promise ( ( f , r ) => {
165
- const buffers = [ ] ;
166
- let size = 0 ;
153
+ body : ( async ( ) => {
167
154
let bodyStream = res ;
168
- let onError ;
169
155
if ( res . headers [ 'content-encoding' ] === 'br' ) {
170
- bodyStream = createBrotliDecompress ( ) ;
171
- onError = function onError ( error ) {
172
- bodyStream . close ( ) ;
173
- dispose ( ) ;
174
- reject ( error ) ;
175
- r ( error ) ;
176
- } ;
177
- res . on ( 'error' , onError ) ;
178
- res . pipe ( bodyStream ) ;
179
- } else if ( res . headers [ 'content-encoding' ] === 'gzip' ||
180
- res . headers [ 'content-encoding' ] === 'deflate' ) {
181
- bodyStream = createUnzip ( ) ;
182
- onError = function onError ( error ) {
183
- bodyStream . close ( ) ;
184
- dispose ( ) ;
185
- reject ( error ) ;
186
- r ( error ) ;
187
- } ;
188
- res . on ( 'error' , onError ) ;
189
- res . pipe ( bodyStream ) ;
190
- } else {
191
- onError = function onError ( error ) {
192
- dispose ( ) ;
193
- reject ( error ) ;
194
- r ( error ) ;
195
- } ;
156
+ bodyStream = compose ( res , createBrotliDecompress ( ) ) ;
157
+ } else if (
158
+ res . headers [ 'content-encoding' ] === 'gzip' ||
159
+ res . headers [ 'content-encoding' ] === 'deflate'
160
+ ) {
161
+ bodyStream = compose ( res , createUnzip ( ) ) ;
196
162
}
197
- bodyStream . on ( 'error' , onError ) ;
198
- bodyStream . on ( 'data' , ( d ) => {
199
- ArrayPrototypePush ( buffers , d ) ;
200
- size += d . length ;
201
- } ) ;
202
- bodyStream . on ( 'end' , ( ) => {
203
- const body = entry . body = /** @type {Buffer } */ (
204
- BufferConcat ( buffers , size )
205
- ) ;
206
- f ( body ) ;
207
- } ) ;
208
- } ) ,
163
+ const buffers = await bodyStream . toArray ( ) ;
164
+ const body = BufferConcat ( buffers ) ;
165
+ entry . body = body ;
166
+ return body ;
167
+ } ) ( ) ,
209
168
} ;
210
169
cacheForGET . set ( parsed . href , entry ) ;
211
- fulfill ( entry ) ;
212
- } ) ;
213
- } ) ;
170
+ await entry . body ;
171
+ return entry ;
172
+ } finally {
173
+ req . destroy ( ) ;
174
+ }
175
+ } ) ( ) ;
214
176
cacheForGET . set ( parsed . href , result ) ;
215
177
return result ;
216
178
}
@@ -227,8 +189,10 @@ allowList.addRange('127.0.0.1', '127.255.255.255');
227
189
*/
228
190
async function isLocalAddress ( hostname ) {
229
191
try {
230
- if ( StringPrototypeStartsWith ( hostname , '[' ) &&
231
- StringPrototypeEndsWith ( hostname , ']' ) ) {
192
+ if (
193
+ StringPrototypeStartsWith ( hostname , '[' ) &&
194
+ StringPrototypeEndsWith ( hostname , ']' )
195
+ ) {
232
196
hostname = StringPrototypeSlice ( hostname , 1 , - 1 ) ;
233
197
}
234
198
const addr = await dnsLookup ( hostname , { verbatim : true } ) ;
@@ -265,10 +229,8 @@ function fetchModule(parsed, { parentURL }) {
265
229
if ( is !== true ) {
266
230
let parent = parentURL ;
267
231
const parentName = path . basename ( parent . pathname ) ;
268
- if (
269
- parentName === '[eval]' ||
270
- parentName === '[stdin]'
271
- ) parent = 'command-line' ;
232
+ if ( parentName === '[eval]' || parentName === '[stdin]' )
233
+ parent = 'command-line' ;
272
234
throw new ERR_NETWORK_IMPORT_DISALLOWED (
273
235
href ,
274
236
parent ,
@@ -282,5 +244,5 @@ function fetchModule(parsed, { parentURL }) {
282
244
}
283
245
284
246
module . exports = {
285
- fetchModule : fetchModule
247
+ fetchModule : fetchModule ,
286
248
} ;
0 commit comments