[79a0317] | 1 | 'use strict'
|
---|
| 2 |
|
---|
| 3 | const { HEX } = require('./scopedChars')
|
---|
| 4 |
|
---|
| 5 | const IPV4_REG = /^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u
|
---|
| 6 |
|
---|
| 7 | function normalizeIPv4 (host) {
|
---|
| 8 | if (findToken(host, '.') < 3) { return { host, isIPV4: false } }
|
---|
| 9 | const matches = host.match(IPV4_REG) || []
|
---|
| 10 | const [address] = matches
|
---|
| 11 | if (address) {
|
---|
| 12 | return { host: stripLeadingZeros(address, '.'), isIPV4: true }
|
---|
| 13 | } else {
|
---|
| 14 | return { host, isIPV4: false }
|
---|
| 15 | }
|
---|
| 16 | }
|
---|
| 17 |
|
---|
| 18 | /**
|
---|
| 19 | * @param {string[]} input
|
---|
| 20 | * @param {boolean} [keepZero=false]
|
---|
| 21 | * @returns {string|undefined}
|
---|
| 22 | */
|
---|
| 23 | function stringArrayToHexStripped (input, keepZero = false) {
|
---|
| 24 | let acc = ''
|
---|
| 25 | let strip = true
|
---|
| 26 | for (const c of input) {
|
---|
| 27 | if (HEX[c] === undefined) return undefined
|
---|
| 28 | if (c !== '0' && strip === true) strip = false
|
---|
| 29 | if (!strip) acc += c
|
---|
| 30 | }
|
---|
| 31 | if (keepZero && acc.length === 0) acc = '0'
|
---|
| 32 | return acc
|
---|
| 33 | }
|
---|
| 34 |
|
---|
| 35 | function getIPV6 (input) {
|
---|
| 36 | let tokenCount = 0
|
---|
| 37 | const output = { error: false, address: '', zone: '' }
|
---|
| 38 | const address = []
|
---|
| 39 | const buffer = []
|
---|
| 40 | let isZone = false
|
---|
| 41 | let endipv6Encountered = false
|
---|
| 42 | let endIpv6 = false
|
---|
| 43 |
|
---|
| 44 | function consume () {
|
---|
| 45 | if (buffer.length) {
|
---|
| 46 | if (isZone === false) {
|
---|
| 47 | const hex = stringArrayToHexStripped(buffer)
|
---|
| 48 | if (hex !== undefined) {
|
---|
| 49 | address.push(hex)
|
---|
| 50 | } else {
|
---|
| 51 | output.error = true
|
---|
| 52 | return false
|
---|
| 53 | }
|
---|
| 54 | }
|
---|
| 55 | buffer.length = 0
|
---|
| 56 | }
|
---|
| 57 | return true
|
---|
| 58 | }
|
---|
| 59 |
|
---|
| 60 | for (let i = 0; i < input.length; i++) {
|
---|
| 61 | const cursor = input[i]
|
---|
| 62 | if (cursor === '[' || cursor === ']') { continue }
|
---|
| 63 | if (cursor === ':') {
|
---|
| 64 | if (endipv6Encountered === true) {
|
---|
| 65 | endIpv6 = true
|
---|
| 66 | }
|
---|
| 67 | if (!consume()) { break }
|
---|
| 68 | tokenCount++
|
---|
| 69 | address.push(':')
|
---|
| 70 | if (tokenCount > 7) {
|
---|
| 71 | // not valid
|
---|
| 72 | output.error = true
|
---|
| 73 | break
|
---|
| 74 | }
|
---|
| 75 | if (i - 1 >= 0 && input[i - 1] === ':') {
|
---|
| 76 | endipv6Encountered = true
|
---|
| 77 | }
|
---|
| 78 | continue
|
---|
| 79 | } else if (cursor === '%') {
|
---|
| 80 | if (!consume()) { break }
|
---|
| 81 | // switch to zone detection
|
---|
| 82 | isZone = true
|
---|
| 83 | } else {
|
---|
| 84 | buffer.push(cursor)
|
---|
| 85 | continue
|
---|
| 86 | }
|
---|
| 87 | }
|
---|
| 88 | if (buffer.length) {
|
---|
| 89 | if (isZone) {
|
---|
| 90 | output.zone = buffer.join('')
|
---|
| 91 | } else if (endIpv6) {
|
---|
| 92 | address.push(buffer.join(''))
|
---|
| 93 | } else {
|
---|
| 94 | address.push(stringArrayToHexStripped(buffer))
|
---|
| 95 | }
|
---|
| 96 | }
|
---|
| 97 | output.address = address.join('')
|
---|
| 98 | return output
|
---|
| 99 | }
|
---|
| 100 |
|
---|
| 101 | function normalizeIPv6 (host) {
|
---|
| 102 | if (findToken(host, ':') < 2) { return { host, isIPV6: false } }
|
---|
| 103 | const ipv6 = getIPV6(host)
|
---|
| 104 |
|
---|
| 105 | if (!ipv6.error) {
|
---|
| 106 | let newHost = ipv6.address
|
---|
| 107 | let escapedHost = ipv6.address
|
---|
| 108 | if (ipv6.zone) {
|
---|
| 109 | newHost += '%' + ipv6.zone
|
---|
| 110 | escapedHost += '%25' + ipv6.zone
|
---|
| 111 | }
|
---|
| 112 | return { host: newHost, escapedHost, isIPV6: true }
|
---|
| 113 | } else {
|
---|
| 114 | return { host, isIPV6: false }
|
---|
| 115 | }
|
---|
| 116 | }
|
---|
| 117 |
|
---|
| 118 | function stripLeadingZeros (str, token) {
|
---|
| 119 | let out = ''
|
---|
| 120 | let skip = true
|
---|
| 121 | const l = str.length
|
---|
| 122 | for (let i = 0; i < l; i++) {
|
---|
| 123 | const c = str[i]
|
---|
| 124 | if (c === '0' && skip) {
|
---|
| 125 | if ((i + 1 <= l && str[i + 1] === token) || i + 1 === l) {
|
---|
| 126 | out += c
|
---|
| 127 | skip = false
|
---|
| 128 | }
|
---|
| 129 | } else {
|
---|
| 130 | if (c === token) {
|
---|
| 131 | skip = true
|
---|
| 132 | } else {
|
---|
| 133 | skip = false
|
---|
| 134 | }
|
---|
| 135 | out += c
|
---|
| 136 | }
|
---|
| 137 | }
|
---|
| 138 | return out
|
---|
| 139 | }
|
---|
| 140 |
|
---|
| 141 | function findToken (str, token) {
|
---|
| 142 | let ind = 0
|
---|
| 143 | for (let i = 0; i < str.length; i++) {
|
---|
| 144 | if (str[i] === token) ind++
|
---|
| 145 | }
|
---|
| 146 | return ind
|
---|
| 147 | }
|
---|
| 148 |
|
---|
| 149 | const RDS1 = /^\.\.?\//u
|
---|
| 150 | const RDS2 = /^\/\.(?:\/|$)/u
|
---|
| 151 | const RDS3 = /^\/\.\.(?:\/|$)/u
|
---|
| 152 | const RDS5 = /^\/?(?:.|\n)*?(?=\/|$)/u
|
---|
| 153 |
|
---|
| 154 | function removeDotSegments (input) {
|
---|
| 155 | const output = []
|
---|
| 156 |
|
---|
| 157 | while (input.length) {
|
---|
| 158 | if (input.match(RDS1)) {
|
---|
| 159 | input = input.replace(RDS1, '')
|
---|
| 160 | } else if (input.match(RDS2)) {
|
---|
| 161 | input = input.replace(RDS2, '/')
|
---|
| 162 | } else if (input.match(RDS3)) {
|
---|
| 163 | input = input.replace(RDS3, '/')
|
---|
| 164 | output.pop()
|
---|
| 165 | } else if (input === '.' || input === '..') {
|
---|
| 166 | input = ''
|
---|
| 167 | } else {
|
---|
| 168 | const im = input.match(RDS5)
|
---|
| 169 | if (im) {
|
---|
| 170 | const s = im[0]
|
---|
| 171 | input = input.slice(s.length)
|
---|
| 172 | output.push(s)
|
---|
| 173 | } else {
|
---|
| 174 | throw new Error('Unexpected dot segment condition')
|
---|
| 175 | }
|
---|
| 176 | }
|
---|
| 177 | }
|
---|
| 178 | return output.join('')
|
---|
| 179 | }
|
---|
| 180 |
|
---|
| 181 | function normalizeComponentEncoding (components, esc) {
|
---|
| 182 | const func = esc !== true ? escape : unescape
|
---|
| 183 | if (components.scheme !== undefined) {
|
---|
| 184 | components.scheme = func(components.scheme)
|
---|
| 185 | }
|
---|
| 186 | if (components.userinfo !== undefined) {
|
---|
| 187 | components.userinfo = func(components.userinfo)
|
---|
| 188 | }
|
---|
| 189 | if (components.host !== undefined) {
|
---|
| 190 | components.host = func(components.host)
|
---|
| 191 | }
|
---|
| 192 | if (components.path !== undefined) {
|
---|
| 193 | components.path = func(components.path)
|
---|
| 194 | }
|
---|
| 195 | if (components.query !== undefined) {
|
---|
| 196 | components.query = func(components.query)
|
---|
| 197 | }
|
---|
| 198 | if (components.fragment !== undefined) {
|
---|
| 199 | components.fragment = func(components.fragment)
|
---|
| 200 | }
|
---|
| 201 | return components
|
---|
| 202 | }
|
---|
| 203 |
|
---|
| 204 | function recomposeAuthority (components) {
|
---|
| 205 | const uriTokens = []
|
---|
| 206 |
|
---|
| 207 | if (components.userinfo !== undefined) {
|
---|
| 208 | uriTokens.push(components.userinfo)
|
---|
| 209 | uriTokens.push('@')
|
---|
| 210 | }
|
---|
| 211 |
|
---|
| 212 | if (components.host !== undefined) {
|
---|
| 213 | let host = unescape(components.host)
|
---|
| 214 | const ipV4res = normalizeIPv4(host)
|
---|
| 215 |
|
---|
| 216 | if (ipV4res.isIPV4) {
|
---|
| 217 | host = ipV4res.host
|
---|
| 218 | } else {
|
---|
| 219 | const ipV6res = normalizeIPv6(ipV4res.host)
|
---|
| 220 | if (ipV6res.isIPV6 === true) {
|
---|
| 221 | host = `[${ipV6res.escapedHost}]`
|
---|
| 222 | } else {
|
---|
| 223 | host = components.host
|
---|
| 224 | }
|
---|
| 225 | }
|
---|
| 226 | uriTokens.push(host)
|
---|
| 227 | }
|
---|
| 228 |
|
---|
| 229 | if (typeof components.port === 'number' || typeof components.port === 'string') {
|
---|
| 230 | uriTokens.push(':')
|
---|
| 231 | uriTokens.push(String(components.port))
|
---|
| 232 | }
|
---|
| 233 |
|
---|
| 234 | return uriTokens.length ? uriTokens.join('') : undefined
|
---|
| 235 | };
|
---|
| 236 |
|
---|
| 237 | module.exports = {
|
---|
| 238 | recomposeAuthority,
|
---|
| 239 | normalizeComponentEncoding,
|
---|
| 240 | removeDotSegments,
|
---|
| 241 | normalizeIPv4,
|
---|
| 242 | normalizeIPv6,
|
---|
| 243 | stringArrayToHexStripped
|
---|
| 244 | }
|
---|