1
0
Fork 0
libreoffice/unotools/source/ucbhelper/tempfile.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

777 lines
21 KiB
C++

/* -*- 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 <cassert>
#include <utility>
#include <com/sun/star/io/BufferSizeExceededException.hpp>
#include <com/sun/star/io/NotConnectedException.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <unotools/tempfile.hxx>
#include <rtl/ustring.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/char16_t2wchar_t.hxx>
#include <osl/mutex.hxx>
#include <osl/detail/file.h>
#include <osl/file.hxx>
#include <tools/time.hxx>
#include <tools/debug.hxx>
#include <tools/Guid.hxx>
#include <comphelper/DirectoryHelper.hxx>
#ifdef UNX
#include <unistd.h>
#elif defined( _WIN32 )
#include <process.h>
#endif
namespace
{
OUString gTempNameBase_Impl;
OUString ensureTrailingSlash(const OUString& url)
{
if (!url.isEmpty() && !url.endsWith("/"))
return url + "/";
return url;
}
OUString stripTrailingSlash(const OUString& url)
{
if (url.endsWith("/"))
return url.copy(0, url.getLength() - 1);
return url;
}
bool okOrExists(osl::FileBase::RC ret)
{
return ret == osl::FileBase::E_None || ret == osl::FileBase::E_EXIST;
}
const OUString& getTempNameBase_Impl()
{
if (gTempNameBase_Impl.isEmpty())
{
OUString ustrTempDirURL;
osl::FileBase::RC rc = osl::File::getTempDirURL(ustrTempDirURL);
if (rc == osl::FileBase::E_None)
{
gTempNameBase_Impl = ensureTrailingSlash(ustrTempDirURL);
osl::Directory::createPath(gTempNameBase_Impl);
}
}
assert(gTempNameBase_Impl.isEmpty() || gTempNameBase_Impl.endsWith("/"));
DBG_ASSERT(!gTempNameBase_Impl.isEmpty(), "No TempDir!");
return gTempNameBase_Impl;
}
OUString ConstructTempDir_Impl( const OUString* pParent, bool bCreateParentDirs )
{
OUString aName;
// Ignore pParent on iOS. We don't want to create any temp files
// in the same directory where the document being edited is.
#ifndef IOS
if ( pParent && !pParent->isEmpty() )
{
// test for valid filename
OUString aRet;
if ((osl::FileBase::getSystemPathFromFileURL(*pParent, aRet)
== osl::FileBase::E_None)
&& (osl::FileBase::getFileURLFromSystemPath(aRet, aRet)
== osl::FileBase::E_None))
{
osl::DirectoryItem aItem;
sal_Int32 i = aRet.getLength();
if ( aRet[i-1] == '/' )
i--;
if ( osl::DirectoryItem::get( aRet.copy(0, i), aItem ) == osl::FileBase::E_None || bCreateParentDirs )
aName = aRet;
}
}
#else
(void) pParent;
(void) bCreateParentDirs;
#endif
if ( aName.isEmpty() )
{
// if no parent or invalid parent : use default directory
aName = getTempNameBase_Impl();
osl::Directory::createPath(aName); // tdf#159769: always make sure it exists
}
// Make sure that directory ends with a separator
return ensureTrailingSlash(aName);
}
class Tokens {
public:
virtual bool next(OUString *) = 0;
protected:
virtual ~Tokens() {} // avoid warnings
};
class SequentialTokens: public Tokens {
public:
explicit SequentialTokens(bool showZero): m_value(0), m_show(showZero) {}
bool next(OUString * token) override {
assert(token != nullptr);
if (m_value == SAL_MAX_UINT32) {
return false;
}
*token = m_show ? OUString::number(m_value) : OUString();
++m_value;
m_show = true;
return true;
}
private:
sal_uInt32 m_value;
bool m_show;
};
class UniqueTokens: public Tokens {
public:
UniqueTokens(): m_count(0) {}
bool next(OUString * token) override {
assert(token != nullptr);
// Because of the shared globalValue, no single instance of UniqueTokens
// is guaranteed to exhaustively test all 36^6 possible values, but stop
// after that many attempts anyway:
sal_uInt32 radix = 36;
sal_uInt32 max = radix * radix * radix * radix * radix * radix;
// 36^6 == 2'176'782'336 < SAL_MAX_UINT32 == 4'294'967'295
if (m_count == max) {
return false;
}
sal_uInt32 v;
{
osl::MutexGuard g(osl::Mutex::getGlobalMutex());
globalValue
= ((globalValue == SAL_MAX_UINT32
? tools::Time::GetSystemTicks() : globalValue + 1)
% max);
v = globalValue;
}
*token = OUString::number(v, radix);
++m_count;
return true;
}
private:
static sal_uInt32 globalValue;
sal_uInt32 m_count;
};
sal_uInt32 UniqueTokens::globalValue = SAL_MAX_UINT32;
class TempDirCreatedObserver : public osl::DirectoryCreationObserver
{
public:
virtual void DirectoryCreated(const OUString& aDirectoryUrl) override
{
osl::File::setAttributes( aDirectoryUrl, osl_File_Attribute_OwnRead |
osl_File_Attribute_OwnWrite | osl_File_Attribute_OwnExe );
};
};
OUString lcl_createName(
std::u16string_view rLeadingChars, Tokens & tokens, std::u16string_view pExtension,
const OUString* pParent, bool bDirectory, bool bKeep, bool bLock,
bool bCreateParentDirs )
{
OUString aName = ConstructTempDir_Impl( pParent, bCreateParentDirs );
if ( bCreateParentDirs )
{
size_t nOffset = rLeadingChars.rfind(u"/");
OUString aDirName;
if (std::u16string_view::npos != nOffset)
aDirName = aName + rLeadingChars.substr( 0, nOffset );
else
aDirName = aName;
TempDirCreatedObserver observer;
if (!okOrExists(osl::Directory::createPath(aDirName, &observer)))
return OUString();
}
aName += rLeadingChars;
OUString token;
while (tokens.next(&token))
{
OUString aTmp( aName + token );
if ( !pExtension.empty() )
aTmp += pExtension;
else
aTmp += ".tmp";
if ( bDirectory )
{
osl::FileBase::RC err = osl::Directory::create(
aTmp,
(osl_File_OpenFlag_Read | osl_File_OpenFlag_Write
| osl_File_OpenFlag_Private));
if (err == osl::FileBase::E_None)
{
// !bKeep: only for creating a name, not a file or directory
if (bKeep || osl::Directory::remove(aTmp) == osl::FileBase::E_None)
return aTmp;
else
return OUString();
}
else if (err != osl::FileBase::E_EXIST)
// if f.e. name contains invalid chars stop trying to create dirs
return OUString();
}
else
{
DBG_ASSERT( bKeep, "Too expensive, use directory for creating name!" );
osl::File aFile(aTmp);
osl::FileBase::RC err = aFile.open(
osl_File_OpenFlag_Create | osl_File_OpenFlag_Private
| (bLock ? 0 : osl_File_OpenFlag_NoLock));
if (err == osl::FileBase::E_None || (bLock && err == osl::FileBase::E_NOLCK))
{
aFile.close();
return aTmp;
}
else if (err != osl::FileBase::E_EXIST)
{
// if f.e. name contains invalid chars stop trying to create dirs
// but if there is a folder with such name proceed further
osl::DirectoryItem aTmpItem;
osl::FileStatus aTmpStatus(osl_FileStatus_Mask_Type);
if (osl::DirectoryItem::get(aTmp, aTmpItem) != osl::FileBase::E_None
|| aTmpItem.getFileStatus(aTmpStatus) != osl::FileBase::E_None
|| aTmpStatus.getFileType() != osl::FileStatus::Directory)
return OUString();
}
}
}
return OUString();
}
OUString createEyeCatcher()
{
OUString eyeCatcher = u"lu"_ustr;
#ifdef DBG_UTIL
#ifdef UNX
if (const char* eye = getenv("LO_TESTNAME"))
eyeCatcher = OUString(eye, strlen(eye), RTL_TEXTENCODING_ASCII_US);
#elif defined(_WIN32)
if (const wchar_t* eye = _wgetenv(L"LO_TESTNAME"))
eyeCatcher = OUString(o3tl::toU(eye));
#endif
#else
#ifdef UNX
eyeCatcher += OUString::number(getpid());
#elif defined(_WIN32)
eyeCatcher += OUString::number(_getpid());
#endif
#endif
return eyeCatcher;
}
const OUString& getEyeCatcher()
{
static const OUString sEyeCatcher = createEyeCatcher();
return sEyeCatcher;
}
OUString CreateTempName_Impl( const OUString* pParent, bool bKeep, bool bDir = true )
{
UniqueTokens t;
return lcl_createName( getEyeCatcher(), t, u"", pParent, bDir, bKeep,
false, false);
}
OUString CreateTempNameFast()
{
OUString aName = getTempNameBase_Impl() + getEyeCatcher();
tools::Guid aGuid(tools::Guid::Generate);
return aName + aGuid.getOUString() + ".tmp" ;
}
}
namespace utl
{
OUString CreateTempName()
{
OUString aName(CreateTempName_Impl( nullptr, false ));
// convert to file URL
OUString aTmp;
if ( !aName.isEmpty() )
osl::FileBase::getSystemPathFromFileURL(aName, aTmp);
return aTmp;
}
TempFileFast::TempFileFast( )
{
}
TempFileFast::TempFileFast(TempFileFast && other) noexcept :
mxStream(std::move(other.mxStream))
{
}
TempFileFast::~TempFileFast()
{
CloseStream();
}
SvStream* TempFileFast::GetStream( StreamMode eMode )
{
if (!mxStream)
{
OUString aName = CreateTempNameFast();
#ifdef _WIN32
mxStream.reset(new SvFileStream(aName, eMode | StreamMode::TEMPORARY | StreamMode::DELETE_ON_CLOSE));
#else
mxStream.reset(new SvFileStream(aName, eMode | StreamMode::TEMPORARY));
#endif
}
return mxStream.get();
}
void TempFileFast::CloseStream()
{
if (mxStream)
{
#if !defined _WIN32
OUString aName = mxStream->GetFileName();
#endif
mxStream.reset();
#ifdef _WIN32
// On Windows, the file is opened with FILE_FLAG_DELETE_ON_CLOSE, so it will delete as soon as the handle closes.
// On other platforms, we need to explicitly delete it.
#else
if (!aName.isEmpty() && (osl::FileBase::getFileURLFromSystemPath(aName, aName) == osl::FileBase::E_None))
osl::File::remove(aName);
#endif
}
}
OUString CreateTempURL( const OUString* pParent, bool bDirectory )
{
return CreateTempName_Impl( pParent, true, bDirectory );
}
OUString CreateTempURL( std::u16string_view rLeadingChars, bool _bStartWithZero,
std::u16string_view pExtension, const OUString* pParent,
bool bCreateParentDirs )
{
SequentialTokens t(_bStartWithZero);
return lcl_createName( rLeadingChars, t, pExtension, pParent, false,
true, true, bCreateParentDirs );
}
TempFileNamed::TempFileNamed( const OUString* pParent, bool bDirectory )
: bIsDirectory( bDirectory )
, bKillingFileEnabled( false )
{
aName = CreateTempName_Impl( pParent, true, bDirectory );
}
TempFileNamed::TempFileNamed( std::u16string_view rLeadingChars, bool _bStartWithZero,
std::u16string_view pExtension, const OUString* pParent,
bool bCreateParentDirs )
: bIsDirectory( false )
, bKillingFileEnabled( false )
{
SequentialTokens t(_bStartWithZero);
aName = lcl_createName( rLeadingChars, t, pExtension, pParent, false,
true, true, bCreateParentDirs );
}
TempFileNamed::TempFileNamed(TempFileNamed && other) noexcept :
aName(std::move(other.aName)), pStream(std::move(other.pStream)), bIsDirectory(other.bIsDirectory),
bKillingFileEnabled(other.bKillingFileEnabled)
{
other.bKillingFileEnabled = false;
}
TempFileNamed::~TempFileNamed()
{
if ( !bKillingFileEnabled )
return;
pStream.reset();
if ( bIsDirectory )
{
comphelper::DirectoryHelper::deleteDirRecursively(aName);
}
else
{
osl::File::remove(aName);
}
}
bool TempFileNamed::IsValid() const
{
return !aName.isEmpty();
}
OUString TempFileNamed::GetFileName() const
{
OUString aTmp;
osl::FileBase::getSystemPathFromFileURL(aName, aTmp);
return aTmp;
}
OUString const & TempFileNamed::GetURL() const
{
// if you request the URL, then you presumably want to access this via UCB,
// and UCB will want to open the file via a separate file handle, which means
// we have to make this file data actually hit disk. We do this here (and not
// elsewhere) to make the other (normal) paths fast. Flushing to disk
// really slows temp files down.
if (pStream)
pStream->Flush();
return aName;
}
SvStream* TempFileNamed::GetStream( StreamMode eMode )
{
if (!pStream)
{
if (!aName.isEmpty())
pStream.reset(new SvFileStream(aName, eMode | StreamMode::TEMPORARY));
else
pStream.reset(new SvMemoryStream);
}
return pStream.get();
}
void TempFileNamed::CloseStream()
{
pStream.reset();
}
OUString SetTempNameBaseDirectory( const OUString &rBaseName )
{
if( rBaseName.isEmpty() )
return OUString();
// remove trailing slash
OUString aUnqPath(stripTrailingSlash(rBaseName));
// try to create the directory
bool bRet = okOrExists(osl::Directory::createPath(aUnqPath));
// failure to create base directory means returning an empty string
OUString aTmp;
if ( bRet )
{
// append own internal directory
gTempNameBase_Impl = ensureTrailingSlash(rBaseName);
TempFileNamed aBase( {}, true );
if ( aBase.IsValid() )
// use it in case of success
gTempNameBase_Impl = ensureTrailingSlash(aBase.GetURL());
// return system path of used directory
osl::FileBase::getSystemPathFromFileURL(gTempNameBase_Impl, aTmp);
}
return aTmp;
}
OUString GetTempNameBaseDirectory()
{
return ConstructTempDir_Impl(nullptr, false);
}
TempFileFastService::TempFileFastService()
: mbInClosed( false )
, mbOutClosed( false )
{
mpTempFile.emplace();
mpStream = mpTempFile->GetStream(StreamMode::READWRITE);
}
TempFileFastService::~TempFileFastService ()
{
}
// XInputStream
sal_Int32 SAL_CALL TempFileFastService::readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead )
{
std::unique_lock aGuard( maMutex );
if ( mbInClosed )
throw css::io::NotConnectedException ( OUString(), getXWeak() );
checkConnected();
if (nBytesToRead < 0)
throw css::io::BufferSizeExceededException( OUString(), getXWeak());
if (aData.getLength() < nBytesToRead)
aData.realloc(nBytesToRead);
sal_uInt32 nRead = mpStream->ReadBytes(static_cast<void*>(aData.getArray()), nBytesToRead);
checkError();
if (nRead < o3tl::make_unsigned(aData.getLength()))
aData.realloc( nRead );
return nRead;
}
sal_Int32 SAL_CALL TempFileFastService::readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead )
{
{
std::unique_lock aGuard( maMutex );
if ( mbInClosed )
throw css::io::NotConnectedException ( OUString(), getXWeak() );
checkConnected();
checkError();
if (nMaxBytesToRead < 0)
throw css::io::BufferSizeExceededException( OUString(), getXWeak() );
if (mpStream->eof())
{
aData.realloc(0);
return 0;
}
}
return readBytes(aData, nMaxBytesToRead);
}
// comphelper::ByteReader
sal_Int32 TempFileFastService::readSomeBytes( sal_Int8* aData, sal_Int32 nBytesToRead )
{
std::unique_lock aGuard( maMutex );
if ( mbInClosed )
throw css::io::NotConnectedException ( OUString(), getXWeak() );
checkConnected();
checkError();
if (nBytesToRead < 0)
throw css::io::BufferSizeExceededException( OUString(), getXWeak() );
if (mpStream->eof())
return 0;
sal_uInt32 nRead = mpStream->ReadBytes(aData, nBytesToRead);
checkError();
return nRead;
}
void SAL_CALL TempFileFastService::skipBytes( sal_Int32 nBytesToSkip )
{
std::unique_lock aGuard( maMutex );
if ( mbInClosed )
throw css::io::NotConnectedException ( OUString(), getXWeak() );
checkConnected();
checkError();
mpStream->SeekRel(nBytesToSkip);
checkError();
}
sal_Int32 SAL_CALL TempFileFastService::available()
{
std::unique_lock aGuard( maMutex );
if ( mbInClosed )
throw css::io::NotConnectedException ( OUString(), getXWeak() );
checkConnected();
sal_Int64 nAvailable = mpStream->remainingSize();
checkError();
return std::min<sal_Int64>(SAL_MAX_INT32, nAvailable);
}
void SAL_CALL TempFileFastService::closeInput()
{
std::unique_lock aGuard( maMutex );
if ( mbInClosed )
throw css::io::NotConnectedException ( OUString(), getXWeak() );
mbInClosed = true;
if ( mbOutClosed )
{
// stream will be deleted by TempFile implementation
mpStream = nullptr;
mpTempFile.reset();
}
}
// XOutputStream
void SAL_CALL TempFileFastService::writeBytes( const css::uno::Sequence< sal_Int8 >& aData )
{
std::unique_lock aGuard( maMutex );
if ( mbOutClosed )
throw css::io::NotConnectedException ( OUString(), getXWeak() );
checkConnected();
sal_uInt32 nWritten = mpStream->WriteBytes(aData.getConstArray(), aData.getLength());
checkError();
if ( nWritten != static_cast<sal_uInt32>(aData.getLength()))
throw css::io::BufferSizeExceededException( OUString(), getXWeak() );
}
// comphelper::ByteWriter
void TempFileFastService::writeBytes( const sal_Int8* aData, sal_Int32 nBytesToWrite )
{
std::unique_lock aGuard( maMutex );
if ( mbOutClosed )
throw css::io::NotConnectedException ( OUString(), getXWeak() );
checkConnected();
sal_uInt32 nWritten = mpStream->WriteBytes(aData, nBytesToWrite);
checkError();
if ( nWritten != o3tl::make_unsigned(nBytesToWrite) )
throw css::io::BufferSizeExceededException( OUString(), getXWeak() );
}
void SAL_CALL TempFileFastService::flush()
{
std::unique_lock aGuard( maMutex );
if ( mbOutClosed )
throw css::io::NotConnectedException ( OUString(), getXWeak() );
checkConnected();
mpStream->Flush();
checkError();
}
void SAL_CALL TempFileFastService::closeOutput()
{
std::unique_lock aGuard( maMutex );
if ( mbOutClosed )
throw css::io::NotConnectedException ( OUString(), getXWeak() );
mbOutClosed = true;
if (mpStream)
{
// so that if you then open the InputStream, you can read the content
mpStream->FlushBuffer();
mpStream->Seek(0);
}
if ( mbInClosed )
{
// stream will be deleted by TempFile implementation
mpStream = nullptr;
mpTempFile.reset();
}
}
void TempFileFastService::checkError() const
{
if (!mpStream || mpStream->SvStream::GetError () != ERRCODE_NONE )
throw css::io::NotConnectedException ( OUString(), const_cast < TempFileFastService * > (this)->getXWeak() );
}
void TempFileFastService::checkConnected()
{
if (!mpStream)
throw css::io::NotConnectedException ( OUString(), getXWeak() );
}
// XSeekable
void SAL_CALL TempFileFastService::seek( sal_Int64 nLocation )
{
std::unique_lock aGuard( maMutex );
checkConnected();
checkError();
if ( nLocation < 0 )
throw css::lang::IllegalArgumentException();
sal_Int64 nNewLoc = mpStream->Seek(static_cast<sal_uInt32>(nLocation) );
if ( nNewLoc != nLocation )
throw css::lang::IllegalArgumentException();
checkError();
}
sal_Int64 SAL_CALL TempFileFastService::getPosition()
{
std::unique_lock aGuard( maMutex );
checkConnected();
sal_uInt64 nPos = mpStream->Tell();
checkError();
return static_cast<sal_Int64>(nPos);
}
sal_Int64 SAL_CALL TempFileFastService::getLength()
{
std::unique_lock aGuard( maMutex );
checkConnected();
checkError();
sal_Int64 nEndPos = mpStream->TellEnd();
return nEndPos;
}
// XStream
css::uno::Reference< css::io::XInputStream > SAL_CALL TempFileFastService::getInputStream()
{
return this;
}
css::uno::Reference< css::io::XOutputStream > SAL_CALL TempFileFastService::getOutputStream()
{
return this;
}
// XTruncate
void SAL_CALL TempFileFastService::truncate()
{
std::unique_lock aGuard( maMutex );
checkConnected();
// SetStreamSize() call does not change the position
mpStream->Seek( 0 );
mpStream->SetStreamSize( 0 );
checkError();
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */