Home | History | Annotate | Download | only in common
      1 /*
      2  * Copyright (C) 2012 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  * 1. Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *
     11  * 2. Redistributions in binary form must reproduce the above
     12  * copyright notice, this list of conditions and the following disclaimer
     13  * in the documentation and/or other materials provided with the
     14  * distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS
     17  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     18  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     19  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC.
     20  * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     21  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     22  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 /**
     30  * @constructor
     31  * @param {string} url
     32  */
     33 WebInspector.ParsedURL = function(url)
     34 {
     35     this.isValid = false;
     36     this.url = url;
     37     this.scheme = "";
     38     this.host = "";
     39     this.port = "";
     40     this.path = "";
     41     this.queryParams = "";
     42     this.fragment = "";
     43     this.folderPathComponents = "";
     44     this.lastPathComponent = "";
     45 
     46     // RegExp groups:
     47     // 1 - scheme (using the RFC3986 grammar)
     48     // 2 - hostname
     49     // 3 - ?port
     50     // 4 - ?path
     51     // 5 - ?fragment
     52     var match = url.match(/^([A-Za-z][A-Za-z0-9+.-]*):\/\/([^\/:]*)(?::([\d]+))?(?:(\/[^#]*)(?:#(.*))?)?$/i);
     53     if (match) {
     54         this.isValid = true;
     55         this.scheme = match[1].toLowerCase();
     56         this.host = match[2];
     57         this.port = match[3];
     58         this.path = match[4] || "/";
     59         this.fragment = match[5];
     60     } else {
     61         if (this.url.startsWith("data:")) {
     62             this.scheme = "data";
     63             return;
     64         }
     65         if (this.url === "about:blank") {
     66             this.scheme = "about";
     67             return;
     68         }
     69         this.path = this.url;
     70     }
     71 
     72     // First cut the query params.
     73     var path = this.path;
     74     var indexOfQuery = path.indexOf("?");
     75     if (indexOfQuery !== -1) {
     76         this.queryParams = path.substring(indexOfQuery + 1)
     77         path = path.substring(0, indexOfQuery);
     78     }
     79 
     80     // Then take last path component.
     81     var lastSlashIndex = path.lastIndexOf("/");
     82     if (lastSlashIndex !== -1) {
     83         this.folderPathComponents = path.substring(0, lastSlashIndex);
     84         this.lastPathComponent = path.substring(lastSlashIndex + 1);
     85     } else
     86         this.lastPathComponent = path;
     87 }
     88 
     89 /**
     90  * @param {string} url
     91  * @return {!Array.<string>}
     92  */
     93 WebInspector.ParsedURL.splitURLIntoPathComponents = function(url)
     94 {
     95     var parsedURL = new WebInspector.ParsedURL(decodeURI(url));
     96     var origin;
     97     var folderPath;
     98     var name;
     99     if (parsedURL.isValid) {
    100         origin = parsedURL.scheme + "://" + parsedURL.host;
    101         if (parsedURL.port)
    102             origin += ":" + parsedURL.port;
    103         folderPath = parsedURL.folderPathComponents;
    104         name = parsedURL.lastPathComponent;
    105         if (parsedURL.queryParams)
    106             name += "?" + parsedURL.queryParams;
    107     } else {
    108         origin = "";
    109         folderPath = "";
    110         name = url;
    111     }
    112     var result = [origin];
    113     var splittedPath = folderPath.split("/");
    114     for (var i = 1; i < splittedPath.length; ++i) {
    115         if (!splittedPath[i])
    116             continue;
    117         result.push(splittedPath[i]);
    118     }
    119     result.push(name);
    120     return result;
    121 }
    122 
    123 /**
    124  * @param {string} baseURL
    125  * @param {string} href
    126  * @return {?string}
    127  */
    128 WebInspector.ParsedURL.completeURL = function(baseURL, href)
    129 {
    130     if (href) {
    131         // Return special URLs as-is.
    132         var trimmedHref = href.trim();
    133         if (trimmedHref.startsWith("data:") || trimmedHref.startsWith("blob:") || trimmedHref.startsWith("javascript:"))
    134             return href;
    135 
    136         // Return absolute URLs as-is.
    137         var parsedHref = trimmedHref.asParsedURL();
    138         if (parsedHref && parsedHref.scheme)
    139             return trimmedHref;
    140     } else {
    141         return baseURL;
    142     }
    143 
    144     var parsedURL = baseURL.asParsedURL();
    145     if (parsedURL) {
    146         if (parsedURL.isDataURL())
    147             return href;
    148         var path = href;
    149 
    150         var query = path.indexOf("?");
    151         var postfix = "";
    152         if (query !== -1) {
    153             postfix = path.substring(query);
    154             path = path.substring(0, query);
    155         } else {
    156             var fragment = path.indexOf("#");
    157             if (fragment !== -1) {
    158                 postfix = path.substring(fragment);
    159                 path = path.substring(0, fragment);
    160             }
    161         }
    162 
    163         if (!path) {  // empty path, must be postfix
    164             var basePath = parsedURL.path;
    165             if (postfix.charAt(0) === "?") {
    166                 // A href of "?foo=bar" implies "basePath?foo=bar".
    167                 // With "basePath?a=b" and "?foo=bar" we should get "basePath?foo=bar".
    168                 var baseQuery = parsedURL.path.indexOf("?");
    169                 if (baseQuery !== -1)
    170                     basePath = basePath.substring(0, baseQuery);
    171             } // else it must be a fragment
    172             return parsedURL.scheme + "://" + parsedURL.host + (parsedURL.port ? (":" + parsedURL.port) : "") + basePath + postfix;
    173         } else if (path.charAt(0) !== "/") {  // relative path
    174             var prefix = parsedURL.path;
    175             var prefixQuery = prefix.indexOf("?");
    176             if (prefixQuery !== -1)
    177                 prefix = prefix.substring(0, prefixQuery);
    178             prefix = prefix.substring(0, prefix.lastIndexOf("/")) + "/";
    179             path = prefix + path;
    180         } else if (path.length > 1 && path.charAt(1) === "/") {
    181             // href starts with "//" which is a full URL with the protocol dropped (use the baseURL protocol).
    182             return parsedURL.scheme + ":" + path + postfix;
    183         }  // else absolute path
    184         return parsedURL.scheme + "://" + parsedURL.host + (parsedURL.port ? (":" + parsedURL.port) : "") + normalizePath(path) + postfix;
    185     }
    186     return null;
    187 }
    188 
    189 WebInspector.ParsedURL.prototype = {
    190     get displayName()
    191     {
    192         if (this._displayName)
    193             return this._displayName;
    194 
    195         if (this.isDataURL())
    196             return this.dataURLDisplayName();
    197         if (this.isAboutBlank())
    198             return this.url;
    199 
    200         this._displayName = this.lastPathComponent;
    201         if (!this._displayName)
    202             this._displayName = (this.host || "") + "/";
    203         if (this._displayName === "/")
    204             this._displayName = this.url;
    205         return this._displayName;
    206     },
    207 
    208     /**
    209      * @return {string}
    210      */
    211     dataURLDisplayName: function()
    212     {
    213         if (this._dataURLDisplayName)
    214             return this._dataURLDisplayName;
    215         if (!this.isDataURL())
    216             return "";
    217         this._dataURLDisplayName = this.url.trimEnd(20);
    218         return this._dataURLDisplayName;
    219     },
    220 
    221     /**
    222      * @return {boolean}
    223      */
    224     isAboutBlank: function()
    225     {
    226         return this.url === "about:blank";
    227     },
    228 
    229     /**
    230      * @return {boolean}
    231      */
    232     isDataURL: function()
    233     {
    234         return this.scheme === "data";
    235     }
    236 }
    237 
    238 /**
    239  * @return {?WebInspector.ParsedURL}
    240  */
    241 String.prototype.asParsedURL = function()
    242 {
    243     var parsedURL = new WebInspector.ParsedURL(this.toString());
    244     if (parsedURL.isValid)
    245         return parsedURL;
    246     return null;
    247 }
    248