/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 { 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, bool> processSegments( std::u16string_view first, std::u16string_view second, bool processSpecialSegments) { std::vector 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(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 const &) { return ::cppu::acquire(new Factory(rxContext)); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */