Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

url-state-machine.js 30KB

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