/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
 
#include <sal/config.h>
 
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <string_view>
#include <utility>
#include <vector>
 
#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
#include <com/sun/star/lang/XMultiComponentFactory.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/uno/Any.hxx>
#include <com/sun/star/uno/Exception.hpp>
#include <com/sun/star/uno/Reference.hxx>
#include <com/sun/star/uno/RuntimeException.hpp>
#include <com/sun/star/uno/Sequence.hxx>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <com/sun/star/uno/XInterface.hpp>
#include <com/sun/star/uri/RelativeUriExcessParentSegments.hpp>
#include <com/sun/star/uri/XUriReference.hpp>
#include <com/sun/star/uri/XUriReferenceFactory.hpp>
#include <com/sun/star/uri/XUriSchemeParser.hpp>
#include <cppuhelper/exc_hlp.hxx>
#include <cppuhelper/implbase.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <cppuhelper/weak.hxx>
#include <rtl/character.hxx>
#include <rtl/ustrbuf.hxx>
#include <rtl/ustring.hxx>
#include <sal/types.h>
 
#include "UriReference.hxx"
 
namespace {
 
bool equalIgnoreEscapeCase(std::u16string_view s1, std::u16string_view s2) {
    if (s1.size() == s2.size()) {
        for (size_t i = 0; i < s1.size();) {
            if (s1[i] == '%' && s2[i] == '%' && s1.size() - i > 2
                && rtl::isAsciiHexDigit(s1[i + 1])
                && rtl::isAsciiHexDigit(s1[i + 2])
                && rtl::isAsciiHexDigit(s2[i + 1])
                && rtl::isAsciiHexDigit(s2[i + 2])
                && rtl::compareIgnoreAsciiCase(s1[i + 1], s2[i + 1]) == 0
                && rtl::compareIgnoreAsciiCase(s1[i + 2], s2[i + 2]) == 0)
            {
                i += 3;
            } else if (s1[i] != s2[i]) {
                return false;
            } else {
                ++i;
            }
        }
        return true;
    } else {
        return false;
    }
}
 
sal_Int32 parseScheme(std::u16string_view uriReference) {
    if (uriReference.size() >= 2 && rtl::isAsciiAlpha(uriReference[0])) {
        for (size_t i = 0; i < uriReference.size(); ++i) {
            sal_Unicode c = uriReference[i];
            if (c == ':') {
                return i;
            } else if (!rtl::isAsciiAlpha(c) && !rtl::isAsciiDigit(c)
                       && c != '+' && c != '-' && c != '.')
            {
                break;
            }
        }
    }
    return -1;
}
 
class UriReference:
    public cppu::WeakImplHelper<css::uri::XUriReference>
{
public:
    UriReference(
        OUString const & scheme, bool bHasAuthority,
        OUString const & authority, OUString const & path,
        bool bHasQuery, OUString const & query):
        m_base(
            scheme, bHasAuthority, authority, path, bHasQuery,
            query)
    {}
 
    UriReference(const UriReference&) = delete;
    UriReference& operator=(const UriReference&) = delete;
 
    virtual OUString SAL_CALL getUriReference() override
    { return m_base.getUriReference(); }
 
    virtual sal_Bool SAL_CALL isAbsolute() override
    { return m_base.isAbsolute(); }
 
    virtual OUString SAL_CALL getScheme() override
    { return m_base.getScheme(); }
 
    virtual OUString SAL_CALL getSchemeSpecificPart() override
    { return m_base.getSchemeSpecificPart(); }
 
    virtual sal_Bool SAL_CALL isHierarchical() override
    { return m_base.isHierarchical(); }
 
    virtual sal_Bool SAL_CALL hasAuthority() override
    { return m_base.hasAuthority(); }
 
    virtual OUString SAL_CALL getAuthority() override
    { return m_base.getAuthority(); }
 
    virtual OUString SAL_CALL getPath() override
    { return m_base.getPath(); }
 
    virtual sal_Bool SAL_CALL hasRelativePath() override
    { return m_base.hasRelativePath(); }
 
    virtual sal_Int32 SAL_CALL getPathSegmentCount() override
    { return m_base.getPathSegmentCount(); }
 
    virtual OUString SAL_CALL getPathSegment(sal_Int32 index) override
    { return m_base.getPathSegment(index); }
 
    virtual sal_Bool SAL_CALL hasQuery() override
    { return m_base.hasQuery(); }
 
    virtual OUString SAL_CALL getQuery() override
    { return m_base.getQuery(); }
 
    virtual sal_Bool SAL_CALL hasFragment() override
    { return m_base.hasFragment(); }
 
    virtual OUString SAL_CALL getFragment() override
    { return m_base.getFragment(); }
 
    virtual void SAL_CALL setFragment(OUString const & fragment) override
    { m_base.setFragment(fragment); }
 
    virtual void SAL_CALL clearFragment() override
    { m_base.clearFragment(); }
 
private:
    virtual ~UriReference() override {}
 
    stoc::uriproc::UriReference m_base;
};
 
css::uno::Reference< css::uri::XUriReference > parseGeneric(
    OUString const & scheme, std::u16string_view schemeSpecificPart)
{
    size_t len = schemeSpecificPart.size();
    size_t i = 0;
    bool hasAuthority = false;
    OUString authority;
    if (len - i >= 2 && schemeSpecificPart[i] == '/'
        && schemeSpecificPart[i + 1] == '/')
    {
        i += 2;
        sal_Int32 n = i;
        while (i < len && schemeSpecificPart[i] != '/'
               && schemeSpecificPart[i] != '?') {
            ++i;
        }
        hasAuthority = true;
        authority = schemeSpecificPart.substr(n, i - n);
    }
    sal_Int32 n = i;
    i = schemeSpecificPart.find('?', i);
    if (i == std::u16string_view::npos) {
        i = len;
    }
    OUString path( schemeSpecificPart.substr(n, i - n) );
    bool hasQuery = false;
    OUString query;
    if (i != len) {
        hasQuery = true;
        query = schemeSpecificPart.substr(i + 1);
    }
    return new UriReference(
        scheme, hasAuthority, authority, path, hasQuery, query);
}
 
struct Segment {
    bool leadingSlash;
    bool excessParent;
    std::u16string_view segment;
 
