/*
 * 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)
