url.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // Licensed under the MIT License. See License.txt in the project root for license information.
  3. import { replaceAll } from "./util/utils";
  4. /**
  5. * A class that handles the query portion of a URLBuilder.
  6. */
  7. var URLQuery = /** @class */ (function () {
  8. function URLQuery() {
  9. this._rawQuery = {};
  10. }
  11. /**
  12. * Get whether or not there any query parameters in this URLQuery.
  13. */
  14. URLQuery.prototype.any = function () {
  15. return Object.keys(this._rawQuery).length > 0;
  16. };
  17. /**
  18. * Set a query parameter with the provided name and value. If the parameterValue is undefined or
  19. * empty, then this will attempt to remove an existing query parameter with the provided
  20. * parameterName.
  21. */
  22. URLQuery.prototype.set = function (parameterName, parameterValue) {
  23. if (parameterName) {
  24. if (parameterValue != undefined) {
  25. var newValue = Array.isArray(parameterValue) ? parameterValue : parameterValue.toString();
  26. this._rawQuery[parameterName] = newValue;
  27. }
  28. else {
  29. delete this._rawQuery[parameterName];
  30. }
  31. }
  32. };
  33. /**
  34. * Get the value of the query parameter with the provided name. If no parameter exists with the
  35. * provided parameter name, then undefined will be returned.
  36. */
  37. URLQuery.prototype.get = function (parameterName) {
  38. return parameterName ? this._rawQuery[parameterName] : undefined;
  39. };
  40. /**
  41. * Get the string representation of this query. The return value will not start with a "?".
  42. */
  43. URLQuery.prototype.toString = function () {
  44. var result = "";
  45. for (var parameterName in this._rawQuery) {
  46. if (result) {
  47. result += "&";
  48. }
  49. var parameterValue = this._rawQuery[parameterName];
  50. if (Array.isArray(parameterValue)) {
  51. var parameterStrings = [];
  52. for (var _i = 0, parameterValue_1 = parameterValue; _i < parameterValue_1.length; _i++) {
  53. var parameterValueElement = parameterValue_1[_i];
  54. parameterStrings.push(parameterName + "=" + parameterValueElement);
  55. }
  56. result += parameterStrings.join("&");
  57. }
  58. else {
  59. result += parameterName + "=" + parameterValue;
  60. }
  61. }
  62. return result;
  63. };
  64. /**
  65. * Parse a URLQuery from the provided text.
  66. */
  67. URLQuery.parse = function (text) {
  68. var result = new URLQuery();
  69. if (text) {
  70. if (text.startsWith("?")) {
  71. text = text.substring(1);
  72. }
  73. var currentState = "ParameterName";
  74. var parameterName = "";
  75. var parameterValue = "";
  76. for (var i = 0; i < text.length; ++i) {
  77. var currentCharacter = text[i];
  78. switch (currentState) {
  79. case "ParameterName":
  80. switch (currentCharacter) {
  81. case "=":
  82. currentState = "ParameterValue";
  83. break;
  84. case "&":
  85. parameterName = "";
  86. parameterValue = "";
  87. break;
  88. default:
  89. parameterName += currentCharacter;
  90. break;
  91. }
  92. break;
  93. case "ParameterValue":
  94. switch (currentCharacter) {
  95. case "=":
  96. parameterName = "";
  97. parameterValue = "";
  98. currentState = "Invalid";
  99. break;
  100. case "&":
  101. result.set(parameterName, parameterValue);
  102. parameterName = "";
  103. parameterValue = "";
  104. currentState = "ParameterName";
  105. break;
  106. default:
  107. parameterValue += currentCharacter;
  108. break;
  109. }
  110. break;
  111. case "Invalid":
  112. if (currentCharacter === "&") {
  113. currentState = "ParameterName";
  114. }
  115. break;
  116. default:
  117. throw new Error("Unrecognized URLQuery parse state: " + currentState);
  118. }
  119. }
  120. if (currentState === "ParameterValue") {
  121. result.set(parameterName, parameterValue);
  122. }
  123. }
  124. return result;
  125. };
  126. return URLQuery;
  127. }());
  128. export { URLQuery };
  129. /**
  130. * A class that handles creating, modifying, and parsing URLs.
  131. */
  132. var URLBuilder = /** @class */ (function () {
  133. function URLBuilder() {
  134. }
  135. /**
  136. * Set the scheme/protocol for this URL. If the provided scheme contains other parts of a URL
  137. * (such as a host, port, path, or query), those parts will be added to this URL as well.
  138. */
  139. URLBuilder.prototype.setScheme = function (scheme) {
  140. if (!scheme) {
  141. this._scheme = undefined;
  142. }
  143. else {
  144. this.set(scheme, "SCHEME");
  145. }
  146. };
  147. /**
  148. * Get the scheme that has been set in this URL.
  149. */
  150. URLBuilder.prototype.getScheme = function () {
  151. return this._scheme;
  152. };
  153. /**
  154. * Set the host for this URL. If the provided host contains other parts of a URL (such as a
  155. * port, path, or query), those parts will be added to this URL as well.
  156. */
  157. URLBuilder.prototype.setHost = function (host) {
  158. if (!host) {
  159. this._host = undefined;
  160. }
  161. else {
  162. this.set(host, "SCHEME_OR_HOST");
  163. }
  164. };
  165. /**
  166. * Get the host that has been set in this URL.
  167. */
  168. URLBuilder.prototype.getHost = function () {
  169. return this._host;
  170. };
  171. /**
  172. * Set the port for this URL. If the provided port contains other parts of a URL (such as a
  173. * path or query), those parts will be added to this URL as well.
  174. */
  175. URLBuilder.prototype.setPort = function (port) {
  176. if (port == undefined || port === "") {
  177. this._port = undefined;
  178. }
  179. else {
  180. this.set(port.toString(), "PORT");
  181. }
  182. };
  183. /**
  184. * Get the port that has been set in this URL.
  185. */
  186. URLBuilder.prototype.getPort = function () {
  187. return this._port;
  188. };
  189. /**
  190. * Set the path for this URL. If the provided path contains a query, then it will be added to
  191. * this URL as well.
  192. */
  193. URLBuilder.prototype.setPath = function (path) {
  194. if (!path) {
  195. this._path = undefined;
  196. }
  197. else {
  198. if (path.indexOf("://") !== -1) {
  199. this.set(path, "SCHEME");
  200. }
  201. else {
  202. this.set(path, "PATH");
  203. }
  204. }
  205. };
  206. /**
  207. * Append the provided path to this URL's existing path. If the provided path contains a query,
  208. * then it will be added to this URL as well.
  209. */
  210. URLBuilder.prototype.appendPath = function (path) {
  211. if (path) {
  212. var currentPath = this.getPath();
  213. if (currentPath) {
  214. if (!currentPath.endsWith("/")) {
  215. currentPath += "/";
  216. }
  217. if (path.startsWith("/")) {
  218. path = path.substring(1);
  219. }
  220. path = currentPath + path;
  221. }
  222. this.set(path, "PATH");
  223. }
  224. };
  225. /**
  226. * Get the path that has been set in this URL.
  227. */
  228. URLBuilder.prototype.getPath = function () {
  229. return this._path;
  230. };
  231. /**
  232. * Set the query in this URL.
  233. */
  234. URLBuilder.prototype.setQuery = function (query) {
  235. if (!query) {
  236. this._query = undefined;
  237. }
  238. else {
  239. this._query = URLQuery.parse(query);
  240. }
  241. };
  242. /**
  243. * Set a query parameter with the provided name and value in this URL's query. If the provided
  244. * query parameter value is undefined or empty, then the query parameter will be removed if it
  245. * existed.
  246. */
  247. URLBuilder.prototype.setQueryParameter = function (queryParameterName, queryParameterValue) {
  248. if (queryParameterName) {
  249. if (!this._query) {
  250. this._query = new URLQuery();
  251. }
  252. this._query.set(queryParameterName, queryParameterValue);
  253. }
  254. };
  255. /**
  256. * Get the value of the query parameter with the provided query parameter name. If no query
  257. * parameter exists with the provided name, then undefined will be returned.
  258. */
  259. URLBuilder.prototype.getQueryParameterValue = function (queryParameterName) {
  260. return this._query ? this._query.get(queryParameterName) : undefined;
  261. };
  262. /**
  263. * Get the query in this URL.
  264. */
  265. URLBuilder.prototype.getQuery = function () {
  266. return this._query ? this._query.toString() : undefined;
  267. };
  268. /**
  269. * Set the parts of this URL by parsing the provided text using the provided startState.
  270. */
  271. URLBuilder.prototype.set = function (text, startState) {
  272. var tokenizer = new URLTokenizer(text, startState);
  273. while (tokenizer.next()) {
  274. var token = tokenizer.current();
  275. if (token) {
  276. switch (token.type) {
  277. case "SCHEME":
  278. this._scheme = token.text || undefined;
  279. break;
  280. case "HOST":
  281. this._host = token.text || undefined;
  282. break;
  283. case "PORT":
  284. this._port = token.text || undefined;
  285. break;
  286. case "PATH":
  287. var tokenPath = token.text || undefined;
  288. if (!this._path || this._path === "/" || tokenPath !== "/") {
  289. this._path = tokenPath;
  290. }
  291. break;
  292. case "QUERY":
  293. this._query = URLQuery.parse(token.text);
  294. break;
  295. default:
  296. throw new Error("Unrecognized URLTokenType: " + token.type);
  297. }
  298. }
  299. }
  300. };
  301. URLBuilder.prototype.toString = function () {
  302. var result = "";
  303. if (this._scheme) {
  304. result += this._scheme + "://";
  305. }
  306. if (this._host) {
  307. result += this._host;
  308. }
  309. if (this._port) {
  310. result += ":" + this._port;
  311. }
  312. if (this._path) {
  313. if (!this._path.startsWith("/")) {
  314. result += "/";
  315. }
  316. result += this._path;
  317. }
  318. if (this._query && this._query.any()) {
  319. result += "?" + this._query.toString();
  320. }
  321. return result;
  322. };
  323. /**
  324. * If the provided searchValue is found in this URLBuilder, then replace it with the provided
  325. * replaceValue.
  326. */
  327. URLBuilder.prototype.replaceAll = function (searchValue, replaceValue) {
  328. if (searchValue) {
  329. this.setScheme(replaceAll(this.getScheme(), searchValue, replaceValue));
  330. this.setHost(replaceAll(this.getHost(), searchValue, replaceValue));
  331. this.setPort(replaceAll(this.getPort(), searchValue, replaceValue));
  332. this.setPath(replaceAll(this.getPath(), searchValue, replaceValue));
  333. this.setQuery(replaceAll(this.getQuery(), searchValue, replaceValue));
  334. }
  335. };
  336. URLBuilder.parse = function (text) {
  337. var result = new URLBuilder();
  338. result.set(text, "SCHEME_OR_HOST");
  339. return result;
  340. };
  341. return URLBuilder;
  342. }());
  343. export { URLBuilder };
  344. var URLToken = /** @class */ (function () {
  345. function URLToken(text, type) {
  346. this.text = text;
  347. this.type = type;
  348. }
  349. URLToken.scheme = function (text) {
  350. return new URLToken(text, "SCHEME");
  351. };
  352. URLToken.host = function (text) {
  353. return new URLToken(text, "HOST");
  354. };
  355. URLToken.port = function (text) {
  356. return new URLToken(text, "PORT");
  357. };
  358. URLToken.path = function (text) {
  359. return new URLToken(text, "PATH");
  360. };
  361. URLToken.query = function (text) {
  362. return new URLToken(text, "QUERY");
  363. };
  364. return URLToken;
  365. }());
  366. export { URLToken };
  367. /**
  368. * Get whether or not the provided character (single character string) is an alphanumeric (letter or
  369. * digit) character.
  370. */
  371. export function isAlphaNumericCharacter(character) {
  372. var characterCode = character.charCodeAt(0);
  373. return (48 /* '0' */ <= characterCode && characterCode <= 57 /* '9' */) ||
  374. (65 /* 'A' */ <= characterCode && characterCode <= 90 /* 'Z' */) ||
  375. (97 /* 'a' */ <= characterCode && characterCode <= 122 /* 'z' */);
  376. }
  377. /**
  378. * A class that tokenizes URL strings.
  379. */
  380. var URLTokenizer = /** @class */ (function () {
  381. function URLTokenizer(_text, state) {
  382. this._text = _text;
  383. this._textLength = _text ? _text.length : 0;
  384. this._currentState = state != undefined ? state : "SCHEME_OR_HOST";
  385. this._currentIndex = 0;
  386. }
  387. /**
  388. * Get the current URLToken this URLTokenizer is pointing at, or undefined if the URLTokenizer
  389. * hasn't started or has finished tokenizing.
  390. */
  391. URLTokenizer.prototype.current = function () {
  392. return this._currentToken;
  393. };
  394. /**
  395. * Advance to the next URLToken and return whether or not a URLToken was found.
  396. */
  397. URLTokenizer.prototype.next = function () {
  398. if (!hasCurrentCharacter(this)) {
  399. this._currentToken = undefined;
  400. }
  401. else {
  402. switch (this._currentState) {
  403. case "SCHEME":
  404. nextScheme(this);
  405. break;
  406. case "SCHEME_OR_HOST":
  407. nextSchemeOrHost(this);
  408. break;
  409. case "HOST":
  410. nextHost(this);
  411. break;
  412. case "PORT":
  413. nextPort(this);
  414. break;
  415. case "PATH":
  416. nextPath(this);
  417. break;
  418. case "QUERY":
  419. nextQuery(this);
  420. break;
  421. default:
  422. throw new Error("Unrecognized URLTokenizerState: " + this._currentState);
  423. }
  424. }
  425. return !!this._currentToken;
  426. };
  427. return URLTokenizer;
  428. }());
  429. export { URLTokenizer };
  430. /**
  431. * Read the remaining characters from this Tokenizer's character stream.
  432. */
  433. function readRemaining(tokenizer) {
  434. var result = "";
  435. if (tokenizer._currentIndex < tokenizer._textLength) {
  436. result = tokenizer._text.substring(tokenizer._currentIndex);
  437. tokenizer._currentIndex = tokenizer._textLength;
  438. }
  439. return result;
  440. }
  441. /**
  442. * Whether or not this URLTokenizer has a current character.
  443. */
  444. function hasCurrentCharacter(tokenizer) {
  445. return tokenizer._currentIndex < tokenizer._textLength;
  446. }
  447. /**
  448. * Get the character in the text string at the current index.
  449. */
  450. function getCurrentCharacter(tokenizer) {
  451. return tokenizer._text[tokenizer._currentIndex];
  452. }
  453. /**
  454. * Advance to the character in text that is "step" characters ahead. If no step value is provided,
  455. * then step will default to 1.
  456. */
  457. function nextCharacter(tokenizer, step) {
  458. if (hasCurrentCharacter(tokenizer)) {
  459. if (!step) {
  460. step = 1;
  461. }
  462. tokenizer._currentIndex += step;
  463. }
  464. }
  465. /**
  466. * Starting with the current character, peek "charactersToPeek" number of characters ahead in this
  467. * Tokenizer's stream of characters.
  468. */
  469. function peekCharacters(tokenizer, charactersToPeek) {
  470. var endIndex = tokenizer._currentIndex + charactersToPeek;
  471. if (tokenizer._textLength < endIndex) {
  472. endIndex = tokenizer._textLength;
  473. }
  474. return tokenizer._text.substring(tokenizer._currentIndex, endIndex);
  475. }
  476. /**
  477. * Read characters from this Tokenizer until the end of the stream or until the provided condition
  478. * is false when provided the current character.
  479. */
  480. function readWhile(tokenizer, condition) {
  481. var result = "";
  482. while (hasCurrentCharacter(tokenizer)) {
  483. var currentCharacter = getCurrentCharacter(tokenizer);
  484. if (!condition(currentCharacter)) {
  485. break;
  486. }
  487. else {
  488. result += currentCharacter;
  489. nextCharacter(tokenizer);
  490. }
  491. }
  492. return result;
  493. }
  494. /**
  495. * Read characters from this Tokenizer until a non-alphanumeric character or the end of the
  496. * character stream is reached.
  497. */
  498. function readWhileLetterOrDigit(tokenizer) {
  499. return readWhile(tokenizer, function (character) { return isAlphaNumericCharacter(character); });
  500. }
  501. /**
  502. * Read characters from this Tokenizer until one of the provided terminating characters is read or
  503. * the end of the character stream is reached.
  504. */
  505. function readUntilCharacter(tokenizer) {
  506. var terminatingCharacters = [];
  507. for (var _i = 1; _i < arguments.length; _i++) {
  508. terminatingCharacters[_i - 1] = arguments[_i];
  509. }
  510. return readWhile(tokenizer, function (character) { return terminatingCharacters.indexOf(character) === -1; });
  511. }
  512. function nextScheme(tokenizer) {
  513. var scheme = readWhileLetterOrDigit(tokenizer);
  514. tokenizer._currentToken = URLToken.scheme(scheme);
  515. if (!hasCurrentCharacter(tokenizer)) {
  516. tokenizer._currentState = "DONE";
  517. }
  518. else {
  519. tokenizer._currentState = "HOST";
  520. }
  521. }
  522. function nextSchemeOrHost(tokenizer) {
  523. var schemeOrHost = readUntilCharacter(tokenizer, ":", "/", "?");
  524. if (!hasCurrentCharacter(tokenizer)) {
  525. tokenizer._currentToken = URLToken.host(schemeOrHost);
  526. tokenizer._currentState = "DONE";
  527. }
  528. else if (getCurrentCharacter(tokenizer) === ":") {
  529. if (peekCharacters(tokenizer, 3) === "://") {
  530. tokenizer._currentToken = URLToken.scheme(schemeOrHost);
  531. tokenizer._currentState = "HOST";
  532. }
  533. else {
  534. tokenizer._currentToken = URLToken.host(schemeOrHost);
  535. tokenizer._currentState = "PORT";
  536. }
  537. }
  538. else {
  539. tokenizer._currentToken = URLToken.host(schemeOrHost);
  540. if (getCurrentCharacter(tokenizer) === "/") {
  541. tokenizer._currentState = "PATH";
  542. }
  543. else {
  544. tokenizer._currentState = "QUERY";
  545. }
  546. }
  547. }
  548. function nextHost(tokenizer) {
  549. if (peekCharacters(tokenizer, 3) === "://") {
  550. nextCharacter(tokenizer, 3);
  551. }
  552. var host = readUntilCharacter(tokenizer, ":", "/", "?");
  553. tokenizer._currentToken = URLToken.host(host);
  554. if (!hasCurrentCharacter(tokenizer)) {
  555. tokenizer._currentState = "DONE";
  556. }
  557. else if (getCurrentCharacter(tokenizer) === ":") {
  558. tokenizer._currentState = "PORT";
  559. }
  560. else if (getCurrentCharacter(tokenizer) === "/") {
  561. tokenizer._currentState = "PATH";
  562. }
  563. else {
  564. tokenizer._currentState = "QUERY";
  565. }
  566. }
  567. function nextPort(tokenizer) {
  568. if (getCurrentCharacter(tokenizer) === ":") {
  569. nextCharacter(tokenizer);
  570. }
  571. var port = readUntilCharacter(tokenizer, "/", "?");
  572. tokenizer._currentToken = URLToken.port(port);
  573. if (!hasCurrentCharacter(tokenizer)) {
  574. tokenizer._currentState = "DONE";
  575. }
  576. else if (getCurrentCharacter(tokenizer) === "/") {
  577. tokenizer._currentState = "PATH";
  578. }
  579. else {
  580. tokenizer._currentState = "QUERY";
  581. }
  582. }
  583. function nextPath(tokenizer) {
  584. var path = readUntilCharacter(tokenizer, "?");
  585. tokenizer._currentToken = URLToken.path(path);
  586. if (!hasCurrentCharacter(tokenizer)) {
  587. tokenizer._currentState = "DONE";
  588. }
  589. else {
  590. tokenizer._currentState = "QUERY";
  591. }
  592. }
  593. function nextQuery(tokenizer) {
  594. if (getCurrentCharacter(tokenizer) === "?") {
  595. nextCharacter(tokenizer);
  596. }
  597. var query = readRemaining(tokenizer);
  598. tokenizer._currentToken = URLToken.query(query);
  599. tokenizer._currentState = "DONE";
  600. }
  601. //# sourceMappingURL=url.js.map