Uri.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /**
  2. * @license
  3. *
  4. * Grauw URI utilities
  5. *
  6. * See: http://hg.grauw.nl/grauw-lib/file/tip/src/uri.js
  7. *
  8. * @author Laurens Holst (http://www.grauw.nl/)
  9. *
  10. * Copyright 2012 Laurens Holst
  11. *
  12. * Licensed under the Apache License, Version 2.0 (the "License");
  13. * you may not use this file except in compliance with the License.
  14. * You may obtain a copy of the License at
  15. *
  16. * http://www.apache.org/licenses/LICENSE-2.0
  17. *
  18. * Unless required by applicable law or agreed to in writing, software
  19. * distributed under the License is distributed on an "AS IS" BASIS,
  20. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21. * See the License for the specific language governing permissions and
  22. * limitations under the License.
  23. *
  24. */
  25. /*global define*/
  26. define(function() {
  27. /**
  28. * Constructs a URI object.
  29. * @constructor
  30. * @class Implementation of URI parsing and base URI resolving algorithm in RFC 3986.
  31. * @param {string|URI} uri A string or URI object to create the object from.
  32. */
  33. function URI(uri) {
  34. if (uri instanceof URI) { // copy constructor
  35. this.scheme = uri.scheme;
  36. this.authority = uri.authority;
  37. this.path = uri.path;
  38. this.query = uri.query;
  39. this.fragment = uri.fragment;
  40. } else if (uri) { // uri is URI string or cast to string
  41. var c = parseRegex.exec(uri);
  42. this.scheme = c[1];
  43. this.authority = c[2];
  44. this.path = c[3];
  45. this.query = c[4];
  46. this.fragment = c[5];
  47. }
  48. };
  49. // Initial values on the prototype
  50. URI.prototype.scheme = null;
  51. URI.prototype.authority = null;
  52. URI.prototype.path = '';
  53. URI.prototype.query = null;
  54. URI.prototype.fragment = null;
  55. // Regular expression from RFC 3986 appendix B
  56. var parseRegex = new RegExp('^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\\?([^#]*))?(?:#(.*))?$');
  57. /**
  58. * Returns the scheme part of the URI.
  59. * In "http://example.com:80/a/b?x#y" this is "http".
  60. */
  61. URI.prototype.getScheme = function() {
  62. return this.scheme;
  63. };
  64. /**
  65. * Returns the authority part of the URI.
  66. * In "http://example.com:80/a/b?x#y" this is "example.com:80".
  67. */
  68. URI.prototype.getAuthority = function() {
  69. return this.authority;
  70. };
  71. /**
  72. * Returns the path part of the URI.
  73. * In "http://example.com:80/a/b?x#y" this is "/a/b".
  74. * In "mailto:mike@example.com" this is "mike@example.com".
  75. */
  76. URI.prototype.getPath = function() {
  77. return this.path;
  78. };
  79. /**
  80. * Returns the query part of the URI.
  81. * In "http://example.com:80/a/b?x#y" this is "x".
  82. */
  83. URI.prototype.getQuery = function() {
  84. return this.query;
  85. };
  86. /**
  87. * Returns the fragment part of the URI.
  88. * In "http://example.com:80/a/b?x#y" this is "y".
  89. */
  90. URI.prototype.getFragment = function() {
  91. return this.fragment;
  92. };
  93. /**
  94. * Tests whether the URI is an absolute URI.
  95. * See RFC 3986 section 4.3.
  96. */
  97. URI.prototype.isAbsolute = function() {
  98. return !!this.scheme && !this.fragment;
  99. };
  100. ///**
  101. //* Extensive validation of the URI against the ABNF in RFC 3986
  102. //*/
  103. //URI.prototype.validate
  104. /**
  105. * Tests whether the URI is a same-document reference.
  106. * See RFC 3986 section 4.4.
  107. *
  108. * To perform more thorough comparison, you can normalise the URI objects.
  109. */
  110. URI.prototype.isSameDocumentAs = function(uri) {
  111. return uri.scheme == this.scheme &&
  112. uri.authority == this.authority &&
  113. uri.path == this.path &&
  114. uri.query == this.query;
  115. };
  116. /**
  117. * Simple String Comparison of two URIs.
  118. * See RFC 3986 section 6.2.1.
  119. *
  120. * To perform more thorough comparison, you can normalise the URI objects.
  121. */
  122. URI.prototype.equals = function(uri) {
  123. return this.isSameDocumentAs(uri) && uri.fragment == this.fragment;
  124. };
  125. /**
  126. * Normalizes the URI using syntax-based normalization.
  127. * This includes case normalization, percent-encoding normalization and path segment normalization.
  128. * XXX: Percent-encoding normalization does not escape characters that need to be escaped.
  129. * (Although that would not be a valid URI in the first place. See validate().)
  130. * See RFC 3986 section 6.2.2.
  131. */
  132. URI.prototype.normalize = function() {
  133. this.removeDotSegments();
  134. if (this.scheme)
  135. this.scheme = this.scheme.toLowerCase();
  136. if (this.authority)
  137. this.authority = this.authority.replace(authorityRegex, replaceAuthority).
  138. replace(caseRegex, replaceCase);
  139. if (this.path)
  140. this.path = this.path.replace(caseRegex, replaceCase);
  141. if (this.query)
  142. this.query = this.query.replace(caseRegex, replaceCase);
  143. if (this.fragment)
  144. this.fragment = this.fragment.replace(caseRegex, replaceCase);
  145. };
  146. var caseRegex = /%[0-9a-z]{2}/gi;
  147. var percentRegex = /[a-zA-Z0-9\-\._~]/;
  148. var authorityRegex = /(.*@)?([^@:]*)(:.*)?/;
  149. function replaceCase(str) {
  150. var dec = unescape(str);
  151. return percentRegex.test(dec) ? dec : str.toUpperCase();
  152. }
  153. function replaceAuthority(str, p1, p2, p3) {
  154. return (p1 || '') + p2.toLowerCase() + (p3 || '');
  155. }
  156. /**
  157. * Resolve a relative URI (this) against a base URI.
  158. * The base URI must be an absolute URI.
  159. * See RFC 3986 section 5.2
  160. */
  161. URI.prototype.resolve = function(baseURI) {
  162. var uri = new URI();
  163. if (this.scheme) {
  164. uri.scheme = this.scheme;
  165. uri.authority = this.authority;
  166. uri.path = this.path;
  167. uri.query = this.query;
  168. } else {
  169. uri.scheme = baseURI.scheme;
  170. if (this.authority) {
  171. uri.authority = this.authority;
  172. uri.path = this.path;
  173. uri.query = this.query;
  174. } else {
  175. uri.authority = baseURI.authority;
  176. if (this.path == '') {
  177. uri.path = baseURI.path;
  178. uri.query = this.query || baseURI.query;
  179. } else {
  180. if (this.path.charAt(0) == '/') {
  181. uri.path = this.path;
  182. uri.removeDotSegments();
  183. } else {
  184. if (baseURI.authority && baseURI.path == '') {
  185. uri.path = '/' + this.path;
  186. } else {
  187. uri.path = baseURI.path.substring(0, baseURI.path.lastIndexOf('/') + 1) + this.path;
  188. }
  189. uri.removeDotSegments();
  190. }
  191. uri.query = this.query;
  192. }
  193. }
  194. }
  195. uri.fragment = this.fragment;
  196. return uri;
  197. };
  198. /**
  199. * Remove dot segments from path.
  200. * See RFC 3986 section 5.2.4
  201. * @private
  202. */
  203. URI.prototype.removeDotSegments = function() {
  204. var input = this.path.split('/'),
  205. output = [],
  206. segment,
  207. absPath = input[0] == '';
  208. if (absPath)
  209. input.shift();
  210. var sFirst = input[0] == '' ? input.shift() : null;
  211. while (input.length) {
  212. segment = input.shift();
  213. if (segment == '..') {
  214. output.pop();
  215. } else if (segment != '.') {
  216. output.push(segment);
  217. }
  218. }
  219. if (segment == '.' || segment == '..')
  220. output.push('');
  221. if (absPath)
  222. output.unshift('');
  223. this.path = output.join('/');
  224. };
  225. // We don't like this function because it builds up a cache that is never cleared.
  226. // /**
  227. // * Resolves a relative URI against an absolute base URI.
  228. // * Convenience method.
  229. // * @param {String} uri the relative URI to resolve
  230. // * @param {String} baseURI the base URI (must be absolute) to resolve against
  231. // */
  232. // URI.resolve = function(sURI, sBaseURI) {
  233. // var uri = cache[sURI] || (cache[sURI] = new URI(sURI));
  234. // var baseURI = cache[sBaseURI] || (cache[sBaseURI] = new URI(sBaseURI));
  235. // return uri.resolve(baseURI).toString();
  236. // };
  237. // var cache = {};
  238. /**
  239. * Serialises the URI to a string.
  240. */
  241. URI.prototype.toString = function() {
  242. var result = '';
  243. if (this.scheme)
  244. result += this.scheme + ':';
  245. if (this.authority)
  246. result += '//' + this.authority;
  247. result += this.path;
  248. if (this.query)
  249. result += '?' + this.query;
  250. if (this.fragment)
  251. result += '#' + this.fragment;
  252. return result;
  253. };
  254. return URI;
  255. });