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