1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 // MARKER(update_precomp.py): autogen include statement, do not remove
23 #include "precompiled_desktop.hxx"
24 
25 #include "dp_misc.h"
26 #include "dp_persmap.h"
27 #include "rtl/strbuf.hxx"
28 
29 #ifndef DISABLE_BDB2PMAP
30 #include <vector>
31 #endif
32 
33 using namespace ::rtl;
34 
35 // the persistent map is used to manage a handful of key-value string pairs
36 // this implementation replaces a rather heavy-weight berkeleydb integration
37 
38 // the file backing up a persistent map consists of line pairs with
39 // - a key string   (encoded with chars 0x00..0x0F being escaped)
40 // - a value string (encoded with chars 0x00..0x0F being escaped)
41 
42 namespace dp_misc
43 {
44 
45 static const char PmapMagic[4] = {'P','m','p','1'};
46 
47 //______________________________________________________________________________
48 PersistentMap::PersistentMap( OUString const & url_, bool readOnly )
49 :	m_MapFile( expandUnoRcUrl(url_) )
50 ,	m_bReadOnly( readOnly)
51 ,	m_bIsOpen( false)
52 ,	m_bToBeCreated( !readOnly)
53 ,	m_bIsDirty( false)
54 {
55 #ifndef DISABLE_BDB2PMAP
56 	m_MapFileName = expandUnoRcUrl( url_);
57 #endif
58 
59 	open();
60 }
61 
62 //______________________________________________________________________________
63 PersistentMap::PersistentMap()
64 :	m_MapFile( OUString())
65 ,	m_bReadOnly( false)
66 ,	m_bIsOpen( false)
67 ,	m_bToBeCreated( false)
68 ,	m_bIsDirty( false)
69 {}
70 
71 //______________________________________________________________________________
72 PersistentMap::~PersistentMap()
73 {
74 	if( m_bIsDirty)
75 		flush();
76 	if( m_bIsOpen)
77 		m_MapFile.close();
78 }
79 
80 //______________________________________________________________________________
81 
82 // replace 0x00..0x0F with "%0".."%F"
83 // replace "%" with "%%"
84 static OString encodeString( const OString& rStr)
85 {
86 	const sal_Char* pChar = rStr.getStr();
87 	const sal_Int32 nLen = rStr.getLength();
88 	sal_Int32 i = nLen;
89 	// short circuit for the simple non-encoded case
90 	while( --i >= 0)
91 	{
92 		const sal_Char c = *(pChar++);
93 		if( (0x00 <= c) && (c <= 0x0F))
94 			break;
95 		if( c == '%')
96 			break;
97 	}
98 	if( i < 0)
99 		return rStr;
100 
101 	// escape chars 0x00..0x0F with "%0".."%F"
102 	OStringBuffer aEncStr( nLen + 32);
103 	aEncStr.append( pChar - (nLen-i), nLen - i);
104 	while( --i >= 0)
105 	{
106 		sal_Char c = *(pChar++);
107 		if( (0x00 <= c) && (c <= 0x0F))
108 		{
109 			aEncStr.append( '%');
110 			c += (c <= 0x09) ? '0' : 'A'-10;
111 		} else if( c == '%')
112 			aEncStr.append( '%');
113 		aEncStr.append( c);
114 	}
115 
116 	return aEncStr.makeStringAndClear();
117 }
118 
119 //______________________________________________________________________________
120 
121 // replace "%0".."%F" with 0x00..0x0F
122 // replace "%%" with "%"
123 static OString decodeString( const sal_Char* pEncChars, int nLen)
124 {
125 	const char* pChar = pEncChars;
126 	sal_Int32 i = nLen;
127 	// short circuit for the simple non-encoded case
128 	while( --i >= 0)
129 		if( *(pChar++) == '%')
130 			break;
131 	if( i < 0)
132 		return OString( pEncChars, nLen);
133 
134 	// replace escaped chars with their decoded counterparts
135 	OStringBuffer aDecStr( nLen);
136 	pChar = pEncChars;
137 	for( i = nLen; --i >= 0;)
138 	{
139 		sal_Char c = *(pChar++);
140 		// handle escaped character
141 		if( c == '%')
142 		{
143 			--i;
144 			OSL_ASSERT( i >= 0);
145 			c = *(pChar++);
146 			if( ('0' <= c) && (c <= '9'))
147 				c -= '0';
148 			else
149 			{
150 				OSL_ASSERT( ('A' <= c) && (c <= 'F'));
151 				c -= ('A'-10);
152 			}
153 		}
154 		aDecStr.append( c);
155 	}
156 
157 	return aDecStr.makeStringAndClear();
158 }
159 
160 //______________________________________________________________________________
161 bool PersistentMap::open()
162 {
163 	// open the existing file
164 	sal_uInt32 nOpenFlags = osl_File_OpenFlag_Read;
165 	if( !m_bReadOnly)
166 		nOpenFlags |= osl_File_OpenFlag_Write;
167 
168 	const osl::File::RC rcOpen = m_MapFile.open( nOpenFlags);
169 	m_bIsOpen = (rcOpen == osl::File::E_None);
170 
171 	// or create later if needed
172 	m_bToBeCreated &= (rcOpen == osl::File::E_NOENT) && !m_bIsOpen;
173 
174 #ifndef DISABLE_BDB2PMAP
175 	if( m_bToBeCreated)
176 		importFromBDB();
177 #endif // DISABLE_BDB2PMAP
178 
179 	if( !m_bIsOpen)
180 		return m_bToBeCreated;
181 
182 	const bool readOK = readAll();
183 	return readOK;
184 }
185 
186 //______________________________________________________________________________
187 bool PersistentMap::readAll()
188 {
189 	// prepare for re-reading the map-file
190 	m_MapFile.setPos( osl_Pos_Absolut, 0);
191 	m_entries.clear();
192 
193 	// read header and check magic
194 	char aHeaderBytes[ sizeof(PmapMagic)];
195 	sal_uInt64 nBytesRead = 0;
196 	m_MapFile.read( aHeaderBytes, sizeof(aHeaderBytes), nBytesRead);
197 	OSL_ASSERT( nBytesRead == sizeof(aHeaderBytes));
198 	if( nBytesRead != sizeof(aHeaderBytes))
199 		return false;
200 	// check header magic
201 	for( int i = 0; i < (int)sizeof(PmapMagic); ++i)
202 		if( aHeaderBytes[i] != PmapMagic[i])
203 			return false;
204 
205 	// read key value pairs and add them to the map
206 	ByteSequence aKeyLine;
207 	ByteSequence aValLine;
208 	for(;;)
209 	{
210 		// read key-value line pair
211 		// an empty key name indicates the end of the line pairs
212 		if( m_MapFile.readLine( aKeyLine) != osl::File::E_None)
213 			return false;
214 		if( !aKeyLine.getLength())
215 			break;
216 		if( m_MapFile.readLine( aValLine) != osl::File::E_None)
217 			return false;
218 		// decode key and value strings
219 		const OString aKeyName = decodeString( (sal_Char*)aKeyLine.getConstArray(), aKeyLine.getLength());
220 		const OString aValName = decodeString( (sal_Char*)aValLine.getConstArray(), aValLine.getLength());
221 		// insert key-value pair into map
222 		add( aKeyName, aValName);
223 		// check end-of-file status
224 		sal_Bool bIsEOF = true;
225 		if( m_MapFile.isEndOfFile( &bIsEOF) != osl::File::E_None)
226 			return false;
227 		if( bIsEOF)
228 			break;
229 	}
230 
231 	m_bIsDirty = false;
232 	return true;
233 }
234 
235 //______________________________________________________________________________
236 void PersistentMap::flush( void)
237 {
238 	if( !m_bIsDirty)
239 		return;
240 	OSL_ASSERT( !m_bReadOnly);
241 	if( m_bToBeCreated && !m_entries.empty())
242 	{
243 		const sal_uInt32 nOpenFlags = osl_File_OpenFlag_Read | osl_File_OpenFlag_Write | osl_File_OpenFlag_Create;
244 		const osl::File::RC rcOpen = m_MapFile.open( nOpenFlags);
245 		m_bIsOpen = (rcOpen == osl::File::E_None);
246 		m_bToBeCreated = !m_bIsOpen;
247 	}
248 	if( !m_bIsOpen)
249 		return;
250 
251 	// write header magic
252 	m_MapFile.setPos( osl_Pos_Absolut, 0);
253 	sal_uInt64 nBytesWritten = 0;
254 	m_MapFile.write( PmapMagic, sizeof(PmapMagic), nBytesWritten);
255 
256 	// write key value pairs
257 	t_string2string_map::const_iterator it = m_entries.begin();
258 	for(; it != m_entries.end(); ++it) {
259 		// write line for key
260 		const OString aKeyString = encodeString( (*it).first);
261 		const sal_Int32 nKeyLen = aKeyString.getLength();
262 		m_MapFile.write( aKeyString.getStr(), nKeyLen, nBytesWritten);
263 		OSL_ASSERT( nKeyLen == (sal_Int32)nBytesWritten);
264 		m_MapFile.write( "\n", 1, nBytesWritten);
265 		// write line for value
266 		const OString& rValString = encodeString( (*it).second);
267 		const sal_Int32 nValLen = rValString.getLength();
268 		m_MapFile.write( rValString.getStr(), nValLen, nBytesWritten);
269 		OSL_ASSERT( nValLen == (sal_Int32)nBytesWritten);
270 		m_MapFile.write( "\n", 1, nBytesWritten);
271 	}
272 
273 	// write a file delimiter (an empty key-string)
274 	m_MapFile.write( "\n", 1, nBytesWritten);
275 	// truncate file here
276 	sal_uInt64 nNewFileSize;
277 	if( m_MapFile.getPos( nNewFileSize) == osl::File::E_None)
278 		m_MapFile.setSize( nNewFileSize);
279 	// flush to disk
280 	m_MapFile.sync();
281 	// the in-memory map now matches to the file on disk
282 	m_bIsDirty = false;
283 }
284 
285 //______________________________________________________________________________
286 bool PersistentMap::has( OString const & key ) const
287 {
288     return get( NULL, key );
289 }
290 
291 //______________________________________________________________________________
292 bool PersistentMap::get( OString * value, OString const & key ) const
293 {
294 	t_string2string_map::const_iterator it = m_entries.find( key);
295 	if( it == m_entries.end())
296 		return false;
297 	if( value)
298 		*value = it->second;
299 	return true;
300 }
301 
302 //______________________________________________________________________________
303 void PersistentMap::add( OString const & key, OString const & value )
304 {
305 	if( m_bReadOnly)
306 		return;
307 	typedef std::pair<t_string2string_map::iterator,bool> InsertRC;
308 	InsertRC r = m_entries.insert( t_string2string_map::value_type(key,value));
309 	m_bIsDirty = r.second;
310 }
311 
312 //______________________________________________________________________________
313 void PersistentMap::put( OString const & key, OString const & value )
314 {
315 	add( key, value);
316 	// HACK: flush now as the extension manager does not seem
317 	//       to properly destruct this object in some situations
318 	if( m_bIsDirty)
319 		flush();
320 }
321 
322 //______________________________________________________________________________
323 bool PersistentMap::erase( OString const & key, bool flush_immediately )
324 {
325 	if( m_bReadOnly)
326 		return false;
327 	size_t nCount = m_entries.erase( key);
328 	if( !nCount)
329 		return false;
330 	m_bIsDirty = true;
331 	if( flush_immediately)
332 		flush();
333 	return true;
334 }
335 
336 //______________________________________________________________________________
337 t_string2string_map PersistentMap::getEntries() const
338 {
339 	// TODO: return by const reference instead?
340 	return m_entries;
341 }
342 
343 //______________________________________________________________________________
344 #ifndef DISABLE_BDB2PMAP
345 bool PersistentMap::importFromBDB()
346 {
347 	if( m_bReadOnly)
348 		return false;
349 
350 	// get the name of its BDB counterpart
351 	rtl::OUString aDBName = m_MapFileName;
352 	if( !aDBName.endsWithAsciiL( ".pmap", 5))
353 		return false;
354 	aDBName = aDBName.replaceAt( aDBName.getLength()-5, 5, OUSTR(".db"));
355 
356 	// open the corresponding BDB file for reading
357 	osl::File aDBFile( aDBName);
358 	osl::File::RC rc = aDBFile.open( osl_File_OpenFlag_Read);
359 	if( rc != osl::File::E_None)
360 		return false;
361 	sal_uInt64 nFileSize = 0;
362 	if( aDBFile.getSize( nFileSize) != osl::File::E_None)
363 		return false;
364 
365 	// read the BDB file
366 	std::vector<sal_uInt8> aRawBDB( nFileSize);
367 	for( sal_uInt64 nOfs = 0; nOfs < nFileSize;) {
368 		sal_uInt64 nBytesRead = 0;
369 		rc = aDBFile.read( (void*)&aRawBDB[nOfs], nFileSize - nOfs, nBytesRead);
370 		if( (rc != osl::File::E_None) || !nBytesRead)
371 			return false;
372 		nOfs += nBytesRead;
373 	}
374 
375 	// check BDB file header for non_encrypted Hash_v9 format
376 	if( nFileSize < 0x1000)
377 		return false;
378 	if( (aRawBDB[12]!=0x61 || aRawBDB[13]!=0x15 || aRawBDB[14]!=0x06)
379 	&&  (aRawBDB[15]!=0x61 || aRawBDB[14]!=0x15 || aRawBDB[13]!=0x06))
380 		return false;
381 	if( aRawBDB[16]!=0x09)
382 		return false;
383 
384 	// find PackageManagers new_style entries
385 	// using a simple heuristic for BDB_Hash_v9 files
386 	int nEntryCount = 0;
387 	const sal_uInt8* pBeg = &aRawBDB[0] + 0x1000;
388 	const sal_uInt8* pEnd = pBeg + nFileSize;
389 	for( const sal_uInt8* pCur = pBeg; pCur < pEnd; ++pCur) {
390 		if( pCur[0] != 0x01)
391 			continue;
392 		// get the value-candidate
393 		const sal_uInt8* pVal = pCur + 1;
394 		while( ++pCur < pEnd)
395 			if( (*pCur < ' ') || ((*pCur > 0x7F) && (*pCur != 0xFF)))
396 				break;
397 		if( pCur >= pEnd)
398 			break;
399 		if( (pCur[0] != 0x01) || (pCur[1] != 0xFF))
400 			continue;
401 		const OString aVal( (sal_Char*)pVal, pCur - pVal);
402 		// get the key-candidate
403 		const sal_uInt8* pKey = pCur + 1;
404 		while( ++pCur < pEnd)
405 			if( (*pCur < ' ') || ((*pCur > 0x7F) && (*pCur != 0xFF)))
406 				break;
407 		if( (pCur < pEnd) && (*pCur > 0x01))
408 			continue;
409 		const OString aKey( (sal_Char*)pKey, pCur - pKey);
410 
411 		// add the key/value pair
412 		add( aKey, aVal);
413 		++nEntryCount;
414 	}
415 
416 	return (nEntryCount > 0);
417 }
418 #endif // DISABLE_BDB2PMAP
419 
420 }
421 
422