diff options
Diffstat (limited to 'stoc/source/uriproc/UriReferenceFactory.cxx')
-rw-r--r-- | stoc/source/uriproc/UriReferenceFactory.cxx | 719 |
1 files changed, 719 insertions, 0 deletions
diff --git a/stoc/source/uriproc/UriReferenceFactory.cxx b/stoc/source/uriproc/UriReferenceFactory.cxx new file mode 100644 index 000000000..bb96a55d8 --- /dev/null +++ b/stoc/source/uriproc/UriReferenceFactory.cxx @@ -0,0 +1,719 @@ +/* -*- 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(OUString const & s1, OUString const & s2) { + if (s1.getLength() == s2.getLength()) { + for (sal_Int32 i = 0; i < s1.getLength();) { + if (s1[i] == '%' && s2[i] == '%' && s1.getLength() - 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(OUString const & uriReference) { + if (uriReference.getLength() >= 2 && rtl::isAsciiAlpha(uriReference[0])) { + for (sal_Int32 i = 0; i < uriReference.getLength(); ++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, OUString const & schemeSpecificPart) +{ + sal_Int32 len = schemeSpecificPart.getLength(); + sal_Int32 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.copy(n, i - n); + } + sal_Int32 n = i; + i = schemeSpecificPart.indexOf('?', i); + if (i == -1) { + i = len; + } + OUString path = schemeSpecificPart.copy(n, i - n); + bool hasQuery = false; + OUString query; + if (i != len) { + hasQuery = true; + query = schemeSpecificPart.copy(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 > const & context): + m_context(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 "com.sun.star.comp.uri.UriReferenceFactory"; +} + +sal_Bool Factory::supportsService(OUString const & serviceName) +{ + return cppu::supportsService(this, serviceName); +} + +css::uno::Sequence< OUString > Factory::getSupportedServiceNames() +{ + css::uno::Sequence< OUString > s { "com.sun.star.uri.UriReferenceFactory" }; + 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, + static_cast< cppu::OWeakObject * >(this), + 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()); + abs.append(':'); + if (uriReference->hasAuthority()) { + abs.append("//"); + 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('?'); + abs.append(uriReference->getQuery()); + } + if (uriReference->hasFragment()) { + abs.append('#'); + abs.append(uriReference->getFragment()); + } + return parse(abs.makeStringAndClear()); + } + } + return clone(uriReference); + } else if (!uriReference->hasAuthority() + && uriReference->getPath().isEmpty()) { + OUStringBuffer abs(baseUriReference->getScheme()); + abs.append(':'); + if (baseUriReference->hasAuthority()) { + abs.append("//"); + abs.append(baseUriReference->getAuthority()); + } + abs.append(baseUriReference->getPath()); + if (uriReference->hasQuery()) { + abs.append('?'); + abs.append(uriReference->getQuery()); + } else if (baseUriReference->hasQuery()) { + abs.append('?'); + abs.append(baseUriReference->getQuery()); + } + if (uriReference->hasFragment()) { + abs.append('#'); + abs.append(uriReference->getFragment()); + } + return parse(abs.makeStringAndClear()); + } else { + OUStringBuffer abs(128); + abs.append(baseUriReference->getScheme()); + abs.append(':'); + if (uriReference->hasAuthority()) { + abs.append("//"); + abs.append(uriReference->getAuthority()); + } else if (baseUriReference->hasAuthority()) { + abs.append("//"); + 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('?'); + abs.append(uriReference->getQuery()); + } + if (uriReference->hasFragment()) { + abs.append('#'); + 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("//"); + 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("//"); + 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('?'); + rel.append(uriReference->getQuery()); + } + if (uriReference->hasFragment()) { + rel.append('#'); + 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: */ |