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 */ 33WebInspector.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 */ 93WebInspector.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 */ 128WebInspector.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 189WebInspector.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 */ 241String.prototype.asParsedURL = function() 242{ 243 var parsedURL = new WebInspector.ParsedURL(this.toString()); 244 if (parsedURL.isValid) 245 return parsedURL; 246 return null; 247} 248