1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 .
28 #include <osl/file.hxx>
29 #include <tools/config.hxx>
30 #include <sal/log.hxx>
46 ImplGroupData
* mpNext
;
47 ImplKeyData
* mpFirstKey
;
49 sal_uInt16 mnEmptyLines
;
54 ImplGroupData
* mpFirstGroup
;
56 sal_uInt32 mnDataUpdateId
;
57 sal_uInt32 mnTimeStamp
;
63 static OUString
toUncPath( const OUString
& rPath
)
67 // check if rFileName is already a URL; if not make it so
68 if( rPath
.startsWith( "file://"))
72 else if( ::osl::FileBase::getFileURLFromSystemPath( rPath
, aFileURL
) != ::osl::FileBase::E_None
)
79 static sal_uInt32
ImplSysGetConfigTimeStamp( const OUString
& rFileName
)
81 sal_uInt32 nTimeStamp
= 0;
82 ::osl::DirectoryItem aItem
;
83 ::osl::FileStatus
aStatus( osl_FileStatus_Mask_ModifyTime
);
85 if( ::osl::DirectoryItem::get( rFileName
, aItem
) == ::osl::FileBase::E_None
&&
86 aItem
.getFileStatus( aStatus
) == ::osl::FileBase::E_None
)
88 nTimeStamp
= aStatus
.getModifyTime().Seconds
;
94 static std::unique_ptr
<sal_uInt8
[]> ImplSysReadConfig( const OUString
& rFileName
,
95 sal_uInt64
& rRead
, bool& rbRead
, bool& rbIsUTF8BOM
, sal_uInt32
& rTimeStamp
)
97 std::unique_ptr
<sal_uInt8
[]> pBuf
;
98 ::osl::File
aFile( rFileName
);
100 if( aFile
.open( osl_File_OpenFlag_Read
) == ::osl::FileBase::E_None
)
103 if( aFile
.getSize( nPos
) == ::osl::FileBase::E_None
)
105 if (nPos
> SAL_MAX_SIZE
) {
109 pBuf
.reset(new sal_uInt8
[static_cast< std::size_t >(nPos
)]);
110 sal_uInt64 nRead
= 0;
111 if( aFile
.read( pBuf
.get(), nPos
, nRead
) == ::osl::FileBase::E_None
&& nRead
== nPos
)
113 //skip the byte-order-mark 0xEF 0xBB 0xBF, if it was UTF8 files
114 unsigned char const BOM
[3] = {0xEF, 0xBB, 0xBF};
115 if (nRead
> 2 && memcmp(pBuf
.get(), BOM
, 3) == 0)
118 memmove(pBuf
.get(), pBuf
.get() + 3, sal::static_int_cast
<std::size_t>(nRead
* sizeof(sal_uInt8
)) );
122 rTimeStamp
= ImplSysGetConfigTimeStamp( rFileName
);
137 static bool ImplSysWriteConfig( const OUString
& rFileName
,
138 const sal_uInt8
* pBuf
, sal_uInt32 nBufLen
, bool rbIsUTF8BOM
, sal_uInt32
& rTimeStamp
)
140 bool bSuccess
= false;
141 bool bUTF8BOMSuccess
= false;
143 ::osl::File
aFile( rFileName
);
144 ::osl::FileBase::RC eError
= aFile
.open( osl_File_OpenFlag_Write
| osl_File_OpenFlag_Create
);
145 if( eError
!= ::osl::FileBase::E_None
)
146 eError
= aFile
.open( osl_File_OpenFlag_Write
);
147 if( eError
== ::osl::FileBase::E_None
)
153 //write the byte-order-mark 0xEF 0xBB 0xBF first , if it was UTF8 files
156 unsigned char const BOM
[3] = {0xEF, 0xBB, 0xBF};
157 sal_uInt64 nUTF8BOMWritten
;
158 if( aFile
.write( BOM
, 3, nUTF8BOMWritten
) == ::osl::FileBase::E_None
&& 3 == nUTF8BOMWritten
)
160 bUTF8BOMSuccess
= true;
164 if( aFile
.write( pBuf
, nBufLen
, nWritten
) == ::osl::FileBase::E_None
&& nWritten
== nBufLen
)
168 if ( rbIsUTF8BOM
? bSuccess
&& bUTF8BOMSuccess
: bSuccess
)
170 rTimeStamp
= ImplSysGetConfigTimeStamp( rFileName
);
174 return rbIsUTF8BOM
? bSuccess
&& bUTF8BOMSuccess
: bSuccess
;
178 OString
makeOString(const sal_uInt8
* p
, sal_uInt64 n
)
180 if (n
> SAL_MAX_INT32
)
185 ::std::abort(); //TODO: handle this gracefully
189 reinterpret_cast< char const * >(p
),
190 sal::static_int_cast
< sal_Int32
>(n
));
194 static void ImplMakeConfigList( ImplConfigData
* pData
,
195 const sal_uInt8
* pBuf
, sal_uInt64 nLen
)
200 // Parse buffer and build config list
206 const sal_uInt8
* pLine
;
207 ImplKeyData
* pPrevKey
= nullptr;
208 ImplGroupData
* pPrevGroup
= nullptr;
209 ImplGroupData
* pGroup
= nullptr;
214 if ( pBuf
[i
] == 0x1A )
217 // Remove spaces and tabs
218 while ( (pBuf
[i
] == ' ') || (pBuf
[i
] == '\t') )
221 // remember line-starts
225 // search line-endings
226 while ( (i
< nLen
) && pBuf
[i
] && (pBuf
[i
] != '\r') && (pBuf
[i
] != '\n') &&
232 // if Line-ending is found, continue once
234 (pBuf
[i
] != pBuf
[i
+1]) &&
235 ((pBuf
[i
+1] == '\r') || (pBuf
[i
+1] == '\n')) )
242 pGroup
= new ImplGroupData
;
243 pGroup
->mpNext
= nullptr;
244 pGroup
->mpFirstKey
= nullptr;
245 pGroup
->mnEmptyLines
= 0;
247 pPrevGroup
->mpNext
= pGroup
;
249 pData
->mpFirstGroup
= pGroup
;
253 // filter group names
255 assert(nLineLen
> 0);
257 // remove spaces and tabs
258 while ( nLineLen
> 0 && (*pLine
== ' ' || *pLine
== '\t') )
264 while ( (nNameLen
< nLineLen
) && (pLine
[nNameLen
] != ']') )
266 while ( nNameLen
> 0 && (pLine
[nNameLen
-1] == ' ' || pLine
[nNameLen
-1] == '\t') )
268 pGroup
->maGroupName
= makeOString(pLine
, nNameLen
);
274 // If no group exists yet, add to default
277 pGroup
= new ImplGroupData
;
278 pGroup
->mpNext
= nullptr;
279 pGroup
->mpFirstKey
= nullptr;
280 pGroup
->mnEmptyLines
= 0;
281 pData
->mpFirstGroup
= pGroup
;
286 // if empty line, append it
289 while ( pGroup
->mnEmptyLines
)
291 ImplKeyData
* pKey
= new ImplKeyData
;
292 pKey
->mbIsComment
= true;
293 pPrevKey
->mpNext
= pKey
;
295 pGroup
->mnEmptyLines
--;
300 ImplKeyData
* pKey
= new ImplKeyData
;
301 pKey
->mpNext
= nullptr;
303 pPrevKey
->mpNext
= pKey
;
305 pGroup
->mpFirstKey
= pKey
;
307 if ( pLine
[0] == ';' )
309 pKey
->maValue
= makeOString(pLine
, nLineLen
);
310 pKey
->mbIsComment
= true;
314 pKey
->mbIsComment
= false;
316 while ( (nNameLen
< nLineLen
) && (pLine
[nNameLen
] != '=') )
319 // Remove spaces and tabs
320 while ( nNameLen
> 0 && (pLine
[nNameLen
-1] == ' ' || pLine
[nNameLen
-1] == '\t') )
322 pKey
->maKey
= makeOString(pLine
, nNameLen
);
324 if ( nKeyLen
< nLineLen
)
328 // Remove spaces and tabs
329 while ( (*pLine
== ' ') || (*pLine
== '\t') )
336 while ( (pLine
[nLineLen
-1] == ' ') || (pLine
[nLineLen
-1] == '\t') )
338 pKey
->maValue
= makeOString(pLine
, nLineLen
);
345 // Spaces are counted and appended only after key generation,
346 // as we want to store spaces even after adding new keys
348 pGroup
->mnEmptyLines
++;
354 static std::unique_ptr
<sal_uInt8
[]> ImplGetConfigBuffer( const ImplConfigData
* pData
, sal_uInt32
& rLen
)
356 std::unique_ptr
<sal_uInt8
[]> pWriteBuf
;
358 sal_uInt8 aLineEndBuf
[2] = {0, 0};
360 ImplGroupData
* pGroup
;
362 sal_uInt32 nValueLen
;
364 sal_uInt32 nLineEndLen
;
366 aLineEndBuf
[0] = '\r';
367 aLineEndBuf
[1] = '\n';
371 pGroup
= pData
->mpFirstGroup
;
374 // Don't write empty groups
375 if ( pGroup
->mpFirstKey
)
377 nBufLen
+= pGroup
->maGroupName
.getLength() + nLineEndLen
+ 2;
378 pKey
= pGroup
->mpFirstKey
;
381 nValueLen
= pKey
->maValue
.getLength();
382 if ( pKey
->mbIsComment
)
383 nBufLen
+= nValueLen
+ nLineEndLen
;
385 nBufLen
+= pKey
->maKey
.getLength() + nValueLen
+ nLineEndLen
+ 1;
390 // Write empty lines after each group
391 if ( !pGroup
->mnEmptyLines
)
392 pGroup
->mnEmptyLines
= 1;
393 nBufLen
+= nLineEndLen
* pGroup
->mnEmptyLines
;
396 pGroup
= pGroup
->mpNext
;
399 // Output buffer length
403 pWriteBuf
.reset(new sal_uInt8
[nLineEndLen
]);
404 pWriteBuf
[0] = aLineEndBuf
[0];
405 if ( nLineEndLen
== 2 )
406 pWriteBuf
[1] = aLineEndBuf
[1];
410 // Allocate new write buffer (caller frees it)
411 pWriteBuf
.reset(new sal_uInt8
[nBufLen
]);
414 pBuf
= pWriteBuf
.get();
415 pGroup
= pData
->mpFirstGroup
;
418 // Don't write empty groups
419 if ( pGroup
->mpFirstKey
)
422 memcpy( pBuf
, pGroup
->maGroupName
.getStr(), pGroup
->maGroupName
.getLength() );
423 pBuf
+= pGroup
->maGroupName
.getLength();
425 *pBuf
= aLineEndBuf
[0]; pBuf
++;
426 if ( nLineEndLen
== 2 )
428 *pBuf
= aLineEndBuf
[1]; pBuf
++;
430 pKey
= pGroup
->mpFirstKey
;
433 nValueLen
= pKey
->maValue
.getLength();
434 if ( pKey
->mbIsComment
)
438 memcpy( pBuf
, pKey
->maValue
.getStr(), nValueLen
);
441 *pBuf
= aLineEndBuf
[0]; pBuf
++;
442 if ( nLineEndLen
== 2 )
444 *pBuf
= aLineEndBuf
[1]; pBuf
++;
449 nKeyLen
= pKey
->maKey
.getLength();
450 memcpy( pBuf
, pKey
->maKey
.getStr(), nKeyLen
);
453 memcpy( pBuf
, pKey
->maValue
.getStr(), nValueLen
);
455 *pBuf
= aLineEndBuf
[0]; pBuf
++;
456 if ( nLineEndLen
== 2 )
458 *pBuf
= aLineEndBuf
[1]; pBuf
++;
465 // Store empty line after each group
466 sal_uInt16 nEmptyLines
= pGroup
->mnEmptyLines
;
467 while ( nEmptyLines
)
469 *pBuf
= aLineEndBuf
[0]; pBuf
++;
470 if ( nLineEndLen
== 2 )
472 *pBuf
= aLineEndBuf
[1]; pBuf
++;
478 pGroup
= pGroup
->mpNext
;
484 static void ImplReadConfig( ImplConfigData
* pData
)
486 sal_uInt32 nTimeStamp
= 0;
487 sal_uInt64 nRead
= 0;
489 bool bIsUTF8BOM
= false;
490 std::unique_ptr
<sal_uInt8
[]> pBuf
= ImplSysReadConfig( pData
->maFileName
, nRead
, bRead
, bIsUTF8BOM
, nTimeStamp
);
492 // Read config list from buffer
495 ImplMakeConfigList( pData
, pBuf
.get(), nRead
);
498 pData
->mnTimeStamp
= nTimeStamp
;
499 pData
->mbModified
= false;
501 pData
->mbRead
= true;
503 pData
->mbIsUTF8BOM
= true;
506 static void ImplWriteConfig( ImplConfigData
* pData
)
508 SAL_WARN_IF( pData
->mnTimeStamp
!= ImplSysGetConfigTimeStamp( pData
->maFileName
),
509 "tools.generic", "Config overwrites modified configfile: " << pData
->maFileName
);
511 // Read config list from buffer
513 std::unique_ptr
<sal_uInt8
[]> pBuf
= ImplGetConfigBuffer( pData
, nBufLen
);
516 if ( ImplSysWriteConfig( pData
->maFileName
, pBuf
.get(), nBufLen
, pData
->mbIsUTF8BOM
, pData
->mnTimeStamp
) )
517 pData
->mbModified
= false;
520 pData
->mbModified
= false;
523 static void ImplDeleteConfigData( ImplConfigData
* pData
)
525 ImplKeyData
* pTempKey
;
527 ImplGroupData
* pTempGroup
;
528 ImplGroupData
* pGroup
= pData
->mpFirstGroup
;
531 pTempGroup
= pGroup
->mpNext
;
534 pKey
= pGroup
->mpFirstKey
;
537 pTempKey
= pKey
->mpNext
;
542 // remove group and continue
547 pData
->mpFirstGroup
= nullptr;
550 static std::unique_ptr
<ImplConfigData
> ImplGetConfigData( const OUString
& rFileName
)
552 std::unique_ptr
<ImplConfigData
> pData(new ImplConfigData
);
553 pData
->maFileName
= rFileName
;
554 pData
->mpFirstGroup
= nullptr;
555 pData
->mnDataUpdateId
= 0;
556 pData
->mbRead
= false;
557 pData
->mbIsUTF8BOM
= false;
558 ImplReadConfig( pData
.get() );
563 bool Config::ImplUpdateConfig() const
565 // Re-read file if timestamp differs
566 if ( mpData
->mnTimeStamp
!= ImplSysGetConfigTimeStamp( maFileName
) )
568 ImplDeleteConfigData( mpData
.get() );
569 ImplReadConfig( mpData
.get() );
570 mpData
->mnDataUpdateId
++;
577 ImplGroupData
* Config::ImplGetGroup() const
579 if ( !mpActGroup
|| (mnDataUpdateId
!= mpData
->mnDataUpdateId
) )
581 ImplGroupData
* pPrevGroup
= nullptr;
582 ImplGroupData
* pGroup
= mpData
->mpFirstGroup
;
585 if ( pGroup
->maGroupName
.equalsIgnoreAsciiCase(maGroupName
) )
589 pGroup
= pGroup
->mpNext
;
592 // Add group if not exists
595 pGroup
= new ImplGroupData
;
596 pGroup
->mpNext
= nullptr;
597 pGroup
->mpFirstKey
= nullptr;
598 pGroup
->mnEmptyLines
= 1;
600 pPrevGroup
->mpNext
= pGroup
;
602 mpData
->mpFirstGroup
= pGroup
;
605 // Always inherit group names and update cache members
606 pGroup
->maGroupName
= maGroupName
;
607 const_cast<Config
*>(this)->mnDataUpdateId
= mpData
->mnDataUpdateId
;
608 const_cast<Config
*>(this)->mpActGroup
= pGroup
;
614 Config::Config( const OUString
& rFileName
)
616 // Initialize config data
617 maFileName
= toUncPath( rFileName
);
618 mpData
= ImplGetConfigData( maFileName
);
619 mpActGroup
= nullptr;
622 SAL_INFO("tools.generic", "Config::Config( " << maFileName
<< " )");
627 SAL_INFO("tools.generic", "Config::~Config()" );
630 ImplDeleteConfigData( mpData
.get() );
633 void Config::SetGroup(const OString
& rGroup
)
635 // If group is to be reset, it needs to be updated on next call
636 if ( maGroupName
!= rGroup
)
638 maGroupName
= rGroup
;
639 mnDataUpdateId
= mpData
->mnDataUpdateId
-1;
643 void Config::DeleteGroup(std::string_view rGroup
)
645 // Update config data if necessary
646 if ( !mpData
->mbRead
)
649 mpData
->mbRead
= true;
652 ImplGroupData
* pPrevGroup
= nullptr;
653 ImplGroupData
* pGroup
= mpData
->mpFirstGroup
;
656 if ( pGroup
->maGroupName
.equalsIgnoreAsciiCase(rGroup
) )
660 pGroup
= pGroup
->mpNext
;
667 ImplKeyData
* pTempKey
;
668 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
671 pTempKey
= pKey
->mpNext
;
676 // Rewire pointers and remove group
678 pPrevGroup
->mpNext
= pGroup
->mpNext
;
680 mpData
->mpFirstGroup
= pGroup
->mpNext
;
683 // Rewrite config data
684 mpData
->mbModified
= true;
686 mnDataUpdateId
= mpData
->mnDataUpdateId
;
687 mpData
->mnDataUpdateId
++;
690 OString
Config::GetGroupName(sal_uInt16 nGroup
) const
692 ImplGroupData
* pGroup
= mpData
->mpFirstGroup
;
693 sal_uInt16 nGroupCount
= 0;
697 if ( nGroup
== nGroupCount
)
699 aGroupName
= pGroup
->maGroupName
;
704 pGroup
= pGroup
->mpNext
;
710 sal_uInt16
Config::GetGroupCount() const
712 ImplGroupData
* pGroup
= mpData
->mpFirstGroup
;
713 sal_uInt16 nGroupCount
= 0;
717 pGroup
= pGroup
->mpNext
;
723 bool Config::HasGroup(std::string_view rGroup
) const
725 ImplGroupData
* pGroup
= mpData
->mpFirstGroup
;
730 if( pGroup
->maGroupName
.equalsIgnoreAsciiCase(rGroup
) )
736 pGroup
= pGroup
->mpNext
;
742 OString
Config::ReadKey(const OString
& rKey
) const
744 return ReadKey(rKey
, OString());
747 OString
Config::ReadKey(const OString
& rKey
, const OString
& rDefault
) const
749 SAL_INFO("tools.generic", "Config::ReadKey( " << rKey
<< " ) from " << GetGroup()
750 << " in " << maFileName
);
752 // Search key, return value if found
753 ImplGroupData
* pGroup
= ImplGetGroup();
756 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
759 if ( !pKey
->mbIsComment
&& pKey
->maKey
.equalsIgnoreAsciiCase(rKey
) )
760 return pKey
->maValue
;
769 void Config::WriteKey(const OString
& rKey
, const OString
& rStr
)
771 SAL_INFO("tools.generic", "Config::WriteKey( " << rKey
<< ", " << rStr
<< " ) to "
772 << GetGroup() << " in " << maFileName
);
774 // Update config data if necessary
775 if ( !mpData
->mbRead
)
778 mpData
->mbRead
= true;
781 // Search key and update value if found
782 ImplGroupData
* pGroup
= ImplGetGroup();
786 ImplKeyData
* pPrevKey
= nullptr;
787 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
790 if ( !pKey
->mbIsComment
&& pKey
->maKey
.equalsIgnoreAsciiCase(rKey
) )
800 pKey
= new ImplKeyData
;
801 pKey
->mpNext
= nullptr;
803 pKey
->mbIsComment
= false;
805 pPrevKey
->mpNext
= pKey
;
807 pGroup
->mpFirstKey
= pKey
;
811 bNewValue
= pKey
->maValue
!= rStr
;
815 pKey
->maValue
= rStr
;
817 mpData
->mbModified
= true;
821 void Config::DeleteKey(std::string_view rKey
)
823 // Update config data if necessary
824 if ( !mpData
->mbRead
)
827 mpData
->mbRead
= true;
830 // Search key and update value
831 ImplGroupData
* pGroup
= ImplGetGroup();
835 ImplKeyData
* pPrevKey
= nullptr;
836 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
839 if ( !pKey
->mbIsComment
&& pKey
->maKey
.equalsIgnoreAsciiCase(rKey
) )
848 // Rewire group pointers and delete
850 pPrevKey
->mpNext
= pKey
->mpNext
;
852 pGroup
->mpFirstKey
= pKey
->mpNext
;
855 mpData
->mbModified
= true;
859 sal_uInt16
Config::GetKeyCount() const
861 SAL_INFO("tools.generic", "Config::GetKeyCount() from " << GetGroup() << " in " << maFileName
);
863 // Search key and update value
864 sal_uInt16 nCount
= 0;
865 ImplGroupData
* pGroup
= ImplGetGroup();
868 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
871 if ( !pKey
->mbIsComment
)
881 OString
Config::GetKeyName(sal_uInt16 nKey
) const
883 SAL_INFO("tools.generic", "Config::GetKeyName( " << OString::number(static_cast<sal_Int32
>(nKey
))
884 << " ) from " << GetGroup() << " in " << maFileName
);
886 // search key and return name if found
887 ImplGroupData
* pGroup
= ImplGetGroup();
890 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
893 if ( !pKey
->mbIsComment
)
907 OString
Config::ReadKey(sal_uInt16 nKey
) const
909 SAL_INFO("tools.generic", "Config::ReadKey( " << OString::number(static_cast<sal_Int32
>(nKey
))
910 << " ) from " << GetGroup() << " in " << maFileName
);
912 // Search key and return value if found
913 ImplGroupData
* pGroup
= ImplGetGroup();
916 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
919 if ( !pKey
->mbIsComment
)
922 return pKey
->maValue
;
935 if ( mpData
->mbModified
)
936 ImplWriteConfig( mpData
.get() );
939 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */