152 lines
4.1 KiB
TypeScript
152 lines
4.1 KiB
TypeScript
import { Agent } from 'http'
|
|
import { RequestOptions, Agent as HttpsAgent } from 'https'
|
|
import { Logger } from '@open-draft/logger'
|
|
|
|
const logger = new Logger('utils getUrlByRequestOptions')
|
|
|
|
// Request instance constructed by the "request" library
|
|
// has a "self" property that has a "uri" field. This is
|
|
// reproducible by performing a "XMLHttpRequest" request in JSDOM.
|
|
export interface RequestSelf {
|
|
uri?: URL
|
|
}
|
|
|
|
export type ResolvedRequestOptions = RequestOptions & RequestSelf
|
|
|
|
export const DEFAULT_PATH = '/'
|
|
const DEFAULT_PROTOCOL = 'http:'
|
|
const DEFAULT_HOSTNAME = 'localhost'
|
|
const SSL_PORT = 443
|
|
|
|
function getAgent(
|
|
options: ResolvedRequestOptions
|
|
): Agent | HttpsAgent | undefined {
|
|
return options.agent instanceof Agent ? options.agent : undefined
|
|
}
|
|
|
|
function getProtocolByRequestOptions(options: ResolvedRequestOptions): string {
|
|
if (options.protocol) {
|
|
return options.protocol
|
|
}
|
|
|
|
const agent = getAgent(options)
|
|
const agentProtocol = (agent as RequestOptions)?.protocol
|
|
|
|
if (agentProtocol) {
|
|
return agentProtocol
|
|
}
|
|
|
|
const port = getPortByRequestOptions(options)
|
|
const isSecureRequest = options.cert || port === SSL_PORT
|
|
|
|
return isSecureRequest ? 'https:' : options.uri?.protocol || DEFAULT_PROTOCOL
|
|
}
|
|
|
|
function getPortByRequestOptions(
|
|
options: ResolvedRequestOptions
|
|
): number | undefined {
|
|
// Use the explicitly provided port.
|
|
if (options.port) {
|
|
return Number(options.port)
|
|
}
|
|
|
|
// Otherwise, try to resolve port from the agent.
|
|
const agent = getAgent(options)
|
|
|
|
if ((agent as HttpsAgent)?.options.port) {
|
|
return Number((agent as HttpsAgent).options.port)
|
|
}
|
|
|
|
if ((agent as RequestOptions)?.defaultPort) {
|
|
return Number((agent as RequestOptions).defaultPort)
|
|
}
|
|
|
|
// Lastly, return undefined indicating that the port
|
|
// must inferred from the protocol. Do not infer it here.
|
|
return undefined
|
|
}
|
|
|
|
interface RequestAuth {
|
|
username: string
|
|
password: string
|
|
}
|
|
|
|
function getAuthByRequestOptions(
|
|
options: ResolvedRequestOptions
|
|
): RequestAuth | undefined {
|
|
if (options.auth) {
|
|
const [username, password] = options.auth.split(':')
|
|
return { username, password }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if host looks like an IPv6 address without surrounding brackets
|
|
* It assumes any host containing `:` is definitely not IPv4 and probably IPv6,
|
|
* but note that this could include invalid IPv6 addresses as well.
|
|
*/
|
|
function isRawIPv6Address(host: string): boolean {
|
|
return host.includes(':') && !host.startsWith('[') && !host.endsWith(']')
|
|
}
|
|
|
|
function getHostname(options: ResolvedRequestOptions): string | undefined {
|
|
let host = options.hostname || options.host
|
|
|
|
if (host) {
|
|
if (isRawIPv6Address(host)) {
|
|
host = `[${host}]`
|
|
}
|
|
|
|
// Check the presence of the port, and if it's present,
|
|
// remove it from the host, returning a hostname.
|
|
return new URL(`http://${host}`).hostname
|
|
}
|
|
|
|
return DEFAULT_HOSTNAME
|
|
}
|
|
|
|
/**
|
|
* Creates a `URL` instance from a given `RequestOptions` object.
|
|
*/
|
|
export function getUrlByRequestOptions(options: ResolvedRequestOptions): URL {
|
|
logger.info('request options', options)
|
|
|
|
if (options.uri) {
|
|
logger.info(
|
|
'constructing url from explicitly provided "options.uri": %s',
|
|
options.uri
|
|
)
|
|
return new URL(options.uri.href)
|
|
}
|
|
|
|
logger.info('figuring out url from request options...')
|
|
|
|
const protocol = getProtocolByRequestOptions(options)
|
|
logger.info('protocol', protocol)
|
|
|
|
const port = getPortByRequestOptions(options)
|
|
logger.info('port', port)
|
|
|
|
const hostname = getHostname(options)
|
|
logger.info('hostname', hostname)
|
|
|
|
const path = options.path || DEFAULT_PATH
|
|
logger.info('path', path)
|
|
|
|
const credentials = getAuthByRequestOptions(options)
|
|
logger.info('credentials', credentials)
|
|
|
|
const authString = credentials
|
|
? `${credentials.username}:${credentials.password}@`
|
|
: ''
|
|
logger.info('auth string:', authString)
|
|
|
|
const portString = typeof port !== 'undefined' ? `:${port}` : ''
|
|
const url = new URL(`${protocol}//${hostname}${portString}${path}`)
|
|
url.username = credentials?.username || ''
|
|
url.password = credentials?.password || ''
|
|
|
|
logger.info('created url:', url)
|
|
|
|
return url
|
|
}
|