URI.js 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364
  1. /*!
  2. * URI.js - Mutating URLs
  3. *
  4. * Version: 1.19.11
  5. *
  6. * Author: Rodney Rehm
  7. * Web: http://medialize.github.io/URI.js/
  8. *
  9. * Licensed under
  10. * MIT License http://www.opensource.org/licenses/mit-license
  11. *
  12. */
  13. (function (root, factory) {
  14. 'use strict';
  15. // https://github.com/umdjs/umd/blob/master/returnExports.js
  16. if (typeof module === 'object' && module.exports) {
  17. // Node
  18. module.exports = factory(require('./punycode'), require('./IPv6'), require('./SecondLevelDomains'));
  19. } else if (typeof define === 'function' && define.amd) {
  20. // AMD. Register as an anonymous module.
  21. define(['./punycode', './IPv6', './SecondLevelDomains'], factory);
  22. } else {
  23. // Browser globals (root is window)
  24. root.URI = factory(root.punycode, root.IPv6, root.SecondLevelDomains, root);
  25. }
  26. }(this, function (punycode, IPv6, SLD, root) {
  27. 'use strict';
  28. /*global location, escape, unescape */
  29. // FIXME: v2.0.0 renamce non-camelCase properties to uppercase
  30. /*jshint camelcase: false */
  31. // save current URI variable, if any
  32. var _URI = root && root.URI;
  33. function URI(url, base) {
  34. var _urlSupplied = arguments.length >= 1;
  35. var _baseSupplied = arguments.length >= 2;
  36. // Allow instantiation without the 'new' keyword
  37. if (!(this instanceof URI)) {
  38. if (_urlSupplied) {
  39. if (_baseSupplied) {
  40. return new URI(url, base);
  41. }
  42. return new URI(url);
  43. }
  44. return new URI();
  45. }
  46. if (url === undefined) {
  47. if (_urlSupplied) {
  48. throw new TypeError('undefined is not a valid argument for URI');
  49. }
  50. if (typeof location !== 'undefined') {
  51. url = location.href + '';
  52. } else {
  53. url = '';
  54. }
  55. }
  56. if (url === null) {
  57. if (_urlSupplied) {
  58. throw new TypeError('null is not a valid argument for URI');
  59. }
  60. }
  61. this.href(url);
  62. // resolve to base according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor
  63. if (base !== undefined) {
  64. return this.absoluteTo(base);
  65. }
  66. return this;
  67. }
  68. function isInteger(value) {
  69. return /^[0-9]+$/.test(value);
  70. }
  71. URI.version = '1.19.11';
  72. var p = URI.prototype;
  73. var hasOwn = Object.prototype.hasOwnProperty;
  74. function escapeRegEx(string) {
  75. // https://github.com/medialize/URI.js/commit/85ac21783c11f8ccab06106dba9735a31a86924d#commitcomment-821963
  76. return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
  77. }
  78. function getType(value) {
  79. // IE8 doesn't return [Object Undefined] but [Object Object] for undefined value
  80. if (value === undefined) {
  81. return 'Undefined';
  82. }
  83. return String(Object.prototype.toString.call(value)).slice(8, -1);
  84. }
  85. function isArray(obj) {
  86. return getType(obj) === 'Array';
  87. }
  88. function filterArrayValues(data, value) {
  89. var lookup = {};
  90. var i, length;
  91. if (getType(value) === 'RegExp') {
  92. lookup = null;
  93. } else if (isArray(value)) {
  94. for (i = 0, length = value.length; i < length; i++) {
  95. lookup[value[i]] = true;
  96. }
  97. } else {
  98. lookup[value] = true;
  99. }
  100. for (i = 0, length = data.length; i < length; i++) {
  101. /*jshint laxbreak: true */
  102. var _match = lookup && lookup[data[i]] !== undefined
  103. || !lookup && value.test(data[i]);
  104. /*jshint laxbreak: false */
  105. if (_match) {
  106. data.splice(i, 1);
  107. length--;
  108. i--;
  109. }
  110. }
  111. return data;
  112. }
  113. function arrayContains(list, value) {
  114. var i, length;
  115. // value may be string, number, array, regexp
  116. if (isArray(value)) {
  117. // Note: this can be optimized to O(n) (instead of current O(m * n))
  118. for (i = 0, length = value.length; i < length; i++) {
  119. if (!arrayContains(list, value[i])) {
  120. return false;
  121. }
  122. }
  123. return true;
  124. }
  125. var _type = getType(value);
  126. for (i = 0, length = list.length; i < length; i++) {
  127. if (_type === 'RegExp') {
  128. if (typeof list[i] === 'string' && list[i].match(value)) {
  129. return true;
  130. }
  131. } else if (list[i] === value) {
  132. return true;
  133. }
  134. }
  135. return false;
  136. }
  137. function arraysEqual(one, two) {
  138. if (!isArray(one) || !isArray(two)) {
  139. return false;
  140. }
  141. // arrays can't be equal if they have different amount of content
  142. if (one.length !== two.length) {
  143. return false;
  144. }
  145. one.sort();
  146. two.sort();
  147. for (var i = 0, l = one.length; i < l; i++) {
  148. if (one[i] !== two[i]) {
  149. return false;
  150. }
  151. }
  152. return true;
  153. }
  154. function trimSlashes(text) {
  155. var trim_expression = /^\/+|\/+$/g;
  156. return text.replace(trim_expression, '');
  157. }
  158. URI._parts = function() {
  159. return {
  160. protocol: null,
  161. username: null,
  162. password: null,
  163. hostname: null,
  164. urn: null,
  165. port: null,
  166. path: null,
  167. query: null,
  168. fragment: null,
  169. // state
  170. preventInvalidHostname: URI.preventInvalidHostname,
  171. duplicateQueryParameters: URI.duplicateQueryParameters,
  172. escapeQuerySpace: URI.escapeQuerySpace
  173. };
  174. };
  175. // state: throw on invalid hostname
  176. // see https://github.com/medialize/URI.js/pull/345
  177. // and https://github.com/medialize/URI.js/issues/354
  178. URI.preventInvalidHostname = false;
  179. // state: allow duplicate query parameters (a=1&a=1)
  180. URI.duplicateQueryParameters = false;
  181. // state: replaces + with %20 (space in query strings)
  182. URI.escapeQuerySpace = true;
  183. // static properties
  184. URI.protocol_expression = /^[a-z][a-z0-9.+-]*$/i;
  185. URI.idn_expression = /[^a-z0-9\._-]/i;
  186. URI.punycode_expression = /(xn--)/i;
  187. // well, 333.444.555.666 matches, but it sure ain't no IPv4 - do we care?
  188. URI.ip4_expression = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
  189. // credits to Rich Brown
  190. // source: http://forums.intermapper.com/viewtopic.php?p=1096#1096
  191. // specification: http://www.ietf.org/rfc/rfc4291.txt
  192. URI.ip6_expression = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/;
  193. // expression used is "gruber revised" (@gruber v2) determined to be the
  194. // best solution in a regex-golf we did a couple of ages ago at
  195. // * http://mathiasbynens.be/demo/url-regex
  196. // * http://rodneyrehm.de/t/url-regex.html
  197. URI.find_uri_expression = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/ig;
  198. URI.findUri = {
  199. // valid "scheme://" or "www."
  200. start: /\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi,
  201. // everything up to the next whitespace
  202. end: /[\s\r\n]|$/,
  203. // trim trailing punctuation captured by end RegExp
  204. trim: /[`!()\[\]{};:'".,<>?«»“”„‘’]+$/,
  205. // balanced parens inclusion (), [], {}, <>
  206. parens: /(\([^\)]*\)|\[[^\]]*\]|\{[^}]*\}|<[^>]*>)/g,
  207. };
  208. URI.leading_whitespace_expression = /^[\x00-\x20\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/
  209. // https://infra.spec.whatwg.org/#ascii-tab-or-newline
  210. URI.ascii_tab_whitespace = /[\u0009\u000A\u000D]+/g
  211. // http://www.iana.org/assignments/uri-schemes.html
  212. // http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Well-known_ports
  213. URI.defaultPorts = {
  214. http: '80',
  215. https: '443',
  216. ftp: '21',
  217. gopher: '70',
  218. ws: '80',
  219. wss: '443'
  220. };
  221. // list of protocols which always require a hostname
  222. URI.hostProtocols = [
  223. 'http',
  224. 'https'
  225. ];
  226. // allowed hostname characters according to RFC 3986
  227. // ALPHA DIGIT "-" "." "_" "~" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" %encoded
  228. // I've never seen a (non-IDN) hostname other than: ALPHA DIGIT . - _
  229. URI.invalid_hostname_characters = /[^a-zA-Z0-9\.\-:_]/;
  230. // map DOM Elements to their URI attribute
  231. URI.domAttributes = {
  232. 'a': 'href',
  233. 'blockquote': 'cite',
  234. 'link': 'href',
  235. 'base': 'href',
  236. 'script': 'src',
  237. 'form': 'action',
  238. 'img': 'src',
  239. 'area': 'href',
  240. 'iframe': 'src',
  241. 'embed': 'src',
  242. 'source': 'src',
  243. 'track': 'src',
  244. 'input': 'src', // but only if type="image"
  245. 'audio': 'src',
  246. 'video': 'src'
  247. };
  248. URI.getDomAttribute = function(node) {
  249. if (!node || !node.nodeName) {
  250. return undefined;
  251. }
  252. var nodeName = node.nodeName.toLowerCase();
  253. // <input> should only expose src for type="image"
  254. if (nodeName === 'input' && node.type !== 'image') {
  255. return undefined;
  256. }
  257. return URI.domAttributes[nodeName];
  258. };
  259. function escapeForDumbFirefox36(value) {
  260. // https://github.com/medialize/URI.js/issues/91
  261. return escape(value);
  262. }
  263. // encoding / decoding according to RFC3986
  264. function strictEncodeURIComponent(string) {
  265. // see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent
  266. return encodeURIComponent(string)
  267. .replace(/[!'()*]/g, escapeForDumbFirefox36)
  268. .replace(/\*/g, '%2A');
  269. }
  270. URI.encode = strictEncodeURIComponent;
  271. URI.decode = decodeURIComponent;
  272. URI.iso8859 = function() {
  273. URI.encode = escape;
  274. URI.decode = unescape;
  275. };
  276. URI.unicode = function() {
  277. URI.encode = strictEncodeURIComponent;
  278. URI.decode = decodeURIComponent;
  279. };
  280. URI.characters = {
  281. pathname: {
  282. encode: {
  283. // RFC3986 2.1: For consistency, URI producers and normalizers should
  284. // use uppercase hexadecimal digits for all percent-encodings.
  285. expression: /%(24|26|2B|2C|3B|3D|3A|40)/ig,
  286. map: {
  287. // -._~!'()*
  288. '%24': '$',
  289. '%26': '&',
  290. '%2B': '+',
  291. '%2C': ',',
  292. '%3B': ';',
  293. '%3D': '=',
  294. '%3A': ':',
  295. '%40': '@'
  296. }
  297. },
  298. decode: {
  299. expression: /[\/\?#]/g,
  300. map: {
  301. '/': '%2F',
  302. '?': '%3F',
  303. '#': '%23'
  304. }
  305. }
  306. },
  307. reserved: {
  308. encode: {
  309. // RFC3986 2.1: For consistency, URI producers and normalizers should
  310. // use uppercase hexadecimal digits for all percent-encodings.
  311. expression: /%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig,
  312. map: {
  313. // gen-delims
  314. '%3A': ':',
  315. '%2F': '/',
  316. '%3F': '?',
  317. '%23': '#',
  318. '%5B': '[',
  319. '%5D': ']',
  320. '%40': '@',
  321. // sub-delims
  322. '%21': '!',
  323. '%24': '$',
  324. '%26': '&',
  325. '%27': '\'',
  326. '%28': '(',
  327. '%29': ')',
  328. '%2A': '*',
  329. '%2B': '+',
  330. '%2C': ',',
  331. '%3B': ';',
  332. '%3D': '='
  333. }
  334. }
  335. },
  336. urnpath: {
  337. // The characters under `encode` are the characters called out by RFC 2141 as being acceptable
  338. // for usage in a URN. RFC2141 also calls out "-", ".", and "_" as acceptable characters, but
  339. // these aren't encoded by encodeURIComponent, so we don't have to call them out here. Also
  340. // note that the colon character is not featured in the encoding map; this is because URI.js
  341. // gives the colons in URNs semantic meaning as the delimiters of path segements, and so it
  342. // should not appear unencoded in a segment itself.
  343. // See also the note above about RFC3986 and capitalalized hex digits.
  344. encode: {
  345. expression: /%(21|24|27|28|29|2A|2B|2C|3B|3D|40)/ig,
  346. map: {
  347. '%21': '!',
  348. '%24': '$',
  349. '%27': '\'',
  350. '%28': '(',
  351. '%29': ')',
  352. '%2A': '*',
  353. '%2B': '+',
  354. '%2C': ',',
  355. '%3B': ';',
  356. '%3D': '=',
  357. '%40': '@'
  358. }
  359. },
  360. // These characters are the characters called out by RFC2141 as "reserved" characters that
  361. // should never appear in a URN, plus the colon character (see note above).
  362. decode: {
  363. expression: /[\/\?#:]/g,
  364. map: {
  365. '/': '%2F',
  366. '?': '%3F',
  367. '#': '%23',
  368. ':': '%3A'
  369. }
  370. }
  371. }
  372. };
  373. URI.encodeQuery = function(string, escapeQuerySpace) {
  374. var escaped = URI.encode(string + '');
  375. if (escapeQuerySpace === undefined) {
  376. escapeQuerySpace = URI.escapeQuerySpace;
  377. }
  378. return escapeQuerySpace ? escaped.replace(/%20/g, '+') : escaped;
  379. };
  380. URI.decodeQuery = function(string, escapeQuerySpace) {
  381. string += '';
  382. if (escapeQuerySpace === undefined) {
  383. escapeQuerySpace = URI.escapeQuerySpace;
  384. }
  385. try {
  386. return URI.decode(escapeQuerySpace ? string.replace(/\+/g, '%20') : string);
  387. } catch(e) {
  388. // we're not going to mess with weird encodings,
  389. // give up and return the undecoded original string
  390. // see https://github.com/medialize/URI.js/issues/87
  391. // see https://github.com/medialize/URI.js/issues/92
  392. return string;
  393. }
  394. };
  395. // generate encode/decode path functions
  396. var _parts = {'encode':'encode', 'decode':'decode'};
  397. var _part;
  398. var generateAccessor = function(_group, _part) {
  399. return function(string) {
  400. try {
  401. return URI[_part](string + '').replace(URI.characters[_group][_part].expression, function(c) {
  402. return URI.characters[_group][_part].map[c];
  403. });
  404. } catch (e) {
  405. // we're not going to mess with weird encodings,
  406. // give up and return the undecoded original string
  407. // see https://github.com/medialize/URI.js/issues/87
  408. // see https://github.com/medialize/URI.js/issues/92
  409. return string;
  410. }
  411. };
  412. };
  413. for (_part in _parts) {
  414. URI[_part + 'PathSegment'] = generateAccessor('pathname', _parts[_part]);
  415. URI[_part + 'UrnPathSegment'] = generateAccessor('urnpath', _parts[_part]);
  416. }
  417. var generateSegmentedPathFunction = function(_sep, _codingFuncName, _innerCodingFuncName) {
  418. return function(string) {
  419. // Why pass in names of functions, rather than the function objects themselves? The
  420. // definitions of some functions (but in particular, URI.decode) will occasionally change due
  421. // to URI.js having ISO8859 and Unicode modes. Passing in the name and getting it will ensure
  422. // that the functions we use here are "fresh".
  423. var actualCodingFunc;
  424. if (!_innerCodingFuncName) {
  425. actualCodingFunc = URI[_codingFuncName];
  426. } else {
  427. actualCodingFunc = function(string) {
  428. return URI[_codingFuncName](URI[_innerCodingFuncName](string));
  429. };
  430. }
  431. var segments = (string + '').split(_sep);
  432. for (var i = 0, length = segments.length; i < length; i++) {
  433. segments[i] = actualCodingFunc(segments[i]);
  434. }
  435. return segments.join(_sep);
  436. };
  437. };
  438. // This takes place outside the above loop because we don't want, e.g., encodeUrnPath functions.
  439. URI.decodePath = generateSegmentedPathFunction('/', 'decodePathSegment');
  440. URI.decodeUrnPath = generateSegmentedPathFunction(':', 'decodeUrnPathSegment');
  441. URI.recodePath = generateSegmentedPathFunction('/', 'encodePathSegment', 'decode');
  442. URI.recodeUrnPath = generateSegmentedPathFunction(':', 'encodeUrnPathSegment', 'decode');
  443. URI.encodeReserved = generateAccessor('reserved', 'encode');
  444. URI.parse = function(string, parts) {
  445. var pos;
  446. if (!parts) {
  447. parts = {
  448. preventInvalidHostname: URI.preventInvalidHostname
  449. };
  450. }
  451. string = string.replace(URI.leading_whitespace_expression, '')
  452. // https://infra.spec.whatwg.org/#ascii-tab-or-newline
  453. string = string.replace(URI.ascii_tab_whitespace, '')
  454. // [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment]
  455. // extract fragment
  456. pos = string.indexOf('#');
  457. if (pos > -1) {
  458. // escaping?
  459. parts.fragment = string.substring(pos + 1) || null;
  460. string = string.substring(0, pos);
  461. }
  462. // extract query
  463. pos = string.indexOf('?');
  464. if (pos > -1) {
  465. // escaping?
  466. parts.query = string.substring(pos + 1) || null;
  467. string = string.substring(0, pos);
  468. }
  469. // slashes and backslashes have lost all meaning for the web protocols (https, http, wss, ws)
  470. string = string.replace(/^(https?|ftp|wss?)?:+[/\\]*/i, '$1://');
  471. // slashes and backslashes have lost all meaning for scheme relative URLs
  472. string = string.replace(/^[/\\]{2,}/i, '//');
  473. // extract protocol
  474. if (string.substring(0, 2) === '//') {
  475. // relative-scheme
  476. parts.protocol = null;
  477. string = string.substring(2);
  478. // extract "user:pass@host:port"
  479. string = URI.parseAuthority(string, parts);
  480. } else {
  481. pos = string.indexOf(':');
  482. if (pos > -1) {
  483. parts.protocol = string.substring(0, pos) || null;
  484. if (parts.protocol && !parts.protocol.match(URI.protocol_expression)) {
  485. // : may be within the path
  486. parts.protocol = undefined;
  487. } else if (string.substring(pos + 1, pos + 3).replace(/\\/g, '/') === '//') {
  488. string = string.substring(pos + 3);
  489. // extract "user:pass@host:port"
  490. string = URI.parseAuthority(string, parts);
  491. } else {
  492. string = string.substring(pos + 1);
  493. parts.urn = true;
  494. }
  495. }
  496. }
  497. // what's left must be the path
  498. parts.path = string;
  499. // and we're done
  500. return parts;
  501. };
  502. URI.parseHost = function(string, parts) {
  503. if (!string) {
  504. string = '';
  505. }
  506. // Copy chrome, IE, opera backslash-handling behavior.
  507. // Back slashes before the query string get converted to forward slashes
  508. // See: https://github.com/joyent/node/blob/386fd24f49b0e9d1a8a076592a404168faeecc34/lib/url.js#L115-L124
  509. // See: https://code.google.com/p/chromium/issues/detail?id=25916
  510. // https://github.com/medialize/URI.js/pull/233
  511. string = string.replace(/\\/g, '/');
  512. // extract host:port
  513. var pos = string.indexOf('/');
  514. var bracketPos;
  515. var t;
  516. if (pos === -1) {
  517. pos = string.length;
  518. }
  519. if (string.charAt(0) === '[') {
  520. // IPv6 host - http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04#section-6
  521. // I claim most client software breaks on IPv6 anyways. To simplify things, URI only accepts
  522. // IPv6+port in the format [2001:db8::1]:80 (for the time being)
  523. bracketPos = string.indexOf(']');
  524. parts.hostname = string.substring(1, bracketPos) || null;
  525. parts.port = string.substring(bracketPos + 2, pos) || null;
  526. if (parts.port === '/') {
  527. parts.port = null;
  528. }
  529. } else {
  530. var firstColon = string.indexOf(':');
  531. var firstSlash = string.indexOf('/');
  532. var nextColon = string.indexOf(':', firstColon + 1);
  533. if (nextColon !== -1 && (firstSlash === -1 || nextColon < firstSlash)) {
  534. // IPv6 host contains multiple colons - but no port
  535. // this notation is actually not allowed by RFC 3986, but we're a liberal parser
  536. parts.hostname = string.substring(0, pos) || null;
  537. parts.port = null;
  538. } else {
  539. t = string.substring(0, pos).split(':');
  540. parts.hostname = t[0] || null;
  541. parts.port = t[1] || null;
  542. }
  543. }
  544. if (parts.hostname && string.substring(pos).charAt(0) !== '/') {
  545. pos++;
  546. string = '/' + string;
  547. }
  548. if (parts.preventInvalidHostname) {
  549. URI.ensureValidHostname(parts.hostname, parts.protocol);
  550. }
  551. if (parts.port) {
  552. URI.ensureValidPort(parts.port);
  553. }
  554. return string.substring(pos) || '/';
  555. };
  556. URI.parseAuthority = function(string, parts) {
  557. string = URI.parseUserinfo(string, parts);
  558. return URI.parseHost(string, parts);
  559. };
  560. URI.parseUserinfo = function(string, parts) {
  561. // extract username:password
  562. var _string = string
  563. var firstBackSlash = string.indexOf('\\');
  564. if (firstBackSlash !== -1) {
  565. string = string.replace(/\\/g, '/')
  566. }
  567. var firstSlash = string.indexOf('/');
  568. var pos = string.lastIndexOf('@', firstSlash > -1 ? firstSlash : string.length - 1);
  569. var t;
  570. // authority@ must come before /path or \path
  571. if (pos > -1 && (firstSlash === -1 || pos < firstSlash)) {
  572. t = string.substring(0, pos).split(':');
  573. parts.username = t[0] ? URI.decode(t[0]) : null;
  574. t.shift();
  575. parts.password = t[0] ? URI.decode(t.join(':')) : null;
  576. string = _string.substring(pos + 1);
  577. } else {
  578. parts.username = null;
  579. parts.password = null;
  580. }
  581. return string;
  582. };
  583. URI.parseQuery = function(string, escapeQuerySpace) {
  584. if (!string) {
  585. return {};
  586. }
  587. // throw out the funky business - "?"[name"="value"&"]+
  588. string = string.replace(/&+/g, '&').replace(/^\?*&*|&+$/g, '');
  589. if (!string) {
  590. return {};
  591. }
  592. var items = {};
  593. var splits = string.split('&');
  594. var length = splits.length;
  595. var v, name, value;
  596. for (var i = 0; i < length; i++) {
  597. v = splits[i].split('=');
  598. name = URI.decodeQuery(v.shift(), escapeQuerySpace);
  599. // no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#collect-url-parameters
  600. value = v.length ? URI.decodeQuery(v.join('='), escapeQuerySpace) : null;
  601. if (name === '__proto__') {
  602. // ignore attempt at exploiting JavaScript internals
  603. continue;
  604. } else if (hasOwn.call(items, name)) {
  605. if (typeof items[name] === 'string' || items[name] === null) {
  606. items[name] = [items[name]];
  607. }
  608. items[name].push(value);
  609. } else {
  610. items[name] = value;
  611. }
  612. }
  613. return items;
  614. };
  615. URI.build = function(parts) {
  616. var t = '';
  617. var requireAbsolutePath = false
  618. if (parts.protocol) {
  619. t += parts.protocol + ':';
  620. }
  621. if (!parts.urn && (t || parts.hostname)) {
  622. t += '//';
  623. requireAbsolutePath = true
  624. }
  625. t += (URI.buildAuthority(parts) || '');
  626. if (typeof parts.path === 'string') {
  627. if (parts.path.charAt(0) !== '/' && requireAbsolutePath) {
  628. t += '/';
  629. }
  630. t += parts.path;
  631. }
  632. if (typeof parts.query === 'string' && parts.query) {
  633. t += '?' + parts.query;
  634. }
  635. if (typeof parts.fragment === 'string' && parts.fragment) {
  636. t += '#' + parts.fragment;
  637. }
  638. return t;
  639. };
  640. URI.buildHost = function(parts) {
  641. var t = '';
  642. if (!parts.hostname) {
  643. return '';
  644. } else if (URI.ip6_expression.test(parts.hostname)) {
  645. t += '[' + parts.hostname + ']';
  646. } else {
  647. t += parts.hostname;
  648. }
  649. if (parts.port) {
  650. t += ':' + parts.port;
  651. }
  652. return t;
  653. };
  654. URI.buildAuthority = function(parts) {
  655. return URI.buildUserinfo(parts) + URI.buildHost(parts);
  656. };
  657. URI.buildUserinfo = function(parts) {
  658. var t = '';
  659. if (parts.username) {
  660. t += URI.encode(parts.username);
  661. }
  662. if (parts.password) {
  663. t += ':' + URI.encode(parts.password);
  664. }
  665. if (t) {
  666. t += '@';
  667. }
  668. return t;
  669. };
  670. URI.buildQuery = function(data, duplicateQueryParameters, escapeQuerySpace) {
  671. // according to http://tools.ietf.org/html/rfc3986 or http://labs.apache.org/webarch/uri/rfc/rfc3986.html
  672. // being »-._~!$&'()*+,;=:@/?« %HEX and alnum are allowed
  673. // the RFC explicitly states ?/foo being a valid use case, no mention of parameter syntax!
  674. // URI.js treats the query string as being application/x-www-form-urlencoded
  675. // see http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type
  676. var t = '';
  677. var unique, key, i, length;
  678. for (key in data) {
  679. if (key === '__proto__') {
  680. // ignore attempt at exploiting JavaScript internals
  681. continue;
  682. } else if (hasOwn.call(data, key)) {
  683. if (isArray(data[key])) {
  684. unique = {};
  685. for (i = 0, length = data[key].length; i < length; i++) {
  686. if (data[key][i] !== undefined && unique[data[key][i] + ''] === undefined) {
  687. t += '&' + URI.buildQueryParameter(key, data[key][i], escapeQuerySpace);
  688. if (duplicateQueryParameters !== true) {
  689. unique[data[key][i] + ''] = true;
  690. }
  691. }
  692. }
  693. } else if (data[key] !== undefined) {
  694. t += '&' + URI.buildQueryParameter(key, data[key], escapeQuerySpace);
  695. }
  696. }
  697. }
  698. return t.substring(1);
  699. };
  700. URI.buildQueryParameter = function(name, value, escapeQuerySpace) {
  701. // http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type -- application/x-www-form-urlencoded
  702. // don't append "=" for null values, according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#url-parameter-serialization
  703. return URI.encodeQuery(name, escapeQuerySpace) + (value !== null ? '=' + URI.encodeQuery(value, escapeQuerySpace) : '');
  704. };
  705. URI.addQuery = function(data, name, value) {
  706. if (typeof name === 'object') {
  707. for (var key in name) {
  708. if (hasOwn.call(name, key)) {
  709. URI.addQuery(data, key, name[key]);
  710. }
  711. }
  712. } else if (typeof name === 'string') {
  713. if (data[name] === undefined) {
  714. data[name] = value;
  715. return;
  716. } else if (typeof data[name] === 'string') {
  717. data[name] = [data[name]];
  718. }
  719. if (!isArray(value)) {
  720. value = [value];
  721. }
  722. data[name] = (data[name] || []).concat(value);
  723. } else {
  724. throw new TypeError('URI.addQuery() accepts an object, string as the name parameter');
  725. }
  726. };
  727. URI.setQuery = function(data, name, value) {
  728. if (typeof name === 'object') {
  729. for (var key in name) {
  730. if (hasOwn.call(name, key)) {
  731. URI.setQuery(data, key, name[key]);
  732. }
  733. }
  734. } else if (typeof name === 'string') {
  735. data[name] = value === undefined ? null : value;
  736. } else {
  737. throw new TypeError('URI.setQuery() accepts an object, string as the name parameter');
  738. }
  739. };
  740. URI.removeQuery = function(data, name, value) {
  741. var i, length, key;
  742. if (isArray(name)) {
  743. for (i = 0, length = name.length; i < length; i++) {
  744. data[name[i]] = undefined;
  745. }
  746. } else if (getType(name) === 'RegExp') {
  747. for (key in data) {
  748. if (name.test(key)) {
  749. data[key] = undefined;
  750. }
  751. }
  752. } else if (typeof name === 'object') {
  753. for (key in name) {
  754. if (hasOwn.call(name, key)) {
  755. URI.removeQuery(data, key, name[key]);
  756. }
  757. }
  758. } else if (typeof name === 'string') {
  759. if (value !== undefined) {
  760. if (getType(value) === 'RegExp') {
  761. if (!isArray(data[name]) && value.test(data[name])) {
  762. data[name] = undefined;
  763. } else {
  764. data[name] = filterArrayValues(data[name], value);
  765. }
  766. } else if (data[name] === String(value) && (!isArray(value) || value.length === 1)) {
  767. data[name] = undefined;
  768. } else if (isArray(data[name])) {
  769. data[name] = filterArrayValues(data[name], value);
  770. }
  771. } else {
  772. data[name] = undefined;
  773. }
  774. } else {
  775. throw new TypeError('URI.removeQuery() accepts an object, string, RegExp as the first parameter');
  776. }
  777. };
  778. URI.hasQuery = function(data, name, value, withinArray) {
  779. switch (getType(name)) {
  780. case 'String':
  781. // Nothing to do here
  782. break;
  783. case 'RegExp':
  784. for (var key in data) {
  785. if (hasOwn.call(data, key)) {
  786. if (name.test(key) && (value === undefined || URI.hasQuery(data, key, value))) {
  787. return true;
  788. }
  789. }
  790. }
  791. return false;
  792. case 'Object':
  793. for (var _key in name) {
  794. if (hasOwn.call(name, _key)) {
  795. if (!URI.hasQuery(data, _key, name[_key])) {
  796. return false;
  797. }
  798. }
  799. }
  800. return true;
  801. default:
  802. throw new TypeError('URI.hasQuery() accepts a string, regular expression or object as the name parameter');
  803. }
  804. switch (getType(value)) {
  805. case 'Undefined':
  806. // true if exists (but may be empty)
  807. return name in data; // data[name] !== undefined;
  808. case 'Boolean':
  809. // true if exists and non-empty
  810. var _booly = Boolean(isArray(data[name]) ? data[name].length : data[name]);
  811. return value === _booly;
  812. case 'Function':
  813. // allow complex comparison
  814. return !!value(data[name], name, data);
  815. case 'Array':
  816. if (!isArray(data[name])) {
  817. return false;
  818. }
  819. var op = withinArray ? arrayContains : arraysEqual;
  820. return op(data[name], value);
  821. case 'RegExp':
  822. if (!isArray(data[name])) {
  823. return Boolean(data[name] && data[name].match(value));
  824. }
  825. if (!withinArray) {
  826. return false;
  827. }
  828. return arrayContains(data[name], value);
  829. case 'Number':
  830. value = String(value);
  831. /* falls through */
  832. case 'String':
  833. if (!isArray(data[name])) {
  834. return data[name] === value;
  835. }
  836. if (!withinArray) {
  837. return false;
  838. }
  839. return arrayContains(data[name], value);
  840. default:
  841. throw new TypeError('URI.hasQuery() accepts undefined, boolean, string, number, RegExp, Function as the value parameter');
  842. }
  843. };
  844. URI.joinPaths = function() {
  845. var input = [];
  846. var segments = [];
  847. var nonEmptySegments = 0;
  848. for (var i = 0; i < arguments.length; i++) {
  849. var url = new URI(arguments[i]);
  850. input.push(url);
  851. var _segments = url.segment();
  852. for (var s = 0; s < _segments.length; s++) {
  853. if (typeof _segments[s] === 'string') {
  854. segments.push(_segments[s]);
  855. }
  856. if (_segments[s]) {
  857. nonEmptySegments++;
  858. }
  859. }
  860. }
  861. if (!segments.length || !nonEmptySegments) {
  862. return new URI('');
  863. }
  864. var uri = new URI('').segment(segments);
  865. if (input[0].path() === '' || input[0].path().slice(0, 1) === '/') {
  866. uri.path('/' + uri.path());
  867. }
  868. return uri.normalize();
  869. };
  870. URI.commonPath = function(one, two) {
  871. var length = Math.min(one.length, two.length);
  872. var pos;
  873. // find first non-matching character
  874. for (pos = 0; pos < length; pos++) {
  875. if (one.charAt(pos) !== two.charAt(pos)) {
  876. pos--;
  877. break;
  878. }
  879. }
  880. if (pos < 1) {
  881. return one.charAt(0) === two.charAt(0) && one.charAt(0) === '/' ? '/' : '';
  882. }
  883. // revert to last /
  884. if (one.charAt(pos) !== '/' || two.charAt(pos) !== '/') {
  885. pos = one.substring(0, pos).lastIndexOf('/');
  886. }
  887. return one.substring(0, pos + 1);
  888. };
  889. URI.withinString = function(string, callback, options) {
  890. options || (options = {});
  891. var _start = options.start || URI.findUri.start;
  892. var _end = options.end || URI.findUri.end;
  893. var _trim = options.trim || URI.findUri.trim;
  894. var _parens = options.parens || URI.findUri.parens;
  895. var _attributeOpen = /[a-z0-9-]=["']?$/i;
  896. _start.lastIndex = 0;
  897. while (true) {
  898. var match = _start.exec(string);
  899. if (!match) {
  900. break;
  901. }
  902. var start = match.index;
  903. if (options.ignoreHtml) {
  904. // attribut(e=["']?$)
  905. var attributeOpen = string.slice(Math.max(start - 3, 0), start);
  906. if (attributeOpen && _attributeOpen.test(attributeOpen)) {
  907. continue;
  908. }
  909. }
  910. var end = start + string.slice(start).search(_end);
  911. var slice = string.slice(start, end);
  912. // make sure we include well balanced parens
  913. var parensEnd = -1;
  914. while (true) {
  915. var parensMatch = _parens.exec(slice);
  916. if (!parensMatch) {
  917. break;
  918. }
  919. var parensMatchEnd = parensMatch.index + parensMatch[0].length;
  920. parensEnd = Math.max(parensEnd, parensMatchEnd);
  921. }
  922. if (parensEnd > -1) {
  923. slice = slice.slice(0, parensEnd) + slice.slice(parensEnd).replace(_trim, '');
  924. } else {
  925. slice = slice.replace(_trim, '');
  926. }
  927. if (slice.length <= match[0].length) {
  928. // the extract only contains the starting marker of a URI,
  929. // e.g. "www" or "http://"
  930. continue;
  931. }
  932. if (options.ignore && options.ignore.test(slice)) {
  933. continue;
  934. }
  935. end = start + slice.length;
  936. var result = callback(slice, start, end, string);
  937. if (result === undefined) {
  938. _start.lastIndex = end;
  939. continue;
  940. }
  941. result = String(result);
  942. string = string.slice(0, start) + result + string.slice(end);
  943. _start.lastIndex = start + result.length;
  944. }
  945. _start.lastIndex = 0;
  946. return string;
  947. };
  948. URI.ensureValidHostname = function(v, protocol) {
  949. // Theoretically URIs allow percent-encoding in Hostnames (according to RFC 3986)
  950. // they are not part of DNS and therefore ignored by URI.js
  951. var hasHostname = !!v; // not null and not an empty string
  952. var hasProtocol = !!protocol;
  953. var rejectEmptyHostname = false;
  954. if (hasProtocol) {
  955. rejectEmptyHostname = arrayContains(URI.hostProtocols, protocol);
  956. }
  957. if (rejectEmptyHostname && !hasHostname) {
  958. throw new TypeError('Hostname cannot be empty, if protocol is ' + protocol);
  959. } else if (v && v.match(URI.invalid_hostname_characters)) {
  960. // test punycode
  961. if (!punycode) {
  962. throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-:_] and Punycode.js is not available');
  963. }
  964. if (punycode.toASCII(v).match(URI.invalid_hostname_characters)) {
  965. throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-:_]');
  966. }
  967. }
  968. };
  969. URI.ensureValidPort = function (v) {
  970. if (!v) {
  971. return;
  972. }
  973. var port = Number(v);
  974. if (isInteger(port) && (port > 0) && (port < 65536)) {
  975. return;
  976. }
  977. throw new TypeError('Port "' + v + '" is not a valid port');
  978. };
  979. // noConflict
  980. URI.noConflict = function(removeAll) {
  981. if (removeAll) {
  982. var unconflicted = {
  983. URI: this.noConflict()
  984. };
  985. if (root.URITemplate && typeof root.URITemplate.noConflict === 'function') {
  986. unconflicted.URITemplate = root.URITemplate.noConflict();
  987. }
  988. if (root.IPv6 && typeof root.IPv6.noConflict === 'function') {
  989. unconflicted.IPv6 = root.IPv6.noConflict();
  990. }
  991. if (root.SecondLevelDomains && typeof root.SecondLevelDomains.noConflict === 'function') {
  992. unconflicted.SecondLevelDomains = root.SecondLevelDomains.noConflict();
  993. }
  994. return unconflicted;
  995. } else if (root.URI === this) {
  996. root.URI = _URI;
  997. }
  998. return this;
  999. };
  1000. p.build = function(deferBuild) {
  1001. if (deferBuild === true) {
  1002. this._deferred_build = true;
  1003. } else if (deferBuild === undefined || this._deferred_build) {
  1004. this._string = URI.build(this._parts);
  1005. this._deferred_build = false;
  1006. }
  1007. return this;
  1008. };
  1009. p.clone = function() {
  1010. return new URI(this);
  1011. };
  1012. p.valueOf = p.toString = function() {
  1013. return this.build(false)._string;
  1014. };
  1015. function generateSimpleAccessor(_part){
  1016. return function(v, build) {
  1017. if (v === undefined) {
  1018. return this._parts[_part] || '';
  1019. } else {
  1020. this._parts[_part] = v || null;
  1021. this.build(!build);
  1022. return this;
  1023. }
  1024. };
  1025. }
  1026. function generatePrefixAccessor(_part, _key){
  1027. return function(v, build) {
  1028. if (v === undefined) {
  1029. return this._parts[_part] || '';
  1030. } else {
  1031. if (v !== null) {
  1032. v = v + '';
  1033. if (v.charAt(0) === _key) {
  1034. v = v.substring(1);
  1035. }
  1036. }
  1037. this._parts[_part] = v;
  1038. this.build(!build);
  1039. return this;
  1040. }
  1041. };
  1042. }
  1043. p.protocol = generateSimpleAccessor('protocol');
  1044. p.username = generateSimpleAccessor('username');
  1045. p.password = generateSimpleAccessor('password');
  1046. p.hostname = generateSimpleAccessor('hostname');
  1047. p.port = generateSimpleAccessor('port');
  1048. p.query = generatePrefixAccessor('query', '?');
  1049. p.fragment = generatePrefixAccessor('fragment', '#');
  1050. p.search = function(v, build) {
  1051. var t = this.query(v, build);
  1052. return typeof t === 'string' && t.length ? ('?' + t) : t;
  1053. };
  1054. p.hash = function(v, build) {
  1055. var t = this.fragment(v, build);
  1056. return typeof t === 'string' && t.length ? ('#' + t) : t;
  1057. };
  1058. p.pathname = function(v, build) {
  1059. if (v === undefined || v === true) {
  1060. var res = this._parts.path || (this._parts.hostname ? '/' : '');
  1061. return v ? (this._parts.urn ? URI.decodeUrnPath : URI.decodePath)(res) : res;
  1062. } else {
  1063. if (this._parts.urn) {
  1064. this._parts.path = v ? URI.recodeUrnPath(v) : '';
  1065. } else {
  1066. this._parts.path = v ? URI.recodePath(v) : '/';
  1067. }
  1068. this.build(!build);
  1069. return this;
  1070. }
  1071. };
  1072. p.path = p.pathname;
  1073. p.href = function(href, build) {
  1074. var key;
  1075. if (href === undefined) {
  1076. return this.toString();
  1077. }
  1078. this._string = '';
  1079. this._parts = URI._parts();
  1080. var _URI = href instanceof URI;
  1081. var _object = typeof href === 'object' && (href.hostname || href.path || href.pathname);
  1082. if (href.nodeName) {
  1083. var attribute = URI.getDomAttribute(href);
  1084. href = href[attribute] || '';
  1085. _object = false;
  1086. }
  1087. // window.location is reported to be an object, but it's not the sort
  1088. // of object we're looking for:
  1089. // * location.protocol ends with a colon
  1090. // * location.query != object.search
  1091. // * location.hash != object.fragment
  1092. // simply serializing the unknown object should do the trick
  1093. // (for location, not for everything...)
  1094. if (!_URI && _object && href.pathname !== undefined) {
  1095. href = href.toString();
  1096. }
  1097. if (typeof href === 'string' || href instanceof String) {
  1098. this._parts = URI.parse(String(href), this._parts);
  1099. } else if (_URI || _object) {
  1100. var src = _URI ? href._parts : href;
  1101. for (key in src) {
  1102. if (key === 'query') { continue; }
  1103. if (hasOwn.call(this._parts, key)) {
  1104. this._parts[key] = src[key];
  1105. }
  1106. }
  1107. if (src.query) {
  1108. this.query(src.query, false);
  1109. }
  1110. } else {
  1111. throw new TypeError('invalid input');
  1112. }
  1113. this.build(!build);
  1114. return this;
  1115. };
  1116. // identification accessors
  1117. p.is = function(what) {
  1118. var ip = false;
  1119. var ip4 = false;
  1120. var ip6 = false;
  1121. var name = false;
  1122. var sld = false;
  1123. var idn = false;
  1124. var punycode = false;
  1125. var relative = !this._parts.urn;
  1126. if (this._parts.hostname) {
  1127. relative = false;
  1128. ip4 = URI.ip4_expression.test(this._parts.hostname);
  1129. ip6 = URI.ip6_expression.test(this._parts.hostname);
  1130. ip = ip4 || ip6;
  1131. name = !ip;
  1132. sld = name && SLD && SLD.has(this._parts.hostname);
  1133. idn = name && URI.idn_expression.test(this._parts.hostname);
  1134. punycode = name && URI.punycode_expression.test(this._parts.hostname);
  1135. }
  1136. switch (what.toLowerCase()) {
  1137. case 'relative':
  1138. return relative;
  1139. case 'absolute':
  1140. return !relative;
  1141. // hostname identification
  1142. case 'domain':
  1143. case 'name':
  1144. return name;
  1145. case 'sld':
  1146. return sld;
  1147. case 'ip':
  1148. return ip;
  1149. case 'ip4':
  1150. case 'ipv4':
  1151. case 'inet4':
  1152. return ip4;
  1153. case 'ip6':
  1154. case 'ipv6':
  1155. case 'inet6':
  1156. return ip6;
  1157. case 'idn':
  1158. return idn;
  1159. case 'url':
  1160. return !this._parts.urn;
  1161. case 'urn':
  1162. return !!this._parts.urn;
  1163. case 'punycode':
  1164. return punycode;
  1165. }
  1166. return null;
  1167. };
  1168. // component specific input validation
  1169. var _protocol = p.protocol;
  1170. var _port = p.port;
  1171. var _hostname = p.hostname;
  1172. p.protocol = function(v, build) {
  1173. if (v) {
  1174. // accept trailing ://
  1175. v = v.replace(/:(\/\/)?$/, '');
  1176. if (!v.match(URI.protocol_expression)) {
  1177. throw new TypeError('Protocol "' + v + '" contains characters other than [A-Z0-9.+-] or doesn\'t start with [A-Z]');
  1178. }
  1179. }
  1180. return _protocol.call(this, v, build);
  1181. };
  1182. p.scheme = p.protocol;
  1183. p.port = function(v, build) {
  1184. if (this._parts.urn) {
  1185. return v === undefined ? '' : this;
  1186. }
  1187. if (v !== undefined) {
  1188. if (v === 0) {
  1189. v = null;
  1190. }
  1191. if (v) {
  1192. v += '';
  1193. if (v.charAt(0) === ':') {
  1194. v = v.substring(1);
  1195. }
  1196. URI.ensureValidPort(v);
  1197. }
  1198. }
  1199. return _port.call(this, v, build);
  1200. };
  1201. p.hostname = function(v, build) {
  1202. if (this._parts.urn) {
  1203. return v === undefined ? '' : this;
  1204. }
  1205. if (v !== undefined) {
  1206. var x = { preventInvalidHostname: this._parts.preventInvalidHostname };
  1207. var res = URI.parseHost(v, x);
  1208. if (res !== '/') {
  1209. throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
  1210. }
  1211. v = x.hostname;
  1212. if (this._parts.preventInvalidHostname) {
  1213. URI.ensureValidHostname(v, this._parts.protocol);
  1214. }
  1215. }
  1216. return _hostname.call(this, v, build);
  1217. };
  1218. // compound accessors
  1219. p.origin = function(v, build) {
  1220. if (this._parts.urn) {
  1221. return v === undefined ? '' : this;
  1222. }
  1223. if (v === undefined) {
  1224. var protocol = this.protocol();
  1225. var authority = this.authority();
  1226. if (!authority) {
  1227. return '';
  1228. }
  1229. return (protocol ? protocol + '://' : '') + this.authority();
  1230. } else {
  1231. var origin = URI(v);
  1232. this
  1233. .protocol(origin.protocol())
  1234. .authority(origin.authority())
  1235. .build(!build);
  1236. return this;
  1237. }
  1238. };
  1239. p.host = function(v, build) {
  1240. if (this._parts.urn) {
  1241. return v === undefined ? '' : this;
  1242. }
  1243. if (v === undefined) {
  1244. return this._parts.hostname ? URI.buildHost(this._parts) : '';
  1245. } else {
  1246. var res = URI.parseHost(v, this._parts);
  1247. if (res !== '/') {
  1248. throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
  1249. }
  1250. this.build(!build);
  1251. return this;
  1252. }
  1253. };
  1254. p.authority = function(v, build) {
  1255. if (this._parts.urn) {
  1256. return v === undefined ? '' : this;
  1257. }
  1258. if (v === undefined) {
  1259. return this._parts.hostname ? URI.buildAuthority(this._parts) : '';
  1260. } else {
  1261. var res = URI.parseAuthority(v, this._parts);
  1262. if (res !== '/') {
  1263. throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
  1264. }
  1265. this.build(!build);
  1266. return this;
  1267. }
  1268. };
  1269. p.userinfo = function(v, build) {
  1270. if (this._parts.urn) {
  1271. return v === undefined ? '' : this;
  1272. }
  1273. if (v === undefined) {
  1274. var t = URI.buildUserinfo(this._parts);
  1275. return t ? t.substring(0, t.length -1) : t;
  1276. } else {
  1277. if (v[v.length-1] !== '@') {
  1278. v += '@';
  1279. }
  1280. URI.parseUserinfo(v, this._parts);
  1281. this.build(!build);
  1282. return this;
  1283. }
  1284. };
  1285. p.resource = function(v, build) {
  1286. var parts;
  1287. if (v === undefined) {
  1288. return this.path() + this.search() + this.hash();
  1289. }
  1290. parts = URI.parse(v);
  1291. this._parts.path = parts.path;
  1292. this._parts.query = parts.query;
  1293. this._parts.fragment = parts.fragment;
  1294. this.build(!build);
  1295. return this;
  1296. };
  1297. // fraction accessors
  1298. p.subdomain = function(v, build) {
  1299. if (this._parts.urn) {
  1300. return v === undefined ? '' : this;
  1301. }
  1302. // convenience, return "www" from "www.example.org"
  1303. if (v === undefined) {
  1304. if (!this._parts.hostname || this.is('IP')) {
  1305. return '';
  1306. }
  1307. // grab domain and add another segment
  1308. var end = this._parts.hostname.length - this.domain().length - 1;
  1309. return this._parts.hostname.substring(0, end) || '';
  1310. } else {
  1311. var e = this._parts.hostname.length - this.domain().length;
  1312. var sub = this._parts.hostname.substring(0, e);
  1313. var replace = new RegExp('^' + escapeRegEx(sub));
  1314. if (v && v.charAt(v.length - 1) !== '.') {
  1315. v += '.';
  1316. }
  1317. if (v.indexOf(':') !== -1) {
  1318. throw new TypeError('Domains cannot contain colons');
  1319. }
  1320. if (v) {
  1321. URI.ensureValidHostname(v, this._parts.protocol);
  1322. }
  1323. this._parts.hostname = this._parts.hostname.replace(replace, v);
  1324. this.build(!build);
  1325. return this;
  1326. }
  1327. };
  1328. p.domain = function(v, build) {
  1329. if (this._parts.urn) {
  1330. return v === undefined ? '' : this;
  1331. }
  1332. if (typeof v === 'boolean') {
  1333. build = v;
  1334. v = undefined;
  1335. }
  1336. // convenience, return "example.org" from "www.example.org"
  1337. if (v === undefined) {
  1338. if (!this._parts.hostname || this.is('IP')) {
  1339. return '';
  1340. }
  1341. // if hostname consists of 1 or 2 segments, it must be the domain
  1342. var t = this._parts.hostname.match(/\./g);
  1343. if (t && t.length < 2) {
  1344. return this._parts.hostname;
  1345. }
  1346. // grab tld and add another segment
  1347. var end = this._parts.hostname.length - this.tld(build).length - 1;
  1348. end = this._parts.hostname.lastIndexOf('.', end -1) + 1;
  1349. return this._parts.hostname.substring(end) || '';
  1350. } else {
  1351. if (!v) {
  1352. throw new TypeError('cannot set domain empty');
  1353. }
  1354. if (v.indexOf(':') !== -1) {
  1355. throw new TypeError('Domains cannot contain colons');
  1356. }
  1357. URI.ensureValidHostname(v, this._parts.protocol);
  1358. if (!this._parts.hostname || this.is('IP')) {
  1359. this._parts.hostname = v;
  1360. } else {
  1361. var replace = new RegExp(escapeRegEx(this.domain()) + '$');
  1362. this._parts.hostname = this._parts.hostname.replace(replace, v);
  1363. }
  1364. this.build(!build);
  1365. return this;
  1366. }
  1367. };
  1368. p.tld = function(v, build) {
  1369. if (this._parts.urn) {
  1370. return v === undefined ? '' : this;
  1371. }
  1372. if (typeof v === 'boolean') {
  1373. build = v;
  1374. v = undefined;
  1375. }
  1376. // return "org" from "www.example.org"
  1377. if (v === undefined) {
  1378. if (!this._parts.hostname || this.is('IP')) {
  1379. return '';
  1380. }
  1381. var pos = this._parts.hostname.lastIndexOf('.');
  1382. var tld = this._parts.hostname.substring(pos + 1);
  1383. if (build !== true && SLD && SLD.list[tld.toLowerCase()]) {
  1384. return SLD.get(this._parts.hostname) || tld;
  1385. }
  1386. return tld;
  1387. } else {
  1388. var replace;
  1389. if (!v) {
  1390. throw new TypeError('cannot set TLD empty');
  1391. } else if (v.match(/[^a-zA-Z0-9-]/)) {
  1392. if (SLD && SLD.is(v)) {
  1393. replace = new RegExp(escapeRegEx(this.tld()) + '$');
  1394. this._parts.hostname = this._parts.hostname.replace(replace, v);
  1395. } else {
  1396. throw new TypeError('TLD "' + v + '" contains characters other than [A-Z0-9]');
  1397. }
  1398. } else if (!this._parts.hostname || this.is('IP')) {
  1399. throw new ReferenceError('cannot set TLD on non-domain host');
  1400. } else {
  1401. replace = new RegExp(escapeRegEx(this.tld()) + '$');
  1402. this._parts.hostname = this._parts.hostname.replace(replace, v);
  1403. }
  1404. this.build(!build);
  1405. return this;
  1406. }
  1407. };
  1408. p.directory = function(v, build) {
  1409. if (this._parts.urn) {
  1410. return v === undefined ? '' : this;
  1411. }
  1412. if (v === undefined || v === true) {
  1413. if (!this._parts.path && !this._parts.hostname) {
  1414. return '';
  1415. }
  1416. if (this._parts.path === '/') {
  1417. return '/';
  1418. }
  1419. var end = this._parts.path.length - this.filename().length - 1;
  1420. var res = this._parts.path.substring(0, end) || (this._parts.hostname ? '/' : '');
  1421. return v ? URI.decodePath(res) : res;
  1422. } else {
  1423. var e = this._parts.path.length - this.filename().length;
  1424. var directory = this._parts.path.substring(0, e);
  1425. var replace = new RegExp('^' + escapeRegEx(directory));
  1426. // fully qualifier directories begin with a slash
  1427. if (!this.is('relative')) {
  1428. if (!v) {
  1429. v = '/';
  1430. }
  1431. if (v.charAt(0) !== '/') {
  1432. v = '/' + v;
  1433. }
  1434. }
  1435. // directories always end with a slash
  1436. if (v && v.charAt(v.length - 1) !== '/') {
  1437. v += '/';
  1438. }
  1439. v = URI.recodePath(v);
  1440. this._parts.path = this._parts.path.replace(replace, v);
  1441. this.build(!build);
  1442. return this;
  1443. }
  1444. };
  1445. p.filename = function(v, build) {
  1446. if (this._parts.urn) {
  1447. return v === undefined ? '' : this;
  1448. }
  1449. if (typeof v !== 'string') {
  1450. if (!this._parts.path || this._parts.path === '/') {
  1451. return '';
  1452. }
  1453. var pos = this._parts.path.lastIndexOf('/');
  1454. var res = this._parts.path.substring(pos+1);
  1455. return v ? URI.decodePathSegment(res) : res;
  1456. } else {
  1457. var mutatedDirectory = false;
  1458. if (v.charAt(0) === '/') {
  1459. v = v.substring(1);
  1460. }
  1461. if (v.match(/\.?\//)) {
  1462. mutatedDirectory = true;
  1463. }
  1464. var replace = new RegExp(escapeRegEx(this.filename()) + '$');
  1465. v = URI.recodePath(v);
  1466. this._parts.path = this._parts.path.replace(replace, v);
  1467. if (mutatedDirectory) {
  1468. this.normalizePath(build);
  1469. } else {
  1470. this.build(!build);
  1471. }
  1472. return this;
  1473. }
  1474. };
  1475. p.suffix = function(v, build) {
  1476. if (this._parts.urn) {
  1477. return v === undefined ? '' : this;
  1478. }
  1479. if (v === undefined || v === true) {
  1480. if (!this._parts.path || this._parts.path === '/') {
  1481. return '';
  1482. }
  1483. var filename = this.filename();
  1484. var pos = filename.lastIndexOf('.');
  1485. var s, res;
  1486. if (pos === -1) {
  1487. return '';
  1488. }
  1489. // suffix may only contain alnum characters (yup, I made this up.)
  1490. s = filename.substring(pos+1);
  1491. res = (/^[a-z0-9%]+$/i).test(s) ? s : '';
  1492. return v ? URI.decodePathSegment(res) : res;
  1493. } else {
  1494. if (v.charAt(0) === '.') {
  1495. v = v.substring(1);
  1496. }
  1497. var suffix = this.suffix();
  1498. var replace;
  1499. if (!suffix) {
  1500. if (!v) {
  1501. return this;
  1502. }
  1503. this._parts.path += '.' + URI.recodePath(v);
  1504. } else if (!v) {
  1505. replace = new RegExp(escapeRegEx('.' + suffix) + '$');
  1506. } else {
  1507. replace = new RegExp(escapeRegEx(suffix) + '$');
  1508. }
  1509. if (replace) {
  1510. v = URI.recodePath(v);
  1511. this._parts.path = this._parts.path.replace(replace, v);
  1512. }
  1513. this.build(!build);
  1514. return this;
  1515. }
  1516. };
  1517. p.segment = function(segment, v, build) {
  1518. var separator = this._parts.urn ? ':' : '/';
  1519. var path = this.path();
  1520. var absolute = path.substring(0, 1) === '/';
  1521. var segments = path.split(separator);
  1522. if (segment !== undefined && typeof segment !== 'number') {
  1523. build = v;
  1524. v = segment;
  1525. segment = undefined;
  1526. }
  1527. if (segment !== undefined && typeof segment !== 'number') {
  1528. throw new Error('Bad segment "' + segment + '", must be 0-based integer');
  1529. }
  1530. if (absolute) {
  1531. segments.shift();
  1532. }
  1533. if (segment < 0) {
  1534. // allow negative indexes to address from the end
  1535. segment = Math.max(segments.length + segment, 0);
  1536. }
  1537. if (v === undefined) {
  1538. /*jshint laxbreak: true */
  1539. return segment === undefined
  1540. ? segments
  1541. : segments[segment];
  1542. /*jshint laxbreak: false */
  1543. } else if (segment === null || segments[segment] === undefined) {
  1544. if (isArray(v)) {
  1545. segments = [];
  1546. // collapse empty elements within array
  1547. for (var i=0, l=v.length; i < l; i++) {
  1548. if (!v[i].length && (!segments.length || !segments[segments.length -1].length)) {
  1549. continue;
  1550. }
  1551. if (segments.length && !segments[segments.length -1].length) {
  1552. segments.pop();
  1553. }
  1554. segments.push(trimSlashes(v[i]));
  1555. }
  1556. } else if (v || typeof v === 'string') {
  1557. v = trimSlashes(v);
  1558. if (segments[segments.length -1] === '') {
  1559. // empty trailing elements have to be overwritten
  1560. // to prevent results such as /foo//bar
  1561. segments[segments.length -1] = v;
  1562. } else {
  1563. segments.push(v);
  1564. }
  1565. }
  1566. } else {
  1567. if (v) {
  1568. segments[segment] = trimSlashes(v);
  1569. } else {
  1570. segments.splice(segment, 1);
  1571. }
  1572. }
  1573. if (absolute) {
  1574. segments.unshift('');
  1575. }
  1576. return this.path(segments.join(separator), build);
  1577. };
  1578. p.segmentCoded = function(segment, v, build) {
  1579. var segments, i, l;
  1580. if (typeof segment !== 'number') {
  1581. build = v;
  1582. v = segment;
  1583. segment = undefined;
  1584. }
  1585. if (v === undefined) {
  1586. segments = this.segment(segment, v, build);
  1587. if (!isArray(segments)) {
  1588. segments = segments !== undefined ? URI.decode(segments) : undefined;
  1589. } else {
  1590. for (i = 0, l = segments.length; i < l; i++) {
  1591. segments[i] = URI.decode(segments[i]);
  1592. }
  1593. }
  1594. return segments;
  1595. }
  1596. if (!isArray(v)) {
  1597. v = (typeof v === 'string' || v instanceof String) ? URI.encode(v) : v;
  1598. } else {
  1599. for (i = 0, l = v.length; i < l; i++) {
  1600. v[i] = URI.encode(v[i]);
  1601. }
  1602. }
  1603. return this.segment(segment, v, build);
  1604. };
  1605. // mutating query string
  1606. var q = p.query;
  1607. p.query = function(v, build) {
  1608. if (v === true) {
  1609. return URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
  1610. } else if (typeof v === 'function') {
  1611. var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
  1612. var result = v.call(this, data);
  1613. this._parts.query = URI.buildQuery(result || data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
  1614. this.build(!build);
  1615. return this;
  1616. } else if (v !== undefined && typeof v !== 'string') {
  1617. this._parts.query = URI.buildQuery(v, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
  1618. this.build(!build);
  1619. return this;
  1620. } else {
  1621. return q.call(this, v, build);
  1622. }
  1623. };
  1624. p.setQuery = function(name, value, build) {
  1625. var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
  1626. if (typeof name === 'string' || name instanceof String) {
  1627. data[name] = value !== undefined ? value : null;
  1628. } else if (typeof name === 'object') {
  1629. for (var key in name) {
  1630. if (hasOwn.call(name, key)) {
  1631. data[key] = name[key];
  1632. }
  1633. }
  1634. } else {
  1635. throw new TypeError('URI.addQuery() accepts an object, string as the name parameter');
  1636. }
  1637. this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
  1638. if (typeof name !== 'string') {
  1639. build = value;
  1640. }
  1641. this.build(!build);
  1642. return this;
  1643. };
  1644. p.addQuery = function(name, value, build) {
  1645. var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
  1646. URI.addQuery(data, name, value === undefined ? null : value);
  1647. this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
  1648. if (typeof name !== 'string') {
  1649. build = value;
  1650. }
  1651. this.build(!build);
  1652. return this;
  1653. };
  1654. p.removeQuery = function(name, value, build) {
  1655. var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
  1656. URI.removeQuery(data, name, value);
  1657. this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
  1658. if (typeof name !== 'string') {
  1659. build = value;
  1660. }
  1661. this.build(!build);
  1662. return this;
  1663. };
  1664. p.hasQuery = function(name, value, withinArray) {
  1665. var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
  1666. return URI.hasQuery(data, name, value, withinArray);
  1667. };
  1668. p.setSearch = p.setQuery;
  1669. p.addSearch = p.addQuery;
  1670. p.removeSearch = p.removeQuery;
  1671. p.hasSearch = p.hasQuery;
  1672. // sanitizing URLs
  1673. p.normalize = function() {
  1674. if (this._parts.urn) {
  1675. return this
  1676. .normalizeProtocol(false)
  1677. .normalizePath(false)
  1678. .normalizeQuery(false)
  1679. .normalizeFragment(false)
  1680. .build();
  1681. }
  1682. return this
  1683. .normalizeProtocol(false)
  1684. .normalizeHostname(false)
  1685. .normalizePort(false)
  1686. .normalizePath(false)
  1687. .normalizeQuery(false)
  1688. .normalizeFragment(false)
  1689. .build();
  1690. };
  1691. p.normalizeProtocol = function(build) {
  1692. if (typeof this._parts.protocol === 'string') {
  1693. this._parts.protocol = this._parts.protocol.toLowerCase();
  1694. this.build(!build);
  1695. }
  1696. return this;
  1697. };
  1698. p.normalizeHostname = function(build) {
  1699. if (this._parts.hostname) {
  1700. if (this.is('IDN') && punycode) {
  1701. this._parts.hostname = punycode.toASCII(this._parts.hostname);
  1702. } else if (this.is('IPv6') && IPv6) {
  1703. this._parts.hostname = IPv6.best(this._parts.hostname);
  1704. }
  1705. this._parts.hostname = this._parts.hostname.toLowerCase();
  1706. this.build(!build);
  1707. }
  1708. return this;
  1709. };
  1710. p.normalizePort = function(build) {
  1711. // remove port of it's the protocol's default
  1712. if (typeof this._parts.protocol === 'string' && this._parts.port === URI.defaultPorts[this._parts.protocol]) {
  1713. this._parts.port = null;
  1714. this.build(!build);
  1715. }
  1716. return this;
  1717. };
  1718. p.normalizePath = function(build) {
  1719. var _path = this._parts.path;
  1720. if (!_path) {
  1721. return this;
  1722. }
  1723. if (this._parts.urn) {
  1724. this._parts.path = URI.recodeUrnPath(this._parts.path);
  1725. this.build(!build);
  1726. return this;
  1727. }
  1728. if (this._parts.path === '/') {
  1729. return this;
  1730. }
  1731. _path = URI.recodePath(_path);
  1732. var _was_relative;
  1733. var _leadingParents = '';
  1734. var _parent, _pos;
  1735. // handle relative paths
  1736. if (_path.charAt(0) !== '/') {
  1737. _was_relative = true;
  1738. _path = '/' + _path;
  1739. }
  1740. // handle relative files (as opposed to directories)
  1741. if (_path.slice(-3) === '/..' || _path.slice(-2) === '/.') {
  1742. _path += '/';
  1743. }
  1744. // resolve simples
  1745. _path = _path
  1746. .replace(/(\/(\.\/)+)|(\/\.$)/g, '/')
  1747. .replace(/\/{2,}/g, '/');
  1748. // remember leading parents
  1749. if (_was_relative) {
  1750. _leadingParents = _path.substring(1).match(/^(\.\.\/)+/) || '';
  1751. if (_leadingParents) {
  1752. _leadingParents = _leadingParents[0];
  1753. }
  1754. }
  1755. // resolve parents
  1756. while (true) {
  1757. _parent = _path.search(/\/\.\.(\/|$)/);
  1758. if (_parent === -1) {
  1759. // no more ../ to resolve
  1760. break;
  1761. } else if (_parent === 0) {
  1762. // top level cannot be relative, skip it
  1763. _path = _path.substring(3);
  1764. continue;
  1765. }
  1766. _pos = _path.substring(0, _parent).lastIndexOf('/');
  1767. if (_pos === -1) {
  1768. _pos = _parent;
  1769. }
  1770. _path = _path.substring(0, _pos) + _path.substring(_parent + 3);
  1771. }
  1772. // revert to relative
  1773. if (_was_relative && this.is('relative')) {
  1774. _path = _leadingParents + _path.substring(1);
  1775. }
  1776. this._parts.path = _path;
  1777. this.build(!build);
  1778. return this;
  1779. };
  1780. p.normalizePathname = p.normalizePath;
  1781. p.normalizeQuery = function(build) {
  1782. if (typeof this._parts.query === 'string') {
  1783. if (!this._parts.query.length) {
  1784. this._parts.query = null;
  1785. } else {
  1786. this.query(URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace));
  1787. }
  1788. this.build(!build);
  1789. }
  1790. return this;
  1791. };
  1792. p.normalizeFragment = function(build) {
  1793. if (!this._parts.fragment) {
  1794. this._parts.fragment = null;
  1795. this.build(!build);
  1796. }
  1797. return this;
  1798. };
  1799. p.normalizeSearch = p.normalizeQuery;
  1800. p.normalizeHash = p.normalizeFragment;
  1801. p.iso8859 = function() {
  1802. // expect unicode input, iso8859 output
  1803. var e = URI.encode;
  1804. var d = URI.decode;
  1805. URI.encode = escape;
  1806. URI.decode = decodeURIComponent;
  1807. try {
  1808. this.normalize();
  1809. } finally {
  1810. URI.encode = e;
  1811. URI.decode = d;
  1812. }
  1813. return this;
  1814. };
  1815. p.unicode = function() {
  1816. // expect iso8859 input, unicode output
  1817. var e = URI.encode;
  1818. var d = URI.decode;
  1819. URI.encode = strictEncodeURIComponent;
  1820. URI.decode = unescape;
  1821. try {
  1822. this.normalize();
  1823. } finally {
  1824. URI.encode = e;
  1825. URI.decode = d;
  1826. }
  1827. return this;
  1828. };
  1829. p.readable = function() {
  1830. var uri = this.clone();
  1831. // removing username, password, because they shouldn't be displayed according to RFC 3986
  1832. uri.username('').password('').normalize();
  1833. var t = '';
  1834. if (uri._parts.protocol) {
  1835. t += uri._parts.protocol + '://';
  1836. }
  1837. if (uri._parts.hostname) {
  1838. if (uri.is('punycode') && punycode) {
  1839. t += punycode.toUnicode(uri._parts.hostname);
  1840. if (uri._parts.port) {
  1841. t += ':' + uri._parts.port;
  1842. }
  1843. } else {
  1844. t += uri.host();
  1845. }
  1846. }
  1847. if (uri._parts.hostname && uri._parts.path && uri._parts.path.charAt(0) !== '/') {
  1848. t += '/';
  1849. }
  1850. t += uri.path(true);
  1851. if (uri._parts.query) {
  1852. var q = '';
  1853. for (var i = 0, qp = uri._parts.query.split('&'), l = qp.length; i < l; i++) {
  1854. var kv = (qp[i] || '').split('=');
  1855. q += '&' + URI.decodeQuery(kv[0], this._parts.escapeQuerySpace)
  1856. .replace(/&/g, '%26');
  1857. if (kv[1] !== undefined) {
  1858. q += '=' + URI.decodeQuery(kv[1], this._parts.escapeQuerySpace)
  1859. .replace(/&/g, '%26');
  1860. }
  1861. }
  1862. t += '?' + q.substring(1);
  1863. }
  1864. t += URI.decodeQuery(uri.hash(), true);
  1865. return t;
  1866. };
  1867. // resolving relative and absolute URLs
  1868. p.absoluteTo = function(base) {
  1869. var resolved = this.clone();
  1870. var properties = ['protocol', 'username', 'password', 'hostname', 'port'];
  1871. var basedir, i, p;
  1872. if (this._parts.urn) {
  1873. throw new Error('URNs do not have any generally defined hierarchical components');
  1874. }
  1875. if (!(base instanceof URI)) {
  1876. base = new URI(base);
  1877. }
  1878. if (resolved._parts.protocol) {
  1879. // Directly returns even if this._parts.hostname is empty.
  1880. return resolved;
  1881. } else {
  1882. resolved._parts.protocol = base._parts.protocol;
  1883. }
  1884. if (this._parts.hostname) {
  1885. return resolved;
  1886. }
  1887. for (i = 0; (p = properties[i]); i++) {
  1888. resolved._parts[p] = base._parts[p];
  1889. }
  1890. if (!resolved._parts.path) {
  1891. resolved._parts.path = base._parts.path;
  1892. if (!resolved._parts.query) {
  1893. resolved._parts.query = base._parts.query;
  1894. }
  1895. } else {
  1896. if (resolved._parts.path.substring(-2) === '..') {
  1897. resolved._parts.path += '/';
  1898. }
  1899. if (resolved.path().charAt(0) !== '/') {
  1900. basedir = base.directory();
  1901. basedir = basedir ? basedir : base.path().indexOf('/') === 0 ? '/' : '';
  1902. resolved._parts.path = (basedir ? (basedir + '/') : '') + resolved._parts.path;
  1903. resolved.normalizePath();
  1904. }
  1905. }
  1906. resolved.build();
  1907. return resolved;
  1908. };
  1909. p.relativeTo = function(base) {
  1910. var relative = this.clone().normalize();
  1911. var relativeParts, baseParts, common, relativePath, basePath;
  1912. if (relative._parts.urn) {
  1913. throw new Error('URNs do not have any generally defined hierarchical components');
  1914. }
  1915. base = new URI(base).normalize();
  1916. relativeParts = relative._parts;
  1917. baseParts = base._parts;
  1918. relativePath = relative.path();
  1919. basePath = base.path();
  1920. if (relativePath.charAt(0) !== '/') {
  1921. throw new Error('URI is already relative');
  1922. }
  1923. if (basePath.charAt(0) !== '/') {
  1924. throw new Error('Cannot calculate a URI relative to another relative URI');
  1925. }
  1926. if (relativeParts.protocol === baseParts.protocol) {
  1927. relativeParts.protocol = null;
  1928. }
  1929. if (relativeParts.username !== baseParts.username || relativeParts.password !== baseParts.password) {
  1930. return relative.build();
  1931. }
  1932. if (relativeParts.protocol !== null || relativeParts.username !== null || relativeParts.password !== null) {
  1933. return relative.build();
  1934. }
  1935. if (relativeParts.hostname === baseParts.hostname && relativeParts.port === baseParts.port) {
  1936. relativeParts.hostname = null;
  1937. relativeParts.port = null;
  1938. } else {
  1939. return relative.build();
  1940. }
  1941. if (relativePath === basePath) {
  1942. relativeParts.path = '';
  1943. return relative.build();
  1944. }
  1945. // determine common sub path
  1946. common = URI.commonPath(relativePath, basePath);
  1947. // If the paths have nothing in common, return a relative URL with the absolute path.
  1948. if (!common) {
  1949. return relative.build();
  1950. }
  1951. var parents = baseParts.path
  1952. .substring(common.length)
  1953. .replace(/[^\/]*$/, '')
  1954. .replace(/.*?\//g, '../');
  1955. relativeParts.path = (parents + relativeParts.path.substring(common.length)) || './';
  1956. return relative.build();
  1957. };
  1958. // comparing URIs
  1959. p.equals = function(uri) {
  1960. var one = this.clone();
  1961. var two = new URI(uri);
  1962. var one_map = {};
  1963. var two_map = {};
  1964. var checked = {};
  1965. var one_query, two_query, key;
  1966. one.normalize();
  1967. two.normalize();
  1968. // exact match
  1969. if (one.toString() === two.toString()) {
  1970. return true;
  1971. }
  1972. // extract query string
  1973. one_query = one.query();
  1974. two_query = two.query();
  1975. one.query('');
  1976. two.query('');
  1977. // definitely not equal if not even non-query parts match
  1978. if (one.toString() !== two.toString()) {
  1979. return false;
  1980. }
  1981. // query parameters have the same length, even if they're permuted
  1982. if (one_query.length !== two_query.length) {
  1983. return false;
  1984. }
  1985. one_map = URI.parseQuery(one_query, this._parts.escapeQuerySpace);
  1986. two_map = URI.parseQuery(two_query, this._parts.escapeQuerySpace);
  1987. for (key in one_map) {
  1988. if (hasOwn.call(one_map, key)) {
  1989. if (!isArray(one_map[key])) {
  1990. if (one_map[key] !== two_map[key]) {
  1991. return false;
  1992. }
  1993. } else if (!arraysEqual(one_map[key], two_map[key])) {
  1994. return false;
  1995. }
  1996. checked[key] = true;
  1997. }
  1998. }
  1999. for (key in two_map) {
  2000. if (hasOwn.call(two_map, key)) {
  2001. if (!checked[key]) {
  2002. // two contains a parameter not present in one
  2003. return false;
  2004. }
  2005. }
  2006. }
  2007. return true;
  2008. };
  2009. // state
  2010. p.preventInvalidHostname = function(v) {
  2011. this._parts.preventInvalidHostname = !!v;
  2012. return this;
  2013. };
  2014. p.duplicateQueryParameters = function(v) {
  2015. this._parts.duplicateQueryParameters = !!v;
  2016. return this;
  2017. };
  2018. p.escapeQuerySpace = function(v) {
  2019. this._parts.escapeQuerySpace = !!v;
  2020. return this;
  2021. };
  2022. return URI;
  2023. }));