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 if( nHfOffset <= 0) 404 continue; 405 const sal_uInt8* pCur = pPage + nHfOffset; 406 // iterate through the entries 407 for(; pCur < pEnd; ++pCur) { 408 if( pCur[0] != 0x01) 409 continue; 410 // get the value-candidate 411 const sal_uInt8* pVal = pCur + 1; 412 while( ++pCur < pEnd) 413 if( (*pCur < ' ') || ((*pCur > 0x7F) && (*pCur != 0xFF))) 414 break; 415 if( pCur >= pEnd) 416 break; 417 if( (pCur[0] != 0x01) || (pCur[1] != 0xFF)) 418 continue; 419 const OString aVal( (sal_Char*)pVal, pCur - pVal); 420 // get the key-candidate 421 const sal_uInt8* pKey = pCur + 1; 422 while( ++pCur < pEnd) 423 if( (*pCur < ' ') || ((*pCur > 0x7F) && (*pCur != 0xFF))) 424 break; 425 if( (pCur < pEnd) && (*pCur > 0x01)) 426 continue; 427 const OString aKey( (sal_Char*)pKey, pCur - pKey); 428 --pCur; // prepare for next round by rewinding to end of key-string 429 430 // add the key/value pair 431 add( aKey, aVal); 432 ++nEntryCount; 433 } 434 } 435 436 return (nEntryCount > 0); 437 } 438 #endif // DISABLE_BDB2PMAP 439 440 } 441 442