    Segment(bool theLeadingSlash, bool theExcessParent, std::u16string_view theSegment):
        leadingSlash(theLeadingSlash), excessParent(theExcessParent), segment(theSegment) {}
};
 
std::pair<std::vector<Segment>, bool> processSegments(
    std::u16string_view first, std::u16string_view second, bool processSpecialSegments)
{
    std::vector<Segment> segments;
    bool processed = false;
    std::u16string_view const * half = &first;
        // later checks for `half == &first` and `half == &second` rely on the fact that `first` and
        // `second` are passed by value, in case a caller passes the same object for both arguments
    std::size_t index = 0;
    bool slash = false;
    if (index == half->length()) {
        half = &second;
        index = 0;
    }
    if (index != half->length()) {
        if ((*half)[index] == u'/') {
            slash = true;
            ++index;
        }
        for (;;) {
            if (index == half->length() && half == &first) {
                half = &second;
                index = 0;
            }
            if (index == half->length()) {
                if (slash) {
                    segments.emplace_back(true, false, std::u16string_view());
                }
                break;
            }
            auto const n = std::min(half->find(u'/', index), half->length());
            auto const leadingSlash = slash;
            auto const segment = half->substr(index, n - index);
            auto const process = processSpecialSegments || half == &second;
            index = n;
            slash = false;
            if (index == half->length() && half == &first) {
                half = &second;
                index = 0;
            }
            if (index != half->length() && (*half)[index] == u'/') {
                slash = true;
                ++index;
            }
            if (process) {
                if (segment == u".") {
                    slash = leadingSlash;
                    processed = true;
                    continue;
                } else if (segment == u"..") {
                    if (segments.empty() || segments.back().excessParent) {
                        segments.emplace_back(leadingSlash, true, segment);
                    } else {
                        if (leadingSlash) {
                            segments.pop_back();
                        }
                        slash = leadingSlash;
                    }
                    processed = true;
                    continue;
                }
            }
            segments.emplace_back(leadingSlash, false, segment);
        }
    }
    return {segments, processed};
}
 
class Factory:
    public cppu::WeakImplHelper<
        css::lang::XServiceInfo, css::uri::XUriReferenceFactory>
{
public:
    explicit Factory(
        css::uno::Reference< css::uno::XComponentContext > context):
        m_context(std::move(context)) {}
 
    Factory(const Factory&) = delete;
    Factory& operator=(const Factory&) = delete;
 
    virtual OUString SAL_CALL getImplementationName() override;
 
    virtual sal_Bool SAL_CALL supportsService(OUString const & serviceName) override;
 
    virtual css::uno::Sequence< OUString > SAL_CALL
    getSupportedServiceNames() override;
 
    virtual css::uno::Reference< css::uri::XUriReference > SAL_CALL
    parse(OUString const & uriReference) override;
 
    virtual css::uno::Reference< css::uri::XUriReference > SAL_CALL
    makeAbsolute(
        css::uno::Reference< css::uri::XUriReference > const & baseUriReference,
        css::uno::Reference< css::uri::XUriReference > const & uriReference,
        sal_Bool processAdditionalSpecialSegments,
        css::uri::RelativeUriExcessParentSegments excessParentSegments) override;
 
    virtual css::uno::Reference< css::uri::XUriReference > SAL_CALL
    makeRelative(
        css::uno::Reference< css::uri::XUriReference > const & baseUriReference,
        css::uno::Reference< css::uri::XUriReference > const & uriReference,
        sal_Bool preferAuthorityOverRelativePath,
        sal_Bool preferAbsoluteOverRelativePath,
        sal_Bool encodeRetainedSpecialSegments) override;
 
private:
    virtual ~Factory() override {}
 
    css::uno::Reference< css::uri::XUriReference > clone(
        css::uno::Reference< css::uri::XUriReference > const & uriReference)
    { return parse(uriReference->getUriReference()); }
 
    css::uno::Reference< css::uno::XComponentContext > m_context;
};
 
OUString Factory::getImplementationName()
{
    return u"com.sun.star.comp.uri.UriReferenceFactory"_ustr;
}
 
sal_Bool Factory::supportsService(OUString const & serviceName)
{
    return cppu::supportsService(this, serviceName);
}
 
css::uno::Sequence< OUString > Factory::getSupportedServiceNames()
{
    css::uno::Sequence< OUString > s { u"com.sun.star.uri.UriReferenceFactory"_ustr };
    return s;
}
 
css::uno::Reference< css::uri::XUriReference > Factory::parse(
    OUString const & uriReference)
{
    sal_Int32 fragment = uriReference.indexOf('#');
    if (fragment == -1) {
        fragment = uriReference.getLength();
    }
    OUString scheme;
    OUString schemeSpecificPart;
    OUString serviceName;
    sal_Int32 n = parseScheme(uriReference);
    assert(n < fragment);
    if (n >= 0) {
        scheme = uriReference.copy(0, n);
        schemeSpecificPart = uriReference.copy(n + 1, fragment - (n + 1));
        OUStringBuffer buf(128);
        buf.append("com.sun.star.uri.UriSchemeParser_");
        for (sal_Int32 i = 0; i < scheme.getLength(); ++i) {
            sal_Unicode c = scheme[i];
            if (rtl::isAsciiUpperCase(c)) {
                buf.append(static_cast<sal_Unicode>(rtl::toAsciiLowerCase(c)));
            } else if (c == '+') {
                buf.append("PLUS");
            } else if (c == '-') {
                buf.append("HYPHEN");
            } else if (c == '.') {
                buf.append("DOT");
            } else {
                assert(rtl::isAsciiLowerCase(c) || rtl::isAsciiDigit(c));
                buf.append(c);
            }
        }
        serviceName = buf.makeStringAndClear();
    } else {
        schemeSpecificPart = uriReference.copy(0, fragment);
    }
    css::uno::Reference< css::uri::XUriSchemeParser > parser;
    if (!serviceName.isEmpty()) {
        css::uno::Reference< css::lang::XMultiComponentFactory > factory(
            m_context->getServiceManager());
        if (factory.is()) {
            css::uno::Reference< css::uno::XInterface > service;
            try {
                service = factory->createInstanceWithContext(
                    serviceName, m_context);
            } catch (css::uno::RuntimeException &) {
                throw;
            } catch (const css::uno::Exception &) {
                css::uno::Any anyEx = cppu::getCaughtException();
                throw css::lang::WrappedTargetRuntimeException(
                    "creating service " + serviceName,
                    getXWeak(),
                    anyEx);
            }
            if (service.is()) {
                parser.set( service, css::uno::UNO_QUERY_THROW);
            }
        }
    }
    css::uno::Reference< css::uri::XUriReference > uriRef(
        parser.is()
        ? parser->parse(scheme, schemeSpecificPart)
        : parseGeneric(scheme, schemeSpecificPart));
    if (uriRef.is() && fragment != uriReference.getLength()) {
        uriRef->setFragment(uriReference.copy(fragment + 1));
    }
    return uriRef;
}
 
css::uno::Reference< css::uri::XUriReference > Factory::makeAbsolute(
    css::uno::Reference< css::uri::XUriReference > const & baseUriReference,
    css::uno::Reference< css::uri::XUriReference > const & uriReference,
    sal_Bool processAdditionalSpecialSegments,
    css::uri::RelativeUriExcessParentSegments excessParentSegments)
{
    if (!baseUriReference.is() || !baseUriReference->isAbsolute()
        || !uriReference.is()) {
        return nullptr;
    } else if (uriReference->isAbsolute()) {
        if (processAdditionalSpecialSegments) {
            auto const path = uriReference->getPath();
            auto [segments, proc] = processSegments(path, {}, true);
            if (proc) {
                OUStringBuffer abs(uriReference->getScheme() + ":");
                if (uriReference->hasAuthority()) {
                    abs.append("//" + uriReference->getAuthority());
                }
                for (auto const & i : segments)
                {
                    if (i.excessParent) {
                        switch (excessParentSegments) {
                        case css::uri::RelativeUriExcessParentSegments_ERROR:
                            return nullptr;
 
                        case css::uri::RelativeUriExcessParentSegments_RETAIN:
                            assert(i.segment == u"..");
                            break;
 
                        case css::uri::RelativeUriExcessParentSegments_REMOVE:
                            continue;
 
                        default:
                            assert(false);
                            break;
                        }
                    }
                    if (i.leadingSlash) {
                        abs.append('/');
                    }
                    abs.append(i.segment);
                }
                if (uriReference->hasQuery()) {
                    abs.append("?" + uriReference->getQuery());
                }
                if (uriReference->hasFragment()) {
                    abs.append("#" + uriReference->getFragment());
                }
                return parse(abs.makeStringAndClear());
            }
        }
        return clone(uriReference);
    } else if (!uriReference->hasAuthority()
               && uriReference->getPath().isEmpty()) {
        OUStringBuffer abs(baseUriReference->getScheme() + ":");
        if (baseUriReference->hasAuthority()) {
            abs.append("//" + baseUriReference->getAuthority());
        }
        abs.append(baseUriReference->getPath());
        if (uriReference->hasQuery()) {
            abs.append("?" + uriReference->getQuery());
        } else if (baseUriReference->hasQuery()) {
            abs.append("?" + baseUriReference->getQuery());
        }
        if (uriReference->hasFragment()) {
            abs.append("#" + uriReference->getFragment());
        }
        return parse(abs.makeStringAndClear());
    } else {
        OUStringBuffer abs(128);
        abs.append(baseUriReference->getScheme() + ":");
        if (uriReference->hasAuthority()) {
            abs.append("//" + uriReference->getAuthority());
        } else if (baseUriReference->hasAuthority()) {
            abs.append("//" + baseUriReference->getAuthority());
        }
        if (uriReference->hasRelativePath()) {
            auto path1 = baseUriReference->getPath();
            if (path1.isEmpty()) {
                if (baseUriReference->hasAuthority()) {
                    path1 = "/";
                }
            } else {
                path1 = path1.copy(0, path1.lastIndexOf('/') + 1);
            }
            auto const path2 = uriReference->getPath();
            auto [segments, _] = processSegments(path1, path2, processAdditionalSpecialSegments);
            (void)_;
            for (auto const & i : segments)
            {
                if (i.excessParent) {
                    switch (excessParentSegments) {
                    case css::uri::RelativeUriExcessParentSegments_ERROR:
                        return nullptr;
 
                    case css::uri::RelativeUriExcessParentSegments_RETAIN:
                        assert(i.segment == u"..");
                        break;
 
                    case css::uri::RelativeUriExcessParentSegments_REMOVE:
                        continue;
 
                    default:
                        assert(false);
                        break;
                    }
                }
                if (i.leadingSlash) {
                    abs.append('/');
                }
                abs.append(i.segment);
            }
        } else {
            bool processed = false;
            if (processAdditionalSpecialSegments) {
                auto const path = uriReference->getPath();
                auto [segments, proc] = processSegments(path, {}, true);
                if (proc) {
                    for (auto const & i : segments)
                    {
                        if (i.excessParent) {
                            switch (excessParentSegments) {
                            case css::uri::RelativeUriExcessParentSegments_ERROR:
                                return nullptr;
 
                            case css::uri::RelativeUriExcessParentSegments_RETAIN:
                                assert(i.segment == u"..");
                                break;
 
                            case css::uri::RelativeUriExcessParentSegments_REMOVE:
                                continue;
 
                            default:
                                assert(false);
                                break;
                            }
                        }
                        if (i.leadingSlash) {
                            abs.append('/');
                        }
                        abs.append(i.segment);
                    }
                    processed = true;
                }
            }
            if (!processed) {
                abs.append(uriReference->getPath());
            }
        }
        if (uriReference->hasQuery()) {
            abs.append("?" + uriReference->getQuery());
        }
        if (uriReference->hasFragment()) {
            abs.append("#" + uriReference->getFragment());
        }
        return parse(abs.makeStringAndClear());
    }
}
 
css::uno::Reference< css::uri::XUriReference > Factory::makeRelative(
    css::uno::Reference< css::uri::XUriReference > const & baseUriReference,
    css::uno::Reference< css::uri::XUriReference > const & uriReference,
    sal_Bool preferAuthorityOverRelativePath,
    sal_Bool preferAbsoluteOverRelativePath,
    sal_Bool encodeRetainedSpecialSegments)
{
    if (!baseUriReference.is() || !baseUriReference->isAbsolute()
        || !uriReference.is()) {
        return nullptr;
    } else if (!uriReference->isAbsolute() || uriReference->hasRelativePath()
               || !baseUriReference->getScheme().equalsIgnoreAsciiCase(
                   uriReference->getScheme())) {
        return clone(uriReference);
    } else {
        OUStringBuffer rel(128);
        bool omitQuery = false;
        if ((baseUriReference->hasAuthority() != uriReference->hasAuthority())
            || !equalIgnoreEscapeCase(
                baseUriReference->getAuthority(),
                uriReference->getAuthority()))
        {
            if (uriReference->hasAuthority()) {
                rel.append("//" + uriReference->getAuthority());
            }
            rel.append(uriReference->getPath());
        } else if ((equalIgnoreEscapeCase(
                        baseUriReference->getPath(), uriReference->getPath())
                    || (baseUriReference->getPath() == "/"
                        && uriReference->getPath().isEmpty()))
                   && baseUriReference->hasQuery() == uriReference->hasQuery()
                   && equalIgnoreEscapeCase(
                       baseUriReference->getQuery(), uriReference->getQuery()))
        {
            omitQuery = true;
        } else {
            sal_Int32 count1 = std::max< sal_Int32 >(
                baseUriReference->getPathSegmentCount(), 1);
            sal_Int32 count2 = std::max< sal_Int32 >(
                uriReference->getPathSegmentCount(), 1);
            sal_Int32 i = 0;
            for (; i < std::min(count1, count2) - 1; ++i) {
                if (!equalIgnoreEscapeCase(
                        baseUriReference->getPathSegment(i),
                        uriReference->getPathSegment(i)))
                {
                    break;
                }
            }
            if (i == 0
                && (preferAbsoluteOverRelativePath || uriReference->hasQuery())
                && (preferAuthorityOverRelativePath
                    || !uriReference->getPath().startsWith("//")))
            {
                if (uriReference->getPath().isEmpty()) {
                    if (!baseUriReference->getPath().isEmpty()
                        && baseUriReference->getPath() != "/")
                    {
                        rel.append('/');
                    }
                } else if (uriReference->getPath() == "/") {
                    if (baseUriReference->getPath().isEmpty()
                        || baseUriReference->getPath() != "/")
                    {
                        rel.append('/');
                    }
                } else {
                    if (uriReference->getPath().startsWith("//")) {
                        assert(uriReference->hasAuthority());
                        rel.append("//" + uriReference->getAuthority());
                    }
                    rel.append(uriReference->getPath());
                }
            } else {
                bool segments = false;
                for (sal_Int32 j = i; j < count1 - 1; ++j) {
                    if (segments) {
                        rel.append('/');
                    }
                    rel.append("..");
                    segments = true;
                }
                if (i < count2 - 1
                    || (!uriReference->getPathSegment(count2 - 1).isEmpty()))
                {
                    if (!segments
                        && (uriReference->getPathSegment(i).isEmpty()
                            || (parseScheme(uriReference->getPathSegment(i))
                                >= 0)))
                    {
                        rel.append('.');
                        segments = true;
                    }
                    for (; i < count2; ++i) {
                        if (segments) {
                            rel.append('/');
                        }
                        OUString s(uriReference->getPathSegment(i));
                        if (encodeRetainedSpecialSegments && s == ".") {
                            rel.append("%2E");
                        } else if (encodeRetainedSpecialSegments && s == "..") {
                            rel.append("%2E%2E");
                        } else {
                            rel.append(s);
                        }
                        segments = true;
                    }
                }
            }
        }
        if (!omitQuery && uriReference->hasQuery()) {
            rel.append("?" + uriReference->getQuery());
        }
        if (uriReference->hasFragment()) {
            rel.append("#" + uriReference->getFragment());
        }
        return parse(rel.makeStringAndClear());
    }
}
 
}
 
extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
com_sun_star_comp_uri_UriReferenceFactory_get_implementation(css::uno::XComponentContext* rxContext,
        css::uno::Sequence<css::uno::Any> const &)
{
    return ::cppu::acquire(new Factory(rxContext));
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.