• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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