Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 

1163 righe
29 KiB

  1. "use strict";
  2. const punycode = require("punycode");
  3. const tr46 = require("tr46");
  4. const specialSchemas = {
  5. ftp: 21,
  6. file: null,
  7. gopher: 70,
  8. http: 80,
  9. https: 443,
  10. ws: 80,
  11. wss: 443
  12. };
  13. const failure = Symbol("failure");
  14. function countSymbols(str) {
  15. return punycode.ucs2.decode(str).length;
  16. }
  17. function at(input, idx) {
  18. const c = input[idx];
  19. return isNaN(c) ? undefined : String.fromCodePoint(c);
  20. }
  21. function isASCIIDigit(c) {
  22. return c >= 0x30 && c <= 0x39;
  23. }
  24. function isASCIIAlpha(c) {
  25. return (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A);
  26. }
  27. function isASCIIHex(c) {
  28. return isASCIIDigit(c) || (c >= 0x41 && c <= 0x46) || (c >= 0x61 && c <= 0x66);
  29. }
  30. function isSingleDot(buffer) {
  31. return buffer === "." || buffer.toLowerCase() === "%2e";
  32. }
  33. function isDoubleDot(buffer) {
  34. buffer = buffer.toLowerCase();
  35. return buffer === ".." || buffer === "%2e." || buffer === ".%2e" || buffer === "%2e%2e";
  36. }
  37. function percentEncode(c) {
  38. let hex = c.toString(16).toUpperCase();
  39. if (hex.length === 1) {
  40. hex = "0" + hex;
  41. }
  42. return "%" + hex;
  43. }
  44. const invalidCodePoint = String.fromCodePoint(65533);
  45. function utf8PercentEncode(c) {
  46. const buf = new Buffer(c);
  47. if (buf.toString() === invalidCodePoint) {
  48. return "";
  49. }
  50. let str = "";
  51. for (let i = 0; i < buf.length; ++i) {
  52. str += percentEncode(buf[i]);
  53. }
  54. return str;
  55. }
  56. function utf8PercentDecode(str) {
  57. const input = new Buffer(str);
  58. const output = [];
  59. for (let i = 0; i < input.length; ++i) {
  60. if (input[i] !== 37) {
  61. output.push(input[i]);
  62. } else if (input[i] === 37 && isASCIIHex(input[i + 1]) && isASCIIHex(input[i + 2])) {
  63. output.push(parseInt(input.slice(i + 1, i + 3).toString(), 16));
  64. i += 2;
  65. } else {
  66. output.push(input[i]);
  67. }
  68. }
  69. return new Buffer(output).toString();
  70. }
  71. function isSimpleEncode(c) {
  72. return c <= 0x1F || c > 0x7E;
  73. }
  74. const defaultEncodeSet = [32, 34, 35, 60, 62, 63, 96, 123, 125];
  75. function isDefaultEncode(c) {
  76. return isSimpleEncode(c) || defaultEncodeSet.indexOf(c) !== -1;
  77. }
  78. const userInfoEncodeSet = [47, 58, 59, 61, 64, 91, 92, 93, 94, 124];
  79. function isUserInfoEncode(c) {
  80. return isDefaultEncode(c) || userInfoEncodeSet.indexOf(c) !== -1;
  81. }
  82. function encodeChar(c, checkCb) {
  83. const cStr = String.fromCodePoint(c);
  84. if (checkCb(c)) {
  85. return utf8PercentEncode(cStr);
  86. }
  87. return cStr;
  88. }
  89. function parseIPv4Number(input) {
  90. let R = 10;
  91. if (input.length >= 2 && input.charAt(0) === "0" && input.charAt(1).toLowerCase() === "x") {
  92. input = input.substring(2);
  93. R = 16;
  94. } else if (input.length >= 2 && input.charAt(0) === "0") {
  95. input = input.substring(1);
  96. R = 8;
  97. }
  98. if (input === "") {
  99. return 0;
  100. }
  101. const regex = R === 10 ? /[^0-9]/ : (R === 16 ? /[^0-9A-Fa-f]/ : /[^0-7]/);
  102. if (regex.test(input)) {
  103. return failure;
  104. }
  105. return parseInt(input, R);
  106. }
  107. function parseIPv4(input) {
  108. const parts = input.split(".");
  109. if (parts[parts.length - 1] === "") {
  110. parts.pop();
  111. }
  112. if (parts.length > 4) {
  113. return input;
  114. }
  115. const numbers = [];
  116. for (const part of parts) {
  117. const n = parseIPv4Number(part);
  118. if (n === failure) {
  119. return input;
  120. }
  121. numbers.push(n);
  122. }
  123. for (let i = 0; i < numbers.length - 1; ++i) {
  124. if (numbers[i] > 255) {
  125. return failure;
  126. }
  127. }
  128. if (numbers[numbers.length - 1] >= Math.pow(256, 5 - numbers.length)) {
  129. return failure;
  130. }
  131. let ipv4 = numbers.pop();
  132. let counter = 0;
  133. for (const n of numbers) {
  134. ipv4 += n * Math.pow(256, 3 - counter);
  135. ++counter;
  136. }
  137. return ipv4;
  138. }
  139. function serializeIPv4(address) {
  140. let output = "";
  141. let n = address;
  142. for (let i = 0; i < 4; ++i) {
  143. output = String(n % 256) + output;
  144. if (i !== 3) {
  145. output = "." + output;
  146. }
  147. n = Math.floor(n / 256);
  148. }
  149. return output;
  150. }
  151. function parseIPv6(input) {
  152. const ip = [0, 0, 0, 0, 0, 0, 0, 0];
  153. let piecePtr = 0;
  154. let compressPtr = null;
  155. let pointer = 0;
  156. input = punycode.ucs2.decode(input);
  157. if (input[pointer] === 58) {
  158. if (input[pointer + 1] !== 58) {
  159. return failure;
  160. }
  161. pointer += 2;
  162. ++piecePtr;
  163. compressPtr = piecePtr;
  164. }
  165. let ipv4 = false;
  166. Main:
  167. while (pointer < input.length) {
  168. if (piecePtr === 8) {
  169. return failure;
  170. }
  171. if (input[pointer] === 58) {
  172. if (compressPtr !== null) {
  173. return failure;
  174. }
  175. ++pointer;
  176. ++piecePtr;
  177. compressPtr = piecePtr;
  178. continue;
  179. }
  180. let value = 0;
  181. let length = 0;
  182. while (length < 4 && isASCIIHex(input[pointer])) {
  183. value = value * 0x10 + parseInt(at(input, pointer), 16);
  184. ++pointer;
  185. ++length;
  186. }
  187. switch (at(input, pointer)) {
  188. case ".":
  189. if (length === 0) {
  190. return failure;
  191. }
  192. pointer -= length;
  193. ipv4 = true;
  194. break Main;
  195. case ":":
  196. ++pointer;
  197. if (input[pointer] === undefined) {
  198. return failure;
  199. }
  200. break;
  201. case undefined:
  202. break;
  203. default:
  204. return failure;
  205. }
  206. ip[piecePtr] = value;
  207. ++piecePtr;
  208. }
  209. if (ipv4 && piecePtr > 6) {
  210. return failure;
  211. } else if (input[pointer] !== undefined) {
  212. let dotsSeen = 0;
  213. while (input[pointer] !== undefined) {
  214. let value = null;
  215. if (!isASCIIDigit(input[pointer])) {
  216. return failure;
  217. }
  218. while (isASCIIDigit(input[pointer])) {
  219. const number = parseInt(at(input, pointer));
  220. if (value === null) {
  221. value = number;
  222. } else if (value === 0) {
  223. return failure;
  224. } else {
  225. value = value * 10 + number;
  226. }
  227. ++pointer;
  228. if (value > 255) {
  229. return failure;
  230. }
  231. }
  232. if (dotsSeen < 3 && input[pointer] !== 46) {
  233. return failure;
  234. }
  235. ip[piecePtr] = ip[piecePtr] * 0x100 + value;
  236. if (dotsSeen === 1 || dotsSeen === 3) {
  237. ++piecePtr;
  238. }
  239. if (input[pointer] !== undefined) {
  240. ++pointer;
  241. }
  242. if (dotsSeen === 3 && input[pointer] !== undefined) {
  243. return failure;
  244. }
  245. ++dotsSeen;
  246. }
  247. }
  248. if (compressPtr !== null) {
  249. let swaps = piecePtr - compressPtr;
  250. piecePtr = 7;
  251. while (piecePtr !== 0 && swaps > 0) {
  252. const temp = ip[compressPtr + swaps - 1]; // piece
  253. ip[compressPtr + swaps - 1] = ip[piecePtr];
  254. ip[piecePtr] = temp;
  255. --piecePtr;
  256. --swaps;
  257. }
  258. } else if (piecePtr !== 8) {
  259. return failure;
  260. }
  261. return ip;
  262. }
  263. function serializeIPv6(address) {
  264. let output = "";
  265. const seqResult = findLongestZeroSequence(address);
  266. const compressPtr = seqResult.idx;
  267. for (let i = 0; i < address.length; ++i) {
  268. if (compressPtr === i) {
  269. if (i === 0) {
  270. output += "::";
  271. } else {
  272. output += ":";
  273. }
  274. i += seqResult.len - 1;
  275. continue;
  276. }
  277. output += address[i].toString(16);
  278. if (i !== address.length - 1) {
  279. output += ":";
  280. }
  281. }
  282. return output;
  283. }
  284. function parseHost(input, isUnicode) {
  285. if (input[0] === "[") {
  286. if (input[input.length - 1] !== "]") {
  287. return failure;
  288. }
  289. return parseIPv6(input.substring(1, input.length - 1));
  290. }
  291. const domain = utf8PercentDecode(input);
  292. const asciiDomain = tr46.toASCII(domain, false, tr46.PROCESSING_OPTIONS.TRANSITIONAL, false);
  293. if (asciiDomain === null) {
  294. return failure;
  295. }
  296. if (asciiDomain.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|%|\/|:|\?|@|\[|\\|\]/) !== -1) {
  297. return failure;
  298. }
  299. const ipv4Host = parseIPv4(asciiDomain);
  300. if (typeof ipv4Host === "number" || ipv4Host === failure) {
  301. return ipv4Host;
  302. }
  303. return isUnicode ? tr46.toUnicode(asciiDomain, false).domain : asciiDomain;
  304. }
  305. function findLongestZeroSequence(arr) {
  306. let maxIdx = null;
  307. let maxLen = 1; // only find elements > 1
  308. let currStart = null;
  309. let currLen = 0;
  310. for (let i = 0; i < arr.length; ++i) {
  311. if (arr[i] !== 0) {
  312. if (currLen > maxLen) {
  313. maxIdx = currStart;
  314. maxLen = currLen;
  315. }
  316. currStart = null;
  317. currLen = 0;
  318. } else {
  319. if (currStart === null) {
  320. currStart = i;
  321. }
  322. ++currLen;
  323. }
  324. }
  325. return {
  326. idx: maxIdx,
  327. len: maxLen
  328. };
  329. }
  330. function serializeHost(host) {
  331. if (typeof host === "number") {
  332. return serializeIPv4(host);
  333. }
  334. // IPv6 serializer
  335. if (host instanceof Array) {
  336. return "[" + serializeIPv6(host) + "]";
  337. }
  338. return host;
  339. }
  340. function trimControlChars(url) {
  341. return url.replace(/^[\u0000-\u001F\u0020]+|[\u0000-\u001F\u0020]+$/g, "");
  342. }
  343. function trimTabAndNewline(url) {
  344. return url.replace(/\u0009|\u000A|\u000D/g, "");
  345. }
  346. function URLStateMachine(input, base, encodingOverride, url, stateOverride) {
  347. this.pointer = 0;
  348. this.input = input;
  349. this.base = base || null;
  350. this.encodingOverride = encodingOverride || "utf-8";
  351. this.stateOverride = stateOverride;
  352. this.url = url;
  353. this.failure = false;
  354. this.parseError = false;
  355. if (!this.url) {
  356. this.url = {
  357. scheme: "",
  358. username: "",
  359. password: null,
  360. host: null,
  361. port: null,
  362. path: [],
  363. query: null,
  364. fragment: null,
  365. cannotBeABaseURL: false
  366. };
  367. const res = trimControlChars(this.input);
  368. if (res !== this.input) {
  369. this.parseError = true;
  370. }
  371. this.input = res;
  372. }
  373. const res = trimTabAndNewline(this.input);
  374. if (res !== this.input) {
  375. this.parseError = true;
  376. }
  377. this.input = res;
  378. this.state = stateOverride || "scheme start";
  379. this.buffer = "";
  380. this.atFlag = false;
  381. this.arrFlag = false;
  382. this.input = punycode.ucs2.decode(this.input);
  383. for (; this.pointer <= this.input.length; ++this.pointer) {
  384. const c = this.input[this.pointer];
  385. const cStr = isNaN(c) ? undefined : String.fromCodePoint(c);
  386. // exec state machine
  387. const ret = this["parse " + this.state](c, cStr);
  388. if (!ret) {
  389. break; // terminate algorithm
  390. } else if (ret === failure) {
  391. this.failure = true;
  392. break;
  393. }
  394. }
  395. }
  396. URLStateMachine.prototype["parse scheme start"] = function parseSchemeStart(c, cStr) {
  397. if (isASCIIAlpha(c)) {
  398. this.buffer += cStr.toLowerCase();
  399. this.state = "scheme";
  400. } else if (!this.stateOverride) {
  401. this.state = "no scheme";
  402. --this.pointer;
  403. } else {
  404. this.parseError = true;
  405. return false;
  406. }
  407. return true;
  408. };
  409. URLStateMachine.prototype["parse scheme"] = function parseScheme(c, cStr) {
  410. if (isASCIIAlpha(c) || c === 43 || c === 45 || c === 46) {
  411. this.buffer += cStr.toLowerCase();
  412. } else if (c === 58) {
  413. if (this.stateOverride) {
  414. // TODO: XOR
  415. if (specialSchemas[this.url.scheme] !== undefined && !specialSchemas[this.buffer]) {
  416. return false;
  417. } else if (specialSchemas[this.url.scheme] === undefined && specialSchemas[this.buffer]) {
  418. return false;
  419. }
  420. }
  421. this.url.scheme = this.buffer;
  422. this.buffer = "";
  423. if (this.stateOverride) {
  424. return false;
  425. }
  426. if (this.url.scheme === "file") {
  427. if (this.input[this.pointer + 1] === 47 && this.input[this.pointer + 2] === 47) {
  428. this.parseError = true;
  429. }
  430. this.state = "file";
  431. } else if (specialSchemas[this.url.scheme] !== undefined && this.base !== null &&
  432. this.base.scheme === this.url.scheme) {
  433. this.state = "special relative or authority";
  434. } else if (specialSchemas[this.url.scheme] !== undefined) {
  435. this.state = "special authority slashes";
  436. } else if (this.input[this.pointer + 1] === 47) {
  437. this.state = "path or authority";
  438. ++this.pointer;
  439. } else {
  440. this.url.cannotBeABaseURL = true;
  441. this.url.path.push("");
  442. this.state = "non-relative path";
  443. }
  444. } else if (!this.stateOverride) {
  445. this.buffer = "";
  446. this.state = "no scheme";
  447. this.pointer = -1;
  448. } else {
  449. this.parseError = true;
  450. return false;
  451. }
  452. return true;
  453. };
  454. URLStateMachine.prototype["parse no scheme"] = function parseNoScheme(c) {
  455. if (this.base === null || (this.base.cannotBeABaseURL && c !== 35)) {
  456. return failure;
  457. } else if (this.base.cannotBeABaseURL && c === 35) {
  458. this.url.scheme = this.base.scheme;
  459. this.url.path = this.base.path.slice();
  460. this.url.query = this.base.query;
  461. this.url.fragment = "";
  462. this.url.cannotBeABaseURL = true;
  463. this.state = "fragment";
  464. } else if (this.base.scheme === "file") {
  465. this.state = "file";
  466. --this.pointer;
  467. } else {
  468. this.state = "relative";
  469. --this.pointer;
  470. }
  471. return true;
  472. };
  473. URLStateMachine.prototype["parse special relative or authority"] = function parseSpecialRelativeOrAuthority(c) {
  474. if (c === 47 && this.input[this.pointer + 1] === 47) {
  475. this.state = "special authority ignore slashes";
  476. ++this.pointer;
  477. } else {
  478. this.parseError = true;
  479. this.state = "relative";
  480. --this.pointer;
  481. }
  482. return true;
  483. };
  484. URLStateMachine.prototype["parse path or authority"] = function parsePathOrAuthority(c) {
  485. if (c === 47) {
  486. this.state = "authority";
  487. } else {
  488. this.state = "path";
  489. --this.pointer;
  490. }
  491. return true;
  492. };
  493. URLStateMachine.prototype["parse relative"] = function parseRelative(c) {
  494. this.url.scheme = this.base.scheme;
  495. if (isNaN(c)) {
  496. this.url.username = this.base.username;
  497. this.url.password = this.base.password;
  498. this.url.host = this.base.host;
  499. this.url.port = this.base.port;
  500. this.url.path = this.base.path.slice();
  501. this.url.query = this.base.query;
  502. } else if (c === 47) {
  503. this.state = "relative slash";
  504. } else if (c === 63) {
  505. this.url.username = this.base.username;
  506. this.url.password = this.base.password;
  507. this.url.host = this.base.host;
  508. this.url.port = this.base.port;
  509. this.url.path = this.base.path.slice();
  510. this.url.query = "";
  511. this.state = "query";
  512. } else if (c === 35) {
  513. this.url.username = this.base.username;
  514. this.url.password = this.base.password;
  515. this.url.host = this.base.host;
  516. this.url.port = this.base.port;
  517. this.url.path = this.base.path.slice();
  518. this.url.query = this.base.query;
  519. this.url.fragment = "";
  520. this.state = "fragment";
  521. } else if (specialSchemas[this.url.scheme] !== undefined && c === 92) {
  522. this.parseError = true;
  523. this.state = "relative slash";
  524. } else {
  525. this.url.username = this.base.username;
  526. this.url.password = this.base.password;
  527. this.url.host = this.base.host;
  528. this.url.port = this.base.port;
  529. this.url.path = this.base.path.slice(0, this.base.path.length - 1);
  530. this.state = "path";
  531. --this.pointer;
  532. }
  533. return true;
  534. };
  535. URLStateMachine.prototype["parse relative slash"] = function parseRelativeSlash(c) {
  536. if (c === 47 || (specialSchemas[this.url.scheme] !== undefined && c === 92)) {
  537. if (c === 92) {
  538. this.parseError = true;
  539. }
  540. this.state = "special authority ignore slashes";
  541. } else {
  542. this.url.username = this.base.username;
  543. this.url.password = this.base.password;
  544. this.url.host = this.base.host;
  545. this.url.port = this.base.port;
  546. this.state = "path";
  547. --this.pointer;
  548. }
  549. return true;
  550. };
  551. URLStateMachine.prototype["parse special authority slashes"] = function parseSpecialAuthoritySlashes(c) {
  552. if (c === 47 && this.input[this.pointer + 1] === 47) {
  553. this.state = "special authority ignore slashes";
  554. ++this.pointer;
  555. } else {
  556. this.parseError = true;
  557. this.state = "special authority ignore slashes";
  558. --this.pointer;
  559. }
  560. return true;
  561. };
  562. URLStateMachine.prototype["parse special authority ignore slashes"] = function parseSpecialAuthorityIgnoreSlashes(c) {
  563. if (c !== 47 && c !== 92) {
  564. this.state = "authority";
  565. --this.pointer;
  566. } else {
  567. this.parseError = true;
  568. }
  569. return true;
  570. };
  571. URLStateMachine.prototype["parse authority"] = function parseAuthority(c, cStr) {
  572. if (c === 64) {
  573. this.parseError = true;
  574. if (this.atFlag) {
  575. this.buffer = "%40" + this.buffer;
  576. }
  577. this.atFlag = true;
  578. // careful, this is based on buffer and has its own pointer (this.pointer != pointer) and inner chars
  579. const len = countSymbols(this.buffer);
  580. for (let pointer = 0; pointer < len; ++pointer) {
  581. const codePoint = this.buffer.codePointAt(pointer);
  582. if (codePoint === 58 && this.url.password === null) {
  583. this.url.password = "";
  584. continue;
  585. }
  586. const encodedCodePoints = encodeChar(codePoint, isUserInfoEncode);
  587. if (this.url.password !== null) {
  588. this.url.password += encodedCodePoints;
  589. } else {
  590. this.url.username += encodedCodePoints;
  591. }
  592. }
  593. this.buffer = "";
  594. } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
  595. (specialSchemas[this.url.scheme] !== undefined && c === 92)) {
  596. this.pointer -= countSymbols(this.buffer) + 1;
  597. this.buffer = "";
  598. this.state = "host";
  599. } else {
  600. this.buffer += cStr;
  601. }
  602. return true;
  603. };
  604. URLStateMachine.prototype["parse hostname"] =
  605. URLStateMachine.prototype["parse host"] = function parseHostName(c, cStr) {
  606. if (c === 58 && !this.arrFlag) {
  607. if (specialSchemas[this.url.scheme] !== undefined && this.buffer === "") {
  608. return failure;
  609. }
  610. const host = parseHost(this.buffer);
  611. if (host === failure) {
  612. return failure;
  613. }
  614. this.url.host = host;
  615. this.buffer = "";
  616. this.state = "port";
  617. if (this.stateOverride === "hostname") {
  618. return false;
  619. }
  620. } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
  621. (specialSchemas[this.url.scheme] !== undefined && c === 92)) {
  622. --this.pointer;
  623. if (specialSchemas[this.url.scheme] !== undefined && this.buffer === "") {
  624. return failure;
  625. }
  626. const host = parseHost(this.buffer);
  627. if (host === failure) {
  628. return failure;
  629. }
  630. this.url.host = host;
  631. this.buffer = "";
  632. this.state = "path start";
  633. if (this.stateOverride) {
  634. return false;
  635. }
  636. } else {
  637. if (c === 91) {
  638. this.arrFlag = true;
  639. } else if (c === 93) {
  640. this.arrFlag = false;
  641. }
  642. this.buffer += cStr;
  643. }
  644. return true;
  645. };
  646. URLStateMachine.prototype["parse port"] = function parsePort(c, cStr) {
  647. if (isASCIIDigit(c)) {
  648. this.buffer += cStr;
  649. } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
  650. (specialSchemas[this.url.scheme] !== undefined && c === 92) ||
  651. this.stateOverride) {
  652. if (this.buffer !== "") {
  653. const port = parseInt(this.buffer);
  654. if (port > Math.pow(2, 16) - 1) {
  655. this.parseError = true;
  656. return failure;
  657. }
  658. this.url.port = port === specialSchemas[this.url.scheme] ? null : port;
  659. this.buffer = "";
  660. }
  661. if (this.stateOverride) {
  662. return false;
  663. }
  664. this.state = "path start";
  665. --this.pointer;
  666. } else {
  667. this.parseError = true;
  668. return failure;
  669. }
  670. return true;
  671. };
  672. URLStateMachine.prototype["parse file"] = function parseFile(c) {
  673. this.url.scheme = "file";
  674. if (isNaN(c)) {
  675. if (this.base !== null && this.base.scheme === "file") {
  676. this.url.host = this.base.host;
  677. this.url.path = this.base.path.slice();
  678. this.url.query = this.base.query;
  679. }
  680. } else if (c === 47 || c === 92) {
  681. if (c === 92) {
  682. this.parseError = true;
  683. }
  684. this.state = "file slash";
  685. } else if (c === 63) {
  686. if (this.base !== null && this.base.scheme === "file") {
  687. this.url.host = this.base.host;
  688. this.url.path = this.base.path.slice();
  689. this.url.query = "";
  690. }
  691. this.state = "query";
  692. } else if (c === 35) {
  693. if (this.base !== null && this.base.scheme === "file") {
  694. this.url.host = this.base.host;
  695. this.url.path = this.base.path.slice();
  696. this.url.query = this.base.query;
  697. this.url.fragment = "";
  698. }
  699. this.state = "fragment";
  700. } else {
  701. if (this.base !== null && this.base.scheme === "file") {
  702. if ((!isASCIIAlpha(c) || // windows drive letter
  703. (this.input[this.pointer + 1] !== 58 && this.input[this.pointer + 1] !== 124)) ||
  704. this.input.length - this.pointer - 1 === 1 || // remaining consists of 1 code point
  705. [47, 92, 63, 35].indexOf(this.input[this.pointer + 2]) === -1) {
  706. this.url.host = this.base.host;
  707. this.url.path = this.base.path.slice();
  708. this.url.path.pop();
  709. } else {
  710. this.parseError = true;
  711. }
  712. }
  713. this.state = "path";
  714. --this.pointer;
  715. }
  716. return true;
  717. };
  718. URLStateMachine.prototype["parse file slash"] = function parseFileSlash(c) {
  719. if (c === 47 || c === 92) {
  720. if (c === 92) {
  721. this.parseError = true;
  722. }
  723. this.state = "file host";
  724. } else {
  725. if (this.base !== null && this.base.scheme === "file") {
  726. if (this.base.path.length && isASCIIAlpha(this.base.path[0][0].charCodeAt(0)) && this.base.path[0][1] === ":") {
  727. this.url.path.push(this.base.path[0]);
  728. }
  729. }
  730. this.state = "path";
  731. --this.pointer;
  732. }
  733. return true;
  734. };
  735. URLStateMachine.prototype["parse file host"] = function parseFileHost(c, cStr) {
  736. if (isNaN(c) || c === 47 || c === 92 || c === 63 || c === 35) {
  737. --this.pointer;
  738. // don't need to count symbols here since we check ASCII values
  739. if (this.buffer.length === 2 &&
  740. isASCIIAlpha(this.buffer.codePointAt(0)) && (this.buffer[1] === ":" || this.buffer[1] === "|")) {
  741. this.state = "path";
  742. } else if (this.buffer === "") {
  743. this.state = "path start";
  744. } else {
  745. const host = parseHost(this.buffer);
  746. if (host === failure) {
  747. return failure;
  748. }
  749. if (host !== "localhost") {
  750. this.url.host = host;
  751. }
  752. this.buffer = "";
  753. this.state = "path start";
  754. }
  755. } else {
  756. this.buffer += cStr;
  757. }
  758. return true;
  759. };
  760. URLStateMachine.prototype["parse path start"] = function parsePathStart(c) {
  761. if (specialSchemas[this.url.scheme] !== undefined && c === 92) {
  762. this.parseError = true;
  763. }
  764. this.state = "path";
  765. if (c !== 47 && !(specialSchemas[this.url.scheme] !== undefined && c === 92)) {
  766. --this.pointer;
  767. }
  768. return true;
  769. };
  770. URLStateMachine.prototype["parse path"] = function parsePath(c) {
  771. if (isNaN(c) || c === 47 || (specialSchemas[this.url.scheme] !== undefined && c === 92) ||
  772. (!this.stateOverride && (c === 63 || c === 35))) {
  773. if (specialSchemas[this.url.scheme] !== undefined && c === 92) {
  774. this.parseError = true;
  775. }
  776. if (isDoubleDot(this.buffer)) {
  777. this.url.path.pop();
  778. if (c !== 47 && !(specialSchemas[this.url.scheme] !== undefined && c === 92)) {
  779. this.url.path.push("");
  780. }
  781. } else if (isSingleDot(this.buffer) && c !== 47 &&
  782. !(specialSchemas[this.url.scheme] !== undefined && c === 92)) {
  783. this.url.path.push("");
  784. } else if (!isSingleDot(this.buffer)) {
  785. if (this.url.scheme === "file" && this.url.path.length === 0 &&
  786. this.buffer.length === 2 && isASCIIAlpha(this.buffer.codePointAt(0)) &&
  787. (this.buffer[1] === "|" || this.buffer[1] === ":")) {
  788. if (this.url.host !== null) {
  789. this.parseError = true;
  790. }
  791. this.url.host = null;
  792. this.buffer = this.buffer[0] + ":";
  793. }
  794. this.url.path.push(this.buffer);
  795. }
  796. this.buffer = "";
  797. if (c === 63) {
  798. this.url.query = "";
  799. this.state = "query";
  800. }
  801. if (c === 35) {
  802. this.url.fragment = "";
  803. this.state = "fragment";
  804. }
  805. } else {
  806. // TODO: If c is not a URL code point and not "%", parse error.
  807. if (c === 37 &&
  808. (!isASCIIHex(this.input[this.pointer + 1]) ||
  809. !isASCIIHex(this.input[this.pointer + 2]))) {
  810. this.parseError = true;
  811. }
  812. if (c === 37 &&
  813. this.input[this.pointer + 1] === 50 &&
  814. (this.input[this.pointer + 2] === 101 || this.input[this.pointer + 2] === 69)) {
  815. this.buffer += ".";
  816. this.pointer += 2;
  817. } else {
  818. this.buffer += encodeChar(c, isDefaultEncode);
  819. }
  820. }
  821. return true;
  822. };
  823. URLStateMachine.prototype["parse non-relative path"] = function parseNonRelativePath(c) {
  824. if (c === 63) {
  825. this.url.query = "";
  826. this.state = "query";
  827. } else if (c === 35) {
  828. this.url.fragment = "";
  829. this.state = "fragment";
  830. } else {
  831. // TODO: Add: not a URL code point
  832. if (!isNaN(c) && c !== 37) {
  833. this.parseError = true;
  834. }
  835. if (c === 37 &&
  836. (!isASCIIHex(this.input[this.pointer + 1]) ||
  837. !isASCIIHex(this.input[this.pointer + 2]))) {
  838. this.parseError = true;
  839. }
  840. if (!isNaN(c)) {
  841. this.url.path[0] = this.url.path[0] + encodeChar(c, isSimpleEncode);
  842. }
  843. }
  844. return true;
  845. };
  846. URLStateMachine.prototype["parse query"] = function parseQuery(c, cStr) {
  847. if (isNaN(c) || (!this.stateOverride && c === 35)) {
  848. if (specialSchemas[this.url.scheme] === undefined || this.url.scheme === "ws" || this.url.scheme === "wss") {
  849. this.encodingOverride = "utf-8";
  850. }
  851. const buffer = new Buffer(this.buffer); // TODO: Use encoding override instead
  852. for (let i = 0; i < buffer.length; ++i) {
  853. if (buffer[i] < 0x21 || buffer[i] > 0x7E || buffer[i] === 0x22 || buffer[i] === 0x23 ||
  854. buffer[i] === 0x3C || buffer[i] === 0x3E) {
  855. this.url.query += percentEncode(buffer[i]);
  856. } else {
  857. this.url.query += String.fromCodePoint(buffer[i]);
  858. }
  859. }
  860. this.buffer = "";
  861. if (c === 35) {
  862. this.url.fragment = "";
  863. this.state = "fragment";
  864. }
  865. } else {
  866. // TODO: If c is not a URL code point and not "%", parse error.
  867. if (c === 37 &&
  868. (!isASCIIHex(this.input[this.pointer + 1]) ||
  869. !isASCIIHex(this.input[this.pointer + 2]))) {
  870. this.parseError = true;
  871. }
  872. this.buffer += cStr;
  873. }
  874. return true;
  875. };
  876. URLStateMachine.prototype["parse fragment"] = function parseFragment(c, cStr) {
  877. if (isNaN(c)) { // do nothing
  878. } else if (c === 0x0) {
  879. this.parseError = true;
  880. } else {
  881. // TODO: If c is not a URL code point and not "%", parse error.
  882. if (c === 37 &&
  883. (!isASCIIHex(this.input[this.pointer + 1]) ||
  884. !isASCIIHex(this.input[this.pointer + 2]))) {
  885. this.parseError = true;
  886. }
  887. this.url.fragment += cStr;
  888. }
  889. return true;
  890. };
  891. function serializeURL(url, excludeFragment) {
  892. let output = url.scheme + ":";
  893. if (url.host !== null) {
  894. output += "//" + url.username;
  895. if (url.password !== null) {
  896. output += ":" + url.password;
  897. }
  898. if (url.username !== "" || url.password !== null) {
  899. output += "@";
  900. }
  901. output += serializeHost(url.host);
  902. if (url.port !== null) {
  903. output += ":" + url.port;
  904. }
  905. } else if (url.host === null && url.scheme === "file") {
  906. output += "//";
  907. }
  908. if (url.cannotBeABaseURL) {
  909. output += url.path[0];
  910. } else {
  911. output += "/" + url.path.join("/");
  912. }
  913. if (url.query !== null) {
  914. output += "?" + url.query;
  915. }
  916. if (!excludeFragment && url.fragment !== null) {
  917. output += "#" + url.fragment;
  918. }
  919. return output;
  920. }
  921. function serializeOrigin(tuple) {
  922. if (tuple.scheme === undefined || tuple.host === undefined || tuple.port === undefined) {
  923. return "null";
  924. }
  925. let result = tuple.scheme + "://";
  926. result += tr46.toUnicode(tuple.host, false).domain;
  927. if (specialSchemas[tuple.scheme] && tuple.port !== specialSchemas[tuple.scheme]) {
  928. result += ":" + tuple.port;
  929. }
  930. return result;
  931. }
  932. module.exports.serializeURL = serializeURL;
  933. module.exports.serializeURLToUnicodeOrigin = function (url) {
  934. switch (url.scheme) {
  935. case "blob":
  936. try {
  937. return module.exports.serializeURLToUnicodeOrigin(module.exports.parseURL(url.path[0]));
  938. } catch (e) {
  939. // serializing an opaque identifier returns "null"
  940. return "null";
  941. }
  942. case "ftp":
  943. case "gopher":
  944. case "http":
  945. case "https":
  946. case "ws":
  947. case "wss":
  948. return serializeOrigin({
  949. scheme: url.scheme,
  950. host: serializeHost(url.host),
  951. port: url.port === null ? specialSchemas[url.scheme] : url.port
  952. });
  953. case "file":
  954. // spec says "exercise to the reader", chrome says "file://"
  955. return "file://";
  956. default:
  957. // serializing an opaque identifier returns "null"
  958. return "null";
  959. }
  960. };
  961. module.exports.basicURLParse = function (input, options) {
  962. if (options === undefined) {
  963. options = {};
  964. }
  965. const usm = new URLStateMachine(input, options.baseURL, options.encodingOverride, options.url, options.stateOverride);
  966. if (usm.failure) {
  967. throw new TypeError("Invalid URL");
  968. }
  969. return usm.url;
  970. };
  971. module.exports.setTheUsername = function (url, username) {
  972. url.username = "";
  973. const decoded = punycode.ucs2.decode(username);
  974. for (let i = 0; i < decoded.length; ++i) {
  975. url.username += encodeChar(decoded[i], isUserInfoEncode);
  976. }
  977. };
  978. module.exports.setThePassword = function (url, password) {
  979. if (password === "") {
  980. url.password = null;
  981. } else {
  982. url.password = "";
  983. const decoded = punycode.ucs2.decode(password);
  984. for (let i = 0; i < decoded.length; ++i) {
  985. url.password += encodeChar(decoded[i], isUserInfoEncode);
  986. }
  987. }
  988. };
  989. module.exports.serializeHost = serializeHost;
  990. module.exports.serializeInteger = function (integer) {
  991. return String(integer);
  992. };
  993. module.exports.parseURL = function (input, options) {
  994. if (options === undefined) {
  995. options = {};
  996. }
  997. // We don't handle blobs, so this just delegates:
  998. return module.exports.basicURLParse(input, { baseURL: options.baseURL, encodingOverride: options.encodingOverride });
  999. };