File size: 4,191 Bytes
9ada4bc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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
}