1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/trace",
  5     "firebug/lib/string",
  6 ],
  7 function (FBTrace, Str) {
  8 
  9 // ********************************************************************************************* //
 10 // Constants
 11 
 12 const Cc = Components.classes;
 13 const Ci = Components.interfaces;
 14 
 15 const ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
 16 
 17 // ********************************************************************************************* //
 18 // Implementation
 19 
 20 var Url = {};
 21 
 22 // ************************************************************************************************
 23 // Regular expressions
 24 
 25 Url.reCSS = /\.css$/;
 26 Url.reJavascript = /\s*javascript:\s*(.*)/;
 27 Url.reFile = /file:\/\/([^\/]*)\//;
 28 Url.reChrome = /chrome:\/\/([^\/]*)\//;
 29 Url.reDataURL = /data:text\/javascript;fileName=([^;]*);baseLineNumber=(\d*?),((?:.*?%0A)|(?:.*))/g;
 30 
 31 // ************************************************************************************************
 32 // URLs
 33 
 34 Url.getFileName = function(url)
 35 {
 36     var split = Url.splitURLBase(url);
 37     return split.name;
 38 };
 39 
 40 Url.getProtocol = function(url)
 41 {
 42     var split = Url.splitURLBase(url);
 43     return split.protocol;
 44 };
 45 
 46 Url.splitURLBase = function(url)
 47 {
 48     if (Url.isDataURL(url))
 49         return Url.splitDataURL(url);
 50     return Url.splitURLTrue(url);
 51 };
 52 
 53 Url.splitDataURL = function(url)
 54 {
 55     if (!Str.hasPrefix(url, "data:"))
 56         return false; //  the first 5 chars must be 'data:'
 57 
 58     var point = url.indexOf(",", 5);
 59     if (point < 5)
 60         return false; // syntax error
 61 
 62     var props = { protocol: "data", encodedContent: url.substr(point+1) };
 63 
 64     var metadataBuffer = url.substring(5, point);
 65     var metadata = metadataBuffer.split(";");
 66     for (var i = 0; i < metadata.length; i++)
 67     {
 68         var nv = metadata[i].split("=");
 69         if (nv.length == 2)
 70             props[nv[0]] = nv[1];
 71     }
 72 
 73     // Additional Firebug-specific properties
 74     if (props.hasOwnProperty("fileName"))
 75     {
 76          var caller_URL = decodeURIComponent(props["fileName"]);
 77          var caller_split = Url.splitURLTrue(caller_URL);
 78 
 79          props["fileName"] = caller_URL;
 80 
 81         if (props.hasOwnProperty("baseLineNumber"))  // this means it's probably an eval()
 82         {
 83             props["path"] = caller_split.path;
 84             props["line"] = props["baseLineNumber"];
 85             var hint = decodeURIComponent(props["encodedContent"]).substr(0,200).replace(/\s*$/, "");
 86             props["name"] =  "eval->"+hint;
 87         }
 88         else
 89         {
 90             props["name"] = caller_split.name;
 91             props["path"] = caller_split.path;
 92         }
 93     }
 94     else
 95     {
 96         if (!props.hasOwnProperty("path"))
 97             props["path"] = "data:";
 98         if (!props.hasOwnProperty("name"))
 99             props["name"] =  decodeURIComponent(props["encodedContent"]).substr(0,200).replace(/\s*$/, "");
100     }
101 
102     return props;
103 };
104 
105 const reSplitFile = /(.*?):\/{2,3}([^\/]*)(.*?)([^\/]*?)($|\?.*)/;
106 Url.splitURLTrue = function(url)
107 {
108     var m = reSplitFile.exec(url);
109     if (!m)
110         return {name: url, path: url};
111     else if (m[4] == "" && m[5] == "")
112         return {protocol: m[1], domain: m[2], path: m[3], name: m[3] != "/" ? m[3] : m[2]};
113     else
114         return {protocol: m[1], domain: m[2], path: m[2]+m[3], name: m[4]+m[5]};
115 };
116 
117 Url.getFileExtension = function(url)
118 {
119     if (!url)
120         return null;
121 
122     // Remove query string from the URL if any.
123     var queryString = url.indexOf("?");
124     if (queryString != -1)
125         url = url.substr(0, queryString);
126 
127     // Now get the file extension.
128     var lastDot = url.lastIndexOf(".");
129     return url.substr(lastDot+1);
130 };
131 
132 Url.isSystemURL = function(url)
133 {
134     if (!url) return true;
135     if (url.length == 0) return true;
136     if (url[0] == "h") return false;
137     if (url.substr(0, 9) == "resource:")
138         return true;
139     else if (url.substr(0, 16) == "chrome://firebug")
140         return true;
141     else if (url  == "XPCSafeJSObjectWrapper.cpp")
142         return true;
143     else if (url.substr(0, 6) == "about:")
144         return true;
145     else if (url.indexOf("firebug-service.js") != -1)
146         return true;
147     else if (url.indexOf("/modules/debuggerHalter.js") != -1)
148         return true;
149     else
150         return false;
151 };
152 
153 Url.isSystemPage = function(win)
154 {
155     try
156     {
157         var doc = win.document;
158         if (!doc)
159             return false;
160 
161         // Detect pages for pretty printed XML
162         if ((doc.styleSheets.length && doc.styleSheets[0].href
163                 == "chrome://global/content/xml/XMLPrettyPrint.css")
164             || (doc.styleSheets.length > 1 && doc.styleSheets[1].href
165                 == "chrome://browser/skin/feeds/subscribe.css"))
166             return true;
167 
168         return Url.isSystemURL(win.location.href);
169     }
170     catch (exc)
171     {
172         // Sometimes documents just aren't ready to be manipulated here, but don't let that
173         // gum up the works
174         FBTrace.sysout("Url.isSystemPage; EXCEPTION document not ready?: " + exc);
175         return false;
176     }
177 }
178 
179 Url.isSystemStyleSheet = function(sheet)
180 {
181     var href = sheet && sheet.href;
182     return href && Url.isSystemURL(href);
183 };
184 
185 Url.getURIHost = function(uri)
186 {
187     try
188     {
189         if (uri)
190             return uri.host;
191         else
192             return "";
193     }
194     catch (exc)
195     {
196         return "";
197     }
198 }
199 
200 Url.isLocalURL = function(url)
201 {
202     if (url.substr(0, 5) == "file:")
203         return true;
204     else if (url.substr(0, 8) == "wyciwyg:")
205         return true;
206     else
207         return false;
208 };
209 
210 Url.isDataURL = function(url)
211 {
212     return (url && url.substr(0,5) == "data:");
213 };
214 
215 Url.getLocalPath = function(url)
216 {
217     if (this.isLocalURL(url))
218     {
219         var fileHandler = ioService.getProtocolHandler("file")
220             .QueryInterface(Ci.nsIFileProtocolHandler);
221         var file = fileHandler.getFileFromURLSpec(url);
222         return file.path;
223     }
224 };
225 
226 /**
227  * Mozilla URI from non-web URL
228  * @param URL 
229  * @returns undefined or nsIURI
230  */
231 Url.getLocalSystemURI = function(url)
232 {
233     try
234     {
235         var uri = ioService.newURI(url, null, null);
236         if (uri.schemeIs("resource"))
237         {
238             var ph = ioService.getProtocolHandler("resource")
239                 .QueryInterface(Ci.nsIResProtocolHandler);
240             var abspath = ph.getSubstitution(uri.host);
241             uri = ioService.newURI(uri.path.substr(1), null, abspath);
242         }
243         while (uri.schemeIs("chrome"))
244         {
245             var chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"]
246                 .getService(Ci.nsIChromeRegistry);
247             uri = chromeRegistry.convertChromeURL(uri);
248         }
249         return uri;
250     }
251     catch(exc)
252     {
253         if (FBTrace.DBG_ERRORS)
254             FBTrace.sysout("getLocalSystemURI failed for "+url);
255     }
256 }
257 
258 /*
259  * Mozilla native path for local URL
260  */
261 Url.getLocalOrSystemPath = function(url, allowDirectories)
262 {
263     var uri = Url.getLocalSystemURI(url);
264     if (uri instanceof Ci.nsIFileURL)
265     {
266         var file = uri.file;
267         if (allowDirectories)
268             return file && file.path;
269         else
270             return file && !file.isDirectory() && file.path;
271     }
272 }
273 
274 Url.getLocalOrSystemFile = function(url)
275 {
276     var uri = Url.getLocalSystemURI(url);
277     if (uri instanceof Ci.nsIFileURL)
278         return uri.file;
279 }
280 
281 Url.getURLFromLocalFile = function(file)
282 {
283     var fileHandler = ioService.getProtocolHandler("file")
284         .QueryInterface(Ci.nsIFileProtocolHandler);
285     var URL = fileHandler.getURLSpecFromFile(file);
286     return URL;
287 };
288 
289 Url.getDataURLForContent = function(content, url)
290 {
291     // data:text/javascript;fileName=x%2Cy.js;baseLineNumber=10,<the-url-encoded-data>
292     var uri = "data:text/html;";
293     uri += "fileName="+encodeURIComponent(url)+ ","
294     uri += encodeURIComponent(content);
295     return uri;
296 },
297 
298 Url.getDomain = function(url)
299 {
300     var m = /[^:]+:\/{1,3}([^\/]+)/.exec(url);
301     return m ? m[1] : "";
302 };
303 
304 Url.getURLPath = function(url)
305 {
306     var m = /[^:]+:\/{1,3}[^\/]+(\/.*?)$/.exec(url);
307     return m ? m[1] : "";
308 };
309 
310 Url.getPrettyDomain = function(url)
311 {
312     var m = /[^:]+:\/{1,3}(www\.)?([^\/]+)/.exec(url);
313     return m ? m[2] : "";
314 };
315 
316 /**
317  * Returns the base URL for a given window
318  * @param {Object} win DOM window
319  * @returns {String} Base URL
320  */
321 Url.getBaseURL = function(win)
322 {
323     if (!win)
324         return;
325 
326     var base = win.document.getElementsByTagName("base").item(0);
327     return base ? base.href : win.location.href;
328 };
329 
330 Url.absoluteURL = function(url, baseURL)
331 {
332     // Replace "/./" with "/" using regular expressions (don't use string since /./
333     // can be treated as regular expressoin too, see 3551).
334     return Url.absoluteURLWithDots(url, baseURL).replace(/\/\.\//, "/", "g");
335 };
336 
337 Url.absoluteURLWithDots = function(url, baseURL)
338 {
339     // Should implement http://www.apps.ietf.org/rfc/rfc3986.html#sec-5
340     // or use the newURI approach described in issue 3110.
341     // See tests/content/lib/absoluteURLs.js
342 
343     if (url.length === 0)
344         return baseURL;
345 
346     var R_query_index = url.indexOf("?");
347     var R_head = url;
348     if (R_query_index !== -1)
349         R_head = url.substr(0, R_query_index);
350 
351     if (url.indexOf(":") !== -1)
352         return url;
353 
354     var reURL = /(([^:]+:)\/{1,2}[^\/]*)(.*?)$/;
355     var m_url = reURL.exec(R_head);
356     if (m_url)
357         return url;
358 
359     var B_query_index = baseURL.indexOf("?");
360     var B_head = baseURL;
361     if (B_query_index !== -1)
362         B_head = baseURL.substr(0, B_query_index);
363 
364     if (url[0] === "?")   // cases where R.path is empty.
365         return B_head + url;
366     if  (url[0] === "#")
367         return baseURL.split("#")[0]+url;
368 
369     var m = reURL.exec(B_head);
370     if (!m)
371         return "";
372 
373     var head = m[1];
374     var tail = m[3];
375     if (url.substr(0, 2) == "//")
376         return m[2] + url;
377     else if (url[0] == "/")
378     {
379         return head + url;
380     }
381     else if (tail[tail.length-1] == "/")
382         return B_head + url;
383     else
384     {
385         var parts = tail.split("/");
386         return head + parts.slice(0, parts.length-1).join("/") + "/" + url;
387     }
388 }
389 
390 var reChromeCase = /chrome:\/\/([^/]*)\/(.*?)$/;
391 Url.normalizeURL = function(url)  // this gets called a lot, any performance improvement welcome
392 {
393     if (!url)
394         return "";
395     // Replace one or more characters that are not forward-slash followed by /.., by space.
396     if (url.length < 255) // guard against monsters.
397     {
398         // Replace one or more characters that are not forward-slash followed by /.., by space.
399         url = url.replace(/[^/]+\/\.\.\//, "", "g");
400         // Issue 1496, avoid #
401         url = url.replace(/#.*/,"");
402         // For some reason, JSDS reports file URLs like "file:/" instead of "file:///", so they
403         // don't match up with the URLs we get back from the DOM
404         url = url.replace(/file:\/([^/])/g, "file:///$1");
405         // For script tags inserted dynamically sometimes the script.fileName is bogus
406         url = url.replace(/[^\s]*\s->\s/, "");
407 
408         if (Str.hasPrefix(url, "chrome:"))
409         {
410             var m = reChromeCase.exec(url);  // 1 is package name, 2 is path
411             if (m)
412             {
413                 url = "chrome://"+m[1].toLowerCase()+"/"+m[2];
414             }
415         }
416     }
417     return url;
418 };
419 
420 Url.denormalizeURL = function(url)
421 {
422     return url.replace(/file:\/\/\//g, "file:/");
423 };
424 
425 // ********************************************************************************************* //
426 
427 Url.parseURLParams = function(url)
428 {
429     var q = url ? url.indexOf("?") : -1;
430     if (q == -1)
431         return [];
432 
433     var search = url.substr(q+1);
434     var h = search.lastIndexOf("#");
435     if (h != -1)
436         search = search.substr(0, h);
437 
438     if (!search)
439         return [];
440 
441     return Url.parseURLEncodedText(search);
442 };
443 
444 Url.parseURLEncodedText = function(text, noLimit)
445 {
446     const maxValueLength = 25000;
447 
448     var params = [];
449 
450     // In case the text is empty just return the empty parameters
451     if (text == "")
452         return params;
453 
454     // Unescape '+' characters that are used to encode a space.
455     // See section 2.2.in RFC 3986: http://www.ietf.org/rfc/rfc3986.txt
456     text = text.replace(/\+/g, " ");
457 
458     // Unescape '&' character
459     text = Str.unescapeForURL(text);
460 
461     function decodeText(text)
462     {
463         try
464         {
465             return decodeURIComponent(text);
466         }
467         catch (e)
468         {
469             return decodeURIComponent(unescape(text));
470         }
471     }
472 
473     var args = text.split("&");
474     for (var i = 0; i < args.length; ++i)
475     {
476         try
477         {
478             var index = args[i].indexOf("=");
479             if (index != -1)
480             {
481                 var paramName = args[i].substring(0, index);
482                 var paramValue = args[i].substring(index + 1);
483 
484                 if (paramValue.length > maxValueLength && !noLimit)
485                     paramValue = Locale.$STR("LargeData");
486 
487                 params.push({name: decodeText(paramName), value: decodeText(paramValue)});
488             }
489             else
490             {
491                 var paramName = args[i];
492                 params.push({name: decodeText(paramName), value: ""});
493             }
494         }
495         catch (e)
496         {
497             if (FBTrace.DBG_ERRORS)
498             {
499                 FBTrace.sysout("parseURLEncodedText EXCEPTION ", e);
500                 FBTrace.sysout("parseURLEncodedText EXCEPTION URI", args[i]);
501             }
502         }
503     }
504 
505     params.sort(function(a, b) { return a.name <= b.name ? -1 : 1; });
506 
507     return params;
508 };
509 
510 Url.reEncodeURL = function(file, text, noLimit)
511 {
512     var lines = text.split("\n");
513     var params = Url.parseURLEncodedText(lines[lines.length-1], noLimit);
514 
515     var args = [];
516     for (var i = 0; i < params.length; ++i)
517         args.push(encodeURIComponent(params[i].name)+"="+encodeURIComponent(params[i].value));
518 
519     var url = file.href;
520     url += (url.indexOf("?") == -1 ? "?" : "&") + args.join("&");
521 
522     return url;
523 };
524 
525 /**
526  * Extracts the URL from a CSS URL definition.
527  * Example: url(../path/to/file) => ../path/to/file
528  * @param {String} url CSS URL definition
529  * @returns {String} Extracted URL
530  */
531 Url.extractFromCSS = function(url)
532 {
533     return url.replace(/^url\(["']?(.*?)["']?\)$/, "$1");
534 };
535 
536 Url.makeURI = function(urlString)
537 {
538     try
539     {
540         if (urlString)
541             return ioService.newURI(urlString, null, null);
542     }
543     catch (exc)
544     {
545         //var explain = {message: "Firebug.lib.makeURI FAILS", url: urlString, exception: exc};
546         // todo convert explain to json and then to data url
547         if (FBTrace.DBG_ERRORS)
548             FBTrace.sysout("makeURI FAILS for \""+urlString+"\" ", exc);
549 
550         return false;
551     }
552 }
553 
554 /**
555  * Converts resource: to file: Url.
556  * @param {String} resourceURL
557  */
558 Url.resourceToFile = function(resourceURL)
559 {
560     var resHandler = ioService.getProtocolHandler("resource")
561         .QueryInterface(Ci.nsIResProtocolHandler);
562 
563     var justURL = resourceURL.split("resource://")[1];
564     var split = justURL.split("/");
565     var sub = split.shift();
566 
567     var path = resHandler.getSubstitution(sub).spec;
568     return path + split.join("/");
569 }
570 
571 // ********************************************************************************************* //
572 // Registration
573 
574 return Url;
575 
576 // ********************************************************************************************* //
577 });
578