2021-08-24 14:29:04 -07:00
/ * *
* Copyright ( c ) Microsoft Corporation .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2021-08-26 16:18:54 -07:00
import * as http from 'http' ;
import * as https from 'https' ;
2021-09-22 12:44:22 -07:00
import { HttpsProxyAgent } from 'https-proxy-agent' ;
2021-11-30 18:12:19 -08:00
import { Progress , ProgressController } from './progress' ;
2021-10-15 11:33:21 -07:00
import { SocksProxyAgent } from 'socks-proxy-agent' ;
2021-08-31 09:34:58 -07:00
import { pipeline , Readable , Transform } from 'stream' ;
2021-09-22 12:44:22 -07:00
import url from 'url' ;
import zlib from 'zlib' ;
import { HTTPCredentials } from '../../types/types' ;
2021-09-30 14:14:29 -07:00
import * as channels from '../protocol/channels' ;
2021-09-22 12:44:22 -07:00
import { TimeoutSettings } from '../utils/timeoutSettings' ;
2021-10-01 12:11:33 -07:00
import { assert , createGuid , getPlaywrightVersion , monotonicTime } from '../utils/utils' ;
2021-09-22 12:44:22 -07:00
import { BrowserContext } from './browserContext' ;
2021-09-29 17:15:32 -07:00
import { CookieStore , domainMatches } from './cookieStore' ;
2021-09-22 12:44:22 -07:00
import { MultipartFormData } from './formData' ;
2021-11-30 18:12:19 -08:00
import { CallMetadata , SdkObject } from './instrumentation' ;
2021-09-14 18:31:35 -07:00
import { Playwright } from './playwright' ;
2021-09-22 12:44:22 -07:00
import * as types from './types' ;
2021-09-14 18:31:35 -07:00
import { HeadersArray , ProxySettings } from './types' ;
2021-08-27 08:26:19 -07:00
2021-10-01 12:11:33 -07:00
type FetchRequestOptions = {
2021-09-14 18:31:35 -07:00
userAgent : string ;
extraHTTPHeaders? : HeadersArray ;
httpCredentials? : HTTPCredentials ;
proxy? : ProxySettings ;
timeoutSettings : TimeoutSettings ;
ignoreHTTPSErrors? : boolean ;
baseURL? : string ;
} ;
2021-08-24 14:29:04 -07:00
2021-11-05 16:27:49 +01:00
export abstract class APIRequestContext extends SdkObject {
2021-09-15 14:02:55 -07:00
static Events = {
Dispose : 'dispose' ,
} ;
2021-09-14 18:31:35 -07:00
readonly fetchResponses : Map < string , Buffer > = new Map ( ) ;
2021-11-30 18:12:19 -08:00
readonly fetchLog : Map < string , string [ ] > = new Map ( ) ;
2021-11-05 16:27:49 +01:00
protected static allInstances : Set < APIRequestContext > = new Set ( ) ;
2021-09-15 14:02:55 -07:00
static findResponseBody ( guid : string ) : Buffer | undefined {
2021-11-05 16:27:49 +01:00
for ( const request of APIRequestContext . allInstances ) {
2021-09-15 14:02:55 -07:00
const body = request . fetchResponses . get ( guid ) ;
if ( body )
return body ;
}
return undefined ;
}
2021-09-08 10:01:40 -07:00
2021-09-14 18:31:35 -07:00
constructor ( parent : SdkObject ) {
super ( parent , 'fetchRequest' ) ;
2021-11-05 16:27:49 +01:00
APIRequestContext . allInstances . add ( this ) ;
2021-09-14 18:31:35 -07:00
}
2021-09-13 14:29:44 -07:00
2021-09-15 14:02:55 -07:00
protected _disposeImpl() {
2021-11-05 16:27:49 +01:00
APIRequestContext . allInstances . delete ( this ) ;
2021-09-14 18:31:35 -07:00
this . fetchResponses . clear ( ) ;
2021-11-30 18:12:19 -08:00
this . fetchLog . clear ( ) ;
2021-11-05 16:27:49 +01:00
this . emit ( APIRequestContext . Events . Dispose ) ;
2021-08-24 14:29:04 -07:00
}
2021-11-30 18:12:19 -08:00
disposeResponse ( fetchUid : string ) {
this . fetchResponses . delete ( fetchUid ) ;
this . fetchLog . delete ( fetchUid ) ;
}
2021-09-15 14:02:55 -07:00
abstract dispose ( ) : void ;
2021-09-14 18:31:35 -07:00
abstract _defaultOptions ( ) : FetchRequestOptions ;
2021-09-29 17:15:32 -07:00
abstract _addCookies ( cookies : types.NetworkCookie [ ] ) : Promise < void > ;
abstract _cookies ( url : URL ) : Promise < types.NetworkCookie [ ] > ;
2021-11-05 16:27:49 +01:00
abstract storageState ( ) : Promise < channels.APIRequestContextStorageStateResult > ;
2021-09-14 18:31:35 -07:00
private _storeResponseBody ( body : Buffer ) : string {
const uid = createGuid ( ) ;
this . fetchResponses . set ( uid , body ) ;
return uid ;
2021-08-26 16:18:54 -07:00
}
2021-11-30 18:12:19 -08:00
async fetch ( params : channels.APIRequestContextFetchParams , metadata : CallMetadata ) : Promise < Omit < types.APIResponse , 'body' > & { fetchUid : string } > {
2021-11-19 20:32:29 -08:00
const headers : { [ name : string ] : string } = { } ;
const defaults = this . _defaultOptions ( ) ;
headers [ 'user-agent' ] = defaults . userAgent ;
headers [ 'accept' ] = '*/*' ;
headers [ 'accept-encoding' ] = 'gzip,deflate,br' ;
if ( defaults . extraHTTPHeaders ) {
for ( const { name , value } of defaults . extraHTTPHeaders )
headers [ name . toLowerCase ( ) ] = value ;
}
2021-09-14 18:31:35 -07:00
2021-11-19 20:32:29 -08:00
if ( params . headers ) {
for ( const { name , value } of params . headers )
headers [ name . toLowerCase ( ) ] = value ;
}
2021-09-14 18:31:35 -07:00
2021-11-19 20:32:29 -08:00
const method = params . method ? . toUpperCase ( ) || 'GET' ;
const proxy = defaults . proxy ;
let agent ;
if ( proxy ) {
// TODO: support bypass proxy
const proxyOpts = url . parse ( proxy . server ) ;
if ( proxyOpts . protocol ? . startsWith ( 'socks' ) ) {
agent = new SocksProxyAgent ( {
host : proxyOpts.hostname ,
port : proxyOpts.port || undefined ,
} ) ;
} else {
if ( proxy . username )
proxyOpts . auth = ` ${ proxy . username } : ${ proxy . password || '' } ` ;
agent = new HttpsProxyAgent ( proxyOpts ) ;
2021-09-14 18:31:35 -07:00
}
2021-11-19 20:32:29 -08:00
}
2021-09-14 18:31:35 -07:00
2021-11-19 20:32:29 -08:00
const timeout = defaults . timeoutSettings . timeout ( params ) ;
const deadline = timeout && ( monotonicTime ( ) + timeout ) ;
const options : https.RequestOptions & { maxRedirects : number , deadline : number } = {
method ,
headers ,
agent ,
maxRedirects : 20 ,
timeout ,
deadline
} ;
// rejectUnauthorized = undefined is treated as true in node 12.
if ( params . ignoreHTTPSErrors || defaults . ignoreHTTPSErrors )
options . rejectUnauthorized = false ;
const requestUrl = new URL ( params . url , defaults . baseURL ) ;
if ( params . params ) {
for ( const { name , value } of params . params )
requestUrl . searchParams . set ( name , value ) ;
2021-09-14 18:31:35 -07:00
}
2021-11-19 20:32:29 -08:00
2021-11-30 18:12:19 -08:00
let postData : Buffer | undefined ;
2021-11-19 20:32:29 -08:00
if ( [ 'POST' , 'PUT' , 'PATCH' , 'DELETE' ] . includes ( method ) )
postData = serializePostData ( params , headers ) ;
else if ( params . postData || params . jsonData || params . formData || params . multipartData )
throw new Error ( ` Method ${ method } does not accept post data ` ) ;
if ( postData )
headers [ 'content-length' ] = String ( postData . byteLength ) ;
2021-11-30 18:12:19 -08:00
const controller = new ProgressController ( metadata , this ) ;
const fetchResponse = await controller . run ( progress = > {
return this . _sendRequest ( progress , requestUrl , options , postData ) ;
} ) ;
2021-11-19 20:32:29 -08:00
const fetchUid = this . _storeResponseBody ( fetchResponse . body ) ;
2021-11-30 18:12:19 -08:00
this . fetchLog . set ( fetchUid , controller . metadata . log ) ;
2021-11-19 20:32:29 -08:00
if ( params . failOnStatusCode && ( fetchResponse . status < 200 || fetchResponse . status >= 400 ) )
throw new Error ( ` ${ fetchResponse . status } ${ fetchResponse . statusText } ` ) ;
return { . . . fetchResponse , fetchUid } ;
2021-08-27 15:28:36 -07:00
}
2021-08-26 16:18:54 -07:00
2021-09-14 18:31:35 -07:00
private async _updateCookiesFromHeader ( responseUrl : string , setCookie : string [ ] ) {
const url = new URL ( responseUrl ) ;
// https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4
const defaultPath = '/' + url . pathname . substr ( 1 ) . split ( '/' ) . slice ( 0 , - 1 ) . join ( '/' ) ;
2021-09-29 17:15:32 -07:00
const cookies : types.NetworkCookie [ ] = [ ] ;
2021-09-14 18:31:35 -07:00
for ( const header of setCookie ) {
// Decode cookie value?
2021-09-29 17:15:32 -07:00
const cookie : types.NetworkCookie | null = parseCookie ( header ) ;
2021-09-14 18:31:35 -07:00
if ( ! cookie )
continue ;
2021-09-29 17:15:32 -07:00
// https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3
2021-09-14 18:31:35 -07:00
if ( ! cookie . domain )
cookie . domain = url . hostname ;
2021-09-29 17:15:32 -07:00
else
assert ( cookie . domain . startsWith ( '.' ) ) ;
if ( ! domainMatches ( url . hostname , cookie . domain ! ) )
2021-09-14 18:31:35 -07:00
continue ;
// https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.4
if ( ! cookie . path || ! cookie . path . startsWith ( '/' ) )
cookie . path = defaultPath ;
cookies . push ( cookie ) ;
}
if ( cookies . length )
await this . _addCookies ( cookies ) ;
}
private async _updateRequestCookieHeader ( url : URL , options : http.RequestOptions ) {
if ( options . headers ! [ 'cookie' ] !== undefined )
return ;
2021-09-29 17:15:32 -07:00
const cookies = await this . _cookies ( url ) ;
2021-09-14 18:31:35 -07:00
if ( cookies . length ) {
const valueArray = cookies . map ( c = > ` ${ c . name } = ${ c . value } ` ) ;
options . headers ! [ 'cookie' ] = valueArray . join ( '; ' ) ;
}
}
2021-11-30 18:12:19 -08:00
private async _sendRequest ( progress : Progress , url : URL , options : https.RequestOptions & { maxRedirects : number , deadline : number } , postData? : Buffer ) : Promise < types.APIResponse > {
2021-09-14 18:31:35 -07:00
await this . _updateRequestCookieHeader ( url , options ) ;
2021-11-05 16:27:49 +01:00
return new Promise < types.APIResponse > ( ( fulfill , reject ) = > {
2021-09-14 18:31:35 -07:00
const requestConstructor : ( ( url : URL , options : http.RequestOptions , callback ? : ( res : http.IncomingMessage ) = > void ) = > http . ClientRequest )
= ( url . protocol === 'https:' ? https : http ) . request ;
const request = requestConstructor ( url , options , async response = > {
2021-11-30 18:12:19 -08:00
progress . log ( ` ← ${ response . statusCode } ${ response . statusMessage } ` ) ;
for ( const [ name , value ] of Object . entries ( response . headers ) )
progress . log ( ` ${ name } : ${ value } ` ) ;
2021-09-14 18:31:35 -07:00
if ( response . headers [ 'set-cookie' ] )
await this . _updateCookiesFromHeader ( response . url || url . toString ( ) , response . headers [ 'set-cookie' ] ) ;
if ( redirectStatus . includes ( response . statusCode ! ) ) {
if ( ! options . maxRedirects ) {
reject ( new Error ( 'Max redirect count exceeded' ) ) ;
2021-10-18 19:41:56 -07:00
request . destroy ( ) ;
2021-09-14 18:31:35 -07:00
return ;
}
const headers = { . . . options . headers } ;
delete headers [ ` cookie ` ] ;
// HTTP-redirect fetch step 13 (https://fetch.spec.whatwg.org/#http-redirect-fetch)
const status = response . statusCode ! ;
let method = options . method ! ;
if ( ( status === 301 || status === 302 ) && method === 'POST' ||
status === 303 && ! [ 'GET' , 'HEAD' ] . includes ( method ) ) {
method = 'GET' ;
postData = undefined ;
delete headers [ ` content-encoding ` ] ;
delete headers [ ` content-language ` ] ;
2021-10-22 16:52:49 -07:00
delete headers [ ` content-length ` ] ;
2021-09-14 18:31:35 -07:00
delete headers [ ` content-location ` ] ;
delete headers [ ` content-type ` ] ;
}
2021-10-26 23:20:52 -07:00
const redirectOptions : https.RequestOptions & { maxRedirects : number , deadline : number } = {
2021-09-14 18:31:35 -07:00
method ,
headers ,
agent : options.agent ,
maxRedirects : options.maxRedirects - 1 ,
timeout : options.timeout ,
deadline : options.deadline
} ;
2021-10-26 23:20:52 -07:00
// rejectUnauthorized = undefined is treated as true in node 12.
if ( options . rejectUnauthorized === false )
redirectOptions . rejectUnauthorized = false ;
2021-09-14 18:31:35 -07:00
// HTTP-redirect fetch step 4: If locationURL is null, then return response.
if ( response . headers . location ) {
const locationURL = new URL ( response . headers . location , url ) ;
2021-11-30 18:12:19 -08:00
fulfill ( this . _sendRequest ( progress , locationURL , redirectOptions , postData ) ) ;
2021-10-18 19:41:56 -07:00
request . destroy ( ) ;
2021-09-14 18:31:35 -07:00
return ;
}
2021-08-26 16:18:54 -07:00
}
2021-09-14 18:31:35 -07:00
if ( response . statusCode === 401 && ! options . headers ! [ 'authorization' ] ) {
const auth = response . headers [ 'www-authenticate' ] ;
const credentials = this . _defaultOptions ( ) . httpCredentials ;
if ( auth ? . trim ( ) . startsWith ( 'Basic ' ) && credentials ) {
2021-09-27 18:58:08 +02:00
const { username , password } = credentials ;
2021-09-14 18:31:35 -07:00
const encoded = Buffer . from ( ` ${ username || '' } : ${ password || '' } ` ) . toString ( 'base64' ) ;
options . headers ! [ 'authorization' ] = ` Basic ${ encoded } ` ;
2021-11-30 18:12:19 -08:00
fulfill ( this . _sendRequest ( progress , url , options , postData ) ) ;
2021-10-18 19:41:56 -07:00
request . destroy ( ) ;
2021-09-14 18:31:35 -07:00
return ;
}
2021-08-26 16:18:54 -07:00
}
2021-09-14 18:31:35 -07:00
response . on ( 'aborted' , ( ) = > reject ( new Error ( 'aborted' ) ) ) ;
2021-08-26 16:18:54 -07:00
2021-09-14 18:31:35 -07:00
let body : Readable = response ;
let transform : Transform | undefined ;
const encoding = response . headers [ 'content-encoding' ] ;
if ( encoding === 'gzip' || encoding === 'x-gzip' ) {
transform = zlib . createGunzip ( {
flush : zlib.constants.Z_SYNC_FLUSH ,
finishFlush : zlib.constants.Z_SYNC_FLUSH
} ) ;
} else if ( encoding === 'br' ) {
transform = zlib . createBrotliDecompress ( ) ;
} else if ( encoding === 'deflate' ) {
transform = zlib . createInflate ( ) ;
2021-08-26 16:18:54 -07:00
}
2021-09-14 18:31:35 -07:00
if ( transform ) {
body = pipeline ( response , transform , e = > {
if ( e )
reject ( new Error ( ` failed to decompress ' ${ encoding } ' encoding: ${ e } ` ) ) ;
} ) ;
2021-08-27 23:47:33 -07:00
}
2021-08-31 09:34:58 -07:00
2021-09-14 18:31:35 -07:00
const chunks : Buffer [ ] = [ ] ;
body . on ( 'data' , chunk = > chunks . push ( chunk ) ) ;
body . on ( 'end' , ( ) = > {
const body = Buffer . concat ( chunks ) ;
fulfill ( {
url : response.url || url . toString ( ) ,
status : response.statusCode || 0 ,
statusText : response.statusMessage || '' ,
headers : toHeadersArray ( response . rawHeaders ) ,
body
} ) ;
2021-08-26 16:18:54 -07:00
} ) ;
2021-09-14 18:31:35 -07:00
body . on ( 'error' , reject ) ;
2021-08-26 16:18:54 -07:00
} ) ;
2021-09-14 18:31:35 -07:00
request . on ( 'error' , reject ) ;
2021-09-22 19:30:56 +02:00
2021-10-18 19:41:56 -07:00
const disposeListener = ( ) = > {
reject ( new Error ( 'Request context disposed.' ) ) ;
request . destroy ( ) ;
} ;
2021-11-05 16:27:49 +01:00
this . on ( APIRequestContext . Events . Dispose , disposeListener ) ;
request . on ( 'close' , ( ) = > this . off ( APIRequestContext . Events . Dispose , disposeListener ) ) ;
2021-10-18 19:41:56 -07:00
2021-11-30 18:12:19 -08:00
progress . log ( ` → ${ options . method } ${ url . toString ( ) } ` ) ;
if ( options . headers ) {
for ( const [ name , value ] of Object . entries ( options . headers ) )
progress . log ( ` ${ name } : ${ value } ` ) ;
2021-10-08 14:11:40 -07:00
}
2021-09-22 19:30:56 +02:00
if ( options . deadline ) {
const rejectOnTimeout = ( ) = > {
reject ( new Error ( ` Request timed out after ${ options . timeout } ms ` ) ) ;
2021-10-18 19:41:56 -07:00
request . destroy ( ) ;
2021-09-22 19:30:56 +02:00
} ;
const remaining = options . deadline - monotonicTime ( ) ;
if ( remaining <= 0 ) {
rejectOnTimeout ( ) ;
return ;
}
request . setTimeout ( remaining , rejectOnTimeout ) ;
2021-09-14 18:31:35 -07:00
}
2021-09-22 19:30:56 +02:00
2021-09-14 18:31:35 -07:00
if ( postData )
request . write ( postData ) ;
request . end ( ) ;
2021-08-26 16:18:54 -07:00
} ) ;
2021-09-14 18:31:35 -07:00
}
}
2021-11-05 16:27:49 +01:00
export class BrowserContextAPIRequestContext extends APIRequestContext {
2021-09-14 18:31:35 -07:00
private readonly _context : BrowserContext ;
constructor ( context : BrowserContext ) {
super ( context ) ;
this . _context = context ;
2021-09-15 14:02:55 -07:00
context . once ( BrowserContext . Events . Close , ( ) = > this . _disposeImpl ( ) ) ;
}
override dispose() {
this . fetchResponses . clear ( ) ;
2021-09-14 18:31:35 -07:00
}
_defaultOptions ( ) : FetchRequestOptions {
return {
userAgent : this._context._options.userAgent || this . _context . _browser . userAgent ( ) ,
extraHTTPHeaders : this._context._options.extraHTTPHeaders ,
httpCredentials : this._context._options.httpCredentials ,
proxy : this._context._options.proxy || this . _context . _browser . options . proxy ,
timeoutSettings : this._context._timeoutSettings ,
ignoreHTTPSErrors : this._context._options.ignoreHTTPSErrors ,
baseURL : this._context._options.baseURL ,
2021-09-08 10:01:40 -07:00
} ;
2021-09-14 18:31:35 -07:00
}
2021-09-29 17:15:32 -07:00
async _addCookies ( cookies : types.NetworkCookie [ ] ) : Promise < void > {
2021-09-14 18:31:35 -07:00
await this . _context . addCookies ( cookies ) ;
}
2021-09-29 17:15:32 -07:00
async _cookies ( url : URL ) : Promise < types.NetworkCookie [ ] > {
return await this . _context . cookies ( url . toString ( ) ) ;
2021-09-14 18:31:35 -07:00
}
2021-09-30 14:14:29 -07:00
2021-11-05 16:27:49 +01:00
override async storageState ( ) : Promise < channels.APIRequestContextStorageStateResult > {
2021-09-30 14:14:29 -07:00
return this . _context . storageState ( ) ;
}
2021-09-14 18:31:35 -07:00
}
2021-11-05 16:27:49 +01:00
export class GlobalAPIRequestContext extends APIRequestContext {
2021-09-29 17:15:32 -07:00
private readonly _cookieStore : CookieStore = new CookieStore ( ) ;
2021-09-22 12:44:22 -07:00
private readonly _options : FetchRequestOptions ;
2021-09-30 14:14:29 -07:00
private readonly _origins : channels.OriginStorage [ ] | undefined ;
constructor ( playwright : Playwright , options : channels.PlaywrightNewRequestOptions ) {
2021-09-14 18:31:35 -07:00
super ( playwright ) ;
2021-09-22 12:44:22 -07:00
const timeoutSettings = new TimeoutSettings ( ) ;
if ( options . timeout !== undefined )
timeoutSettings . setDefaultTimeout ( options . timeout ) ;
const proxy = options . proxy ;
if ( proxy ? . server ) {
let url = proxy ? . server . trim ( ) ;
if ( ! /^\w+:\/\// . test ( url ) )
url = 'http://' + url ;
proxy . server = url ;
}
2021-09-30 14:14:29 -07:00
if ( options . storageState ) {
this . _origins = options . storageState . origins ;
this . _cookieStore . addCookies ( options . storageState . cookies ) ;
}
2021-09-22 12:44:22 -07:00
this . _options = {
baseURL : options.baseURL ,
2021-09-28 13:01:35 -07:00
userAgent : options.userAgent || ` Playwright/ ${ getPlaywrightVersion ( ) } ` ,
2021-09-22 12:44:22 -07:00
extraHTTPHeaders : options.extraHTTPHeaders ,
ignoreHTTPSErrors : ! ! options . ignoreHTTPSErrors ,
httpCredentials : options.httpCredentials ,
proxy ,
timeoutSettings ,
} ;
2021-09-14 18:31:35 -07:00
}
2021-09-15 14:02:55 -07:00
override dispose() {
this . _disposeImpl ( ) ;
}
2021-09-14 18:31:35 -07:00
_defaultOptions ( ) : FetchRequestOptions {
2021-09-22 12:44:22 -07:00
return this . _options ;
2021-09-14 18:31:35 -07:00
}
2021-09-29 17:15:32 -07:00
async _addCookies ( cookies : types.NetworkCookie [ ] ) : Promise < void > {
this . _cookieStore . addCookies ( cookies ) ;
2021-09-14 18:31:35 -07:00
}
2021-09-29 17:15:32 -07:00
async _cookies ( url : URL ) : Promise < types.NetworkCookie [ ] > {
return this . _cookieStore . cookies ( url ) ;
2021-09-14 18:31:35 -07:00
}
2021-09-30 14:14:29 -07:00
2021-11-05 16:27:49 +01:00
override async storageState ( ) : Promise < channels.APIRequestContextStorageStateResult > {
2021-09-30 14:14:29 -07:00
return {
cookies : this._cookieStore.allCookies ( ) ,
origins : this._origins || [ ]
} ;
}
2021-08-26 16:18:54 -07:00
}
2021-09-10 14:03:56 -07:00
function toHeadersArray ( rawHeaders : string [ ] ) : types . HeadersArray {
2021-08-26 16:18:54 -07:00
const result : types.HeadersArray = [ ] ;
2021-09-10 14:03:56 -07:00
for ( let i = 0 ; i < rawHeaders . length ; i += 2 )
result . push ( { name : rawHeaders [ i ] , value : rawHeaders [ i + 1 ] } ) ;
2021-08-26 16:18:54 -07:00
return result ;
}
const redirectStatus = [ 301 , 302 , 303 , 307 , 308 ] ;
2021-09-29 17:15:32 -07:00
function parseCookie ( header : string ) : types . NetworkCookie | null {
2021-08-24 14:29:04 -07:00
const pairs = header . split ( ';' ) . filter ( s = > s . trim ( ) . length > 0 ) . map ( p = > p . split ( '=' ) . map ( s = > s . trim ( ) ) ) ;
if ( ! pairs . length )
return null ;
const [ name , value ] = pairs [ 0 ] ;
const cookie : types.NetworkCookie = {
name ,
value ,
domain : '' ,
path : '' ,
expires : - 1 ,
httpOnly : false ,
secure : false ,
sameSite : 'Lax' // None for non-chromium
} ;
for ( let i = 1 ; i < pairs . length ; i ++ ) {
const [ name , value ] = pairs [ i ] ;
switch ( name . toLowerCase ( ) ) {
case 'expires' :
const expiresMs = ( + new Date ( value ) ) ;
if ( isFinite ( expiresMs ) )
cookie . expires = expiresMs / 1000 ;
break ;
case 'max-age' :
const maxAgeSec = parseInt ( value , 10 ) ;
if ( isFinite ( maxAgeSec ) )
cookie . expires = Date . now ( ) / 1000 + maxAgeSec ;
break ;
case 'domain' :
2021-09-29 17:15:32 -07:00
cookie . domain = value . toLocaleLowerCase ( ) || '' ;
if ( cookie . domain && ! cookie . domain . startsWith ( '.' ) )
cookie . domain = '.' + cookie . domain ;
2021-08-24 14:29:04 -07:00
break ;
case 'path' :
cookie . path = value || '' ;
break ;
case 'secure' :
cookie . secure = true ;
break ;
case 'httponly' :
cookie . httpOnly = true ;
break ;
}
}
return cookie ;
}
2021-09-16 17:48:43 -07:00
2021-11-11 11:12:24 -08:00
function isJsonParsable ( value : any ) {
if ( typeof value !== 'string' )
return false ;
try {
JSON . parse ( value ) ;
return true ;
} catch ( e ) {
if ( e instanceof SyntaxError )
return false ;
else
throw e ;
}
}
2021-11-05 16:27:49 +01:00
function serializePostData ( params : channels.APIRequestContextFetchParams , headers : { [ name : string ] : string } ) : Buffer | undefined {
2021-10-01 12:11:33 -07:00
assert ( ( params . postData ? 1 : 0 ) + ( params . jsonData ? 1 : 0 ) + ( params . formData ? 1 : 0 ) + ( params . multipartData ? 1 : 0 ) <= 1 , ` Only one of 'data', 'form' or 'multipart' can be specified ` ) ;
if ( params . jsonData ) {
2021-11-11 11:12:24 -08:00
const json = isJsonParsable ( params . jsonData ) ? params.jsonData : JSON.stringify ( params . jsonData ) ;
2021-10-01 12:11:33 -07:00
headers [ 'content-type' ] ? ? = 'application/json' ;
2021-09-16 17:48:43 -07:00
return Buffer . from ( json , 'utf8' ) ;
2021-10-01 12:11:33 -07:00
} else if ( params . formData ) {
2021-09-16 17:48:43 -07:00
const searchParams = new URLSearchParams ( ) ;
2021-10-01 12:11:33 -07:00
for ( const { name , value } of params . formData )
searchParams . append ( name , value ) ;
headers [ 'content-type' ] ? ? = 'application/x-www-form-urlencoded' ;
2021-09-16 17:48:43 -07:00
return Buffer . from ( searchParams . toString ( ) , 'utf8' ) ;
2021-10-01 12:11:33 -07:00
} else if ( params . multipartData ) {
2021-09-16 17:48:43 -07:00
const formData = new MultipartFormData ( ) ;
2021-10-01 12:11:33 -07:00
for ( const field of params . multipartData ) {
if ( field . file )
formData . addFileField ( field . name , field . file ) ;
else if ( field . value )
formData . addField ( field . name , field . value ) ;
2021-09-16 17:48:43 -07:00
}
2021-10-01 12:11:33 -07:00
headers [ 'content-type' ] ? ? = formData . contentTypeHeader ( ) ;
2021-09-16 17:48:43 -07:00
return formData . finish ( ) ;
2021-10-01 12:11:33 -07:00
} else if ( params . postData ) {
headers [ 'content-type' ] ? ? = 'application/octet-stream' ;
return Buffer . from ( params . postData , 'base64' ) ;
2021-09-16 17:48:43 -07:00
}
2021-10-01 12:11:33 -07:00
return undefined ;
2021-09-16 17:48:43 -07:00
}