blob: 7ef05006430afc2ba2733c674977bf6b4a6edcce [file] [log] [blame]
/*
* Copyright 2007 Google Inc. All rights reserved.
* Copyright 2012 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// Canonicalizer functions for working with and resolving relative URLs.
#include "config.h"
#include "URLCanon.h"
#include "URLCanonInternal.h"
#include "URLFile.h"
#include "URLParseInternal.h"
#include "URLUtilInternal.h"
#if USE(WTFURL)
namespace WTF {
namespace URLCanonicalizer {
namespace {
// Firefox does a case-sensitive compare (which is probably wrong--Mozilla bug
// 379034), whereas IE is case-insensetive.
//
// We choose to be more permissive like IE. We don't need to worry about
// unescaping or anything here: neither IE or Firefox allow this. We also
// don't have to worry about invalid scheme characters since we are comparing
// against the canonical scheme of the base.
//
// The base URL should always be canonical, therefore is ASCII.
template<typename CHAR>
bool AreSchemesEqual(const char* base,
const URLComponent& baseScheme,
const CHAR* cmp,
const URLComponent& cmpScheme)
{
if (baseScheme.length() != cmpScheme.length())
return false;
for (int i = 0; i < baseScheme.length(); i++) {
// We assume the base is already canonical, so we don't have to
// canonicalize it.
if (canonicalSchemeChar(cmp[cmpScheme.begin() + i]) !=
base[baseScheme.begin() + i])
return false;
}
return true;
}
#if OS(WINDOWS)
// Here, we also allow Windows paths to be represented as "/C:/" so we can be
// consistent about URL paths beginning with slashes. This function is like
// DoesBeginWindowsDrivePath except that it also requires a slash at the
// beginning.
template<typename CHAR>
bool doesBeginSlashWindowsDriveSpec(const CHAR* spec, int startOffset, int specLength)
{
if (startOffset >= specLength)
return false;
return URLParser::isURLSlash(spec[startOffset]) && URLParser::doesBeginWindowsDriveSpec(spec, startOffset + 1, specLength);
}
#endif // OS(WINDOWS)
// See isRelativeURL in the header file for usage.
template<typename CharacterType>
bool doIsRelativeURL(const char* base, const URLSegments& baseParsed,
const CharacterType* url, int urlLength,
bool isBaseHierarchical,
bool& isRelative, URLComponent& relativeComponent)
{
isRelative = false; // So we can default later to not relative.
// Trim whitespace and construct a new range for the substring.
int begin = 0;
URLParser::trimURL(url, begin, urlLength);
if (begin >= urlLength) {
// Empty URLs are relative, but do nothing.
relativeComponent = URLComponent(begin, 0);
isRelative = true;
return true;
}
#if OS(WINDOWS)
// We special case paths like "C:\foo" so they can link directly to the
// file on Windows (IE compatability). The security domain stuff should
// prevent a link like this from actually being followed if its on a
// web page.
//
// We treat "C:/foo" as an absolute URL. We can go ahead and treat "/c:/"
// as relative, as this will just replace the path when the base scheme
// is a file and the answer will still be correct.
//
// We require strict backslashes when detecting UNC since two forward
// shashes should be treated a a relative URL with a hostname.
if (URLParser::doesBeginWindowsDriveSpec(url, begin, urlLength) || URLParser::doesBeginUNCPath(url, begin, urlLength, true))
return true;
#endif // OS(WINDOWS)
// See if we've got a scheme, if not, we know this is a relative URL.
// BUT: Just because we have a scheme, doesn't make it absolute.
// "http:foo.html" is a relative URL with path "foo.html". If the scheme is
// empty, we treat it as relative (":foo") like IE does.
URLComponent scheme;
if (!URLParser::ExtractScheme(url, urlLength, &scheme) || !scheme.length()) {
// Don't allow relative URLs if the base scheme doesn't support it.
if (!isBaseHierarchical)
return false;
relativeComponent = URLComponent::fromRange(begin, urlLength);
isRelative = true;
return true;
}
// If the scheme isn't valid, then it's relative.
int schemeEnd = scheme.end();
for (int i = scheme.begin(); i < schemeEnd; i++) {
if (!canonicalSchemeChar(url[i])) {
relativeComponent = URLComponent::fromRange(begin, urlLength);
isRelative = true;
return true;
}
}
// If the scheme is not the same, then we can't count it as relative.
if (!AreSchemesEqual(base, baseParsed.scheme, url, scheme))
return true;
// When the scheme that they both share is not hierarchical, treat the
// incoming scheme as absolute (this way with the base of "data:foo",
// "data:bar" will be reported as absolute.
if (!isBaseHierarchical)
return true;
int colonOffset = scheme.end();
// If it's a filesystem URL, the only valid way to make it relative is not to
// supply a scheme. There's no equivalent to e.g. http:index.html.
if (URLUtilities::CompareSchemeComponent(url, scheme, "filesystem"))
return true;
// ExtractScheme guarantees that the colon immediately follows what it
// considers to be the scheme. countConsecutiveSlashes will handle the
// case where the begin offset is the end of the input.
int numSlashes = URLParser::countConsecutiveSlashes(url, colonOffset + 1, urlLength);
if (!numSlashes || numSlashes == 1) {
// No slashes means it's a relative path like "http:foo.html". One slash
// is an absolute path. "http:/home/foo.html"
isRelative = true;
relativeComponent = URLComponent::fromRange(colonOffset + 1, urlLength);
return true;
}
// Two or more slashes after the scheme we treat as absolute.
return true;
}
// Copies all characters in the range [begin, end) of |spec| to the output,
// up until and including the last slash. There should be a slash in the
// range, if not, nothing will be copied.
//
// The input is assumed to be canonical, so we search only for exact slashes
// and not backslashes as well. We also know that it's ASCII.
void CopyToLastSlash(const char* spec, int begin, int end, URLBuffer<char>& output)
{
// Find the last slash.
int lastSlash = -1;
for (int i = end - 1; i >= begin; --i) {
if (spec[i] == '/') {
lastSlash = i;
break;
}
}
if (lastSlash < 0)
return; // No slash.
// Copy.
for (int i = begin; i <= lastSlash; ++i)
output.append(spec[i]);
}
// Copies a single component from the source to the output. This is used
// when resolving relative URLs and a given component is unchanged. Since the
// source should already be canonical, we don't have to do anything special,
// and the input is ASCII.
void CopyOneComponent(const char* source,
const URLComponent& sourceComponent,
URLBuffer<char>& output,
URLComponent* outputComponent)
{
if (sourceComponent.length() < 0) {
// This component is not present.
*outputComponent = URLComponent();
return;
}
outputComponent->setBegin(output.length());
int sourceEnd = sourceComponent.end();
for (int i = sourceComponent.begin(); i < sourceEnd; i++)
output.append(source[i]);
outputComponent->setLength(output.length() - outputComponent->begin());
}
#if OS(WINDOWS)
// Called on Windows when the base URL is a file URL, this will copy the "C:"
// to the output, if there is a drive letter and if that drive letter is not
// being overridden by the relative URL. Otherwise, do nothing.
//
// It will return the index of the beginning of the next character in the
// base to be processed: if there is a "C:", the slash after it, or if
// there is no drive letter, the slash at the beginning of the path, or
// the end of the base. This can be used as the starting offset for further
// path processing.
template<typename CHAR>
int CopyBaseDriveSpecIfNecessary(const char* baseURL,
int basePathBegin,
int basePathEnd,
const CHAR* relativeURL,
int pathStart,
int relativeUrlLength,
URLBuffer<char>& output)
{
if (basePathBegin >= basePathEnd)
return basePathBegin; // No path.
// If the relative begins with a drive spec, don't do anything. The existing
// drive spec in the base will be replaced.
if (URLParser::doesBeginWindowsDriveSpec(relativeURL,
pathStart, relativeUrlLength)) {
return basePathBegin; // Relative URL path is "C:/foo"
}
// The path should begin with a slash (as all canonical paths do). We check
// if it is followed by a drive letter and copy it.
if (doesBeginSlashWindowsDriveSpec(baseURL, basePathBegin, basePathEnd)) {
// Copy the two-character drive spec to the output. It will now look like
// "file:///C:" so the rest of it can be treated like a standard path.
output.append('/');
output.append(baseURL[basePathBegin + 1]);
output.append(baseURL[basePathBegin + 2]);
return basePathBegin + 3;
}
return basePathBegin;
}
#endif // OS(WINDOWS)
// A subroutine of doResolveRelativeURL, this resolves the URL knowning that
// the input is a relative path or less (qyuery or ref).
template<typename CHAR>
bool doResolveRelativePath(const char* baseURL,
const URLSegments& baseParsed,
bool /* baseIsFile */,
const CHAR* relativeURL,
const URLComponent& relativeComponent,
URLQueryCharsetConverter* queryConverter,
URLBuffer<char>& output,
URLSegments* outputParsed)
{
bool success = true;
// We know the authority section didn't change, copy it to the output. We
// also know we have a path so can copy up to there.
URLComponent path, query, ref;
URLParser::parsePathInternal(relativeURL,
relativeComponent,
&path,
&query,
&ref);
// Canonical URLs always have a path, so we can use that offset.
output.append(baseURL, baseParsed.path.begin());
if (path.length() > 0) {
// The path is replaced or modified.
int truePathBegin = output.length();
// For file: URLs on Windows, we don't want to treat the drive letter and
// colon as part of the path for relative file resolution when the
// incoming URL does not provide a drive spec. We save the true path
// beginning so we can fix it up after we are done.
int basePathBegin = baseParsed.path.begin();
#if OS(WINDOWS)
if (baseIsFile) {
basePathBegin = CopyBaseDriveSpecIfNecessary(baseURL, baseParsed.path.begin(), baseParsed.path.end(),
relativeURL, relativeComponent.begin(), relativeComponent.end(),
output);
// Now the output looks like either "file://" or "file:///C:"
// and we can start appending the rest of the path. |basePathBegin|
// points to the character in the base that comes next.
}
#endif // OS(WINDOWS)
if (URLParser::isURLSlash(relativeURL[path.begin()])) {
// Easy case: the path is an absolute path on the server, so we can
// just replace everything from the path on with the new versions.
// Since the input should be canonical hierarchical URL, we should
// always have a path.
success &= CanonicalizePath(relativeURL, path,
output, &outputParsed->path);
} else {
// Relative path, replace the query, and reference. We take the
// original path with the file part stripped, and append the new path.
// The canonicalizer will take care of resolving ".." and "."
int pathBegin = output.length();
CopyToLastSlash(baseURL, basePathBegin, baseParsed.path.end(),
output);
success &= CanonicalizePartialPath(relativeURL, path, pathBegin,
output);
outputParsed->path = URLComponent::fromRange(pathBegin, output.length());
// Copy the rest of the stuff after the path from the relative path.
}
// Finish with the query and reference part (these can't fail).
CanonicalizeQuery(relativeURL, query, queryConverter, output, &outputParsed->query);
canonicalizeFragment(relativeURL, ref, output, outputParsed->fragment);
// Fix the path beginning to add back the "C:" we may have written above.
outputParsed->path = URLComponent::fromRange(truePathBegin,
outputParsed->path.end());
return success;
}
// If we get here, the path is unchanged: copy to output.
CopyOneComponent(baseURL, baseParsed.path, output, &outputParsed->path);
if (query.isValid()) {
// Just the query specified, replace the query and reference (ignore
// failures for refs)
CanonicalizeQuery(relativeURL, query, queryConverter,
output, &outputParsed->query);
canonicalizeFragment(relativeURL, ref, output, outputParsed->fragment);
return success;
}
// If we get here, the query is unchanged: copy to output. Note that the
// range of the query parameter doesn't include the question mark, so we
// have to add it manually if there is a component.
if (baseParsed.query.isValid())
output.append('?');
CopyOneComponent(baseURL, baseParsed.query, output, &outputParsed->query);
if (ref.isValid()) {
// Just the reference specified: replace it (ignoring failures).
canonicalizeFragment(relativeURL, ref, output, outputParsed->fragment);
return success;
}
// We should always have something to do in this function, the caller checks
// that some component is being replaced.
ASSERT_NOT_REACHED();
return success;
}
// Resolves a relative URL that contains a host. Typically, these will
// be of the form "//www.apple.com/foo/bar?baz#fragment" and the only thing which
// should be kept from the original URL is the scheme.
template<typename CHAR>
bool doResolveRelativeHost(const char* baseURL,
const URLSegments& baseParsed,
const CHAR* relativeURL,
const URLComponent& relativeComponent,
URLQueryCharsetConverter* queryConverter,
URLBuffer<char>& output,
URLSegments* outputParsed)
{
// Parse the relative URL, just like we would for anything following a
// scheme.
URLSegments relativeParsed; // Everything but the scheme is valid.
URLParser::parseAfterScheme(&relativeURL[relativeComponent.begin()],
relativeComponent.length(), relativeComponent.begin(),
relativeParsed);
// Now we can just use the replacement function to replace all the necessary
// parts of the old URL with the new one.
Replacements<CHAR> replacements;
replacements.SetUsername(relativeURL, relativeParsed.username);
replacements.SetPassword(relativeURL, relativeParsed.password);
replacements.SetHost(relativeURL, relativeParsed.host);
replacements.SetPort(relativeURL, relativeParsed.port);
replacements.SetPath(relativeURL, relativeParsed.path);
replacements.SetQuery(relativeURL, relativeParsed.query);
replacements.SetRef(relativeURL, relativeParsed.fragment);
return ReplaceStandardURL(baseURL, baseParsed, replacements,
queryConverter, output, outputParsed);
}
// Resolves a relative URL that happens to be an absolute file path. Examples
// include: "//hostname/path", "/c:/foo", and "//hostname/c:/foo".
template<typename CharacterType>
bool doResolveAbsoluteFile(const CharacterType* relativeURL,
const URLComponent& relativeComponent,
URLQueryCharsetConverter* queryConverter,
URLBuffer<char>& output,
URLSegments& outputParsed)
{
// Parse the file URL. The file URl parsing function uses the same logic
// as we do for determining if the file is absolute, in which case it will
// not bother to look for a scheme.
URLSegments relativeParsed;
URLParser::ParseFileURL(&relativeURL[relativeComponent.begin()],
relativeComponent.length(), &relativeParsed);
return CanonicalizeFileURL(&relativeURL[relativeComponent.begin()],
relativeComponent.length(), relativeParsed,
queryConverter, output, &outputParsed);
}
// TODO(brettw) treat two slashes as root like Mozilla for FTP?
template<typename CHAR>
bool doResolveRelativeURL(const char* baseURL,
const URLSegments& baseParsed,
bool baseIsFile,
const CHAR* relativeURL,
const URLComponent& relativeComponent,
URLQueryCharsetConverter* queryConverter,
URLBuffer<char>& output,
URLSegments* outputParsed)
{
// Starting point for our output parsed. We'll fix what we change.
*outputParsed = baseParsed;
// Sanity check: the input should have a host or we'll break badly below.
// We can only resolve relative URLs with base URLs that have hosts and
// paths (even the default path of "/" is OK).
//
// We allow hosts with no length so we can handle file URLs, for example.
if (baseParsed.path.length() <= 0) {
// On error, return the input (resolving a relative URL on a non-relative
// base = the base).
int baseLength = baseParsed.length();
for (int i = 0; i < baseLength; i++)
output.append(baseURL[i]);
return false;
}
if (relativeComponent.length() <= 0) {
// Empty relative URL, leave unchanged, only removing the ref component.
int baseLength = baseParsed.length();
baseLength -= baseParsed.fragment.length() + 1;
outputParsed->fragment.reset();
output.append(baseURL, baseLength);
return true;
}
int numSlashes = URLParser::countConsecutiveSlashes(relativeURL, relativeComponent.begin(), relativeComponent.end());
#if OS(WINDOWS)
// On Windows, two slashes for a file path (regardless of which direction
// they are) means that it's UNC. Two backslashes on any base scheme mean
// that it's an absolute UNC path (we use the baseIsFile flag to control
// how strict the UNC finder is).
//
// We also allow Windows absolute drive specs on any scheme (for example
// "c:\foo") like IE does. There must be no preceeding slashes in this
// case (we reject anything like "/c:/foo") because that should be treated
// as a path. For file URLs, we allow any number of slashes since that would
// be setting the path.
//
// This assumes the absolute path resolver handles absolute URLs like this
// properly. URLUtilities::DoCanonicalize does this.
int afterSlashes = relativeComponent.begin + numSlashes;
if (URLParser::doesBeginUNCPath(relativeURL, relativeComponent.begin(), relativeComponent.end(), !baseIsFile)
|| ((!numSlashes || baseIsFile) && URLParser::doesBeginWindowsDriveSpec(relativeURL, afterSlashes, relativeComponent.end()))) {
return doResolveAbsoluteFile(relativeURL, relativeComponent,
queryConverter, output, *outputParsed);
}
#else
// Other platforms need explicit handling for file: URLs with multiple
// slashes because the generic scheme parsing always extracts a host, but a
// file: URL only has a host if it has exactly 2 slashes. This also
// handles the special case where the URL is only slashes, since that
// doesn't have a host part either.
if (baseIsFile && (numSlashes > 2 || numSlashes == relativeComponent.length())) {
return doResolveAbsoluteFile(relativeURL, relativeComponent,
queryConverter, output, *outputParsed);
}
#endif
// Any other double-slashes mean that this is relative to the scheme.
if (numSlashes >= 2) {
return doResolveRelativeHost(baseURL, baseParsed,
relativeURL, relativeComponent,
queryConverter, output, outputParsed);
}
// When we get here, we know that the relative URL is on the same host.
return doResolveRelativePath(baseURL, baseParsed, baseIsFile,
relativeURL, relativeComponent,
queryConverter, output, outputParsed);
}
} // namespace
bool isRelativeURL(const char* base, const URLSegments& baseParsed,
const char* fragment, int fragmentLength,
bool isBaseHierarchical,
bool& isRelative, URLComponent& relativeComponent)
{
return doIsRelativeURL<char>(base, baseParsed, fragment, fragmentLength, isBaseHierarchical, isRelative, relativeComponent);
}
bool isRelativeURL(const char* base, const URLSegments& baseParsed,
const UChar* fragment, int fragmentLength,
bool isBaseHierarchical,
bool& isRelative, URLComponent& relativeComponent)
{
return doIsRelativeURL<UChar>(base, baseParsed, fragment, fragmentLength, isBaseHierarchical, isRelative, relativeComponent);
}
bool resolveRelativeURL(const char* baseURL,
const URLSegments& baseParsed,
bool baseIsFile,
const char* relativeURL,
const URLComponent& relativeComponent,
URLQueryCharsetConverter* queryConverter,
URLBuffer<char>& output,
URLSegments* outputParsed)
{
return doResolveRelativeURL<char>(baseURL, baseParsed, baseIsFile, relativeURL,
relativeComponent, queryConverter, output, outputParsed);
}
bool resolveRelativeURL(const char* baseURL,
const URLSegments& baseParsed,
bool baseIsFile,
const UChar* relativeURL,
const URLComponent& relativeComponent,
URLQueryCharsetConverter* queryConverter,
URLBuffer<char>& output,
URLSegments* outputParsed)
{
return doResolveRelativeURL<UChar>(baseURL, baseParsed, baseIsFile, relativeURL,
relativeComponent, queryConverter, output, outputParsed);
}
} // namespace URLCanonicalizer
} // namespace WTF
#endif // USE(WTFURL)