parse.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. // Copyright Joyent, Inc. and other Node contributors.
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a
  4. // copy of this software and associated documentation files (the
  5. // "Software"), to deal in the Software without restriction, including
  6. // without limitation the rights to use, copy, modify, merge, publish,
  7. // distribute, sublicense, and/or sell copies of the Software, and to permit
  8. // persons to whom the Software is furnished to do so, subject to the
  9. // following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included
  12. // in all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  15. // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  16. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  17. // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  18. // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  19. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  20. // USE OR OTHER DEALINGS IN THE SOFTWARE.
  21. 'use strict';
  22. //
  23. // Changes from joyent/node:
  24. //
  25. // 1. No leading slash in paths,
  26. // e.g. in `url.parse('http://foo?bar')` pathname is ``, not `/`
  27. //
  28. // 2. Backslashes are not replaced with slashes,
  29. // so `http:\\example.org\` is treated like a relative path
  30. //
  31. // 3. Trailing colon is treated like a part of the path,
  32. // i.e. in `http://example.org:foo` pathname is `:foo`
  33. //
  34. // 4. Nothing is URL-encoded in the resulting object,
  35. // (in joyent/node some chars in auth and paths are encoded)
  36. //
  37. // 5. `url.parse()` does not have `parseQueryString` argument
  38. //
  39. // 6. Removed extraneous result properties: `host`, `path`, `query`, etc.,
  40. // which can be constructed using other parts of the url.
  41. //
  42. function Url() {
  43. this.protocol = null;
  44. this.slashes = null;
  45. this.auth = null;
  46. this.port = null;
  47. this.hostname = null;
  48. this.hash = null;
  49. this.search = null;
  50. this.pathname = null;
  51. }
  52. // Reference: RFC 3986, RFC 1808, RFC 2396
  53. // define these here so at least they only have to be
  54. // compiled once on the first module load.
  55. var protocolPattern = /^([a-z0-9.+-]+:)/i,
  56. portPattern = /:[0-9]*$/,
  57. // Special case for a simple path URL
  58. simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,
  59. // RFC 2396: characters reserved for delimiting URLs.
  60. // We actually just auto-escape these.
  61. delims = [ '<', '>', '"', '`', ' ', '\r', '\n', '\t' ],
  62. // RFC 2396: characters not allowed for various reasons.
  63. unwise = [ '{', '}', '|', '\\', '^', '`' ].concat(delims),
  64. // Allowed by RFCs, but cause of XSS attacks. Always escape these.
  65. autoEscape = [ '\'' ].concat(unwise),
  66. // Characters that are never ever allowed in a hostname.
  67. // Note that any invalid chars are also handled, but these
  68. // are the ones that are *expected* to be seen, so we fast-path
  69. // them.
  70. nonHostChars = [ '%', '/', '?', ';', '#' ].concat(autoEscape),
  71. hostEndingChars = [ '/', '?', '#' ],
  72. hostnameMaxLen = 255,
  73. hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/,
  74. hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/,
  75. // protocols that can allow "unsafe" and "unwise" chars.
  76. /* eslint-disable no-script-url */
  77. // protocols that never have a hostname.
  78. hostlessProtocol = {
  79. 'javascript': true,
  80. 'javascript:': true
  81. },
  82. // protocols that always contain a // bit.
  83. slashedProtocol = {
  84. 'http': true,
  85. 'https': true,
  86. 'ftp': true,
  87. 'gopher': true,
  88. 'file': true,
  89. 'http:': true,
  90. 'https:': true,
  91. 'ftp:': true,
  92. 'gopher:': true,
  93. 'file:': true
  94. };
  95. /* eslint-enable no-script-url */
  96. function urlParse(url, slashesDenoteHost) {
  97. if (url && url instanceof Url) { return url; }
  98. var u = new Url();
  99. u.parse(url, slashesDenoteHost);
  100. return u;
  101. }
  102. Url.prototype.parse = function(url, slashesDenoteHost) {
  103. var i, l, lowerProto, hec, slashes,
  104. rest = url;
  105. // trim before proceeding.
  106. // This is to support parse stuff like " http://foo.com \n"
  107. rest = rest.trim();
  108. if (!slashesDenoteHost && url.split('#').length === 1) {
  109. // Try fast path regexp
  110. var simplePath = simplePathPattern.exec(rest);
  111. if (simplePath) {
  112. this.pathname = simplePath[1];
  113. if (simplePath[2]) {
  114. this.search = simplePath[2];
  115. }
  116. return this;
  117. }
  118. }
  119. var proto = protocolPattern.exec(rest);
  120. if (proto) {
  121. proto = proto[0];
  122. lowerProto = proto.toLowerCase();
  123. this.protocol = proto;
  124. rest = rest.substr(proto.length);
  125. }
  126. // figure out if it's got a host
  127. // user@server is *always* interpreted as a hostname, and url
  128. // resolution will treat //foo/bar as host=foo,path=bar because that's
  129. // how the browser resolves relative URLs.
  130. if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) {
  131. slashes = rest.substr(0, 2) === '//';
  132. if (slashes && !(proto && hostlessProtocol[proto])) {
  133. rest = rest.substr(2);
  134. this.slashes = true;
  135. }
  136. }
  137. if (!hostlessProtocol[proto] &&
  138. (slashes || (proto && !slashedProtocol[proto]))) {
  139. // there's a hostname.
  140. // the first instance of /, ?, ;, or # ends the host.
  141. //
  142. // If there is an @ in the hostname, then non-host chars *are* allowed
  143. // to the left of the last @ sign, unless some host-ending character
  144. // comes *before* the @-sign.
  145. // URLs are obnoxious.
  146. //
  147. // ex:
  148. // http://a@b@c/ => user:a@b host:c
  149. // http://a@b?@c => user:a host:c path:/?@c
  150. // v0.12 TODO(isaacs): This is not quite how Chrome does things.
  151. // Review our test case against browsers more comprehensively.
  152. // find the first instance of any hostEndingChars
  153. var hostEnd = -1;
  154. for (i = 0; i < hostEndingChars.length; i++) {
  155. hec = rest.indexOf(hostEndingChars[i]);
  156. if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) {
  157. hostEnd = hec;
  158. }
  159. }
  160. // at this point, either we have an explicit point where the
  161. // auth portion cannot go past, or the last @ char is the decider.
  162. var auth, atSign;
  163. if (hostEnd === -1) {
  164. // atSign can be anywhere.
  165. atSign = rest.lastIndexOf('@');
  166. } else {
  167. // atSign must be in auth portion.
  168. // http://a@b/c@d => host:b auth:a path:/c@d
  169. atSign = rest.lastIndexOf('@', hostEnd);
  170. }
  171. // Now we have a portion which is definitely the auth.
  172. // Pull that off.
  173. if (atSign !== -1) {
  174. auth = rest.slice(0, atSign);
  175. rest = rest.slice(atSign + 1);
  176. this.auth = auth;
  177. }
  178. // the host is the remaining to the left of the first non-host char
  179. hostEnd = -1;
  180. for (i = 0; i < nonHostChars.length; i++) {
  181. hec = rest.indexOf(nonHostChars[i]);
  182. if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) {
  183. hostEnd = hec;
  184. }
  185. }
  186. // if we still have not hit it, then the entire thing is a host.
  187. if (hostEnd === -1) {
  188. hostEnd = rest.length;
  189. }
  190. if (rest[hostEnd - 1] === ':') { hostEnd--; }
  191. var host = rest.slice(0, hostEnd);
  192. rest = rest.slice(hostEnd);
  193. // pull out port.
  194. this.parseHost(host);
  195. // we've indicated that there is a hostname,
  196. // so even if it's empty, it has to be present.
  197. this.hostname = this.hostname || '';
  198. // if hostname begins with [ and ends with ]
  199. // assume that it's an IPv6 address.
  200. var ipv6Hostname = this.hostname[0] === '[' &&
  201. this.hostname[this.hostname.length - 1] === ']';
  202. // validate a little.
  203. if (!ipv6Hostname) {
  204. var hostparts = this.hostname.split(/\./);
  205. for (i = 0, l = hostparts.length; i < l; i++) {
  206. var part = hostparts[i];
  207. if (!part) { continue; }
  208. if (!part.match(hostnamePartPattern)) {
  209. var newpart = '';
  210. for (var j = 0, k = part.length; j < k; j++) {
  211. if (part.charCodeAt(j) > 127) {
  212. // we replace non-ASCII char with a temporary placeholder
  213. // we need this to make sure size of hostname is not
  214. // broken by replacing non-ASCII by nothing
  215. newpart += 'x';
  216. } else {
  217. newpart += part[j];
  218. }
  219. }
  220. // we test again with ASCII char only
  221. if (!newpart.match(hostnamePartPattern)) {
  222. var validParts = hostparts.slice(0, i);
  223. var notHost = hostparts.slice(i + 1);
  224. var bit = part.match(hostnamePartStart);
  225. if (bit) {
  226. validParts.push(bit[1]);
  227. notHost.unshift(bit[2]);
  228. }
  229. if (notHost.length) {
  230. rest = notHost.join('.') + rest;
  231. }
  232. this.hostname = validParts.join('.');
  233. break;
  234. }
  235. }
  236. }
  237. }
  238. if (this.hostname.length > hostnameMaxLen) {
  239. this.hostname = '';
  240. }
  241. // strip [ and ] from the hostname
  242. // the host field still retains them, though
  243. if (ipv6Hostname) {
  244. this.hostname = this.hostname.substr(1, this.hostname.length - 2);
  245. }
  246. }
  247. // chop off from the tail first.
  248. var hash = rest.indexOf('#');
  249. if (hash !== -1) {
  250. // got a fragment string.
  251. this.hash = rest.substr(hash);
  252. rest = rest.slice(0, hash);
  253. }
  254. var qm = rest.indexOf('?');
  255. if (qm !== -1) {
  256. this.search = rest.substr(qm);
  257. rest = rest.slice(0, qm);
  258. }
  259. if (rest) { this.pathname = rest; }
  260. if (slashedProtocol[lowerProto] &&
  261. this.hostname && !this.pathname) {
  262. this.pathname = '';
  263. }
  264. return this;
  265. };
  266. Url.prototype.parseHost = function(host) {
  267. var port = portPattern.exec(host);
  268. if (port) {
  269. port = port[0];
  270. if (port !== ':') {
  271. this.port = port.substr(1);
  272. }
  273. host = host.substr(0, host.length - port.length);
  274. }
  275. if (host) { this.hostname = host; }
  276. };
  277. module.exports = urlParse;