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