utils.js 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Simple utilities for dealing with URI strings.
  16. *
  17. * This is intended to be a lightweight alternative to constructing goog.Uri
  18. * objects. Whereas goog.Uri adds several kilobytes to the binary regardless
  19. * of how much of its functionality you use, this is designed to be a set of
  20. * mostly-independent utilities so that the compiler includes only what is
  21. * necessary for the task. Estimated savings of porting is 5k pre-gzip and
  22. * 1.5k post-gzip. To ensure the savings remain, future developers should
  23. * avoid adding new functionality to existing functions, but instead create
  24. * new ones and factor out shared code.
  25. *
  26. * Many of these utilities have limited functionality, tailored to common
  27. * cases. The query parameter utilities assume that the parameter keys are
  28. * already encoded, since most keys are compile-time alphanumeric strings. The
  29. * query parameter mutation utilities also do not tolerate fragment identifiers.
  30. *
  31. * By design, these functions can be slower than goog.Uri equivalents.
  32. * Repeated calls to some of functions may be quadratic in behavior for IE,
  33. * although the effect is somewhat limited given the 2kb limit.
  34. *
  35. * One advantage of the limited functionality here is that this approach is
  36. * less sensitive to differences in URI encodings than goog.Uri, since these
  37. * functions operate on strings directly, rather than decoding them and
  38. * then re-encoding.
  39. *
  40. * Uses features of RFC 3986 for parsing/formatting URIs:
  41. * http://www.ietf.org/rfc/rfc3986.txt
  42. *
  43. * @author gboyer@google.com (Garrett Boyer) - The "lightened" design.
  44. * @author msamuel@google.com (Mike Samuel) - Domain knowledge and regexes.
  45. */
  46. goog.provide('goog.uri.utils');
  47. goog.provide('goog.uri.utils.ComponentIndex');
  48. goog.provide('goog.uri.utils.QueryArray');
  49. goog.provide('goog.uri.utils.QueryValue');
  50. goog.provide('goog.uri.utils.StandardQueryParam');
  51. goog.require('goog.array');
  52. goog.require('goog.asserts');
  53. goog.require('goog.string');
  54. /**
  55. * Character codes inlined to avoid object allocations due to charCode.
  56. * @enum {number}
  57. * @private
  58. */
  59. goog.uri.utils.CharCode_ = {
  60. AMPERSAND: 38,
  61. EQUAL: 61,
  62. HASH: 35,
  63. QUESTION: 63
  64. };
  65. /**
  66. * Builds a URI string from already-encoded parts.
  67. *
  68. * No encoding is performed. Any component may be omitted as either null or
  69. * undefined.
  70. *
  71. * @param {?string=} opt_scheme The scheme such as 'http'.
  72. * @param {?string=} opt_userInfo The user name before the '@'.
  73. * @param {?string=} opt_domain The domain such as 'www.google.com', already
  74. * URI-encoded.
  75. * @param {(string|number|null)=} opt_port The port number.
  76. * @param {?string=} opt_path The path, already URI-encoded. If it is not
  77. * empty, it must begin with a slash.
  78. * @param {?string=} opt_queryData The URI-encoded query data.
  79. * @param {?string=} opt_fragment The URI-encoded fragment identifier.
  80. * @return {string} The fully combined URI.
  81. */
  82. goog.uri.utils.buildFromEncodedParts = function(
  83. opt_scheme, opt_userInfo, opt_domain, opt_port, opt_path, opt_queryData,
  84. opt_fragment) {
  85. var out = '';
  86. if (opt_scheme) {
  87. out += opt_scheme + ':';
  88. }
  89. if (opt_domain) {
  90. out += '//';
  91. if (opt_userInfo) {
  92. out += opt_userInfo + '@';
  93. }
  94. out += opt_domain;
  95. if (opt_port) {
  96. out += ':' + opt_port;
  97. }
  98. }
  99. if (opt_path) {
  100. out += opt_path;
  101. }
  102. if (opt_queryData) {
  103. out += '?' + opt_queryData;
  104. }
  105. if (opt_fragment) {
  106. out += '#' + opt_fragment;
  107. }
  108. return out;
  109. };
  110. /**
  111. * A regular expression for breaking a URI into its component parts.
  112. *
  113. * {@link http://www.ietf.org/rfc/rfc3986.txt} says in Appendix B
  114. * As the "first-match-wins" algorithm is identical to the "greedy"
  115. * disambiguation method used by POSIX regular expressions, it is natural and
  116. * commonplace to use a regular expression for parsing the potential five
  117. * components of a URI reference.
  118. *
  119. * The following line is the regular expression for breaking-down a
  120. * well-formed URI reference into its components.
  121. *
  122. * <pre>
  123. * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
  124. * 12 3 4 5 6 7 8 9
  125. * </pre>
  126. *
  127. * The numbers in the second line above are only to assist readability; they
  128. * indicate the reference points for each subexpression (i.e., each paired
  129. * parenthesis). We refer to the value matched for subexpression <n> as $<n>.
  130. * For example, matching the above expression to
  131. * <pre>
  132. * http://www.ics.uci.edu/pub/ietf/uri/#Related
  133. * </pre>
  134. * results in the following subexpression matches:
  135. * <pre>
  136. * $1 = http:
  137. * $2 = http
  138. * $3 = //www.ics.uci.edu
  139. * $4 = www.ics.uci.edu
  140. * $5 = /pub/ietf/uri/
  141. * $6 = <undefined>
  142. * $7 = <undefined>
  143. * $8 = #Related
  144. * $9 = Related
  145. * </pre>
  146. * where <undefined> indicates that the component is not present, as is the
  147. * case for the query component in the above example. Therefore, we can
  148. * determine the value of the five components as
  149. * <pre>
  150. * scheme = $2
  151. * authority = $4
  152. * path = $5
  153. * query = $7
  154. * fragment = $9
  155. * </pre>
  156. *
  157. * The regular expression has been modified slightly to expose the
  158. * userInfo, domain, and port separately from the authority.
  159. * The modified version yields
  160. * <pre>
  161. * $1 = http scheme
  162. * $2 = <undefined> userInfo -\
  163. * $3 = www.ics.uci.edu domain | authority
  164. * $4 = <undefined> port -/
  165. * $5 = /pub/ietf/uri/ path
  166. * $6 = <undefined> query without ?
  167. * $7 = Related fragment without #
  168. * </pre>
  169. * @type {!RegExp}
  170. * @private
  171. */
  172. goog.uri.utils.splitRe_ = new RegExp(
  173. '^' +
  174. '(?:' +
  175. '([^:/?#.]+)' + // scheme - ignore special characters
  176. // used by other URL parts such as :,
  177. // ?, /, #, and .
  178. ':)?' +
  179. '(?://' +
  180. '(?:([^/?#]*)@)?' + // userInfo
  181. '([^/#?]*?)' + // domain
  182. '(?::([0-9]+))?' + // port
  183. '(?=[/#?]|$)' + // authority-terminating character
  184. ')?' +
  185. '([^?#]+)?' + // path
  186. '(?:\\?([^#]*))?' + // query
  187. '(?:#([\\s\\S]*))?' + // fragment
  188. '$');
  189. /**
  190. * The index of each URI component in the return value of goog.uri.utils.split.
  191. * @enum {number}
  192. */
  193. goog.uri.utils.ComponentIndex = {
  194. SCHEME: 1,
  195. USER_INFO: 2,
  196. DOMAIN: 3,
  197. PORT: 4,
  198. PATH: 5,
  199. QUERY_DATA: 6,
  200. FRAGMENT: 7
  201. };
  202. /**
  203. * Splits a URI into its component parts.
  204. *
  205. * Each component can be accessed via the component indices; for example:
  206. * <pre>
  207. * goog.uri.utils.split(someStr)[goog.uri.utils.ComponentIndex.QUERY_DATA];
  208. * </pre>
  209. *
  210. * @param {string} uri The URI string to examine.
  211. * @return {!Array<string|undefined>} Each component still URI-encoded.
  212. * Each component that is present will contain the encoded value, whereas
  213. * components that are not present will be undefined or empty, depending
  214. * on the browser's regular expression implementation. Never null, since
  215. * arbitrary strings may still look like path names.
  216. */
  217. goog.uri.utils.split = function(uri) {
  218. // See @return comment -- never null.
  219. return /** @type {!Array<string|undefined>} */ (
  220. uri.match(goog.uri.utils.splitRe_));
  221. };
  222. /**
  223. * @param {?string} uri A possibly null string.
  224. * @param {boolean=} opt_preserveReserved If true, percent-encoding of RFC-3986
  225. * reserved characters will not be removed.
  226. * @return {?string} The string URI-decoded, or null if uri is null.
  227. * @private
  228. */
  229. goog.uri.utils.decodeIfPossible_ = function(uri, opt_preserveReserved) {
  230. if (!uri) {
  231. return uri;
  232. }
  233. return opt_preserveReserved ? decodeURI(uri) : decodeURIComponent(uri);
  234. };
  235. /**
  236. * Gets a URI component by index.
  237. *
  238. * It is preferred to use the getPathEncoded() variety of functions ahead,
  239. * since they are more readable.
  240. *
  241. * @param {goog.uri.utils.ComponentIndex} componentIndex The component index.
  242. * @param {string} uri The URI to examine.
  243. * @return {?string} The still-encoded component, or null if the component
  244. * is not present.
  245. * @private
  246. */
  247. goog.uri.utils.getComponentByIndex_ = function(componentIndex, uri) {
  248. // Convert undefined, null, and empty string into null.
  249. return goog.uri.utils.split(uri)[componentIndex] || null;
  250. };
  251. /**
  252. * @param {string} uri The URI to examine.
  253. * @return {?string} The protocol or scheme, or null if none. Does not
  254. * include trailing colons or slashes.
  255. */
  256. goog.uri.utils.getScheme = function(uri) {
  257. return goog.uri.utils.getComponentByIndex_(
  258. goog.uri.utils.ComponentIndex.SCHEME, uri);
  259. };
  260. /**
  261. * Gets the effective scheme for the URL. If the URL is relative then the
  262. * scheme is derived from the page's location.
  263. * @param {string} uri The URI to examine.
  264. * @return {string} The protocol or scheme, always lower case.
  265. */
  266. goog.uri.utils.getEffectiveScheme = function(uri) {
  267. var scheme = goog.uri.utils.getScheme(uri);
  268. if (!scheme && goog.global.self && goog.global.self.location) {
  269. var protocol = goog.global.self.location.protocol;
  270. scheme = protocol.substr(0, protocol.length - 1);
  271. }
  272. // NOTE: When called from a web worker in Firefox 3.5, location maybe null.
  273. // All other browsers with web workers support self.location from the worker.
  274. return scheme ? scheme.toLowerCase() : '';
  275. };
  276. /**
  277. * @param {string} uri The URI to examine.
  278. * @return {?string} The user name still encoded, or null if none.
  279. */
  280. goog.uri.utils.getUserInfoEncoded = function(uri) {
  281. return goog.uri.utils.getComponentByIndex_(
  282. goog.uri.utils.ComponentIndex.USER_INFO, uri);
  283. };
  284. /**
  285. * @param {string} uri The URI to examine.
  286. * @return {?string} The decoded user info, or null if none.
  287. */
  288. goog.uri.utils.getUserInfo = function(uri) {
  289. return goog.uri.utils.decodeIfPossible_(
  290. goog.uri.utils.getUserInfoEncoded(uri));
  291. };
  292. /**
  293. * @param {string} uri The URI to examine.
  294. * @return {?string} The domain name still encoded, or null if none.
  295. */
  296. goog.uri.utils.getDomainEncoded = function(uri) {
  297. return goog.uri.utils.getComponentByIndex_(
  298. goog.uri.utils.ComponentIndex.DOMAIN, uri);
  299. };
  300. /**
  301. * @param {string} uri The URI to examine.
  302. * @return {?string} The decoded domain, or null if none.
  303. */
  304. goog.uri.utils.getDomain = function(uri) {
  305. return goog.uri.utils.decodeIfPossible_(
  306. goog.uri.utils.getDomainEncoded(uri), true /* opt_preserveReserved */);
  307. };
  308. /**
  309. * @param {string} uri The URI to examine.
  310. * @return {?number} The port number, or null if none.
  311. */
  312. goog.uri.utils.getPort = function(uri) {
  313. // Coerce to a number. If the result of getComponentByIndex_ is null or
  314. // non-numeric, the number coersion yields NaN. This will then return
  315. // null for all non-numeric cases (though also zero, which isn't a relevant
  316. // port number).
  317. return Number(
  318. goog.uri.utils.getComponentByIndex_(
  319. goog.uri.utils.ComponentIndex.PORT, uri)) ||
  320. null;
  321. };
  322. /**
  323. * @param {string} uri The URI to examine.
  324. * @return {?string} The path still encoded, or null if none. Includes the
  325. * leading slash, if any.
  326. */
  327. goog.uri.utils.getPathEncoded = function(uri) {
  328. return goog.uri.utils.getComponentByIndex_(
  329. goog.uri.utils.ComponentIndex.PATH, uri);
  330. };
  331. /**
  332. * @param {string} uri The URI to examine.
  333. * @return {?string} The decoded path, or null if none. Includes the leading
  334. * slash, if any.
  335. */
  336. goog.uri.utils.getPath = function(uri) {
  337. return goog.uri.utils.decodeIfPossible_(
  338. goog.uri.utils.getPathEncoded(uri), true /* opt_preserveReserved */);
  339. };
  340. /**
  341. * @param {string} uri The URI to examine.
  342. * @return {?string} The query data still encoded, or null if none. Does not
  343. * include the question mark itself.
  344. */
  345. goog.uri.utils.getQueryData = function(uri) {
  346. return goog.uri.utils.getComponentByIndex_(
  347. goog.uri.utils.ComponentIndex.QUERY_DATA, uri);
  348. };
  349. /**
  350. * @param {string} uri The URI to examine.
  351. * @return {?string} The fragment identifier, or null if none. Does not
  352. * include the hash mark itself.
  353. */
  354. goog.uri.utils.getFragmentEncoded = function(uri) {
  355. // The hash mark may not appear in any other part of the URL.
  356. var hashIndex = uri.indexOf('#');
  357. return hashIndex < 0 ? null : uri.substr(hashIndex + 1);
  358. };
  359. /**
  360. * @param {string} uri The URI to examine.
  361. * @param {?string} fragment The encoded fragment identifier, or null if none.
  362. * Does not include the hash mark itself.
  363. * @return {string} The URI with the fragment set.
  364. */
  365. goog.uri.utils.setFragmentEncoded = function(uri, fragment) {
  366. return goog.uri.utils.removeFragment(uri) + (fragment ? '#' + fragment : '');
  367. };
  368. /**
  369. * @param {string} uri The URI to examine.
  370. * @return {?string} The decoded fragment identifier, or null if none. Does
  371. * not include the hash mark.
  372. */
  373. goog.uri.utils.getFragment = function(uri) {
  374. return goog.uri.utils.decodeIfPossible_(
  375. goog.uri.utils.getFragmentEncoded(uri));
  376. };
  377. /**
  378. * Extracts everything up to the port of the URI.
  379. * @param {string} uri The URI string.
  380. * @return {string} Everything up to and including the port.
  381. */
  382. goog.uri.utils.getHost = function(uri) {
  383. var pieces = goog.uri.utils.split(uri);
  384. return goog.uri.utils.buildFromEncodedParts(
  385. pieces[goog.uri.utils.ComponentIndex.SCHEME],
  386. pieces[goog.uri.utils.ComponentIndex.USER_INFO],
  387. pieces[goog.uri.utils.ComponentIndex.DOMAIN],
  388. pieces[goog.uri.utils.ComponentIndex.PORT]);
  389. };
  390. /**
  391. * Returns the origin for a given URL.
  392. * @param {string} uri The URI string.
  393. * @return {string} Everything up to and including the port.
  394. */
  395. goog.uri.utils.getOrigin = function(uri) {
  396. var pieces = goog.uri.utils.split(uri);
  397. return goog.uri.utils.buildFromEncodedParts(
  398. pieces[goog.uri.utils.ComponentIndex.SCHEME], null /* opt_userInfo */,
  399. pieces[goog.uri.utils.ComponentIndex.DOMAIN],
  400. pieces[goog.uri.utils.ComponentIndex.PORT]);
  401. };
  402. /**
  403. * Extracts the path of the URL and everything after.
  404. * @param {string} uri The URI string.
  405. * @return {string} The URI, starting at the path and including the query
  406. * parameters and fragment identifier.
  407. */
  408. goog.uri.utils.getPathAndAfter = function(uri) {
  409. var pieces = goog.uri.utils.split(uri);
  410. return goog.uri.utils.buildFromEncodedParts(
  411. null, null, null, null, pieces[goog.uri.utils.ComponentIndex.PATH],
  412. pieces[goog.uri.utils.ComponentIndex.QUERY_DATA],
  413. pieces[goog.uri.utils.ComponentIndex.FRAGMENT]);
  414. };
  415. /**
  416. * Gets the URI with the fragment identifier removed.
  417. * @param {string} uri The URI to examine.
  418. * @return {string} Everything preceding the hash mark.
  419. */
  420. goog.uri.utils.removeFragment = function(uri) {
  421. // The hash mark may not appear in any other part of the URL.
  422. var hashIndex = uri.indexOf('#');
  423. return hashIndex < 0 ? uri : uri.substr(0, hashIndex);
  424. };
  425. /**
  426. * Ensures that two URI's have the exact same domain, scheme, and port.
  427. *
  428. * Unlike the version in goog.Uri, this checks protocol, and therefore is
  429. * suitable for checking against the browser's same-origin policy.
  430. *
  431. * @param {string} uri1 The first URI.
  432. * @param {string} uri2 The second URI.
  433. * @return {boolean} Whether they have the same scheme, domain and port.
  434. */
  435. goog.uri.utils.haveSameDomain = function(uri1, uri2) {
  436. var pieces1 = goog.uri.utils.split(uri1);
  437. var pieces2 = goog.uri.utils.split(uri2);
  438. return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] ==
  439. pieces2[goog.uri.utils.ComponentIndex.DOMAIN] &&
  440. pieces1[goog.uri.utils.ComponentIndex.SCHEME] ==
  441. pieces2[goog.uri.utils.ComponentIndex.SCHEME] &&
  442. pieces1[goog.uri.utils.ComponentIndex.PORT] ==
  443. pieces2[goog.uri.utils.ComponentIndex.PORT];
  444. };
  445. /**
  446. * Asserts that there are no fragment or query identifiers, only in uncompiled
  447. * mode.
  448. * @param {string} uri The URI to examine.
  449. * @private
  450. */
  451. goog.uri.utils.assertNoFragmentsOrQueries_ = function(uri) {
  452. goog.asserts.assert(
  453. uri.indexOf('#') < 0 && uri.indexOf('?') < 0,
  454. 'goog.uri.utils: Fragment or query identifiers are not supported: [%s]',
  455. uri);
  456. };
  457. /**
  458. * Supported query parameter values by the parameter serializing utilities.
  459. *
  460. * If a value is null or undefined, the key-value pair is skipped, as an easy
  461. * way to omit parameters conditionally. Non-array parameters are converted
  462. * to a string and URI encoded. Array values are expanded into multiple
  463. * &key=value pairs, with each element stringized and URI-encoded.
  464. *
  465. * @typedef {*}
  466. */
  467. goog.uri.utils.QueryValue;
  468. /**
  469. * An array representing a set of query parameters with alternating keys
  470. * and values.
  471. *
  472. * Keys are assumed to be URI encoded already and live at even indices. See
  473. * goog.uri.utils.QueryValue for details on how parameter values are encoded.
  474. *
  475. * Example:
  476. * <pre>
  477. * var data = [
  478. * // Simple param: ?name=BobBarker
  479. * 'name', 'BobBarker',
  480. * // Conditional param -- may be omitted entirely.
  481. * 'specialDietaryNeeds', hasDietaryNeeds() ? getDietaryNeeds() : null,
  482. * // Multi-valued param: &house=LosAngeles&house=NewYork&house=null
  483. * 'house', ['LosAngeles', 'NewYork', null]
  484. * ];
  485. * </pre>
  486. *
  487. * @typedef {!Array<string|goog.uri.utils.QueryValue>}
  488. */
  489. goog.uri.utils.QueryArray;
  490. /**
  491. * Parses encoded query parameters and calls callback function for every
  492. * parameter found in the string.
  493. *
  494. * Missing value of parameter (e.g. “…&key&…”) is treated as if the value was an
  495. * empty string. Keys may be empty strings (e.g. “…&=value&…”) which also means
  496. * that “…&=&…” and “…&&…” will result in an empty key and value.
  497. *
  498. * @param {string} encodedQuery Encoded query string excluding question mark at
  499. * the beginning.
  500. * @param {function(string, string)} callback Function called for every
  501. * parameter found in query string. The first argument (name) will not be
  502. * urldecoded (so the function is consistent with buildQueryData), but the
  503. * second will. If the parameter has no value (i.e. “=” was not present)
  504. * the second argument (value) will be an empty string.
  505. */
  506. goog.uri.utils.parseQueryData = function(encodedQuery, callback) {
  507. if (!encodedQuery) {
  508. return;
  509. }
  510. var pairs = encodedQuery.split('&');
  511. for (var i = 0; i < pairs.length; i++) {
  512. var indexOfEquals = pairs[i].indexOf('=');
  513. var name = null;
  514. var value = null;
  515. if (indexOfEquals >= 0) {
  516. name = pairs[i].substring(0, indexOfEquals);
  517. value = pairs[i].substring(indexOfEquals + 1);
  518. } else {
  519. name = pairs[i];
  520. }
  521. callback(name, value ? goog.string.urlDecode(value) : '');
  522. }
  523. };
  524. /**
  525. * Split the URI into 3 parts where the [1] is the queryData without a leading
  526. * '?'. For example, the URI http://foo.com/bar?a=b#abc returns
  527. * ['http://foo.com/bar','a=b','#abc'].
  528. * @param {string} uri The URI to parse.
  529. * @return {!Array<string>} An array representation of uri of length 3 where the
  530. * middle value is the queryData without a leading '?'.
  531. * @private
  532. */
  533. goog.uri.utils.splitQueryData_ = function(uri) {
  534. // Find the query data and and hash.
  535. var hashIndex = uri.indexOf('#');
  536. if (hashIndex < 0) {
  537. hashIndex = uri.length;
  538. }
  539. var questionIndex = uri.indexOf('?');
  540. var queryData;
  541. if (questionIndex < 0 || questionIndex > hashIndex) {
  542. questionIndex = hashIndex;
  543. queryData = '';
  544. } else {
  545. queryData = uri.substring(questionIndex + 1, hashIndex);
  546. }
  547. return [uri.substr(0, questionIndex), queryData, uri.substr(hashIndex)];
  548. };
  549. /**
  550. * Join an array created by splitQueryData_ back into a URI.
  551. * @param {!Array<string>} parts A URI in the form generated by splitQueryData_.
  552. * @return {string} The joined URI.
  553. * @private
  554. */
  555. goog.uri.utils.joinQueryData_ = function(parts) {
  556. return parts[0] + (parts[1] ? '?' + parts[1] : '') + parts[2];
  557. };
  558. /**
  559. * @param {string} queryData
  560. * @param {string} newData
  561. * @return {string}
  562. * @private
  563. */
  564. goog.uri.utils.appendQueryData_ = function(queryData, newData) {
  565. if (!newData) {
  566. return queryData;
  567. }
  568. return queryData ? queryData + '&' + newData : newData;
  569. };
  570. /**
  571. * @param {string} uri
  572. * @param {string} queryData
  573. * @return {string}
  574. * @private
  575. */
  576. goog.uri.utils.appendQueryDataToUri_ = function(uri, queryData) {
  577. if (!queryData) {
  578. return uri;
  579. }
  580. var parts = goog.uri.utils.splitQueryData_(uri);
  581. parts[1] = goog.uri.utils.appendQueryData_(parts[1], queryData);
  582. return goog.uri.utils.joinQueryData_(parts);
  583. };
  584. /**
  585. * Appends key=value pairs to an array, supporting multi-valued objects.
  586. * @param {*} key The key prefix.
  587. * @param {goog.uri.utils.QueryValue} value The value to serialize.
  588. * @param {!Array<string>} pairs The array to which the 'key=value' strings
  589. * should be appended.
  590. * @private
  591. */
  592. goog.uri.utils.appendKeyValuePairs_ = function(key, value, pairs) {
  593. goog.asserts.assertString(key);
  594. if (goog.isArray(value)) {
  595. // Convince the compiler it's an array.
  596. goog.asserts.assertArray(value);
  597. for (var j = 0; j < value.length; j++) {
  598. // Convert to string explicitly, to short circuit the null and array
  599. // logic in this function -- this ensures that null and undefined get
  600. // written as literal 'null' and 'undefined', and arrays don't get
  601. // expanded out but instead encoded in the default way.
  602. goog.uri.utils.appendKeyValuePairs_(key, String(value[j]), pairs);
  603. }
  604. } else if (value != null) {
  605. // Skip a top-level null or undefined entirely.
  606. pairs.push(
  607. key +
  608. // Check for empty string. Zero gets encoded into the url as literal
  609. // strings. For empty string, skip the equal sign, to be consistent
  610. // with UriBuilder.java.
  611. (value === '' ? '' : '=' + goog.string.urlEncode(value)));
  612. }
  613. };
  614. /**
  615. * Builds a query data string from a sequence of alternating keys and values.
  616. * Currently generates "&key&" for empty args.
  617. *
  618. * @param {!IArrayLike<string|goog.uri.utils.QueryValue>} keysAndValues
  619. * Alternating keys and values. See the QueryArray typedef.
  620. * @param {number=} opt_startIndex A start offset into the arary, defaults to 0.
  621. * @return {string} The encoded query string, in the form 'a=1&b=2'.
  622. */
  623. goog.uri.utils.buildQueryData = function(keysAndValues, opt_startIndex) {
  624. goog.asserts.assert(
  625. Math.max(keysAndValues.length - (opt_startIndex || 0), 0) % 2 == 0,
  626. 'goog.uri.utils: Key/value lists must be even in length.');
  627. var params = [];
  628. for (var i = opt_startIndex || 0; i < keysAndValues.length; i += 2) {
  629. var key = /** @type {string} */ (keysAndValues[i]);
  630. goog.uri.utils.appendKeyValuePairs_(key, keysAndValues[i + 1], params);
  631. }
  632. return params.join('&');
  633. };
  634. /**
  635. * Builds a query data string from a map.
  636. * Currently generates "&key&" for empty args.
  637. *
  638. * @param {!Object<string, goog.uri.utils.QueryValue>} map An object where keys
  639. * are URI-encoded parameter keys, and the values are arbitrary types
  640. * or arrays. Keys with a null value are dropped.
  641. * @return {string} The encoded query string, in the form 'a=1&b=2'.
  642. */
  643. goog.uri.utils.buildQueryDataFromMap = function(map) {
  644. var params = [];
  645. for (var key in map) {
  646. goog.uri.utils.appendKeyValuePairs_(key, map[key], params);
  647. }
  648. return params.join('&');
  649. };
  650. /**
  651. * Appends URI parameters to an existing URI.
  652. *
  653. * The variable arguments may contain alternating keys and values. Keys are
  654. * assumed to be already URI encoded. The values should not be URI-encoded,
  655. * and will instead be encoded by this function.
  656. * <pre>
  657. * appendParams('http://www.foo.com?existing=true',
  658. * 'key1', 'value1',
  659. * 'key2', 'value?willBeEncoded',
  660. * 'key3', ['valueA', 'valueB', 'valueC'],
  661. * 'key4', null);
  662. * result: 'http://www.foo.com?existing=true&' +
  663. * 'key1=value1&' +
  664. * 'key2=value%3FwillBeEncoded&' +
  665. * 'key3=valueA&key3=valueB&key3=valueC'
  666. * </pre>
  667. *
  668. * A single call to this function will not exhibit quadratic behavior in IE,
  669. * whereas multiple repeated calls may, although the effect is limited by
  670. * fact that URL's generally can't exceed 2kb.
  671. *
  672. * @param {string} uri The original URI, which may already have query data.
  673. * @param {...(goog.uri.utils.QueryArray|goog.uri.utils.QueryValue)}
  674. * var_args
  675. * An array or argument list conforming to goog.uri.utils.QueryArray.
  676. * @return {string} The URI with all query parameters added.
  677. */
  678. goog.uri.utils.appendParams = function(uri, var_args) {
  679. var queryData = arguments.length == 2 ?
  680. goog.uri.utils.buildQueryData(arguments[1], 0) :
  681. goog.uri.utils.buildQueryData(arguments, 1);
  682. return goog.uri.utils.appendQueryDataToUri_(uri, queryData);
  683. };
  684. /**
  685. * Appends query parameters from a map.
  686. *
  687. * @param {string} uri The original URI, which may already have query data.
  688. * @param {!Object<goog.uri.utils.QueryValue>} map An object where keys are
  689. * URI-encoded parameter keys, and the values are arbitrary types or arrays.
  690. * Keys with a null value are dropped.
  691. * @return {string} The new parameters.
  692. */
  693. goog.uri.utils.appendParamsFromMap = function(uri, map) {
  694. var queryData = goog.uri.utils.buildQueryDataFromMap(map);
  695. return goog.uri.utils.appendQueryDataToUri_(uri, queryData);
  696. };
  697. /**
  698. * Appends a single URI parameter.
  699. *
  700. * Repeated calls to this can exhibit quadratic behavior in IE6 due to the
  701. * way string append works, though it should be limited given the 2kb limit.
  702. *
  703. * @param {string} uri The original URI, which may already have query data.
  704. * @param {string} key The key, which must already be URI encoded.
  705. * @param {*=} opt_value The value, which will be stringized and encoded
  706. * (assumed not already to be encoded). If omitted, undefined, or null, the
  707. * key will be added as a valueless parameter.
  708. * @return {string} The URI with the query parameter added.
  709. */
  710. goog.uri.utils.appendParam = function(uri, key, opt_value) {
  711. var value = goog.isDefAndNotNull(opt_value) ?
  712. '=' + goog.string.urlEncode(opt_value) :
  713. '';
  714. return goog.uri.utils.appendQueryDataToUri_(uri, key + value);
  715. };
  716. /**
  717. * Finds the next instance of a query parameter with the specified name.
  718. *
  719. * Does not instantiate any objects.
  720. *
  721. * @param {string} uri The URI to search. May contain a fragment identifier
  722. * if opt_hashIndex is specified.
  723. * @param {number} startIndex The index to begin searching for the key at. A
  724. * match may be found even if this is one character after the ampersand.
  725. * @param {string} keyEncoded The URI-encoded key.
  726. * @param {number} hashOrEndIndex Index to stop looking at. If a hash
  727. * mark is present, it should be its index, otherwise it should be the
  728. * length of the string.
  729. * @return {number} The position of the first character in the key's name,
  730. * immediately after either a question mark or a dot.
  731. * @private
  732. */
  733. goog.uri.utils.findParam_ = function(
  734. uri, startIndex, keyEncoded, hashOrEndIndex) {
  735. var index = startIndex;
  736. var keyLength = keyEncoded.length;
  737. // Search for the key itself and post-filter for surronuding punctuation,
  738. // rather than expensively building a regexp.
  739. while ((index = uri.indexOf(keyEncoded, index)) >= 0 &&
  740. index < hashOrEndIndex) {
  741. var precedingChar = uri.charCodeAt(index - 1);
  742. // Ensure that the preceding character is '&' or '?'.
  743. if (precedingChar == goog.uri.utils.CharCode_.AMPERSAND ||
  744. precedingChar == goog.uri.utils.CharCode_.QUESTION) {
  745. // Ensure the following character is '&', '=', '#', or NaN
  746. // (end of string).
  747. var followingChar = uri.charCodeAt(index + keyLength);
  748. if (!followingChar || followingChar == goog.uri.utils.CharCode_.EQUAL ||
  749. followingChar == goog.uri.utils.CharCode_.AMPERSAND ||
  750. followingChar == goog.uri.utils.CharCode_.HASH) {
  751. return index;
  752. }
  753. }
  754. index += keyLength + 1;
  755. }
  756. return -1;
  757. };
  758. /**
  759. * Regular expression for finding a hash mark or end of string.
  760. * @type {RegExp}
  761. * @private
  762. */
  763. goog.uri.utils.hashOrEndRe_ = /#|$/;
  764. /**
  765. * Determines if the URI contains a specific key.
  766. *
  767. * Performs no object instantiations.
  768. *
  769. * @param {string} uri The URI to process. May contain a fragment
  770. * identifier.
  771. * @param {string} keyEncoded The URI-encoded key. Case-sensitive.
  772. * @return {boolean} Whether the key is present.
  773. */
  774. goog.uri.utils.hasParam = function(uri, keyEncoded) {
  775. return goog.uri.utils.findParam_(
  776. uri, 0, keyEncoded, uri.search(goog.uri.utils.hashOrEndRe_)) >= 0;
  777. };
  778. /**
  779. * Gets the first value of a query parameter.
  780. * @param {string} uri The URI to process. May contain a fragment.
  781. * @param {string} keyEncoded The URI-encoded key. Case-sensitive.
  782. * @return {?string} The first value of the parameter (URI-decoded), or null
  783. * if the parameter is not found.
  784. */
  785. goog.uri.utils.getParamValue = function(uri, keyEncoded) {
  786. var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
  787. var foundIndex =
  788. goog.uri.utils.findParam_(uri, 0, keyEncoded, hashOrEndIndex);
  789. if (foundIndex < 0) {
  790. return null;
  791. } else {
  792. var endPosition = uri.indexOf('&', foundIndex);
  793. if (endPosition < 0 || endPosition > hashOrEndIndex) {
  794. endPosition = hashOrEndIndex;
  795. }
  796. // Progress forth to the end of the "key=" or "key&" substring.
  797. foundIndex += keyEncoded.length + 1;
  798. // Use substr, because it (unlike substring) will return empty string
  799. // if foundIndex > endPosition.
  800. return goog.string.urlDecode(
  801. uri.substr(foundIndex, endPosition - foundIndex));
  802. }
  803. };
  804. /**
  805. * Gets all values of a query parameter.
  806. * @param {string} uri The URI to process. May contain a fragment.
  807. * @param {string} keyEncoded The URI-encoded key. Case-sensitive.
  808. * @return {!Array<string>} All URI-decoded values with the given key.
  809. * If the key is not found, this will have length 0, but never be null.
  810. */
  811. goog.uri.utils.getParamValues = function(uri, keyEncoded) {
  812. var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
  813. var position = 0;
  814. var foundIndex;
  815. var result = [];
  816. while ((foundIndex = goog.uri.utils.findParam_(
  817. uri, position, keyEncoded, hashOrEndIndex)) >= 0) {
  818. // Find where this parameter ends, either the '&' or the end of the
  819. // query parameters.
  820. position = uri.indexOf('&', foundIndex);
  821. if (position < 0 || position > hashOrEndIndex) {
  822. position = hashOrEndIndex;
  823. }
  824. // Progress forth to the end of the "key=" or "key&" substring.
  825. foundIndex += keyEncoded.length + 1;
  826. // Use substr, because it (unlike substring) will return empty string
  827. // if foundIndex > position.
  828. result.push(
  829. goog.string.urlDecode(uri.substr(foundIndex, position - foundIndex)));
  830. }
  831. return result;
  832. };
  833. /**
  834. * Regexp to find trailing question marks and ampersands.
  835. * @type {RegExp}
  836. * @private
  837. */
  838. goog.uri.utils.trailingQueryPunctuationRe_ = /[?&]($|#)/;
  839. /**
  840. * Removes all instances of a query parameter.
  841. * @param {string} uri The URI to process. Must not contain a fragment.
  842. * @param {string} keyEncoded The URI-encoded key.
  843. * @return {string} The URI with all instances of the parameter removed.
  844. */
  845. goog.uri.utils.removeParam = function(uri, keyEncoded) {
  846. var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
  847. var position = 0;
  848. var foundIndex;
  849. var buffer = [];
  850. // Look for a query parameter.
  851. while ((foundIndex = goog.uri.utils.findParam_(
  852. uri, position, keyEncoded, hashOrEndIndex)) >= 0) {
  853. // Get the portion of the query string up to, but not including, the ?
  854. // or & starting the parameter.
  855. buffer.push(uri.substring(position, foundIndex));
  856. // Progress to immediately after the '&'. If not found, go to the end.
  857. // Avoid including the hash mark.
  858. position = Math.min(
  859. (uri.indexOf('&', foundIndex) + 1) || hashOrEndIndex, hashOrEndIndex);
  860. }
  861. // Append everything that is remaining.
  862. buffer.push(uri.substr(position));
  863. // Join the buffer, and remove trailing punctuation that remains.
  864. return buffer.join('').replace(
  865. goog.uri.utils.trailingQueryPunctuationRe_, '$1');
  866. };
  867. /**
  868. * Replaces all existing definitions of a parameter with a single definition.
  869. *
  870. * Repeated calls to this can exhibit quadratic behavior due to the need to
  871. * find existing instances and reconstruct the string, though it should be
  872. * limited given the 2kb limit. Consider using appendParams or setParamsFromMap
  873. * to update multiple parameters in bulk.
  874. *
  875. * @param {string} uri The original URI, which may already have query data.
  876. * @param {string} keyEncoded The key, which must already be URI encoded.
  877. * @param {*} value The value, which will be stringized and encoded (assumed
  878. * not already to be encoded).
  879. * @return {string} The URI with the query parameter added.
  880. */
  881. goog.uri.utils.setParam = function(uri, keyEncoded, value) {
  882. return goog.uri.utils.appendParam(
  883. goog.uri.utils.removeParam(uri, keyEncoded), keyEncoded, value);
  884. };
  885. /**
  886. * Effeciently set or remove multiple query parameters in a URI. Order of
  887. * unchanged parameters will not be modified, all updated parameters will be
  888. * appended to the end of the query. Params with values of null or undefined are
  889. * removed.
  890. *
  891. * @param {string} uri The URI to process.
  892. * @param {!Object<string, goog.uri.utils.QueryValue>} params A list of
  893. * parameters to update. If null or undefined, the param will be removed.
  894. * @return {string} An updated URI where the query data has been updated with
  895. * the params.
  896. */
  897. goog.uri.utils.setParamsFromMap = function(uri, params) {
  898. var parts = goog.uri.utils.splitQueryData_(uri);
  899. var queryData = parts[1];
  900. var buffer = [];
  901. if (queryData) {
  902. goog.array.forEach(queryData.split('&'), function(pair) {
  903. var indexOfEquals = pair.indexOf('=');
  904. var name = indexOfEquals >= 0 ? pair.substr(0, indexOfEquals) : pair;
  905. if (!params.hasOwnProperty(name)) {
  906. buffer.push(pair);
  907. }
  908. });
  909. }
  910. parts[1] = goog.uri.utils.appendQueryData_(
  911. buffer.join('&'), goog.uri.utils.buildQueryDataFromMap(params));
  912. return goog.uri.utils.joinQueryData_(parts);
  913. };
  914. /**
  915. * Generates a URI path using a given URI and a path with checks to
  916. * prevent consecutive "//". The baseUri passed in must not contain
  917. * query or fragment identifiers. The path to append may not contain query or
  918. * fragment identifiers.
  919. *
  920. * @param {string} baseUri URI to use as the base.
  921. * @param {string} path Path to append.
  922. * @return {string} Updated URI.
  923. */
  924. goog.uri.utils.appendPath = function(baseUri, path) {
  925. goog.uri.utils.assertNoFragmentsOrQueries_(baseUri);
  926. // Remove any trailing '/'
  927. if (goog.string.endsWith(baseUri, '/')) {
  928. baseUri = baseUri.substr(0, baseUri.length - 1);
  929. }
  930. // Remove any leading '/'
  931. if (goog.string.startsWith(path, '/')) {
  932. path = path.substr(1);
  933. }
  934. return goog.string.buildString(baseUri, '/', path);
  935. };
  936. /**
  937. * Replaces the path.
  938. * @param {string} uri URI to use as the base.
  939. * @param {string} path New path.
  940. * @return {string} Updated URI.
  941. */
  942. goog.uri.utils.setPath = function(uri, path) {
  943. // Add any missing '/'.
  944. if (!goog.string.startsWith(path, '/')) {
  945. path = '/' + path;
  946. }
  947. var parts = goog.uri.utils.split(uri);
  948. return goog.uri.utils.buildFromEncodedParts(
  949. parts[goog.uri.utils.ComponentIndex.SCHEME],
  950. parts[goog.uri.utils.ComponentIndex.USER_INFO],
  951. parts[goog.uri.utils.ComponentIndex.DOMAIN],
  952. parts[goog.uri.utils.ComponentIndex.PORT], path,
  953. parts[goog.uri.utils.ComponentIndex.QUERY_DATA],
  954. parts[goog.uri.utils.ComponentIndex.FRAGMENT]);
  955. };
  956. /**
  957. * Standard supported query parameters.
  958. * @enum {string}
  959. */
  960. goog.uri.utils.StandardQueryParam = {
  961. /** Unused parameter for unique-ifying. */
  962. RANDOM: 'zx'
  963. };
  964. /**
  965. * Sets the zx parameter of a URI to a random value.
  966. * @param {string} uri Any URI.
  967. * @return {string} That URI with the "zx" parameter added or replaced to
  968. * contain a random string.
  969. */
  970. goog.uri.utils.makeUnique = function(uri) {
  971. return goog.uri.utils.setParam(
  972. uri, goog.uri.utils.StandardQueryParam.RANDOM,
  973. goog.string.getRandomString());
  974. };