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.splitURL = function(url)
     94 {
     95     var parsedURL = new WebInspector.ParsedURL(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 /**
    125  * http://tools.ietf.org/html/rfc3986#section-5.2.4
    126  * @param {string} path
    127  * @return {string}
    128  */
    129 
    130 WebInspector.ParsedURL.normalizePath = function(path)
    131 {
    132     if (path.indexOf("..") === -1 && path.indexOf('.') === -1)
    133         return path;
    134 
    135     var normalizedSegments = [];
    136     var segments = path.split("/");
    137     for (var i = 0; i < segments.length; i++) {
    138         var segment = segments[i];
    139         if (segment === ".")
    140             continue;
    141         else if (segment === "..")
    142             normalizedSegments.pop();
    143         else if (segment)
    144             normalizedSegments.push(segment);
    145     }
    146     var normalizedPath = normalizedSegments.join("/");
    147     if (normalizedPath[normalizedPath.length - 1] === "/")
    148         return normalizedPath;
    149     if (path[0] === "/" && normalizedPath)
    150         normalizedPath = "/" + normalizedPath;
    151     if ((path[path.length - 1] === "/") || (segments[segments.length - 1] === ".") || (segments[segments.length - 1] === ".."))
    152         normalizedPath = normalizedPath + "/";
    153 
    154     return normalizedPath;
    155 }
    156 
    157 /**
    158  * @param {string} baseURL
    159  * @param {string} href
    160  * @return {?string}
    161  */
    162 WebInspector.ParsedURL.completeURL = function(baseURL, href)
    163 {
    164     if (href) {
    165         // Return special URLs as-is.
    166         var trimmedHref = href.trim();
    167         if (trimmedHref.startsWith("data:") || trimmedHref.startsWith("blob:") || trimmedHref.startsWith("javascript:"))
    168             return href;
    169 
    170         // Return absolute URLs as-is.
    171         var parsedHref = trimmedHref.asParsedURL();
    172         if (parsedHref && parsedHref.scheme)
    173             return trimmedHref;
    174     } else {
    175         return baseURL;
    176     }
    177 
    178     var parsedURL = baseURL.asParsedURL();
    179     if (parsedURL) {
    180         if (parsedURL.isDataURL())
    181             return href;
    182         var path = href;
    183 
    184         var query = path.indexOf("?");
    185         var postfix = "";
    186         if (query !== -1) {
    187             postfix = path.substring(query);
    188             path = path.substring(0, query);
    189         } else {
    190             var fragment = path.indexOf("#");
    191             if (fragment !== -1) {
    192                 postfix = path.substring(fragment);
    193                 path = path.substring(0, fragment);
    194             }
    195         }
    196 
    197         if (!path) {  // empty path, must be postfix
    198             var basePath = parsedURL.path;
    199             if (postfix.charAt(0) === "?") {
    200                 // A href of "?foo=bar" implies "basePath?foo=bar".
    201                 // With "basePath?a=b" and "?foo=bar" we should get "basePath?foo=bar".
    202                 var baseQuery = parsedURL.path.indexOf("?");
    203                 if (baseQuery !== -1)
    204                     basePath = basePath.substring(0, baseQuery);
    205             } // else it must be a fragment
    206             return parsedURL.scheme + "://" + parsedURL.host + (parsedURL.port ? (":" + parsedURL.port) : "") + basePath + postfix;
    207         } else if (path.charAt(0) !== "/") {  // relative path
    208             var prefix = parsedURL.path;
    209             var prefixQuery = prefix.indexOf("?");
    210             if (prefixQuery !== -1)
    211                 prefix = prefix.substring(0, prefixQuery);
    212             prefix = prefix.substring(0, prefix.lastIndexOf("/")) + "/";
    213             path = prefix + path;
    214         } else if (path.length > 1 && path.charAt(1) === "/") {
    215             // href starts with "//" which is a full URL with the protocol dropped (use the baseURL protocol).
    216             return parsedURL.scheme + ":" + path + postfix;
    217         }  // else absolute path
    218         return parsedURL.scheme + "://" + parsedURL.host + (parsedURL.port ? (":" + parsedURL.port) : "") + WebInspector.ParsedURL.normalizePath(path) + postfix;
    219     }
    220     return null;
    221 }
    222 
    223 WebInspector.ParsedURL.prototype = {
    224     get displayName()
    225     {
    226         if (this._displayName)
    227             return this._displayName;
    228 
    229         if (this.isDataURL())
    230             return this.dataURLDisplayName();
    231         if (this.isAboutBlank())
    232             return this.url;
    233 
    234         this._displayName = this.lastPathComponent;
    235         if (!this._displayName)
    236             this._displayName = (this.host || "") + "/";
    237         if (this._displayName === "/")
    238             this._displayName = this.url;
    239         return this._displayName;
    240     },
    241 
    242     /**
    243      * @return {string}
    244      */
    245     dataURLDisplayName: function()
    246     {
    247         if (this._dataURLDisplayName)
    248             return this._dataURLDisplayName;
    249         if (!this.isDataURL())
    250             return "";
    251         this._dataURLDisplayName = this.url.trimEnd(20);
    252         return this._dataURLDisplayName;
    253     },
    254 
    255     /**
    256      * @return {boolean}
    257      */
    258     isAboutBlank: function()
    259     {
    260         return this.url === "about:blank";
    261     },
    262 
    263     /**
    264      * @return {boolean}
    265      */
    266     isDataURL: function()
    267     {
    268         return this.scheme === "data";
    269     }
    270 }
    271 
    272 /**
    273  * @return {?WebInspector.ParsedURL}
    274  */
    275 String.prototype.asParsedURL = function()
    276 {
    277     var parsedURL = new WebInspector.ParsedURL(this.toString());
    278     if (parsedURL.isValid)
    279         return parsedURL;
    280     return null;
    281 }
    282