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-06-09 16:51:22 -07:00
import http from 'http' ;
2020-05-25 07:43:10 -07:00
2020-06-09 16:51:22 -07:00
const validateHeaderName = typeof http . validateHeaderName === 'function' ?
http . validateHeaderName :
name => {
if ( ! /^[\^`\-\w!#$%&'*+.|~]+$/ . test ( name ) ) {
const err = new TypeError ( ` Header name must be a valid HTTP token [ ${ name } ] ` ) ;
Object . defineProperty ( err , 'code' , { value : 'ERR_INVALID_HTTP_TOKEN' } ) ;
throw err ;
}
} ;
2016-10-10 15:32:56 -07:00
2020-06-09 16:51:22 -07:00
const validateHeaderValue = typeof http . validateHeaderValue === 'function' ?
http . validateHeaderValue :
( name , value ) => {
if ( /[^\t\u0020-\u007E\u0080-\u00FF]/ . test ( value ) ) {
const err = new TypeError ( ` Invalid character in header content [" ${ name } "] ` ) ;
Object . defineProperty ( err , 'code' , { value : 'ERR_INVALID_CHAR' } ) ;
throw err ;
}
} ;
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 ] ) => {
2020-06-09 16:51:22 -07:00
validateHeaderName ( name ) ;
validateHeaderValue ( name , String ( value ) ) ;
return [ String ( name ) . toLowerCase ( ) , String ( value ) ] ;
2020-05-25 07:43:10 -07:00
} ) :
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 ) => {
2020-06-09 16:51:22 -07:00
validateHeaderName ( name ) ;
validateHeaderValue ( name , String ( value ) ) ;
2020-05-25 07:43:10 -07:00
return URLSearchParams . prototype [ p ] . call (
receiver ,
String ( name ) . toLowerCase ( ) ,
2020-06-09 16:51:22 -07:00
String ( value )
2020-05-25 07:43:10 -07:00
) ;
} ;
case 'delete' :
case 'has' :
case 'getAll' :
return name => {
2020-06-09 16:51:22 -07:00
validateHeaderName ( name ) ;
2020-05-25 07:43:10 -07:00
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 ] ( ) {
2020-06-09 16:51:22 -07:00
return this . constructor . name ;
2020-05-25 07:43:10 -07:00
}
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 ;
} , [ ] )
2020-06-09 16:51:22 -07:00
. filter ( ( [ name , value ] ) => {
try {
validateHeaderName ( name ) ;
validateHeaderValue ( name , String ( value ) ) ;
return true ;
} catch {
return false ;
}
} )
2020-03-13 08:06:25 -07:00
2020-05-25 07:43:10 -07:00
) ;
2018-03-04 13:12:36 -08:00
}