massive update, probably broken
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-go / node_modules / whatwg-url / lib / url-state-machine.js
1 "use strict";\r
2 const punycode = require("punycode");\r
3 const tr46 = require("tr46");\r
4 \r
5 const specialSchemes = {\r
6   ftp: 21,\r
7   file: null,\r
8   gopher: 70,\r
9   http: 80,\r
10   https: 443,\r
11   ws: 80,\r
12   wss: 443\r
13 };\r
14 \r
15 const failure = Symbol("failure");\r
16 \r
17 function countSymbols(str) {\r
18   return punycode.ucs2.decode(str).length;\r
19 }\r
20 \r
21 function at(input, idx) {\r
22   const c = input[idx];\r
23   return isNaN(c) ? undefined : String.fromCodePoint(c);\r
24 }\r
25 \r
26 function isASCIIDigit(c) {\r
27   return c >= 0x30 && c <= 0x39;\r
28 }\r
29 \r
30 function isASCIIAlpha(c) {\r
31   return (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A);\r
32 }\r
33 \r
34 function isASCIIAlphanumeric(c) {\r
35   return isASCIIAlpha(c) || isASCIIDigit(c);\r
36 }\r
37 \r
38 function isASCIIHex(c) {\r
39   return isASCIIDigit(c) || (c >= 0x41 && c <= 0x46) || (c >= 0x61 && c <= 0x66);\r
40 }\r
41 \r
42 function isSingleDot(buffer) {\r
43   return buffer === "." || buffer.toLowerCase() === "%2e";\r
44 }\r
45 \r
46 function isDoubleDot(buffer) {\r
47   buffer = buffer.toLowerCase();\r
48   return buffer === ".." || buffer === "%2e." || buffer === ".%2e" || buffer === "%2e%2e";\r
49 }\r
50 \r
51 function isWindowsDriveLetterCodePoints(cp1, cp2) {\r
52   return isASCIIAlpha(cp1) && (cp2 === 58 || cp2 === 124);\r
53 }\r
54 \r
55 function isWindowsDriveLetterString(string) {\r
56   return string.length === 2 && isASCIIAlpha(string.codePointAt(0)) && (string[1] === ":" || string[1] === "|");\r
57 }\r
58 \r
59 function isNormalizedWindowsDriveLetterString(string) {\r
60   return string.length === 2 && isASCIIAlpha(string.codePointAt(0)) && string[1] === ":";\r
61 }\r
62 \r
63 function containsForbiddenHostCodePoint(string) {\r
64   return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|%|\/|:|\?|@|\[|\\|\]/) !== -1;\r
65 }\r
66 \r
67 function containsForbiddenHostCodePointExcludingPercent(string) {\r
68   return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|\/|:|\?|@|\[|\\|\]/) !== -1;\r
69 }\r
70 \r
71 function isSpecialScheme(scheme) {\r
72   return specialSchemes[scheme] !== undefined;\r
73 }\r
74 \r
75 function isSpecial(url) {\r
76   return isSpecialScheme(url.scheme);\r
77 }\r
78 \r
79 function defaultPort(scheme) {\r
80   return specialSchemes[scheme];\r
81 }\r
82 \r
83 function percentEncode(c) {\r
84   let hex = c.toString(16).toUpperCase();\r
85   if (hex.length === 1) {\r
86     hex = "0" + hex;\r
87   }\r
88 \r
89   return "%" + hex;\r
90 }\r
91 \r
92 function utf8PercentEncode(c) {\r
93   const buf = new Buffer(c);\r
94 \r
95   let str = "";\r
96 \r
97   for (let i = 0; i < buf.length; ++i) {\r
98     str += percentEncode(buf[i]);\r
99   }\r
100 \r
101   return str;\r
102 }\r
103 \r
104 function utf8PercentDecode(str) {\r
105   const input = new Buffer(str);\r
106   const output = [];\r
107   for (let i = 0; i < input.length; ++i) {\r
108     if (input[i] !== 37) {\r
109       output.push(input[i]);\r
110     } else if (input[i] === 37 && isASCIIHex(input[i + 1]) && isASCIIHex(input[i + 2])) {\r
111       output.push(parseInt(input.slice(i + 1, i + 3).toString(), 16));\r
112       i += 2;\r
113     } else {\r
114       output.push(input[i]);\r
115     }\r
116   }\r
117   return new Buffer(output).toString();\r
118 }\r
119 \r
120 function isC0ControlPercentEncode(c) {\r
121   return c <= 0x1F || c > 0x7E;\r
122 }\r
123 \r
124 const extraPathPercentEncodeSet = new Set([32, 34, 35, 60, 62, 63, 96, 123, 125]);\r
125 function isPathPercentEncode(c) {\r
126   return isC0ControlPercentEncode(c) || extraPathPercentEncodeSet.has(c);\r
127 }\r
128 \r
129 const extraUserinfoPercentEncodeSet =\r
130   new Set([47, 58, 59, 61, 64, 91, 92, 93, 94, 124]);\r
131 function isUserinfoPercentEncode(c) {\r
132   return isPathPercentEncode(c) || extraUserinfoPercentEncodeSet.has(c);\r
133 }\r
134 \r
135 function percentEncodeChar(c, encodeSetPredicate) {\r
136   const cStr = String.fromCodePoint(c);\r
137 \r
138   if (encodeSetPredicate(c)) {\r
139     return utf8PercentEncode(cStr);\r
140   }\r
141 \r
142   return cStr;\r
143 }\r
144 \r
145 function parseIPv4Number(input) {\r
146   let R = 10;\r
147 \r
148   if (input.length >= 2 && input.charAt(0) === "0" && input.charAt(1).toLowerCase() === "x") {\r
149     input = input.substring(2);\r
150     R = 16;\r
151   } else if (input.length >= 2 && input.charAt(0) === "0") {\r
152     input = input.substring(1);\r
153     R = 8;\r
154   }\r
155 \r
156   if (input === "") {\r
157     return 0;\r
158   }\r
159 \r
160   const regex = R === 10 ? /[^0-9]/ : (R === 16 ? /[^0-9A-Fa-f]/ : /[^0-7]/);\r
161   if (regex.test(input)) {\r
162     return failure;\r
163   }\r
164 \r
165   return parseInt(input, R);\r
166 }\r
167 \r
168 function parseIPv4(input) {\r
169   const parts = input.split(".");\r
170   if (parts[parts.length - 1] === "") {\r
171     if (parts.length > 1) {\r
172       parts.pop();\r
173     }\r
174   }\r
175 \r
176   if (parts.length > 4) {\r
177     return input;\r
178   }\r
179 \r
180   const numbers = [];\r
181   for (const part of parts) {\r
182     if (part === "") {\r
183       return input;\r
184     }\r
185     const n = parseIPv4Number(part);\r
186     if (n === failure) {\r
187       return input;\r
188     }\r
189 \r
190     numbers.push(n);\r
191   }\r
192 \r
193   for (let i = 0; i < numbers.length - 1; ++i) {\r
194     if (numbers[i] > 255) {\r
195       return failure;\r
196     }\r
197   }\r
198   if (numbers[numbers.length - 1] >= Math.pow(256, 5 - numbers.length)) {\r
199     return failure;\r
200   }\r
201 \r
202   let ipv4 = numbers.pop();\r
203   let counter = 0;\r
204 \r
205   for (const n of numbers) {\r
206     ipv4 += n * Math.pow(256, 3 - counter);\r
207     ++counter;\r
208   }\r
209 \r
210   return ipv4;\r
211 }\r
212 \r
213 function serializeIPv4(address) {\r
214   let output = "";\r
215   let n = address;\r
216 \r
217   for (let i = 1; i <= 4; ++i) {\r
218     output = String(n % 256) + output;\r
219     if (i !== 4) {\r
220       output = "." + output;\r
221     }\r
222     n = Math.floor(n / 256);\r
223   }\r
224 \r
225   return output;\r
226 }\r
227 \r
228 function parseIPv6(input) {\r
229   const address = [0, 0, 0, 0, 0, 0, 0, 0];\r
230   let pieceIndex = 0;\r
231   let compress = null;\r
232   let pointer = 0;\r
233 \r
234   input = punycode.ucs2.decode(input);\r
235 \r
236   if (input[pointer] === 58) {\r
237     if (input[pointer + 1] !== 58) {\r
238       return failure;\r
239     }\r
240 \r
241     pointer += 2;\r
242     ++pieceIndex;\r
243     compress = pieceIndex;\r
244   }\r
245 \r
246   while (pointer < input.length) {\r
247     if (pieceIndex === 8) {\r
248       return failure;\r
249     }\r
250 \r
251     if (input[pointer] === 58) {\r
252       if (compress !== null) {\r
253         return failure;\r
254       }\r
255       ++pointer;\r
256       ++pieceIndex;\r
257       compress = pieceIndex;\r
258       continue;\r
259     }\r
260 \r
261     let value = 0;\r
262     let length = 0;\r
263 \r
264     while (length < 4 && isASCIIHex(input[pointer])) {\r
265       value = value * 0x10 + parseInt(at(input, pointer), 16);\r
266       ++pointer;\r
267       ++length;\r
268     }\r
269 \r
270     if (input[pointer] === 46) {\r
271       if (length === 0) {\r
272         return failure;\r
273       }\r
274 \r
275       pointer -= length;\r
276 \r
277       if (pieceIndex > 6) {\r
278         return failure;\r
279       }\r
280 \r
281       let numbersSeen = 0;\r
282 \r
283       while (input[pointer] !== undefined) {\r
284         let ipv4Piece = null;\r
285 \r
286         if (numbersSeen > 0) {\r
287           if (input[pointer] === 46 && numbersSeen < 4) {\r
288             ++pointer;\r
289           } else {\r
290             return failure;\r
291           }\r
292         }\r
293 \r
294         if (!isASCIIDigit(input[pointer])) {\r
295           return failure;\r
296         }\r
297 \r
298         while (isASCIIDigit(input[pointer])) {\r
299           const number = parseInt(at(input, pointer));\r
300           if (ipv4Piece === null) {\r
301             ipv4Piece = number;\r
302           } else if (ipv4Piece === 0) {\r
303             return failure;\r
304           } else {\r
305             ipv4Piece = ipv4Piece * 10 + number;\r
306           }\r
307           if (ipv4Piece > 255) {\r
308             return failure;\r
309           }\r
310           ++pointer;\r
311         }\r
312 \r
313         address[pieceIndex] = address[pieceIndex] * 0x100 + ipv4Piece;\r
314 \r
315         ++numbersSeen;\r
316 \r
317         if (numbersSeen === 2 || numbersSeen === 4) {\r
318           ++pieceIndex;\r
319         }\r
320       }\r
321 \r
322       if (numbersSeen !== 4) {\r
323         return failure;\r
324       }\r
325 \r
326       break;\r
327     } else if (input[pointer] === 58) {\r
328       ++pointer;\r
329       if (input[pointer] === undefined) {\r
330         return failure;\r
331       }\r
332     } else if (input[pointer] !== undefined) {\r
333       return failure;\r
334     }\r
335 \r
336     address[pieceIndex] = value;\r
337     ++pieceIndex;\r
338   }\r
339 \r
340   if (compress !== null) {\r
341     let swaps = pieceIndex - compress;\r
342     pieceIndex = 7;\r
343     while (pieceIndex !== 0 && swaps > 0) {\r
344       const temp = address[compress + swaps - 1];\r
345       address[compress + swaps - 1] = address[pieceIndex];\r
346       address[pieceIndex] = temp;\r
347       --pieceIndex;\r
348       --swaps;\r
349     }\r
350   } else if (compress === null && pieceIndex !== 8) {\r
351     return failure;\r
352   }\r
353 \r
354   return address;\r
355 }\r
356 \r
357 function serializeIPv6(address) {\r
358   let output = "";\r
359   const seqResult = findLongestZeroSequence(address);\r
360   const compress = seqResult.idx;\r
361   let ignore0 = false;\r
362 \r
363   for (let pieceIndex = 0; pieceIndex <= 7; ++pieceIndex) {\r
364     if (ignore0 && address[pieceIndex] === 0) {\r
365       continue;\r
366     } else if (ignore0) {\r
367       ignore0 = false;\r
368     }\r
369 \r
370     if (compress === pieceIndex) {\r
371       const separator = pieceIndex === 0 ? "::" : ":";\r
372       output += separator;\r
373       ignore0 = true;\r
374       continue;\r
375     }\r
376 \r
377     output += address[pieceIndex].toString(16);\r
378 \r
379     if (pieceIndex !== 7) {\r
380       output += ":";\r
381     }\r
382   }\r
383 \r
384   return output;\r
385 }\r
386 \r
387 function parseHost(input, isSpecialArg) {\r
388   if (input[0] === "[") {\r
389     if (input[input.length - 1] !== "]") {\r
390       return failure;\r
391     }\r
392 \r
393     return parseIPv6(input.substring(1, input.length - 1));\r
394   }\r
395 \r
396   if (!isSpecialArg) {\r
397     return parseOpaqueHost(input);\r
398   }\r
399 \r
400   const domain = utf8PercentDecode(input);\r
401   const asciiDomain = tr46.toASCII(domain, false, tr46.PROCESSING_OPTIONS.NONTRANSITIONAL, false);\r
402   if (asciiDomain === null) {\r
403     return failure;\r
404   }\r
405 \r
406   if (containsForbiddenHostCodePoint(asciiDomain)) {\r
407     return failure;\r
408   }\r
409 \r
410   const ipv4Host = parseIPv4(asciiDomain);\r
411   if (typeof ipv4Host === "number" || ipv4Host === failure) {\r
412     return ipv4Host;\r
413   }\r
414 \r
415   return asciiDomain;\r
416 }\r
417 \r
418 function parseOpaqueHost(input) {\r
419   if (containsForbiddenHostCodePointExcludingPercent(input)) {\r
420     return failure;\r
421   }\r
422 \r
423   let output = "";\r
424   const decoded = punycode.ucs2.decode(input);\r
425   for (let i = 0; i < decoded.length; ++i) {\r
426     output += percentEncodeChar(decoded[i], isC0ControlPercentEncode);\r
427   }\r
428   return output;\r
429 }\r
430 \r
431 function findLongestZeroSequence(arr) {\r
432   let maxIdx = null;\r
433   let maxLen = 1; // only find elements > 1\r
434   let currStart = null;\r
435   let currLen = 0;\r
436 \r
437   for (let i = 0; i < arr.length; ++i) {\r
438     if (arr[i] !== 0) {\r
439       if (currLen > maxLen) {\r
440         maxIdx = currStart;\r
441         maxLen = currLen;\r
442       }\r
443 \r
444       currStart = null;\r
445       currLen = 0;\r
446     } else {\r
447       if (currStart === null) {\r
448         currStart = i;\r
449       }\r
450       ++currLen;\r
451     }\r
452   }\r
453 \r
454   // if trailing zeros\r
455   if (currLen > maxLen) {\r
456     maxIdx = currStart;\r
457     maxLen = currLen;\r
458   }\r
459 \r
460   return {\r
461     idx: maxIdx,\r
462     len: maxLen\r
463   };\r
464 }\r
465 \r
466 function serializeHost(host) {\r
467   if (typeof host === "number") {\r
468     return serializeIPv4(host);\r
469   }\r
470 \r
471   // IPv6 serializer\r
472   if (host instanceof Array) {\r
473     return "[" + serializeIPv6(host) + "]";\r
474   }\r
475 \r
476   return host;\r
477 }\r
478 \r
479 function trimControlChars(url) {\r
480   return url.replace(/^[\u0000-\u001F\u0020]+|[\u0000-\u001F\u0020]+$/g, "");\r
481 }\r
482 \r
483 function trimTabAndNewline(url) {\r
484   return url.replace(/\u0009|\u000A|\u000D/g, "");\r
485 }\r
486 \r
487 function shortenPath(url) {\r
488   const path = url.path;\r
489   if (path.length === 0) {\r
490     return;\r
491   }\r
492   if (url.scheme === "file" && path.length === 1 && isNormalizedWindowsDriveLetter(path[0])) {\r
493     return;\r
494   }\r
495 \r
496   path.pop();\r
497 }\r
498 \r
499 function includesCredentials(url) {\r
500   return url.username !== "" || url.password !== "";\r
501 }\r
502 \r
503 function cannotHaveAUsernamePasswordPort(url) {\r
504   return url.host === null || url.host === "" || url.cannotBeABaseURL || url.scheme === "file";\r
505 }\r
506 \r
507 function isNormalizedWindowsDriveLetter(string) {\r
508   return /^[A-Za-z]:$/.test(string);\r
509 }\r
510 \r
511 function URLStateMachine(input, base, encodingOverride, url, stateOverride) {\r
512   this.pointer = 0;\r
513   this.input = input;\r
514   this.base = base || null;\r
515   this.encodingOverride = encodingOverride || "utf-8";\r
516   this.stateOverride = stateOverride;\r
517   this.url = url;\r
518   this.failure = false;\r
519   this.parseError = false;\r
520 \r
521   if (!this.url) {\r
522     this.url = {\r
523       scheme: "",\r
524       username: "",\r
525       password: "",\r
526       host: null,\r
527       port: null,\r
528       path: [],\r
529       query: null,\r
530       fragment: null,\r
531 \r
532       cannotBeABaseURL: false\r
533     };\r
534 \r
535     const res = trimControlChars(this.input);\r
536     if (res !== this.input) {\r
537       this.parseError = true;\r
538     }\r
539     this.input = res;\r
540   }\r
541 \r
542   const res = trimTabAndNewline(this.input);\r
543   if (res !== this.input) {\r
544     this.parseError = true;\r
545   }\r
546   this.input = res;\r
547 \r
548   this.state = stateOverride || "scheme start";\r
549 \r
550   this.buffer = "";\r
551   this.atFlag = false;\r
552   this.arrFlag = false;\r
553   this.passwordTokenSeenFlag = false;\r
554 \r
555   this.input = punycode.ucs2.decode(this.input);\r
556 \r
557   for (; this.pointer <= this.input.length; ++this.pointer) {\r
558     const c = this.input[this.pointer];\r
559     const cStr = isNaN(c) ? undefined : String.fromCodePoint(c);\r
560 \r
561     // exec state machine\r
562     const ret = this["parse " + this.state](c, cStr);\r
563     if (!ret) {\r
564       break; // terminate algorithm\r
565     } else if (ret === failure) {\r
566       this.failure = true;\r
567       break;\r
568     }\r
569   }\r
570 }\r
571 \r
572 URLStateMachine.prototype["parse scheme start"] = function parseSchemeStart(c, cStr) {\r
573   if (isASCIIAlpha(c)) {\r
574     this.buffer += cStr.toLowerCase();\r
575     this.state = "scheme";\r
576   } else if (!this.stateOverride) {\r
577     this.state = "no scheme";\r
578     --this.pointer;\r
579   } else {\r
580     this.parseError = true;\r
581     return failure;\r
582   }\r
583 \r
584   return true;\r
585 };\r
586 \r
587 URLStateMachine.prototype["parse scheme"] = function parseScheme(c, cStr) {\r
588   if (isASCIIAlphanumeric(c) || c === 43 || c === 45 || c === 46) {\r
589     this.buffer += cStr.toLowerCase();\r
590   } else if (c === 58) {\r
591     if (this.stateOverride) {\r
592       if (isSpecial(this.url) && !isSpecialScheme(this.buffer)) {\r
593         return false;\r
594       }\r
595 \r
596       if (!isSpecial(this.url) && isSpecialScheme(this.buffer)) {\r
597         return false;\r
598       }\r
599 \r
600       if ((includesCredentials(this.url) || this.url.port !== null) && this.buffer === "file") {\r
601         return false;\r
602       }\r
603 \r
604       if (this.url.scheme === "file" && (this.url.host === "" || this.url.host === null)) {\r
605         return false;\r
606       }\r
607     }\r
608     this.url.scheme = this.buffer;\r
609     this.buffer = "";\r
610     if (this.stateOverride) {\r
611       return false;\r
612     }\r
613     if (this.url.scheme === "file") {\r
614       if (this.input[this.pointer + 1] !== 47 || this.input[this.pointer + 2] !== 47) {\r
615         this.parseError = true;\r
616       }\r
617       this.state = "file";\r
618     } else if (isSpecial(this.url) && this.base !== null && this.base.scheme === this.url.scheme) {\r
619       this.state = "special relative or authority";\r
620     } else if (isSpecial(this.url)) {\r
621       this.state = "special authority slashes";\r
622     } else if (this.input[this.pointer + 1] === 47) {\r
623       this.state = "path or authority";\r
624       ++this.pointer;\r
625     } else {\r
626       this.url.cannotBeABaseURL = true;\r
627       this.url.path.push("");\r
628       this.state = "cannot-be-a-base-URL path";\r
629     }\r
630   } else if (!this.stateOverride) {\r
631     this.buffer = "";\r
632     this.state = "no scheme";\r
633     this.pointer = -1;\r
634   } else {\r
635     this.parseError = true;\r
636     return failure;\r
637   }\r
638 \r
639   return true;\r
640 };\r
641 \r
642 URLStateMachine.prototype["parse no scheme"] = function parseNoScheme(c) {\r
643   if (this.base === null || (this.base.cannotBeABaseURL && c !== 35)) {\r
644     return failure;\r
645   } else if (this.base.cannotBeABaseURL && c === 35) {\r
646     this.url.scheme = this.base.scheme;\r
647     this.url.path = this.base.path.slice();\r
648     this.url.query = this.base.query;\r
649     this.url.fragment = "";\r
650     this.url.cannotBeABaseURL = true;\r
651     this.state = "fragment";\r
652   } else if (this.base.scheme === "file") {\r
653     this.state = "file";\r
654     --this.pointer;\r
655   } else {\r
656     this.state = "relative";\r
657     --this.pointer;\r
658   }\r
659 \r
660   return true;\r
661 };\r
662 \r
663 URLStateMachine.prototype["parse special relative or authority"] = function parseSpecialRelativeOrAuthority(c) {\r
664   if (c === 47 && this.input[this.pointer + 1] === 47) {\r
665     this.state = "special authority ignore slashes";\r
666     ++this.pointer;\r
667   } else {\r
668     this.parseError = true;\r
669     this.state = "relative";\r
670     --this.pointer;\r
671   }\r
672 \r
673   return true;\r
674 };\r
675 \r
676 URLStateMachine.prototype["parse path or authority"] = function parsePathOrAuthority(c) {\r
677   if (c === 47) {\r
678     this.state = "authority";\r
679   } else {\r
680     this.state = "path";\r
681     --this.pointer;\r
682   }\r
683 \r
684   return true;\r
685 };\r
686 \r
687 URLStateMachine.prototype["parse relative"] = function parseRelative(c) {\r
688   this.url.scheme = this.base.scheme;\r
689   if (isNaN(c)) {\r
690     this.url.username = this.base.username;\r
691     this.url.password = this.base.password;\r
692     this.url.host = this.base.host;\r
693     this.url.port = this.base.port;\r
694     this.url.path = this.base.path.slice();\r
695     this.url.query = this.base.query;\r
696   } else if (c === 47) {\r
697     this.state = "relative slash";\r
698   } else if (c === 63) {\r
699     this.url.username = this.base.username;\r
700     this.url.password = this.base.password;\r
701     this.url.host = this.base.host;\r
702     this.url.port = this.base.port;\r
703     this.url.path = this.base.path.slice();\r
704     this.url.query = "";\r
705     this.state = "query";\r
706   } else if (c === 35) {\r
707     this.url.username = this.base.username;\r
708     this.url.password = this.base.password;\r
709     this.url.host = this.base.host;\r
710     this.url.port = this.base.port;\r
711     this.url.path = this.base.path.slice();\r
712     this.url.query = this.base.query;\r
713     this.url.fragment = "";\r
714     this.state = "fragment";\r
715   } else if (isSpecial(this.url) && c === 92) {\r
716     this.parseError = true;\r
717     this.state = "relative slash";\r
718   } else {\r
719     this.url.username = this.base.username;\r
720     this.url.password = this.base.password;\r
721     this.url.host = this.base.host;\r
722     this.url.port = this.base.port;\r
723     this.url.path = this.base.path.slice(0, this.base.path.length - 1);\r
724 \r
725     this.state = "path";\r
726     --this.pointer;\r
727   }\r
728 \r
729   return true;\r
730 };\r
731 \r
732 URLStateMachine.prototype["parse relative slash"] = function parseRelativeSlash(c) {\r
733   if (isSpecial(this.url) && (c === 47 || c === 92)) {\r
734     if (c === 92) {\r
735       this.parseError = true;\r
736     }\r
737     this.state = "special authority ignore slashes";\r
738   } else if (c === 47) {\r
739     this.state = "authority";\r
740   } else {\r
741     this.url.username = this.base.username;\r
742     this.url.password = this.base.password;\r
743     this.url.host = this.base.host;\r
744     this.url.port = this.base.port;\r
745     this.state = "path";\r
746     --this.pointer;\r
747   }\r
748 \r
749   return true;\r
750 };\r
751 \r
752 URLStateMachine.prototype["parse special authority slashes"] = function parseSpecialAuthoritySlashes(c) {\r
753   if (c === 47 && this.input[this.pointer + 1] === 47) {\r
754     this.state = "special authority ignore slashes";\r
755     ++this.pointer;\r
756   } else {\r
757     this.parseError = true;\r
758     this.state = "special authority ignore slashes";\r
759     --this.pointer;\r
760   }\r
761 \r
762   return true;\r
763 };\r
764 \r
765 URLStateMachine.prototype["parse special authority ignore slashes"] = function parseSpecialAuthorityIgnoreSlashes(c) {\r
766   if (c !== 47 && c !== 92) {\r
767     this.state = "authority";\r
768     --this.pointer;\r
769   } else {\r
770     this.parseError = true;\r
771   }\r
772 \r
773   return true;\r
774 };\r
775 \r
776 URLStateMachine.prototype["parse authority"] = function parseAuthority(c, cStr) {\r
777   if (c === 64) {\r
778     this.parseError = true;\r
779     if (this.atFlag) {\r
780       this.buffer = "%40" + this.buffer;\r
781     }\r
782     this.atFlag = true;\r
783 \r
784     // careful, this is based on buffer and has its own pointer (this.pointer != pointer) and inner chars\r
785     const len = countSymbols(this.buffer);\r
786     for (let pointer = 0; pointer < len; ++pointer) {\r
787       const codePoint = this.buffer.codePointAt(pointer);\r
788 \r
789       if (codePoint === 58 && !this.passwordTokenSeenFlag) {\r
790         this.passwordTokenSeenFlag = true;\r
791         continue;\r
792       }\r
793       const encodedCodePoints = percentEncodeChar(codePoint, isUserinfoPercentEncode);\r
794       if (this.passwordTokenSeenFlag) {\r
795         this.url.password += encodedCodePoints;\r
796       } else {\r
797         this.url.username += encodedCodePoints;\r
798       }\r
799     }\r
800     this.buffer = "";\r
801   } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||\r
802              (isSpecial(this.url) && c === 92)) {\r
803     if (this.atFlag && this.buffer === "") {\r
804       this.parseError = true;\r
805       return failure;\r
806     }\r
807     this.pointer -= countSymbols(this.buffer) + 1;\r
808     this.buffer = "";\r
809     this.state = "host";\r
810   } else {\r
811     this.buffer += cStr;\r
812   }\r
813 \r
814   return true;\r
815 };\r
816 \r
817 URLStateMachine.prototype["parse hostname"] =\r
818 URLStateMachine.prototype["parse host"] = function parseHostName(c, cStr) {\r
819   if (this.stateOverride && this.url.scheme === "file") {\r
820     --this.pointer;\r
821     this.state = "file host";\r
822   } else if (c === 58 && !this.arrFlag) {\r
823     if (this.buffer === "") {\r
824       this.parseError = true;\r
825       return failure;\r
826     }\r
827 \r
828     const host = parseHost(this.buffer, isSpecial(this.url));\r
829     if (host === failure) {\r
830       return failure;\r
831     }\r
832 \r
833     this.url.host = host;\r
834     this.buffer = "";\r
835     this.state = "port";\r
836     if (this.stateOverride === "hostname") {\r
837       return false;\r
838     }\r
839   } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||\r
840              (isSpecial(this.url) && c === 92)) {\r
841     --this.pointer;\r
842     if (isSpecial(this.url) && this.buffer === "") {\r
843       this.parseError = true;\r
844       return failure;\r
845     } else if (this.stateOverride && this.buffer === "" &&\r
846                (includesCredentials(this.url) || this.url.port !== null)) {\r
847       this.parseError = true;\r
848       return false;\r
849     }\r
850 \r
851     const host = parseHost(this.buffer, isSpecial(this.url));\r
852     if (host === failure) {\r
853       return failure;\r
854     }\r
855 \r
856     this.url.host = host;\r
857     this.buffer = "";\r
858     this.state = "path start";\r
859     if (this.stateOverride) {\r
860       return false;\r
861     }\r
862   } else {\r
863     if (c === 91) {\r
864       this.arrFlag = true;\r
865     } else if (c === 93) {\r
866       this.arrFlag = false;\r
867     }\r
868     this.buffer += cStr;\r
869   }\r
870 \r
871   return true;\r
872 };\r
873 \r
874 URLStateMachine.prototype["parse port"] = function parsePort(c, cStr) {\r
875   if (isASCIIDigit(c)) {\r
876     this.buffer += cStr;\r
877   } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||\r
878              (isSpecial(this.url) && c === 92) ||\r
879              this.stateOverride) {\r
880     if (this.buffer !== "") {\r
881       const port = parseInt(this.buffer);\r
882       if (port > Math.pow(2, 16) - 1) {\r
883         this.parseError = true;\r
884         return failure;\r
885       }\r
886       this.url.port = port === defaultPort(this.url.scheme) ? null : port;\r
887       this.buffer = "";\r
888     }\r
889     if (this.stateOverride) {\r
890       return false;\r
891     }\r
892     this.state = "path start";\r
893     --this.pointer;\r
894   } else {\r
895     this.parseError = true;\r
896     return failure;\r
897   }\r
898 \r
899   return true;\r
900 };\r
901 \r
902 const fileOtherwiseCodePoints = new Set([47, 92, 63, 35]);\r
903 \r
904 URLStateMachine.prototype["parse file"] = function parseFile(c) {\r
905   this.url.scheme = "file";\r
906 \r
907   if (c === 47 || c === 92) {\r
908     if (c === 92) {\r
909       this.parseError = true;\r
910     }\r
911     this.state = "file slash";\r
912   } else if (this.base !== null && this.base.scheme === "file") {\r
913     if (isNaN(c)) {\r
914       this.url.host = this.base.host;\r
915       this.url.path = this.base.path.slice();\r
916       this.url.query = this.base.query;\r
917     } else if (c === 63) {\r
918       this.url.host = this.base.host;\r
919       this.url.path = this.base.path.slice();\r
920       this.url.query = "";\r
921       this.state = "query";\r
922     } else if (c === 35) {\r
923       this.url.host = this.base.host;\r
924       this.url.path = this.base.path.slice();\r
925       this.url.query = this.base.query;\r
926       this.url.fragment = "";\r
927       this.state = "fragment";\r
928     } else {\r
929       if (this.input.length - this.pointer - 1 === 0 || // remaining consists of 0 code points\r
930           !isWindowsDriveLetterCodePoints(c, this.input[this.pointer + 1]) ||\r
931           (this.input.length - this.pointer - 1 >= 2 && // remaining has at least 2 code points\r
932            !fileOtherwiseCodePoints.has(this.input[this.pointer + 2]))) {\r
933         this.url.host = this.base.host;\r
934         this.url.path = this.base.path.slice();\r
935         shortenPath(this.url);\r
936       } else {\r
937         this.parseError = true;\r
938       }\r
939 \r
940       this.state = "path";\r
941       --this.pointer;\r
942     }\r
943   } else {\r
944     this.state = "path";\r
945     --this.pointer;\r
946   }\r
947 \r
948   return true;\r
949 };\r
950 \r
951 URLStateMachine.prototype["parse file slash"] = function parseFileSlash(c) {\r
952   if (c === 47 || c === 92) {\r
953     if (c === 92) {\r
954       this.parseError = true;\r
955     }\r
956     this.state = "file host";\r
957   } else {\r
958     if (this.base !== null && this.base.scheme === "file") {\r
959       if (isNormalizedWindowsDriveLetterString(this.base.path[0])) {\r
960         this.url.path.push(this.base.path[0]);\r
961       } else {\r
962         this.url.host = this.base.host;\r
963       }\r
964     }\r
965     this.state = "path";\r
966     --this.pointer;\r
967   }\r
968 \r
969   return true;\r
970 };\r
971 \r
972 URLStateMachine.prototype["parse file host"] = function parseFileHost(c, cStr) {\r
973   if (isNaN(c) || c === 47 || c === 92 || c === 63 || c === 35) {\r
974     --this.pointer;\r
975     if (!this.stateOverride && isWindowsDriveLetterString(this.buffer)) {\r
976       this.parseError = true;\r
977       this.state = "path";\r
978     } else if (this.buffer === "") {\r
979       this.url.host = "";\r
980       if (this.stateOverride) {\r
981         return false;\r
982       }\r
983       this.state = "path start";\r
984     } else {\r
985       let host = parseHost(this.buffer, isSpecial(this.url));\r
986       if (host === failure) {\r
987         return failure;\r
988       }\r
989       if (host === "localhost") {\r
990         host = "";\r
991       }\r
992       this.url.host = host;\r
993 \r
994       if (this.stateOverride) {\r
995         return false;\r
996       }\r
997 \r
998       this.buffer = "";\r
999       this.state = "path start";\r
1000     }\r
1001   } else {\r
1002     this.buffer += cStr;\r
1003   }\r
1004 \r
1005   return true;\r
1006 };\r
1007 \r
1008 URLStateMachine.prototype["parse path start"] = function parsePathStart(c) {\r
1009   if (isSpecial(this.url)) {\r
1010     if (c === 92) {\r
1011       this.parseError = true;\r
1012     }\r
1013     this.state = "path";\r
1014 \r
1015     if (c !== 47 && c !== 92) {\r
1016       --this.pointer;\r
1017     }\r
1018   } else if (!this.stateOverride && c === 63) {\r
1019     this.url.query = "";\r
1020     this.state = "query";\r
1021   } else if (!this.stateOverride && c === 35) {\r
1022     this.url.fragment = "";\r
1023     this.state = "fragment";\r
1024   } else if (c !== undefined) {\r
1025     this.state = "path";\r
1026     if (c !== 47) {\r
1027       --this.pointer;\r
1028     }\r
1029   }\r
1030 \r
1031   return true;\r
1032 };\r
1033 \r
1034 URLStateMachine.prototype["parse path"] = function parsePath(c) {\r
1035   if (isNaN(c) || c === 47 || (isSpecial(this.url) && c === 92) ||\r
1036       (!this.stateOverride && (c === 63 || c === 35))) {\r
1037     if (isSpecial(this.url) && c === 92) {\r
1038       this.parseError = true;\r
1039     }\r
1040 \r
1041     if (isDoubleDot(this.buffer)) {\r
1042       shortenPath(this.url);\r
1043       if (c !== 47 && !(isSpecial(this.url) && c === 92)) {\r
1044         this.url.path.push("");\r
1045       }\r
1046     } else if (isSingleDot(this.buffer) && c !== 47 &&\r
1047                !(isSpecial(this.url) && c === 92)) {\r
1048       this.url.path.push("");\r
1049     } else if (!isSingleDot(this.buffer)) {\r
1050       if (this.url.scheme === "file" && this.url.path.length === 0 && isWindowsDriveLetterString(this.buffer)) {\r
1051         if (this.url.host !== "" && this.url.host !== null) {\r
1052           this.parseError = true;\r
1053           this.url.host = "";\r
1054         }\r
1055         this.buffer = this.buffer[0] + ":";\r
1056       }\r
1057       this.url.path.push(this.buffer);\r
1058     }\r
1059     this.buffer = "";\r
1060     if (this.url.scheme === "file" && (c === undefined || c === 63 || c === 35)) {\r
1061       while (this.url.path.length > 1 && this.url.path[0] === "") {\r
1062         this.parseError = true;\r
1063         this.url.path.shift();\r
1064       }\r
1065     }\r
1066     if (c === 63) {\r
1067       this.url.query = "";\r
1068       this.state = "query";\r
1069     }\r
1070     if (c === 35) {\r
1071       this.url.fragment = "";\r
1072       this.state = "fragment";\r
1073     }\r
1074   } else {\r
1075     // TODO: If c is not a URL code point and not "%", parse error.\r
1076 \r
1077     if (c === 37 &&\r
1078       (!isASCIIHex(this.input[this.pointer + 1]) ||\r
1079         !isASCIIHex(this.input[this.pointer + 2]))) {\r
1080       this.parseError = true;\r
1081     }\r
1082 \r
1083     this.buffer += percentEncodeChar(c, isPathPercentEncode);\r
1084   }\r
1085 \r
1086   return true;\r
1087 };\r
1088 \r
1089 URLStateMachine.prototype["parse cannot-be-a-base-URL path"] = function parseCannotBeABaseURLPath(c) {\r
1090   if (c === 63) {\r
1091     this.url.query = "";\r
1092     this.state = "query";\r
1093   } else if (c === 35) {\r
1094     this.url.fragment = "";\r
1095     this.state = "fragment";\r
1096   } else {\r
1097     // TODO: Add: not a URL code point\r
1098     if (!isNaN(c) && c !== 37) {\r
1099       this.parseError = true;\r
1100     }\r
1101 \r
1102     if (c === 37 &&\r
1103         (!isASCIIHex(this.input[this.pointer + 1]) ||\r
1104          !isASCIIHex(this.input[this.pointer + 2]))) {\r
1105       this.parseError = true;\r
1106     }\r
1107 \r
1108     if (!isNaN(c)) {\r
1109       this.url.path[0] = this.url.path[0] + percentEncodeChar(c, isC0ControlPercentEncode);\r
1110     }\r
1111   }\r
1112 \r
1113   return true;\r
1114 };\r
1115 \r
1116 URLStateMachine.prototype["parse query"] = function parseQuery(c, cStr) {\r
1117   if (isNaN(c) || (!this.stateOverride && c === 35)) {\r
1118     if (!isSpecial(this.url) || this.url.scheme === "ws" || this.url.scheme === "wss") {\r
1119       this.encodingOverride = "utf-8";\r
1120     }\r
1121 \r
1122     const buffer = new Buffer(this.buffer); // TODO: Use encoding override instead\r
1123     for (let i = 0; i < buffer.length; ++i) {\r
1124       if (buffer[i] < 0x21 || buffer[i] > 0x7E || buffer[i] === 0x22 || buffer[i] === 0x23 ||\r
1125           buffer[i] === 0x3C || buffer[i] === 0x3E) {\r
1126         this.url.query += percentEncode(buffer[i]);\r
1127       } else {\r
1128         this.url.query += String.fromCodePoint(buffer[i]);\r
1129       }\r
1130     }\r
1131 \r
1132     this.buffer = "";\r
1133     if (c === 35) {\r
1134       this.url.fragment = "";\r
1135       this.state = "fragment";\r
1136     }\r
1137   } else {\r
1138     // TODO: If c is not a URL code point and not "%", parse error.\r
1139     if (c === 37 &&\r
1140       (!isASCIIHex(this.input[this.pointer + 1]) ||\r
1141         !isASCIIHex(this.input[this.pointer + 2]))) {\r
1142       this.parseError = true;\r
1143     }\r
1144 \r
1145     this.buffer += cStr;\r
1146   }\r
1147 \r
1148   return true;\r
1149 };\r
1150 \r
1151 URLStateMachine.prototype["parse fragment"] = function parseFragment(c) {\r
1152   if (isNaN(c)) { // do nothing\r
1153   } else if (c === 0x0) {\r
1154     this.parseError = true;\r
1155   } else {\r
1156     // TODO: If c is not a URL code point and not "%", parse error.\r
1157     if (c === 37 &&\r
1158       (!isASCIIHex(this.input[this.pointer + 1]) ||\r
1159         !isASCIIHex(this.input[this.pointer + 2]))) {\r
1160       this.parseError = true;\r
1161     }\r
1162 \r
1163     this.url.fragment += percentEncodeChar(c, isC0ControlPercentEncode);\r
1164   }\r
1165 \r
1166   return true;\r
1167 };\r
1168 \r
1169 function serializeURL(url, excludeFragment) {\r
1170   let output = url.scheme + ":";\r
1171   if (url.host !== null) {\r
1172     output += "//";\r
1173 \r
1174     if (url.username !== "" || url.password !== "") {\r
1175       output += url.username;\r
1176       if (url.password !== "") {\r
1177         output += ":" + url.password;\r
1178       }\r
1179       output += "@";\r
1180     }\r
1181 \r
1182     output += serializeHost(url.host);\r
1183 \r
1184     if (url.port !== null) {\r
1185       output += ":" + url.port;\r
1186     }\r
1187   } else if (url.host === null && url.scheme === "file") {\r
1188     output += "//";\r
1189   }\r
1190 \r
1191   if (url.cannotBeABaseURL) {\r
1192     output += url.path[0];\r
1193   } else {\r
1194     for (const string of url.path) {\r
1195       output += "/" + string;\r
1196     }\r
1197   }\r
1198 \r
1199   if (url.query !== null) {\r
1200     output += "?" + url.query;\r
1201   }\r
1202 \r
1203   if (!excludeFragment && url.fragment !== null) {\r
1204     output += "#" + url.fragment;\r
1205   }\r
1206 \r
1207   return output;\r
1208 }\r
1209 \r
1210 function serializeOrigin(tuple) {\r
1211   let result = tuple.scheme + "://";\r
1212   result += serializeHost(tuple.host);\r
1213 \r
1214   if (tuple.port !== null) {\r
1215     result += ":" + tuple.port;\r
1216   }\r
1217 \r
1218   return result;\r
1219 }\r
1220 \r
1221 module.exports.serializeURL = serializeURL;\r
1222 \r
1223 module.exports.serializeURLOrigin = function (url) {\r
1224   // https://url.spec.whatwg.org/#concept-url-origin\r
1225   switch (url.scheme) {\r
1226     case "blob":\r
1227       try {\r
1228         return module.exports.serializeURLOrigin(module.exports.parseURL(url.path[0]));\r
1229       } catch (e) {\r
1230         // serializing an opaque origin returns "null"\r
1231         return "null";\r
1232       }\r
1233     case "ftp":\r
1234     case "gopher":\r
1235     case "http":\r
1236     case "https":\r
1237     case "ws":\r
1238     case "wss":\r
1239       return serializeOrigin({\r
1240         scheme: url.scheme,\r
1241         host: url.host,\r
1242         port: url.port\r
1243       });\r
1244     case "file":\r
1245       // spec says "exercise to the reader", chrome says "file://"\r
1246       return "file://";\r
1247     default:\r
1248       // serializing an opaque origin returns "null"\r
1249       return "null";\r
1250   }\r
1251 };\r
1252 \r
1253 module.exports.basicURLParse = function (input, options) {\r
1254   if (options === undefined) {\r
1255     options = {};\r
1256   }\r
1257 \r
1258   const usm = new URLStateMachine(input, options.baseURL, options.encodingOverride, options.url, options.stateOverride);\r
1259   if (usm.failure) {\r
1260     return "failure";\r
1261   }\r
1262 \r
1263   return usm.url;\r
1264 };\r
1265 \r
1266 module.exports.setTheUsername = function (url, username) {\r
1267   url.username = "";\r
1268   const decoded = punycode.ucs2.decode(username);\r
1269   for (let i = 0; i < decoded.length; ++i) {\r
1270     url.username += percentEncodeChar(decoded[i], isUserinfoPercentEncode);\r
1271   }\r
1272 };\r
1273 \r
1274 module.exports.setThePassword = function (url, password) {\r
1275   url.password = "";\r
1276   const decoded = punycode.ucs2.decode(password);\r
1277   for (let i = 0; i < decoded.length; ++i) {\r
1278     url.password += percentEncodeChar(decoded[i], isUserinfoPercentEncode);\r
1279   }\r
1280 };\r
1281 \r
1282 module.exports.serializeHost = serializeHost;\r
1283 \r
1284 module.exports.cannotHaveAUsernamePasswordPort = cannotHaveAUsernamePasswordPort;\r
1285 \r
1286 module.exports.serializeInteger = function (integer) {\r
1287   return String(integer);\r
1288 };\r
1289 \r
1290 module.exports.parseURL = function (input, options) {\r
1291   if (options === undefined) {\r
1292     options = {};\r
1293   }\r
1294 \r
1295   // We don't handle blobs, so this just delegates:\r
1296   return module.exports.basicURLParse(input, { baseURL: options.baseURL, encodingOverride: options.encodingOverride });\r
1297 };\r