2015-01-27 05:11:26 -08:00
/ * *
2020-03-13 08:06:25 -07:00
* Headers . js
2015-01-27 05:11:26 -08:00
*
* Headers class offers convenient helpers
* /
2020-05-25 07:43:10 -07:00
import { types } from 'util' ;
2020-03-13 08:06:25 -07:00
const invalidTokenRegex = /[^`\-\w!#$%&'*+.|~]/ ;
const invalidHeaderCharRegex = /[^\t\u0020-\u007E\u0080-\u00FF]/ ;
2016-10-10 15:32:56 -07:00
2018-03-04 12:54:56 -08:00
function validateName ( name ) {
2020-05-25 07:43:10 -07:00
name = String ( name ) ;
2018-12-29 01:04:44 -08:00
if ( invalidTokenRegex . test ( name ) || name === '' ) {
2020-05-25 07:43:10 -07:00
throw new TypeError ( ` ' ${ name } ' is not a legal HTTP header name ` ) ;
2016-10-10 15:32:56 -07:00
}
}
2018-03-04 12:54:56 -08:00
function validateValue ( value ) {
2020-05-25 07:43:10 -07:00
value = String ( value ) ;
2018-03-04 12:21:40 -08:00
if ( invalidHeaderCharRegex . test ( value ) ) {
2020-05-25 07:43:10 -07:00
throw new TypeError ( ` ' ${ value } ' is not a legal HTTP header value ` ) ;
2016-10-10 15:32:56 -07:00
}
2018-03-04 12:54:56 -08:00
}
/ * *
2020-05-28 17:41:26 -07:00
* @ typedef { Headers | Record < string , string > | Iterable < readonly [ string , string ] > | Iterable < Iterable < string >> } HeadersInit
2018-03-04 12:54:56 -08:00
* /
2020-03-13 08:06:25 -07:00
2020-05-25 07:43:10 -07:00
/ * *
* This Fetch API interface allows you to perform various actions on HTTP request and response headers .
* These actions include retrieving , setting , adding to , and removing .
* A Headers object has an associated header list , which is initially empty and consists of zero or more name and value pairs .
* You can add to this using methods like append ( ) ( see Examples . )
* In all methods of this interface , header names are matched by case - insensitive byte sequence .
*
* /
export default class Headers extends URLSearchParams {
2016-10-08 20:51:01 -07:00
/ * *
* Headers class
*
2020-05-25 07:43:10 -07:00
* @ constructor
* @ param { HeadersInit } [ init ] - Response headers
2016-10-08 20:51:01 -07:00
* /
2020-05-25 07:43:10 -07:00
constructor ( init ) {
// Validate and normalize init object in [name, value(s)][]
/** @type {string[][]} */
let result = [ ] ;
2017-03-20 09:22:49 -07:00
if ( init instanceof Headers ) {
2020-05-25 07:43:10 -07:00
const raw = init . raw ( ) ;
for ( const [ name , values ] of Object . entries ( raw ) ) {
result . push ( ... values . map ( value => [ name , value ] ) ) ;
2017-03-20 09:22:49 -07:00
}
2020-05-25 07:43:10 -07:00
} else if ( init == null ) { // eslint-disable-line no-eq-null, eqeqeq
2020-03-13 08:06:25 -07:00
// No op
2020-05-25 07:43:10 -07:00
} else if ( typeof init === 'object' && ! types . isBoxedPrimitive ( init ) ) {
2017-01-29 08:58:16 -08:00
const method = init [ Symbol . iterator ] ;
2020-03-13 08:06:25 -07:00
// eslint-disable-next-line no-eq-null, eqeqeq
2020-05-25 08:11:56 -07:00
if ( method == null ) {
// Record<ByteString, ByteString>
result . push ( ... Object . entries ( init ) ) ;
} else {
2017-01-29 08:58:16 -08:00
if ( typeof method !== 'function' ) {
throw new TypeError ( 'Header pairs must be iterable' ) ;
2017-01-14 21:11:30 -08:00
}
2017-01-29 08:58:16 -08:00
2020-03-13 08:06:25 -07:00
// Sequence<sequence<ByteString>>
2017-01-29 08:58:16 -08:00
// Note: per spec we have to first exhaust the lists then process them
2020-05-25 07:43:10 -07:00
result = [ ... init ]
. map ( pair => {
if (
typeof pair !== 'object' || types . isBoxedPrimitive ( pair )
) {
throw new TypeError ( 'Each header pair must be an iterable object' ) ;
}
return [ ... pair ] ;
} ) . map ( pair => {
if ( pair . length !== 2 ) {
throw new TypeError ( 'Each header pair must be a name/value tuple' ) ;
}
return [ ... pair ] ;
} ) ;
2016-10-08 20:51:01 -07:00
}
2017-01-29 08:58:16 -08:00
} else {
2020-05-25 07:43:10 -07:00
throw new TypeError ( 'Failed to construct \'Headers\': The provided value is not of type \'(sequence<sequence<ByteString>> or record<ByteString, ByteString>)' ) ;
2016-10-08 20:51:01 -07:00
}
2020-05-25 07:43:10 -07:00
// Validate and lowercase
result =
result . length > 0 ?
result . map ( ( [ name , value ] ) => {
validateName ( name ) ;
validateValue ( value ) ;
return [ String ( name ) . toLowerCase ( ) , value ] ;
} ) :
undefined ;
super ( result ) ;
// Returning a Proxy that will lowercase key names, validate parameters and sort keys
// eslint-disable-next-line no-constructor-return
return new Proxy ( this , {
get ( target , p , receiver ) {
switch ( p ) {
case 'append' :
case 'set' :
return ( name , value ) => {
validateName ( name ) ;
validateValue ( value ) ;
return URLSearchParams . prototype [ p ] . call (
receiver ,
String ( name ) . toLowerCase ( ) ,
value
) ;
} ;
case 'delete' :
case 'has' :
case 'getAll' :
return name => {
validateName ( name ) ;
return URLSearchParams . prototype [ p ] . call (
receiver ,
String ( name ) . toLowerCase ( )
) ;
} ;
case 'keys' :
return ( ) => {
target . sort ( ) ;
return new Set ( URLSearchParams . prototype . keys . call ( target ) ) . keys ( ) ;
} ;
default :
return Reflect . get ( target , p , receiver ) ;
}
}
2020-05-25 08:11:56 -07:00
/* c8 ignore next */
2020-05-25 07:43:10 -07:00
} ) ;
}
get [ Symbol . toStringTag ] ( ) {
return 'Headers' ;
}
toString ( ) {
return Object . prototype . toString . call ( this ) ;
2016-10-08 20:51:01 -07:00
}
2015-01-27 05:11:26 -08:00
2016-10-08 20:51:01 -07:00
get ( name ) {
2020-05-25 07:43:10 -07:00
const values = this . getAll ( name ) ;
if ( values . length === 0 ) {
2016-10-10 18:31:53 -07:00
return null ;
}
2020-05-25 07:43:10 -07:00
let value = values . join ( ', ' ) ;
if ( /^content-encoding$/i . test ( name ) ) {
2020-03-13 08:06:25 -07:00
value = value . toLowerCase ( ) ;
}
return value ;
2016-10-08 20:51:01 -07:00
}
2015-05-03 21:05:06 -07:00
2020-05-25 07:43:10 -07:00
forEach ( callback ) {
for ( const name of this . keys ( ) ) {
callback ( this . get ( name ) , name ) ;
2016-10-15 10:02:52 -07:00
}
2016-10-08 20:51:01 -07:00
}
2020-05-25 07:43:10 -07:00
* values ( ) {
for ( const name of this . keys ( ) ) {
yield this . get ( name ) ;
2015-01-27 05:11:26 -08:00
}
}
2016-10-08 20:51:01 -07:00
/ * *
2020-05-25 07:43:10 -07:00
* @ type { ( ) => IterableIterator < [ string , string ] > }
2016-10-08 20:51:01 -07:00
* /
2020-05-25 07:43:10 -07:00
* entries ( ) {
for ( const name of this . keys ( ) ) {
yield [ name , this . get ( name ) ] ;
2018-03-04 12:54:56 -08:00
}
2020-03-13 08:06:25 -07:00
}
2016-10-08 20:51:01 -07:00
2020-05-25 07:43:10 -07:00
[ Symbol . iterator ] ( ) {
return this . entries ( ) ;
2015-01-27 05:11:26 -08:00
}
2016-10-08 20:51:01 -07:00
/ * *
2020-05-25 07:43:10 -07:00
* Node - fetch non - spec method
* returning all headers and their values as array
* @ returns { Record < string , string [ ] > }
2016-10-08 20:51:01 -07:00
* /
2020-05-25 07:43:10 -07:00
raw ( ) {
2020-05-25 08:11:56 -07:00
return [ ... this . keys ( ) ] . reduce ( ( result , key ) => {
result [ key ] = this . getAll ( key ) ;
return result ;
2020-05-25 07:43:10 -07:00
} , { } ) ;
2016-10-08 20:51:01 -07:00
}
2015-10-26 22:13:02 -07:00
2016-10-08 20:51:01 -07:00
/ * *
2020-05-25 07:43:10 -07:00
* For better console . log ( headers ) and also to convert Headers into Node . js Request compatible format
2016-10-08 20:51:01 -07:00
* /
2020-05-25 07:43:10 -07:00
[ Symbol . for ( 'nodejs.util.inspect.custom' ) ] ( ) {
2020-05-25 08:11:56 -07:00
return [ ... this . keys ( ) ] . reduce ( ( result , key ) => {
2020-05-25 07:43:10 -07:00
const values = this . getAll ( key ) ;
// Http.request() only supports string as Host header.
// This hack makes specifying custom Host header possible.
if ( key === 'host' ) {
2020-05-25 08:11:56 -07:00
result [ key ] = values [ 0 ] ;
2020-05-25 07:43:10 -07:00
} else {
2020-05-25 08:11:56 -07:00
result [ key ] = values . length > 1 ? values : values [ 0 ] ;
2020-05-25 07:43:10 -07:00
}
2020-05-06 04:56:49 -07:00
2020-05-25 08:11:56 -07:00
return result ;
2020-05-25 07:43:10 -07:00
} , { } ) ;
2020-05-06 04:56:49 -07:00
}
2016-11-23 15:06:30 -08:00
}
2018-03-22 22:01:45 -07:00
/ * *
2020-05-25 07:43:10 -07:00
* Re - shaping object for Web IDL tests
* Only need to do it for overridden methods
2018-03-22 22:01:45 -07:00
* /
2020-05-25 07:43:10 -07:00
Object . defineProperties (
Headers . prototype ,
2020-05-25 08:11:56 -07:00
[ 'get' , 'entries' , 'forEach' , 'values' ] . reduce ( ( result , property ) => {
result [ property ] = { enumerable : true } ;
return result ;
2020-05-25 07:43:10 -07:00
} , { } )
) ;
2018-03-22 22:01:45 -07:00
2018-03-04 13:12:36 -08:00
/ * *
2020-05-25 07:43:10 -07:00
* Create a Headers object from an http . IncomingMessage . rawHeaders , ignoring those that do
2018-03-04 13:12:36 -08:00
* not conform to HTTP grammar productions .
2020-05-25 07:43:10 -07:00
* @ param { import ( 'http' ) . IncomingMessage [ 'rawHeaders' ] } headers
2018-03-04 13:12:36 -08:00
* /
2020-05-25 07:43:10 -07:00
export function fromRawHeaders ( headers = [ ] ) {
return new Headers (
headers
// Split into pairs
. reduce ( ( result , value , index , array ) => {
if ( index % 2 === 0 ) {
result . push ( array . slice ( index , index + 2 ) ) ;
2018-03-04 13:12:36 -08:00
}
2020-03-13 08:06:25 -07:00
2020-05-25 07:43:10 -07:00
return result ;
} , [ ] )
. filter ( ( [ name , value ] ) => ! ( invalidTokenRegex . test ( name ) || invalidHeaderCharRegex . test ( value ) ) )
2020-03-13 08:06:25 -07:00
2020-05-25 07:43:10 -07:00
) ;
2018-03-04 13:12:36 -08:00
}