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 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 130WebInspector.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 */ 162WebInspector.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 223WebInspector.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 */ 275String.prototype.asParsedURL = function() 276{ 277 var parsedURL = new WebInspector.ParsedURL(this.toString()); 278 if (parsedURL.isValid) 279 return parsedURL; 280 return null; 281} 282