Uri.js 7.4 KB

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