1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | (function URLConstructorClosure() {
|
8 | 'use strict';
|
9 |
|
10 | var relative = Object.create(null);
|
11 | relative['ftp'] = 21;
|
12 | relative['file'] = 0;
|
13 | relative['gopher'] = 70;
|
14 | relative['http'] = 80;
|
15 | relative['https'] = 443;
|
16 | relative['ws'] = 80;
|
17 | relative['wss'] = 443;
|
18 |
|
19 | var relativePathDotMapping = Object.create(null);
|
20 | relativePathDotMapping['%2e'] = '.';
|
21 | relativePathDotMapping['.%2e'] = '..';
|
22 | relativePathDotMapping['%2e.'] = '..';
|
23 | relativePathDotMapping['%2e%2e'] = '..';
|
24 |
|
25 | function isRelativeScheme(scheme) {
|
26 | return relative[scheme] !== undefined;
|
27 | }
|
28 |
|
29 | function invalid() {
|
30 | clear.call(this);
|
31 | this._isInvalid = true;
|
32 | }
|
33 |
|
34 | function IDNAToASCII(h) {
|
35 | if (h === '') {
|
36 | invalid.call(this);
|
37 | }
|
38 |
|
39 | return h.toLowerCase();
|
40 | }
|
41 |
|
42 | function percentEscape(c) {
|
43 | var unicode = c.charCodeAt(0);
|
44 | if (unicode > 0x20 &&
|
45 | unicode < 0x7F &&
|
46 |
|
47 | [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) === -1
|
48 | ) {
|
49 | return c;
|
50 | }
|
51 | return encodeURIComponent(c);
|
52 | }
|
53 |
|
54 | function percentEscapeQuery(c) {
|
55 |
|
56 |
|
57 |
|
58 | var unicode = c.charCodeAt(0);
|
59 | if (unicode > 0x20 &&
|
60 | unicode < 0x7F &&
|
61 |
|
62 | [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) === -1
|
63 | ) {
|
64 | return c;
|
65 | }
|
66 | return encodeURIComponent(c);
|
67 | }
|
68 |
|
69 | var EOF, ALPHA = /[a-zA-Z]/,
|
70 | ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/;
|
71 |
|
72 | function parse(input, stateOverride, base) {
|
73 | function err(message) {
|
74 | errors.push(message);
|
75 | }
|
76 |
|
77 | var state = stateOverride || 'scheme start',
|
78 | cursor = 0,
|
79 | buffer = '',
|
80 | seenAt = false,
|
81 | seenBracket = false,
|
82 | errors = [];
|
83 |
|
84 | loop: while ((input[cursor - 1] !== EOF || cursor === 0) &&
|
85 | !this._isInvalid) {
|
86 | var c = input[cursor];
|
87 | switch (state) {
|
88 | case 'scheme start':
|
89 | if (c && ALPHA.test(c)) {
|
90 | buffer += c.toLowerCase();
|
91 | state = 'scheme';
|
92 | } else if (!stateOverride) {
|
93 | buffer = '';
|
94 | state = 'no scheme';
|
95 | continue;
|
96 | } else {
|
97 | err('Invalid scheme.');
|
98 | break loop;
|
99 | }
|
100 | break;
|
101 |
|
102 | case 'scheme':
|
103 | if (c && ALPHANUMERIC.test(c)) {
|
104 | buffer += c.toLowerCase();
|
105 | } else if (c === ':') {
|
106 | this._scheme = buffer;
|
107 | buffer = '';
|
108 | if (stateOverride) {
|
109 | break loop;
|
110 | }
|
111 | if (isRelativeScheme(this._scheme)) {
|
112 | this._isRelative = true;
|
113 | }
|
114 | if (this._scheme === 'file') {
|
115 | state = 'relative';
|
116 | } else if (this._isRelative && base &&
|
117 | base._scheme === this._scheme) {
|
118 | state = 'relative or authority';
|
119 | } else if (this._isRelative) {
|
120 | state = 'authority first slash';
|
121 | } else {
|
122 | state = 'scheme data';
|
123 | }
|
124 | } else if (!stateOverride) {
|
125 | buffer = '';
|
126 | cursor = 0;
|
127 | state = 'no scheme';
|
128 | continue;
|
129 | } else if (c === EOF) {
|
130 | break loop;
|
131 | } else {
|
132 | err('Code point not allowed in scheme: ' + c);
|
133 | break loop;
|
134 | }
|
135 | break;
|
136 |
|
137 | case 'scheme data':
|
138 | if (c === '?') {
|
139 | this._query = '?';
|
140 | state = 'query';
|
141 | } else if (c === '#') {
|
142 | this._fragment = '#';
|
143 | state = 'fragment';
|
144 | } else {
|
145 |
|
146 | if (c !== EOF && c !== '\t' && c !== '\n' && c !== '\r') {
|
147 | this._schemeData += percentEscape(c);
|
148 | }
|
149 | }
|
150 | break;
|
151 |
|
152 | case 'no scheme':
|
153 | if (!base || !(isRelativeScheme(base._scheme))) {
|
154 | err('Missing scheme.');
|
155 | invalid.call(this);
|
156 | } else {
|
157 | state = 'relative';
|
158 | continue;
|
159 | }
|
160 | break;
|
161 |
|
162 | case 'relative or authority':
|
163 | if (c === '/' && input[cursor + 1] === '/') {
|
164 | state = 'authority ignore slashes';
|
165 | } else {
|
166 | err('Expected /, got: ' + c);
|
167 | state = 'relative';
|
168 | continue;
|
169 | }
|
170 | break;
|
171 |
|
172 | case 'relative':
|
173 | this._isRelative = true;
|
174 | if (this._scheme !== 'file') {
|
175 | this._scheme = base._scheme;
|
176 | }
|
177 | if (c === EOF) {
|
178 | this._host = base._host;
|
179 | this._port = base._port;
|
180 | this._path = base._path.slice();
|
181 | this._query = base._query;
|
182 | this._username = base._username;
|
183 | this._password = base._password;
|
184 | break loop;
|
185 | } else if (c === '/' || c === '\\') {
|
186 | if (c === '\\') {
|
187 | err('\\ is an invalid code point.');
|
188 | }
|
189 | state = 'relative slash';
|
190 | } else if (c === '?') {
|
191 | this._host = base._host;
|
192 | this._port = base._port;
|
193 | this._path = base._path.slice();
|
194 | this._query = '?';
|
195 | this._username = base._username;
|
196 | this._password = base._password;
|
197 | state = 'query';
|
198 | } else if (c === '#') {
|
199 | this._host = base._host;
|
200 | this._port = base._port;
|
201 | this._path = base._path.slice();
|
202 | this._query = base._query;
|
203 | this._fragment = '#';
|
204 | this._username = base._username;
|
205 | this._password = base._password;
|
206 | state = 'fragment';
|
207 | } else {
|
208 | var nextC = input[cursor + 1];
|
209 | var nextNextC = input[cursor + 2];
|
210 | if (this._scheme !== 'file' || !ALPHA.test(c) ||
|
211 | (nextC !== ':' && nextC !== '|') ||
|
212 | (nextNextC !== EOF && nextNextC !== '/' && nextNextC !== '\\' &&
|
213 | nextNextC !== '?' && nextNextC !== '#')) {
|
214 | this._host = base._host;
|
215 | this._port = base._port;
|
216 | this._username = base._username;
|
217 | this._password = base._password;
|
218 | this._path = base._path.slice();
|
219 | this._path.pop();
|
220 | }
|
221 | state = 'relative path';
|
222 | continue;
|
223 | }
|
224 | break;
|
225 |
|
226 | case 'relative slash':
|
227 | if (c === '/' || c === '\\') {
|
228 | if (c === '\\') {
|
229 | err('\\ is an invalid code point.');
|
230 | }
|
231 | if (this._scheme === 'file') {
|
232 | state = 'file host';
|
233 | } else {
|
234 | state = 'authority ignore slashes';
|
235 | }
|
236 | } else {
|
237 | if (this._scheme !== 'file') {
|
238 | this._host = base._host;
|
239 | this._port = base._port;
|
240 | this._username = base._username;
|
241 | this._password = base._password;
|
242 | }
|
243 | state = 'relative path';
|
244 | continue;
|
245 | }
|
246 | break;
|
247 |
|
248 | case 'authority first slash':
|
249 | if (c === '/') {
|
250 | state = 'authority second slash';
|
251 | } else {
|
252 | err('Expected \'/\', got: ' + c);
|
253 | state = 'authority ignore slashes';
|
254 | continue;
|
255 | }
|
256 | break;
|
257 |
|
258 | case 'authority second slash':
|
259 | state = 'authority ignore slashes';
|
260 | if (c !== '/') {
|
261 | err('Expected \'/\', got: ' + c);
|
262 | continue;
|
263 | }
|
264 | break;
|
265 |
|
266 | case 'authority ignore slashes':
|
267 | if (c !== '/' && c !== '\\') {
|
268 | state = 'authority';
|
269 | continue;
|
270 | } else {
|
271 | err('Expected authority, got: ' + c);
|
272 | }
|
273 | break;
|
274 |
|
275 | case 'authority':
|
276 | if (c === '@') {
|
277 | if (seenAt) {
|
278 | err('@ already seen.');
|
279 | buffer += '%40';
|
280 | }
|
281 | seenAt = true;
|
282 | for (var i = 0; i < buffer.length; i++) {
|
283 | var cp = buffer[i];
|
284 | if (cp === '\t' || cp === '\n' || cp === '\r') {
|
285 | err('Invalid whitespace in authority.');
|
286 | continue;
|
287 | }
|
288 |
|
289 | if (cp === ':' && this._password === null) {
|
290 | this._password = '';
|
291 | continue;
|
292 | }
|
293 | var tempC = percentEscape(cp);
|
294 | if (this._password !== null) {
|
295 | this._password += tempC;
|
296 | } else {
|
297 | this._username += tempC;
|
298 | }
|
299 | }
|
300 | buffer = '';
|
301 | } else if (c === EOF || c === '/' || c === '\\' ||
|
302 | c === '?' || c === '#') {
|
303 | cursor -= buffer.length;
|
304 | buffer = '';
|
305 | state = 'host';
|
306 | continue;
|
307 | } else {
|
308 | buffer += c;
|
309 | }
|
310 | break;
|
311 |
|
312 | case 'file host':
|
313 | if (c === EOF || c === '/' || c === '\\' || c === '?' || c === '#') {
|
314 | if (buffer.length === 2 && ALPHA.test(buffer[0]) &&
|
315 | (buffer[1] === ':' || buffer[1] === '|')) {
|
316 | state = 'relative path';
|
317 | } else if (buffer.length === 0) {
|
318 | state = 'relative path start';
|
319 | } else {
|
320 | this._host = IDNAToASCII.call(this, buffer);
|
321 | buffer = '';
|
322 | state = 'relative path start';
|
323 | }
|
324 | continue;
|
325 | } else if (c === '\t' || c === '\n' || c === '\r') {
|
326 | err('Invalid whitespace in file host.');
|
327 | } else {
|
328 | buffer += c;
|
329 | }
|
330 | break;
|
331 |
|
332 | case 'host':
|
333 | case 'hostname':
|
334 | if (c === ':' && !seenBracket) {
|
335 |
|
336 | this._host = IDNAToASCII.call(this, buffer);
|
337 | buffer = '';
|
338 | state = 'port';
|
339 | if (stateOverride === 'hostname') {
|
340 | break loop;
|
341 | }
|
342 | } else if (c === EOF || c === '/' ||
|
343 | c === '\\' || c === '?' || c === '#') {
|
344 | this._host = IDNAToASCII.call(this, buffer);
|
345 | buffer = '';
|
346 | state = 'relative path start';
|
347 | if (stateOverride) {
|
348 | break loop;
|
349 | }
|
350 | continue;
|
351 | } else if (c !== '\t' && c !== '\n' && c !== '\r') {
|
352 | if (c === '[') {
|
353 | seenBracket = true;
|
354 | } else if (c === ']') {
|
355 | seenBracket = false;
|
356 | }
|
357 | buffer += c;
|
358 | } else {
|
359 | err('Invalid code point in host/hostname: ' + c);
|
360 | }
|
361 | break;
|
362 |
|
363 | case 'port':
|
364 | if (/[0-9]/.test(c)) {
|
365 | buffer += c;
|
366 | } else if (c === EOF || c === '/' || c === '\\' ||
|
367 | c === '?' || c === '#' || stateOverride) {
|
368 | if (buffer !== '') {
|
369 | var temp = parseInt(buffer, 10);
|
370 | if (temp !== relative[this._scheme]) {
|
371 | this._port = temp + '';
|
372 | }
|
373 | buffer = '';
|
374 | }
|
375 | if (stateOverride) {
|
376 | break loop;
|
377 | }
|
378 | state = 'relative path start';
|
379 | continue;
|
380 | } else if (c === '\t' || c === '\n' || c === '\r') {
|
381 | err('Invalid code point in port: ' + c);
|
382 | } else {
|
383 | invalid.call(this);
|
384 | }
|
385 | break;
|
386 |
|
387 | case 'relative path start':
|
388 | if (c === '\\') {
|
389 | err('\'\\\' not allowed in path.');
|
390 | }
|
391 | state = 'relative path';
|
392 | if (c !== '/' && c !== '\\') {
|
393 | continue;
|
394 | }
|
395 | break;
|
396 |
|
397 | case 'relative path':
|
398 | if (c === EOF || c === '/' || c === '\\' ||
|
399 | (!stateOverride && (c === '?' || c === '#'))) {
|
400 | if (c === '\\') {
|
401 | err('\\ not allowed in relative path.');
|
402 | }
|
403 | var tmp;
|
404 | if ((tmp = relativePathDotMapping[buffer.toLowerCase()])) {
|
405 | buffer = tmp;
|
406 | }
|
407 | if (buffer === '..') {
|
408 | this._path.pop();
|
409 | if (c !== '/' && c !== '\\') {
|
410 | this._path.push('');
|
411 | }
|
412 | } else if (buffer === '.' && c !== '/' && c !== '\\') {
|
413 | this._path.push('');
|
414 | } else if (buffer !== '.') {
|
415 | if (this._scheme === 'file' && this._path.length === 0 &&
|
416 | buffer.length === 2 && ALPHA.test(buffer[0]) &&
|
417 | buffer[1] === '|') {
|
418 | buffer = buffer[0] + ':';
|
419 | }
|
420 | this._path.push(buffer);
|
421 | }
|
422 | buffer = '';
|
423 | if (c === '?') {
|
424 | this._query = '?';
|
425 | state = 'query';
|
426 | } else if (c === '#') {
|
427 | this._fragment = '#';
|
428 | state = 'fragment';
|
429 | }
|
430 | } else if (c !== '\t' && c !== '\n' && c !== '\r') {
|
431 | buffer += percentEscape(c);
|
432 | }
|
433 | break;
|
434 |
|
435 | case 'query':
|
436 | if (!stateOverride && c === '#') {
|
437 | this._fragment = '#';
|
438 | state = 'fragment';
|
439 | } else if (c !== EOF && c !== '\t' && c !== '\n' && c !== '\r') {
|
440 | this._query += percentEscapeQuery(c);
|
441 | }
|
442 | break;
|
443 |
|
444 | case 'fragment':
|
445 | if (c !== EOF && c !== '\t' && c !== '\n' && c !== '\r') {
|
446 | this._fragment += c;
|
447 | }
|
448 | break;
|
449 | }
|
450 |
|
451 | cursor++;
|
452 | }
|
453 | }
|
454 |
|
455 | function clear() {
|
456 | this._scheme = '';
|
457 | this._schemeData = '';
|
458 | this._username = '';
|
459 | this._password = null;
|
460 | this._host = '';
|
461 | this._port = '';
|
462 | this._path = [];
|
463 | this._query = '';
|
464 | this._fragment = '';
|
465 | this._isInvalid = false;
|
466 | this._isRelative = false;
|
467 | }
|
468 |
|
469 |
|
470 |
|
471 | function JURL(url, base /* , encoding */) {
|
472 | if (base !== undefined && !(base instanceof JURL)) {
|
473 | base = new JURL(String(base));
|
474 | }
|
475 |
|
476 | this._url = url;
|
477 | clear.call(this);
|
478 |
|
479 | var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, '');
|
480 |
|
481 |
|
482 | parse.call(this, input, null, base);
|
483 | }
|
484 |
|
485 | JURL.prototype = {
|
486 | toString() {
|
487 | return this.href;
|
488 | },
|
489 | get href() {
|
490 | if (this._isInvalid) {
|
491 | return this._url;
|
492 | }
|
493 | var authority = '';
|
494 | if (this._username !== '' || this._password !== null) {
|
495 | authority = this._username +
|
496 | (this._password !== null ? ':' + this._password : '') + '@';
|
497 | }
|
498 |
|
499 | return this.protocol +
|
500 | (this._isRelative ? '//' + authority + this.host : '') +
|
501 | this.pathname + this._query + this._fragment;
|
502 | },
|
503 |
|
504 |
|
505 | set href(value) {
|
506 | clear.call(this);
|
507 | parse.call(this, value);
|
508 | },
|
509 |
|
510 | get protocol() {
|
511 | return this._scheme + ':';
|
512 | },
|
513 | set protocol(value) {
|
514 | if (this._isInvalid) {
|
515 | return;
|
516 | }
|
517 | parse.call(this, value + ':', 'scheme start');
|
518 | },
|
519 |
|
520 | get host() {
|
521 | return this._isInvalid ? '' : this._port ?
|
522 | this._host + ':' + this._port : this._host;
|
523 | },
|
524 | set host(value) {
|
525 | if (this._isInvalid || !this._isRelative) {
|
526 | return;
|
527 | }
|
528 | parse.call(this, value, 'host');
|
529 | },
|
530 |
|
531 | get hostname() {
|
532 | return this._host;
|
533 | },
|
534 | set hostname(value) {
|
535 | if (this._isInvalid || !this._isRelative) {
|
536 | return;
|
537 | }
|
538 | parse.call(this, value, 'hostname');
|
539 | },
|
540 |
|
541 | get port() {
|
542 | return this._port;
|
543 | },
|
544 | set port(value) {
|
545 | if (this._isInvalid || !this._isRelative) {
|
546 | return;
|
547 | }
|
548 | parse.call(this, value, 'port');
|
549 | },
|
550 |
|
551 | get pathname() {
|
552 | return this._isInvalid ? '' : this._isRelative ?
|
553 | '/' + this._path.join('/') : this._schemeData;
|
554 | },
|
555 | set pathname(value) {
|
556 | if (this._isInvalid || !this._isRelative) {
|
557 | return;
|
558 | }
|
559 | this._path = [];
|
560 | parse.call(this, value, 'relative path start');
|
561 | },
|
562 |
|
563 | get search() {
|
564 | return this._isInvalid || !this._query || this._query === '?' ?
|
565 | '' : this._query;
|
566 | },
|
567 | set search(value) {
|
568 | if (this._isInvalid || !this._isRelative) {
|
569 | return;
|
570 | }
|
571 | this._query = '?';
|
572 | if (value[0] === '?') {
|
573 | value = value.slice(1);
|
574 | }
|
575 | parse.call(this, value, 'query');
|
576 | },
|
577 |
|
578 | get hash() {
|
579 | return this._isInvalid || !this._fragment || this._fragment === '#' ?
|
580 | '' : this._fragment;
|
581 | },
|
582 | set hash(value) {
|
583 | if (this._isInvalid) {
|
584 | return;
|
585 | }
|
586 | this._fragment = '#';
|
587 | if (value[0] === '#') {
|
588 | value = value.slice(1);
|
589 | }
|
590 | parse.call(this, value, 'fragment');
|
591 | },
|
592 |
|
593 | get origin() {
|
594 | var host;
|
595 | if (this._isInvalid || !this._scheme) {
|
596 | return '';
|
597 | }
|
598 |
|
599 |
|
600 |
|
601 |
|
602 |
|
603 | switch (this._scheme) {
|
604 | case 'data':
|
605 | case 'file':
|
606 | case 'javascript':
|
607 | case 'mailto':
|
608 | return 'null';
|
609 | case 'blob':
|
610 |
|
611 | try {
|
612 | return new JURL(this._schemeData).origin || 'null';
|
613 | } catch (_) {
|
614 |
|
615 | }
|
616 | return 'null';
|
617 | }
|
618 | host = this.host;
|
619 | if (!host) {
|
620 | return '';
|
621 | }
|
622 | return this._scheme + '://' + host;
|
623 | },
|
624 | };
|
625 |
|
626 | exports.URL = JURL;
|
627 | })();
|