summaryrefslogtreecommitdiffstats
path: root/svl/source/misc
diff options
context:
space:
mode:
Diffstat (limited to 'svl/source/misc')
-rw-r--r--svl/source/misc/PasswordHelper.cxx180
-rw-r--r--svl/source/misc/adrparse.cxx556
-rw-r--r--svl/source/misc/documentlockfile.cxx226
-rw-r--r--svl/source/misc/filenotation.cxx122
-rw-r--r--svl/source/misc/fstathelper.cxx94
-rw-r--r--svl/source/misc/getstringresource.cxx31
-rw-r--r--svl/source/misc/gridprinter.cxx133
-rw-r--r--svl/source/misc/inethist.cxx371
-rw-r--r--svl/source/misc/inettype.cxx443
-rw-r--r--svl/source/misc/lngmisc.cxx132
-rw-r--r--svl/source/misc/lockfilecommon.cxx243
-rw-r--r--svl/source/misc/msodocumentlockfile.cxx269
-rw-r--r--svl/source/misc/ownlist.cxx70
-rw-r--r--svl/source/misc/sharecontrolfile.cxx337
-rw-r--r--svl/source/misc/sharedstring.cxx70
-rw-r--r--svl/source/misc/sharedstringpool.cxx184
-rw-r--r--svl/source/misc/strmadpt.cxx653
-rw-r--r--svl/source/misc/urihelper.cxx884
18 files changed, 4998 insertions, 0 deletions
diff --git a/svl/source/misc/PasswordHelper.cxx b/svl/source/misc/PasswordHelper.cxx
new file mode 100644
index 0000000000..cfae72f649
--- /dev/null
+++ b/svl/source/misc/PasswordHelper.cxx
@@ -0,0 +1,180 @@
+/* -*- 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 <svl/PasswordHelper.hxx>
+#include <comphelper/hash.hxx>
+#include <rtl/digest.h>
+#include <memory>
+#include <unicode/regex.h>
+#include <unicode/unistr.h>
+#include <unicode/errorcode.h>
+#include <zxcvbn.h>
+#include <sal/log.hxx>
+
+using namespace com::sun::star;
+
+void SvPasswordHelper::GetHashPasswordSHA256(uno::Sequence<sal_Int8>& rPassHash, std::u16string_view rPassword)
+{
+ OString const tmp(OUStringToOString(rPassword, RTL_TEXTENCODING_UTF8));
+ ::std::vector<unsigned char> const hash(::comphelper::Hash::calculateHash(
+ reinterpret_cast<unsigned char const*>(tmp.getStr()), tmp.getLength(),
+ ::comphelper::HashType::SHA256));
+ rPassHash.realloc(hash.size());
+ ::std::copy(hash.begin(), hash.end(), rPassHash.getArray());
+ rtl_secureZeroMemory(const_cast<char *>(tmp.getStr()), tmp.getLength());
+}
+
+void SvPasswordHelper::GetHashPasswordSHA1UTF8(uno::Sequence<sal_Int8>& rPassHash, std::u16string_view rPassword)
+{
+ OString const tmp(OUStringToOString(rPassword, RTL_TEXTENCODING_UTF8));
+ ::std::vector<unsigned char> const hash(::comphelper::Hash::calculateHash(
+ reinterpret_cast<unsigned char const*>(tmp.getStr()), tmp.getLength(),
+ ::comphelper::HashType::SHA1));
+ rPassHash.realloc(hash.size());
+ ::std::copy(hash.begin(), hash.end(), rPassHash.getArray());
+ rtl_secureZeroMemory(const_cast<char *>(tmp.getStr()), tmp.getLength());
+}
+
+void SvPasswordHelper::GetHashPassword(uno::Sequence<sal_Int8>& rPassHash, const char* pPass, sal_uInt32 nLen)
+{
+ rPassHash.realloc(RTL_DIGEST_LENGTH_SHA1);
+
+ rtlDigestError aError = rtl_digest_SHA1 (pPass, nLen, reinterpret_cast<sal_uInt8*>(rPassHash.getArray()), rPassHash.getLength());
+ if (aError != rtl_Digest_E_None)
+ {
+ rPassHash.realloc(0);
+ }
+}
+
+void SvPasswordHelper::GetHashPasswordLittleEndian(uno::Sequence<sal_Int8>& rPassHash, std::u16string_view sPass)
+{
+ sal_Int32 nSize(sPass.size());
+ std::unique_ptr<char[]> pCharBuffer(new char[nSize * sizeof(sal_Unicode)]);
+
+ for (sal_Int32 i = 0; i < nSize; ++i)
+ {
+ sal_Unicode ch(sPass[ i ]);
+ pCharBuffer[2 * i] = static_cast< char >(ch & 0xFF);
+ pCharBuffer[2 * i + 1] = static_cast< char >(ch >> 8);
+ }
+
+ GetHashPassword(rPassHash, pCharBuffer.get(), nSize * sizeof(sal_Unicode));
+ rtl_secureZeroMemory(pCharBuffer.get(), nSize * sizeof(sal_Unicode));
+}
+
+void SvPasswordHelper::GetHashPasswordBigEndian(uno::Sequence<sal_Int8>& rPassHash, std::u16string_view sPass)
+{
+ sal_Int32 nSize(sPass.size());
+ std::unique_ptr<char[]> pCharBuffer(new char[nSize * sizeof(sal_Unicode)]);
+
+ for (sal_Int32 i = 0; i < nSize; ++i)
+ {
+ sal_Unicode ch(sPass[ i ]);
+ pCharBuffer[2 * i] = static_cast< char >(ch >> 8);
+ pCharBuffer[2 * i + 1] = static_cast< char >(ch & 0xFF);
+ }
+
+ GetHashPassword(rPassHash, pCharBuffer.get(), nSize * sizeof(sal_Unicode));
+ rtl_secureZeroMemory(pCharBuffer.get(), nSize * sizeof(sal_Unicode));
+}
+
+void SvPasswordHelper::GetHashPassword(uno::Sequence<sal_Int8>& rPassHash, std::u16string_view sPass)
+{
+ GetHashPasswordLittleEndian(rPassHash, sPass);
+}
+
+bool SvPasswordHelper::CompareHashPassword(const uno::Sequence<sal_Int8>& rOldPassHash, std::u16string_view sNewPass)
+{
+ bool bResult = false;
+
+ if (rOldPassHash.getLength() == RTL_DIGEST_LENGTH_SHA1)
+ {
+ uno::Sequence<sal_Int8> aNewPass(RTL_DIGEST_LENGTH_SHA1);
+ GetHashPasswordSHA1UTF8(aNewPass, sNewPass);
+ if (aNewPass == rOldPassHash)
+ {
+ bResult = true;
+ }
+ else
+ {
+ GetHashPasswordLittleEndian(aNewPass, sNewPass);
+ if (aNewPass == rOldPassHash)
+ bResult = true;
+ else
+ {
+ GetHashPasswordBigEndian(aNewPass, sNewPass);
+ bResult = (aNewPass == rOldPassHash);
+ }
+ }
+ }
+ else if (rOldPassHash.getLength() == 32)
+ {
+ uno::Sequence<sal_Int8> aNewPass;
+ GetHashPasswordSHA256(aNewPass, sNewPass);
+ bResult = aNewPass == rOldPassHash;
+ }
+
+ return bResult;
+}
+
+double SvPasswordHelper::GetPasswordStrengthPercentage(const char* pPassword)
+{
+ // Entropy bits corresponding to 100% password strength
+ static constexpr double fMaxPassStrengthEntorpyBits = 112.0;
+ return std::min(100.0,
+ ZxcvbnMatch(pPassword, nullptr, nullptr) * 100.0 / fMaxPassStrengthEntorpyBits);
+}
+
+double SvPasswordHelper::GetPasswordStrengthPercentage(const OUString& aPassword)
+{
+ OString aPasswordUtf8 = aPassword.toUtf8();
+ return GetPasswordStrengthPercentage(aPasswordUtf8.getStr());
+}
+
+bool SvPasswordHelper::PasswordMeetsPolicy(const char* pPassword,
+ const std::optional<OUString>& oPasswordPolicy)
+{
+ if (oPasswordPolicy)
+ {
+ icu::ErrorCode aStatus;
+ icu::UnicodeString sPassword(pPassword);
+ icu::UnicodeString sRegex(oPasswordPolicy->getStr());
+ icu::RegexMatcher aRegexMatcher(sRegex, sPassword, 0, aStatus);
+
+ if (aRegexMatcher.matches(aStatus))
+ return true;
+
+ SAL_WARN_IF(
+ aStatus.isFailure(), "svl.misc",
+ "Password policy regular expression failed with error: " << aStatus.errorName());
+
+ return false;
+ }
+ return true;
+}
+
+bool SvPasswordHelper::PasswordMeetsPolicy(const OUString& aPassword,
+ const std::optional<OUString>& oPasswordPolicy)
+{
+ OString aPasswordUtf8 = aPassword.toUtf8();
+ return PasswordMeetsPolicy(aPasswordUtf8.getStr(), oPasswordPolicy);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/adrparse.cxx b/svl/source/misc/adrparse.cxx
new file mode 100644
index 0000000000..19e869a092
--- /dev/null
+++ b/svl/source/misc/adrparse.cxx
@@ -0,0 +1,556 @@
+/* -*- 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 <rtl/ustrbuf.hxx>
+#include <svl/adrparse.hxx>
+
+namespace
+{
+
+enum ElementType { ELEMENT_START, ELEMENT_DELIM, ELEMENT_ITEM, ELEMENT_END };
+
+struct ParsedAddrSpec
+{
+ sal_Unicode const * m_pBegin;
+ sal_Unicode const * m_pEnd;
+ ElementType m_eLastElem;
+ bool m_bAtFound;
+ bool m_bReparse;
+
+ ParsedAddrSpec() { reset(); }
+
+ bool isPoorlyValid() const { return m_eLastElem >= ELEMENT_ITEM; }
+
+ bool isValid() const { return isPoorlyValid() && m_bAtFound; }
+
+ void reset();
+
+ void finish();
+};
+
+void ParsedAddrSpec::reset()
+{
+ m_pBegin = nullptr;
+ m_pEnd = nullptr;
+ m_eLastElem = ELEMENT_START;
+ m_bAtFound = false;
+ m_bReparse = false;
+}
+
+void ParsedAddrSpec::finish()
+{
+ if (isPoorlyValid())
+ m_eLastElem = ELEMENT_END;
+ else
+ reset();
+}
+
+}
+
+class SvAddressParser_Impl
+{
+ enum State { BEFORE_COLON, BEFORE_LESS, AFTER_LESS, AFTER_GREATER };
+
+ enum TokenType: sal_uInt32 {
+ TOKEN_QUOTED = 0x80000000, TOKEN_DOMAIN, TOKEN_COMMENT, TOKEN_ATOM };
+
+ sal_Unicode const * m_pInputPos;
+ sal_Unicode const * m_pInputEnd;
+ sal_uInt32 m_nCurToken;
+ sal_Unicode const * m_pCurTokenBegin;
+ sal_Unicode const * m_pCurTokenEnd;
+ ParsedAddrSpec m_aOuterAddrSpec;
+ ParsedAddrSpec m_aInnerAddrSpec;
+ ParsedAddrSpec * m_pAddrSpec;
+ State m_eState;
+ TokenType m_eType;
+
+ inline void reset();
+
+ void addTokenToAddrSpec(ElementType eTokenElem);
+
+ bool readToken();
+
+ static OUString reparse(sal_Unicode const * pBegin,
+ sal_Unicode const * pEnd);
+
+public:
+ SvAddressParser_Impl(SvAddressParser * pParser, const OUString& rIn);
+};
+
+inline void SvAddressParser_Impl::reset()
+{
+ m_aOuterAddrSpec.reset();
+ m_aInnerAddrSpec.reset();
+ m_pAddrSpec = &m_aOuterAddrSpec;
+ m_eState = BEFORE_COLON;
+ m_eType = TOKEN_ATOM;
+}
+
+void SvAddressParser_Impl::addTokenToAddrSpec(ElementType eTokenElem)
+{
+ if (!m_pAddrSpec->m_pBegin)
+ m_pAddrSpec->m_pBegin = m_pCurTokenBegin;
+ else if (m_pAddrSpec->m_pEnd < m_pCurTokenBegin)
+ m_pAddrSpec->m_bReparse = true;
+ m_pAddrSpec->m_pEnd = m_pCurTokenEnd;
+ m_pAddrSpec->m_eLastElem = eTokenElem;
+}
+
+
+// SvAddressParser_Impl
+
+
+bool SvAddressParser_Impl::readToken()
+{
+ m_nCurToken = m_eType;
+ switch (m_eType)
+ {
+ case TOKEN_QUOTED:
+ {
+ m_pCurTokenBegin = m_pInputPos - 1;
+ bool bEscaped = false;
+ for (;;)
+ {
+ if (m_pInputPos >= m_pInputEnd)
+ return false;
+ sal_Unicode cChar = *m_pInputPos++;
+ if (bEscaped)
+ {
+ bEscaped = false;
+ }
+ else if (cChar == '"')
+ {
+ m_pCurTokenEnd = m_pInputPos;
+ return true;
+ }
+ else if (cChar == '\\')
+ bEscaped = true;
+ }
+ }
+
+ case TOKEN_DOMAIN:
+ {
+ m_pCurTokenBegin = m_pInputPos - 1;
+ bool bEscaped = false;
+ for (;;)
+ {
+ if (m_pInputPos >= m_pInputEnd)
+ return false;
+ sal_Unicode cChar = *m_pInputPos++;
+ if (bEscaped)
+ bEscaped = false;
+ else if (cChar == ']')
+ {
+ m_pCurTokenEnd = m_pInputPos;
+ return true;
+ }
+ else if (cChar == '\\')
+ bEscaped = true;
+ }
+ }
+
+ case TOKEN_COMMENT:
+ {
+ m_pCurTokenBegin = m_pInputPos - 1;
+ bool bEscaped = false;
+ int nLevel = 0;
+ for (;;)
+ {
+ if (m_pInputPos >= m_pInputEnd)
+ return false;
+ sal_Unicode cChar = *m_pInputPos++;
+ if (bEscaped)
+ {
+ bEscaped = false;
+ }
+ else if (cChar == '(')
+ {
+ ++nLevel;
+ }
+ else if (cChar == ')')
+ if (nLevel)
+ {
+ --nLevel;
+ }
+ else
+ return true;
+ else if (cChar == '\\')
+ {
+ bEscaped = true;
+ }
+ }
+ }
+
+ default:
+ {
+ sal_Unicode cChar;
+ for (;;)
+ {
+ if (m_pInputPos >= m_pInputEnd)
+ return false;
+ cChar = *m_pInputPos++;
+ if (cChar > ' ' && cChar != 0x7F) // DEL
+ break;
+ }
+ m_pCurTokenBegin = m_pInputPos - 1;
+ if (cChar == '"' || cChar == '(' || cChar == ')' || cChar == ','
+ || cChar == '.' || cChar == ':' || cChar == ';'
+ || cChar == '<' || cChar == '>' || cChar == '@'
+ || cChar == '[' || cChar == '\\' || cChar == ']')
+ {
+ m_nCurToken = cChar;
+ m_pCurTokenEnd = m_pInputPos;
+ return true;
+ }
+ else
+ for (;;)
+ {
+ if (m_pInputPos >= m_pInputEnd)
+ {
+ m_pCurTokenEnd = m_pInputPos;
+ return true;
+ }
+ cChar = *m_pInputPos++;
+ if (cChar <= ' ' || cChar == '"' || cChar == '('
+ || cChar == ')' || cChar == ',' || cChar == '.'
+ || cChar == ':' || cChar == ';' || cChar == '<'
+ || cChar == '>' || cChar == '@' || cChar == '['
+ || cChar == '\\' || cChar == ']'
+ || cChar == 0x7F) // DEL
+ {
+ m_pCurTokenEnd = --m_pInputPos;
+ return true;
+ }
+ }
+ }
+ }
+}
+
+// static
+OUString SvAddressParser_Impl::reparse(sal_Unicode const * pBegin,
+ sal_Unicode const * pEnd)
+{
+ OUStringBuffer aResult;
+ TokenType eMode = TOKEN_ATOM;
+ bool bEscaped = false;
+ int nLevel = 0;
+ while (pBegin < pEnd)
+ {
+ sal_Unicode cChar = *pBegin++;
+ switch (eMode)
+ {
+ case TOKEN_QUOTED:
+ if (bEscaped)
+ {
+ aResult.append(cChar);
+ bEscaped = false;
+ }
+ else if (cChar == '"')
+ {
+ aResult.append(cChar);
+ eMode = TOKEN_ATOM;
+ }
+ else if (cChar == '\\')
+ {
+ aResult.append(cChar);
+ bEscaped = true;
+ }
+ else
+ aResult.append(cChar);
+ break;
+
+ case TOKEN_DOMAIN:
+ if (bEscaped)
+ {
+ aResult.append(cChar);
+ bEscaped = false;
+ }
+ else if (cChar == ']')
+ {
+ aResult.append(cChar);
+ eMode = TOKEN_ATOM;
+ }
+ else if (cChar == '\\')
+ {
+ aResult.append(cChar);
+ bEscaped = true;
+ }
+ else
+ aResult.append(cChar);
+ break;
+
+ case TOKEN_COMMENT:
+ if (bEscaped)
+ bEscaped = false;
+ else if (cChar == '(')
+ ++nLevel;
+ else if (cChar == ')')
+ if (nLevel)
+ --nLevel;
+ else
+ eMode = TOKEN_ATOM;
+ else if (cChar == '\\')
+ bEscaped = true;
+ break;
+
+ case TOKEN_ATOM:
+ if (cChar <= ' ' || cChar == 0x7F) // DEL
+ {
+ }
+ else if (cChar == '(')
+ {
+ eMode = TOKEN_COMMENT;
+ }
+ else
+ {
+ if (cChar == '"')
+ {
+ aResult.append(cChar);
+ eMode = TOKEN_QUOTED;
+ }
+ else if (cChar == '[')
+ {
+ aResult.append(cChar);
+ eMode = TOKEN_QUOTED;
+ }
+ else
+ aResult.append(cChar);
+ }
+ break;
+ }
+ }
+ return aResult.makeStringAndClear();
+}
+
+SvAddressParser_Impl::SvAddressParser_Impl(SvAddressParser * pParser,
+ const OUString& rInput)
+ : m_pCurTokenBegin(nullptr)
+ , m_pCurTokenEnd(nullptr)
+{
+ m_pInputPos = rInput.getStr();
+ m_pInputEnd = m_pInputPos + rInput.getLength();
+
+ reset();
+ bool bDone = false;
+ for (;;)
+ {
+ if (!readToken())
+ {
+ if (m_eState == AFTER_LESS)
+ m_nCurToken = '>';
+ else
+ {
+ m_nCurToken = ',';
+ bDone = true;
+ }
+ }
+ switch (m_nCurToken)
+ {
+ case TOKEN_QUOTED:
+ if (m_pAddrSpec->m_eLastElem != ELEMENT_END)
+ {
+ if (m_pAddrSpec->m_bAtFound
+ || m_pAddrSpec->m_eLastElem <= ELEMENT_DELIM)
+ m_pAddrSpec->reset();
+ addTokenToAddrSpec(ELEMENT_ITEM);
+ }
+ m_eType = TOKEN_ATOM;
+ break;
+
+ case TOKEN_DOMAIN:
+ if (m_pAddrSpec->m_eLastElem != ELEMENT_END)
+ {
+ if (m_pAddrSpec->m_bAtFound && m_pAddrSpec->m_eLastElem == ELEMENT_DELIM)
+ addTokenToAddrSpec(ELEMENT_ITEM);
+ else
+ m_pAddrSpec->reset();
+ }
+ m_eType = TOKEN_ATOM;
+ break;
+
+ case TOKEN_COMMENT:
+ m_eType = TOKEN_ATOM;
+ break;
+
+ case TOKEN_ATOM:
+ if (m_pAddrSpec->m_eLastElem != ELEMENT_END)
+ {
+ if (m_pAddrSpec->m_eLastElem != ELEMENT_DELIM)
+ m_pAddrSpec->reset();
+ addTokenToAddrSpec(ELEMENT_ITEM);
+ }
+ break;
+
+ case '(':
+ m_eType = TOKEN_COMMENT;
+ break;
+
+ case ')':
+ case '\\':
+ case ']':
+ m_pAddrSpec->finish();
+ break;
+
+ case '<':
+ switch (m_eState)
+ {
+ case BEFORE_COLON:
+ case BEFORE_LESS:
+ m_aOuterAddrSpec.finish();
+ m_pAddrSpec = &m_aInnerAddrSpec;
+ m_eState = AFTER_LESS;
+ break;
+
+ case AFTER_LESS:
+ m_aInnerAddrSpec.finish();
+ break;
+
+ case AFTER_GREATER:
+ m_aOuterAddrSpec.finish();
+ break;
+ }
+ break;
+
+ case '>':
+ if (m_eState == AFTER_LESS)
+ {
+ m_aInnerAddrSpec.finish();
+ if (m_aInnerAddrSpec.isValid())
+ m_aOuterAddrSpec.m_eLastElem = ELEMENT_END;
+ m_pAddrSpec = &m_aOuterAddrSpec;
+ m_eState = AFTER_GREATER;
+ }
+ else
+ {
+ m_aOuterAddrSpec.finish();
+ }
+ break;
+
+ case '@':
+ if (m_pAddrSpec->m_eLastElem != ELEMENT_END)
+ {
+ if (!m_pAddrSpec->m_bAtFound
+ && m_pAddrSpec->m_eLastElem == ELEMENT_ITEM)
+ {
+ addTokenToAddrSpec(ELEMENT_DELIM);
+ m_pAddrSpec->m_bAtFound = true;
+ }
+ else
+ m_pAddrSpec->reset();
+ }
+ break;
+
+ case ',':
+ case ';':
+ if (m_eState == AFTER_LESS)
+ if (m_nCurToken == ',')
+ {
+ if (m_aInnerAddrSpec.m_eLastElem != ELEMENT_END)
+ m_aInnerAddrSpec.reset();
+ }
+ else
+ m_aInnerAddrSpec.finish();
+ else
+ {
+ if(m_aInnerAddrSpec.isValid() || (!m_aOuterAddrSpec.isValid() && m_aInnerAddrSpec.isPoorlyValid()))
+ {
+ m_pAddrSpec = &m_aInnerAddrSpec;
+ }
+ else if(m_aOuterAddrSpec.isPoorlyValid())
+ {
+ m_pAddrSpec = &m_aOuterAddrSpec;
+ }
+ else
+ {
+ m_pAddrSpec = nullptr;
+ }
+
+ if (m_pAddrSpec)
+ {
+ OUString aTheAddrSpec;
+ if (m_pAddrSpec->m_bReparse)
+ aTheAddrSpec = reparse(m_pAddrSpec->m_pBegin, m_pAddrSpec->m_pEnd);
+ else
+ {
+ sal_Int32 nLen = m_pAddrSpec->m_pEnd - m_pAddrSpec->m_pBegin;
+ if (nLen == rInput.getLength())
+ aTheAddrSpec = rInput;
+ else
+ aTheAddrSpec = rInput.copy( (m_pAddrSpec->m_pBegin - rInput.getStr()),
+ nLen);
+ }
+ pParser->m_vAddresses.emplace_back( aTheAddrSpec );
+ }
+ if (bDone)
+ return;
+ reset();
+ }
+ break;
+
+ case ':':
+ switch (m_eState)
+ {
+ case BEFORE_COLON:
+ m_aOuterAddrSpec.reset();
+ m_eState = BEFORE_LESS;
+ break;
+
+ case BEFORE_LESS:
+ case AFTER_GREATER:
+ m_aOuterAddrSpec.finish();
+ break;
+
+ case AFTER_LESS:
+ m_aInnerAddrSpec.reset();
+ break;
+ }
+ break;
+
+ case '"':
+ m_eType = TOKEN_QUOTED;
+ break;
+
+ case '.':
+ if (m_pAddrSpec->m_eLastElem != ELEMENT_END)
+ {
+ if (m_pAddrSpec->m_eLastElem != ELEMENT_DELIM)
+ addTokenToAddrSpec(ELEMENT_DELIM);
+ else
+ m_pAddrSpec->reset();
+ }
+ break;
+
+ case '[':
+ m_eType = TOKEN_DOMAIN;
+ break;
+ }
+ }
+}
+
+SvAddressParser::SvAddressParser(const OUString& rInput)
+{
+ SvAddressParser_Impl aDoParse(this, rInput);
+}
+
+SvAddressParser::~SvAddressParser()
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/documentlockfile.cxx b/svl/source/misc/documentlockfile.cxx
new file mode 100644
index 0000000000..31cbd19693
--- /dev/null
+++ b/svl/source/misc/documentlockfile.cxx
@@ -0,0 +1,226 @@
+/* -*- 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 <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/ucb/InsertCommandArgument.hpp>
+#include <com/sun/star/ucb/NameClashException.hpp>
+#include <com/sun/star/io/WrongFormatException.hpp>
+#include <com/sun/star/io/TempFile.hpp>
+#include <com/sun/star/io/XStream.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/io/XSeekable.hpp>
+#include <com/sun/star/io/XTruncate.hpp>
+
+#include <o3tl/enumrange.hxx>
+
+#include <rtl/string.hxx>
+#include <rtl/ustring.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <comphelper/processfactory.hxx>
+
+#include <ucbhelper/content.hxx>
+
+#include <svl/documentlockfile.hxx>
+
+using namespace ::com::sun::star;
+
+namespace svt {
+
+GenDocumentLockFile::GenDocumentLockFile(const OUString& aLockFileURL)
+ : LockFileCommon(aLockFileURL)
+{
+}
+
+
+GenDocumentLockFile::~GenDocumentLockFile()
+{
+}
+
+uno::Reference< io::XInputStream > GenDocumentLockFile::OpenStream(std::unique_lock<std::mutex>& /*rGuard*/)
+{
+ uno::Reference < css::ucb::XCommandEnvironment > xEnv;
+ ::ucbhelper::Content aSourceContent( GetURL(), xEnv, comphelper::getProcessComponentContext() );
+
+ // the file can be opened readonly, no locking will be done
+ return aSourceContent.openStream();
+}
+
+bool GenDocumentLockFile::CreateOwnLockFile()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ try
+ {
+ uno::Reference< io::XStream > xTempFile(
+ io::TempFile::create( comphelper::getProcessComponentContext() ),
+ uno::UNO_QUERY_THROW );
+ uno::Reference< io::XSeekable > xSeekable( xTempFile, uno::UNO_QUERY_THROW );
+
+ uno::Reference< io::XInputStream > xInput = xTempFile->getInputStream();
+ uno::Reference< io::XOutputStream > xOutput = xTempFile->getOutputStream();
+
+ if ( !xInput.is() || !xOutput.is() )
+ throw uno::RuntimeException();
+
+ LockFileEntry aNewEntry = GenerateOwnEntry();
+ WriteEntryToStream( aGuard, aNewEntry, xOutput );
+ xOutput->closeOutput();
+
+ xSeekable->seek( 0 );
+
+ uno::Reference < css::ucb::XCommandEnvironment > xEnv;
+ ::ucbhelper::Content aTargetContent( GetURL(), xEnv, comphelper::getProcessComponentContext() );
+
+ ucb::InsertCommandArgument aInsertArg;
+ aInsertArg.Data = xInput;
+ aInsertArg.ReplaceExisting = false;
+ uno::Any aCmdArg;
+ aCmdArg <<= aInsertArg;
+ aTargetContent.executeCommand( "insert", aCmdArg );
+
+ // try to let the file be hidden if possible
+ try {
+ aTargetContent.setPropertyValue("IsHidden", uno::Any( true ) );
+ } catch( uno::Exception& ) {}
+ }
+ catch( ucb::NameClashException& )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+bool GenDocumentLockFile::OverwriteOwnLockFile()
+{
+ std::unique_lock aGuard(m_aMutex);
+
+ // allows to overwrite the lock file with the current data
+ try
+ {
+ uno::Reference < css::ucb::XCommandEnvironment > xEnv;
+ ::ucbhelper::Content aTargetContent( GetURL(), xEnv, comphelper::getProcessComponentContext() );
+
+ LockFileEntry aNewEntry = GenerateOwnEntry();
+
+ uno::Reference< io::XStream > xStream = aTargetContent.openWriteableStreamNoLock();
+ uno::Reference< io::XOutputStream > xOutput = xStream->getOutputStream();
+ uno::Reference< io::XTruncate > xTruncate( xOutput, uno::UNO_QUERY_THROW );
+
+ xTruncate->truncate();
+ WriteEntryToStream( aGuard, aNewEntry, xOutput );
+ xOutput->closeOutput();
+ }
+ catch( uno::Exception& )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void GenDocumentLockFile::RemoveFile()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ // TODO/LATER: the removing is not atomic, is it possible in general to make it atomic?
+ LockFileEntry aNewEntry = GenerateOwnEntry();
+ LockFileEntry aFileData = GetLockDataImpl(aGuard);
+
+ if ( aFileData[LockFileComponent::SYSUSERNAME] != aNewEntry[LockFileComponent::SYSUSERNAME]
+ || aFileData[LockFileComponent::LOCALHOST] != aNewEntry[LockFileComponent::LOCALHOST]
+ || aFileData[LockFileComponent::USERURL] != aNewEntry[LockFileComponent::USERURL] )
+ throw io::IOException(); // not the owner, access denied
+
+ RemoveFileDirectly();
+}
+
+void GenDocumentLockFile::RemoveFileDirectly()
+{
+ uno::Reference < css::ucb::XCommandEnvironment > xEnv;
+ ::ucbhelper::Content aCnt(GetURL(), xEnv, comphelper::getProcessComponentContext());
+ aCnt.executeCommand("delete",
+ uno::Any(true));
+}
+
+LockFileEntry GenDocumentLockFile::GetLockData()
+{
+ std::unique_lock aGuard(m_aMutex);
+ return GetLockDataImpl(aGuard);
+}
+
+DocumentLockFile::DocumentLockFile( std::u16string_view aOrigURL )
+ : GenDocumentLockFile(GenerateOwnLockFileURL(aOrigURL, u".~lock."))
+{
+}
+
+
+DocumentLockFile::~DocumentLockFile()
+{
+}
+
+
+void DocumentLockFile::WriteEntryToStream(
+ std::unique_lock<std::mutex>& /*rGuard*/,
+ const LockFileEntry& aEntry, const uno::Reference< io::XOutputStream >& xOutput )
+{
+ OUStringBuffer aBuffer(256);
+
+ for ( LockFileComponent lft : o3tl::enumrange<LockFileComponent>() )
+ {
+ aBuffer.append( EscapeCharacters( aEntry[lft] ) );
+ if ( lft < LockFileComponent::LAST )
+ aBuffer.append( ',' );
+ else
+ aBuffer.append( ';' );
+ }
+
+ OString aStringData( OUStringToOString( aBuffer, RTL_TEXTENCODING_UTF8 ) );
+ uno::Sequence< sal_Int8 > aData( reinterpret_cast<sal_Int8 const *>(aStringData.getStr()), aStringData.getLength() );
+ xOutput->writeBytes( aData );
+}
+
+LockFileEntry DocumentLockFile::GetLockDataImpl(std::unique_lock<std::mutex>& rGuard)
+{
+ uno::Reference< io::XInputStream > xInput = OpenStream(rGuard);
+ if ( !xInput.is() )
+ throw uno::RuntimeException();
+
+ const sal_Int32 nBufLen = 32000;
+ uno::Sequence< sal_Int8 > aBuffer( nBufLen );
+
+ sal_Int32 nRead = xInput->readBytes( aBuffer, nBufLen );
+ xInput->closeInput();
+
+ if ( nRead == nBufLen )
+ throw io::WrongFormatException();
+
+ sal_Int32 nCurPos = 0;
+ return ParseEntry( aBuffer, nCurPos );
+}
+
+
+
+
+} // namespace svt
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/filenotation.cxx b/svl/source/misc/filenotation.cxx
new file mode 100644
index 0000000000..c4337708bc
--- /dev/null
+++ b/svl/source/misc/filenotation.cxx
@@ -0,0 +1,122 @@
+/* -*- 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 <svl/filenotation.hxx>
+#include <osl/file.h>
+#include <osl/diagnose.h>
+#include <tools/urlobj.hxx>
+
+namespace svt
+{
+
+ OFileNotation::OFileNotation( const OUString& _rUrlOrPath )
+ {
+ construct( _rUrlOrPath );
+ }
+
+ OFileNotation::OFileNotation( const OUString& _rUrlOrPath, NOTATION _eInputNotation )
+ {
+ if ( _eInputNotation == N_URL )
+ {
+ INetURLObject aParser( _rUrlOrPath );
+ if ( aParser.GetProtocol() == INetProtocol::File )
+ implInitWithURLNotation( _rUrlOrPath );
+ else
+ m_sSystem = m_sFileURL = _rUrlOrPath;
+ }
+ else
+ implInitWithSystemNotation( _rUrlOrPath );
+ }
+
+ bool OFileNotation::implInitWithSystemNotation( const OUString& _rSystemPath )
+ {
+ bool bSuccess = false;
+
+ m_sSystem = _rSystemPath;
+ if ( ( osl_File_E_None != osl_getFileURLFromSystemPath( m_sSystem.pData, &m_sFileURL.pData ) )
+ && ( m_sFileURL.isEmpty() )
+ )
+ {
+ if ( !_rSystemPath.isEmpty() )
+ {
+ INetURLObject aSmartParser;
+ aSmartParser.SetSmartProtocol( INetProtocol::File );
+ if ( aSmartParser.SetSmartURL( _rSystemPath ) )
+ {
+ m_sFileURL = aSmartParser.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ osl_getSystemPathFromFileURL( m_sFileURL.pData, &m_sSystem.pData );
+ bSuccess = true;
+ }
+ }
+ }
+ else
+ bSuccess = true;
+ return bSuccess;
+ }
+
+ void OFileNotation::implInitWithURLNotation( const OUString& _rURL )
+ {
+ m_sFileURL = _rURL;
+ osl_getSystemPathFromFileURL( _rURL.pData, &m_sSystem.pData );
+ }
+
+ void OFileNotation::construct( const OUString& _rUrlOrPath )
+ {
+ bool bSuccess = false;
+ // URL notation?
+ INetURLObject aParser( _rUrlOrPath );
+ switch ( aParser.GetProtocol() )
+ {
+ case INetProtocol::File:
+ // file URL
+ implInitWithURLNotation( _rUrlOrPath );
+ bSuccess = true;
+ break;
+
+ case INetProtocol::NotValid:
+ // assume system notation
+ bSuccess = implInitWithSystemNotation( _rUrlOrPath );
+ break;
+
+ default:
+ // it's a known scheme, but no file-URL -> assume that both the URL representation and the
+ // system representation are the URL itself
+ m_sSystem = m_sFileURL = _rUrlOrPath;
+ bSuccess = true;
+ break;
+ }
+
+ OSL_ENSURE( bSuccess, "OFileNotation::OFileNotation: could not detect the format!" );
+ }
+
+ OUString OFileNotation::get(NOTATION _eOutputNotation) const
+ {
+ switch (_eOutputNotation)
+ {
+ case N_SYSTEM: return m_sSystem;
+ case N_URL: return m_sFileURL;
+ }
+
+ OSL_FAIL("OFileNotation::get: invalid enum value!");
+ return OUString();
+ }
+
+} // namespace svt
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/fstathelper.cxx b/svl/source/misc/fstathelper.cxx
new file mode 100644
index 0000000000..6530ff7244
--- /dev/null
+++ b/svl/source/misc/fstathelper.cxx
@@ -0,0 +1,94 @@
+/* -*- 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 <com/sun/star/util/DateTime.hpp>
+#include <comphelper/processfactory.hxx>
+#include <o3tl/any.hxx>
+#include <rtl/ustring.hxx>
+#include <svl/fstathelper.hxx>
+#include <tools/date.hxx>
+#include <tools/time.hxx>
+#include <ucbhelper/content.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+
+bool FStatHelper::GetModifiedDateTimeOfFile( const OUString& rURL,
+ Date* pDate, tools::Time* pTime )
+{
+ bool bRet = false;
+ try
+ {
+ ::ucbhelper::Content aTestContent( rURL,
+ uno::Reference< XCommandEnvironment > (),
+ comphelper::getProcessComponentContext());
+ uno::Any aAny = aTestContent.getPropertyValue(
+ "DateModified" );
+ if( aAny.hasValue() )
+ {
+ bRet = true;
+ auto pDT = o3tl::doAccess<util::DateTime>(aAny);
+ if( pDate )
+ *pDate = Date( pDT->Day, pDT->Month, pDT->Year );
+ if( pTime )
+ *pTime = tools::Time( pDT->Hours, pDT->Minutes,
+ pDT->Seconds, pDT->NanoSeconds );
+ }
+ }
+ catch(...)
+ {
+ }
+
+ return bRet;
+}
+
+bool FStatHelper::IsDocument( const OUString& rURL )
+{
+ bool bExist = false;
+ try
+ {
+ ::ucbhelper::Content aTestContent( rURL,
+ uno::Reference< XCommandEnvironment > (),
+ comphelper::getProcessComponentContext());
+ bExist = aTestContent.isDocument();
+ }
+ catch(...)
+ {
+ }
+ return bExist;
+}
+
+bool FStatHelper::IsFolder( const OUString& rURL )
+{
+ bool bExist = false;
+ try
+ {
+ ::ucbhelper::Content aTestContent( rURL,
+ uno::Reference< XCommandEnvironment > (),
+ comphelper::getProcessComponentContext());
+ bExist = aTestContent.isFolder();
+ }
+ catch(...)
+ {
+ }
+ return bExist;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/getstringresource.cxx b/svl/source/misc/getstringresource.cxx
new file mode 100644
index 0000000000..6b066c24da
--- /dev/null
+++ b/svl/source/misc/getstringresource.cxx
@@ -0,0 +1,31 @@
+/* -*- 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 <rtl/ustring.hxx>
+#include <svl/svlresid.hxx>
+#include <unotools/resmgr.hxx>
+
+OUString SvlResId(TranslateId sContextAndId)
+{
+ return Translate::get(sContextAndId, Translate::Create("svl"));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/gridprinter.cxx b/svl/source/misc/gridprinter.cxx
new file mode 100644
index 0000000000..c6a1bdd5f9
--- /dev/null
+++ b/svl/source/misc/gridprinter.cxx
@@ -0,0 +1,133 @@
+/* -*- 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/.
+ */
+
+#include <svl/gridprinter.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <mdds/multi_type_vector/types.hpp>
+#include <mdds/multi_type_vector/macro.hpp>
+#include <mdds/multi_type_matrix.hpp>
+
+#include <iostream>
+
+namespace svl {
+
+// String ID
+const mdds::mtv::element_t element_type_string = mdds::mtv::element_type_user_start;
+// String block
+typedef mdds::mtv::default_element_block<element_type_string, OUString> string_block;
+
+namespace {
+
+struct matrix_traits
+{
+ typedef string_block string_element_block;
+ typedef mdds::mtv::uint16_element_block integer_element_block;
+};
+
+}
+
+}
+
+namespace rtl {
+
+// Callbacks for the string block. This needs to be in the same namespace as
+// OUString for argument dependent lookup.
+MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(OUString, svl::element_type_string, OUString(), svl::string_block)
+
+}
+
+namespace svl {
+
+typedef mdds::multi_type_matrix<matrix_traits> MatrixImplType;
+
+struct GridPrinter::Impl
+{
+ MatrixImplType maMatrix;
+ bool mbPrint;
+
+ Impl( size_t nRows, size_t nCols, bool bPrint ) :
+ maMatrix(nRows, nCols, OUString()), mbPrint(bPrint) {}
+};
+
+GridPrinter::GridPrinter( size_t nRows, size_t nCols, bool bPrint ) :
+ mpImpl(new Impl(nRows, nCols, bPrint)) {}
+
+GridPrinter::~GridPrinter()
+{
+}
+
+void GridPrinter::set( size_t nRow, size_t nCol, const OUString& rStr )
+{
+#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12 && __cplusplus == 202002L
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Warray-bounds"
+#endif
+ mpImpl->maMatrix.set(nRow, nCol, rStr);
+#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12 && __cplusplus == 202002L
+#pragma GCC diagnostic pop
+#endif
+}
+
+void GridPrinter::print( const char* pHeader ) const
+{
+ if (!mpImpl->mbPrint)
+ return;
+
+ if (pHeader)
+ std::cout << pHeader << std::endl;
+
+ MatrixImplType::size_pair_type ns = mpImpl->maMatrix.size();
+ std::vector<sal_Int32> aColWidths(ns.column, 0);
+
+ // Calculate column widths first.
+ for (size_t row = 0; row < ns.row; ++row)
+ {
+ for (size_t col = 0; col < ns.column; ++col)
+ {
+ OUString aStr = mpImpl->maMatrix.get_string(row, col);
+ if (aColWidths[col] < aStr.getLength())
+ aColWidths[col] = aStr.getLength();
+ }
+ }
+
+ // Make the row separator string.
+ OUStringBuffer aBuf("+");
+ for (size_t col = 0; col < ns.column; ++col)
+ {
+ aBuf.append("-");
+ for (sal_Int32 i = 0; i < aColWidths[col]; ++i)
+ aBuf.append(u'-');
+ aBuf.append("-+");
+ }
+
+ OUString aSep = aBuf.makeStringAndClear();
+
+ // Now print to stdout.
+ std::cout << aSep << std::endl;
+ for (size_t row = 0; row < ns.row; ++row)
+ {
+ std::cout << "| ";
+ for (size_t col = 0; col < ns.column; ++col)
+ {
+ OUString aStr = mpImpl->maMatrix.get_string(row, col);
+ size_t nPadding = aColWidths[col] - aStr.getLength();
+ aBuf.append(aStr);
+ for (size_t i = 0; i < nPadding; ++i)
+ aBuf.append(u' ');
+ std::cout << aBuf.makeStringAndClear() << " | ";
+ }
+ std::cout << std::endl;
+ std::cout << aSep << std::endl;
+ }
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/inethist.cxx b/svl/source/misc/inethist.cxx
new file mode 100644
index 0000000000..dc25d6b0b5
--- /dev/null
+++ b/svl/source/misc/inethist.cxx
@@ -0,0 +1,371 @@
+/* -*- 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 <svl/inethist.hxx>
+
+#include <algorithm>
+#include <string.h>
+
+#include <rtl/crc.h>
+#include <tools/debug.hxx>
+#include <tools/urlobj.hxx>
+
+/*
+ * INetURLHistory internals.
+ */
+#define INETHIST_DEF_FTP_PORT 21
+#define INETHIST_DEF_HTTP_PORT 80
+#define INETHIST_DEF_HTTPS_PORT 443
+
+#define INETHIST_SIZE_LIMIT 1024
+#define INETHIST_MAGIC_HEAD 0x484D4849UL
+
+class INetURLHistory_Impl
+{
+ struct head_entry
+ {
+ /** Representation.
+ */
+ sal_uInt32 m_nMagic;
+ sal_uInt16 m_nNext;
+
+ /** Initialization.
+ */
+ void initialize()
+ {
+ m_nMagic = INETHIST_MAGIC_HEAD;
+ m_nNext = 0;
+ }
+ };
+
+ struct hash_entry
+ {
+ /** Representation.
+ */
+ sal_uInt32 m_nHash;
+ sal_uInt16 m_nLru;
+
+ /** Initialization.
+ */
+ void initialize (sal_uInt16 nLru)
+ {
+ m_nHash = 0;
+ m_nLru = nLru;
+ }
+
+ /** Comparison.
+ */
+ bool operator== (sal_uInt32 nHash) const
+ {
+ return (m_nHash == nHash);
+ }
+ bool operator< (sal_uInt32 nHash) const
+ {
+ return (m_nHash < nHash);
+ }
+ };
+
+ struct lru_entry
+ {
+ /** Representation.
+ */
+ sal_uInt32 m_nHash;
+ sal_uInt16 m_nNext;
+ sal_uInt16 m_nPrev;
+
+ /** Initialization.
+ */
+ void initialize (sal_uInt16 nThis)
+ {
+ m_nHash = 0;
+ m_nNext = nThis;
+ m_nPrev = nThis;
+ }
+ };
+
+ /** Representation.
+ */
+ head_entry m_aHead;
+ hash_entry m_pHash[INETHIST_SIZE_LIMIT];
+ lru_entry m_pList[INETHIST_SIZE_LIMIT];
+
+ /** Initialization.
+ */
+ void initialize();
+
+ static sal_uInt16 capacity()
+ {
+ return sal_uInt16(INETHIST_SIZE_LIMIT);
+ }
+
+ static sal_uInt32 crc32 (OUString const & rData)
+ {
+ return rtl_crc32 (0, rData.getStr(), rData.getLength() * sizeof(sal_Unicode));
+ }
+
+ sal_uInt16 find (sal_uInt32 nHash) const;
+
+ void move (sal_uInt16 nSI, sal_uInt16 nDI);
+
+ void backlink (sal_uInt16 nThis, sal_uInt16 nTail)
+ {
+ lru_entry &rThis = m_pList[nThis];
+ lru_entry &rTail = m_pList[nTail];
+
+ rTail.m_nNext = nThis;
+ rTail.m_nPrev = rThis.m_nPrev;
+ rThis.m_nPrev = nTail;
+ m_pList[rTail.m_nPrev].m_nNext = nTail;
+ }
+
+ void unlink (sal_uInt16 nThis)
+ {
+ lru_entry &rThis = m_pList[nThis];
+
+ m_pList[rThis.m_nPrev].m_nNext = rThis.m_nNext;
+ m_pList[rThis.m_nNext].m_nPrev = rThis.m_nPrev;
+ rThis.m_nNext = nThis;
+ rThis.m_nPrev = nThis;
+ }
+
+public:
+ INetURLHistory_Impl();
+ INetURLHistory_Impl(const INetURLHistory_Impl&) = delete;
+ INetURLHistory_Impl& operator=(const INetURLHistory_Impl&) = delete;
+
+ /** putUrl/queryUrl.
+ */
+ void putUrl (const OUString &rUrl);
+ bool queryUrl (const OUString &rUrl) const;
+};
+
+INetURLHistory_Impl::INetURLHistory_Impl()
+{
+ initialize();
+}
+
+void INetURLHistory_Impl::initialize()
+{
+ m_aHead.initialize();
+
+ sal_uInt16 i, n = capacity();
+ for (i = 0; i < n; i++)
+ m_pHash[i].initialize(i);
+ for (i = 0; i < n; i++)
+ m_pList[i].initialize(i);
+ for (i = 1; i < n; i++)
+ backlink (m_aHead.m_nNext, i);
+}
+
+sal_uInt16 INetURLHistory_Impl::find (sal_uInt32 nHash) const
+{
+ sal_uInt16 l = 0;
+ sal_uInt16 r = capacity() - 1;
+ sal_uInt16 c = capacity();
+
+ while ((l < r) && (r < c))
+ {
+ sal_uInt16 m = (l + r) / 2;
+ if (m_pHash[m] == nHash)
+ return m;
+
+ if (m_pHash[m] < nHash)
+ l = m + 1;
+ else
+ r = m - 1;
+ }
+ return l;
+}
+
+void INetURLHistory_Impl::move (sal_uInt16 nSI, sal_uInt16 nDI)
+{
+ hash_entry e = m_pHash[nSI];
+ if (nSI < nDI)
+ {
+ // shift left.
+ memmove (
+ &m_pHash[nSI ],
+ &m_pHash[nSI + 1],
+ (nDI - nSI) * sizeof(hash_entry));
+ }
+ if (nSI > nDI)
+ {
+ // shift right.
+ memmove (
+ &m_pHash[nDI + 1],
+ &m_pHash[nDI ],
+ (nSI - nDI) * sizeof(hash_entry));
+ }
+ m_pHash[nDI] = e;
+}
+
+void INetURLHistory_Impl::putUrl (const OUString &rUrl)
+{
+ sal_uInt32 h = crc32 (rUrl);
+ sal_uInt16 k = find (h);
+ if ((k < capacity()) && (m_pHash[k] == h))
+ {
+ // Cache hit.
+ sal_uInt16 nMRU = m_pHash[k].m_nLru;
+ if (nMRU != m_aHead.m_nNext)
+ {
+ // Update LRU chain.
+ unlink (nMRU);
+ backlink (m_aHead.m_nNext, nMRU);
+
+ // Rotate LRU chain.
+ m_aHead.m_nNext = m_pList[m_aHead.m_nNext].m_nPrev;
+ }
+ }
+ else
+ {
+ // Cache miss. Obtain least recently used.
+ sal_uInt16 nLRU = m_pList[m_aHead.m_nNext].m_nPrev;
+
+ sal_uInt16 nSI = find (m_pList[nLRU].m_nHash);
+ if (nLRU != m_pHash[nSI].m_nLru)
+ {
+ // Update LRU chain.
+ nLRU = m_pHash[nSI].m_nLru;
+ unlink (nLRU);
+ backlink (m_aHead.m_nNext, nLRU);
+ }
+
+ // Rotate LRU chain.
+ m_aHead.m_nNext = m_pList[m_aHead.m_nNext].m_nPrev;
+
+ // Check source and destination.
+ sal_uInt16 nDI = std::min (k, sal_uInt16(capacity() - 1));
+ if (nSI < nDI && !(m_pHash[nDI] < h))
+ nDI -= 1;
+ if (nDI < nSI && m_pHash[nDI] < h)
+ nDI += 1;
+
+ // Assign data.
+ m_pList[m_aHead.m_nNext].m_nHash = m_pHash[nSI].m_nHash = h;
+ move (nSI, nDI);
+ }
+}
+
+bool INetURLHistory_Impl::queryUrl (const OUString &rUrl) const
+{
+ sal_uInt32 h = crc32 (rUrl);
+ sal_uInt16 k = find (h);
+ // true if cache hit
+ return (k < capacity()) && (m_pHash[k] == h);
+}
+
+INetURLHistory::INetURLHistory() : m_pImpl (new INetURLHistory_Impl())
+{
+}
+
+INetURLHistory::~INetURLHistory()
+{
+}
+
+/*
+ * GetOrCreate.
+ */
+INetURLHistory* INetURLHistory::GetOrCreate()
+{
+ static INetURLHistory instance;
+ return &instance;
+}
+
+void INetURLHistory::NormalizeUrl_Impl (INetURLObject &rUrl)
+{
+ switch (rUrl.GetProtocol())
+ {
+ case INetProtocol::File:
+ if (!INetURLObject::IsCaseSensitive())
+ {
+ OUString aPath (rUrl.GetURLPath(INetURLObject::DecodeMechanism::NONE).toAsciiLowerCase());
+ rUrl.SetURLPath (aPath, INetURLObject::EncodeMechanism::NotCanonical);
+ }
+ break;
+
+ case INetProtocol::Ftp:
+ if (!rUrl.HasPort())
+ rUrl.SetPort (INETHIST_DEF_FTP_PORT);
+ break;
+
+ case INetProtocol::Http:
+ if (!rUrl.HasPort())
+ rUrl.SetPort (INETHIST_DEF_HTTP_PORT);
+ if (!rUrl.HasURLPath())
+ rUrl.SetURLPath(u"/");
+ break;
+
+ case INetProtocol::Https:
+ if (!rUrl.HasPort())
+ rUrl.SetPort (INETHIST_DEF_HTTPS_PORT);
+ if (!rUrl.HasURLPath())
+ rUrl.SetURLPath(u"/");
+ break;
+
+ default:
+ break;
+ }
+}
+
+void INetURLHistory::PutUrl_Impl (const INetURLObject &rUrl)
+{
+ DBG_ASSERT (m_pImpl, "PutUrl_Impl(): no Implementation");
+ if (!m_pImpl)
+ return;
+
+ INetURLObject aHistUrl (rUrl);
+ NormalizeUrl_Impl (aHistUrl);
+
+ m_pImpl->putUrl (aHistUrl.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ Broadcast (INetURLHistoryHint (&rUrl));
+
+ if (aHistUrl.HasMark())
+ {
+ aHistUrl.SetURL (aHistUrl.GetURLNoMark(INetURLObject::DecodeMechanism::NONE),
+ INetURLObject::EncodeMechanism::NotCanonical);
+
+ m_pImpl->putUrl (aHistUrl.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ Broadcast (INetURLHistoryHint (&aHistUrl));
+ }
+}
+
+bool INetURLHistory::QueryUrl(std::u16string_view rUrl) const
+{
+ INetProtocol eProto = INetURLObject::CompareProtocolScheme (rUrl);
+ if (!QueryProtocol (eProto))
+ return false;
+ return QueryUrl_Impl( INetURLObject(rUrl) );
+}
+
+
+bool INetURLHistory::QueryUrl_Impl (INetURLObject rUrl) const
+{
+ DBG_ASSERT (m_pImpl, "QueryUrl_Impl(): no Implementation");
+ if (m_pImpl)
+ {
+ NormalizeUrl_Impl (rUrl);
+
+ return m_pImpl->queryUrl (rUrl.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ }
+ return false;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/inettype.cxx b/svl/source/misc/inettype.cxx
new file mode 100644
index 0000000000..da6c86f5d9
--- /dev/null
+++ b/svl/source/misc/inettype.cxx
@@ -0,0 +1,443 @@
+/* -*- 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 <array>
+
+#include <tools/debug.hxx>
+#include <tools/wldcrd.hxx>
+#include <tools/inetmime.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+#include <svl/inettype.hxx>
+
+namespace
+{
+
+struct MediaTypeEntry
+{
+ OUString m_pTypeName;
+ INetContentType m_eTypeID;
+};
+
+
+MediaTypeEntry const * seekEntry(OUString const & rTypeName,
+ MediaTypeEntry const * pMap, std::size_t nSize);
+
+/** A mapping from type names to type ids and extensions. Sorted by type
+ name.
+ */
+MediaTypeEntry const aStaticTypeNameMap[CONTENT_TYPE_LAST + 1]
+ = { { " ", CONTENT_TYPE_UNKNOWN },
+ { CONTENT_TYPE_STR_X_CNT_FSYSBOX, CONTENT_TYPE_X_CNT_FSYSBOX },
+ { CONTENT_TYPE_STR_X_CNT_FSYSFOLDER, CONTENT_TYPE_X_CNT_FSYSFOLDER },
+ { CONTENT_TYPE_STR_X_CNT_FSYSSPECIALFOLDER, CONTENT_TYPE_X_CNT_FSYSSPECIALFOLDER },
+ { CONTENT_TYPE_STR_APP_OCTSTREAM, CONTENT_TYPE_APP_OCTSTREAM },
+ { CONTENT_TYPE_STR_APP_PDF, CONTENT_TYPE_APP_PDF },
+ { CONTENT_TYPE_STR_APP_RTF, CONTENT_TYPE_APP_RTF },
+ { CONTENT_TYPE_STR_APP_VND_CALC, CONTENT_TYPE_APP_VND_CALC },
+ { CONTENT_TYPE_STR_APP_VND_CHART, CONTENT_TYPE_APP_VND_CHART },
+ { CONTENT_TYPE_STR_APP_VND_DRAW, CONTENT_TYPE_APP_VND_DRAW },
+ { CONTENT_TYPE_STR_APP_VND_IMAGE, CONTENT_TYPE_APP_VND_IMAGE },
+ { CONTENT_TYPE_STR_APP_VND_IMPRESS, CONTENT_TYPE_APP_VND_IMPRESS },
+ { CONTENT_TYPE_STR_APP_VND_IMPRESSPACKED, CONTENT_TYPE_APP_VND_IMPRESSPACKED },
+ { CONTENT_TYPE_STR_APP_VND_MAIL, CONTENT_TYPE_APP_VND_MAIL },
+ { CONTENT_TYPE_STR_APP_VND_MATH, CONTENT_TYPE_APP_VND_MATH },
+ { CONTENT_TYPE_STR_APP_VND_NEWS, CONTENT_TYPE_APP_VND_NEWS },
+ { CONTENT_TYPE_STR_APP_VND_OUTTRAY, CONTENT_TYPE_APP_VND_OUTTRAY },
+ { CONTENT_TYPE_STR_APP_VND_TEMPLATE, CONTENT_TYPE_APP_VND_TEMPLATE },
+ { CONTENT_TYPE_STR_APP_VND_WRITER, CONTENT_TYPE_APP_VND_WRITER },
+ { CONTENT_TYPE_STR_APP_VND_WRITER_GLOBAL, CONTENT_TYPE_APP_VND_WRITER_GLOBAL },
+ { CONTENT_TYPE_STR_APP_VND_WRITER_WEB, CONTENT_TYPE_APP_VND_WRITER_WEB },
+ { CONTENT_TYPE_STR_APP_VND_SUN_XML_CALC, CONTENT_TYPE_APP_VND_SUN_XML_CALC },
+ { CONTENT_TYPE_STR_APP_VND_SUN_XML_CHART, CONTENT_TYPE_APP_VND_SUN_XML_CHART },
+ { CONTENT_TYPE_STR_APP_VND_SUN_XML_DRAW, CONTENT_TYPE_APP_VND_SUN_XML_DRAW },
+ { CONTENT_TYPE_STR_APP_VND_SUN_XML_IMPRESS, CONTENT_TYPE_APP_VND_SUN_XML_IMPRESS },
+ { CONTENT_TYPE_STR_APP_VND_SUN_XML_IMPRESSPACKED, CONTENT_TYPE_APP_VND_SUN_XML_IMPRESSPACKED },
+ { CONTENT_TYPE_STR_APP_VND_SUN_XML_MATH, CONTENT_TYPE_APP_VND_SUN_XML_MATH },
+ { CONTENT_TYPE_STR_APP_VND_SUN_XML_WRITER, CONTENT_TYPE_APP_VND_SUN_XML_WRITER },
+ { CONTENT_TYPE_STR_APP_VND_SUN_XML_WRITER_GLOBAL, CONTENT_TYPE_APP_VND_SUN_XML_WRITER_GLOBAL },
+ { CONTENT_TYPE_STR_APP_FRAMESET, CONTENT_TYPE_APP_FRAMESET },
+ { CONTENT_TYPE_STR_APP_GALLERY, CONTENT_TYPE_APP_GALLERY },
+ { CONTENT_TYPE_STR_APP_GALLERY_THEME, CONTENT_TYPE_APP_GALLERY_THEME },
+ { CONTENT_TYPE_STR_APP_JAR, CONTENT_TYPE_APP_JAR },
+ { CONTENT_TYPE_STR_APP_MACRO, CONTENT_TYPE_APP_MACRO },
+ { CONTENT_TYPE_STR_APP_MSEXCEL, CONTENT_TYPE_APP_MSEXCEL },
+ { CONTENT_TYPE_STR_APP_MSEXCEL_TEMPL, CONTENT_TYPE_APP_MSEXCEL_TEMPL },
+ { CONTENT_TYPE_STR_APP_MSPPOINT, CONTENT_TYPE_APP_MSPPOINT },
+ { CONTENT_TYPE_STR_APP_MSPPOINT_TEMPL, CONTENT_TYPE_APP_MSPPOINT_TEMPL },
+ { CONTENT_TYPE_STR_APP_MSWORD, CONTENT_TYPE_APP_MSWORD },
+ { CONTENT_TYPE_STR_APP_MSWORD_TEMPL, CONTENT_TYPE_APP_MSWORD_TEMPL },
+ { CONTENT_TYPE_STR_APP_STARCALC, CONTENT_TYPE_APP_STARCALC },
+ { CONTENT_TYPE_STR_APP_STARCHART, CONTENT_TYPE_APP_STARCHART },
+ { CONTENT_TYPE_STR_APP_STARDRAW, CONTENT_TYPE_APP_STARDRAW },
+ { CONTENT_TYPE_STR_APP_STARHELP, CONTENT_TYPE_APP_STARHELP },
+ { CONTENT_TYPE_STR_APP_STARIMAGE, CONTENT_TYPE_APP_STARIMAGE },
+ { CONTENT_TYPE_STR_APP_STARIMPRESS, CONTENT_TYPE_APP_STARIMPRESS },
+ { CONTENT_TYPE_STR_APP_STARMAIL_SDM, CONTENT_TYPE_APP_STARMAIL_SDM },
+ { CONTENT_TYPE_STR_APP_STARMAIL_SMD, CONTENT_TYPE_APP_STARMAIL_SMD },
+ { CONTENT_TYPE_STR_APP_STARMATH, CONTENT_TYPE_APP_STARMATH },
+ { CONTENT_TYPE_STR_APP_STARWRITER, CONTENT_TYPE_APP_STARWRITER },
+ { CONTENT_TYPE_STR_APP_STARWRITER_GLOB, CONTENT_TYPE_APP_STARWRITER_GLOB },
+ { CONTENT_TYPE_STR_APP_CDE_CALENDAR_APP, CONTENT_TYPE_APP_CDE_CALENDAR_APP },
+ { CONTENT_TYPE_STR_APP_ZIP, CONTENT_TYPE_APP_ZIP },
+ { CONTENT_TYPE_STR_AUDIO_AIFF, CONTENT_TYPE_AUDIO_AIFF },
+ { CONTENT_TYPE_STR_AUDIO_BASIC, CONTENT_TYPE_AUDIO_BASIC },
+ { CONTENT_TYPE_STR_AUDIO_MIDI, CONTENT_TYPE_AUDIO_MIDI },
+ { CONTENT_TYPE_STR_AUDIO_VORBIS, CONTENT_TYPE_AUDIO_VORBIS },
+ { CONTENT_TYPE_STR_AUDIO_WAV, CONTENT_TYPE_AUDIO_WAV },
+ { CONTENT_TYPE_STR_AUDIO_WEBM, CONTENT_TYPE_AUDIO_WEBM },
+ { CONTENT_TYPE_STR_IMAGE_GENERIC, CONTENT_TYPE_IMAGE_GENERIC },
+ { CONTENT_TYPE_STR_IMAGE_GIF, CONTENT_TYPE_IMAGE_GIF },
+ { CONTENT_TYPE_STR_IMAGE_JPEG, CONTENT_TYPE_IMAGE_JPEG },
+ { CONTENT_TYPE_STR_IMAGE_PCX, CONTENT_TYPE_IMAGE_PCX },
+ { CONTENT_TYPE_STR_IMAGE_PNG, CONTENT_TYPE_IMAGE_PNG },
+ { CONTENT_TYPE_STR_IMAGE_TIFF, CONTENT_TYPE_IMAGE_TIFF },
+ { CONTENT_TYPE_STR_IMAGE_BMP, CONTENT_TYPE_IMAGE_BMP },
+ { CONTENT_TYPE_STR_INET_MSG_RFC822, CONTENT_TYPE_INET_MESSAGE_RFC822 },
+ { CONTENT_TYPE_STR_INET_MULTI_ALTERNATIVE, CONTENT_TYPE_INET_MULTIPART_ALTERNATIVE },
+ { CONTENT_TYPE_STR_INET_MULTI_DIGEST, CONTENT_TYPE_INET_MULTIPART_DIGEST },
+ { CONTENT_TYPE_STR_INET_MULTI_MIXED, CONTENT_TYPE_INET_MULTIPART_MIXED },
+ { CONTENT_TYPE_STR_INET_MULTI_PARALLEL, CONTENT_TYPE_INET_MULTIPART_PARALLEL },
+ { CONTENT_TYPE_STR_INET_MULTI_RELATED, CONTENT_TYPE_INET_MULTIPART_RELATED },
+ { CONTENT_TYPE_STR_TEXT_ICALENDAR, CONTENT_TYPE_TEXT_ICALENDAR },
+ { CONTENT_TYPE_STR_TEXT_HTML, CONTENT_TYPE_TEXT_HTML },
+ { CONTENT_TYPE_STR_TEXT_PLAIN, CONTENT_TYPE_TEXT_PLAIN },
+ { CONTENT_TYPE_STR_TEXT_XMLICALENDAR, CONTENT_TYPE_TEXT_XMLICALENDAR },
+ { CONTENT_TYPE_STR_TEXT_URL, CONTENT_TYPE_TEXT_URL },
+ { CONTENT_TYPE_STR_TEXT_VCALENDAR, CONTENT_TYPE_TEXT_VCALENDAR },
+ { CONTENT_TYPE_STR_TEXT_VCARD, CONTENT_TYPE_TEXT_VCARD },
+ { CONTENT_TYPE_STR_VIDEO_MSVIDEO, CONTENT_TYPE_VIDEO_MSVIDEO },
+ { CONTENT_TYPE_STR_VIDEO_THEORA, CONTENT_TYPE_VIDEO_THEORA },
+ { CONTENT_TYPE_STR_VIDEO_VDO, CONTENT_TYPE_VIDEO_VDO },
+ { CONTENT_TYPE_STR_VIDEO_WEBM, CONTENT_TYPE_VIDEO_WEBM },
+ { CONTENT_TYPE_STR_X_STARMAIL, CONTENT_TYPE_X_STARMAIL },
+ { CONTENT_TYPE_STR_X_VRML, CONTENT_TYPE_X_VRML }
+};
+
+
+/** A mapping from extensions to type IDs. Sorted by extension.
+ */
+MediaTypeEntry const aStaticExtensionMap[]
+ = { { "aif", CONTENT_TYPE_AUDIO_AIFF },
+ { "aiff", CONTENT_TYPE_AUDIO_AIFF },
+ { "appt", CONTENT_TYPE_APP_CDE_CALENDAR_APP },
+ { "au", CONTENT_TYPE_AUDIO_BASIC },
+ { "avi", CONTENT_TYPE_VIDEO_MSVIDEO },
+ { "bmp", CONTENT_TYPE_IMAGE_BMP },
+ { "cgm", CONTENT_TYPE_IMAGE_GENERIC },
+ { "doc", CONTENT_TYPE_APP_MSWORD },
+ { "dot", CONTENT_TYPE_APP_MSWORD_TEMPL },
+ { "dxf", CONTENT_TYPE_IMAGE_GENERIC },
+ { "eps", CONTENT_TYPE_IMAGE_GENERIC },
+ { "gal", CONTENT_TYPE_APP_GALLERY },
+ { "gif", CONTENT_TYPE_IMAGE_GIF },
+ { "htm", CONTENT_TYPE_TEXT_HTML },
+ { "html", CONTENT_TYPE_TEXT_HTML },
+ { "ics", CONTENT_TYPE_TEXT_ICALENDAR },
+ { "jar", CONTENT_TYPE_APP_JAR },
+ { "jpeg", CONTENT_TYPE_IMAGE_JPEG },
+ { "jpg", CONTENT_TYPE_IMAGE_JPEG },
+ { "met", CONTENT_TYPE_IMAGE_GENERIC },
+ { "mid", CONTENT_TYPE_AUDIO_MIDI },
+ { "midi", CONTENT_TYPE_AUDIO_MIDI },
+ { "ogg", CONTENT_TYPE_AUDIO_VORBIS },
+ { "pbm", CONTENT_TYPE_IMAGE_GENERIC },
+ { "pcd", CONTENT_TYPE_IMAGE_GENERIC },
+ { "pct", CONTENT_TYPE_IMAGE_GENERIC },
+ { "pcx", CONTENT_TYPE_IMAGE_PCX },
+ { "pdf", CONTENT_TYPE_APP_PDF },
+ { "pgm", CONTENT_TYPE_IMAGE_GENERIC },
+ { "png", CONTENT_TYPE_IMAGE_PNG },
+ { "pot", CONTENT_TYPE_APP_MSPPOINT_TEMPL },
+ { "ppm", CONTENT_TYPE_IMAGE_GENERIC },
+ { "ppt", CONTENT_TYPE_APP_MSPPOINT },
+ { "psd", CONTENT_TYPE_IMAGE_GENERIC },
+ { "ras", CONTENT_TYPE_IMAGE_GENERIC },
+ { "rtf", CONTENT_TYPE_APP_RTF },
+ { "sda", CONTENT_TYPE_APP_VND_DRAW },
+ { "sdc", CONTENT_TYPE_APP_VND_CALC },
+ { "sdd", CONTENT_TYPE_APP_VND_IMPRESS },
+ { "sdm", CONTENT_TYPE_APP_VND_MAIL },
+ { "sdp", CONTENT_TYPE_APP_VND_IMPRESSPACKED },
+ { "sds", CONTENT_TYPE_APP_VND_CHART },
+ { "sdw", CONTENT_TYPE_APP_VND_WRITER },
+ { "sd~", CONTENT_TYPE_X_STARMAIL },
+ { "sfs", CONTENT_TYPE_APP_FRAMESET },
+ { "sgl", CONTENT_TYPE_APP_VND_WRITER_GLOBAL },
+ { "sim", CONTENT_TYPE_APP_VND_IMAGE },
+ { "smd", CONTENT_TYPE_APP_STARMAIL_SMD }, //CONTENT_TYPE_X_STARMAIL
+ { "smf", CONTENT_TYPE_APP_VND_MATH },
+ { "svh", CONTENT_TYPE_APP_STARHELP },
+ { "svm", CONTENT_TYPE_IMAGE_GENERIC },
+ { "sxc", CONTENT_TYPE_APP_VND_SUN_XML_CALC },
+ { "sxd", CONTENT_TYPE_APP_VND_SUN_XML_DRAW },
+ { "sxg", CONTENT_TYPE_APP_VND_SUN_XML_WRITER_GLOBAL },
+ { "sxi", CONTENT_TYPE_APP_VND_SUN_XML_IMPRESS },
+ { "sxm", CONTENT_TYPE_APP_VND_SUN_XML_MATH },
+ { "sxp", CONTENT_TYPE_APP_VND_SUN_XML_IMPRESSPACKED },
+ { "sxs", CONTENT_TYPE_APP_VND_SUN_XML_CHART },
+ { "sxw", CONTENT_TYPE_APP_VND_SUN_XML_WRITER },
+ { "tga", CONTENT_TYPE_IMAGE_GENERIC },
+ { "thm", CONTENT_TYPE_APP_GALLERY_THEME },
+ { "tif", CONTENT_TYPE_IMAGE_TIFF },
+ { "tiff", CONTENT_TYPE_IMAGE_TIFF },
+ { "txt", CONTENT_TYPE_TEXT_PLAIN },
+ { "url", CONTENT_TYPE_TEXT_URL },
+ { "vcf", CONTENT_TYPE_TEXT_VCARD },
+ { "vcs", CONTENT_TYPE_TEXT_VCALENDAR },
+ { "vdo", CONTENT_TYPE_VIDEO_VDO },
+ { "vor", CONTENT_TYPE_APP_VND_TEMPLATE },
+ { "wav", CONTENT_TYPE_AUDIO_WAV },
+ { "webm", CONTENT_TYPE_VIDEO_WEBM },
+ { "wmf", CONTENT_TYPE_IMAGE_GENERIC },
+ { "wrl", CONTENT_TYPE_X_VRML },
+ { "xbm", CONTENT_TYPE_IMAGE_GENERIC },
+ { "xcs", CONTENT_TYPE_TEXT_XMLICALENDAR },
+ { "xls", CONTENT_TYPE_APP_MSEXCEL },
+ { "xlt", CONTENT_TYPE_APP_MSEXCEL_TEMPL },
+ { "xlw", CONTENT_TYPE_APP_MSEXCEL },
+ { "xpm", CONTENT_TYPE_IMAGE_GENERIC },
+ { "zip", CONTENT_TYPE_APP_ZIP } };
+
+}
+
+
+// seekEntry
+
+
+namespace
+{
+
+MediaTypeEntry const * seekEntry(OUString const & rTypeName,
+ MediaTypeEntry const * pMap, std::size_t nSize)
+{
+#if defined DBG_UTIL
+ for (std::size_t i = 0; i < nSize - 1; ++i)
+ DBG_ASSERT(
+ pMap[i].m_pTypeName < pMap[i + 1].m_pTypeName,
+ "seekEntry(): Bad map");
+#endif
+
+ std::size_t nLow = 0;
+ std::size_t nHigh = nSize;
+ while (nLow != nHigh)
+ {
+ std::size_t nMiddle = (nLow + nHigh) / 2;
+ MediaTypeEntry const * pEntry = pMap + nMiddle;
+ sal_Int32 nCmp = rTypeName.compareToIgnoreAsciiCase(pEntry->m_pTypeName);
+ if (nCmp < 0)
+ nHigh = nMiddle;
+ else if (nCmp == 0)
+ return pEntry;
+
+ else
+ nLow = nMiddle + 1;
+ }
+ return nullptr;
+}
+
+}
+
+// static
+INetContentType INetContentTypes::GetContentType(OUString const & rTypeName)
+{
+ OUString aType;
+ OUString aSubType;
+ if (parse(rTypeName, aType, aSubType))
+ {
+ aType += "/" + aSubType;
+ MediaTypeEntry const * pEntry = seekEntry(aType, aStaticTypeNameMap,
+ CONTENT_TYPE_LAST + 1);
+ return pEntry ? pEntry->m_eTypeID : CONTENT_TYPE_UNKNOWN;
+ }
+ else
+ return rTypeName.equalsIgnoreAsciiCase(CONTENT_TYPE_STR_X_STARMAIL) ?
+ CONTENT_TYPE_X_STARMAIL : CONTENT_TYPE_UNKNOWN;
+ // the content type "x-starmail" has no sub type
+}
+
+//static
+OUString INetContentTypes::GetContentType(INetContentType eTypeID)
+{
+ static std::array<OUString, CONTENT_TYPE_LAST + 1> aMap = []()
+ {
+ std::array<OUString, CONTENT_TYPE_LAST + 1> tmp;
+ for (std::size_t i = 0; i <= CONTENT_TYPE_LAST; ++i)
+ tmp[aStaticTypeNameMap[i].m_eTypeID] = aStaticTypeNameMap[i].m_pTypeName;
+ tmp[CONTENT_TYPE_UNKNOWN] = CONTENT_TYPE_STR_APP_OCTSTREAM;
+ tmp[CONTENT_TYPE_TEXT_PLAIN] = CONTENT_TYPE_STR_TEXT_PLAIN +
+ "; charset=iso-8859-1";
+ return tmp;
+ }();
+
+ OUString aTypeName = eTypeID <= CONTENT_TYPE_LAST ? aMap[eTypeID]
+ : OUString();
+ if (aTypeName.isEmpty())
+ {
+ OSL_FAIL("INetContentTypes::GetContentType(): Bad ID");
+ return CONTENT_TYPE_STR_APP_OCTSTREAM;
+ }
+ return aTypeName;
+}
+
+//static
+INetContentType INetContentTypes::GetContentType4Extension(OUString const & rExtension)
+{
+ MediaTypeEntry const * pEntry = seekEntry(rExtension, aStaticExtensionMap,
+ SAL_N_ELEMENTS(aStaticExtensionMap));
+ if (pEntry)
+ return pEntry->m_eTypeID;
+ return CONTENT_TYPE_APP_OCTSTREAM;
+}
+
+//static
+INetContentType INetContentTypes::GetContentTypeFromURL(std::u16string_view rURL)
+{
+ INetContentType eTypeID = CONTENT_TYPE_UNKNOWN;
+ std::size_t nIdx{ 0 };
+ OUString aToken( o3tl::getToken(rURL, u':', nIdx) );
+ if (!aToken.isEmpty())
+ {
+ if (aToken.equalsIgnoreAsciiCase(INETTYPE_URL_PROT_FILE))
+ if (rURL[ rURL.size() - 1 ] == '/') // folder
+ if (rURL.size() > RTL_CONSTASCII_LENGTH("file:///"))
+ if (WildCard(u"*/{*}/").Matches(rURL)) // special folder
+ eTypeID = CONTENT_TYPE_X_CNT_FSYSSPECIALFOLDER;
+ else
+ // drive? -> "file:///?|/"
+ if (rURL.size() == 11
+ && rURL[ rURL.size() - 2 ] == '|')
+ {
+ // Drives need further processing, because of
+ // dynamic type according to underlying volume,
+ // which cannot be determined here.
+ }
+ else // normal folder
+ eTypeID = CONTENT_TYPE_X_CNT_FSYSFOLDER;
+ else // file system root
+ eTypeID = CONTENT_TYPE_X_CNT_FSYSBOX;
+ else // file
+ {
+ //@@@
+ }
+ else if (aToken.equalsIgnoreAsciiCase(INETTYPE_URL_PROT_HTTP)
+ || aToken.equalsIgnoreAsciiCase(INETTYPE_URL_PROT_HTTPS))
+ eTypeID = CONTENT_TYPE_TEXT_HTML;
+ else if (aToken.equalsIgnoreAsciiCase(INETTYPE_URL_PROT_PRIVATE))
+ {
+ aToken = o3tl::getToken(rURL, u'/', nIdx);
+ if (aToken == "factory")
+ {
+ aToken = o3tl::getToken(rURL, u'/', nIdx);
+ if (aToken == "swriter")
+ {
+ aToken = o3tl::getToken(rURL, u'/', nIdx);
+ eTypeID = aToken == "web" ?
+ CONTENT_TYPE_APP_VND_WRITER_WEB :
+ aToken == "GlobalDocument" ?
+ CONTENT_TYPE_APP_VND_WRITER_GLOBAL :
+ CONTENT_TYPE_APP_VND_WRITER;
+ }
+ else if (aToken == "scalc")
+ eTypeID = CONTENT_TYPE_APP_VND_CALC;
+ else if (aToken == "sdraw")
+ eTypeID = CONTENT_TYPE_APP_VND_DRAW;
+ else if (aToken == "simpress")
+ eTypeID = CONTENT_TYPE_APP_VND_IMPRESS;
+ else if (aToken == "schart")
+ eTypeID = CONTENT_TYPE_APP_VND_CHART;
+ else if (aToken == "simage")
+ eTypeID = CONTENT_TYPE_APP_VND_IMAGE;
+ else if (aToken == "smath")
+ eTypeID = CONTENT_TYPE_APP_VND_MATH;
+ else if (aToken == "frameset")
+ eTypeID = CONTENT_TYPE_APP_FRAMESET;
+ }
+ else if (aToken == "helpid")
+ eTypeID = CONTENT_TYPE_APP_STARHELP;
+ }
+ else if (aToken.equalsIgnoreAsciiCase(INETTYPE_URL_PROT_MAILTO))
+ eTypeID = CONTENT_TYPE_APP_VND_OUTTRAY;
+ else if (aToken.equalsIgnoreAsciiCase(INETTYPE_URL_PROT_MACRO))
+ eTypeID = CONTENT_TYPE_APP_MACRO;
+ else if (aToken.equalsIgnoreAsciiCase(INETTYPE_URL_PROT_DATA))
+ {
+ aToken = o3tl::getToken(rURL, u',', nIdx);
+ eTypeID = GetContentType(aToken);
+ }
+ }
+ if (eTypeID == CONTENT_TYPE_UNKNOWN)
+ {
+ OUString aExtension;
+ if (GetExtensionFromURL(rURL, aExtension))
+ eTypeID = GetContentType4Extension(aExtension);
+ }
+ return eTypeID;
+}
+
+//static
+bool INetContentTypes::GetExtensionFromURL(std::u16string_view rURL,
+ OUString & rExtension)
+{
+ size_t nSlashPos = 0;
+ size_t i = 0;
+ while (i != std::u16string_view::npos)
+ {
+ nSlashPos = i;
+ i = rURL.find('/', i + 1);
+ }
+ if (nSlashPos != 0)
+ {
+ size_t nLastDotPos = i = rURL.find('.', nSlashPos);
+ while (i != std::u16string_view::npos)
+ {
+ nLastDotPos = i;
+ i = rURL.find('.', i + 1);
+ }
+ if (nLastDotPos >- 0)
+ rExtension = rURL.substr(nLastDotPos + 1);
+ return true;
+ }
+ return false;
+}
+
+bool INetContentTypes::parse(
+ OUString const & rMediaType, OUString & rType, OUString & rSubType,
+ INetContentTypeParameterList * pParameters)
+{
+ sal_Unicode const * b = rMediaType.getStr();
+ sal_Unicode const * e = b + rMediaType.getLength();
+ OUString t;
+ OUString s;
+ INetContentTypeParameterList p;
+ if (INetMIME::scanContentType(rMediaType, &t, &s, pParameters == nullptr ? nullptr : &p) == e) {
+ rType = t;
+ rSubType = s;
+ if (pParameters != nullptr) {
+ *pParameters = p;
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/lngmisc.cxx b/svl/source/misc/lngmisc.cxx
new file mode 100644
index 0000000000..7ccf0aed72
--- /dev/null
+++ b/svl/source/misc/lngmisc.cxx
@@ -0,0 +1,132 @@
+/* -*- 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 <svl/lngmisc.hxx>
+
+#include <comphelper/string.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <tools/debug.hxx>
+
+namespace linguistic
+{
+ sal_Int32 GetNumControlChars(std::u16string_view rTxt)
+ {
+ sal_Int32 nCnt = 0;
+ for (size_t i = 0; i < rTxt.size(); ++i)
+ if (IsControlChar(rTxt[i]))
+ ++nCnt;
+ return nCnt;
+ }
+
+ bool RemoveHyphens(OUString &rTxt)
+ {
+ sal_Int32 n = rTxt.getLength();
+ rTxt = rTxt.replaceAll(OUStringChar(SVT_SOFT_HYPHEN), "");
+ rTxt = rTxt.replaceAll(OUStringChar(SVT_HARD_HYPHEN), "");
+ return n != rTxt.getLength();
+ }
+
+ bool RemoveControlChars(OUString &rTxt)
+ {
+ sal_Int32 nSize = rTxt.getLength() - GetNumControlChars(rTxt);
+ if(nSize == rTxt.getLength())
+ return false;
+
+ OUStringBuffer aBuf(nSize);
+ aBuf.setLength(nSize);
+ for (sal_Int32 i = 0, j = 0; i < rTxt.getLength() && j < nSize; ++i)
+ if (!IsControlChar(rTxt[i]))
+ aBuf[j++] = rTxt[i];
+
+ rTxt = aBuf.makeStringAndClear();
+ DBG_ASSERT(rTxt.getLength() == nSize, "GetNumControlChars returned a different number of control characters than were actually removed.");
+
+ return true;
+ }
+
+ bool ReplaceControlChars(OUString &rTxt)
+ {
+ // non breaking field character
+ static const char CH_TXTATR_INWORD = static_cast<char>(0x02);
+
+ // the resulting string looks like this:
+ // 1. non breaking field characters get removed
+ // 2. remaining control characters will be replaced by ' '
+
+ if (GetNumControlChars(rTxt) == 0)
+ return false;
+
+ sal_Int32 n = rTxt.getLength();
+
+ OUStringBuffer aBuf(n);
+ aBuf.setLength(n);
+
+ sal_Int32 j = 0;
+ for (sal_Int32 i = 0; i < n && j < n; ++i)
+ {
+ if (CH_TXTATR_INWORD == rTxt[i])
+ continue;
+
+ aBuf[j++] = IsControlChar(rTxt[i]) ? ' ' : rTxt[i];
+ }
+
+ aBuf.setLength(j);
+ rTxt = aBuf.makeStringAndClear();
+
+ return true;
+ }
+
+ OUString GetThesaurusReplaceText(const OUString &rText)
+ {
+ // The strings for synonyms returned by the thesaurus sometimes have some
+ // explanation text put in between '(' and ')' or a trailing '*'.
+ // These parts should not be put in the ReplaceEdit Text that may get
+ // inserted into the document. Thus we strip them from the text.
+
+ OUString aText(rText);
+
+ sal_Int32 nPos = aText.indexOf('(');
+ while (nPos >= 0)
+ {
+ sal_Int32 nEnd = aText.indexOf(')', nPos);
+ if (nEnd >= 0)
+ {
+ OUStringBuffer aTextBuf(aText);
+ aTextBuf.remove(nPos, nEnd - nPos + 1);
+ aText = aTextBuf.makeStringAndClear();
+ }
+ else
+ break;
+ nPos = aText.indexOf('(');
+ }
+
+ nPos = aText.indexOf('*');
+ if(nPos == 0)
+ return OUString();
+ else if(nPos > 0)
+ aText = aText.copy(0, nPos);
+
+ // remove any possible remaining ' ' that may confuse the thesaurus
+ // when it gets called with the text
+ return comphelper::string::strip(aText, ' ');
+ }
+} // namespace linguistic
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/lockfilecommon.cxx b/svl/source/misc/lockfilecommon.cxx
new file mode 100644
index 0000000000..bcf568b70a
--- /dev/null
+++ b/svl/source/misc/lockfilecommon.cxx
@@ -0,0 +1,243 @@
+/* -*- 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 <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/io/WrongFormatException.hpp>
+
+#include <osl/time.h>
+#include <osl/security.hxx>
+#include <osl/socket.hxx>
+#include <osl/file.hxx>
+#include <o3tl/enumrange.hxx>
+#include <o3tl/sprintf.hxx>
+
+#include <rtl/ustring.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <tools/urlobj.hxx>
+#include <unotools/bootstrap.hxx>
+
+#include <unotools/useroptions.hxx>
+
+#include <salhelper/linkhelper.hxx>
+
+#include <svl/lockfilecommon.hxx>
+#include <utility>
+
+using namespace ::com::sun::star;
+
+namespace svt {
+
+
+LockFileCommon::LockFileCommon(OUString aLockFileURL)
+ : m_aURL(std::move(aLockFileURL))
+{
+}
+
+LockFileCommon::~LockFileCommon()
+{
+}
+
+
+const OUString& LockFileCommon::GetURL() const
+{
+ return m_aURL;
+}
+
+
+void LockFileCommon::SetURL(const OUString& aURL)
+{
+ m_aURL = aURL;
+}
+
+
+OUString LockFileCommon::GenerateOwnLockFileURL(
+ std::u16string_view aOrigURL, std::u16string_view aPrefix)
+{
+ INetURLObject aURL = ResolveLinks(INetURLObject(aOrigURL));
+ aURL.setName(Concat2View(aPrefix + aURL.GetLastName() + "%23" /*'#'*/));
+ return aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+}
+
+
+INetURLObject LockFileCommon::ResolveLinks( const INetURLObject& aDocURL )
+{
+ if ( aDocURL.HasError() )
+ throw lang::IllegalArgumentException();
+
+ OUString aURLToCheck = aDocURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+
+ // there is currently no UCB functionality to resolve the symbolic links;
+ // since the lock files are used only for local file systems the osl
+ // functionality is used directly
+ salhelper::LinkResolver aResolver(osl_FileStatus_Mask_FileName);
+ osl::FileBase::RC eStatus = aResolver.fetchFileStatus(aURLToCheck);
+ if (eStatus == osl::FileBase::E_None)
+ aURLToCheck = aResolver.m_aStatus.getFileURL();
+ else if (eStatus == osl::FileBase::E_MULTIHOP)
+ {
+ // do not allow too deep links
+ throw io::IOException();
+ }
+
+ return INetURLObject( aURLToCheck );
+}
+
+
+void LockFileCommon::ParseList( const uno::Sequence< sal_Int8 >& aBuffer, std::vector< LockFileEntry > & aResult )
+{
+ sal_Int32 nCurPos = 0;
+ while ( nCurPos < aBuffer.getLength() )
+ {
+ aResult.push_back( ParseEntry( aBuffer, nCurPos ) );
+ }
+}
+
+
+LockFileEntry LockFileCommon::ParseEntry( const uno::Sequence< sal_Int8 >& aBuffer, sal_Int32& io_nCurPos )
+{
+ LockFileEntry aResult;
+
+ for ( LockFileComponent nInd : o3tl::enumrange<LockFileComponent>() )
+ {
+ aResult[nInd] = ParseName( aBuffer, io_nCurPos );
+ if ( io_nCurPos >= aBuffer.getLength()
+ || ( nInd < LockFileComponent::LAST && aBuffer[io_nCurPos++] != ',' )
+ || ( nInd == LockFileComponent::LAST && aBuffer[io_nCurPos++] != ';' ) )
+ throw io::WrongFormatException();
+ }
+
+ return aResult;
+}
+
+
+OUString LockFileCommon::ParseName( const uno::Sequence< sal_Int8 >& aBuffer, sal_Int32& io_nCurPos )
+{
+ OStringBuffer aResult(128);
+ bool bHaveName = false;
+ bool bEscape = false;
+
+ while( !bHaveName )
+ {
+ if ( io_nCurPos >= aBuffer.getLength() )
+ throw io::WrongFormatException();
+
+ if ( bEscape )
+ {
+ if ( aBuffer[io_nCurPos] != ',' && aBuffer[io_nCurPos] != ';' && aBuffer[io_nCurPos] != '\\' )
+ throw io::WrongFormatException();
+
+ aResult.append( static_cast<char>(aBuffer[io_nCurPos]) );
+
+ bEscape = false;
+ io_nCurPos++;
+ }
+ else if ( aBuffer[io_nCurPos] == ',' || aBuffer[io_nCurPos] == ';' )
+ bHaveName = true;
+ else
+ {
+ if ( aBuffer[io_nCurPos] == '\\' )
+ bEscape = true;
+ else
+ aResult.append( static_cast<char>(aBuffer[io_nCurPos]) );
+
+ io_nCurPos++;
+ }
+ }
+
+ return OStringToOUString( aResult, RTL_TEXTENCODING_UTF8 );
+}
+
+
+OUString LockFileCommon::EscapeCharacters( const OUString& aSource )
+{
+ OUStringBuffer aBuffer(aSource.getLength()*2);
+ const sal_Unicode* pStr = aSource.getStr();
+ for ( sal_Int32 nInd = 0; nInd < aSource.getLength() && pStr[nInd] != 0; nInd++ )
+ {
+ if ( pStr[nInd] == '\\' || pStr[nInd] == ';' || pStr[nInd] == ',' )
+ aBuffer.append( '\\' );
+ aBuffer.append( pStr[nInd] );
+ }
+
+ return aBuffer.makeStringAndClear();
+}
+
+
+OUString LockFileCommon::GetOOOUserName()
+{
+ SvtUserOptions aUserOpt;
+ OUString aName = aUserOpt.GetFirstName();
+ if ( !aName.isEmpty() )
+ aName += " ";
+ aName += aUserOpt.GetLastName();
+
+ return aName;
+}
+
+
+OUString LockFileCommon::GetCurrentLocalTime()
+{
+ OUString aTime;
+
+ TimeValue aSysTime;
+ if ( osl_getSystemTime( &aSysTime ) )
+ {
+ TimeValue aLocTime;
+ if ( osl_getLocalTimeFromSystemTime( &aSysTime, &aLocTime ) )
+ {
+ oslDateTime aDateTime;
+ if ( osl_getDateTimeFromTimeValue( &aLocTime, &aDateTime ) )
+ {
+ char pDateTime[sizeof("65535.65535.-32768 65535:65535")];
+ // reserve enough space for hypothetical max length
+ o3tl::sprintf( pDateTime, "%02" SAL_PRIuUINT32 ".%02" SAL_PRIuUINT32 ".%4" SAL_PRIdINT32 " %02" SAL_PRIuUINT32 ":%02" SAL_PRIuUINT32, sal_uInt32(aDateTime.Day), sal_uInt32(aDateTime.Month), sal_Int32(aDateTime.Year), sal_uInt32(aDateTime.Hours), sal_uInt32(aDateTime.Minutes) );
+ aTime = OUString::createFromAscii( pDateTime );
+ }
+ }
+ }
+
+ return aTime;
+}
+
+
+LockFileEntry LockFileCommon::GenerateOwnEntry()
+{
+ LockFileEntry aResult;
+
+ aResult[LockFileComponent::OOOUSERNAME] = GetOOOUserName();
+
+ ::osl::Security aSecurity;
+ aSecurity.getUserName( aResult[LockFileComponent::SYSUSERNAME] );
+
+ aResult[LockFileComponent::LOCALHOST] = ::osl::SocketAddr::getLocalHostname();
+
+ aResult[LockFileComponent::EDITTIME] = GetCurrentLocalTime();
+
+ ::utl::Bootstrap::locateUserInstallation( aResult[LockFileComponent::USERURL] );
+
+
+ return aResult;
+}
+
+} // namespace svt
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/msodocumentlockfile.cxx b/svl/source/misc/msodocumentlockfile.cxx
new file mode 100644
index 0000000000..dab0486e43
--- /dev/null
+++ b/svl/source/misc/msodocumentlockfile.cxx
@@ -0,0 +1,269 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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/.
+ */
+
+#include <svl/msodocumentlockfile.hxx>
+#include <algorithm>
+#include <ucbhelper/content.hxx>
+#include <comphelper/processfactory.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+
+namespace svt
+{
+namespace
+{
+bool isWordFormat(std::u16string_view sExt)
+{
+ return o3tl::equalsIgnoreAsciiCase(sExt, u"DOC") || o3tl::equalsIgnoreAsciiCase(sExt, u"DOCX")
+ || o3tl::equalsIgnoreAsciiCase(sExt, u"RTF")
+ || o3tl::equalsIgnoreAsciiCase(sExt, u"ODT");
+}
+
+bool isExcelFormat(std::u16string_view sExt)
+{
+ return //sExt.equalsIgnoreAsciiCase("XLS") || // MSO does not create lockfile for XLS
+ o3tl::equalsIgnoreAsciiCase(sExt, u"XLSX") || o3tl::equalsIgnoreAsciiCase(sExt, u"ODS");
+}
+
+bool isPowerPointFormat(std::u16string_view sExt)
+{
+ return o3tl::equalsIgnoreAsciiCase(sExt, u"PPTX") || o3tl::equalsIgnoreAsciiCase(sExt, u"PPT")
+ || o3tl::equalsIgnoreAsciiCase(sExt, u"ODP");
+}
+
+// Need to generate different lock file name for MSO.
+OUString GenerateMSOLockFileURL(std::u16string_view aOrigURL)
+{
+ INetURLObject aURL = LockFileCommon::ResolveLinks(INetURLObject(aOrigURL));
+
+ // For text documents MSO Word cuts some of the first characters of the file name
+ OUString sFileName = aURL.GetLastName();
+ const OUString sExt = aURL.GetFileExtension();
+
+ if (isWordFormat(sExt))
+ {
+ const sal_Int32 nFileNameLength = sFileName.getLength() - sExt.getLength() - 1;
+ if (nFileNameLength >= 8)
+ sFileName = sFileName.copy(2);
+ else if (nFileNameLength == 7)
+ sFileName = sFileName.copy(1);
+ }
+ aURL.setName(Concat2View("~$" + sFileName));
+ return aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+}
+}
+
+// static
+MSODocumentLockFile::AppType MSODocumentLockFile::getAppType(std::u16string_view sOrigURL)
+{
+ AppType eResult = AppType::PowerPoint;
+ INetURLObject aDocURL = LockFileCommon::ResolveLinks(INetURLObject(sOrigURL));
+ const OUString sExt = aDocURL.GetFileExtension();
+ if (isWordFormat(sExt))
+ eResult = AppType::Word;
+ else if (isExcelFormat(sExt))
+ eResult = AppType::Excel;
+
+ return eResult;
+}
+
+MSODocumentLockFile::MSODocumentLockFile(std::u16string_view aOrigURL)
+ : GenDocumentLockFile(GenerateMSOLockFileURL(aOrigURL))
+ , m_eAppType(getAppType(aOrigURL))
+{
+}
+
+MSODocumentLockFile::~MSODocumentLockFile() {}
+
+void MSODocumentLockFile::WriteEntryToStream(
+ std::unique_lock<std::mutex>& /*rGuard*/, const LockFileEntry& aEntry,
+ const css::uno::Reference<css::io::XOutputStream>& xOutput)
+{
+ // Reallocate the date with the right size, different lock file size for different components
+ int nLockFileSize = m_eAppType == AppType::Word ? MSO_WORD_LOCKFILE_SIZE
+ : MSO_EXCEL_AND_POWERPOINT_LOCKFILE_SIZE;
+ css::uno::Sequence<sal_Int8> aData(nLockFileSize);
+ auto pData = aData.getArray();
+
+ // Write out the user name's length as a single byte integer
+ // The maximum length is 52 in MSO, so we'll need to truncate the user name if it's longer
+ OUString aUserName = aEntry[LockFileComponent::OOOUSERNAME];
+ int nIndex = 0;
+ pData[nIndex] = static_cast<sal_Int8>(
+ std::min(aUserName.getLength(), sal_Int32(MSO_USERNAME_MAX_LENGTH)));
+
+ if (aUserName.getLength() > MSO_USERNAME_MAX_LENGTH)
+ aUserName = aUserName.copy(0, MSO_USERNAME_MAX_LENGTH);
+
+ // From the second position write out the user name using one byte characters.
+ nIndex = 1;
+ for (int nChar = 0; nChar < aUserName.getLength(); ++nChar)
+ {
+ pData[nIndex] = static_cast<sal_Int8>(aUserName[nChar]);
+ ++nIndex;
+ }
+
+ // Fill up the remaining bytes with dummy data
+ switch (m_eAppType)
+ {
+ case AppType::Word:
+ while (nIndex < MSO_USERNAME_MAX_LENGTH + 2)
+ {
+ pData[nIndex] = static_cast<sal_Int8>(0);
+ ++nIndex;
+ }
+ break;
+ case AppType::PowerPoint:
+ pData[nIndex] = static_cast<sal_Int8>(0);
+ ++nIndex;
+ [[fallthrough]];
+ case AppType::Excel:
+ while (nIndex < MSO_USERNAME_MAX_LENGTH + 3)
+ {
+ pData[nIndex] = static_cast<sal_Int8>(0x20);
+ ++nIndex;
+ }
+ break;
+ }
+
+ // At the next position we have the user name's length again, but now as a 2 byte integer
+ pData[nIndex] = static_cast<sal_Int8>(
+ std::min(aUserName.getLength(), sal_Int32(MSO_USERNAME_MAX_LENGTH)));
+ ++nIndex;
+ pData[nIndex] = 0;
+ ++nIndex;
+
+ // And the user name again with unicode characters
+ for (int nChar = 0; nChar < aUserName.getLength(); ++nChar)
+ {
+ pData[nIndex] = static_cast<sal_Int8>(aUserName[nChar] & 0xff);
+ ++nIndex;
+ pData[nIndex] = static_cast<sal_Int8>(aUserName[nChar] >> 8);
+ ++nIndex;
+ }
+
+ // Fill the remaining part with dummy bits
+ switch (m_eAppType)
+ {
+ case AppType::Word:
+ while (nIndex < nLockFileSize)
+ {
+ pData[nIndex] = static_cast<sal_Int8>(0);
+ ++nIndex;
+ }
+ break;
+ case AppType::Excel:
+ case AppType::PowerPoint:
+ while (nIndex < nLockFileSize)
+ {
+ pData[nIndex] = static_cast<sal_Int8>(0x20);
+ ++nIndex;
+ if (nIndex < nLockFileSize)
+ {
+ pData[nIndex] = static_cast<sal_Int8>(0);
+ ++nIndex;
+ }
+ }
+ break;
+ }
+
+ xOutput->writeBytes(aData);
+}
+
+css::uno::Reference<css::io::XInputStream>
+MSODocumentLockFile::OpenStream(std::unique_lock<std::mutex>& /*rGuard*/)
+{
+ css::uno::Reference<css::ucb::XCommandEnvironment> xEnv;
+ ::ucbhelper::Content aSourceContent(GetURL(), xEnv, comphelper::getProcessComponentContext());
+
+ // the file can be opened readonly, no locking will be done
+ return aSourceContent.openStreamNoLock();
+}
+
+LockFileEntry MSODocumentLockFile::GetLockDataImpl(std::unique_lock<std::mutex>& rGuard)
+{
+ LockFileEntry aResult;
+ css::uno::Reference<css::io::XInputStream> xInput = OpenStream(rGuard);
+ if (!xInput.is())
+ throw css::uno::RuntimeException();
+
+ const sal_Int32 nBufLen = 256;
+ css::uno::Sequence<sal_Int8> aBuf(nBufLen);
+ const sal_Int32 nRead = xInput->readBytes(aBuf, nBufLen);
+ xInput->closeInput();
+ if (nRead >= 162)
+ {
+ // Reverse engineering of MS Office Owner Files format (MS Office 2016 tested).
+ // It starts with a single byte with name length, after which characters of username go
+ // in current Windows 8-bit codepage.
+ // For Word lockfiles, the name is followed by zero bytes up to position 54.
+ // For PowerPoint lockfiles, the name is followed by a single zero byte, and then 0x20
+ // bytes up to position 55.
+ // For Excel lockfiles, the name is followed by 0x20 bytes up to position 55.
+ // At those positions in each type of lockfile, a name length 2-byte word goes, followed
+ // by UTF-16-LE-encoded copy of username. Spaces or some garbage follow up to the end of
+ // the lockfile (total 162 bytes for Word, 165 bytes for Excel/PowerPoint).
+ // Apparently MS Office does not allow username to be longer than 52 characters (trying
+ // to enter more in its options dialog results in error messages stating this limit).
+ const int nACPLen = aBuf[0];
+ if (nACPLen > 0 && nACPLen <= 52) // skip wrong format
+ {
+ const sal_Int8* pBuf = aBuf.getConstArray() + 54;
+ int nUTF16Len = *pBuf; // try Word position
+ // If UTF-16 length is 0x20, then ACP length is also less than maximal, which means
+ // that in Word lockfile case, at least two preceding bytes would be zero. Both
+ // Excel and PowerPoint lockfiles would have at least one of those bytes non-zero.
+ if (nUTF16Len == 0x20 && (*(pBuf - 1) != 0 || *(pBuf - 2) != 0))
+ nUTF16Len = *++pBuf; // use Excel/PowerPoint position
+
+ if (nUTF16Len > 0 && nUTF16Len <= 52) // skip wrong format
+ {
+ OUStringBuffer str(nUTF16Len);
+ sal_uInt8 const* p = reinterpret_cast<sal_uInt8 const*>(pBuf + 2);
+ for (int i = 0; i != nUTF16Len; ++i)
+ {
+ str.append(sal_Unicode(p[0] | (sal_uInt32(p[1]) << 8)));
+ p += 2;
+ }
+ aResult[LockFileComponent::OOOUSERNAME] = str.makeStringAndClear();
+ }
+ }
+ }
+ return aResult;
+}
+
+void MSODocumentLockFile::RemoveFile()
+{
+ std::unique_lock aGuard(m_aMutex);
+
+ // TODO/LATER: the removing is not atomic, is it possible in general to make it atomic?
+ LockFileEntry aNewEntry = GenerateOwnEntry();
+ LockFileEntry aFileData = GetLockDataImpl(aGuard);
+
+ if (aFileData[LockFileComponent::OOOUSERNAME] != aNewEntry[LockFileComponent::OOOUSERNAME])
+ throw css::io::IOException(); // not the owner, access denied
+
+ RemoveFileDirectly();
+}
+
+bool MSODocumentLockFile::IsMSOSupportedFileFormat(std::u16string_view aURL)
+{
+ INetURLObject aDocURL = LockFileCommon::ResolveLinks(INetURLObject(aURL));
+ const OUString sExt = aDocURL.GetFileExtension();
+
+ return isWordFormat(sExt) || isExcelFormat(sExt) || isPowerPointFormat(sExt);
+}
+
+} // namespace svt
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/svl/source/misc/ownlist.cxx b/svl/source/misc/ownlist.cxx
new file mode 100644
index 0000000000..22b9d21fcc
--- /dev/null
+++ b/svl/source/misc/ownlist.cxx
@@ -0,0 +1,70 @@
+/* -*- 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 <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/beans/PropertyState.hpp>
+
+#include <svl/ownlist.hxx>
+
+using namespace com::sun::star;
+
+
+/**
+ * An object of the type SvCommand is created and the list is
+ * attached.
+*/
+void SvCommandList::Append
+(
+ const OUString & rCommand, /* The command */
+ const OUString & rArg /* The command's argument */
+)
+{
+ aCommandList.emplace_back( rCommand, rArg );
+}
+
+void SvCommandList::FillFromSequence( const css::uno::Sequence < css::beans::PropertyValue >& aCommandSequence )
+{
+ OUString aCommand, aArg;
+ OUString aApiArg;
+ for( const auto& rCommand : aCommandSequence )
+ {
+ aCommand = rCommand.Name;
+ if( !( rCommand.Value >>= aApiArg ) )
+ return;
+ aArg = aApiArg;
+ Append( aCommand, aArg );
+ }
+}
+
+void SvCommandList::FillSequence( css::uno::Sequence < css::beans::PropertyValue >& aCommandSequence ) const
+{
+ const sal_Int32 nCount = aCommandList.size();
+ aCommandSequence.realloc( nCount );
+ auto pCommandSequence = aCommandSequence.getArray();
+ for( sal_Int32 nIndex = 0; nIndex < nCount; nIndex++ )
+ {
+ pCommandSequence[nIndex].Name = aCommandList[ nIndex ].GetCommand();
+ pCommandSequence[nIndex].Handle = -1;
+ pCommandSequence[nIndex].Value <<= aCommandList[ nIndex ].GetArgument();
+ pCommandSequence[nIndex].State = beans::PropertyState_DIRECT_VALUE;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/sharecontrolfile.cxx b/svl/source/misc/sharecontrolfile.cxx
new file mode 100644
index 0000000000..7c8fd854b2
--- /dev/null
+++ b/svl/source/misc/sharecontrolfile.cxx
@@ -0,0 +1,337 @@
+/* -*- 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 <com/sun/star/ucb/SimpleFileAccess.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/ucb/XContent.hpp>
+#include <com/sun/star/ucb/InsertCommandArgument.hpp>
+#include <com/sun/star/ucb/InteractiveIOException.hpp>
+#include <com/sun/star/io/NotConnectedException.hpp>
+
+#include <o3tl/enumrange.hxx>
+
+#include <rtl/string.hxx>
+#include <rtl/ustring.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <ucbhelper/content.hxx>
+
+#include <tools/stream.hxx>
+#include <unotools/streamwrap.hxx>
+
+#include <svl/sharecontrolfile.hxx>
+
+using namespace ::com::sun::star;
+
+namespace svt {
+
+
+ShareControlFile::ShareControlFile( std::u16string_view aOrigURL )
+ : LockFileCommon(GenerateOwnLockFileURL(aOrigURL, u".~sharing."))
+{
+ if ( !m_xStream.is() && !GetURL().isEmpty() )
+ {
+ uno::Reference< ucb::XCommandEnvironment > xDummyEnv;
+ ::ucbhelper::Content aContent( GetURL(), xDummyEnv, comphelper::getProcessComponentContext() );
+
+ uno::Reference< ucb::XContentIdentifier > xContId( aContent.get().is() ? aContent.get()->getIdentifier() : nullptr );
+ if ( !xContId.is() || xContId->getContentProviderScheme() != "file" )
+ throw io::IOException(); // the implementation supports only local files for now
+
+ uno::Reference< io::XStream > xStream;
+
+ // Currently the locking of the original document is intended to be used.
+ // That means that the shared file should be accessed only when the original document is locked and only by user who has locked the document.
+ // TODO/LATER: should the own file locking be used?
+
+ try
+ {
+ xStream = aContent.openWriteableStreamNoLock();
+ }
+ catch ( ucb::InteractiveIOException const & e )
+ {
+ if ( e.Code == ucb::IOErrorCode_NOT_EXISTING )
+ {
+ // Create file...
+ SvMemoryStream aStream(0,0);
+ uno::Reference< io::XInputStream > xInput( new ::utl::OInputStreamWrapper( aStream ) );
+ ucb::InsertCommandArgument aInsertArg;
+ aInsertArg.Data = xInput;
+ aInsertArg.ReplaceExisting = false;
+ aContent.executeCommand( "insert", uno::Any( aInsertArg ) );
+
+ // try to let the file be hidden if possible
+ try {
+ aContent.setPropertyValue("IsHidden", uno::Any( true ) );
+ } catch( uno::Exception& ) {}
+
+ // Try to open one more time
+ xStream = aContent.openWriteableStreamNoLock();
+ }
+ else
+ throw;
+ }
+
+ m_xSeekable.set( xStream, uno::UNO_QUERY_THROW );
+ m_xInputStream.set( xStream->getInputStream(), uno::UNO_SET_THROW );
+ m_xOutputStream.set( xStream->getOutputStream(), uno::UNO_SET_THROW );
+ m_xTruncate.set( m_xOutputStream, uno::UNO_QUERY_THROW );
+ m_xStream = xStream;
+ }
+
+ if ( !IsValid() )
+ throw io::NotConnectedException();
+}
+
+ShareControlFile::~ShareControlFile()
+{
+ try
+ {
+ Close();
+ }
+ catch( uno::Exception& )
+ {}
+}
+
+void ShareControlFile::Close()
+{
+ // if it is called outside of destructor the mutex must be locked
+
+ if ( !m_xStream.is() )
+ return;
+
+ try
+ {
+ if ( m_xInputStream.is() )
+ m_xInputStream->closeInput();
+ if ( m_xOutputStream.is() )
+ m_xOutputStream->closeOutput();
+ }
+ catch( uno::Exception& )
+ {}
+
+ m_xStream.clear();
+ m_xInputStream.clear();
+ m_xOutputStream.clear();
+ m_xSeekable.clear();
+ m_xTruncate.clear();
+ m_aUsersData.clear();
+}
+
+
+std::vector< o3tl::enumarray< LockFileComponent, OUString > > ShareControlFile::GetUsersData()
+{
+ std::unique_lock aGuard(m_aMutex);
+ return GetUsersDataImpl(aGuard);
+}
+
+std::vector< o3tl::enumarray< LockFileComponent, OUString > > ShareControlFile::GetUsersDataImpl(std::unique_lock<std::mutex>& /*rGuard*/)
+{
+ if ( !IsValid() )
+ throw io::NotConnectedException();
+
+ if ( m_aUsersData.empty() )
+ {
+ sal_Int64 nLength = m_xSeekable->getLength();
+ if ( nLength > SAL_MAX_INT32 )
+ throw uno::RuntimeException();
+
+ uno::Sequence< sal_Int8 > aBuffer( static_cast<sal_Int32>(nLength) );
+ m_xSeekable->seek( 0 );
+
+ sal_Int32 nRead = m_xInputStream->readBytes( aBuffer, static_cast<sal_Int32>(nLength) );
+ auto aBufferRange = asNonConstRange(aBuffer);
+ nLength -= nRead;
+ while ( nLength > 0 )
+ {
+ uno::Sequence< sal_Int8 > aTmpBuf( static_cast<sal_Int32>(nLength) );
+ nRead = m_xInputStream->readBytes( aTmpBuf, static_cast<sal_Int32>(nLength) );
+ if ( nRead > nLength )
+ throw uno::RuntimeException();
+
+ for ( sal_Int32 nInd = 0; nInd < nRead; nInd++ )
+ aBufferRange[aBuffer.getLength() - static_cast<sal_Int32>(nLength) + nInd] = aTmpBuf[nInd];
+ nLength -= nRead;
+ }
+
+ ParseList( aBuffer, m_aUsersData );
+ }
+
+ return m_aUsersData;
+}
+
+
+void ShareControlFile::SetUsersDataAndStore( std::unique_lock<std::mutex>& /*rGuard*/, std::vector< LockFileEntry >&& aUsersData )
+{
+ if ( !IsValid() )
+ throw io::NotConnectedException();
+
+ if ( !m_xTruncate.is() || !m_xOutputStream.is() || !m_xSeekable.is() )
+ throw uno::RuntimeException();
+
+ m_xTruncate->truncate();
+ m_xSeekable->seek( 0 );
+
+ OUStringBuffer aBuffer;
+ for (const auto & rData : aUsersData)
+ {
+ for ( LockFileComponent nEntryInd : o3tl::enumrange<LockFileComponent>() )
+ {
+ aBuffer.append( EscapeCharacters( rData[nEntryInd] ) );
+ if ( nEntryInd < LockFileComponent::LAST )
+ aBuffer.append( ',' );
+ else
+ aBuffer.append( ';' );
+ }
+ }
+
+ OString aStringData( OUStringToOString( aBuffer, RTL_TEXTENCODING_UTF8 ) );
+ uno::Sequence< sal_Int8 > aData( reinterpret_cast<sal_Int8 const *>(aStringData.getStr()), aStringData.getLength() );
+ m_xOutputStream->writeBytes( aData );
+ m_aUsersData = aUsersData;
+}
+
+
+LockFileEntry ShareControlFile::InsertOwnEntry()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( !IsValid() )
+ throw io::NotConnectedException();
+
+ GetUsersDataImpl(aGuard);
+ std::vector< LockFileEntry > aNewData( m_aUsersData );
+ LockFileEntry aNewEntry = GenerateOwnEntry();
+
+ bool bExists = false;
+ sal_Int32 nNewInd = 0;
+ for (LockFileEntry & rEntry : m_aUsersData)
+ {
+ if ( rEntry[LockFileComponent::LOCALHOST] == aNewEntry[LockFileComponent::LOCALHOST]
+ && rEntry[LockFileComponent::SYSUSERNAME] == aNewEntry[LockFileComponent::SYSUSERNAME]
+ && rEntry[LockFileComponent::USERURL] == aNewEntry[LockFileComponent::USERURL] )
+ {
+ if ( !bExists )
+ {
+ aNewData[nNewInd] = aNewEntry;
+ bExists = true;
+ }
+ }
+ else
+ {
+ aNewData[nNewInd] = rEntry;
+ }
+
+ nNewInd++;
+ }
+
+ if ( !bExists )
+ aNewData.push_back( aNewEntry );
+
+ SetUsersDataAndStore( aGuard, std::move(aNewData) );
+
+ return aNewEntry;
+}
+
+
+bool ShareControlFile::HasOwnEntry()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( !IsValid() )
+ {
+ throw io::NotConnectedException();
+ }
+
+ GetUsersDataImpl(aGuard);
+ LockFileEntry aEntry = GenerateOwnEntry();
+
+ for (LockFileEntry & rEntry : m_aUsersData)
+ {
+ if ( rEntry[LockFileComponent::LOCALHOST] == aEntry[LockFileComponent::LOCALHOST] &&
+ rEntry[LockFileComponent::SYSUSERNAME] == aEntry[LockFileComponent::SYSUSERNAME] &&
+ rEntry[LockFileComponent::USERURL] == aEntry[LockFileComponent::USERURL] )
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+void ShareControlFile::RemoveEntry()
+{
+ RemoveEntry(GenerateOwnEntry());
+}
+
+void ShareControlFile::RemoveEntry( const LockFileEntry& aEntry )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( !IsValid() )
+ throw io::NotConnectedException();
+
+ GetUsersDataImpl(aGuard);
+
+ std::vector< LockFileEntry > aNewData;
+
+ for (LockFileEntry & rEntry : m_aUsersData)
+ {
+ if ( rEntry[LockFileComponent::LOCALHOST] != aEntry[LockFileComponent::LOCALHOST]
+ || rEntry[LockFileComponent::SYSUSERNAME] != aEntry[LockFileComponent::SYSUSERNAME]
+ || rEntry[LockFileComponent::USERURL] != aEntry[LockFileComponent::USERURL] )
+ {
+ aNewData.push_back( rEntry );
+ }
+ }
+
+ const bool bNewDataEmpty = aNewData.empty();
+ SetUsersDataAndStore( aGuard, std::move(aNewData) );
+
+ if ( bNewDataEmpty )
+ {
+ // try to remove the file if it is empty
+ RemoveFileImpl(aGuard);
+ }
+}
+
+
+void ShareControlFile::RemoveFile()
+{
+ std::unique_lock aGuard(m_aMutex);
+ return RemoveFileImpl(aGuard);
+}
+
+void ShareControlFile::RemoveFileImpl(std::unique_lock<std::mutex>& /*rGuard*/)
+{
+ if ( !IsValid() )
+ throw io::NotConnectedException();
+
+ Close();
+
+ uno::Reference<ucb::XSimpleFileAccess3> xSimpleFileAccess(ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext()));
+ xSimpleFileAccess->kill( GetURL() );
+}
+
+} // namespace svt
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/sharedstring.cxx b/svl/source/misc/sharedstring.cxx
new file mode 100644
index 0000000000..499136da97
--- /dev/null
+++ b/svl/source/misc/sharedstring.cxx
@@ -0,0 +1,70 @@
+/* -*- 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/.
+ */
+
+#include <svl/sharedstring.hxx>
+
+namespace svl {
+
+const OUString SharedString::EMPTY_STRING;
+
+const SharedString & SharedString::getEmptyString()
+{
+ // ref-counting traffic associated with SharedString temporaries can be significant,
+ // so use a singleton here, so we can return a const& from getEmptyString.
+ // unicode string array for empty string is globally shared in OUString.
+ // Let's take advantage of that.
+ static const SharedString EMPTY_SHARED_STRING(EMPTY_STRING.pData, EMPTY_STRING.pData);
+ return EMPTY_SHARED_STRING;
+}
+
+SharedString& SharedString::operator= ( const SharedString& r )
+{
+ if(this == &r)
+ return *this;
+
+ if (mpData)
+ rtl_uString_release(mpData);
+ if (mpDataIgnoreCase)
+ rtl_uString_release(mpDataIgnoreCase);
+
+ mpData = r.mpData;
+ mpDataIgnoreCase = r.mpDataIgnoreCase;
+
+ if (mpData)
+ rtl_uString_acquire(mpData);
+ if (mpDataIgnoreCase)
+ rtl_uString_acquire(mpDataIgnoreCase);
+
+ return *this;
+}
+
+bool SharedString::operator== ( const SharedString& r ) const
+{
+ // Compare only the original (not case-folded) string.
+
+ if (mpData == r.mpData)
+ return true;
+
+ if (mpData)
+ {
+ if (!r.mpData)
+ return false;
+
+ if (mpData->length != r.mpData->length)
+ return false;
+
+ return rtl_ustr_reverseCompare_WithLength(mpData->buffer, mpData->length, r.mpData->buffer, r.mpData->length) == 0;
+ }
+
+ return !r.mpData;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/sharedstringpool.cxx b/svl/source/misc/sharedstringpool.cxx
new file mode 100644
index 0000000000..06cf8a5cef
--- /dev/null
+++ b/svl/source/misc/sharedstringpool.cxx
@@ -0,0 +1,184 @@
+/* -*- 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/.
+ */
+
+#include <svl/sharedstringpool.hxx>
+#include <svl/sharedstring.hxx>
+#include <unotools/charclass.hxx>
+
+#include <mutex>
+#include <unordered_map>
+#include <unordered_set>
+
+/** create a key class that caches the hashcode */
+namespace
+{
+struct StringWithHash
+{
+ OUString str;
+ sal_Int32 hashCode;
+ StringWithHash(OUString s)
+ : str(s)
+ , hashCode(s.hashCode())
+ {
+ }
+
+ bool operator==(StringWithHash const& rhs) const
+ {
+ if (hashCode != rhs.hashCode)
+ return false;
+ return str == rhs.str;
+ }
+};
+}
+
+namespace std
+{
+template <> struct hash<StringWithHash>
+{
+ std::size_t operator()(const StringWithHash& k) const { return k.hashCode; }
+};
+}
+
+namespace svl
+{
+namespace
+{
+sal_Int32 getRefCount(const rtl_uString* p) { return (p->refCount & 0x3FFFFFFF); }
+}
+
+struct SharedStringPool::Impl
+{
+ mutable std::mutex maMutex;
+ // We use this map for two purposes - to store lower->upper case mappings
+ // and to retrieve a shared uppercase object, so the management logic
+ // is quite complex.
+ std::unordered_map<StringWithHash, OUString> maStrMap;
+ const CharClass& mrCharClass;
+
+ explicit Impl(const CharClass& rCharClass)
+ : mrCharClass(rCharClass)
+ {
+ }
+};
+
+SharedStringPool::SharedStringPool(const CharClass& rCharClass)
+ : mpImpl(new Impl(rCharClass))
+{
+ // make sure the one empty string instance is shared in this pool as well
+ intern(SharedString::EMPTY_STRING);
+ assert(intern(SharedString::EMPTY_STRING) == SharedString::getEmptyString());
+}
+
+SharedStringPool::~SharedStringPool() {}
+
+SharedString SharedStringPool::intern(const OUString& rStr)
+{
+ StringWithHash aStrWithHash(rStr);
+ std::scoped_lock<std::mutex> aGuard(mpImpl->maMutex);
+
+ auto[mapIt, bInserted] = mpImpl->maStrMap.emplace(aStrWithHash, rStr);
+ if (!bInserted)
+ // there is already a mapping
+ return SharedString(mapIt->first.str.pData, mapIt->second.pData);
+
+ // This is a new string insertion. Establish mapping to upper-case variant.
+ OUString aUpper = mpImpl->mrCharClass.uppercase(rStr);
+ if (aUpper == rStr)
+ // no need to do anything more, because we inserted an upper->upper mapping
+ return SharedString(mapIt->first.str.pData, mapIt->second.pData);
+
+ // We need to insert a lower->upper mapping, so also insert
+ // an upper->upper mapping, which we can use both for when an upper string
+ // is interned, and to look up a shared upper string.
+ StringWithHash aUpperWithHash(aUpper);
+ auto mapIt2 = mpImpl->maStrMap.find(aUpperWithHash);
+ if (mapIt2 != mpImpl->maStrMap.end())
+ {
+ // there is an already existing upper string
+ mapIt->second = mapIt2->first.str;
+ return SharedString(mapIt->first.str.pData, mapIt->second.pData);
+ }
+
+ // There is no already existing upper string.
+ // First, update using the iterator, can't do this later because
+ // the iterator will be invalid.
+ mapIt->second = aUpper;
+ mpImpl->maStrMap.emplace_hint(mapIt2, aUpperWithHash, aUpper);
+ return SharedString(rStr.pData, aUpper.pData);
+}
+
+void SharedStringPool::purge()
+{
+ std::scoped_lock<std::mutex> aGuard(mpImpl->maMutex);
+
+ // Because we can have an uppercase entry mapped to itself,
+ // and then a bunch of lowercase entries mapped to that same
+ // upper-case entry, we need to scan the map twice - the first
+ // time to remove lowercase entries, and then only can we
+ // check for unused uppercase entries.
+
+ auto it = mpImpl->maStrMap.begin();
+ auto itEnd = mpImpl->maStrMap.end();
+ while (it != itEnd)
+ {
+ rtl_uString* p1 = it->first.str.pData;
+ rtl_uString* p2 = it->second.pData;
+ if (p1 != p2)
+ {
+ // normal case - lowercase mapped to uppercase, which
+ // means that the lowercase entry has one ref-counted
+ // entry as the key in the map
+ if (getRefCount(p1) == 1)
+ {
+ it = mpImpl->maStrMap.erase(it);
+ continue;
+ }
+ }
+ ++it;
+ }
+
+ it = mpImpl->maStrMap.begin();
+ itEnd = mpImpl->maStrMap.end();
+ while (it != itEnd)
+ {
+ rtl_uString* p1 = it->first.str.pData;
+ rtl_uString* p2 = it->second.pData;
+ if (p1 == p2)
+ {
+ // uppercase which is mapped to itself, which means
+ // one ref-counted entry as the key in the map, and
+ // one ref-counted entry in the value in the map
+ if (getRefCount(p1) == 2)
+ {
+ it = mpImpl->maStrMap.erase(it);
+ continue;
+ }
+ }
+ ++it;
+ }
+}
+
+size_t SharedStringPool::getCount() const
+{
+ std::scoped_lock<std::mutex> aGuard(mpImpl->maMutex);
+ return mpImpl->maStrMap.size();
+}
+
+size_t SharedStringPool::getCountIgnoreCase() const
+{
+ std::scoped_lock<std::mutex> aGuard(mpImpl->maMutex);
+ // this is only called from unit tests, so no need to be efficient
+ std::unordered_set<OUString> aUpperSet;
+ for (auto const& pair : mpImpl->maStrMap)
+ aUpperSet.insert(pair.second);
+ return aUpperSet.size();
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/strmadpt.cxx b/svl/source/misc/strmadpt.cxx
new file mode 100644
index 0000000000..7a755d9249
--- /dev/null
+++ b/svl/source/misc/strmadpt.cxx
@@ -0,0 +1,653 @@
+/* -*- 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 <algorithm>
+#include <limits>
+#include <set>
+#include <string.h>
+
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/io/XSeekable.hpp>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <svl/instrm.hxx>
+#include <svl/outstrm.hxx>
+#include <utility>
+
+using namespace com::sun::star;
+
+class SvDataPipe_Impl
+{
+public:
+ enum SeekResult { SEEK_BEFORE_MARKED, SEEK_OK, SEEK_PAST_END };
+
+private:
+ struct Page
+ {
+ Page * m_pPrev;
+ Page * m_pNext;
+ sal_Int8 * m_pStart;
+ sal_Int8 * m_pRead;
+ sal_Int8 * m_pEnd;
+ sal_uInt32 m_nOffset;
+ sal_Int8 m_aBuffer[1];
+ };
+ static const sal_uInt32 m_nPageSize = 1000;
+
+ std::multiset< sal_uInt32 > m_aMarks;
+ Page * m_pFirstPage;
+ Page * m_pReadPage;
+ Page * m_pWritePage;
+ sal_Int8 * m_pReadBuffer;
+ sal_uInt32 m_nReadBufferSize;
+ sal_uInt32 m_nReadBufferFilled;
+ sal_uInt32 m_nPages;
+ bool m_bEOF;
+
+ void remove(Page * pPage);
+
+public:
+ inline SvDataPipe_Impl();
+
+ ~SvDataPipe_Impl();
+
+ inline void setReadBuffer(sal_Int8 * pBuffer, sal_uInt32 nSize);
+
+ sal_uInt32 read();
+
+ void clearReadBuffer() { m_pReadBuffer = nullptr; }
+
+ void write(sal_Int8 const * pBuffer, sal_uInt32 nSize);
+
+ void setEOF() { m_bEOF = true; }
+
+ inline bool isEOF() const;
+
+ SeekResult setReadPosition(sal_uInt32 nPosition);
+};
+
+SvDataPipe_Impl::SvDataPipe_Impl()
+ : m_pFirstPage( nullptr )
+ , m_pReadPage( nullptr )
+ , m_pWritePage( nullptr )
+ , m_pReadBuffer( nullptr )
+ , m_nReadBufferSize( 0 )
+ , m_nReadBufferFilled( 0 )
+ , m_nPages( 0 )
+ , m_bEOF( false )
+{}
+
+inline void SvDataPipe_Impl::setReadBuffer(sal_Int8 * pBuffer,
+ sal_uInt32 nSize)
+{
+ m_pReadBuffer = pBuffer;
+ m_nReadBufferSize = nSize;
+ m_nReadBufferFilled = 0;
+}
+
+inline bool SvDataPipe_Impl::isEOF() const
+{
+ return m_bEOF && m_pReadPage == m_pWritePage
+ && (!m_pReadPage || m_pReadPage->m_pRead == m_pReadPage->m_pEnd);
+}
+
+
+
+// SvInputStream
+
+bool SvInputStream::open()
+{
+ if (GetError() != ERRCODE_NONE)
+ return false;
+ if (!(m_xSeekable.is() || m_pPipe))
+ {
+ if (!m_xStream.is())
+ {
+ SetError(ERRCODE_IO_INVALIDDEVICE);
+ return false;
+ }
+ m_xSeekable.set(m_xStream, uno::UNO_QUERY);
+ if (!m_xSeekable.is())
+ m_pPipe.reset( new SvDataPipe_Impl );
+ }
+ return true;
+}
+
+// virtual
+std::size_t SvInputStream::GetData(void * pData, std::size_t const nSize)
+{
+ if (!open())
+ {
+ SetError(ERRCODE_IO_CANTREAD);
+ return 0;
+ }
+ // check if a truncated STREAM_SEEK_TO_END was passed
+ assert(m_nSeekedFrom != SAL_MAX_UINT32);
+ sal_uInt32 nRead = 0;
+ if (m_xSeekable.is())
+ {
+ if (m_nSeekedFrom != STREAM_SEEK_TO_END)
+ {
+ try
+ {
+ m_xSeekable->seek(m_nSeekedFrom);
+ }
+ catch (const io::IOException&)
+ {
+ SetError(ERRCODE_IO_CANTREAD);
+ return 0;
+ }
+ m_nSeekedFrom = STREAM_SEEK_TO_END;
+ }
+ for (;;)
+ {
+ sal_Int32 nRemain
+ = sal_Int32(
+ std::min(std::size_t(nSize - nRead),
+ std::size_t(std::numeric_limits<sal_Int32>::max())));
+ if (nRemain == 0)
+ break;
+ uno::Sequence< sal_Int8 > aBuffer;
+ sal_Int32 nCount;
+ try
+ {
+ nCount = m_xStream->readBytes(aBuffer, nRemain);
+ }
+ catch (const io::IOException&)
+ {
+ SetError(ERRCODE_IO_CANTREAD);
+ return nRead;
+ }
+ memcpy(static_cast< sal_Int8 * >(pData) + nRead,
+ aBuffer.getConstArray(), sal_uInt32(nCount));
+ nRead += nCount;
+ if (nCount < nRemain)
+ break;
+ }
+ }
+ else
+ {
+ if (m_nSeekedFrom != STREAM_SEEK_TO_END)
+ {
+ SetError(ERRCODE_IO_CANTREAD);
+ return 0;
+ }
+ m_pPipe->setReadBuffer(static_cast< sal_Int8 * >(pData), nSize);
+ nRead = m_pPipe->read();
+ if (nRead < nSize && !m_pPipe->isEOF())
+ for (;;)
+ {
+ sal_Int32 nRemain
+ = sal_Int32(
+ std::min(
+ std::size_t(nSize - nRead),
+ std::size_t(std::numeric_limits<sal_Int32>::max())));
+ if (nRemain == 0)
+ break;
+ uno::Sequence< sal_Int8 > aBuffer;
+ sal_Int32 nCount;
+ try
+ {
+ nCount = m_xStream->readBytes(aBuffer, nRemain);
+ }
+ catch (const io::IOException&)
+ {
+ SetError(ERRCODE_IO_CANTREAD);
+ break;
+ }
+ m_pPipe->write(aBuffer.getConstArray(), sal_uInt32(nCount));
+ nRead += m_pPipe->read();
+ if (nCount < nRemain)
+ {
+ m_xStream->closeInput();
+ m_pPipe->setEOF();
+ break;
+ }
+ }
+ m_pPipe->clearReadBuffer();
+ }
+ return nRead;
+}
+
+// virtual
+std::size_t SvInputStream::PutData(void const *, std::size_t)
+{
+ SetError(ERRCODE_IO_NOTSUPPORTED);
+ return 0;
+}
+
+// virtual
+void SvInputStream::FlushData()
+{}
+
+// virtual
+sal_uInt64 SvInputStream::SeekPos(sal_uInt64 const nPos)
+{
+ // check if a truncated STREAM_SEEK_TO_END was passed
+ assert(nPos != SAL_MAX_UINT32);
+ if (open())
+ {
+ if (nPos == STREAM_SEEK_TO_END)
+ {
+ if (m_nSeekedFrom == STREAM_SEEK_TO_END)
+ {
+ if (m_xSeekable.is())
+ try
+ {
+ sal_Int64 nLength = m_xSeekable->getLength();
+ OSL_ASSERT(nLength >= 0);
+ if (o3tl::make_unsigned(nLength)
+ < STREAM_SEEK_TO_END)
+ {
+ m_nSeekedFrom = Tell();
+ return sal_uInt64(nLength);
+ }
+ }
+ catch (const io::IOException&)
+ {
+ }
+ else
+ return Tell(); //@@@
+ }
+ else
+ return Tell();
+ }
+ else if (nPos == m_nSeekedFrom)
+ {
+ m_nSeekedFrom = STREAM_SEEK_TO_END;
+ return nPos;
+ }
+ else if (m_xSeekable.is())
+ {
+ try
+ {
+ m_xSeekable->seek(nPos);
+ m_nSeekedFrom = STREAM_SEEK_TO_END;
+ return nPos;
+ }
+ catch (const io::IOException&)
+ {
+ }
+ }
+ else if (m_pPipe->setReadPosition(nPos) == SvDataPipe_Impl::SEEK_OK)
+ {
+ m_nSeekedFrom = STREAM_SEEK_TO_END;
+ return nPos;
+ }
+ else if ( nPos > Tell() )
+ {
+ // Read out the bytes
+ sal_Int32 nRead = nPos - Tell();
+ uno::Sequence< sal_Int8 > aBuffer;
+ m_xStream->readBytes( aBuffer, nRead );
+ return nPos;
+ }
+ else if ( nPos == Tell() )
+ return nPos;
+ }
+ SetError(ERRCODE_IO_CANTSEEK);
+ return Tell();
+}
+
+// virtual
+void SvInputStream::SetSize(sal_uInt64)
+{
+ SetError(ERRCODE_IO_NOTSUPPORTED);
+}
+
+SvInputStream::SvInputStream( css::uno::Reference< css::io::XInputStream > xTheStream):
+ m_xStream(std::move(xTheStream)),
+ m_nSeekedFrom(STREAM_SEEK_TO_END)
+{
+ SetBufferSize(0);
+}
+
+// virtual
+SvInputStream::~SvInputStream()
+{
+ if (m_xStream.is())
+ {
+ try
+ {
+ m_xStream->closeInput();
+ }
+ catch (const io::IOException&)
+ {
+ }
+ }
+}
+
+// SvOutputStream
+
+// virtual
+std::size_t SvOutputStream::GetData(void *, std::size_t)
+{
+ SetError(ERRCODE_IO_NOTSUPPORTED);
+ return 0;
+}
+
+// virtual
+std::size_t SvOutputStream::PutData(void const * pData, std::size_t nSize)
+{
+ if (!m_xStream.is())
+ {
+ SetError(ERRCODE_IO_CANTWRITE);
+ return 0;
+ }
+ std::size_t nWritten = 0;
+ for (;;)
+ {
+ sal_Int32 nRemain
+ = sal_Int32(
+ std::min(std::size_t(nSize - nWritten),
+ std::size_t(std::numeric_limits<sal_Int32>::max())));
+ if (nRemain == 0)
+ break;
+ try
+ {
+ m_xStream->writeBytes(uno::Sequence< sal_Int8 >(
+ static_cast<const sal_Int8 * >(pData)
+ + nWritten,
+ nRemain));
+ }
+ catch (const io::IOException&)
+ {
+ SetError(ERRCODE_IO_CANTWRITE);
+ break;
+ }
+ nWritten += nRemain;
+ }
+ return nWritten;
+}
+
+// virtual
+sal_uInt64 SvOutputStream::SeekPos(sal_uInt64)
+{
+ SetError(ERRCODE_IO_NOTSUPPORTED);
+ return 0;
+}
+
+// virtual
+void SvOutputStream::FlushData()
+{
+ if (!m_xStream.is())
+ {
+ SetError(ERRCODE_IO_INVALIDDEVICE);
+ return;
+ }
+ try
+ {
+ m_xStream->flush();
+ }
+ catch (const io::IOException&)
+ {
+ }
+}
+
+// virtual
+void SvOutputStream::SetSize(sal_uInt64)
+{
+ SetError(ERRCODE_IO_NOTSUPPORTED);
+}
+
+SvOutputStream::SvOutputStream(uno::Reference< io::XOutputStream > xTheStream):
+ m_xStream(std::move(xTheStream))
+{
+ SetBufferSize(0);
+}
+
+// virtual
+SvOutputStream::~SvOutputStream()
+{
+ if (m_xStream.is())
+ {
+ try
+ {
+ m_xStream->closeOutput();
+ }
+ catch (const io::IOException&)
+ {
+ }
+ }
+}
+
+
+// SvDataPipe_Impl
+
+
+void SvDataPipe_Impl::remove(Page * pPage)
+{
+ if (
+ pPage != m_pFirstPage ||
+ m_pReadPage == m_pFirstPage ||
+ (
+ !m_aMarks.empty() &&
+ *m_aMarks.begin() < m_pFirstPage->m_nOffset + m_nPageSize
+ )
+ )
+ {
+ return;
+ }
+
+ m_pFirstPage = m_pFirstPage->m_pNext;
+
+ if (m_nPages <= 100) // min pages
+ return;
+
+ pPage->m_pPrev->m_pNext = pPage->m_pNext;
+ pPage->m_pNext->m_pPrev = pPage->m_pPrev;
+ std::free(pPage);
+ --m_nPages;
+}
+
+SvDataPipe_Impl::~SvDataPipe_Impl()
+{
+ if (m_pFirstPage != nullptr)
+ for (Page * pPage = m_pFirstPage;;)
+ {
+ Page * pNext = pPage->m_pNext;
+ std::free(pPage);
+ if (pNext == m_pFirstPage)
+ break;
+ pPage = pNext;
+ }
+}
+
+sal_uInt32 SvDataPipe_Impl::read()
+{
+ if (m_pReadBuffer == nullptr || m_nReadBufferSize == 0 || m_pReadPage == nullptr)
+ return 0;
+
+ sal_uInt32 nSize = m_nReadBufferSize;
+ sal_uInt32 nRemain = m_nReadBufferSize - m_nReadBufferFilled;
+
+ m_pReadBuffer += m_nReadBufferFilled;
+ m_nReadBufferSize -= m_nReadBufferFilled;
+ m_nReadBufferFilled = 0;
+
+ while (nRemain > 0)
+ {
+ sal_uInt32 nBlock = std::min(sal_uInt32(m_pReadPage->m_pEnd
+ - m_pReadPage->m_pRead),
+ nRemain);
+ memcpy(m_pReadBuffer, m_pReadPage->m_pRead, nBlock);
+ m_pReadPage->m_pRead += nBlock;
+ m_pReadBuffer += nBlock;
+ m_nReadBufferSize -= nBlock;
+ m_nReadBufferFilled = 0;
+ nRemain -= nBlock;
+
+ if (m_pReadPage == m_pWritePage)
+ break;
+
+ if (m_pReadPage->m_pRead == m_pReadPage->m_pEnd)
+ {
+ Page * pRemove = m_pReadPage;
+ m_pReadPage = pRemove->m_pNext;
+ remove(pRemove);
+ }
+ }
+
+ return nSize - nRemain;
+}
+
+void SvDataPipe_Impl::write(sal_Int8 const * pBuffer, sal_uInt32 nSize)
+{
+ if (nSize == 0)
+ return;
+
+ if (m_pWritePage == nullptr)
+ {
+ m_pFirstPage
+ = static_cast< Page * >(std::malloc(sizeof (Page)
+ + m_nPageSize
+ - 1));
+ m_pFirstPage->m_pPrev = m_pFirstPage;
+ m_pFirstPage->m_pNext = m_pFirstPage;
+ m_pFirstPage->m_pStart = m_pFirstPage->m_aBuffer;
+ m_pFirstPage->m_pRead = m_pFirstPage->m_aBuffer;
+ m_pFirstPage->m_pEnd = m_pFirstPage->m_aBuffer;
+ m_pFirstPage->m_nOffset = 0;
+ m_pReadPage = m_pFirstPage;
+ m_pWritePage = m_pFirstPage;
+ ++m_nPages;
+ }
+
+ sal_uInt32 nRemain = nSize;
+
+ if (m_pReadBuffer != nullptr && m_pReadPage == m_pWritePage
+ && m_pReadPage->m_pRead == m_pWritePage->m_pEnd)
+ {
+ sal_uInt32 nBlock = std::min(nRemain,
+ sal_uInt32(m_nReadBufferSize
+ - m_nReadBufferFilled));
+ sal_uInt32 nPosition = m_pWritePage->m_nOffset
+ + (m_pWritePage->m_pEnd
+ - m_pWritePage->m_aBuffer);
+ if (!m_aMarks.empty())
+ nBlock = *m_aMarks.begin() > nPosition ?
+ std::min(nBlock, sal_uInt32(*m_aMarks.begin()
+ - nPosition)) :
+ 0;
+
+ if (nBlock > 0)
+ {
+ memcpy(m_pReadBuffer + m_nReadBufferFilled, pBuffer,
+ nBlock);
+ m_nReadBufferFilled += nBlock;
+ nRemain -= nBlock;
+
+ nPosition += nBlock;
+ m_pWritePage->m_nOffset = (nPosition / m_nPageSize) * m_nPageSize;
+ m_pWritePage->m_pStart = m_pWritePage->m_aBuffer
+ + nPosition % m_nPageSize;
+ m_pWritePage->m_pRead = m_pWritePage->m_pStart;
+ m_pWritePage->m_pEnd = m_pWritePage->m_pStart;
+ }
+ }
+
+ if (nRemain <= 0)
+ return;
+
+ for (;;)
+ {
+ sal_uInt32 nBlock
+ = std::min(sal_uInt32(m_pWritePage->m_aBuffer + m_nPageSize
+ - m_pWritePage->m_pEnd),
+ nRemain);
+ memcpy(m_pWritePage->m_pEnd, pBuffer, nBlock);
+ m_pWritePage->m_pEnd += nBlock;
+ pBuffer += nBlock;
+ nRemain -= nBlock;
+
+ if (nRemain == 0)
+ break;
+
+ if (m_pWritePage->m_pNext == m_pFirstPage)
+ {
+ if (m_nPages == std::numeric_limits< sal_uInt32 >::max())
+ break;
+
+ Page * pNew
+ = static_cast< Page * >(std::malloc(
+ sizeof (Page) + m_nPageSize
+ - 1));
+ pNew->m_pPrev = m_pWritePage;
+ pNew->m_pNext = m_pWritePage->m_pNext;
+
+ m_pWritePage->m_pNext->m_pPrev = pNew;
+ m_pWritePage->m_pNext = pNew;
+ ++m_nPages;
+ }
+
+ m_pWritePage->m_pNext->m_nOffset = m_pWritePage->m_nOffset
+ + m_nPageSize;
+ m_pWritePage = m_pWritePage->m_pNext;
+ m_pWritePage->m_pStart = m_pWritePage->m_aBuffer;
+ m_pWritePage->m_pRead = m_pWritePage->m_aBuffer;
+ m_pWritePage->m_pEnd = m_pWritePage->m_aBuffer;
+ }
+}
+
+SvDataPipe_Impl::SeekResult SvDataPipe_Impl::setReadPosition(sal_uInt32
+ nPosition)
+{
+ if (m_pFirstPage == nullptr)
+ return nPosition == 0 ? SEEK_OK : SEEK_PAST_END;
+
+ if (nPosition
+ <= m_pReadPage->m_nOffset
+ + (m_pReadPage->m_pRead - m_pReadPage->m_aBuffer))
+ {
+ if (nPosition
+ < m_pFirstPage->m_nOffset
+ + (m_pFirstPage->m_pStart - m_pFirstPage->m_aBuffer))
+ return SEEK_BEFORE_MARKED;
+
+ while (nPosition < m_pReadPage->m_nOffset)
+ {
+ m_pReadPage->m_pRead = m_pReadPage->m_pStart;
+ m_pReadPage = m_pReadPage->m_pPrev;
+ }
+ }
+ else
+ {
+ if (nPosition
+ > m_pWritePage->m_nOffset
+ + (m_pWritePage->m_pEnd - m_pWritePage->m_aBuffer))
+ return SEEK_PAST_END;
+
+ while (m_pReadPage != m_pWritePage
+ && nPosition >= m_pReadPage->m_nOffset + m_nPageSize)
+ {
+ Page * pRemove = m_pReadPage;
+ m_pReadPage = pRemove->m_pNext;
+ remove(pRemove);
+ }
+ }
+
+ m_pReadPage->m_pRead = m_pReadPage->m_aBuffer
+ + (nPosition - m_pReadPage->m_nOffset);
+ return SEEK_OK;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/urihelper.cxx b/svl/source/misc/urihelper.cxx
new file mode 100644
index 0000000000..6488edb5bb
--- /dev/null
+++ b/svl/source/misc/urihelper.cxx
@@ -0,0 +1,884 @@
+/* -*- 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 <memory>
+#include <string_view>
+
+#include <sal/config.h>
+
+#include <unicode/idna.h>
+
+#include <svl/urihelper.hxx>
+#include <com/sun/star/ucb/Command.hpp>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <com/sun/star/ucb/UniversalContentBroker.hpp>
+#include <com/sun/star/ucb/UnsupportedCommandException.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/ucb/XCommandProcessor.hpp>
+#include <com/sun/star/ucb/XContent.hpp>
+#include <com/sun/star/ucb/XUniversalContentBroker.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/XComponentContext.hpp>
+#include <com/sun/star/uri/UriReferenceFactory.hpp>
+#include <com/sun/star/uri/XUriReference.hpp>
+#include <com/sun/star/uri/XUriReferenceFactory.hpp>
+#include <comphelper/processfactory.hxx>
+#include <osl/diagnose.h>
+#include <rtl/character.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <sal/log.hxx>
+#include <tools/inetmime.hxx>
+#include <unotools/charclass.hxx>
+
+using namespace com::sun::star;
+
+OUString URIHelper::SmartRel2Abs(INetURLObject const & rTheBaseURIRef,
+ OUString const & rTheRelURIRef,
+ Link<OUString *, bool> const & rMaybeFileHdl,
+ bool bCheckFileExists,
+ bool bIgnoreFragment,
+ INetURLObject::EncodeMechanism eEncodeMechanism,
+ INetURLObject::DecodeMechanism eDecodeMechanism,
+ rtl_TextEncoding eCharset,
+ FSysStyle eStyle)
+{
+ // Backwards compatibility:
+ if( rTheRelURIRef.startsWith("#") )
+ return rTheRelURIRef;
+
+ INetURLObject aAbsURIRef;
+ if (rTheBaseURIRef.HasError())
+ aAbsURIRef. SetSmartURL(rTheRelURIRef, eEncodeMechanism, eCharset, eStyle);
+ else
+ {
+ bool bWasAbsolute;
+ aAbsURIRef = rTheBaseURIRef.smartRel2Abs(rTheRelURIRef,
+ bWasAbsolute,
+ bIgnoreFragment,
+ eEncodeMechanism,
+ eCharset,
+ false/*bRelativeNonURIs*/,
+ eStyle);
+ if (bCheckFileExists
+ && !bWasAbsolute
+ && (aAbsURIRef.GetProtocol() == INetProtocol::File))
+ {
+ INetURLObject aNonFileURIRef;
+ aNonFileURIRef.SetSmartURL(rTheRelURIRef,
+ eEncodeMechanism,
+ eCharset,
+ eStyle);
+ if (!aNonFileURIRef.HasError()
+ && aNonFileURIRef.GetProtocol() != INetProtocol::File)
+ {
+ bool bMaybeFile = false;
+ if (rMaybeFileHdl.IsSet())
+ {
+ OUString aFilePath(rTheRelURIRef);
+ bMaybeFile = rMaybeFileHdl.Call(&aFilePath);
+ }
+ if (!bMaybeFile)
+ aAbsURIRef = aNonFileURIRef;
+ }
+ }
+ }
+ return aAbsURIRef.GetMainURL(eDecodeMechanism, eCharset);
+}
+
+namespace { Link<OUString *, bool> gMaybeFileHdl; }
+
+void URIHelper::SetMaybeFileHdl(Link<OUString *, bool> const & rTheMaybeFileHdl)
+{
+ gMaybeFileHdl = rTheMaybeFileHdl;
+}
+
+Link<OUString *, bool> const & URIHelper::GetMaybeFileHdl()
+{
+ return gMaybeFileHdl;
+}
+
+namespace {
+
+bool isAbsoluteHierarchicalUriReference(
+ css::uno::Reference< css::uri::XUriReference > const & uriReference)
+{
+ return uriReference.is() && uriReference->isAbsolute()
+ && !uriReference->hasRelativePath();
+}
+
+// To improve performance, assume that if for any prefix URL of a given
+// hierarchical URL either a UCB content cannot be created, or the UCB content
+// does not support the getCasePreservingURL command, then this will hold for
+// any other prefix URL of the given URL, too:
+enum Result { Success, GeneralFailure, SpecificFailure };
+
+Result normalizePrefix( css::uno::Reference< css::ucb::XUniversalContentBroker > const & broker,
+ OUString const & uri, OUString * normalized)
+{
+ OSL_ASSERT(broker.is() && normalized != nullptr);
+ css::uno::Reference< css::ucb::XContent > content;
+ try {
+ content = broker->queryContent(broker->createContentIdentifier(uri));
+ } catch (css::ucb::IllegalIdentifierException &) {}
+ if (!content.is()) {
+ return GeneralFailure;
+ }
+ try {
+ bool ok =
+ (css::uno::Reference< css::ucb::XCommandProcessor >(
+ content, css::uno::UNO_QUERY_THROW)->execute(
+ css::ucb::Command("getCasePreservingURL",
+ -1, css::uno::Any()),
+ 0,
+ css::uno::Reference< css::ucb::XCommandEnvironment >())
+ >>= *normalized);
+ OSL_ASSERT(ok);
+ } catch (css::uno::RuntimeException &) {
+ throw;
+ } catch (css::ucb::UnsupportedCommandException &) {
+ return GeneralFailure;
+ } catch (css::uno::Exception &) {
+ return SpecificFailure;
+ }
+ return Success;
+}
+
+OUString normalize(
+ css::uno::Reference< css::ucb::XUniversalContentBroker > const & broker,
+ css::uno::Reference< css::uri::XUriReferenceFactory > const & uriFactory,
+ OUString const & uriReference)
+{
+ // normalizePrefix can potentially fail (a typically example being a file
+ // URL that denotes a non-existing resource); in such a case, try to
+ // normalize as long a prefix of the given URL as possible (i.e., normalize
+ // all the existing directories within the path):
+ OUString normalized;
+ sal_Int32 n = uriReference.indexOf('#');
+ normalized = n == -1 ? uriReference : uriReference.copy(0, n);
+ switch (normalizePrefix(broker, normalized, &normalized)) {
+ case Success:
+ return n == -1 ? normalized : normalized + uriReference.subView(n);
+ case GeneralFailure:
+ return uriReference;
+ case SpecificFailure:
+ default:
+ break;
+ }
+ css::uno::Reference< css::uri::XUriReference > ref(
+ uriFactory->parse(uriReference));
+ if (!isAbsoluteHierarchicalUriReference(ref)) {
+ return uriReference;
+ }
+ sal_Int32 count = ref->getPathSegmentCount();
+ if (count < 2) {
+ return uriReference;
+ }
+ OUStringBuffer head(ref->getScheme());
+ head.append(':');
+ if (ref->hasAuthority()) {
+ head.append("//" + ref->getAuthority());
+ }
+ for (sal_Int32 i = count - 1; i > 0; --i) {
+ OUStringBuffer buf(head);
+ for (sal_Int32 j = 0; j < i; ++j) {
+ buf.append('/');
+ buf.append(ref->getPathSegment(j));
+ }
+ normalized = buf.makeStringAndClear();
+ if (normalizePrefix(broker, normalized, &normalized) != SpecificFailure)
+ {
+ buf.append(normalized);
+ css::uno::Reference< css::uri::XUriReference > preRef(
+ uriFactory->parse(normalized));
+ if (!isAbsoluteHierarchicalUriReference(preRef)) {
+ // This could only happen if something is inconsistent:
+ break;
+ }
+ sal_Int32 preCount = preRef->getPathSegmentCount();
+ // normalizePrefix may have added or removed a final slash:
+ if (preCount != i) {
+ if (preCount == i - 1) {
+ buf.append('/');
+ } else if (preCount - 1 == i && !buf.isEmpty()
+ && buf[buf.getLength() - 1] == '/')
+ {
+ buf.setLength(buf.getLength() - 1);
+ } else {
+ // This could only happen if something is inconsistent:
+ break;
+ }
+ }
+ for (sal_Int32 j = i; j < count; ++j) {
+ buf.append('/');
+ buf.append(ref->getPathSegment(j));
+ }
+ if (ref->hasQuery()) {
+ buf.append('?');
+ buf.append(ref->getQuery());
+ }
+ if (ref->hasFragment()) {
+ buf.append('#');
+ buf.append(ref->getFragment());
+ }
+ return buf.makeStringAndClear();
+ }
+ }
+ return uriReference;
+}
+
+}
+
+css::uno::Reference< css::uri::XUriReference >
+URIHelper::normalizedMakeRelative(
+ css::uno::Reference< css::uno::XComponentContext > const & context,
+ OUString const & baseUriReference, OUString const & uriReference)
+{
+ OSL_ASSERT(context.is());
+ css::uno::Reference< css::ucb::XUniversalContentBroker > broker(
+ css::ucb::UniversalContentBroker::create(context));
+ css::uno::Reference< css::uri::XUriReferenceFactory > uriFactory(
+ css::uri::UriReferenceFactory::create(context));
+ return uriFactory->makeRelative(
+ uriFactory->parse(normalize(broker, uriFactory, baseUriReference)),
+ uriFactory->parse(normalize(broker, uriFactory, uriReference)), true,
+ true, false);
+}
+
+OUString URIHelper::simpleNormalizedMakeRelative(
+ OUString const & baseUriReference, OUString const & uriReference)
+{
+ css::uno::Reference< css::uri::XUriReference > rel(
+ URIHelper::normalizedMakeRelative(
+ comphelper::getProcessComponentContext(), baseUriReference,
+ uriReference));
+ return rel.is() ? rel->getUriReference() : uriReference;
+}
+
+
+// FindFirstURLInText
+
+
+namespace {
+
+sal_Int32 nextChar(std::u16string_view rStr, sal_Int32 nPos)
+{
+ return rtl::isHighSurrogate(rStr[nPos])
+ && rStr.size() - nPos >= 2
+ && rtl::isLowSurrogate(rStr[nPos + 1]) ?
+ nPos + 2 : nPos + 1;
+}
+
+bool isBoundary1(CharClass const & rCharClass, OUString const & rStr,
+ sal_Int32 nPos, sal_Int32 nEnd)
+{
+ if (nPos == nEnd)
+ return true;
+ if (rCharClass.isLetterNumeric(rStr, nPos))
+ return false;
+ switch (rStr[nPos])
+ {
+ case '$':
+ case '%':
+ case '&':
+ case '-':
+ case '/':
+ case '@':
+ case '\\':
+ return false;
+ default:
+ return true;
+ }
+}
+
+bool isBoundary2(CharClass const & rCharClass, OUString const & rStr,
+ sal_Int32 nPos, sal_Int32 nEnd)
+{
+ if (nPos == nEnd)
+ return true;
+ if (rCharClass.isLetterNumeric(rStr, nPos))
+ return false;
+ switch (rStr[nPos])
+ {
+ case '!':
+ case '#':
+ case '$':
+ case '%':
+ case '&':
+ case '\'':
+ case '*':
+ case '+':
+ case '-':
+ case '/':
+ case '=':
+ case '?':
+ case '@':
+ case '^':
+ case '_':
+ case '`':
+ case '{':
+ case '|':
+ case '}':
+ case '~':
+ return false;
+ default:
+ return true;
+ }
+}
+
+// tdf#145381 Added MatchingBracketDepth counter to detect matching closing
+// brackets that are part of the uri
+bool checkWChar(CharClass const & rCharClass, OUString const & rStr,
+ sal_Int32 * pPos, sal_Int32 * pEnd,
+ sal_Int32 * pMatchingBracketDepth = nullptr,
+ bool bBackslash = false, bool bPipe = false)
+{
+ sal_Unicode c = rStr[*pPos];
+ if (rtl::isAscii(c))
+ {
+ static sal_uInt8 const aMap[128]
+ = { 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 4, 4, 4, 1, // !"#$%&'
+ 5, 6, 1, 1, 1, 4, 1, 4, // ()*+,-./
+ 4, 4, 4, 4, 4, 4, 4, 4, // 01234567
+ 4, 4, 1, 1, 0, 1, 0, 1, // 89:;<=>?
+ 4, 4, 4, 4, 4, 4, 4, 4, // @ABCDEFG
+ 4, 4, 4, 4, 4, 4, 4, 4, // HIJKLMNO
+ 4, 4, 4, 4, 4, 4, 4, 4, // PQRSTUVW
+ 4, 4, 4, 1, 2, 1, 0, 1, // XYZ[\]^_
+ 0, 4, 4, 4, 4, 4, 4, 4, // `abcdefg
+ 4, 4, 4, 4, 4, 4, 4, 4, // hijklmno
+ 4, 4, 4, 4, 4, 4, 4, 4, // pqrstuvw
+ 4, 4, 4, 0, 3, 0, 1, 0 }; // xyz{|}~
+ switch (aMap[c])
+ {
+ default: // not uric
+ return false;
+
+ case 1: // uric
+ ++(*pPos);
+ return true;
+
+ case 2: // "\"
+ if (bBackslash)
+ {
+ *pEnd = ++(*pPos);
+ return true;
+ }
+ else
+ return false;
+
+ case 3: // "|"
+ if (bPipe)
+ {
+ *pEnd = ++(*pPos);
+ return true;
+ }
+ else
+ return false;
+
+ case 4: // alpha, digit, "$", "%", "&", "-", "/", "@" (see
+ // isBoundary1)
+ *pEnd = ++(*pPos);
+ return true;
+
+ case 5: // opening bracket
+ ++(*pPos);
+ if(nullptr != pMatchingBracketDepth)
+ ++(*pMatchingBracketDepth);
+ return true;
+
+ case 6: // closing bracket
+ ++(*pPos);
+ if(nullptr != pMatchingBracketDepth && *pMatchingBracketDepth > 0)
+ {
+ --(*pMatchingBracketDepth);
+ // tdf#145381 When there was an opening bracket, detect this closing bracket
+ // as part of the uri
+ *pEnd = *pPos;
+ }
+ return true;
+
+ }
+ }
+ else if (rCharClass.isLetterNumeric(rStr, *pPos))
+ {
+ *pEnd = *pPos = nextChar(rStr, *pPos);
+ return true;
+ }
+ else
+ return false;
+}
+
+sal_uInt32 scanDomain(OUString const & rStr, sal_Int32 * pPos,
+ sal_Int32 nEnd)
+{
+ sal_Unicode const * pBuffer = rStr.getStr();
+ sal_Unicode const * p = pBuffer + *pPos;
+ sal_uInt32 nLabels = INetURLObject::scanDomain(p, pBuffer + nEnd, false);
+ *pPos = sal::static_int_cast< sal_Int32 >(p - pBuffer);
+ return nLabels;
+}
+
+}
+
+OUString URIHelper::FindFirstURLInText(OUString const & rText,
+ sal_Int32 & rBegin,
+ sal_Int32 & rEnd,
+ CharClass const & rCharClass,
+ INetURLObject::EncodeMechanism eMechanism,
+ rtl_TextEncoding eCharset)
+{
+ if (rBegin > rEnd || rEnd > rText.getLength())
+ return OUString();
+
+ // Search for the first substring of [rBegin..rEnd[ that matches any of the
+ // following productions (for which the appropriate style bit is set in
+ // eStyle, if applicable).
+
+ // 1st Production (known scheme):
+ // \B1 <one of the known schemes, except file> ":" 1*wchar ["#" 1*wchar]
+ // \B1
+
+ // 2nd Production (file):
+ // \B1 "FILE:" 1*(wchar / "\" / "|") ["#" 1*wchar] \B1
+
+ // 3rd Production (ftp):
+ // \B1 "FTP" 2*("." label) ["/" *wchar] ["#" 1*wchar] \B1
+
+ // 4th Production (http):
+ // \B1 "WWW" 2*("." label) ["/" *wchar] ["#" 1*wchar] \B1
+
+ // 5th Production (mailto):
+ // \B2 local-part "@" domain \B1
+
+ // 6th Production (UNC file):
+ // \B1 "\\" domain "\" *(wchar / "\") \B1
+
+ // 7th Production (DOS file):
+ // \B1 ALPHA ":\" *(wchar / "\") \B1
+
+ // 8th Production (Unix-like DOS file):
+ // \B1 ALPHA ":/" *(wchar / "\") \B1
+
+ // The productions use the following auxiliary rules.
+
+ // local-part = atom *("." atom)
+ // atom = 1*(alphanum / "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+"
+ // / "-" / "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}"
+ // / "~")
+ // domain = label *("." label)
+ // label = alphanum [*(alphanum / "-") alphanum]
+ // alphanum = ALPHA / DIGIT
+ // wchar = <any uric character (ignoring the escaped rule), or "%", or
+ // a letter or digit (according to rCharClass)>
+
+ // "\B1" (boundary 1) stands for the beginning or end of the block of text,
+ // or a character that is neither (a) a letter or digit (according to
+ // rCharClass), nor (b) any of "$", "%", "&", "-", "/", "@", or "\".
+ // (FIXME: What was the rationale for this set of punctuation characters?)
+
+ // "\B2" (boundary 2) stands for the beginning or end of the block of text,
+ // or a character that is neither (a) a letter or digit (according to
+ // rCharClass), nor (b) any of "!", "#", "$", "%", "&", "'", "*", "+", "-",
+ // "/", "=", "?", "@", "^", "_", "`", "{", "|", "}", or "~" (i.e., an RFC
+ // 822 <atom> character, or "@" from \B1's set above).
+
+ // Productions 1--4, and 6--8 try to find a maximum-length match, but they
+ // stop at the first <wchar> character that is a "\B1" character which is
+ // only followed by "\B1" characters (taking "\" and "|" characters into
+ // account appropriately). Production 5 simply tries to find a maximum-
+ // length match.
+
+ // Productions 1--4 use the given eMechanism and eCharset. Productions 5--9
+ // use EncodeMechanism::All.
+
+ // Productions 6--9 are only applicable if the FSysStyle::Dos bit is set in
+ // eStyle.
+
+ // tdf#145381: In addition to the productions I added a mechanism to detect
+ // matching brackets. The task presents the case of an url that ends on a
+ // closing bracket. This needs to be detected as part of the uri in the case
+ // that a matching opening bracket exists.
+
+ bool bBoundary1 = true;
+ bool bBoundary2 = true;
+ for (sal_Int32 nPos = rBegin; nPos != rEnd; nPos = nextChar(rText, nPos))
+ {
+ sal_Unicode c = rText[nPos];
+ if (bBoundary1)
+ {
+ if (rtl::isAsciiAlpha(c))
+ {
+ sal_Int32 i = nPos;
+ INetProtocol eScheme = INetURLObject::CompareProtocolScheme(rText.subView(i, rEnd - i));
+ if (eScheme == INetProtocol::File) // 2nd
+ {
+ while (rText[i++] != ':') ;
+ sal_Int32 nPrefixEnd = i;
+ sal_Int32 nUriEnd = i;
+ while (i != rEnd
+ && checkWChar(rCharClass, rText, &i, &nUriEnd, nullptr, true,
+ true)) ;
+ if (i != nPrefixEnd && i != rEnd && rText[i] == '#')
+ {
+ ++i;
+ while (i != rEnd
+ && checkWChar(rCharClass, rText, &i, &nUriEnd)) ;
+ }
+ if (nUriEnd != nPrefixEnd
+ && isBoundary1(rCharClass, rText, nUriEnd, rEnd))
+ {
+ INetURLObject aUri(rText.subView(nPos, nUriEnd - nPos),
+ INetProtocol::File, eMechanism, eCharset,
+ FSysStyle::Detect);
+ if (!aUri.HasError())
+ {
+ rBegin = nPos;
+ rEnd = nUriEnd;
+ return
+ aUri.GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+ }
+ }
+ }
+ else if (eScheme != INetProtocol::NotValid) // 1st
+ {
+ while (rText[i++] != ':') ;
+ sal_Int32 nPrefixEnd = i;
+ sal_Int32 nUriEnd = i;
+ sal_Int32 nMatchingBracketDepth = 0;
+ while (i != rEnd
+ && checkWChar(rCharClass, rText, &i, &nUriEnd,
+ &nMatchingBracketDepth)) ;
+ if (i != nPrefixEnd && i != rEnd && rText[i] == '#')
+ {
+ ++i;
+ while (i != rEnd
+ && checkWChar(rCharClass, rText, &i, &nUriEnd)) ;
+ }
+ if (nUriEnd != nPrefixEnd
+ && (isBoundary1(rCharClass, rText, nUriEnd, rEnd)
+ || rText[nUriEnd] == '\\'))
+ {
+ INetURLObject aUri(rText.subView(nPos, nUriEnd - nPos),
+ INetProtocol::Http, eMechanism,
+ eCharset);
+ if (!aUri.HasError())
+ {
+ rBegin = nPos;
+ rEnd = nUriEnd;
+ return
+ aUri.GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+ }
+ }
+ }
+
+ // 3rd, 4th:
+ i = nPos;
+ sal_uInt32 nLabels = scanDomain(rText, &i, rEnd);
+ if (nLabels >= 3
+ && rText[nPos + 3] == '.'
+ && (((rText[nPos] == 'w'
+ || rText[nPos] == 'W')
+ && (rText[nPos + 1] == 'w'
+ || rText[nPos + 1] == 'W')
+ && (rText[nPos + 2] == 'w'
+ || rText[nPos + 2] == 'W'))
+ || ((rText[nPos] == 'f'
+ || rText[nPos] == 'F')
+ && (rText[nPos + 1] == 't'
+ || rText[nPos + 1] == 'T')
+ && (rText[nPos + 2] == 'p'
+ || rText[nPos + 2] == 'P'))))
+ // (note that rText.GetChar(nPos + 3) is guaranteed to be
+ // valid)
+ {
+ sal_Int32 nUriEnd = i;
+ if (i != rEnd && rText[i] == '/')
+ {
+ nUriEnd = ++i;
+ while (i != rEnd
+ && checkWChar(rCharClass, rText, &i, &nUriEnd)) ;
+ }
+ if (i != rEnd && rText[i] == '#')
+ {
+ ++i;
+ while (i != rEnd
+ && checkWChar(rCharClass, rText, &i, &nUriEnd)) ;
+ }
+ if (isBoundary1(rCharClass, rText, nUriEnd, rEnd)
+ || rText[nUriEnd] == '\\')
+ {
+ INetURLObject aUri(rText.subView(nPos, nUriEnd - nPos),
+ INetProtocol::Http, eMechanism,
+ eCharset);
+ if (!aUri.HasError())
+ {
+ rBegin = nPos;
+ rEnd = nUriEnd;
+ return
+ aUri.GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+ }
+ }
+ }
+
+ if (rEnd - nPos >= 3
+ && rText[nPos + 1] == ':'
+ && (rText[nPos + 2] == '/'
+ || rText[nPos + 2] == '\\')) // 7th, 8th
+ {
+ i = nPos + 3;
+ sal_Int32 nUriEnd = i;
+ while (i != rEnd
+ && checkWChar(rCharClass, rText, &i, &nUriEnd)) ;
+ if (isBoundary1(rCharClass, rText, nUriEnd, rEnd))
+ {
+ INetURLObject aUri(rText.subView(nPos, nUriEnd - nPos),
+ INetProtocol::File,
+ INetURLObject::EncodeMechanism::All,
+ RTL_TEXTENCODING_UTF8,
+ FSysStyle::Dos);
+ if (!aUri.HasError())
+ {
+ rBegin = nPos;
+ rEnd = nUriEnd;
+ return
+ aUri.GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+ }
+ }
+ }
+ }
+ else if (rEnd - nPos >= 2
+ && rText[nPos] == '\\'
+ && rText[nPos + 1] == '\\') // 6th
+ {
+ sal_Int32 i = nPos + 2;
+ sal_uInt32 nLabels = scanDomain(rText, &i, rEnd);
+ if (nLabels >= 1 && i != rEnd && rText[i] == '\\')
+ {
+ sal_Int32 nUriEnd = ++i;
+ while (i != rEnd
+ && checkWChar(rCharClass, rText, &i, &nUriEnd,
+ nullptr, true)) ;
+ if (isBoundary1(rCharClass, rText, nUriEnd, rEnd))
+ {
+ INetURLObject aUri(rText.subView(nPos, nUriEnd - nPos),
+ INetProtocol::File,
+ INetURLObject::EncodeMechanism::All,
+ RTL_TEXTENCODING_UTF8,
+ FSysStyle::Dos);
+ if (!aUri.HasError())
+ {
+ rBegin = nPos;
+ rEnd = nUriEnd;
+ return
+ aUri.GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+ }
+ }
+ }
+ }
+ }
+ if (bBoundary2 && INetMIME::isAtomChar(c)) // 5th
+ {
+ bool bDot = false;
+ for (sal_Int32 i = nPos + 1; i != rEnd; ++i)
+ {
+ sal_Unicode c2 = rText[i];
+ if (INetMIME::isAtomChar(c2))
+ bDot = false;
+ else if (bDot)
+ break;
+ else if (c2 == '.')
+ bDot = true;
+ else
+ {
+ if (c2 == '@')
+ {
+ ++i;
+ sal_uInt32 nLabels = scanDomain(rText, &i, rEnd);
+ if (nLabels >= 1
+ && isBoundary1(rCharClass, rText, i, rEnd))
+ {
+ INetURLObject aUri(rText.subView(nPos, i - nPos),
+ INetProtocol::Mailto,
+ INetURLObject::EncodeMechanism::All);
+ if (!aUri.HasError())
+ {
+ rBegin = nPos;
+ rEnd = i;
+ return aUri.GetMainURL(
+ INetURLObject::DecodeMechanism::ToIUri);
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ bBoundary1 = isBoundary1(rCharClass, rText, nPos, rEnd);
+ bBoundary2 = isBoundary2(rCharClass, rText, nPos, rEnd);
+ }
+ rBegin = rEnd;
+ return OUString();
+}
+
+OUString URIHelper::FindFirstDOIInText(OUString const & rText,
+ sal_Int32 & rBegin,
+ sal_Int32 & rEnd,
+ CharClass const & rCharClass)
+{
+ if (rBegin > rEnd || rEnd > rText.getLength())
+ return OUString();
+
+ sal_Int32 start = 7;
+ sal_Int32 count = rEnd-rBegin;
+ OUString candidate(rText.subView(rBegin, count));
+ // Match with regex "doi:10\.\d{4,9}\/[-._;()\/:a-zA-Z0-9]+"
+ if (candidate.startsWithIgnoreAsciiCase("doi:10."))
+ {
+ bool flag = true;
+ sal_Int32 digit = 0;
+ for (sal_Int32 i=start; i<count; i++)
+ {
+ sal_Unicode c = candidate[i];
+ // Match 4 to 9 digits before slash
+ if (digit >= 0)
+ {
+ if (digit>9)
+ {
+ flag = false;
+ break;
+ }
+
+ if ( rCharClass.isDigit(candidate,i) )
+ {
+ digit++;
+ }
+ else if (c=='/' && digit>=4 && i<count-1)
+ {
+ digit=-1;
+ }
+ else
+ {
+ flag = false;
+ break;
+ }
+ }
+ // Match [-._;()\/:a-zA-Z0-9] after slash
+ else if (!( rCharClass.isAlphaNumeric(candidate, i) || c == '.' || c == '-' || c=='_' ||
+ c==';' || c=='(' || c==')' || c=='\\' || (c=='/' && i<count-1) || c==':'))
+ {
+ flag = false;
+ break;
+ }
+ }
+ if (flag && digit==-1)
+ {
+ return OUString::Concat("https://doi.org/")+candidate.subView(4);
+ }
+ }
+ rBegin = rEnd;
+ return OUString();
+}
+
+OUString URIHelper::removePassword(OUString const & rURI,
+ INetURLObject::EncodeMechanism eEncodeMechanism,
+ INetURLObject::DecodeMechanism eDecodeMechanism,
+ rtl_TextEncoding eCharset)
+{
+ INetURLObject aObj(rURI, eEncodeMechanism, eCharset);
+ return aObj.HasError() ?
+ rURI :
+ aObj.GetURLNoPass(eDecodeMechanism, eCharset);
+}
+
+OUString URIHelper::resolveIdnaHost(OUString const & url) {
+ css::uno::Reference<css::uri::XUriReference> uri(
+ css::uri::UriReferenceFactory::create(
+ comphelper::getProcessComponentContext())
+ ->parse(url));
+ if (!(uri.is() && uri->hasAuthority())) {
+ return url;
+ }
+ auto auth(uri->getAuthority());
+ if (auth.isEmpty())
+ return url;
+ sal_Int32 hostStart = auth.indexOf('@') + 1;
+ sal_Int32 hostEnd = auth.getLength();
+ while (hostEnd > hostStart && rtl::isAsciiDigit(auth[hostEnd - 1])) {
+ --hostEnd;
+ }
+ if (hostEnd > hostStart && auth[hostEnd - 1] == ':') {
+ --hostEnd;
+ } else {
+ hostEnd = auth.getLength();
+ }
+ auto asciiOnly = true;
+ for (auto i = hostStart; i != hostEnd; ++i) {
+ if (!rtl::isAscii(auth[i])) {
+ asciiOnly = false;
+ break;
+ }
+ }
+ if (asciiOnly) {
+ // Avoid icu::IDNA case normalization in purely non-IDNA domain names:
+ return url;
+ }
+ UErrorCode e = U_ZERO_ERROR;
+ std::unique_ptr<icu::IDNA> idna(
+ icu::IDNA::createUTS46Instance(
+ (UIDNA_USE_STD3_RULES | UIDNA_CHECK_BIDI | UIDNA_CHECK_CONTEXTJ | UIDNA_CHECK_CONTEXTO),
+ e));
+ if (U_FAILURE(e)) {
+ SAL_WARN("vcl.gdi", "icu::IDNA::createUTS46Instance " << e);
+ return url;
+ }
+ icu::UnicodeString ascii;
+ icu::IDNAInfo info;
+ idna->nameToASCII(
+ icu::UnicodeString(
+ reinterpret_cast<UChar const *>(auth.getStr() + hostStart),
+ hostEnd - hostStart),
+ ascii, info, e);
+ if (U_FAILURE(e) || info.hasErrors()) {
+ return url;
+ }
+ OUStringBuffer buf(uri->getScheme());
+ buf.append(OUString::Concat("://") + auth.subView(0, hostStart));
+ buf.append(
+ reinterpret_cast<sal_Unicode const *>(ascii.getBuffer()),
+ ascii.length());
+ buf.append(auth.subView(hostEnd) + uri->getPath());
+ if (uri->hasQuery()) {
+ buf.append("?" + uri->getQuery());
+ }
+ if (uri->hasFragment()) {
+ buf.append("#" + uri->getFragment());
+ }
+ return buf.makeStringAndClear();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */