2021-03-07 02:58:48 +00:00
|
|
|
import {Request} from "../lifecycle/Request";
|
2021-03-07 15:58:21 +00:00
|
|
|
import {uninfer, infer, uuid_v4} from "@extollo/util";
|
2021-03-07 02:58:48 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Base type representing a parsed cookie.
|
|
|
|
*/
|
|
|
|
export interface HTTPCookie {
|
|
|
|
key: string,
|
|
|
|
originalValue: string,
|
|
|
|
value: any,
|
|
|
|
exists: boolean,
|
2021-03-07 15:58:21 +00:00
|
|
|
options?: HTTPCookieOptions,
|
2021-03-07 02:58:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export type MaybeHTTPCookie = HTTPCookie | undefined;
|
|
|
|
|
2021-03-07 15:58:21 +00:00
|
|
|
export interface HTTPCookieOptions {
|
|
|
|
domain?: string,
|
|
|
|
expires?: Date, // encodeURIComponent
|
|
|
|
httpOnly?: boolean,
|
|
|
|
maxAge?: number,
|
|
|
|
path?: string,
|
|
|
|
secure?: boolean,
|
|
|
|
signed?: boolean,
|
|
|
|
sameSite?: 'strict' | 'lax' | 'none-secure',
|
|
|
|
}
|
|
|
|
|
2021-03-07 02:58:48 +00:00
|
|
|
export class HTTPCookieJar {
|
|
|
|
protected parsed: {[key: string]: HTTPCookie} = {}
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
protected request: Request,
|
|
|
|
) {
|
|
|
|
this.parseCookies()
|
|
|
|
}
|
|
|
|
|
|
|
|
get(name: string): MaybeHTTPCookie {
|
|
|
|
if ( name in this.parsed ) {
|
|
|
|
return this.parsed[name]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-07 15:58:21 +00:00
|
|
|
set(name: string, value: any, options?: HTTPCookieOptions) {
|
|
|
|
this.parsed[name] = {
|
|
|
|
key: name,
|
|
|
|
value,
|
|
|
|
originalValue: uninfer(value),
|
|
|
|
exists: false,
|
|
|
|
options,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-07 18:51:04 +00:00
|
|
|
has(name: string) {
|
|
|
|
return !!this.parsed[name]
|
|
|
|
}
|
|
|
|
|
2021-03-07 15:58:21 +00:00
|
|
|
clear(name: string, options?: HTTPCookieOptions) {
|
|
|
|
if ( !options ) options = {}
|
|
|
|
options.expires = new Date(0)
|
|
|
|
|
|
|
|
this.parsed[name] = {
|
|
|
|
key: name,
|
|
|
|
value: undefined,
|
|
|
|
originalValue: uuid_v4(),
|
|
|
|
exists: false,
|
|
|
|
options,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getSetCookieHeaders(): string[] {
|
|
|
|
const headers: string[] = []
|
|
|
|
|
|
|
|
for ( const key in this.parsed ) {
|
|
|
|
if ( !this.parsed.hasOwnProperty(key) ) continue
|
2021-03-07 18:51:04 +00:00
|
|
|
|
2021-03-07 15:58:21 +00:00
|
|
|
const cookie = this.parsed[key]
|
2021-03-07 18:51:04 +00:00
|
|
|
if ( cookie.exists ) continue
|
2021-03-07 15:58:21 +00:00
|
|
|
|
|
|
|
const parts = []
|
|
|
|
parts.push(`${key}=${encodeURIComponent(cookie.originalValue)}`)
|
|
|
|
|
|
|
|
if ( cookie.options?.expires ) {
|
|
|
|
parts.push(`Expires=${cookie.options.expires.toUTCString()}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( cookie.options?.maxAge ) {
|
|
|
|
parts.push(`Max-Age=${Math.floor(cookie.options.maxAge)}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( cookie.options?.domain ) {
|
|
|
|
parts.push(`Domain=${cookie.options.domain}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( cookie.options?.path ) {
|
|
|
|
parts.push(`Path=${cookie.options.path}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( cookie.options?.secure ) {
|
|
|
|
parts.push('Secure')
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( cookie.options?.httpOnly ) {
|
|
|
|
parts.push('HttpOnly')
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( cookie.options?.sameSite ) {
|
|
|
|
const map = {
|
|
|
|
strict: 'Strict',
|
|
|
|
lax: 'Lax',
|
|
|
|
'none-secure': 'None; Secure'
|
|
|
|
}
|
|
|
|
|
|
|
|
parts.push(map[cookie.options.sameSite])
|
|
|
|
}
|
|
|
|
|
|
|
|
headers.push(parts.join('; '))
|
|
|
|
}
|
|
|
|
|
|
|
|
return headers
|
|
|
|
}
|
|
|
|
|
2021-03-07 02:58:48 +00:00
|
|
|
private parseCookies() {
|
|
|
|
const cookies = String(this.request.getHeader('cookie'))
|
|
|
|
cookies.split(';').forEach(cookie => {
|
|
|
|
const parts = cookie.split('=')
|
|
|
|
|
|
|
|
const key = parts.shift()?.trim()
|
|
|
|
if ( !key ) return;
|
|
|
|
|
|
|
|
const value = decodeURI(parts.join('='))
|
|
|
|
|
|
|
|
this.parsed[key] = {
|
|
|
|
key,
|
|
|
|
originalValue: value,
|
|
|
|
value: infer(value),
|
|
|
|
exists: true,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|