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