diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /svl/source/misc | |
parent | Initial commit. (diff) | |
download | libreoffice-cb75148ebd0135178ff46f89a30139c44f8d2040.tar.xz libreoffice-cb75148ebd0135178ff46f89a30139c44f8d2040.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | svl/source/misc/PasswordHelper.cxx | 132 | ||||
-rw-r--r-- | svl/source/misc/adrparse.cxx | 556 | ||||
-rw-r--r-- | svl/source/misc/documentlockfile.cxx | 223 | ||||
-rw-r--r-- | svl/source/misc/filenotation.cxx | 122 | ||||
-rw-r--r-- | svl/source/misc/fstathelper.cxx | 94 | ||||
-rw-r--r-- | svl/source/misc/getstringresource.cxx | 31 | ||||
-rw-r--r-- | svl/source/misc/gridprinter.cxx | 130 | ||||
-rw-r--r-- | svl/source/misc/inethist.cxx | 371 | ||||
-rw-r--r-- | svl/source/misc/inettype.cxx | 441 | ||||
-rw-r--r-- | svl/source/misc/lngmisc.cxx | 132 | ||||
-rw-r--r-- | svl/source/misc/lockfilecommon.cxx | 244 | ||||
-rw-r--r-- | svl/source/misc/msodocumentlockfile.cxx | 273 | ||||
-rw-r--r-- | svl/source/misc/ownlist.cxx | 70 | ||||
-rw-r--r-- | svl/source/misc/sharecontrolfile.cxx | 331 | ||||
-rw-r--r-- | svl/source/misc/sharedstring.cxx | 69 | ||||
-rw-r--r-- | svl/source/misc/sharedstringpool.cxx | 184 | ||||
-rw-r--r-- | svl/source/misc/strmadpt.cxx | 653 | ||||
-rw-r--r-- | svl/source/misc/urihelper.cxx | 830 |
18 files changed, 4886 insertions, 0 deletions
diff --git a/svl/source/misc/PasswordHelper.cxx b/svl/source/misc/PasswordHelper.cxx new file mode 100644 index 000000000..41d7bf1ea --- /dev/null +++ b/svl/source/misc/PasswordHelper.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/PasswordHelper.hxx> +#include <comphelper/hash.hxx> +#include <rtl/digest.h> +#include <memory> + +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; +} + +/* 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 000000000..19e869a09 --- /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 000000000..09d67a9e5 --- /dev/null +++ b/svl/source/misc/documentlockfile.cxx @@ -0,0 +1,223 @@ +/* -*- 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() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + 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() +{ + ::osl::MutexGuard 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( 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() +{ + // 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( aNewEntry, xOutput ); + xOutput->closeOutput(); + } + catch( uno::Exception& ) + { + return false; + } + + return true; +} + +void GenDocumentLockFile::RemoveFile() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + // TODO/LATER: the removing is not atomic, is it possible in general to make it atomic? + LockFileEntry aNewEntry = GenerateOwnEntry(); + LockFileEntry aFileData = GetLockData(); + + 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)); +} + + +DocumentLockFile::DocumentLockFile( std::u16string_view aOrigURL ) + : GenDocumentLockFile(GenerateOwnLockFileURL(aOrigURL, u".~lock.")) +{ +} + + +DocumentLockFile::~DocumentLockFile() +{ +} + + +void DocumentLockFile::WriteEntryToStream( const LockFileEntry& aEntry, const uno::Reference< io::XOutputStream >& xOutput ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + 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.makeStringAndClear(), RTL_TEXTENCODING_UTF8 ) ); + uno::Sequence< sal_Int8 > aData( reinterpret_cast<sal_Int8 const *>(aStringData.getStr()), aStringData.getLength() ); + xOutput->writeBytes( aData ); +} + +LockFileEntry DocumentLockFile::GetLockData() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + uno::Reference< io::XInputStream > xInput = OpenStream(); + 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 000000000..c4337708b --- /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 000000000..6530ff724 --- /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 000000000..6b066c24d --- /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 000000000..ef83a3b21 --- /dev/null +++ b/svl/source/misc/gridprinter.cxx @@ -0,0 +1,130 @@ +/* -*- 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/custom_func1.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_trait +{ + typedef string_block string_element_block; + typedef mdds::mtv::uint16_element_block integer_element_block; + + typedef mdds::mtv::custom_block_func1<string_block> element_block_func; +}; + +} + +} + +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_trait> 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 ) +{ + mpImpl->maMatrix.set(nRow, nCol, rStr); +} + +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; + aBuf.append("+"); + 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 000000000..dc25d6b0b --- /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 000000000..b38346d70 --- /dev/null +++ b/svl/source/misc/inettype.cxx @@ -0,0 +1,441 @@ +/* -*- 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/wldcrd.hxx> +#include <tools/inetmime.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(OUString const & rURL) +{ + INetContentType eTypeID = CONTENT_TYPE_UNKNOWN; + sal_Int32 nIdx{ 0 }; + OUString aToken( rURL.getToken(0, ':', nIdx) ); + if (!aToken.isEmpty()) + { + if (aToken.equalsIgnoreAsciiCase(INETTYPE_URL_PROT_FILE)) + if (rURL[ rURL.getLength() - 1 ] == '/') // folder + if (rURL.getLength() > RTL_CONSTASCII_LENGTH("file:///")) + if (WildCard(u"*/{*}/").Matches(rURL)) // special folder + eTypeID = CONTENT_TYPE_X_CNT_FSYSSPECIALFOLDER; + else + // drive? -> "file:///?|/" + if (rURL.getLength() == 11 + && rURL[ rURL.getLength() - 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 = rURL.getToken(0, '/', nIdx); + if (aToken == "factory") + { + aToken = rURL.getToken(0, '/', nIdx); + if (aToken == "swriter") + { + aToken = rURL.getToken(0, '/', 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 = rURL.getToken(0, ',', 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 000000000..7ccf0aed7 --- /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 000000000..982ce7ce0 --- /dev/null +++ b/svl/source/misc/lockfilecommon.cxx @@ -0,0 +1,244 @@ +/* -*- 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 <stdio.h> + +#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 <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(OUStringConcatenation(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.makeStringAndClear(), 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 + 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 000000000..ce1bf287a --- /dev/null +++ b/svl/source/misc/msodocumentlockfile.cxx @@ -0,0 +1,273 @@ +/* -*- 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(OUStringConcatenation("~$" + 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( + const LockFileEntry& aEntry, const css::uno::Reference<css::io::XOutputStream>& xOutput) +{ + ::osl::MutexGuard aGuard(m_aMutex); + + // 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() +{ + ::osl::MutexGuard aGuard(m_aMutex); + + 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::GetLockData() +{ + ::osl::MutexGuard aGuard(m_aMutex); + + LockFileEntry aResult; + css::uno::Reference<css::io::XInputStream> xInput = OpenStream(); + 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() +{ + ::osl::MutexGuard aGuard(m_aMutex); + + // TODO/LATER: the removing is not atomic, is it possible in general to make it atomic? + LockFileEntry aNewEntry = GenerateOwnEntry(); + LockFileEntry aFileData = GetLockData(); + + 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 000000000..22b9d21fc --- /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 000000000..486f28053 --- /dev/null +++ b/svl/source/misc/sharecontrolfile.cxx @@ -0,0 +1,331 @@ +/* -*- 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() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + 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::vector< LockFileEntry >&& aUsersData ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + 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.makeStringAndClear(), 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() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( !IsValid() ) + throw io::NotConnectedException(); + + GetUsersData(); + 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( std::move(aNewData) ); + + return aNewEntry; +} + + +bool ShareControlFile::HasOwnEntry() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( !IsValid() ) + { + throw io::NotConnectedException(); + } + + GetUsersData(); + 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 ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( !IsValid() ) + throw io::NotConnectedException(); + + GetUsersData(); + + 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( std::move(aNewData) ); + + if ( bNewDataEmpty ) + { + // try to remove the file if it is empty + RemoveFile(); + } +} + + +void ShareControlFile::RemoveFile() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + 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 000000000..1bb05c6ac --- /dev/null +++ b/svl/source/misc/sharedstring.cxx @@ -0,0 +1,69 @@ +/* -*- 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 { + +/** ref-counting traffic associated with SharedString temporaries can be significant, so use a singleton here, so we can return a const& from getEmptyString */ +static OUString EMPTY(u""); +const SharedString EMPTY_SHARED_STRING(EMPTY.pData, EMPTY.pData); + +const SharedString & SharedString::getEmptyString() +{ + // unicode string array for empty string is globally shared in OUString. + // Let's take advantage of that. + 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 000000000..377ab5769 --- /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(OUString()); + assert(intern(OUString()) == 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 000000000..7a755d924 --- /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 000000000..1cb5c9f7c --- /dev/null +++ b/svl/source/misc/urihelper.cxx @@ -0,0 +1,830 @@ +/* -*- 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("//"); + 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::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 +#if U_ICU_VERSION_MAJOR_NUM >= 49 + | UIDNA_CHECK_CONTEXTO +#endif + ), + 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: */ |