Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/uri/uri.js
4122 views
1
/**
2
* @license
3
* Copyright The Closure Library Authors.
4
* SPDX-License-Identifier: Apache-2.0
5
*/
6
7
/**
8
* @fileoverview Class for parsing and formatting URIs.
9
*
10
* This package is deprecated in favour of the Closure URL package (goog.url)
11
* when manipulating URIs for use by a browser. This package uses regular
12
* expressions to parse a potential URI which can fall out of sync with how a
13
* browser will actually interpret the URI. See
14
* `goog.uri.utils.setUrlPackageSupportLoggingHandler` for one way to identify
15
* URIs that should instead be parsed using the URL package.
16
*
17
* Use goog.Uri(string) to parse a URI string. Use goog.Uri.create(...) to
18
* create a new instance of the goog.Uri object from Uri parts.
19
*
20
* e.g: <code>var myUri = new goog.Uri(window.location);</code>
21
*
22
* Implements RFC 3986 for parsing/formatting URIs.
23
* http://www.ietf.org/rfc/rfc3986.txt
24
*
25
* Some changes have been made to the interface (more like .NETs), though the
26
* internal representation is now of un-encoded parts, this will change the
27
* behavior slightly.
28
*/
29
30
goog.provide('goog.Uri');
31
goog.provide('goog.Uri.QueryData');
32
33
goog.require('goog.array');
34
goog.require('goog.asserts');
35
goog.require('goog.collections.maps');
36
goog.require('goog.string');
37
goog.require('goog.structs');
38
goog.require('goog.uri.utils');
39
goog.require('goog.uri.utils.ComponentIndex');
40
goog.require('goog.uri.utils.StandardQueryParam');
41
42
43
44
/**
45
* This class contains setters and getters for the parts of the URI.
46
* The <code>getXyz</code>/<code>setXyz</code> methods return the decoded part
47
* -- so<code>goog.Uri.parse('/foo%20bar').getPath()</code> will return the
48
* decoded path, <code>/foo bar</code>.
49
*
50
* Reserved characters (see RFC 3986 section 2.2) can be present in
51
* their percent-encoded form in scheme, domain, and path URI components and
52
* will not be auto-decoded. For example:
53
* <code>goog.Uri.parse('rel%61tive/path%2fto/resource').getPath()</code> will
54
* return <code>relative/path%2fto/resource</code>.
55
*
56
* The constructor accepts an optional unparsed, raw URI string. The parser
57
* is relaxed, so special characters that aren't escaped but don't cause
58
* ambiguities will not cause parse failures.
59
*
60
* All setters return <code>this</code> and so may be chained, a la
61
* <code>goog.Uri.parse('/foo').setFragment('part').toString()</code>.
62
*
63
* @param {*=} opt_uri Optional string URI to parse
64
* (use goog.Uri.create() to create a URI from parts), or if
65
* a goog.Uri is passed, a clone is created.
66
* @param {boolean=} opt_ignoreCase If true, #getParameterValue will ignore
67
* the case of the parameter name.
68
*
69
* @throws URIError If opt_uri is provided and URI is malformed (that is,
70
* if decodeURIComponent fails on any of the URI components).
71
* @constructor
72
* @struct
73
*/
74
goog.Uri = function(opt_uri, opt_ignoreCase) {
75
'use strict';
76
/**
77
* Scheme such as "http".
78
* @private {string}
79
*/
80
this.scheme_ = '';
81
82
/**
83
* User credentials in the form "username:password".
84
* @private {string}
85
*/
86
this.userInfo_ = '';
87
88
/**
89
* Domain part, e.g. "www.google.com".
90
* @private {string}
91
*/
92
this.domain_ = '';
93
94
/**
95
* Port, e.g. 8080.
96
* @private {?number}
97
*/
98
this.port_ = null;
99
100
/**
101
* Path, e.g. "/tests/img.png".
102
* @private {string}
103
*/
104
this.path_ = '';
105
106
/**
107
* The fragment without the #.
108
* @private {string}
109
*/
110
this.fragment_ = '';
111
112
/**
113
* Whether or not this Uri should be treated as Read Only.
114
* @private {boolean}
115
*/
116
this.isReadOnly_ = false;
117
118
/**
119
* Whether or not to ignore case when comparing query params.
120
* @private {boolean}
121
*/
122
this.ignoreCase_ = false;
123
124
/**
125
* Object representing query data.
126
* @private {!goog.Uri.QueryData}
127
*/
128
this.queryData_;
129
130
// Parse in the uri string
131
var m;
132
if (opt_uri instanceof goog.Uri) {
133
this.ignoreCase_ = (opt_ignoreCase !== undefined) ? opt_ignoreCase :
134
opt_uri.getIgnoreCase();
135
this.setScheme(opt_uri.getScheme());
136
this.setUserInfo(opt_uri.getUserInfo());
137
this.setDomain(opt_uri.getDomain());
138
this.setPort(opt_uri.getPort());
139
this.setPath(opt_uri.getPath());
140
this.setQueryData(opt_uri.getQueryData().clone());
141
this.setFragment(opt_uri.getFragment());
142
} else if (opt_uri && (m = goog.uri.utils.split(String(opt_uri)))) {
143
this.ignoreCase_ = !!opt_ignoreCase;
144
145
// Set the parts -- decoding as we do so.
146
// COMPATIBILITY NOTE - In IE, unmatched fields may be empty strings,
147
// whereas in other browsers they will be undefined.
148
this.setScheme(m[goog.uri.utils.ComponentIndex.SCHEME] || '', true);
149
this.setUserInfo(m[goog.uri.utils.ComponentIndex.USER_INFO] || '', true);
150
this.setDomain(m[goog.uri.utils.ComponentIndex.DOMAIN] || '', true);
151
this.setPort(m[goog.uri.utils.ComponentIndex.PORT]);
152
this.setPath(m[goog.uri.utils.ComponentIndex.PATH] || '', true);
153
this.setQueryData(m[goog.uri.utils.ComponentIndex.QUERY_DATA] || '', true);
154
this.setFragment(m[goog.uri.utils.ComponentIndex.FRAGMENT] || '', true);
155
156
} else {
157
this.ignoreCase_ = !!opt_ignoreCase;
158
this.queryData_ = new goog.Uri.QueryData(null, this.ignoreCase_);
159
}
160
};
161
162
163
/**
164
* Parameter name added to stop caching.
165
* @type {string}
166
*/
167
goog.Uri.RANDOM_PARAM = goog.uri.utils.StandardQueryParam.RANDOM;
168
169
170
/**
171
* @return {string} The string form of the url.
172
* @override
173
*/
174
goog.Uri.prototype.toString = function() {
175
'use strict';
176
var out = [];
177
178
var scheme = this.getScheme();
179
if (scheme) {
180
out.push(
181
goog.Uri.encodeSpecialChars_(
182
scheme, goog.Uri.reDisallowedInSchemeOrUserInfo_, true),
183
':');
184
}
185
186
var domain = this.getDomain();
187
if (domain || scheme == 'file') {
188
out.push('//');
189
190
var userInfo = this.getUserInfo();
191
if (userInfo) {
192
out.push(
193
goog.Uri.encodeSpecialChars_(
194
userInfo, goog.Uri.reDisallowedInSchemeOrUserInfo_, true),
195
'@');
196
}
197
198
out.push(goog.Uri.removeDoubleEncoding_(goog.string.urlEncode(domain)));
199
200
var port = this.getPort();
201
if (port != null) {
202
out.push(':', String(port));
203
}
204
}
205
206
var path = this.getPath();
207
if (path) {
208
if (this.hasDomain() && path.charAt(0) != '/') {
209
out.push('/');
210
}
211
out.push(goog.Uri.encodeSpecialChars_(
212
path,
213
path.charAt(0) == '/' ? goog.Uri.reDisallowedInAbsolutePath_ :
214
goog.Uri.reDisallowedInRelativePath_,
215
true));
216
}
217
218
var query = this.getEncodedQuery();
219
if (query) {
220
out.push('?', query);
221
}
222
223
var fragment = this.getFragment();
224
if (fragment) {
225
out.push(
226
'#',
227
goog.Uri.encodeSpecialChars_(
228
fragment, goog.Uri.reDisallowedInFragment_));
229
}
230
return out.join('');
231
};
232
233
234
/**
235
* Resolves the given relative URI (a goog.Uri object), using the URI
236
* represented by this instance as the base URI.
237
*
238
* There are several kinds of relative URIs:<br>
239
* 1. foo - replaces the last part of the path, the whole query and fragment<br>
240
* 2. /foo - replaces the path, the query and fragment<br>
241
* 3. //foo - replaces everything from the domain on. foo is a domain name<br>
242
* 4. ?foo - replace the query and fragment<br>
243
* 5. #foo - replace the fragment only
244
*
245
* Additionally, if relative URI has a non-empty path, all ".." and "."
246
* segments will be resolved, as described in RFC 3986.
247
*
248
* @param {!goog.Uri} relativeUri The relative URI to resolve.
249
* @return {!goog.Uri} The resolved URI.
250
*/
251
goog.Uri.prototype.resolve = function(relativeUri) {
252
'use strict';
253
var absoluteUri = this.clone();
254
255
// we satisfy these conditions by looking for the first part of relativeUri
256
// that is not blank and applying defaults to the rest
257
258
var overridden = relativeUri.hasScheme();
259
260
if (overridden) {
261
absoluteUri.setScheme(relativeUri.getScheme());
262
} else {
263
overridden = relativeUri.hasUserInfo();
264
}
265
266
if (overridden) {
267
absoluteUri.setUserInfo(relativeUri.getUserInfo());
268
} else {
269
overridden = relativeUri.hasDomain();
270
}
271
272
if (overridden) {
273
absoluteUri.setDomain(relativeUri.getDomain());
274
} else {
275
overridden = relativeUri.hasPort();
276
}
277
278
var path = relativeUri.getPath();
279
if (overridden) {
280
absoluteUri.setPort(relativeUri.getPort());
281
} else {
282
overridden = relativeUri.hasPath();
283
if (overridden) {
284
// resolve path properly
285
if (path.charAt(0) != '/') {
286
// path is relative
287
if (this.hasDomain() && !this.hasPath()) {
288
// RFC 3986, section 5.2.3, case 1
289
path = '/' + path;
290
} else {
291
// RFC 3986, section 5.2.3, case 2
292
var lastSlashIndex = absoluteUri.getPath().lastIndexOf('/');
293
if (lastSlashIndex != -1) {
294
path = absoluteUri.getPath().slice(0, lastSlashIndex + 1) + path;
295
}
296
}
297
}
298
path = goog.Uri.removeDotSegments(path);
299
}
300
}
301
302
if (overridden) {
303
absoluteUri.setPath(path);
304
} else {
305
overridden = relativeUri.hasQuery();
306
}
307
308
if (overridden) {
309
absoluteUri.setQueryData(relativeUri.getQueryData().clone());
310
} else {
311
overridden = relativeUri.hasFragment();
312
}
313
314
if (overridden) {
315
absoluteUri.setFragment(relativeUri.getFragment());
316
}
317
318
return absoluteUri;
319
};
320
321
322
/**
323
* Clones the URI instance.
324
* @return {!goog.Uri} New instance of the URI object.
325
*/
326
goog.Uri.prototype.clone = function() {
327
'use strict';
328
return new goog.Uri(this);
329
};
330
331
332
/**
333
* @return {string} The encoded scheme/protocol for the URI.
334
*/
335
goog.Uri.prototype.getScheme = function() {
336
'use strict';
337
return this.scheme_;
338
};
339
340
341
/**
342
* Sets the scheme/protocol.
343
* @throws URIError If opt_decode is true and newScheme is malformed (that is,
344
* if decodeURIComponent fails).
345
* @param {string} newScheme New scheme value.
346
* @param {boolean=} opt_decode Optional param for whether to decode new value.
347
* @return {!goog.Uri} Reference to this URI object.
348
*/
349
goog.Uri.prototype.setScheme = function(newScheme, opt_decode) {
350
'use strict';
351
this.enforceReadOnly();
352
this.scheme_ =
353
opt_decode ? goog.Uri.decodeOrEmpty_(newScheme, true) : newScheme;
354
355
// remove an : at the end of the scheme so somebody can pass in
356
// window.location.protocol
357
if (this.scheme_) {
358
this.scheme_ = this.scheme_.replace(/:$/, '');
359
}
360
return this;
361
};
362
363
364
/**
365
* @return {boolean} Whether the scheme has been set.
366
*/
367
goog.Uri.prototype.hasScheme = function() {
368
'use strict';
369
return !!this.scheme_;
370
};
371
372
373
/**
374
* @return {string} The decoded user info.
375
*/
376
goog.Uri.prototype.getUserInfo = function() {
377
'use strict';
378
return this.userInfo_;
379
};
380
381
382
/**
383
* Sets the userInfo.
384
* @throws URIError If opt_decode is true and newUserInfo is malformed (that is,
385
* if decodeURIComponent fails).
386
* @param {string} newUserInfo New userInfo value.
387
* @param {boolean=} opt_decode Optional param for whether to decode new value.
388
* @return {!goog.Uri} Reference to this URI object.
389
*/
390
goog.Uri.prototype.setUserInfo = function(newUserInfo, opt_decode) {
391
'use strict';
392
this.enforceReadOnly();
393
this.userInfo_ =
394
opt_decode ? goog.Uri.decodeOrEmpty_(newUserInfo) : newUserInfo;
395
return this;
396
};
397
398
399
/**
400
* @return {boolean} Whether the user info has been set.
401
*/
402
goog.Uri.prototype.hasUserInfo = function() {
403
'use strict';
404
return !!this.userInfo_;
405
};
406
407
408
/**
409
* @return {string} The decoded domain.
410
*/
411
goog.Uri.prototype.getDomain = function() {
412
'use strict';
413
return this.domain_;
414
};
415
416
417
/**
418
* Sets the domain.
419
* @throws URIError If opt_decode is true and newDomain is malformed (that is,
420
* if decodeURIComponent fails).
421
* @param {string} newDomain New domain value.
422
* @param {boolean=} opt_decode Optional param for whether to decode new value.
423
* @return {!goog.Uri} Reference to this URI object.
424
*/
425
goog.Uri.prototype.setDomain = function(newDomain, opt_decode) {
426
'use strict';
427
this.enforceReadOnly();
428
this.domain_ =
429
opt_decode ? goog.Uri.decodeOrEmpty_(newDomain, true) : newDomain;
430
return this;
431
};
432
433
434
/**
435
* @return {boolean} Whether the domain has been set.
436
*/
437
goog.Uri.prototype.hasDomain = function() {
438
'use strict';
439
return !!this.domain_;
440
};
441
442
443
/**
444
* @return {?number} The port number.
445
*/
446
goog.Uri.prototype.getPort = function() {
447
'use strict';
448
return this.port_;
449
};
450
451
452
/**
453
* Sets the port number.
454
* @param {*} newPort Port number. Will be explicitly casted to a number.
455
* @return {!goog.Uri} Reference to this URI object.
456
*/
457
goog.Uri.prototype.setPort = function(newPort) {
458
'use strict';
459
this.enforceReadOnly();
460
461
if (newPort) {
462
newPort = Number(newPort);
463
if (isNaN(newPort) || newPort < 0) {
464
throw new Error('Bad port number ' + newPort);
465
}
466
this.port_ = newPort;
467
} else {
468
this.port_ = null;
469
}
470
471
return this;
472
};
473
474
475
/**
476
* @return {boolean} Whether the port has been set.
477
*/
478
goog.Uri.prototype.hasPort = function() {
479
'use strict';
480
return this.port_ != null;
481
};
482
483
484
/**
485
* @return {string} The decoded path.
486
*/
487
goog.Uri.prototype.getPath = function() {
488
'use strict';
489
return this.path_;
490
};
491
492
493
/**
494
* Sets the path.
495
* @throws URIError If opt_decode is true and newPath is malformed (that is,
496
* if decodeURIComponent fails).
497
* @param {string} newPath New path value.
498
* @param {boolean=} opt_decode Optional param for whether to decode new value.
499
* @return {!goog.Uri} Reference to this URI object.
500
*/
501
goog.Uri.prototype.setPath = function(newPath, opt_decode) {
502
'use strict';
503
this.enforceReadOnly();
504
this.path_ = opt_decode ? goog.Uri.decodeOrEmpty_(newPath, true) : newPath;
505
return this;
506
};
507
508
509
/**
510
* @return {boolean} Whether the path has been set.
511
*/
512
goog.Uri.prototype.hasPath = function() {
513
'use strict';
514
return !!this.path_;
515
};
516
517
518
/**
519
* @return {boolean} Whether the query string has been set.
520
*/
521
goog.Uri.prototype.hasQuery = function() {
522
'use strict';
523
return this.queryData_.toString() !== '';
524
};
525
526
527
/**
528
* Sets the query data.
529
* @param {goog.Uri.QueryData|string|undefined} queryData QueryData object.
530
* @param {boolean=} opt_decode Optional param for whether to decode new value.
531
* Applies only if queryData is a string.
532
* @return {!goog.Uri} Reference to this URI object.
533
*/
534
goog.Uri.prototype.setQueryData = function(queryData, opt_decode) {
535
'use strict';
536
this.enforceReadOnly();
537
538
if (queryData instanceof goog.Uri.QueryData) {
539
this.queryData_ = queryData;
540
this.queryData_.setIgnoreCase(this.ignoreCase_);
541
} else {
542
if (!opt_decode) {
543
// QueryData accepts encoded query string, so encode it if
544
// opt_decode flag is not true.
545
queryData = goog.Uri.encodeSpecialChars_(
546
queryData, goog.Uri.reDisallowedInQuery_);
547
}
548
this.queryData_ = new goog.Uri.QueryData(queryData, this.ignoreCase_);
549
}
550
551
return this;
552
};
553
554
555
/**
556
* Sets the URI query.
557
* @param {string} newQuery New query value.
558
* @param {boolean=} opt_decode Optional param for whether to decode new value.
559
* @return {!goog.Uri} Reference to this URI object.
560
*/
561
goog.Uri.prototype.setQuery = function(newQuery, opt_decode) {
562
'use strict';
563
return this.setQueryData(newQuery, opt_decode);
564
};
565
566
567
/**
568
* @return {string} The encoded URI query, not including the ?.
569
*/
570
goog.Uri.prototype.getEncodedQuery = function() {
571
'use strict';
572
return this.queryData_.toString();
573
};
574
575
576
/**
577
* @return {string} The decoded URI query, not including the ?.
578
*/
579
goog.Uri.prototype.getDecodedQuery = function() {
580
'use strict';
581
return this.queryData_.toDecodedString();
582
};
583
584
585
/**
586
* Returns the query data.
587
* @return {!goog.Uri.QueryData} QueryData object.
588
*/
589
goog.Uri.prototype.getQueryData = function() {
590
'use strict';
591
return this.queryData_;
592
};
593
594
595
/**
596
* @return {string} The encoded URI query, not including the ?.
597
*
598
* Warning: This method, unlike other getter methods, returns encoded
599
* value, instead of decoded one.
600
*/
601
goog.Uri.prototype.getQuery = function() {
602
'use strict';
603
return this.getEncodedQuery();
604
};
605
606
607
/**
608
* Sets the value of the named query parameters, clearing previous values for
609
* that key.
610
*
611
* @param {string} key The parameter to set.
612
* @param {*} value The new value. Value does not need to be encoded.
613
* @return {!goog.Uri} Reference to this URI object.
614
*/
615
goog.Uri.prototype.setParameterValue = function(key, value) {
616
'use strict';
617
this.enforceReadOnly();
618
this.queryData_.set(key, value);
619
return this;
620
};
621
622
623
/**
624
* Sets the values of the named query parameters, clearing previous values for
625
* that key. Not new values will currently be moved to the end of the query
626
* string.
627
*
628
* So, <code>goog.Uri.parse('foo?a=b&c=d&e=f').setParameterValues('c', ['new'])
629
* </code> yields <tt>foo?a=b&e=f&c=new</tt>.</p>
630
*
631
* @param {string} key The parameter to set.
632
* @param {*} values The new values. If values is a single
633
* string then it will be treated as the sole value. Values do not need to
634
* be encoded.
635
* @return {!goog.Uri} Reference to this URI object.
636
*/
637
goog.Uri.prototype.setParameterValues = function(key, values) {
638
'use strict';
639
this.enforceReadOnly();
640
641
if (!Array.isArray(values)) {
642
values = [String(values)];
643
}
644
645
this.queryData_.setValues(key, values);
646
647
return this;
648
};
649
650
651
/**
652
* Returns the value<b>s</b> for a given cgi parameter as a list of decoded
653
* query parameter values.
654
* @param {string} name The parameter to get values for.
655
* @return {!Array<?>} The values for a given cgi parameter as a list of
656
* decoded query parameter values.
657
*/
658
goog.Uri.prototype.getParameterValues = function(name) {
659
'use strict';
660
return this.queryData_.getValues(name);
661
};
662
663
664
/**
665
* Returns the first value for a given cgi parameter or undefined if the given
666
* parameter name does not appear in the query string.
667
* @param {string} paramName Unescaped parameter name.
668
* @return {string|undefined} The first value for a given cgi parameter or
669
* undefined if the given parameter name does not appear in the query
670
* string.
671
*/
672
goog.Uri.prototype.getParameterValue = function(paramName) {
673
'use strict';
674
return /** @type {string|undefined} */ (this.queryData_.get(paramName));
675
};
676
677
678
/**
679
* @return {string} The URI fragment, not including the #.
680
*/
681
goog.Uri.prototype.getFragment = function() {
682
'use strict';
683
return this.fragment_;
684
};
685
686
687
/**
688
* Sets the URI fragment.
689
* @throws URIError If opt_decode is true and newFragment is malformed (that is,
690
* if decodeURIComponent fails).
691
* @param {string} newFragment New fragment value.
692
* @param {boolean=} opt_decode Optional param for whether to decode new value.
693
* @return {!goog.Uri} Reference to this URI object.
694
*/
695
goog.Uri.prototype.setFragment = function(newFragment, opt_decode) {
696
'use strict';
697
this.enforceReadOnly();
698
this.fragment_ =
699
opt_decode ? goog.Uri.decodeOrEmpty_(newFragment) : newFragment;
700
return this;
701
};
702
703
704
/**
705
* @return {boolean} Whether the URI has a fragment set.
706
*/
707
goog.Uri.prototype.hasFragment = function() {
708
'use strict';
709
return !!this.fragment_;
710
};
711
712
713
/**
714
* Returns true if this has the same domain as that of uri2.
715
* @param {!goog.Uri} uri2 The URI object to compare to.
716
* @return {boolean} true if same domain; false otherwise.
717
*/
718
goog.Uri.prototype.hasSameDomainAs = function(uri2) {
719
'use strict';
720
return ((!this.hasDomain() && !uri2.hasDomain()) ||
721
this.getDomain() == uri2.getDomain()) &&
722
((!this.hasPort() && !uri2.hasPort()) ||
723
this.getPort() == uri2.getPort());
724
};
725
726
727
/**
728
* Adds a random parameter to the Uri.
729
* @return {!goog.Uri} Reference to this Uri object.
730
*/
731
goog.Uri.prototype.makeUnique = function() {
732
'use strict';
733
this.enforceReadOnly();
734
this.setParameterValue(goog.Uri.RANDOM_PARAM, goog.string.getRandomString());
735
736
return this;
737
};
738
739
740
/**
741
* Removes the named query parameter.
742
*
743
* @param {string} key The parameter to remove.
744
* @return {!goog.Uri} Reference to this URI object.
745
*/
746
goog.Uri.prototype.removeParameter = function(key) {
747
'use strict';
748
this.enforceReadOnly();
749
this.queryData_.remove(key);
750
return this;
751
};
752
753
754
/**
755
* Sets whether Uri is read only. If this goog.Uri is read-only,
756
* enforceReadOnly_ will be called at the start of any function that may modify
757
* this Uri.
758
* @param {boolean} isReadOnly whether this goog.Uri should be read only.
759
* @return {!goog.Uri} Reference to this Uri object.
760
*/
761
goog.Uri.prototype.setReadOnly = function(isReadOnly) {
762
'use strict';
763
this.isReadOnly_ = isReadOnly;
764
return this;
765
};
766
767
768
/**
769
* @return {boolean} Whether the URI is read only.
770
*/
771
goog.Uri.prototype.isReadOnly = function() {
772
'use strict';
773
return this.isReadOnly_;
774
};
775
776
777
/**
778
* Checks if this Uri has been marked as read only, and if so, throws an error.
779
* This should be called whenever any modifying function is called.
780
*/
781
goog.Uri.prototype.enforceReadOnly = function() {
782
'use strict';
783
if (this.isReadOnly_) {
784
throw new Error('Tried to modify a read-only Uri');
785
}
786
};
787
788
789
/**
790
* Sets whether to ignore case.
791
* NOTE: If there are already key/value pairs in the QueryData, and
792
* ignoreCase_ is set to false, the keys will all be lower-cased.
793
* @param {boolean} ignoreCase whether this goog.Uri should ignore case.
794
* @return {!goog.Uri} Reference to this Uri object.
795
*/
796
goog.Uri.prototype.setIgnoreCase = function(ignoreCase) {
797
'use strict';
798
this.ignoreCase_ = ignoreCase;
799
if (this.queryData_) {
800
this.queryData_.setIgnoreCase(ignoreCase);
801
}
802
return this;
803
};
804
805
806
/**
807
* @return {boolean} Whether to ignore case.
808
*/
809
goog.Uri.prototype.getIgnoreCase = function() {
810
'use strict';
811
return this.ignoreCase_;
812
};
813
814
815
//==============================================================================
816
// Static members
817
//==============================================================================
818
819
820
/**
821
* Creates a uri from the string form. Basically an alias of new goog.Uri().
822
* If a Uri object is passed to parse then it will return a clone of the object.
823
*
824
* @throws URIError If parsing the URI is malformed. The passed URI components
825
* should all be parseable by decodeURIComponent.
826
* @param {*} uri Raw URI string or instance of Uri
827
* object.
828
* @param {boolean=} opt_ignoreCase Whether to ignore the case of parameter
829
* names in #getParameterValue.
830
* @return {!goog.Uri} The new URI object.
831
*/
832
goog.Uri.parse = function(uri, opt_ignoreCase) {
833
'use strict';
834
return uri instanceof goog.Uri ? uri.clone() :
835
new goog.Uri(uri, opt_ignoreCase);
836
};
837
838
839
/**
840
* Creates a new goog.Uri object from unencoded parts.
841
*
842
* @param {?string=} opt_scheme Scheme/protocol or full URI to parse.
843
* @param {?string=} opt_userInfo username:password.
844
* @param {?string=} opt_domain www.google.com.
845
* @param {?number=} opt_port 9830.
846
* @param {?string=} opt_path /some/path/to/a/file.html.
847
* @param {string|goog.Uri.QueryData=} opt_query a=1&b=2.
848
* @param {?string=} opt_fragment The fragment without the #.
849
* @param {boolean=} opt_ignoreCase Whether to ignore parameter name case in
850
* #getParameterValue.
851
*
852
* @return {!goog.Uri} The new URI object.
853
*/
854
goog.Uri.create = function(
855
opt_scheme, opt_userInfo, opt_domain, opt_port, opt_path, opt_query,
856
opt_fragment, opt_ignoreCase) {
857
'use strict';
858
var uri = new goog.Uri(null, opt_ignoreCase);
859
860
// Only set the parts if they are defined and not empty strings.
861
opt_scheme && uri.setScheme(opt_scheme);
862
opt_userInfo && uri.setUserInfo(opt_userInfo);
863
opt_domain && uri.setDomain(opt_domain);
864
opt_port && uri.setPort(opt_port);
865
opt_path && uri.setPath(opt_path);
866
opt_query && uri.setQueryData(opt_query);
867
opt_fragment && uri.setFragment(opt_fragment);
868
869
return uri;
870
};
871
872
873
/**
874
* Resolves a relative Uri against a base Uri, accepting both strings and
875
* Uri objects.
876
*
877
* @param {*} base Base Uri.
878
* @param {*} rel Relative Uri.
879
* @return {!goog.Uri} Resolved uri.
880
*/
881
goog.Uri.resolve = function(base, rel) {
882
'use strict';
883
if (!(base instanceof goog.Uri)) {
884
base = goog.Uri.parse(base);
885
}
886
887
if (!(rel instanceof goog.Uri)) {
888
rel = goog.Uri.parse(rel);
889
}
890
891
return base.resolve(rel);
892
};
893
894
895
/**
896
* Removes dot segments in given path component, as described in
897
* RFC 3986, section 5.2.4.
898
*
899
* @param {string} path A non-empty path component.
900
* @return {string} Path component with removed dot segments.
901
*/
902
goog.Uri.removeDotSegments = function(path) {
903
'use strict';
904
if (path == '..' || path == '.') {
905
return '';
906
907
} else if (
908
!goog.string.contains(path, './') && !goog.string.contains(path, '/.')) {
909
// This optimization detects uris which do not contain dot-segments,
910
// and as a consequence do not require any processing.
911
return path;
912
913
} else {
914
var leadingSlash = goog.string.startsWith(path, '/');
915
var segments = path.split('/');
916
var out = [];
917
918
for (var pos = 0; pos < segments.length;) {
919
var segment = segments[pos++];
920
921
if (segment == '.') {
922
if (leadingSlash && pos == segments.length) {
923
out.push('');
924
}
925
} else if (segment == '..') {
926
if (out.length > 1 || out.length == 1 && out[0] != '') {
927
out.pop();
928
}
929
if (leadingSlash && pos == segments.length) {
930
out.push('');
931
}
932
} else {
933
out.push(segment);
934
leadingSlash = true;
935
}
936
}
937
938
return out.join('/');
939
}
940
};
941
942
943
/**
944
* Decodes a value or returns the empty string if it isn't defined or empty.
945
* @throws URIError If decodeURIComponent fails to decode val.
946
* @param {string|undefined} val Value to decode.
947
* @param {boolean=} opt_preserveReserved If true, restricted characters will
948
* not be decoded.
949
* @return {string} Decoded value.
950
* @private
951
*/
952
goog.Uri.decodeOrEmpty_ = function(val, opt_preserveReserved) {
953
'use strict';
954
// Don't use UrlDecode() here because val is not a query parameter.
955
if (!val) {
956
return '';
957
}
958
959
// decodeURI has the same output for '%2f' and '%252f'. We double encode %25
960
// so that we can distinguish between the 2 inputs. This is later undone by
961
// removeDoubleEncoding_.
962
return opt_preserveReserved ? decodeURI(val.replace(/%25/g, '%2525')) :
963
decodeURIComponent(val);
964
};
965
966
967
/**
968
* If unescapedPart is non null, then escapes any characters in it that aren't
969
* valid characters in a url and also escapes any special characters that
970
* appear in extra.
971
*
972
* @param {*} unescapedPart The string to encode.
973
* @param {RegExp} extra A character set of characters in [\01-\177].
974
* @param {boolean=} opt_removeDoubleEncoding If true, remove double percent
975
* encoding.
976
* @return {?string} null iff unescapedPart == null.
977
* @private
978
*/
979
goog.Uri.encodeSpecialChars_ = function(
980
unescapedPart, extra, opt_removeDoubleEncoding) {
981
'use strict';
982
if (typeof unescapedPart === 'string') {
983
var encoded = encodeURI(unescapedPart).replace(extra, goog.Uri.encodeChar_);
984
if (opt_removeDoubleEncoding) {
985
// encodeURI double-escapes %XX sequences used to represent restricted
986
// characters in some URI components, remove the double escaping here.
987
encoded = goog.Uri.removeDoubleEncoding_(encoded);
988
}
989
return encoded;
990
}
991
return null;
992
};
993
994
995
/**
996
* Converts a character in [\01-\177] to its unicode character equivalent.
997
* @param {string} ch One character string.
998
* @return {string} Encoded string.
999
* @private
1000
*/
1001
goog.Uri.encodeChar_ = function(ch) {
1002
'use strict';
1003
var n = ch.charCodeAt(0);
1004
return '%' + ((n >> 4) & 0xf).toString(16) + (n & 0xf).toString(16);
1005
};
1006
1007
1008
/**
1009
* Removes double percent-encoding from a string.
1010
* @param {string} doubleEncodedString String
1011
* @return {string} String with double encoding removed.
1012
* @private
1013
*/
1014
goog.Uri.removeDoubleEncoding_ = function(doubleEncodedString) {
1015
'use strict';
1016
return doubleEncodedString.replace(/%25([0-9a-fA-F]{2})/g, '%$1');
1017
};
1018
1019
1020
/**
1021
* Regular expression for characters that are disallowed in the scheme or
1022
* userInfo part of the URI.
1023
* @type {RegExp}
1024
* @private
1025
*/
1026
goog.Uri.reDisallowedInSchemeOrUserInfo_ = /[#\/\?@]/g;
1027
1028
1029
/**
1030
* Regular expression for characters that are disallowed in a relative path.
1031
* Colon is included due to RFC 3986 3.3.
1032
* @type {RegExp}
1033
* @private
1034
*/
1035
goog.Uri.reDisallowedInRelativePath_ = /[\#\?:]/g;
1036
1037
1038
/**
1039
* Regular expression for characters that are disallowed in an absolute path.
1040
* @type {RegExp}
1041
* @private
1042
*/
1043
goog.Uri.reDisallowedInAbsolutePath_ = /[\#\?]/g;
1044
1045
1046
/**
1047
* Regular expression for characters that are disallowed in the query.
1048
* @type {RegExp}
1049
* @private
1050
*/
1051
goog.Uri.reDisallowedInQuery_ = /[\#\?@]/g;
1052
1053
1054
/**
1055
* Regular expression for characters that are disallowed in the fragment.
1056
* @type {RegExp}
1057
* @private
1058
*/
1059
goog.Uri.reDisallowedInFragment_ = /#/g;
1060
1061
1062
/**
1063
* Checks whether two URIs have the same domain.
1064
* @param {string} uri1String First URI string.
1065
* @param {string} uri2String Second URI string.
1066
* @return {boolean} true if the two URIs have the same domain; false otherwise.
1067
*/
1068
goog.Uri.haveSameDomain = function(uri1String, uri2String) {
1069
'use strict';
1070
// Differs from goog.uri.utils.haveSameDomain, since this ignores scheme.
1071
// TODO(gboyer): Have this just call goog.uri.util.haveSameDomain.
1072
var pieces1 = goog.uri.utils.split(uri1String);
1073
var pieces2 = goog.uri.utils.split(uri2String);
1074
return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] ==
1075
pieces2[goog.uri.utils.ComponentIndex.DOMAIN] &&
1076
pieces1[goog.uri.utils.ComponentIndex.PORT] ==
1077
pieces2[goog.uri.utils.ComponentIndex.PORT];
1078
};
1079
1080
1081
1082
/**
1083
* Class used to represent URI query parameters. It is essentially a hash of
1084
* name-value pairs, though a name can be present more than once.
1085
*
1086
* Has the same interface as the collections in goog.structs.
1087
*
1088
* @param {?string=} opt_query Optional encoded query string to parse into
1089
* the object.
1090
* @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter
1091
* name in #get.
1092
* @constructor
1093
* @struct
1094
* @final
1095
*/
1096
goog.Uri.QueryData = function(opt_query, opt_ignoreCase) {
1097
'use strict';
1098
/**
1099
* The map containing name/value or name/array-of-values pairs.
1100
* May be null if it requires parsing from the query string.
1101
*
1102
* We need to use a Map because we cannot guarantee that the key names will
1103
* not be problematic for IE.
1104
*
1105
* @private {?Map<string, !Array<*>>}
1106
*/
1107
this.keyMap_ = null;
1108
1109
/**
1110
* The number of params, or null if it requires computing.
1111
* @private {?number}
1112
*/
1113
this.count_ = null;
1114
1115
/**
1116
* Encoded query string, or null if it requires computing from the key map.
1117
* @private {?string}
1118
*/
1119
this.encodedQuery_ = opt_query || null;
1120
1121
/**
1122
* If true, ignore the case of the parameter name in #get.
1123
* @private {boolean}
1124
*/
1125
this.ignoreCase_ = !!opt_ignoreCase;
1126
};
1127
1128
1129
/**
1130
* If the underlying key map is not yet initialized, it parses the
1131
* query string and fills the map with parsed data.
1132
* @private
1133
*/
1134
goog.Uri.QueryData.prototype.ensureKeyMapInitialized_ = function() {
1135
'use strict';
1136
if (!this.keyMap_) {
1137
this.keyMap_ = /** @type {!Map<string, !Array<*>>} */ (new Map());
1138
this.count_ = 0;
1139
if (this.encodedQuery_) {
1140
var self = this;
1141
goog.uri.utils.parseQueryData(this.encodedQuery_, function(name, value) {
1142
'use strict';
1143
self.add(goog.string.urlDecode(name), value);
1144
});
1145
}
1146
}
1147
};
1148
1149
1150
/**
1151
* Creates a new query data instance from a map of names and values.
1152
*
1153
* @param {!goog.collections.maps.MapLike<string, ?>|!Object} map Map of string
1154
* parameter names to parameter value. If parameter value is an array, it is
1155
* treated as if the key maps to each individual value in the
1156
* array.
1157
* @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter
1158
* name in #get.
1159
* @return {!goog.Uri.QueryData} The populated query data instance.
1160
*/
1161
goog.Uri.QueryData.createFromMap = function(map, opt_ignoreCase) {
1162
'use strict';
1163
var keys = goog.structs.getKeys(map);
1164
if (typeof keys == 'undefined') {
1165
throw new Error('Keys are undefined');
1166
}
1167
1168
var queryData = new goog.Uri.QueryData(null, opt_ignoreCase);
1169
var values = goog.structs.getValues(map);
1170
for (var i = 0; i < keys.length; i++) {
1171
var key = keys[i];
1172
var value = values[i];
1173
if (!Array.isArray(value)) {
1174
queryData.add(key, value);
1175
} else {
1176
queryData.setValues(key, value);
1177
}
1178
}
1179
return queryData;
1180
};
1181
1182
1183
/**
1184
* Creates a new query data instance from parallel arrays of parameter names
1185
* and values. Allows for duplicate parameter names. Throws an error if the
1186
* lengths of the arrays differ.
1187
*
1188
* @param {!Array<string>} keys Parameter names.
1189
* @param {!Array<?>} values Parameter values.
1190
* @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter
1191
* name in #get.
1192
* @return {!goog.Uri.QueryData} The populated query data instance.
1193
*/
1194
goog.Uri.QueryData.createFromKeysValues = function(
1195
keys, values, opt_ignoreCase) {
1196
'use strict';
1197
if (keys.length != values.length) {
1198
throw new Error('Mismatched lengths for keys/values');
1199
}
1200
var queryData = new goog.Uri.QueryData(null, opt_ignoreCase);
1201
for (var i = 0; i < keys.length; i++) {
1202
queryData.add(keys[i], values[i]);
1203
}
1204
return queryData;
1205
};
1206
1207
1208
/**
1209
* @return {?number} The number of parameters.
1210
*/
1211
goog.Uri.QueryData.prototype.getCount = function() {
1212
'use strict';
1213
this.ensureKeyMapInitialized_();
1214
return this.count_;
1215
};
1216
1217
1218
/**
1219
* Adds a key value pair.
1220
* @param {string} key Name.
1221
* @param {*} value Value.
1222
* @return {!goog.Uri.QueryData} Instance of this object.
1223
*/
1224
goog.Uri.QueryData.prototype.add = function(key, value) {
1225
'use strict';
1226
this.ensureKeyMapInitialized_();
1227
this.invalidateCache_();
1228
1229
key = this.getKeyName_(key);
1230
var values = this.keyMap_.get(key);
1231
if (!values) {
1232
this.keyMap_.set(key, (values = []));
1233
}
1234
values.push(value);
1235
this.count_ = goog.asserts.assertNumber(this.count_) + 1;
1236
return this;
1237
};
1238
1239
1240
/**
1241
* Removes all the params with the given key.
1242
* @param {string} key Name.
1243
* @return {boolean} Whether any parameter was removed.
1244
*/
1245
goog.Uri.QueryData.prototype.remove = function(key) {
1246
'use strict';
1247
this.ensureKeyMapInitialized_();
1248
1249
key = this.getKeyName_(key);
1250
if (this.keyMap_.has(key)) {
1251
this.invalidateCache_();
1252
1253
// Decrement parameter count.
1254
this.count_ =
1255
goog.asserts.assertNumber(this.count_) - this.keyMap_.get(key).length;
1256
return this.keyMap_.delete(key);
1257
}
1258
return false;
1259
};
1260
1261
1262
/**
1263
* Clears the parameters.
1264
*/
1265
goog.Uri.QueryData.prototype.clear = function() {
1266
'use strict';
1267
this.invalidateCache_();
1268
this.keyMap_ = null;
1269
this.count_ = 0;
1270
};
1271
1272
1273
/**
1274
* @return {boolean} Whether we have any parameters.
1275
*/
1276
goog.Uri.QueryData.prototype.isEmpty = function() {
1277
'use strict';
1278
this.ensureKeyMapInitialized_();
1279
return this.count_ == 0;
1280
};
1281
1282
1283
/**
1284
* Whether there is a parameter with the given name
1285
* @param {string} key The parameter name to check for.
1286
* @return {boolean} Whether there is a parameter with the given name.
1287
*/
1288
goog.Uri.QueryData.prototype.containsKey = function(key) {
1289
'use strict';
1290
this.ensureKeyMapInitialized_();
1291
key = this.getKeyName_(key);
1292
return this.keyMap_.has(key);
1293
};
1294
1295
1296
/**
1297
* Whether there is a parameter with the given value.
1298
* @param {*} value The value to check for.
1299
* @return {boolean} Whether there is a parameter with the given value.
1300
*/
1301
goog.Uri.QueryData.prototype.containsValue = function(value) {
1302
'use strict';
1303
// NOTE(arv): This solution goes through all the params even if it was the
1304
// first param. We can get around this by not reusing code or by switching to
1305
// iterators.
1306
var vals = this.getValues();
1307
return goog.array.contains(vals, value);
1308
};
1309
1310
1311
/**
1312
* Runs a callback on every key-value pair in the map, including duplicate keys.
1313
* This won't maintain original order when duplicate keys are interspersed (like
1314
* getKeys() / getValues()).
1315
* @param {function(this:SCOPE, ?, string, !goog.Uri.QueryData)} f
1316
* @param {SCOPE=} opt_scope The value of "this" inside f.
1317
* @template SCOPE
1318
*/
1319
goog.Uri.QueryData.prototype.forEach = function(f, opt_scope) {
1320
'use strict';
1321
this.ensureKeyMapInitialized_();
1322
this.keyMap_.forEach(function(values, key) {
1323
'use strict';
1324
values.forEach(function(value) {
1325
'use strict';
1326
f.call(opt_scope, value, key, this);
1327
}, this);
1328
}, this);
1329
};
1330
1331
1332
/**
1333
* Returns all the keys of the parameters. If a key is used multiple times
1334
* it will be included multiple times in the returned array
1335
* @return {!Array<string>} All the keys of the parameters.
1336
*/
1337
goog.Uri.QueryData.prototype.getKeys = function() {
1338
'use strict';
1339
this.ensureKeyMapInitialized_();
1340
// We need to get the values to know how many keys to add.
1341
const vals = Array.from(this.keyMap_.values());
1342
const keys = Array.from(this.keyMap_.keys());
1343
const rv = [];
1344
for (let i = 0; i < keys.length; i++) {
1345
const val = vals[i];
1346
for (let j = 0; j < val.length; j++) {
1347
rv.push(keys[i]);
1348
}
1349
}
1350
return rv;
1351
};
1352
1353
1354
/**
1355
* Returns all the values of the parameters with the given name. If the query
1356
* data has no such key this will return an empty array. If no key is given
1357
* all values wil be returned.
1358
* @param {string=} opt_key The name of the parameter to get the values for.
1359
* @return {!Array<?>} All the values of the parameters with the given name.
1360
*/
1361
goog.Uri.QueryData.prototype.getValues = function(opt_key) {
1362
'use strict';
1363
this.ensureKeyMapInitialized_();
1364
let rv = [];
1365
if (typeof opt_key === 'string') {
1366
if (this.containsKey(opt_key)) {
1367
rv = rv.concat(this.keyMap_.get(this.getKeyName_(opt_key)));
1368
}
1369
} else {
1370
// Return all values.
1371
const values = Array.from(this.keyMap_.values());
1372
for (let i = 0; i < values.length; i++) {
1373
rv = rv.concat(values[i]);
1374
}
1375
}
1376
return rv;
1377
};
1378
1379
1380
/**
1381
* Sets a key value pair and removes all other keys with the same value.
1382
*
1383
* @param {string} key Name.
1384
* @param {*} value Value.
1385
* @return {!goog.Uri.QueryData} Instance of this object.
1386
*/
1387
goog.Uri.QueryData.prototype.set = function(key, value) {
1388
'use strict';
1389
this.ensureKeyMapInitialized_();
1390
this.invalidateCache_();
1391
1392
// TODO(chrishenry): This could be better written as
1393
// this.remove(key), this.add(key, value), but that would reorder
1394
// the key (since the key is first removed and then added at the
1395
// end) and we would have to fix unit tests that depend on key
1396
// ordering.
1397
key = this.getKeyName_(key);
1398
if (this.containsKey(key)) {
1399
this.count_ =
1400
goog.asserts.assertNumber(this.count_) - this.keyMap_.get(key).length;
1401
}
1402
this.keyMap_.set(key, [value]);
1403
this.count_ = goog.asserts.assertNumber(this.count_) + 1;
1404
return this;
1405
};
1406
1407
1408
/**
1409
* Returns the first value associated with the key. If the query data has no
1410
* such key this will return undefined or the optional default.
1411
* @param {string} key The name of the parameter to get the value for.
1412
* @param {*=} opt_default The default value to return if the query data
1413
* has no such key.
1414
* @return {*} The first string value associated with the key, or opt_default
1415
* if there's no value.
1416
*/
1417
goog.Uri.QueryData.prototype.get = function(key, opt_default) {
1418
'use strict';
1419
if (!key) {
1420
return opt_default;
1421
}
1422
var values = this.getValues(key);
1423
return values.length > 0 ? String(values[0]) : opt_default;
1424
};
1425
1426
1427
/**
1428
* Sets the values for a key. If the key already exists, this will
1429
* override all of the existing values that correspond to the key.
1430
* @param {string} key The key to set values for.
1431
* @param {!Array<?>} values The values to set.
1432
*/
1433
goog.Uri.QueryData.prototype.setValues = function(key, values) {
1434
'use strict';
1435
this.remove(key);
1436
1437
if (values.length > 0) {
1438
this.invalidateCache_();
1439
this.keyMap_.set(this.getKeyName_(key), goog.array.clone(values));
1440
this.count_ = goog.asserts.assertNumber(this.count_) + values.length;
1441
}
1442
};
1443
1444
1445
/**
1446
* @return {string} Encoded query string.
1447
* @override
1448
*/
1449
goog.Uri.QueryData.prototype.toString = function() {
1450
'use strict';
1451
if (this.encodedQuery_) {
1452
return this.encodedQuery_;
1453
}
1454
1455
if (!this.keyMap_) {
1456
return '';
1457
}
1458
1459
const sb = [];
1460
1461
// In the past, we use this.getKeys() and this.getVals(), but that
1462
// generates a lot of allocations as compared to simply iterating
1463
// over the keys.
1464
const keys = Array.from(this.keyMap_.keys());
1465
for (var i = 0; i < keys.length; i++) {
1466
const key = keys[i];
1467
const encodedKey = goog.string.urlEncode(key);
1468
const val = this.getValues(key);
1469
for (var j = 0; j < val.length; j++) {
1470
var param = encodedKey;
1471
// Ensure that null and undefined are encoded into the url as
1472
// literal strings.
1473
if (val[j] !== '') {
1474
param += '=' + goog.string.urlEncode(val[j]);
1475
}
1476
sb.push(param);
1477
}
1478
}
1479
1480
return this.encodedQuery_ = sb.join('&');
1481
};
1482
1483
1484
/**
1485
* @throws URIError If URI is malformed (that is, if decodeURIComponent fails on
1486
* any of the URI components).
1487
* @return {string} Decoded query string.
1488
*/
1489
goog.Uri.QueryData.prototype.toDecodedString = function() {
1490
'use strict';
1491
return goog.Uri.decodeOrEmpty_(this.toString());
1492
};
1493
1494
1495
/**
1496
* Invalidate the cache.
1497
* @private
1498
*/
1499
goog.Uri.QueryData.prototype.invalidateCache_ = function() {
1500
'use strict';
1501
this.encodedQuery_ = null;
1502
};
1503
1504
1505
/**
1506
* Removes all keys that are not in the provided list. (Modifies this object.)
1507
* @param {Array<string>} keys The desired keys.
1508
* @return {!goog.Uri.QueryData} a reference to this object.
1509
*/
1510
goog.Uri.QueryData.prototype.filterKeys = function(keys) {
1511
'use strict';
1512
this.ensureKeyMapInitialized_();
1513
this.keyMap_.forEach(function(value, key) {
1514
'use strict';
1515
if (!goog.array.contains(keys, key)) {
1516
this.remove(key);
1517
}
1518
}, this);
1519
return this;
1520
};
1521
1522
1523
/**
1524
* Clone the query data instance.
1525
* @return {!goog.Uri.QueryData} New instance of the QueryData object.
1526
*/
1527
goog.Uri.QueryData.prototype.clone = function() {
1528
'use strict';
1529
var rv = new goog.Uri.QueryData();
1530
rv.encodedQuery_ = this.encodedQuery_;
1531
if (this.keyMap_) {
1532
rv.keyMap_ = /** @type {!Map<string, !Array<*>>} */ (new Map(this.keyMap_));
1533
rv.count_ = this.count_;
1534
}
1535
return rv;
1536
};
1537
1538
1539
/**
1540
* Helper function to get the key name from a JavaScript object. Converts
1541
* the object to a string, and to lower case if necessary.
1542
* @private
1543
* @param {*} arg The object to get a key name from.
1544
* @return {string} valid key name which can be looked up in #keyMap_.
1545
*/
1546
goog.Uri.QueryData.prototype.getKeyName_ = function(arg) {
1547
'use strict';
1548
var keyName = String(arg);
1549
if (this.ignoreCase_) {
1550
keyName = keyName.toLowerCase();
1551
}
1552
return keyName;
1553
};
1554
1555
1556
/**
1557
* Ignore case in parameter names.
1558
* NOTE: If there are already key/value pairs in the QueryData, and
1559
* ignoreCase_ is set to false, the keys will all be lower-cased.
1560
* @param {boolean} ignoreCase whether this goog.Uri should ignore case.
1561
*/
1562
goog.Uri.QueryData.prototype.setIgnoreCase = function(ignoreCase) {
1563
'use strict';
1564
var resetKeys = ignoreCase && !this.ignoreCase_;
1565
if (resetKeys) {
1566
this.ensureKeyMapInitialized_();
1567
this.invalidateCache_();
1568
this.keyMap_.forEach(function(value, key) {
1569
'use strict';
1570
var lowerCase = key.toLowerCase();
1571
if (key != lowerCase) {
1572
this.remove(key);
1573
this.setValues(lowerCase, value);
1574
}
1575
}, this);
1576
}
1577
this.ignoreCase_ = ignoreCase;
1578
};
1579
1580
1581
/**
1582
* Extends a query data object with another query data or map like object. This
1583
* operates 'in-place', it does not create a new QueryData object.
1584
*
1585
* @param {...(?goog.Uri.QueryData|?goog.collections.maps.MapLike<?,
1586
* ?>|?Object)} var_args The object from which key value pairs will be
1587
* copied. Note: does not accept null.
1588
* @suppress {deprecated} Use deprecated goog.structs.forEach to allow different
1589
* types of parameters.
1590
*/
1591
goog.Uri.QueryData.prototype.extend = function(var_args) {
1592
'use strict';
1593
for (var i = 0; i < arguments.length; i++) {
1594
var data = arguments[i];
1595
goog.structs.forEach(data, function(value, key) {
1596
'use strict';
1597
this.add(key, value);
1598
}, this);
1599
}
1600
};
1601
1602