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 format v4..9
376 	if( nFileSize < 0x0100)
377 		return false;
378 	if( aRawBDB[24] != 0) // only not-encrypted migration
379 		return false;
380 	if( aRawBDB[25] != 8) // we expect a P_HASHMETA page
381 		return false;
382 	const bool bLE = (aRawBDB[12]==0x61 && aRawBDB[13]==0x15 && aRawBDB[14]==0x06);
383 	const bool bBE = (aRawBDB[15]==0x61 && aRawBDB[14]==0x15 && aRawBDB[13]==0x06);
384 	if( bBE == bLE)
385 		return false;
386 	if( (aRawBDB[16] < 4) || (9 < aRawBDB[16])) // version
387 		return false;
388 	const sal_uInt64 nPgSize = bLE
389 	?	(aRawBDB[20] + (aRawBDB[21]<<8) + (aRawBDB[22]<<16) + (aRawBDB[23]<<24))
390 	:	(aRawBDB[23] + (aRawBDB[22]<<8) + (aRawBDB[21]<<16) + (aRawBDB[20]<<24));
391 	const int nPgCount = nFileSize / nPgSize;
392 	if( nPgCount * nPgSize != nFileSize)
393 		return false;
394 
395 	// find PackageManager's new_style entries
396 	// using a simple heuristic for BDB_Hash pages
397 	int nEntryCount = 0;
398 	for( int nPgNo = 1; nPgNo < nPgCount; ++nPgNo) {
399 		// parse the next _db_page
400 		const sal_uInt8* const pPage = &aRawBDB[ nPgNo * nPgSize];
401 		const sal_uInt8* const pEnd = pPage + nPgSize;
402 		const int nHfOffset = bLE ? (pPage[22] + (pPage[23]<<8)) : (pPage[23] + (pPage[22]<<8));
403 		const sal_uInt8* pCur = pPage + nHfOffset;
404 		// iterate through the entries
405 		for(; pCur < pEnd; ++pCur) {
406 			if( pCur[0] != 0x01)
407 				continue;
408 			// get the value-candidate
409 			const sal_uInt8* pVal = pCur + 1;
410 			while( ++pCur < pEnd)
411 				if( (*pCur < ' ') || ((*pCur > 0x7F) && (*pCur != 0xFF)))
412 					break;
413 			if( pCur >= pEnd)
414 				break;
415 			if( (pCur[0] != 0x01) || (pCur[1] != 0xFF))
416 				continue;
417 			const OString aVal( (sal_Char*)pVal, pCur - pVal);
418 			// get the key-candidate
419 			const sal_uInt8* pKey = pCur + 1;
420 			while( ++pCur < pEnd)
421 				if( (*pCur < ' ') || ((*pCur > 0x7F) && (*pCur != 0xFF)))
422 					break;
423 			if( (pCur < pEnd) && (*pCur > 0x01))
424 				continue;
425 			const OString aKey( (sal_Char*)pKey, pCur - pKey);
426 
427 			// add the key/value pair
428 			add( aKey, aVal);
429 			++nEntryCount;
430 		}
431 	}
432 
433 	return (nEntryCount > 0);
434 }
435 #endif // DISABLE_BDB2PMAP
436 
437 }
438 
439