cid#1640468 Dereference after null check
[LibreOffice.git] / unotools / source / ucbhelper / tempfile.cxx
blob7862bc5f934d136d118198e2cf29ce389edfa8ae
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
22 #include <cassert>
23 #include <utility>
25 #include <com/sun/star/io/BufferSizeExceededException.hpp>
26 #include <com/sun/star/io/NotConnectedException.hpp>
27 #include <com/sun/star/lang/IllegalArgumentException.hpp>
28 #include <unotools/tempfile.hxx>
29 #include <rtl/ustring.hxx>
30 #include <o3tl/safeint.hxx>
31 #include <o3tl/char16_t2wchar_t.hxx>
32 #include <osl/mutex.hxx>
33 #include <osl/detail/file.h>
34 #include <osl/file.hxx>
35 #include <tools/time.hxx>
36 #include <tools/debug.hxx>
37 #include <tools/Guid.hxx>
38 #include <comphelper/DirectoryHelper.hxx>
40 #ifdef UNX
41 #include <unistd.h>
42 #elif defined( _WIN32 )
43 #include <process.h>
44 #endif
46 namespace
48 OUString gTempNameBase_Impl;
50 OUString ensureTrailingSlash(const OUString& url)
52 if (!url.isEmpty() && !url.endsWith("/"))
53 return url + "/";
54 return url;
57 OUString stripTrailingSlash(const OUString& url)
59 if (url.endsWith("/"))
60 return url.copy(0, url.getLength() - 1);
61 return url;
64 bool okOrExists(osl::FileBase::RC ret)
66 return ret == osl::FileBase::E_None || ret == osl::FileBase::E_EXIST;
69 const OUString& getTempNameBase_Impl()
71 if (gTempNameBase_Impl.isEmpty())
73 OUString ustrTempDirURL;
74 osl::FileBase::RC rc = osl::File::getTempDirURL(ustrTempDirURL);
75 if (rc == osl::FileBase::E_None)
77 gTempNameBase_Impl = ensureTrailingSlash(ustrTempDirURL);
78 osl::Directory::createPath(gTempNameBase_Impl);
81 assert(gTempNameBase_Impl.isEmpty() || gTempNameBase_Impl.endsWith("/"));
82 DBG_ASSERT(!gTempNameBase_Impl.isEmpty(), "No TempDir!");
83 return gTempNameBase_Impl;
86 OUString ConstructTempDir_Impl( const OUString* pParent, bool bCreateParentDirs )
88 OUString aName;
90 // Ignore pParent on iOS. We don't want to create any temp files
91 // in the same directory where the document being edited is.
92 #ifndef IOS
93 if ( pParent && !pParent->isEmpty() )
95 // test for valid filename
96 OUString aRet;
97 if ((osl::FileBase::getSystemPathFromFileURL(*pParent, aRet)
98 == osl::FileBase::E_None)
99 && (osl::FileBase::getFileURLFromSystemPath(aRet, aRet)
100 == osl::FileBase::E_None))
102 osl::DirectoryItem aItem;
103 sal_Int32 i = aRet.getLength();
104 if ( aRet[i-1] == '/' )
105 i--;
107 if ( osl::DirectoryItem::get( aRet.copy(0, i), aItem ) == osl::FileBase::E_None || bCreateParentDirs )
108 aName = aRet;
111 #else
112 (void) pParent;
113 (void) bCreateParentDirs;
114 #endif
116 if ( aName.isEmpty() )
118 // if no parent or invalid parent : use default directory
119 aName = getTempNameBase_Impl();
120 osl::Directory::createPath(aName); // tdf#159769: always make sure it exists
123 // Make sure that directory ends with a separator
124 return ensureTrailingSlash(aName);
127 class Tokens {
128 public:
129 virtual bool next(OUString *) = 0;
131 protected:
132 virtual ~Tokens() {} // avoid warnings
135 class SequentialTokens: public Tokens {
136 public:
137 explicit SequentialTokens(bool showZero): m_value(0), m_show(showZero) {}
139 bool next(OUString * token) override {
140 assert(token != nullptr);
141 if (m_value == SAL_MAX_UINT32) {
142 return false;
144 *token = m_show ? OUString::number(m_value) : OUString();
145 ++m_value;
146 m_show = true;
147 return true;
150 private:
151 sal_uInt32 m_value;
152 bool m_show;
155 class UniqueTokens: public Tokens {
156 public:
157 UniqueTokens(): m_count(0) {}
159 bool next(OUString * token) override {
160 assert(token != nullptr);
161 // Because of the shared globalValue, no single instance of UniqueTokens
162 // is guaranteed to exhaustively test all 36^6 possible values, but stop
163 // after that many attempts anyway:
164 sal_uInt32 radix = 36;
165 sal_uInt32 max = radix * radix * radix * radix * radix * radix;
166 // 36^6 == 2'176'782'336 < SAL_MAX_UINT32 == 4'294'967'295
167 if (m_count == max) {
168 return false;
170 sal_uInt32 v;
172 osl::MutexGuard g(osl::Mutex::getGlobalMutex());
173 globalValue
174 = ((globalValue == SAL_MAX_UINT32
175 ? tools::Time::GetSystemTicks() : globalValue + 1)
176 % max);
177 v = globalValue;
179 *token = OUString::number(v, radix);
180 ++m_count;
181 return true;
184 private:
185 static sal_uInt32 globalValue;
187 sal_uInt32 m_count;
190 sal_uInt32 UniqueTokens::globalValue = SAL_MAX_UINT32;
192 class TempDirCreatedObserver : public osl::DirectoryCreationObserver
194 public:
195 virtual void DirectoryCreated(const OUString& aDirectoryUrl) override
197 osl::File::setAttributes( aDirectoryUrl, osl_File_Attribute_OwnRead |
198 osl_File_Attribute_OwnWrite | osl_File_Attribute_OwnExe );
202 OUString lcl_createName(
203 std::u16string_view rLeadingChars, Tokens & tokens, std::u16string_view pExtension,
204 const OUString* pParent, bool bDirectory, bool bKeep, bool bLock,
205 bool bCreateParentDirs )
207 OUString aName = ConstructTempDir_Impl( pParent, bCreateParentDirs );
208 if ( bCreateParentDirs )
210 size_t nOffset = rLeadingChars.rfind(u"/");
211 OUString aDirName;
212 if (std::u16string_view::npos != nOffset)
213 aDirName = aName + rLeadingChars.substr( 0, nOffset );
214 else
215 aDirName = aName;
216 TempDirCreatedObserver observer;
217 if (!okOrExists(osl::Directory::createPath(aDirName, &observer)))
218 return OUString();
220 aName += rLeadingChars;
222 OUString token;
223 while (tokens.next(&token))
225 OUString aTmp( aName + token );
226 if ( !pExtension.empty() )
227 aTmp += pExtension;
228 else
229 aTmp += ".tmp";
230 if ( bDirectory )
232 osl::FileBase::RC err = osl::Directory::create(
233 aTmp,
234 (osl_File_OpenFlag_Read | osl_File_OpenFlag_Write
235 | osl_File_OpenFlag_Private));
236 if (err == osl::FileBase::E_None)
238 // !bKeep: only for creating a name, not a file or directory
239 if (bKeep || osl::Directory::remove(aTmp) == osl::FileBase::E_None)
240 return aTmp;
241 else
242 return OUString();
244 else if (err != osl::FileBase::E_EXIST)
245 // if f.e. name contains invalid chars stop trying to create dirs
246 return OUString();
248 else
250 DBG_ASSERT( bKeep, "Too expensive, use directory for creating name!" );
251 osl::File aFile(aTmp);
252 osl::FileBase::RC err = aFile.open(
253 osl_File_OpenFlag_Create | osl_File_OpenFlag_Private
254 | (bLock ? 0 : osl_File_OpenFlag_NoLock));
255 if (err == osl::FileBase::E_None || (bLock && err == osl::FileBase::E_NOLCK))
257 aFile.close();
258 return aTmp;
260 else if (err != osl::FileBase::E_EXIST)
262 // if f.e. name contains invalid chars stop trying to create dirs
263 // but if there is a folder with such name proceed further
265 osl::DirectoryItem aTmpItem;
266 osl::FileStatus aTmpStatus(osl_FileStatus_Mask_Type);
267 if (osl::DirectoryItem::get(aTmp, aTmpItem) != osl::FileBase::E_None
268 || aTmpItem.getFileStatus(aTmpStatus) != osl::FileBase::E_None
269 || aTmpStatus.getFileType() != osl::FileStatus::Directory)
270 return OUString();
274 return OUString();
277 OUString createEyeCatcher()
279 OUString eyeCatcher = u"lu"_ustr;
280 #ifdef DBG_UTIL
281 #ifdef UNX
282 if (const char* eye = getenv("LO_TESTNAME"))
283 eyeCatcher = OUString(eye, strlen(eye), RTL_TEXTENCODING_ASCII_US);
284 #elif defined(_WIN32)
285 if (const wchar_t* eye = _wgetenv(L"LO_TESTNAME"))
286 eyeCatcher = OUString(o3tl::toU(eye));
287 #endif
288 #else
289 #ifdef UNX
290 eyeCatcher += OUString::number(getpid());
291 #elif defined(_WIN32)
292 eyeCatcher += OUString::number(_getpid());
293 #endif
294 #endif
295 return eyeCatcher;
298 const OUString& getEyeCatcher()
300 static const OUString sEyeCatcher = createEyeCatcher();
301 return sEyeCatcher;
304 OUString CreateTempName_Impl( const OUString* pParent, bool bKeep, bool bDir = true )
306 UniqueTokens t;
307 return lcl_createName( getEyeCatcher(), t, u"", pParent, bDir, bKeep,
308 false, false);
311 OUString CreateTempNameFast()
313 OUString aName = getTempNameBase_Impl() + getEyeCatcher();
315 tools::Guid aGuid(tools::Guid::Generate);
317 return aName + aGuid.getOUString() + ".tmp" ;
321 namespace utl
324 OUString CreateTempName()
326 OUString aName(CreateTempName_Impl( nullptr, false ));
328 // convert to file URL
329 OUString aTmp;
330 if ( !aName.isEmpty() )
331 osl::FileBase::getSystemPathFromFileURL(aName, aTmp);
332 return aTmp;
335 TempFileFast::TempFileFast( )
339 TempFileFast::TempFileFast(TempFileFast && other) noexcept :
340 mxStream(std::move(other.mxStream))
344 TempFileFast::~TempFileFast()
346 CloseStream();
349 SvStream* TempFileFast::GetStream( StreamMode eMode )
351 if (!mxStream)
353 OUString aName = CreateTempNameFast();
354 #ifdef _WIN32
355 mxStream.reset(new SvFileStream(aName, eMode | StreamMode::TEMPORARY | StreamMode::DELETE_ON_CLOSE));
356 #else
357 mxStream.reset(new SvFileStream(aName, eMode | StreamMode::TEMPORARY));
358 #endif
360 return mxStream.get();
363 void TempFileFast::CloseStream()
365 if (mxStream)
367 #if !defined _WIN32
368 OUString aName = mxStream->GetFileName();
369 #endif
370 mxStream.reset();
371 #ifdef _WIN32
372 // On Windows, the file is opened with FILE_FLAG_DELETE_ON_CLOSE, so it will delete as soon as the handle closes.
373 // On other platforms, we need to explicitly delete it.
374 #else
375 if (!aName.isEmpty() && (osl::FileBase::getFileURLFromSystemPath(aName, aName) == osl::FileBase::E_None))
376 osl::File::remove(aName);
377 #endif
381 OUString CreateTempURL( const OUString* pParent, bool bDirectory )
383 return CreateTempName_Impl( pParent, true, bDirectory );
386 OUString CreateTempURL( std::u16string_view rLeadingChars, bool _bStartWithZero,
387 std::u16string_view pExtension, const OUString* pParent,
388 bool bCreateParentDirs )
390 SequentialTokens t(_bStartWithZero);
391 return lcl_createName( rLeadingChars, t, pExtension, pParent, false,
392 true, true, bCreateParentDirs );
395 TempFileNamed::TempFileNamed( const OUString* pParent, bool bDirectory )
396 : bIsDirectory( bDirectory )
397 , bKillingFileEnabled( false )
399 aName = CreateTempName_Impl( pParent, true, bDirectory );
402 TempFileNamed::TempFileNamed( std::u16string_view rLeadingChars, bool _bStartWithZero,
403 std::u16string_view pExtension, const OUString* pParent,
404 bool bCreateParentDirs )
405 : bIsDirectory( false )
406 , bKillingFileEnabled( false )
408 SequentialTokens t(_bStartWithZero);
409 aName = lcl_createName( rLeadingChars, t, pExtension, pParent, false,
410 true, true, bCreateParentDirs );
413 TempFileNamed::TempFileNamed(TempFileNamed && other) noexcept :
414 aName(std::move(other.aName)), pStream(std::move(other.pStream)), bIsDirectory(other.bIsDirectory),
415 bKillingFileEnabled(other.bKillingFileEnabled)
417 other.bKillingFileEnabled = false;
420 TempFileNamed::~TempFileNamed()
422 if ( !bKillingFileEnabled )
423 return;
425 pStream.reset();
426 if ( bIsDirectory )
428 comphelper::DirectoryHelper::deleteDirRecursively(aName);
430 else
432 osl::File::remove(aName);
436 bool TempFileNamed::IsValid() const
438 return !aName.isEmpty();
441 OUString TempFileNamed::GetFileName() const
443 OUString aTmp;
444 osl::FileBase::getSystemPathFromFileURL(aName, aTmp);
445 return aTmp;
448 OUString const & TempFileNamed::GetURL() const
450 // if you request the URL, then you presumably want to access this via UCB,
451 // and UCB will want to open the file via a separate file handle, which means
452 // we have to make this file data actually hit disk. We do this here (and not
453 // elsewhere) to make the other (normal) paths fast. Flushing to disk
454 // really slows temp files down.
455 if (pStream)
456 pStream->Flush();
457 return aName;
460 SvStream* TempFileNamed::GetStream( StreamMode eMode )
462 if (!pStream)
464 if (!aName.isEmpty())
465 pStream.reset(new SvFileStream(aName, eMode | StreamMode::TEMPORARY));
466 else
467 pStream.reset(new SvMemoryStream);
470 return pStream.get();
473 void TempFileNamed::CloseStream()
475 pStream.reset();
478 OUString SetTempNameBaseDirectory( const OUString &rBaseName )
480 if( rBaseName.isEmpty() )
481 return OUString();
483 // remove trailing slash
484 OUString aUnqPath(stripTrailingSlash(rBaseName));
486 // try to create the directory
487 bool bRet = okOrExists(osl::Directory::createPath(aUnqPath));
489 // failure to create base directory means returning an empty string
490 OUString aTmp;
491 if ( bRet )
493 // append own internal directory
494 gTempNameBase_Impl = ensureTrailingSlash(rBaseName);
496 TempFileNamed aBase( {}, true );
497 if ( aBase.IsValid() )
498 // use it in case of success
499 gTempNameBase_Impl = ensureTrailingSlash(aBase.GetURL());
501 // return system path of used directory
502 osl::FileBase::getSystemPathFromFileURL(gTempNameBase_Impl, aTmp);
505 return aTmp;
508 OUString GetTempNameBaseDirectory()
510 return ConstructTempDir_Impl(nullptr, false);
514 TempFileFastService::TempFileFastService()
515 : mbInClosed( false )
516 , mbOutClosed( false )
518 mpTempFile.emplace();
519 mpStream = mpTempFile->GetStream(StreamMode::READWRITE);
522 TempFileFastService::~TempFileFastService ()
526 // XInputStream
528 sal_Int32 SAL_CALL TempFileFastService::readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead )
530 std::unique_lock aGuard( maMutex );
531 if ( mbInClosed )
532 throw css::io::NotConnectedException ( OUString(), getXWeak() );
534 checkConnected();
535 if (nBytesToRead < 0)
536 throw css::io::BufferSizeExceededException( OUString(), getXWeak());
538 if (aData.getLength() < nBytesToRead)
539 aData.realloc(nBytesToRead);
541 sal_uInt32 nRead = mpStream->ReadBytes(static_cast<void*>(aData.getArray()), nBytesToRead);
542 checkError();
544 if (nRead < o3tl::make_unsigned(aData.getLength()))
545 aData.realloc( nRead );
547 return nRead;
550 sal_Int32 SAL_CALL TempFileFastService::readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead )
553 std::unique_lock aGuard( maMutex );
554 if ( mbInClosed )
555 throw css::io::NotConnectedException ( OUString(), getXWeak() );
557 checkConnected();
558 checkError();
560 if (nMaxBytesToRead < 0)
561 throw css::io::BufferSizeExceededException( OUString(), getXWeak() );
563 if (mpStream->eof())
565 aData.realloc(0);
566 return 0;
569 return readBytes(aData, nMaxBytesToRead);
572 // comphelper::ByteReader
573 sal_Int32 TempFileFastService::readSomeBytes( sal_Int8* aData, sal_Int32 nBytesToRead )
575 std::unique_lock aGuard( maMutex );
576 if ( mbInClosed )
577 throw css::io::NotConnectedException ( OUString(), getXWeak() );
579 checkConnected();
580 checkError();
582 if (nBytesToRead < 0)
583 throw css::io::BufferSizeExceededException( OUString(), getXWeak() );
585 if (mpStream->eof())
586 return 0;
588 sal_uInt32 nRead = mpStream->ReadBytes(aData, nBytesToRead);
589 checkError();
591 return nRead;
594 void SAL_CALL TempFileFastService::skipBytes( sal_Int32 nBytesToSkip )
596 std::unique_lock aGuard( maMutex );
597 if ( mbInClosed )
598 throw css::io::NotConnectedException ( OUString(), getXWeak() );
600 checkConnected();
601 checkError();
602 mpStream->SeekRel(nBytesToSkip);
603 checkError();
606 sal_Int32 SAL_CALL TempFileFastService::available()
608 std::unique_lock aGuard( maMutex );
609 if ( mbInClosed )
610 throw css::io::NotConnectedException ( OUString(), getXWeak() );
612 checkConnected();
614 sal_Int64 nAvailable = mpStream->remainingSize();
615 checkError();
617 return std::min<sal_Int64>(SAL_MAX_INT32, nAvailable);
620 void SAL_CALL TempFileFastService::closeInput()
622 std::unique_lock aGuard( maMutex );
623 if ( mbInClosed )
624 throw css::io::NotConnectedException ( OUString(), getXWeak() );
626 mbInClosed = true;
628 if ( mbOutClosed )
630 // stream will be deleted by TempFile implementation
631 mpStream = nullptr;
632 mpTempFile.reset();
636 // XOutputStream
638 void SAL_CALL TempFileFastService::writeBytes( const css::uno::Sequence< sal_Int8 >& aData )
640 std::unique_lock aGuard( maMutex );
641 if ( mbOutClosed )
642 throw css::io::NotConnectedException ( OUString(), getXWeak() );
644 checkConnected();
645 sal_uInt32 nWritten = mpStream->WriteBytes(aData.getConstArray(), aData.getLength());
646 checkError();
647 if ( nWritten != static_cast<sal_uInt32>(aData.getLength()))
648 throw css::io::BufferSizeExceededException( OUString(), getXWeak() );
651 // comphelper::ByteWriter
653 void TempFileFastService::writeBytes( const sal_Int8* aData, sal_Int32 nBytesToWrite )
655 std::unique_lock aGuard( maMutex );
656 if ( mbOutClosed )
657 throw css::io::NotConnectedException ( OUString(), getXWeak() );
659 checkConnected();
660 sal_uInt32 nWritten = mpStream->WriteBytes(aData, nBytesToWrite);
661 checkError();
662 if ( nWritten != o3tl::make_unsigned(nBytesToWrite) )
663 throw css::io::BufferSizeExceededException( OUString(), getXWeak() );
666 void SAL_CALL TempFileFastService::flush()
668 std::unique_lock aGuard( maMutex );
669 if ( mbOutClosed )
670 throw css::io::NotConnectedException ( OUString(), getXWeak() );
672 checkConnected();
673 mpStream->Flush();
674 checkError();
677 void SAL_CALL TempFileFastService::closeOutput()
679 std::unique_lock aGuard( maMutex );
680 if ( mbOutClosed )
681 throw css::io::NotConnectedException ( OUString(), getXWeak() );
683 mbOutClosed = true;
684 if (mpStream)
686 // so that if you then open the InputStream, you can read the content
687 mpStream->FlushBuffer();
688 mpStream->Seek(0);
691 if ( mbInClosed )
693 // stream will be deleted by TempFile implementation
694 mpStream = nullptr;
695 mpTempFile.reset();
699 void TempFileFastService::checkError() const
701 if (!mpStream || mpStream->SvStream::GetError () != ERRCODE_NONE )
702 throw css::io::NotConnectedException ( OUString(), const_cast < TempFileFastService * > (this)->getXWeak() );
705 void TempFileFastService::checkConnected()
707 if (!mpStream)
708 throw css::io::NotConnectedException ( OUString(), getXWeak() );
711 // XSeekable
713 void SAL_CALL TempFileFastService::seek( sal_Int64 nLocation )
715 std::unique_lock aGuard( maMutex );
716 checkConnected();
717 checkError();
718 if ( nLocation < 0 )
719 throw css::lang::IllegalArgumentException();
721 sal_Int64 nNewLoc = mpStream->Seek(static_cast<sal_uInt32>(nLocation) );
722 if ( nNewLoc != nLocation )
723 throw css::lang::IllegalArgumentException();
724 checkError();
727 sal_Int64 SAL_CALL TempFileFastService::getPosition()
729 std::unique_lock aGuard( maMutex );
730 checkConnected();
732 sal_uInt64 nPos = mpStream->Tell();
733 checkError();
734 return static_cast<sal_Int64>(nPos);
737 sal_Int64 SAL_CALL TempFileFastService::getLength()
739 std::unique_lock aGuard( maMutex );
740 checkConnected();
742 checkError();
744 sal_Int64 nEndPos = mpStream->TellEnd();
746 return nEndPos;
749 // XStream
751 css::uno::Reference< css::io::XInputStream > SAL_CALL TempFileFastService::getInputStream()
753 return this;
756 css::uno::Reference< css::io::XOutputStream > SAL_CALL TempFileFastService::getOutputStream()
758 return this;
761 // XTruncate
763 void SAL_CALL TempFileFastService::truncate()
765 std::unique_lock aGuard( maMutex );
766 checkConnected();
767 // SetStreamSize() call does not change the position
768 mpStream->Seek( 0 );
769 mpStream->SetStreamSize( 0 );
770 checkError();
777 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */