Path: blob/trunk/third_party/closure/goog/uri/uri.js
4122 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Class for parsing and formatting URIs.8*9* This package is deprecated in favour of the Closure URL package (goog.url)10* when manipulating URIs for use by a browser. This package uses regular11* expressions to parse a potential URI which can fall out of sync with how a12* browser will actually interpret the URI. See13* `goog.uri.utils.setUrlPackageSupportLoggingHandler` for one way to identify14* URIs that should instead be parsed using the URL package.15*16* Use goog.Uri(string) to parse a URI string. Use goog.Uri.create(...) to17* create a new instance of the goog.Uri object from Uri parts.18*19* e.g: <code>var myUri = new goog.Uri(window.location);</code>20*21* Implements RFC 3986 for parsing/formatting URIs.22* http://www.ietf.org/rfc/rfc3986.txt23*24* Some changes have been made to the interface (more like .NETs), though the25* internal representation is now of un-encoded parts, this will change the26* behavior slightly.27*/2829goog.provide('goog.Uri');30goog.provide('goog.Uri.QueryData');3132goog.require('goog.array');33goog.require('goog.asserts');34goog.require('goog.collections.maps');35goog.require('goog.string');36goog.require('goog.structs');37goog.require('goog.uri.utils');38goog.require('goog.uri.utils.ComponentIndex');39goog.require('goog.uri.utils.StandardQueryParam');40414243/**44* This class contains setters and getters for the parts of the URI.45* The <code>getXyz</code>/<code>setXyz</code> methods return the decoded part46* -- so<code>goog.Uri.parse('/foo%20bar').getPath()</code> will return the47* decoded path, <code>/foo bar</code>.48*49* Reserved characters (see RFC 3986 section 2.2) can be present in50* their percent-encoded form in scheme, domain, and path URI components and51* will not be auto-decoded. For example:52* <code>goog.Uri.parse('rel%61tive/path%2fto/resource').getPath()</code> will53* return <code>relative/path%2fto/resource</code>.54*55* The constructor accepts an optional unparsed, raw URI string. The parser56* is relaxed, so special characters that aren't escaped but don't cause57* ambiguities will not cause parse failures.58*59* All setters return <code>this</code> and so may be chained, a la60* <code>goog.Uri.parse('/foo').setFragment('part').toString()</code>.61*62* @param {*=} opt_uri Optional string URI to parse63* (use goog.Uri.create() to create a URI from parts), or if64* a goog.Uri is passed, a clone is created.65* @param {boolean=} opt_ignoreCase If true, #getParameterValue will ignore66* the case of the parameter name.67*68* @throws URIError If opt_uri is provided and URI is malformed (that is,69* if decodeURIComponent fails on any of the URI components).70* @constructor71* @struct72*/73goog.Uri = function(opt_uri, opt_ignoreCase) {74'use strict';75/**76* Scheme such as "http".77* @private {string}78*/79this.scheme_ = '';8081/**82* User credentials in the form "username:password".83* @private {string}84*/85this.userInfo_ = '';8687/**88* Domain part, e.g. "www.google.com".89* @private {string}90*/91this.domain_ = '';9293/**94* Port, e.g. 8080.95* @private {?number}96*/97this.port_ = null;9899/**100* Path, e.g. "/tests/img.png".101* @private {string}102*/103this.path_ = '';104105/**106* The fragment without the #.107* @private {string}108*/109this.fragment_ = '';110111/**112* Whether or not this Uri should be treated as Read Only.113* @private {boolean}114*/115this.isReadOnly_ = false;116117/**118* Whether or not to ignore case when comparing query params.119* @private {boolean}120*/121this.ignoreCase_ = false;122123/**124* Object representing query data.125* @private {!goog.Uri.QueryData}126*/127this.queryData_;128129// Parse in the uri string130var m;131if (opt_uri instanceof goog.Uri) {132this.ignoreCase_ = (opt_ignoreCase !== undefined) ? opt_ignoreCase :133opt_uri.getIgnoreCase();134this.setScheme(opt_uri.getScheme());135this.setUserInfo(opt_uri.getUserInfo());136this.setDomain(opt_uri.getDomain());137this.setPort(opt_uri.getPort());138this.setPath(opt_uri.getPath());139this.setQueryData(opt_uri.getQueryData().clone());140this.setFragment(opt_uri.getFragment());141} else if (opt_uri && (m = goog.uri.utils.split(String(opt_uri)))) {142this.ignoreCase_ = !!opt_ignoreCase;143144// Set the parts -- decoding as we do so.145// COMPATIBILITY NOTE - In IE, unmatched fields may be empty strings,146// whereas in other browsers they will be undefined.147this.setScheme(m[goog.uri.utils.ComponentIndex.SCHEME] || '', true);148this.setUserInfo(m[goog.uri.utils.ComponentIndex.USER_INFO] || '', true);149this.setDomain(m[goog.uri.utils.ComponentIndex.DOMAIN] || '', true);150this.setPort(m[goog.uri.utils.ComponentIndex.PORT]);151this.setPath(m[goog.uri.utils.ComponentIndex.PATH] || '', true);152this.setQueryData(m[goog.uri.utils.ComponentIndex.QUERY_DATA] || '', true);153this.setFragment(m[goog.uri.utils.ComponentIndex.FRAGMENT] || '', true);154155} else {156this.ignoreCase_ = !!opt_ignoreCase;157this.queryData_ = new goog.Uri.QueryData(null, this.ignoreCase_);158}159};160161162/**163* Parameter name added to stop caching.164* @type {string}165*/166goog.Uri.RANDOM_PARAM = goog.uri.utils.StandardQueryParam.RANDOM;167168169/**170* @return {string} The string form of the url.171* @override172*/173goog.Uri.prototype.toString = function() {174'use strict';175var out = [];176177var scheme = this.getScheme();178if (scheme) {179out.push(180goog.Uri.encodeSpecialChars_(181scheme, goog.Uri.reDisallowedInSchemeOrUserInfo_, true),182':');183}184185var domain = this.getDomain();186if (domain || scheme == 'file') {187out.push('//');188189var userInfo = this.getUserInfo();190if (userInfo) {191out.push(192goog.Uri.encodeSpecialChars_(193userInfo, goog.Uri.reDisallowedInSchemeOrUserInfo_, true),194'@');195}196197out.push(goog.Uri.removeDoubleEncoding_(goog.string.urlEncode(domain)));198199var port = this.getPort();200if (port != null) {201out.push(':', String(port));202}203}204205var path = this.getPath();206if (path) {207if (this.hasDomain() && path.charAt(0) != '/') {208out.push('/');209}210out.push(goog.Uri.encodeSpecialChars_(211path,212path.charAt(0) == '/' ? goog.Uri.reDisallowedInAbsolutePath_ :213goog.Uri.reDisallowedInRelativePath_,214true));215}216217var query = this.getEncodedQuery();218if (query) {219out.push('?', query);220}221222var fragment = this.getFragment();223if (fragment) {224out.push(225'#',226goog.Uri.encodeSpecialChars_(227fragment, goog.Uri.reDisallowedInFragment_));228}229return out.join('');230};231232233/**234* Resolves the given relative URI (a goog.Uri object), using the URI235* represented by this instance as the base URI.236*237* There are several kinds of relative URIs:<br>238* 1. foo - replaces the last part of the path, the whole query and fragment<br>239* 2. /foo - replaces the path, the query and fragment<br>240* 3. //foo - replaces everything from the domain on. foo is a domain name<br>241* 4. ?foo - replace the query and fragment<br>242* 5. #foo - replace the fragment only243*244* Additionally, if relative URI has a non-empty path, all ".." and "."245* segments will be resolved, as described in RFC 3986.246*247* @param {!goog.Uri} relativeUri The relative URI to resolve.248* @return {!goog.Uri} The resolved URI.249*/250goog.Uri.prototype.resolve = function(relativeUri) {251'use strict';252var absoluteUri = this.clone();253254// we satisfy these conditions by looking for the first part of relativeUri255// that is not blank and applying defaults to the rest256257var overridden = relativeUri.hasScheme();258259if (overridden) {260absoluteUri.setScheme(relativeUri.getScheme());261} else {262overridden = relativeUri.hasUserInfo();263}264265if (overridden) {266absoluteUri.setUserInfo(relativeUri.getUserInfo());267} else {268overridden = relativeUri.hasDomain();269}270271if (overridden) {272absoluteUri.setDomain(relativeUri.getDomain());273} else {274overridden = relativeUri.hasPort();275}276277var path = relativeUri.getPath();278if (overridden) {279absoluteUri.setPort(relativeUri.getPort());280} else {281overridden = relativeUri.hasPath();282if (overridden) {283// resolve path properly284if (path.charAt(0) != '/') {285// path is relative286if (this.hasDomain() && !this.hasPath()) {287// RFC 3986, section 5.2.3, case 1288path = '/' + path;289} else {290// RFC 3986, section 5.2.3, case 2291var lastSlashIndex = absoluteUri.getPath().lastIndexOf('/');292if (lastSlashIndex != -1) {293path = absoluteUri.getPath().slice(0, lastSlashIndex + 1) + path;294}295}296}297path = goog.Uri.removeDotSegments(path);298}299}300301if (overridden) {302absoluteUri.setPath(path);303} else {304overridden = relativeUri.hasQuery();305}306307if (overridden) {308absoluteUri.setQueryData(relativeUri.getQueryData().clone());309} else {310overridden = relativeUri.hasFragment();311}312313if (overridden) {314absoluteUri.setFragment(relativeUri.getFragment());315}316317return absoluteUri;318};319320321/**322* Clones the URI instance.323* @return {!goog.Uri} New instance of the URI object.324*/325goog.Uri.prototype.clone = function() {326'use strict';327return new goog.Uri(this);328};329330331/**332* @return {string} The encoded scheme/protocol for the URI.333*/334goog.Uri.prototype.getScheme = function() {335'use strict';336return this.scheme_;337};338339340/**341* Sets the scheme/protocol.342* @throws URIError If opt_decode is true and newScheme is malformed (that is,343* if decodeURIComponent fails).344* @param {string} newScheme New scheme value.345* @param {boolean=} opt_decode Optional param for whether to decode new value.346* @return {!goog.Uri} Reference to this URI object.347*/348goog.Uri.prototype.setScheme = function(newScheme, opt_decode) {349'use strict';350this.enforceReadOnly();351this.scheme_ =352opt_decode ? goog.Uri.decodeOrEmpty_(newScheme, true) : newScheme;353354// remove an : at the end of the scheme so somebody can pass in355// window.location.protocol356if (this.scheme_) {357this.scheme_ = this.scheme_.replace(/:$/, '');358}359return this;360};361362363/**364* @return {boolean} Whether the scheme has been set.365*/366goog.Uri.prototype.hasScheme = function() {367'use strict';368return !!this.scheme_;369};370371372/**373* @return {string} The decoded user info.374*/375goog.Uri.prototype.getUserInfo = function() {376'use strict';377return this.userInfo_;378};379380381/**382* Sets the userInfo.383* @throws URIError If opt_decode is true and newUserInfo is malformed (that is,384* if decodeURIComponent fails).385* @param {string} newUserInfo New userInfo value.386* @param {boolean=} opt_decode Optional param for whether to decode new value.387* @return {!goog.Uri} Reference to this URI object.388*/389goog.Uri.prototype.setUserInfo = function(newUserInfo, opt_decode) {390'use strict';391this.enforceReadOnly();392this.userInfo_ =393opt_decode ? goog.Uri.decodeOrEmpty_(newUserInfo) : newUserInfo;394return this;395};396397398/**399* @return {boolean} Whether the user info has been set.400*/401goog.Uri.prototype.hasUserInfo = function() {402'use strict';403return !!this.userInfo_;404};405406407/**408* @return {string} The decoded domain.409*/410goog.Uri.prototype.getDomain = function() {411'use strict';412return this.domain_;413};414415416/**417* Sets the domain.418* @throws URIError If opt_decode is true and newDomain is malformed (that is,419* if decodeURIComponent fails).420* @param {string} newDomain New domain value.421* @param {boolean=} opt_decode Optional param for whether to decode new value.422* @return {!goog.Uri} Reference to this URI object.423*/424goog.Uri.prototype.setDomain = function(newDomain, opt_decode) {425'use strict';426this.enforceReadOnly();427this.domain_ =428opt_decode ? goog.Uri.decodeOrEmpty_(newDomain, true) : newDomain;429return this;430};431432433/**434* @return {boolean} Whether the domain has been set.435*/436goog.Uri.prototype.hasDomain = function() {437'use strict';438return !!this.domain_;439};440441442/**443* @return {?number} The port number.444*/445goog.Uri.prototype.getPort = function() {446'use strict';447return this.port_;448};449450451/**452* Sets the port number.453* @param {*} newPort Port number. Will be explicitly casted to a number.454* @return {!goog.Uri} Reference to this URI object.455*/456goog.Uri.prototype.setPort = function(newPort) {457'use strict';458this.enforceReadOnly();459460if (newPort) {461newPort = Number(newPort);462if (isNaN(newPort) || newPort < 0) {463throw new Error('Bad port number ' + newPort);464}465this.port_ = newPort;466} else {467this.port_ = null;468}469470return this;471};472473474/**475* @return {boolean} Whether the port has been set.476*/477goog.Uri.prototype.hasPort = function() {478'use strict';479return this.port_ != null;480};481482483/**484* @return {string} The decoded path.485*/486goog.Uri.prototype.getPath = function() {487'use strict';488return this.path_;489};490491492/**493* Sets the path.494* @throws URIError If opt_decode is true and newPath is malformed (that is,495* if decodeURIComponent fails).496* @param {string} newPath New path value.497* @param {boolean=} opt_decode Optional param for whether to decode new value.498* @return {!goog.Uri} Reference to this URI object.499*/500goog.Uri.prototype.setPath = function(newPath, opt_decode) {501'use strict';502this.enforceReadOnly();503this.path_ = opt_decode ? goog.Uri.decodeOrEmpty_(newPath, true) : newPath;504return this;505};506507508/**509* @return {boolean} Whether the path has been set.510*/511goog.Uri.prototype.hasPath = function() {512'use strict';513return !!this.path_;514};515516517/**518* @return {boolean} Whether the query string has been set.519*/520goog.Uri.prototype.hasQuery = function() {521'use strict';522return this.queryData_.toString() !== '';523};524525526/**527* Sets the query data.528* @param {goog.Uri.QueryData|string|undefined} queryData QueryData object.529* @param {boolean=} opt_decode Optional param for whether to decode new value.530* Applies only if queryData is a string.531* @return {!goog.Uri} Reference to this URI object.532*/533goog.Uri.prototype.setQueryData = function(queryData, opt_decode) {534'use strict';535this.enforceReadOnly();536537if (queryData instanceof goog.Uri.QueryData) {538this.queryData_ = queryData;539this.queryData_.setIgnoreCase(this.ignoreCase_);540} else {541if (!opt_decode) {542// QueryData accepts encoded query string, so encode it if543// opt_decode flag is not true.544queryData = goog.Uri.encodeSpecialChars_(545queryData, goog.Uri.reDisallowedInQuery_);546}547this.queryData_ = new goog.Uri.QueryData(queryData, this.ignoreCase_);548}549550return this;551};552553554/**555* Sets the URI query.556* @param {string} newQuery New query value.557* @param {boolean=} opt_decode Optional param for whether to decode new value.558* @return {!goog.Uri} Reference to this URI object.559*/560goog.Uri.prototype.setQuery = function(newQuery, opt_decode) {561'use strict';562return this.setQueryData(newQuery, opt_decode);563};564565566/**567* @return {string} The encoded URI query, not including the ?.568*/569goog.Uri.prototype.getEncodedQuery = function() {570'use strict';571return this.queryData_.toString();572};573574575/**576* @return {string} The decoded URI query, not including the ?.577*/578goog.Uri.prototype.getDecodedQuery = function() {579'use strict';580return this.queryData_.toDecodedString();581};582583584/**585* Returns the query data.586* @return {!goog.Uri.QueryData} QueryData object.587*/588goog.Uri.prototype.getQueryData = function() {589'use strict';590return this.queryData_;591};592593594/**595* @return {string} The encoded URI query, not including the ?.596*597* Warning: This method, unlike other getter methods, returns encoded598* value, instead of decoded one.599*/600goog.Uri.prototype.getQuery = function() {601'use strict';602return this.getEncodedQuery();603};604605606/**607* Sets the value of the named query parameters, clearing previous values for608* that key.609*610* @param {string} key The parameter to set.611* @param {*} value The new value. Value does not need to be encoded.612* @return {!goog.Uri} Reference to this URI object.613*/614goog.Uri.prototype.setParameterValue = function(key, value) {615'use strict';616this.enforceReadOnly();617this.queryData_.set(key, value);618return this;619};620621622/**623* Sets the values of the named query parameters, clearing previous values for624* that key. Not new values will currently be moved to the end of the query625* string.626*627* So, <code>goog.Uri.parse('foo?a=b&c=d&e=f').setParameterValues('c', ['new'])628* </code> yields <tt>foo?a=b&e=f&c=new</tt>.</p>629*630* @param {string} key The parameter to set.631* @param {*} values The new values. If values is a single632* string then it will be treated as the sole value. Values do not need to633* be encoded.634* @return {!goog.Uri} Reference to this URI object.635*/636goog.Uri.prototype.setParameterValues = function(key, values) {637'use strict';638this.enforceReadOnly();639640if (!Array.isArray(values)) {641values = [String(values)];642}643644this.queryData_.setValues(key, values);645646return this;647};648649650/**651* Returns the value<b>s</b> for a given cgi parameter as a list of decoded652* query parameter values.653* @param {string} name The parameter to get values for.654* @return {!Array<?>} The values for a given cgi parameter as a list of655* decoded query parameter values.656*/657goog.Uri.prototype.getParameterValues = function(name) {658'use strict';659return this.queryData_.getValues(name);660};661662663/**664* Returns the first value for a given cgi parameter or undefined if the given665* parameter name does not appear in the query string.666* @param {string} paramName Unescaped parameter name.667* @return {string|undefined} The first value for a given cgi parameter or668* undefined if the given parameter name does not appear in the query669* string.670*/671goog.Uri.prototype.getParameterValue = function(paramName) {672'use strict';673return /** @type {string|undefined} */ (this.queryData_.get(paramName));674};675676677/**678* @return {string} The URI fragment, not including the #.679*/680goog.Uri.prototype.getFragment = function() {681'use strict';682return this.fragment_;683};684685686/**687* Sets the URI fragment.688* @throws URIError If opt_decode is true and newFragment is malformed (that is,689* if decodeURIComponent fails).690* @param {string} newFragment New fragment value.691* @param {boolean=} opt_decode Optional param for whether to decode new value.692* @return {!goog.Uri} Reference to this URI object.693*/694goog.Uri.prototype.setFragment = function(newFragment, opt_decode) {695'use strict';696this.enforceReadOnly();697this.fragment_ =698opt_decode ? goog.Uri.decodeOrEmpty_(newFragment) : newFragment;699return this;700};701702703/**704* @return {boolean} Whether the URI has a fragment set.705*/706goog.Uri.prototype.hasFragment = function() {707'use strict';708return !!this.fragment_;709};710711712/**713* Returns true if this has the same domain as that of uri2.714* @param {!goog.Uri} uri2 The URI object to compare to.715* @return {boolean} true if same domain; false otherwise.716*/717goog.Uri.prototype.hasSameDomainAs = function(uri2) {718'use strict';719return ((!this.hasDomain() && !uri2.hasDomain()) ||720this.getDomain() == uri2.getDomain()) &&721((!this.hasPort() && !uri2.hasPort()) ||722this.getPort() == uri2.getPort());723};724725726/**727* Adds a random parameter to the Uri.728* @return {!goog.Uri} Reference to this Uri object.729*/730goog.Uri.prototype.makeUnique = function() {731'use strict';732this.enforceReadOnly();733this.setParameterValue(goog.Uri.RANDOM_PARAM, goog.string.getRandomString());734735return this;736};737738739/**740* Removes the named query parameter.741*742* @param {string} key The parameter to remove.743* @return {!goog.Uri} Reference to this URI object.744*/745goog.Uri.prototype.removeParameter = function(key) {746'use strict';747this.enforceReadOnly();748this.queryData_.remove(key);749return this;750};751752753/**754* Sets whether Uri is read only. If this goog.Uri is read-only,755* enforceReadOnly_ will be called at the start of any function that may modify756* this Uri.757* @param {boolean} isReadOnly whether this goog.Uri should be read only.758* @return {!goog.Uri} Reference to this Uri object.759*/760goog.Uri.prototype.setReadOnly = function(isReadOnly) {761'use strict';762this.isReadOnly_ = isReadOnly;763return this;764};765766767/**768* @return {boolean} Whether the URI is read only.769*/770goog.Uri.prototype.isReadOnly = function() {771'use strict';772return this.isReadOnly_;773};774775776/**777* Checks if this Uri has been marked as read only, and if so, throws an error.778* This should be called whenever any modifying function is called.779*/780goog.Uri.prototype.enforceReadOnly = function() {781'use strict';782if (this.isReadOnly_) {783throw new Error('Tried to modify a read-only Uri');784}785};786787788/**789* Sets whether to ignore case.790* NOTE: If there are already key/value pairs in the QueryData, and791* ignoreCase_ is set to false, the keys will all be lower-cased.792* @param {boolean} ignoreCase whether this goog.Uri should ignore case.793* @return {!goog.Uri} Reference to this Uri object.794*/795goog.Uri.prototype.setIgnoreCase = function(ignoreCase) {796'use strict';797this.ignoreCase_ = ignoreCase;798if (this.queryData_) {799this.queryData_.setIgnoreCase(ignoreCase);800}801return this;802};803804805/**806* @return {boolean} Whether to ignore case.807*/808goog.Uri.prototype.getIgnoreCase = function() {809'use strict';810return this.ignoreCase_;811};812813814//==============================================================================815// Static members816//==============================================================================817818819/**820* Creates a uri from the string form. Basically an alias of new goog.Uri().821* If a Uri object is passed to parse then it will return a clone of the object.822*823* @throws URIError If parsing the URI is malformed. The passed URI components824* should all be parseable by decodeURIComponent.825* @param {*} uri Raw URI string or instance of Uri826* object.827* @param {boolean=} opt_ignoreCase Whether to ignore the case of parameter828* names in #getParameterValue.829* @return {!goog.Uri} The new URI object.830*/831goog.Uri.parse = function(uri, opt_ignoreCase) {832'use strict';833return uri instanceof goog.Uri ? uri.clone() :834new goog.Uri(uri, opt_ignoreCase);835};836837838/**839* Creates a new goog.Uri object from unencoded parts.840*841* @param {?string=} opt_scheme Scheme/protocol or full URI to parse.842* @param {?string=} opt_userInfo username:password.843* @param {?string=} opt_domain www.google.com.844* @param {?number=} opt_port 9830.845* @param {?string=} opt_path /some/path/to/a/file.html.846* @param {string|goog.Uri.QueryData=} opt_query a=1&b=2.847* @param {?string=} opt_fragment The fragment without the #.848* @param {boolean=} opt_ignoreCase Whether to ignore parameter name case in849* #getParameterValue.850*851* @return {!goog.Uri} The new URI object.852*/853goog.Uri.create = function(854opt_scheme, opt_userInfo, opt_domain, opt_port, opt_path, opt_query,855opt_fragment, opt_ignoreCase) {856'use strict';857var uri = new goog.Uri(null, opt_ignoreCase);858859// Only set the parts if they are defined and not empty strings.860opt_scheme && uri.setScheme(opt_scheme);861opt_userInfo && uri.setUserInfo(opt_userInfo);862opt_domain && uri.setDomain(opt_domain);863opt_port && uri.setPort(opt_port);864opt_path && uri.setPath(opt_path);865opt_query && uri.setQueryData(opt_query);866opt_fragment && uri.setFragment(opt_fragment);867868return uri;869};870871872/**873* Resolves a relative Uri against a base Uri, accepting both strings and874* Uri objects.875*876* @param {*} base Base Uri.877* @param {*} rel Relative Uri.878* @return {!goog.Uri} Resolved uri.879*/880goog.Uri.resolve = function(base, rel) {881'use strict';882if (!(base instanceof goog.Uri)) {883base = goog.Uri.parse(base);884}885886if (!(rel instanceof goog.Uri)) {887rel = goog.Uri.parse(rel);888}889890return base.resolve(rel);891};892893894/**895* Removes dot segments in given path component, as described in896* RFC 3986, section 5.2.4.897*898* @param {string} path A non-empty path component.899* @return {string} Path component with removed dot segments.900*/901goog.Uri.removeDotSegments = function(path) {902'use strict';903if (path == '..' || path == '.') {904return '';905906} else if (907!goog.string.contains(path, './') && !goog.string.contains(path, '/.')) {908// This optimization detects uris which do not contain dot-segments,909// and as a consequence do not require any processing.910return path;911912} else {913var leadingSlash = goog.string.startsWith(path, '/');914var segments = path.split('/');915var out = [];916917for (var pos = 0; pos < segments.length;) {918var segment = segments[pos++];919920if (segment == '.') {921if (leadingSlash && pos == segments.length) {922out.push('');923}924} else if (segment == '..') {925if (out.length > 1 || out.length == 1 && out[0] != '') {926out.pop();927}928if (leadingSlash && pos == segments.length) {929out.push('');930}931} else {932out.push(segment);933leadingSlash = true;934}935}936937return out.join('/');938}939};940941942/**943* Decodes a value or returns the empty string if it isn't defined or empty.944* @throws URIError If decodeURIComponent fails to decode val.945* @param {string|undefined} val Value to decode.946* @param {boolean=} opt_preserveReserved If true, restricted characters will947* not be decoded.948* @return {string} Decoded value.949* @private950*/951goog.Uri.decodeOrEmpty_ = function(val, opt_preserveReserved) {952'use strict';953// Don't use UrlDecode() here because val is not a query parameter.954if (!val) {955return '';956}957958// decodeURI has the same output for '%2f' and '%252f'. We double encode %25959// so that we can distinguish between the 2 inputs. This is later undone by960// removeDoubleEncoding_.961return opt_preserveReserved ? decodeURI(val.replace(/%25/g, '%2525')) :962decodeURIComponent(val);963};964965966/**967* If unescapedPart is non null, then escapes any characters in it that aren't968* valid characters in a url and also escapes any special characters that969* appear in extra.970*971* @param {*} unescapedPart The string to encode.972* @param {RegExp} extra A character set of characters in [\01-\177].973* @param {boolean=} opt_removeDoubleEncoding If true, remove double percent974* encoding.975* @return {?string} null iff unescapedPart == null.976* @private977*/978goog.Uri.encodeSpecialChars_ = function(979unescapedPart, extra, opt_removeDoubleEncoding) {980'use strict';981if (typeof unescapedPart === 'string') {982var encoded = encodeURI(unescapedPart).replace(extra, goog.Uri.encodeChar_);983if (opt_removeDoubleEncoding) {984// encodeURI double-escapes %XX sequences used to represent restricted985// characters in some URI components, remove the double escaping here.986encoded = goog.Uri.removeDoubleEncoding_(encoded);987}988return encoded;989}990return null;991};992993994/**995* Converts a character in [\01-\177] to its unicode character equivalent.996* @param {string} ch One character string.997* @return {string} Encoded string.998* @private999*/1000goog.Uri.encodeChar_ = function(ch) {1001'use strict';1002var n = ch.charCodeAt(0);1003return '%' + ((n >> 4) & 0xf).toString(16) + (n & 0xf).toString(16);1004};100510061007/**1008* Removes double percent-encoding from a string.1009* @param {string} doubleEncodedString String1010* @return {string} String with double encoding removed.1011* @private1012*/1013goog.Uri.removeDoubleEncoding_ = function(doubleEncodedString) {1014'use strict';1015return doubleEncodedString.replace(/%25([0-9a-fA-F]{2})/g, '%$1');1016};101710181019/**1020* Regular expression for characters that are disallowed in the scheme or1021* userInfo part of the URI.1022* @type {RegExp}1023* @private1024*/1025goog.Uri.reDisallowedInSchemeOrUserInfo_ = /[#\/\?@]/g;102610271028/**1029* Regular expression for characters that are disallowed in a relative path.1030* Colon is included due to RFC 3986 3.3.1031* @type {RegExp}1032* @private1033*/1034goog.Uri.reDisallowedInRelativePath_ = /[\#\?:]/g;103510361037/**1038* Regular expression for characters that are disallowed in an absolute path.1039* @type {RegExp}1040* @private1041*/1042goog.Uri.reDisallowedInAbsolutePath_ = /[\#\?]/g;104310441045/**1046* Regular expression for characters that are disallowed in the query.1047* @type {RegExp}1048* @private1049*/1050goog.Uri.reDisallowedInQuery_ = /[\#\?@]/g;105110521053/**1054* Regular expression for characters that are disallowed in the fragment.1055* @type {RegExp}1056* @private1057*/1058goog.Uri.reDisallowedInFragment_ = /#/g;105910601061/**1062* Checks whether two URIs have the same domain.1063* @param {string} uri1String First URI string.1064* @param {string} uri2String Second URI string.1065* @return {boolean} true if the two URIs have the same domain; false otherwise.1066*/1067goog.Uri.haveSameDomain = function(uri1String, uri2String) {1068'use strict';1069// Differs from goog.uri.utils.haveSameDomain, since this ignores scheme.1070// TODO(gboyer): Have this just call goog.uri.util.haveSameDomain.1071var pieces1 = goog.uri.utils.split(uri1String);1072var pieces2 = goog.uri.utils.split(uri2String);1073return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] ==1074pieces2[goog.uri.utils.ComponentIndex.DOMAIN] &&1075pieces1[goog.uri.utils.ComponentIndex.PORT] ==1076pieces2[goog.uri.utils.ComponentIndex.PORT];1077};1078107910801081/**1082* Class used to represent URI query parameters. It is essentially a hash of1083* name-value pairs, though a name can be present more than once.1084*1085* Has the same interface as the collections in goog.structs.1086*1087* @param {?string=} opt_query Optional encoded query string to parse into1088* the object.1089* @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter1090* name in #get.1091* @constructor1092* @struct1093* @final1094*/1095goog.Uri.QueryData = function(opt_query, opt_ignoreCase) {1096'use strict';1097/**1098* The map containing name/value or name/array-of-values pairs.1099* May be null if it requires parsing from the query string.1100*1101* We need to use a Map because we cannot guarantee that the key names will1102* not be problematic for IE.1103*1104* @private {?Map<string, !Array<*>>}1105*/1106this.keyMap_ = null;11071108/**1109* The number of params, or null if it requires computing.1110* @private {?number}1111*/1112this.count_ = null;11131114/**1115* Encoded query string, or null if it requires computing from the key map.1116* @private {?string}1117*/1118this.encodedQuery_ = opt_query || null;11191120/**1121* If true, ignore the case of the parameter name in #get.1122* @private {boolean}1123*/1124this.ignoreCase_ = !!opt_ignoreCase;1125};112611271128/**1129* If the underlying key map is not yet initialized, it parses the1130* query string and fills the map with parsed data.1131* @private1132*/1133goog.Uri.QueryData.prototype.ensureKeyMapInitialized_ = function() {1134'use strict';1135if (!this.keyMap_) {1136this.keyMap_ = /** @type {!Map<string, !Array<*>>} */ (new Map());1137this.count_ = 0;1138if (this.encodedQuery_) {1139var self = this;1140goog.uri.utils.parseQueryData(this.encodedQuery_, function(name, value) {1141'use strict';1142self.add(goog.string.urlDecode(name), value);1143});1144}1145}1146};114711481149/**1150* Creates a new query data instance from a map of names and values.1151*1152* @param {!goog.collections.maps.MapLike<string, ?>|!Object} map Map of string1153* parameter names to parameter value. If parameter value is an array, it is1154* treated as if the key maps to each individual value in the1155* array.1156* @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter1157* name in #get.1158* @return {!goog.Uri.QueryData} The populated query data instance.1159*/1160goog.Uri.QueryData.createFromMap = function(map, opt_ignoreCase) {1161'use strict';1162var keys = goog.structs.getKeys(map);1163if (typeof keys == 'undefined') {1164throw new Error('Keys are undefined');1165}11661167var queryData = new goog.Uri.QueryData(null, opt_ignoreCase);1168var values = goog.structs.getValues(map);1169for (var i = 0; i < keys.length; i++) {1170var key = keys[i];1171var value = values[i];1172if (!Array.isArray(value)) {1173queryData.add(key, value);1174} else {1175queryData.setValues(key, value);1176}1177}1178return queryData;1179};118011811182/**1183* Creates a new query data instance from parallel arrays of parameter names1184* and values. Allows for duplicate parameter names. Throws an error if the1185* lengths of the arrays differ.1186*1187* @param {!Array<string>} keys Parameter names.1188* @param {!Array<?>} values Parameter values.1189* @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter1190* name in #get.1191* @return {!goog.Uri.QueryData} The populated query data instance.1192*/1193goog.Uri.QueryData.createFromKeysValues = function(1194keys, values, opt_ignoreCase) {1195'use strict';1196if (keys.length != values.length) {1197throw new Error('Mismatched lengths for keys/values');1198}1199var queryData = new goog.Uri.QueryData(null, opt_ignoreCase);1200for (var i = 0; i < keys.length; i++) {1201queryData.add(keys[i], values[i]);1202}1203return queryData;1204};120512061207/**1208* @return {?number} The number of parameters.1209*/1210goog.Uri.QueryData.prototype.getCount = function() {1211'use strict';1212this.ensureKeyMapInitialized_();1213return this.count_;1214};121512161217/**1218* Adds a key value pair.1219* @param {string} key Name.1220* @param {*} value Value.1221* @return {!goog.Uri.QueryData} Instance of this object.1222*/1223goog.Uri.QueryData.prototype.add = function(key, value) {1224'use strict';1225this.ensureKeyMapInitialized_();1226this.invalidateCache_();12271228key = this.getKeyName_(key);1229var values = this.keyMap_.get(key);1230if (!values) {1231this.keyMap_.set(key, (values = []));1232}1233values.push(value);1234this.count_ = goog.asserts.assertNumber(this.count_) + 1;1235return this;1236};123712381239/**1240* Removes all the params with the given key.1241* @param {string} key Name.1242* @return {boolean} Whether any parameter was removed.1243*/1244goog.Uri.QueryData.prototype.remove = function(key) {1245'use strict';1246this.ensureKeyMapInitialized_();12471248key = this.getKeyName_(key);1249if (this.keyMap_.has(key)) {1250this.invalidateCache_();12511252// Decrement parameter count.1253this.count_ =1254goog.asserts.assertNumber(this.count_) - this.keyMap_.get(key).length;1255return this.keyMap_.delete(key);1256}1257return false;1258};125912601261/**1262* Clears the parameters.1263*/1264goog.Uri.QueryData.prototype.clear = function() {1265'use strict';1266this.invalidateCache_();1267this.keyMap_ = null;1268this.count_ = 0;1269};127012711272/**1273* @return {boolean} Whether we have any parameters.1274*/1275goog.Uri.QueryData.prototype.isEmpty = function() {1276'use strict';1277this.ensureKeyMapInitialized_();1278return this.count_ == 0;1279};128012811282/**1283* Whether there is a parameter with the given name1284* @param {string} key The parameter name to check for.1285* @return {boolean} Whether there is a parameter with the given name.1286*/1287goog.Uri.QueryData.prototype.containsKey = function(key) {1288'use strict';1289this.ensureKeyMapInitialized_();1290key = this.getKeyName_(key);1291return this.keyMap_.has(key);1292};129312941295/**1296* Whether there is a parameter with the given value.1297* @param {*} value The value to check for.1298* @return {boolean} Whether there is a parameter with the given value.1299*/1300goog.Uri.QueryData.prototype.containsValue = function(value) {1301'use strict';1302// NOTE(arv): This solution goes through all the params even if it was the1303// first param. We can get around this by not reusing code or by switching to1304// iterators.1305var vals = this.getValues();1306return goog.array.contains(vals, value);1307};130813091310/**1311* Runs a callback on every key-value pair in the map, including duplicate keys.1312* This won't maintain original order when duplicate keys are interspersed (like1313* getKeys() / getValues()).1314* @param {function(this:SCOPE, ?, string, !goog.Uri.QueryData)} f1315* @param {SCOPE=} opt_scope The value of "this" inside f.1316* @template SCOPE1317*/1318goog.Uri.QueryData.prototype.forEach = function(f, opt_scope) {1319'use strict';1320this.ensureKeyMapInitialized_();1321this.keyMap_.forEach(function(values, key) {1322'use strict';1323values.forEach(function(value) {1324'use strict';1325f.call(opt_scope, value, key, this);1326}, this);1327}, this);1328};132913301331/**1332* Returns all the keys of the parameters. If a key is used multiple times1333* it will be included multiple times in the returned array1334* @return {!Array<string>} All the keys of the parameters.1335*/1336goog.Uri.QueryData.prototype.getKeys = function() {1337'use strict';1338this.ensureKeyMapInitialized_();1339// We need to get the values to know how many keys to add.1340const vals = Array.from(this.keyMap_.values());1341const keys = Array.from(this.keyMap_.keys());1342const rv = [];1343for (let i = 0; i < keys.length; i++) {1344const val = vals[i];1345for (let j = 0; j < val.length; j++) {1346rv.push(keys[i]);1347}1348}1349return rv;1350};135113521353/**1354* Returns all the values of the parameters with the given name. If the query1355* data has no such key this will return an empty array. If no key is given1356* all values wil be returned.1357* @param {string=} opt_key The name of the parameter to get the values for.1358* @return {!Array<?>} All the values of the parameters with the given name.1359*/1360goog.Uri.QueryData.prototype.getValues = function(opt_key) {1361'use strict';1362this.ensureKeyMapInitialized_();1363let rv = [];1364if (typeof opt_key === 'string') {1365if (this.containsKey(opt_key)) {1366rv = rv.concat(this.keyMap_.get(this.getKeyName_(opt_key)));1367}1368} else {1369// Return all values.1370const values = Array.from(this.keyMap_.values());1371for (let i = 0; i < values.length; i++) {1372rv = rv.concat(values[i]);1373}1374}1375return rv;1376};137713781379/**1380* Sets a key value pair and removes all other keys with the same value.1381*1382* @param {string} key Name.1383* @param {*} value Value.1384* @return {!goog.Uri.QueryData} Instance of this object.1385*/1386goog.Uri.QueryData.prototype.set = function(key, value) {1387'use strict';1388this.ensureKeyMapInitialized_();1389this.invalidateCache_();13901391// TODO(chrishenry): This could be better written as1392// this.remove(key), this.add(key, value), but that would reorder1393// the key (since the key is first removed and then added at the1394// end) and we would have to fix unit tests that depend on key1395// ordering.1396key = this.getKeyName_(key);1397if (this.containsKey(key)) {1398this.count_ =1399goog.asserts.assertNumber(this.count_) - this.keyMap_.get(key).length;1400}1401this.keyMap_.set(key, [value]);1402this.count_ = goog.asserts.assertNumber(this.count_) + 1;1403return this;1404};140514061407/**1408* Returns the first value associated with the key. If the query data has no1409* such key this will return undefined or the optional default.1410* @param {string} key The name of the parameter to get the value for.1411* @param {*=} opt_default The default value to return if the query data1412* has no such key.1413* @return {*} The first string value associated with the key, or opt_default1414* if there's no value.1415*/1416goog.Uri.QueryData.prototype.get = function(key, opt_default) {1417'use strict';1418if (!key) {1419return opt_default;1420}1421var values = this.getValues(key);1422return values.length > 0 ? String(values[0]) : opt_default;1423};142414251426/**1427* Sets the values for a key. If the key already exists, this will1428* override all of the existing values that correspond to the key.1429* @param {string} key The key to set values for.1430* @param {!Array<?>} values The values to set.1431*/1432goog.Uri.QueryData.prototype.setValues = function(key, values) {1433'use strict';1434this.remove(key);14351436if (values.length > 0) {1437this.invalidateCache_();1438this.keyMap_.set(this.getKeyName_(key), goog.array.clone(values));1439this.count_ = goog.asserts.assertNumber(this.count_) + values.length;1440}1441};144214431444/**1445* @return {string} Encoded query string.1446* @override1447*/1448goog.Uri.QueryData.prototype.toString = function() {1449'use strict';1450if (this.encodedQuery_) {1451return this.encodedQuery_;1452}14531454if (!this.keyMap_) {1455return '';1456}14571458const sb = [];14591460// In the past, we use this.getKeys() and this.getVals(), but that1461// generates a lot of allocations as compared to simply iterating1462// over the keys.1463const keys = Array.from(this.keyMap_.keys());1464for (var i = 0; i < keys.length; i++) {1465const key = keys[i];1466const encodedKey = goog.string.urlEncode(key);1467const val = this.getValues(key);1468for (var j = 0; j < val.length; j++) {1469var param = encodedKey;1470// Ensure that null and undefined are encoded into the url as1471// literal strings.1472if (val[j] !== '') {1473param += '=' + goog.string.urlEncode(val[j]);1474}1475sb.push(param);1476}1477}14781479return this.encodedQuery_ = sb.join('&');1480};148114821483/**1484* @throws URIError If URI is malformed (that is, if decodeURIComponent fails on1485* any of the URI components).1486* @return {string} Decoded query string.1487*/1488goog.Uri.QueryData.prototype.toDecodedString = function() {1489'use strict';1490return goog.Uri.decodeOrEmpty_(this.toString());1491};149214931494/**1495* Invalidate the cache.1496* @private1497*/1498goog.Uri.QueryData.prototype.invalidateCache_ = function() {1499'use strict';1500this.encodedQuery_ = null;1501};150215031504/**1505* Removes all keys that are not in the provided list. (Modifies this object.)1506* @param {Array<string>} keys The desired keys.1507* @return {!goog.Uri.QueryData} a reference to this object.1508*/1509goog.Uri.QueryData.prototype.filterKeys = function(keys) {1510'use strict';1511this.ensureKeyMapInitialized_();1512this.keyMap_.forEach(function(value, key) {1513'use strict';1514if (!goog.array.contains(keys, key)) {1515this.remove(key);1516}1517}, this);1518return this;1519};152015211522/**1523* Clone the query data instance.1524* @return {!goog.Uri.QueryData} New instance of the QueryData object.1525*/1526goog.Uri.QueryData.prototype.clone = function() {1527'use strict';1528var rv = new goog.Uri.QueryData();1529rv.encodedQuery_ = this.encodedQuery_;1530if (this.keyMap_) {1531rv.keyMap_ = /** @type {!Map<string, !Array<*>>} */ (new Map(this.keyMap_));1532rv.count_ = this.count_;1533}1534return rv;1535};153615371538/**1539* Helper function to get the key name from a JavaScript object. Converts1540* the object to a string, and to lower case if necessary.1541* @private1542* @param {*} arg The object to get a key name from.1543* @return {string} valid key name which can be looked up in #keyMap_.1544*/1545goog.Uri.QueryData.prototype.getKeyName_ = function(arg) {1546'use strict';1547var keyName = String(arg);1548if (this.ignoreCase_) {1549keyName = keyName.toLowerCase();1550}1551return keyName;1552};155315541555/**1556* Ignore case in parameter names.1557* NOTE: If there are already key/value pairs in the QueryData, and1558* ignoreCase_ is set to false, the keys will all be lower-cased.1559* @param {boolean} ignoreCase whether this goog.Uri should ignore case.1560*/1561goog.Uri.QueryData.prototype.setIgnoreCase = function(ignoreCase) {1562'use strict';1563var resetKeys = ignoreCase && !this.ignoreCase_;1564if (resetKeys) {1565this.ensureKeyMapInitialized_();1566this.invalidateCache_();1567this.keyMap_.forEach(function(value, key) {1568'use strict';1569var lowerCase = key.toLowerCase();1570if (key != lowerCase) {1571this.remove(key);1572this.setValues(lowerCase, value);1573}1574}, this);1575}1576this.ignoreCase_ = ignoreCase;1577};157815791580/**1581* Extends a query data object with another query data or map like object. This1582* operates 'in-place', it does not create a new QueryData object.1583*1584* @param {...(?goog.Uri.QueryData|?goog.collections.maps.MapLike<?,1585* ?>|?Object)} var_args The object from which key value pairs will be1586* copied. Note: does not accept null.1587* @suppress {deprecated} Use deprecated goog.structs.forEach to allow different1588* types of parameters.1589*/1590goog.Uri.QueryData.prototype.extend = function(var_args) {1591'use strict';1592for (var i = 0; i < arguments.length; i++) {1593var data = arguments[i];1594goog.structs.forEach(data, function(value, key) {1595'use strict';1596this.add(key, value);1597}, this);1598}1599};160016011602