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