1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <sal/config.h>
21 
22 #include <cassert>
23 #include <utility>
24 
25 #include <unotools/tempfile.hxx>
26 #include <rtl/ustring.hxx>
27 #include <rtl/instance.hxx>
28 #include <osl/detail/file.h>
29 #include <osl/file.hxx>
30 #include <tools/time.hxx>
31 #include <tools/debug.hxx>
32 #include <comphelper/DirectoryHelper.hxx>
33 
34 #ifdef UNX
35 #include <unistd.h>
36 #elif defined( _WIN32 )
37 #include <process.h>
38 #endif
39 
40 using namespace osl;
41 
42 namespace
43 {
44     struct TempNameBase_Impl
45         : public rtl::Static< OUString, TempNameBase_Impl > {};
46 }
47 
48 namespace utl
49 {
50 
getParentName(const OUString & aFileName)51 static OUString getParentName( const OUString& aFileName )
52 {
53     sal_Int32 lastIndex = aFileName.lastIndexOf( '/' );
54     OUString aParent;
55 
56     if (lastIndex > -1)
57     {
58         aParent = aFileName.copy(0, lastIndex);
59 
60         if (aParent.endsWith(":") && aParent.getLength() == 6)
61             aParent += "/";
62 
63         if (aParent.equalsIgnoreAsciiCase("file://"))
64             aParent = "file:///";
65     }
66 
67     return aParent;
68 }
69 
ensuredir(const OUString & rUnqPath)70 static bool ensuredir( const OUString& rUnqPath )
71 {
72     OUString aPath;
73     if ( rUnqPath.isEmpty() )
74         return false;
75 
76     // remove trailing slash
77     if ( rUnqPath.endsWith("/") )
78         aPath = rUnqPath.copy( 0, rUnqPath.getLength() - 1 );
79     else
80         aPath = rUnqPath;
81 
82     // HACK: create directory on a mount point with nobrowse option
83     // returns ENOSYS in any case !!
84     osl::Directory aDirectory( aPath );
85     osl::FileBase::RC nError = aDirectory.open();
86     aDirectory.close();
87     if( nError == osl::File::E_None )
88         return true;
89 
90     // try to create the directory
91     nError = osl::Directory::create( aPath );
92     bool  bSuccess = ( nError == osl::File::E_None || nError == osl::FileBase::E_EXIST );
93     if( !bSuccess )
94     {
95         // perhaps parent(s) don't exist
96         OUString aParentDir = getParentName( aPath );
97         if ( aParentDir != aPath )
98         {
99             bSuccess = ensuredir( getParentName( aPath ) );
100 
101             // After parent directory structure exists try it one's more
102             if ( bSuccess )
103             {
104                 // Parent directory exists, retry creation of directory
105                 nError = osl::Directory::create( aPath );
106                 bSuccess =( nError == osl::File::E_None || nError == osl::FileBase::E_EXIST );
107             }
108         }
109     }
110 
111     return bSuccess;
112 }
113 
ConstructTempDir_Impl(const OUString * pParent,bool bCreateParentDirs)114 static OUString ConstructTempDir_Impl( const OUString* pParent, bool bCreateParentDirs )
115 {
116     OUString aName;
117 
118     // Ignore pParent on iOS. We don't want to create any temp files
119     // in the same directory where the document being edited is.
120 #ifndef IOS
121     if ( pParent && !pParent->isEmpty() )
122     {
123         // test for valid filename
124         OUString aRet;
125         if ((osl::FileBase::getSystemPathFromFileURL(*pParent, aRet)
126              == osl::FileBase::E_None)
127             && (osl::FileBase::getFileURLFromSystemPath(aRet, aRet)
128                 == osl::FileBase::E_None))
129         {
130             ::osl::DirectoryItem aItem;
131             sal_Int32 i = aRet.getLength();
132             if ( aRet[i-1] == '/' )
133                 i--;
134 
135             if ( DirectoryItem::get( aRet.copy(0, i), aItem ) == FileBase::E_None || bCreateParentDirs )
136                 aName = aRet;
137         }
138     }
139 #else
140     (void) pParent;
141     (void) bCreateParentDirs;
142 #endif
143 
144     if ( aName.isEmpty() )
145     {
146         OUString &rTempNameBase_Impl = TempNameBase_Impl::get();
147         if (rTempNameBase_Impl.isEmpty())
148         {
149             OUString ustrTempDirURL;
150             ::osl::FileBase::RC rc = ::osl::File::getTempDirURL(
151                 ustrTempDirURL );
152             if (rc == ::osl::FileBase::E_None)
153                 rTempNameBase_Impl = ustrTempDirURL;
154         }
155         // if no parent or invalid parent : use default directory
156         DBG_ASSERT( !rTempNameBase_Impl.isEmpty(), "No TempDir!" );
157         aName = rTempNameBase_Impl;
158         ensuredir( aName );
159     }
160 
161     // Make sure that directory ends with a separator
162     if( !aName.isEmpty() && !aName.endsWith("/") )
163         aName += "/";
164 
165     return aName;
166 }
167 
168 namespace {
169 
170 class Tokens {
171 public:
172     virtual bool next(OUString *) = 0;
173 
174 protected:
~Tokens()175     virtual ~Tokens() {} // avoid warnings
176 };
177 
178 class SequentialTokens: public Tokens {
179 public:
SequentialTokens(bool showZero)180     explicit SequentialTokens(bool showZero): m_value(0), m_show(showZero) {}
181 
next(OUString * token)182     bool next(OUString * token) override {
183         assert(token != nullptr);
184         if (m_value == SAL_MAX_UINT32) {
185             return false;
186         }
187         *token = m_show ? OUString::number(m_value) : OUString();
188         ++m_value;
189         m_show = true;
190         return true;
191     }
192 
193 private:
194     sal_uInt32 m_value;
195     bool m_show;
196 };
197 
198 class UniqueTokens: public Tokens {
199 public:
UniqueTokens()200     UniqueTokens(): m_count(0) {}
201 
next(OUString * token)202     bool next(OUString * token) override {
203         assert(token != nullptr);
204         // Because of the shared globalValue, no single instance of UniqueTokens
205         // is guaranteed to exhaustively test all 36^6 possible values, but stop
206         // after that many attempts anyway:
207         sal_uInt32 radix = 36;
208         sal_uInt32 max = radix * radix * radix * radix * radix * radix;
209             // 36^6 == 2'176'782'336 < SAL_MAX_UINT32 == 4'294'967'295
210         if (m_count == max) {
211             return false;
212         }
213         sal_uInt32 v;
214         {
215             osl::MutexGuard g(osl::Mutex::getGlobalMutex());
216             globalValue
217                 = ((globalValue == SAL_MAX_UINT32
218                     ? tools::Time::GetSystemTicks() : globalValue + 1)
219                    % max);
220             v = globalValue;
221         }
222         *token = OUString::number(v, radix);
223         ++m_count;
224         return true;
225     }
226 
227 private:
228     static sal_uInt32 globalValue;
229 
230     sal_uInt32 m_count;
231 };
232 
233 }
234 
235 sal_uInt32 UniqueTokens::globalValue = SAL_MAX_UINT32;
236 
237 namespace
238 {
239     class TempDirCreatedObserver : public DirectoryCreationObserver
240     {
241     public:
DirectoryCreated(const OUString & aDirectoryUrl)242         virtual void DirectoryCreated(const OUString& aDirectoryUrl) override
243         {
244             File::setAttributes( aDirectoryUrl, osl_File_Attribute_OwnRead |
245                 osl_File_Attribute_OwnWrite | osl_File_Attribute_OwnExe );
246         };
247     };
248 };
249 
lcl_createName(const OUString & rLeadingChars,Tokens & tokens,const OUString * pExtension,const OUString * pParent,bool bDirectory,bool bKeep,bool bLock,bool bCreateParentDirs)250 static OUString lcl_createName(
251     const OUString& rLeadingChars, Tokens & tokens, const OUString* pExtension,
252     const OUString* pParent, bool bDirectory, bool bKeep, bool bLock,
253     bool bCreateParentDirs )
254 {
255     OUString aName = ConstructTempDir_Impl( pParent, bCreateParentDirs );
256     if ( bCreateParentDirs )
257     {
258         sal_Int32 nOffset = rLeadingChars.lastIndexOf("/");
259         OUString aDirName;
260         if (-1 != nOffset)
261             aDirName = aName + rLeadingChars.subView( 0, nOffset );
262         else
263             aDirName = aName;
264         TempDirCreatedObserver observer;
265         FileBase::RC err = Directory::createPath( aDirName, &observer );
266         if ( err != FileBase::E_None && err != FileBase::E_EXIST )
267             return OUString();
268     }
269     aName += rLeadingChars;
270 
271     OUString token;
272     while (tokens.next(&token))
273     {
274         OUString aTmp( aName + token );
275         if ( pExtension )
276             aTmp += *pExtension;
277         else
278             aTmp += ".tmp";
279         if ( bDirectory )
280         {
281             FileBase::RC err = Directory::create(
282                 aTmp,
283                 (osl_File_OpenFlag_Read | osl_File_OpenFlag_Write
284                  | osl_File_OpenFlag_Private));
285             if ( err == FileBase::E_None )
286             {
287                 // !bKeep: only for creating a name, not a file or directory
288                 if ( bKeep || Directory::remove( aTmp ) == FileBase::E_None )
289                     return aTmp;
290                 else
291                     return OUString();
292             }
293             else if ( err != FileBase::E_EXIST )
294                 // if f.e. name contains invalid chars stop trying to create dirs
295                 return OUString();
296         }
297         else
298         {
299             DBG_ASSERT( bKeep, "Too expensive, use directory for creating name!" );
300             File aFile( aTmp );
301             FileBase::RC err = aFile.open(
302                 osl_File_OpenFlag_Create | osl_File_OpenFlag_Private
303                 | (bLock ? 0 : osl_File_OpenFlag_NoLock));
304             if ( err == FileBase::E_None || (bLock && err == FileBase::E_NOLCK) )
305             {
306                 aFile.close();
307                 return aTmp;
308             }
309             else if ( err != FileBase::E_EXIST )
310             {
311                 // if f.e. name contains invalid chars stop trying to create dirs
312                 // but if there is a folder with such name proceed further
313 
314                 DirectoryItem aTmpItem;
315                 FileStatus aTmpStatus( osl_FileStatus_Mask_Type );
316                 if ( DirectoryItem::get( aTmp, aTmpItem ) != FileBase::E_None
317                   || aTmpItem.getFileStatus( aTmpStatus ) != FileBase::E_None
318                   || aTmpStatus.getFileType() != FileStatus::Directory )
319                     return OUString();
320             }
321         }
322     }
323     return OUString();
324 }
325 
CreateTempName_Impl(const OUString * pParent,bool bKeep,bool bDir=true)326 static OUString CreateTempName_Impl( const OUString* pParent, bool bKeep, bool bDir = true )
327 {
328     OUString aEyeCatcher = "lu";
329 #ifdef UNX
330 #ifdef DBG_UTIL
331     const char* eye = getenv("LO_TESTNAME");
332     if(eye)
333     {
334         aEyeCatcher = OUString(eye, strlen(eye), RTL_TEXTENCODING_ASCII_US);
335     }
336 #else
337     static const pid_t pid = getpid();
338     static const OUString aPidString = OUString::number(pid);
339     aEyeCatcher += aPidString;
340 #endif
341 #elif defined(_WIN32)
342     static const int pid = _getpid();
343     static const OUString aPidString = OUString::number(pid);
344     aEyeCatcher += aPidString;
345 #endif
346     UniqueTokens t;
347     return lcl_createName( aEyeCatcher, t, nullptr, pParent, bDir, bKeep,
348                            false, false);
349 }
350 
CreateTempName()351 OUString TempFile::CreateTempName()
352 {
353     OUString aName(CreateTempName_Impl( nullptr, false ));
354 
355     // convert to file URL
356     OUString aTmp;
357     if ( !aName.isEmpty() )
358         FileBase::getSystemPathFromFileURL( aName, aTmp );
359     return aTmp;
360 }
361 
TempFile(const OUString * pParent,bool bDirectory)362 TempFile::TempFile( const OUString* pParent, bool bDirectory )
363     : bIsDirectory( bDirectory )
364     , bKillingFileEnabled( false )
365 {
366     aName = CreateTempName_Impl( pParent, true, bDirectory );
367 }
368 
TempFile(const OUString & rLeadingChars,bool _bStartWithZero,const OUString * pExtension,const OUString * pParent,bool bCreateParentDirs)369 TempFile::TempFile( const OUString& rLeadingChars, bool _bStartWithZero,
370                     const OUString* pExtension, const OUString* pParent,
371                     bool bCreateParentDirs )
372     : bIsDirectory( false )
373     , bKillingFileEnabled( false )
374 {
375     SequentialTokens t(_bStartWithZero);
376     aName = lcl_createName( rLeadingChars, t, pExtension, pParent, false,
377                             true, true, bCreateParentDirs );
378 }
379 
TempFile(TempFile && other)380 TempFile::TempFile(TempFile && other) noexcept :
381     aName(std::move(other.aName)), pStream(std::move(other.pStream)), bIsDirectory(other.bIsDirectory),
382     bKillingFileEnabled(other.bKillingFileEnabled)
383 {
384     other.bKillingFileEnabled = false;
385 }
386 
~TempFile()387 TempFile::~TempFile()
388 {
389     // if we're going to delete this file, no point in flushing it when closing
390     if (pStream && bKillingFileEnabled && !aName.isEmpty())
391         static_cast<SvFileStream*>(pStream.get())->SetDontFlushOnClose(true);
392     pStream.reset();
393     if ( bKillingFileEnabled )
394     {
395         if ( bIsDirectory )
396         {
397             comphelper::DirectoryHelper::deleteDirRecursively(aName);
398         }
399         else
400         {
401             File::remove( aName );
402         }
403     }
404 }
405 
IsValid() const406 bool TempFile::IsValid() const
407 {
408     return !aName.isEmpty();
409 }
410 
GetFileName() const411 OUString TempFile::GetFileName() const
412 {
413     OUString aTmp;
414     FileBase::getSystemPathFromFileURL(aName, aTmp);
415     return aTmp;
416 }
417 
GetURL() const418 OUString const & TempFile::GetURL() const
419 {
420     return aName;
421 }
422 
GetStream(StreamMode eMode)423 SvStream* TempFile::GetStream( StreamMode eMode )
424 {
425     if (!pStream)
426     {
427         if (!aName.isEmpty())
428             pStream.reset(new SvFileStream(aName, eMode | StreamMode::TEMPORARY));
429         else
430             pStream.reset(new SvMemoryStream(nullptr, 0, eMode));
431     }
432 
433     return pStream.get();
434 }
435 
CloseStream()436 void TempFile::CloseStream()
437 {
438     pStream.reset();
439 }
440 
SetTempNameBaseDirectory(const OUString & rBaseName)441 OUString TempFile::SetTempNameBaseDirectory( const OUString &rBaseName )
442 {
443     if( rBaseName.isEmpty() )
444         return OUString();
445 
446     OUString aUnqPath( rBaseName );
447 
448     // remove trailing slash
449     if ( rBaseName.endsWith("/") )
450         aUnqPath = rBaseName.copy( 0, rBaseName.getLength() - 1 );
451 
452     // try to create the directory
453     bool bRet = false;
454     osl::FileBase::RC err = osl::Directory::create( aUnqPath );
455     if ( err != FileBase::E_None && err != FileBase::E_EXIST )
456         // perhaps parent(s) don't exist
457         bRet = ensuredir( aUnqPath );
458     else
459         bRet = true;
460 
461     // failure to create base directory means returning an empty string
462     OUString aTmp;
463     if ( bRet )
464     {
465         // append own internal directory
466         OUString &rTempNameBase_Impl = TempNameBase_Impl::get();
467         rTempNameBase_Impl = rBaseName + "/";
468 
469         TempFile aBase( nullptr, true );
470         if ( aBase.IsValid() )
471             // use it in case of success
472             rTempNameBase_Impl = aBase.aName;
473 
474         // return system path of used directory
475         FileBase::getSystemPathFromFileURL( rTempNameBase_Impl, aTmp );
476     }
477 
478     return aTmp;
479 }
480 
GetTempNameBaseDirectory()481 OUString TempFile::GetTempNameBaseDirectory()
482 {
483     return ConstructTempDir_Impl(nullptr, false);
484 }
485 
486 }
487 
488 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
489