You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					237 lines
				
				5.5 KiB
			
		
		
			
		
	
	
					237 lines
				
				5.5 KiB
			| 
											3 years ago
										 | 'use strict' | ||
|  | 
 | ||
|  | const { HEX } = require('./scopedChars') | ||
|  | 
 | ||
|  | function normalizeIPv4 (host) { | ||
|  |   if (findToken(host, '.') < 3) { return { host, isIPV4: false } } | ||
|  |   const matches = host.match(/^(\b25[0-5]|\b2[0-4][0-9]|\b[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/) || [] | ||
|  |   const [address] = matches | ||
|  |   if (address) { | ||
|  |     return { host: stripLeadingZeros(address, '.'), isIPV4: true } | ||
|  |   } else { | ||
|  |     return { host, isIPV4: false } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | function stringToHexStripped (input) { | ||
|  |   let acc = '' | ||
|  |   let strip = true | ||
|  |   for (const c of input) { | ||
|  |     if (c !== '0' && strip === true) strip = false | ||
|  |     if (HEX[c] === undefined) return undefined | ||
|  |     if (!strip) acc += c | ||
|  |   } | ||
|  |   return acc | ||
|  | } | ||
|  | 
 | ||
|  | function getIPV6 (input) { | ||
|  |   let tokenCount = 0 | ||
|  |   const output = { error: false, address: '', zone: '' } | ||
|  |   const address = [] | ||
|  |   const buffer = [] | ||
|  |   let isZone = false | ||
|  |   let endipv6Encountered = false | ||
|  |   let endIpv6 = false | ||
|  | 
 | ||
|  |   function consume () { | ||
|  |     if (buffer.length) { | ||
|  |       if (isZone === false) { | ||
|  |         const hex = stringToHexStripped(buffer.join('')) | ||
|  |         if (hex !== undefined) { | ||
|  |           address.push(hex) | ||
|  |         } else { | ||
|  |           output.error = true | ||
|  |           return false | ||
|  |         } | ||
|  |       } | ||
|  |       buffer.length = 0 | ||
|  |     } | ||
|  |     return true | ||
|  |   } | ||
|  | 
 | ||
|  |   for (let i = 0; i < input.length; i++) { | ||
|  |     const cursor = input[i] | ||
|  |     if (cursor === '[' || cursor === ']') { continue } | ||
|  |     if (cursor === ':') { | ||
|  |       if (endipv6Encountered === true) { | ||
|  |         endIpv6 = true | ||
|  |       } | ||
|  |       if (!consume()) { break } | ||
|  |       tokenCount++ | ||
|  |       address.push(':') | ||
|  |       if (tokenCount > 7) { | ||
|  |         // not valid
 | ||
|  |         output.error = true | ||
|  |         break | ||
|  |       } | ||
|  |       if (i - 1 >= 0 && input[i - 1] === ':') { | ||
|  |         endipv6Encountered = true | ||
|  |       } | ||
|  |       continue | ||
|  |     } else if (cursor === '%') { | ||
|  |       if (!consume()) { break } | ||
|  |       // switch to zone detection
 | ||
|  |       isZone = true | ||
|  |     } else { | ||
|  |       buffer.push(cursor) | ||
|  |       continue | ||
|  |     } | ||
|  |   } | ||
|  |   if (buffer.length) { | ||
|  |     if (isZone) { | ||
|  |       output.zone = buffer.join('') | ||
|  |     } else if (endIpv6) { | ||
|  |       address.push(buffer.join('')) | ||
|  |     } else { | ||
|  |       address.push(stringToHexStripped(buffer.join(''))) | ||
|  |     } | ||
|  |   } | ||
|  |   output.address = address.join('') | ||
|  |   return output | ||
|  | } | ||
|  | 
 | ||
|  | function normalizeIPv6 (host, opts = {}) { | ||
|  |   if (findToken(host, ':') < 2) { return { host, isIPV6: false } } | ||
|  |   const ipv6 = getIPV6(host) | ||
|  | 
 | ||
|  |   if (!ipv6.error) { | ||
|  |     let newHost = ipv6.address | ||
|  |     let escapedHost = ipv6.address | ||
|  |     if (ipv6.zone) { | ||
|  |       newHost += '%' + ipv6.zone | ||
|  |       escapedHost += '%25' + ipv6.zone | ||
|  |     } | ||
|  |     return { host: newHost, escapedHost, isIPV6: true } | ||
|  |   } else { | ||
|  |     return { host, isIPV6: false } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | function stripLeadingZeros (str, token) { | ||
|  |   let out = '' | ||
|  |   let skip = true | ||
|  |   const l = str.length | ||
|  |   for (let i = 0; i < l; i++) { | ||
|  |     const c = str[i] | ||
|  |     if (c === '0' && skip) { | ||
|  |       if ((i + 1 <= l && str[i + 1] === token) || i + 1 === l) { | ||
|  |         out += c | ||
|  |         skip = false | ||
|  |       } | ||
|  |     } else { | ||
|  |       if (c === token) { | ||
|  |         skip = true | ||
|  |       } else { | ||
|  |         skip = false | ||
|  |       } | ||
|  |       out += c | ||
|  |     } | ||
|  |   } | ||
|  |   return out | ||
|  | } | ||
|  | 
 | ||
|  | function findToken (str, token) { | ||
|  |   let ind = 0 | ||
|  |   for (let i = 0; i < str.length; i++) { | ||
|  |     if (str[i] === token) ind++ | ||
|  |   } | ||
|  |   return ind | ||
|  | } | ||
|  | 
 | ||
|  | const RDS1 = /^\.\.?\// | ||
|  | const RDS2 = /^\/\.(\/|$)/ | ||
|  | const RDS3 = /^\/\.\.(\/|$)/ | ||
|  | const RDS5 = /^\/?(?:.|\n)*?(?=\/|$)/ | ||
|  | 
 | ||
|  | function removeDotSegments (input) { | ||
|  |   const output = [] | ||
|  | 
 | ||
|  |   while (input.length) { | ||
|  |     if (input.match(RDS1)) { | ||
|  |       input = input.replace(RDS1, '') | ||
|  |     } else if (input.match(RDS2)) { | ||
|  |       input = input.replace(RDS2, '/') | ||
|  |     } else if (input.match(RDS3)) { | ||
|  |       input = input.replace(RDS3, '/') | ||
|  |       output.pop() | ||
|  |     } else if (input === '.' || input === '..') { | ||
|  |       input = '' | ||
|  |     } else { | ||
|  |       const im = input.match(RDS5) | ||
|  |       if (im) { | ||
|  |         const s = im[0] | ||
|  |         input = input.slice(s.length) | ||
|  |         output.push(s) | ||
|  |       } else { | ||
|  |         throw new Error('Unexpected dot segment condition') | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  |   return output.join('') | ||
|  | } | ||
|  | 
 | ||
|  | function normalizeComponentEncoding (components, esc) { | ||
|  |   const func = esc !== true ? escape : unescape | ||
|  |   if (components.scheme !== undefined) { | ||
|  |     components.scheme = func(components.scheme) | ||
|  |   } | ||
|  |   if (components.userinfo !== undefined) { | ||
|  |     components.userinfo = func(components.userinfo) | ||
|  |   } | ||
|  |   if (components.host !== undefined) { | ||
|  |     components.host = func(components.host) | ||
|  |   } | ||
|  |   if (components.path !== undefined) { | ||
|  |     components.path = func(components.path) | ||
|  |   } | ||
|  |   if (components.query !== undefined) { | ||
|  |     components.query = func(components.query) | ||
|  |   } | ||
|  |   if (components.fragment !== undefined) { | ||
|  |     components.fragment = func(components.fragment) | ||
|  |   } | ||
|  |   return components | ||
|  | } | ||
|  | 
 | ||
|  | function recomposeAuthority (components, options) { | ||
|  |   const uriTokens = [] | ||
|  | 
 | ||
|  |   if (components.userinfo !== undefined) { | ||
|  |     uriTokens.push(components.userinfo) | ||
|  |     uriTokens.push('@') | ||
|  |   } | ||
|  | 
 | ||
|  |   if (components.host !== undefined) { | ||
|  |     let host = unescape(components.host) | ||
|  |     const ipV4res = normalizeIPv4(host) | ||
|  | 
 | ||
|  |     if (ipV4res.isIPV4) { | ||
|  |       host = ipV4res.host | ||
|  |     } else { | ||
|  |       const ipV6res = normalizeIPv6(ipV4res.host, { isIPV4: false }) | ||
|  |       if (ipV6res.isIPV6 === true) { | ||
|  |         host = `[${ipV6res.escapedHost}]` | ||
|  |       } else { | ||
|  |         host = components.host | ||
|  |       } | ||
|  |     } | ||
|  |     uriTokens.push(host) | ||
|  |   } | ||
|  | 
 | ||
|  |   if (typeof components.port === 'number' || typeof components.port === 'string') { | ||
|  |     uriTokens.push(':') | ||
|  |     uriTokens.push(String(components.port)) | ||
|  |   } | ||
|  | 
 | ||
|  |   return uriTokens.length ? uriTokens.join('') : undefined | ||
|  | }; | ||
|  | 
 | ||
|  | module.exports = { | ||
|  |   recomposeAuthority, | ||
|  |   normalizeComponentEncoding, | ||
|  |   removeDotSegments, | ||
|  |   normalizeIPv4, | ||
|  |   normalizeIPv6, | ||
|  |   stringToHexStripped | ||
|  | } |