UNPKG

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