11'use strict' ;
22const {
3- ArrayPrototypePush,
4- Promise,
3+ ObjectPrototypeHasOwnProperty,
54 PromisePrototypeThen,
6- PromiseResolve,
75 SafeMap,
86 StringPrototypeEndsWith,
97 StringPrototypeSlice,
108 StringPrototypeStartsWith,
119} = primordials ;
1210const {
13- Buffer : {
14- concat : BufferConcat
15- }
11+ Buffer : { concat : BufferConcat } ,
1612} = require ( 'buffer' ) ;
1713const {
1814 ERR_NETWORK_IMPORT_DISALLOWED ,
1915 ERR_NETWORK_IMPORT_BAD_RESPONSE ,
16+ ERR_MODULE_NOT_FOUND ,
2017} = require ( 'internal/errors' ) . codes ;
2118const { URL } = require ( 'internal/url' ) ;
2219const net = require ( 'net' ) ;
23-
20+ const { once } = require ( 'events' ) ;
21+ const { compose } = require ( 'stream' ) ;
2422/**
2523 * @typedef CacheEntry
2624 * @property {Promise<string> | string } resolvedHREF
@@ -32,6 +30,9 @@ const net = require('net');
3230 * Only for GET requests, other requests would need new Map
3331 * HTTP cache semantics keep diff caches
3432 *
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+ *
3536 * Maps HREF to pending cache entry
3637 * @type {Map<string, Promise<CacheEntry> | CacheEntry> }
3738 */
@@ -47,23 +48,23 @@ let HTTPSAgent;
4748function HTTPSGet ( url , opts ) {
4849 const https = require ( 'https' ) ; // [1]
4950 HTTPSAgent ??= new https . Agent ( { // [2]
50- keepAlive : true
51+ keepAlive : true ,
5152 } ) ;
5253 return https . get ( url , {
5354 agent : HTTPSAgent ,
54- ...opts
55+ ...opts ,
5556 } ) ;
5657}
5758
5859let HTTPAgent ;
5960function HTTPGet ( url , opts ) {
6061 const http = require ( 'http' ) ; // [1]
6162 HTTPAgent ??= new http . Agent ( { // [2]
62- keepAlive : true
63+ keepAlive : true ,
6364 } ) ;
6465 return http . get ( url , {
6566 agent : HTTPAgent ,
66- ...opts
67+ ...opts ,
6768 } ) ;
6869}
6970
@@ -98,118 +99,79 @@ function fetchWithRedirects(parsed) {
9899 return existing ;
99100 }
100101 const handler = parsed . protocol === 'http:' ? HTTPGet : HTTPSGet ;
101- const result = new Promise ( ( fulfill , reject ) => {
102+ const result = ( async ( ) => {
102103 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+ ) ;
136120 }
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 ;
137129 }
138130 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' ) ;
145135 }
146136 const { headers } = res ;
147137 const contentType = headers [ 'content-type' ] ;
148138 if ( ! contentType ) {
149- dispose ( ) ;
150- reject ( new ERR_NETWORK_IMPORT_BAD_RESPONSE (
139+ throw new ERR_NETWORK_IMPORT_BAD_RESPONSE (
151140 parsed . href ,
152- ' the \ 'Content-Type\ ' header is required' ) ) ;
153- return ;
141+ " the 'Content-Type' header is required"
142+ ) ;
154143 }
155144 /**
156145 * @type {CacheEntry }
157146 */
158147 const entry = {
159148 resolvedHREF : parsed . href ,
160149 headers : {
161- 'content-type' : res . headers [ 'content-type' ]
150+ 'content-type' : res . headers [ 'content-type' ] ,
162151 } ,
163- body : new Promise ( ( f , r ) => {
164- const buffers = [ ] ;
165- let size = 0 ;
152+ body : ( async ( ) => {
166153 let bodyStream = res ;
167- let onError ;
168154 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 ( ) ) ;
195161 }
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+ } ) ( ) ,
208167 } ;
209168 cacheForGET . set ( parsed . href , entry ) ;
210- fulfill ( entry ) ;
211- } ) ;
212- } ) ;
169+ await entry . body ;
170+ return entry ;
171+ } finally {
172+ req . destroy ( ) ;
173+ }
174+ } ) ( ) ;
213175 cacheForGET . set ( parsed . href , result ) ;
214176 return result ;
215177}
@@ -226,8 +188,10 @@ allowList.addRange('127.0.0.1', '127.255.255.255');
226188 */
227189async function isLocalAddress ( hostname ) {
228190 try {
229- if ( StringPrototypeStartsWith ( hostname , '[' ) &&
230- StringPrototypeEndsWith ( hostname , ']' ) ) {
191+ if (
192+ StringPrototypeStartsWith ( hostname , '[' ) &&
193+ StringPrototypeEndsWith ( hostname , ']' )
194+ ) {
231195 hostname = StringPrototypeSlice ( hostname , 1 , - 1 ) ;
232196 }
233197 const addr = await dnsLookup ( hostname , { verbatim : true } ) ;
@@ -275,5 +239,5 @@ function fetchModule(parsed, { parentURL }) {
275239}
276240
277241module . exports = {
278- fetchModule : fetchModule
242+ fetchModule : fetchModule ,
279243} ;
0 commit comments