|
1 /** |
|
2 * URI.js |
|
3 * |
|
4 * Copyright, Moxiecode Systems AB |
|
5 * Released under LGPL License. |
|
6 * |
|
7 * License: http://www.tinymce.com/license |
|
8 * Contributing: http://www.tinymce.com/contributing |
|
9 */ |
|
10 |
|
11 /** |
|
12 * This class handles parsing, modification and serialization of URI/URL strings. |
|
13 * @class tinymce.util.URI |
|
14 */ |
|
15 define("tinymce/util/URI", [ |
|
16 "tinymce/util/Tools" |
|
17 ], function(Tools) { |
|
18 var each = Tools.each, trim = Tools.trim; |
|
19 var queryParts = "source protocol authority userInfo user password host port relative path directory file query anchor".split(' '); |
|
20 var DEFAULT_PORTS = { |
|
21 'ftp': 21, |
|
22 'http': 80, |
|
23 'https': 443, |
|
24 'mailto': 25 |
|
25 }; |
|
26 |
|
27 /** |
|
28 * Constructs a new URI instance. |
|
29 * |
|
30 * @constructor |
|
31 * @method URI |
|
32 * @param {String} url URI string to parse. |
|
33 * @param {Object} settings Optional settings object. |
|
34 */ |
|
35 function URI(url, settings) { |
|
36 var self = this, baseUri, base_url; |
|
37 |
|
38 url = trim(url); |
|
39 settings = self.settings = settings || {}; |
|
40 baseUri = settings.base_uri; |
|
41 |
|
42 // Strange app protocol that isn't http/https or local anchor |
|
43 // For example: mailto,skype,tel etc. |
|
44 if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) { |
|
45 self.source = url; |
|
46 return; |
|
47 } |
|
48 |
|
49 var isProtocolRelative = url.indexOf('//') === 0; |
|
50 |
|
51 // Absolute path with no host, fake host and protocol |
|
52 if (url.indexOf('/') === 0 && !isProtocolRelative) { |
|
53 url = (baseUri ? baseUri.protocol || 'http' : 'http') + '://mce_host' + url; |
|
54 } |
|
55 |
|
56 // Relative path http:// or protocol relative //path |
|
57 if (!/^[\w\-]*:?\/\//.test(url)) { |
|
58 base_url = settings.base_uri ? settings.base_uri.path : new URI(location.href).directory; |
|
59 if (settings.base_uri.protocol === "") { |
|
60 url = '//mce_host' + self.toAbsPath(base_url, url); |
|
61 } else { |
|
62 url = /([^#?]*)([#?]?.*)/.exec(url); |
|
63 url = ((baseUri && baseUri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url[1]) + url[2]; |
|
64 } |
|
65 } |
|
66 |
|
67 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) |
|
68 url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something |
|
69 |
|
70 /*jshint maxlen: 255 */ |
|
71 /*eslint max-len: 0 */ |
|
72 url = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url); |
|
73 |
|
74 each(queryParts, function(v, i) { |
|
75 var part = url[i]; |
|
76 |
|
77 // Zope 3 workaround, they use @@something |
|
78 if (part) { |
|
79 part = part.replace(/\(mce_at\)/g, '@@'); |
|
80 } |
|
81 |
|
82 self[v] = part; |
|
83 }); |
|
84 |
|
85 if (baseUri) { |
|
86 if (!self.protocol) { |
|
87 self.protocol = baseUri.protocol; |
|
88 } |
|
89 |
|
90 if (!self.userInfo) { |
|
91 self.userInfo = baseUri.userInfo; |
|
92 } |
|
93 |
|
94 if (!self.port && self.host === 'mce_host') { |
|
95 self.port = baseUri.port; |
|
96 } |
|
97 |
|
98 if (!self.host || self.host === 'mce_host') { |
|
99 self.host = baseUri.host; |
|
100 } |
|
101 |
|
102 self.source = ''; |
|
103 } |
|
104 |
|
105 if (isProtocolRelative) { |
|
106 self.protocol = ''; |
|
107 } |
|
108 |
|
109 //t.path = t.path || '/'; |
|
110 } |
|
111 |
|
112 URI.prototype = { |
|
113 /** |
|
114 * Sets the internal path part of the URI. |
|
115 * |
|
116 * @method setPath |
|
117 * @param {string} path Path string to set. |
|
118 */ |
|
119 setPath: function(path) { |
|
120 var self = this; |
|
121 |
|
122 path = /^(.*?)\/?(\w+)?$/.exec(path); |
|
123 |
|
124 // Update path parts |
|
125 self.path = path[0]; |
|
126 self.directory = path[1]; |
|
127 self.file = path[2]; |
|
128 |
|
129 // Rebuild source |
|
130 self.source = ''; |
|
131 self.getURI(); |
|
132 }, |
|
133 |
|
134 /** |
|
135 * Converts the specified URI into a relative URI based on the current URI instance location. |
|
136 * |
|
137 * @method toRelative |
|
138 * @param {String} uri URI to convert into a relative path/URI. |
|
139 * @return {String} Relative URI from the point specified in the current URI instance. |
|
140 * @example |
|
141 * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm |
|
142 * var url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm'); |
|
143 */ |
|
144 toRelative: function(uri) { |
|
145 var self = this, output; |
|
146 |
|
147 if (uri === "./") { |
|
148 return uri; |
|
149 } |
|
150 |
|
151 uri = new URI(uri, {base_uri: self}); |
|
152 |
|
153 // Not on same domain/port or protocol |
|
154 if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port || |
|
155 (self.protocol != uri.protocol && uri.protocol !== "")) { |
|
156 return uri.getURI(); |
|
157 } |
|
158 |
|
159 var tu = self.getURI(), uu = uri.getURI(); |
|
160 |
|
161 // Allow usage of the base_uri when relative_urls = true |
|
162 if (tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) { |
|
163 return tu; |
|
164 } |
|
165 |
|
166 output = self.toRelPath(self.path, uri.path); |
|
167 |
|
168 // Add query |
|
169 if (uri.query) { |
|
170 output += '?' + uri.query; |
|
171 } |
|
172 |
|
173 // Add anchor |
|
174 if (uri.anchor) { |
|
175 output += '#' + uri.anchor; |
|
176 } |
|
177 |
|
178 return output; |
|
179 }, |
|
180 |
|
181 /** |
|
182 * Converts the specified URI into a absolute URI based on the current URI instance location. |
|
183 * |
|
184 * @method toAbsolute |
|
185 * @param {String} uri URI to convert into a relative path/URI. |
|
186 * @param {Boolean} noHost No host and protocol prefix. |
|
187 * @return {String} Absolute URI from the point specified in the current URI instance. |
|
188 * @example |
|
189 * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm |
|
190 * var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm'); |
|
191 */ |
|
192 toAbsolute: function(uri, noHost) { |
|
193 uri = new URI(uri, {base_uri: this}); |
|
194 |
|
195 return uri.getURI(noHost && this.isSameOrigin(uri)); |
|
196 }, |
|
197 |
|
198 /** |
|
199 * Determine whether the given URI has the same origin as this URI. Based on RFC-6454. |
|
200 * Supports default ports for protocols listed in DEFAULT_PORTS. Unsupported protocols will fail safe: they |
|
201 * won't match, if the port specifications differ. |
|
202 * |
|
203 * @method isSameOrigin |
|
204 * @param {tinymce.util.URI} uri Uri instance to compare. |
|
205 * @returns {Boolean} True if the origins are the same. |
|
206 */ |
|
207 isSameOrigin: function(uri) { |
|
208 if (this.host == uri.host && this.protocol == uri.protocol) { |
|
209 if (this.port == uri.port) { |
|
210 return true; |
|
211 } |
|
212 |
|
213 var defaultPort = DEFAULT_PORTS[this.protocol]; |
|
214 if (defaultPort && ((this.port || defaultPort) == (uri.port || defaultPort))) { |
|
215 return true; |
|
216 } |
|
217 } |
|
218 |
|
219 return false; |
|
220 }, |
|
221 |
|
222 /** |
|
223 * Converts a absolute path into a relative path. |
|
224 * |
|
225 * @method toRelPath |
|
226 * @param {String} base Base point to convert the path from. |
|
227 * @param {String} path Absolute path to convert into a relative path. |
|
228 */ |
|
229 toRelPath: function(base, path) { |
|
230 var items, breakPoint = 0, out = '', i, l; |
|
231 |
|
232 // Split the paths |
|
233 base = base.substring(0, base.lastIndexOf('/')); |
|
234 base = base.split('/'); |
|
235 items = path.split('/'); |
|
236 |
|
237 if (base.length >= items.length) { |
|
238 for (i = 0, l = base.length; i < l; i++) { |
|
239 if (i >= items.length || base[i] != items[i]) { |
|
240 breakPoint = i + 1; |
|
241 break; |
|
242 } |
|
243 } |
|
244 } |
|
245 |
|
246 if (base.length < items.length) { |
|
247 for (i = 0, l = items.length; i < l; i++) { |
|
248 if (i >= base.length || base[i] != items[i]) { |
|
249 breakPoint = i + 1; |
|
250 break; |
|
251 } |
|
252 } |
|
253 } |
|
254 |
|
255 if (breakPoint === 1) { |
|
256 return path; |
|
257 } |
|
258 |
|
259 for (i = 0, l = base.length - (breakPoint - 1); i < l; i++) { |
|
260 out += "../"; |
|
261 } |
|
262 |
|
263 for (i = breakPoint - 1, l = items.length; i < l; i++) { |
|
264 if (i != breakPoint - 1) { |
|
265 out += "/" + items[i]; |
|
266 } else { |
|
267 out += items[i]; |
|
268 } |
|
269 } |
|
270 |
|
271 return out; |
|
272 }, |
|
273 |
|
274 /** |
|
275 * Converts a relative path into a absolute path. |
|
276 * |
|
277 * @method toAbsPath |
|
278 * @param {String} base Base point to convert the path from. |
|
279 * @param {String} path Relative path to convert into an absolute path. |
|
280 */ |
|
281 toAbsPath: function(base, path) { |
|
282 var i, nb = 0, o = [], tr, outPath; |
|
283 |
|
284 // Split paths |
|
285 tr = /\/$/.test(path) ? '/' : ''; |
|
286 base = base.split('/'); |
|
287 path = path.split('/'); |
|
288 |
|
289 // Remove empty chunks |
|
290 each(base, function(k) { |
|
291 if (k) { |
|
292 o.push(k); |
|
293 } |
|
294 }); |
|
295 |
|
296 base = o; |
|
297 |
|
298 // Merge relURLParts chunks |
|
299 for (i = path.length - 1, o = []; i >= 0; i--) { |
|
300 // Ignore empty or . |
|
301 if (path[i].length === 0 || path[i] === ".") { |
|
302 continue; |
|
303 } |
|
304 |
|
305 // Is parent |
|
306 if (path[i] === '..') { |
|
307 nb++; |
|
308 continue; |
|
309 } |
|
310 |
|
311 // Move up |
|
312 if (nb > 0) { |
|
313 nb--; |
|
314 continue; |
|
315 } |
|
316 |
|
317 o.push(path[i]); |
|
318 } |
|
319 |
|
320 i = base.length - nb; |
|
321 |
|
322 // If /a/b/c or / |
|
323 if (i <= 0) { |
|
324 outPath = o.reverse().join('/'); |
|
325 } else { |
|
326 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); |
|
327 } |
|
328 |
|
329 // Add front / if it's needed |
|
330 if (outPath.indexOf('/') !== 0) { |
|
331 outPath = '/' + outPath; |
|
332 } |
|
333 |
|
334 // Add traling / if it's needed |
|
335 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) { |
|
336 outPath += tr; |
|
337 } |
|
338 |
|
339 return outPath; |
|
340 }, |
|
341 |
|
342 /** |
|
343 * Returns the full URI of the internal structure. |
|
344 * |
|
345 * @method getURI |
|
346 * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false. |
|
347 */ |
|
348 getURI: function(noProtoHost) { |
|
349 var s, self = this; |
|
350 |
|
351 // Rebuild source |
|
352 if (!self.source || noProtoHost) { |
|
353 s = ''; |
|
354 |
|
355 if (!noProtoHost) { |
|
356 if (self.protocol) { |
|
357 s += self.protocol + '://'; |
|
358 } else { |
|
359 s += '//'; |
|
360 } |
|
361 |
|
362 if (self.userInfo) { |
|
363 s += self.userInfo + '@'; |
|
364 } |
|
365 |
|
366 if (self.host) { |
|
367 s += self.host; |
|
368 } |
|
369 |
|
370 if (self.port) { |
|
371 s += ':' + self.port; |
|
372 } |
|
373 } |
|
374 |
|
375 if (self.path) { |
|
376 s += self.path; |
|
377 } |
|
378 |
|
379 if (self.query) { |
|
380 s += '?' + self.query; |
|
381 } |
|
382 |
|
383 if (self.anchor) { |
|
384 s += '#' + self.anchor; |
|
385 } |
|
386 |
|
387 self.source = s; |
|
388 } |
|
389 |
|
390 return self.source; |
|
391 } |
|
392 }; |
|
393 |
|
394 return URI; |
|
395 }); |