@@ -1425,6 +1425,20 @@ const isDomainOrSubdomain = function isDomainOrSubdomain(destination, original)
1425
1425
return orig === dest || orig [ orig . length - dest . length - 1 ] === '.' && orig . endsWith ( dest ) ;
1426
1426
} ;
1427
1427
1428
+ /**
1429
+ * isSameProtocol reports whether the two provided URLs use the same protocol.
1430
+ *
1431
+ * Both domains must already be in canonical form.
1432
+ * @param {string|URL } original
1433
+ * @param {string|URL } destination
1434
+ */
1435
+ const isSameProtocol = function isSameProtocol ( destination , original ) {
1436
+ const orig = new URL$1 ( original ) . protocol ;
1437
+ const dest = new URL$1 ( destination ) . protocol ;
1438
+
1439
+ return orig === dest ;
1440
+ } ;
1441
+
1428
1442
/**
1429
1443
* Fetch function
1430
1444
*
@@ -1456,7 +1470,7 @@ function fetch(url, opts) {
1456
1470
let error = new AbortError ( 'The user aborted a request.' ) ;
1457
1471
reject ( error ) ;
1458
1472
if ( request . body && request . body instanceof Stream . Readable ) {
1459
- request . body . destroy ( error ) ;
1473
+ destroyStream ( request . body , error ) ;
1460
1474
}
1461
1475
if ( ! response || ! response . body ) return ;
1462
1476
response . body . emit ( 'error' , error ) ;
@@ -1497,9 +1511,43 @@ function fetch(url, opts) {
1497
1511
1498
1512
req . on ( 'error' , function ( err ) {
1499
1513
reject ( new FetchError ( `request to ${ request . url } failed, reason: ${ err . message } ` , 'system' , err ) ) ;
1514
+
1515
+ if ( response && response . body ) {
1516
+ destroyStream ( response . body , err ) ;
1517
+ }
1518
+
1500
1519
finalize ( ) ;
1501
1520
} ) ;
1502
1521
1522
+ fixResponseChunkedTransferBadEnding ( req , function ( err ) {
1523
+ if ( signal && signal . aborted ) {
1524
+ return ;
1525
+ }
1526
+
1527
+ if ( response && response . body ) {
1528
+ destroyStream ( response . body , err ) ;
1529
+ }
1530
+ } ) ;
1531
+
1532
+ /* c8 ignore next 18 */
1533
+ if ( parseInt ( process . version . substring ( 1 ) ) < 14 ) {
1534
+ // Before Node.js 14, pipeline() does not fully support async iterators and does not always
1535
+ // properly handle when the socket close/end events are out of order.
1536
+ req . on ( 'socket' , function ( s ) {
1537
+ s . addListener ( 'close' , function ( hadError ) {
1538
+ // if a data listener is still present we didn't end cleanly
1539
+ const hasDataListener = s . listenerCount ( 'data' ) > 0 ;
1540
+
1541
+ // if end happened before close but the socket didn't emit an error, do it now
1542
+ if ( response && hasDataListener && ! hadError && ! ( signal && signal . aborted ) ) {
1543
+ const err = new Error ( 'Premature close' ) ;
1544
+ err . code = 'ERR_STREAM_PREMATURE_CLOSE' ;
1545
+ response . body . emit ( 'error' , err ) ;
1546
+ }
1547
+ } ) ;
1548
+ } ) ;
1549
+ }
1550
+
1503
1551
req . on ( 'response' , function ( res ) {
1504
1552
clearTimeout ( reqTimeout ) ;
1505
1553
@@ -1571,7 +1619,7 @@ function fetch(url, opts) {
1571
1619
size : request . size
1572
1620
} ;
1573
1621
1574
- if ( ! isDomainOrSubdomain ( request . url , locationURL ) ) {
1622
+ if ( ! isDomainOrSubdomain ( request . url , locationURL ) || ! isSameProtocol ( request . url , locationURL ) ) {
1575
1623
for ( const name of [ 'authorization' , 'www-authenticate' , 'cookie' , 'cookie2' ] ) {
1576
1624
requestOpts . headers . delete ( name ) ;
1577
1625
}
@@ -1664,6 +1712,13 @@ function fetch(url, opts) {
1664
1712
response = new Response ( body , response_options ) ;
1665
1713
resolve ( response ) ;
1666
1714
} ) ;
1715
+ raw . on ( 'end' , function ( ) {
1716
+ // some old IIS servers return zero-length OK deflate responses, so 'data' is never emitted.
1717
+ if ( ! response ) {
1718
+ response = new Response ( body , response_options ) ;
1719
+ resolve ( response ) ;
1720
+ }
1721
+ } ) ;
1667
1722
return ;
1668
1723
}
1669
1724
@@ -1683,6 +1738,44 @@ function fetch(url, opts) {
1683
1738
writeToStream ( req , request ) ;
1684
1739
} ) ;
1685
1740
}
1741
+ function fixResponseChunkedTransferBadEnding ( request , errorCallback ) {
1742
+ let socket ;
1743
+
1744
+ request . on ( 'socket' , function ( s ) {
1745
+ socket = s ;
1746
+ } ) ;
1747
+
1748
+ request . on ( 'response' , function ( response ) {
1749
+ const headers = response . headers ;
1750
+
1751
+ if ( headers [ 'transfer-encoding' ] === 'chunked' && ! headers [ 'content-length' ] ) {
1752
+ response . once ( 'close' , function ( hadError ) {
1753
+ // tests for socket presence, as in some situations the
1754
+ // the 'socket' event is not triggered for the request
1755
+ // (happens in deno), avoids `TypeError`
1756
+ // if a data listener is still present we didn't end cleanly
1757
+ const hasDataListener = socket && socket . listenerCount ( 'data' ) > 0 ;
1758
+
1759
+ if ( hasDataListener && ! hadError ) {
1760
+ const err = new Error ( 'Premature close' ) ;
1761
+ err . code = 'ERR_STREAM_PREMATURE_CLOSE' ;
1762
+ errorCallback ( err ) ;
1763
+ }
1764
+ } ) ;
1765
+ }
1766
+ } ) ;
1767
+ }
1768
+
1769
+ function destroyStream ( stream , err ) {
1770
+ if ( stream . destroy ) {
1771
+ stream . destroy ( err ) ;
1772
+ } else {
1773
+ // node < 8
1774
+ stream . emit ( 'error' , err ) ;
1775
+ stream . end ( ) ;
1776
+ }
1777
+ }
1778
+
1686
1779
/**
1687
1780
* Redirect code matching
1688
1781
*
0 commit comments