compile
[kdegraphics.git] / okular / generators / ooo / manifest.cpp
blobae086099a7fe9eb3cc261fd4d4d08c236d8b564e
1 /***************************************************************************
2 * Copyright (C) 2007, 2009 by Brad Hards <bradh@frogmouth.net> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 ***************************************************************************/
10 #include "manifest.h"
11 #include "debug.h"
13 #include <QBuffer>
14 #include <QXmlStreamReader>
16 #include <KFilterDev>
17 #include <KLocale>
18 #include <KMessageBox>
19 #include <KPasswordDialog>
20 #include <KWallet/Wallet>
22 using namespace OOO;
24 //---------------------------------------------------------------------
26 ManifestEntry::ManifestEntry( const QString &fileName ) :
27 m_fileName( fileName )
31 void ManifestEntry::setMimeType( const QString &mimeType )
33 m_mimeType = mimeType;
36 void ManifestEntry::setSize( const QString &size )
38 m_size = size;
41 QString ManifestEntry::fileName() const
43 return m_fileName;
46 QString ManifestEntry::mimeType() const
48 return m_mimeType;
51 QString ManifestEntry::size() const
53 return m_size;
56 void ManifestEntry::setChecksumType( const QString &checksumType )
58 m_checksumType = checksumType;
61 QString ManifestEntry::checksumType() const
63 return m_checksumType;
66 void ManifestEntry::setChecksum( const QString &checksum )
68 m_checksum = QByteArray::fromBase64( checksum.toAscii() );
71 QByteArray ManifestEntry::checksum() const
73 return m_checksum;
76 void ManifestEntry::setAlgorithm( const QString &algorithm )
78 m_algorithm = algorithm;
81 QString ManifestEntry::algorithm() const
83 return m_algorithm;
86 void ManifestEntry::setInitialisationVector( const QString &initialisationVector )
88 m_initialisationVector = QByteArray::fromBase64( initialisationVector.toAscii() );
91 QByteArray ManifestEntry::initialisationVector() const
93 return m_initialisationVector;
96 void ManifestEntry::setKeyDerivationName( const QString &keyDerivationName )
98 m_keyDerivationName = keyDerivationName;
101 QString ManifestEntry::keyDerivationName() const
103 return m_keyDerivationName;
106 void ManifestEntry::setIterationCount( const QString &iterationCount )
108 m_iterationCount = iterationCount.toInt();
111 int ManifestEntry::iterationCount() const
113 return m_iterationCount;
116 void ManifestEntry::setSalt( const QString &salt )
118 m_salt = QByteArray::fromBase64( salt.toAscii() );
121 QByteArray ManifestEntry::salt() const
123 return m_salt;
126 //---------------------------------------------------------------------
128 Manifest::Manifest( const QString &odfFileName, const QByteArray &manifestData )
129 : m_odfFileName( odfFileName ), m_haveGoodPassword( false ), m_userCancelled( false )
131 // I don't know why the parser barfs on this.
132 QByteArray manifestCopy = manifestData;
133 manifestCopy.replace(QByteArray("DOCTYPE manifest:manifest"), QByteArray("DOCTYPE manifest"));
135 QXmlStreamReader xml( manifestCopy );
137 ManifestEntry *currentEntry = 0;
138 while ( ! xml.atEnd() ) {
139 xml.readNext();
140 if ( (xml.tokenType() == QXmlStreamReader::NoToken) ||
141 (xml.tokenType() == QXmlStreamReader::Invalid) ||
142 (xml.tokenType() == QXmlStreamReader::StartDocument) ||
143 (xml.tokenType() == QXmlStreamReader::EndDocument) ||
144 (xml.tokenType() == QXmlStreamReader::DTD) ||
145 (xml.tokenType() == QXmlStreamReader::Characters) ) {
146 continue;
148 if (xml.tokenType() == QXmlStreamReader::StartElement) {
149 if ( xml.name().toString() == "manifest" ) {
150 continue;
151 } else if ( xml.name().toString() == "file-entry" ) {
152 QXmlStreamAttributes attributes = xml.attributes();
153 if (currentEntry != 0) {
154 kWarning(OooDebug) << "Got new StartElement for new file-entry, but haven't finished the last one yet!";
155 kWarning(OooDebug) << "processing" << currentEntry->fileName() << ", got" << attributes.value("manifest:full-path").toString();
157 currentEntry = new ManifestEntry( attributes.value("manifest:full-path").toString() );
158 currentEntry->setMimeType( attributes.value("manifest:media-type").toString() );
159 currentEntry->setSize( attributes.value("manifest:size").toString() );
160 } else if ( xml.name().toString() == "encryption-data" ) {
161 if (currentEntry == 0) {
162 kWarning(OooDebug) << "Got encryption-data without valid file-entry at line" << xml.lineNumber();
163 continue;
165 QXmlStreamAttributes encryptionAttributes = xml.attributes();
166 currentEntry->setChecksumType( encryptionAttributes.value("manifest:checksum-type").toString() );
167 currentEntry->setChecksum( encryptionAttributes.value("manifest:checksum").toString() );
168 } else if ( xml.name().toString() == "algorithm" ) {
169 if (currentEntry == 0) {
170 kWarning(OooDebug) << "Got algorithm without valid file-entry at line" << xml.lineNumber();
171 continue;
173 QXmlStreamAttributes algorithmAttributes = xml.attributes();
174 currentEntry->setAlgorithm( algorithmAttributes.value("manifest:algorithm-name").toString() );
175 currentEntry->setInitialisationVector( algorithmAttributes.value("manifest:initialisation-vector").toString() );
176 } else if ( xml.name().toString() == "key-derivation" ) {
177 if (currentEntry == 0) {
178 kWarning(OooDebug) << "Got key-derivation without valid file-entry at line" << xml.lineNumber();
179 continue;
181 QXmlStreamAttributes kdfAttributes = xml.attributes();
182 currentEntry->setKeyDerivationName( kdfAttributes.value("manifest:key-derivation-name").toString() );
183 currentEntry->setIterationCount( kdfAttributes.value("manifest:iteration-count").toString() );
184 currentEntry->setSalt( kdfAttributes.value("manifest:salt").toString() );
185 } else {
186 // handle other StartDocument types here
187 kWarning(OooDebug) << "Unexpected start document type: " << xml.name().toString();
189 } else if ( xml.tokenType() == QXmlStreamReader::EndElement ) {
190 if ( xml.name().toString() == "manifest" ) {
191 continue;
192 } else if ( xml.name().toString() == "file-entry") {
193 if (currentEntry == 0) {
194 kWarning(OooDebug) << "Got EndElement for file-entry without valid StartElement at line" << xml.lineNumber();
195 continue;
197 // we're finished processing that file entry
198 if ( mEntries.contains( currentEntry->fileName() ) ) {
199 kWarning(OooDebug) << "Can't insert entry because of duplicate name:" << currentEntry->fileName();
200 delete currentEntry;
201 } else {
202 mEntries.insert( currentEntry->fileName(), currentEntry);
204 currentEntry = 0;
208 if (xml.hasError()) {
209 kWarning(OooDebug) << "error: " << xml.errorString() << xml.lineNumber() << xml.columnNumber();
213 Manifest::~Manifest()
215 savePasswordToWallet();
217 qDeleteAll( mEntries );
220 ManifestEntry* Manifest::entryByName( const QString &filename )
222 return mEntries.value( filename, 0 );
225 bool Manifest::testIfEncrypted( const QString &filename )
227 ManifestEntry *entry = entryByName( filename );
229 if (entry) {
230 return ( entry->salt().length() > 0 );
233 return false;
236 void Manifest::getPasswordFromUser()
238 // TODO: This should have a proper parent
239 KPasswordDialog dlg( 0, KPasswordDialog::KPasswordDialogFlags() );
240 dlg.setCaption( i18n( "Document Password" ) );
241 dlg.setPrompt( i18n( "Please insert the password to read the document:" ) );
242 if( ! dlg.exec() ) {
243 // user cancel
244 m_userCancelled = true;
245 } else {
246 m_password = dlg.password();
250 void Manifest::getPasswordFromWallet()
252 if ( KWallet::Wallet::folderDoesNotExist( KWallet::Wallet::LocalWallet(), KWallet::Wallet::PasswordFolder() ) ) {
253 return;
256 if ( m_odfFileName.isEmpty() ) {
257 return;
259 // This naming is consistent with how KOffice does it.
260 QString entryKey = m_odfFileName + "/opendocument";
262 if ( KWallet::Wallet::keyDoesNotExist( KWallet::Wallet::LocalWallet(), KWallet::Wallet::PasswordFolder(), entryKey ) ) {
263 return;
266 // TODO: this should have a proper parent. I can't see a way to get one though...
267 KWallet::Wallet *wallet = KWallet::Wallet::openWallet( KWallet::Wallet::LocalWallet(), 0 );
268 if ( ! wallet ) {
269 return;
272 if ( ! wallet->setFolder( KWallet::Wallet::PasswordFolder() ) ) {
273 delete wallet;
274 return;
277 wallet->readPassword( entryKey, m_password );
278 delete wallet;
281 void Manifest::savePasswordToWallet()
283 if ( ! m_haveGoodPassword ) {
284 return;
287 if ( m_odfFileName.isEmpty() ) {
288 return;
291 // TODO: this should have a proper parent. I can't see a way to get one though...
292 KWallet::Wallet *wallet = KWallet::Wallet::openWallet( KWallet::Wallet::LocalWallet(), 0 );
293 if ( ! wallet ) {
294 return;
297 if ( ! wallet->hasFolder( KWallet::Wallet::PasswordFolder() ) ) {
298 wallet->createFolder( KWallet::Wallet::PasswordFolder() );
301 if ( ! wallet->setFolder( KWallet::Wallet::PasswordFolder() ) ) {
302 delete wallet;
303 return;
306 // This naming is consistent with how KOffice does it.
307 QString entryKey = m_odfFileName + "/opendocument";
309 if ( wallet->hasEntry( entryKey ) ) {
310 wallet->removeEntry( entryKey );
313 wallet->writePassword( entryKey, m_password );
315 delete wallet;
318 void Manifest::checkPassword( ManifestEntry *entry, const QByteArray &fileData, QByteArray *decryptedData )
320 #ifdef QCA2
321 QCA::SymmetricKey key = QCA::PBKDF2( "sha1" ).makeKey( QCA::Hash( "sha1" ).hash( m_password.toLocal8Bit() ),
322 QCA::InitializationVector( entry->salt() ),
323 16, //128 bit key
324 entry->iterationCount() );
326 QCA::Cipher decoder( "blowfish", QCA::Cipher::CFB, QCA::Cipher::DefaultPadding,
327 QCA::Decode, key, QCA::InitializationVector( entry->initialisationVector() ) );
328 *decryptedData = decoder.update( QCA::MemoryRegion(fileData) ).toByteArray();
329 *decryptedData += decoder.final().toByteArray();
331 QByteArray csum;
332 if ( entry->checksumType() == "SHA1/1K" ) {
333 csum = QCA::Hash( "sha1").hash( decryptedData->left(1024) ).toByteArray();
334 } else if ( entry->checksumType() == "SHA1" ) {
335 csum = QCA::Hash( "sha1").hash( *decryptedData ).toByteArray();
336 } else {
337 kDebug(OooDebug) << "unknown checksum type: " << entry->checksumType();
338 // we can only assume it will be OK.
339 m_haveGoodPassword = true;
340 return;
343 if ( entry->checksum() == csum ) {
344 m_haveGoodPassword = true;
345 } else {
346 m_haveGoodPassword = false;
348 #else
349 m_haveGoodPassword = false;
350 #endif
353 QByteArray Manifest::decryptFile( const QString &filename, const QByteArray &fileData )
355 #ifdef QCA2
356 ManifestEntry *entry = entryByName( filename );
358 if ( ! QCA::isSupported( "sha1" ) ) {
359 KMessageBox::error( 0, i18n("This document is encrypted, and crypto support is compiled in, but a hashing plugin could not be located") );
360 // in the hope that it wasn't really encrypted...
361 return QByteArray( fileData );
364 if ( ! QCA::isSupported( "pbkdf2(sha1)") ) {
365 KMessageBox::error( 0, i18n("This document is encrypted, and crypto support is compiled in, but a key derivation plugin could not be located") );
366 // in the hope that it wasn't really encrypted...
367 return QByteArray( fileData );
370 if ( ! QCA::isSupported( "blowfish-cfb") ) {
371 KMessageBox::error( 0, i18n("This document is encrypted, and crypto support is compiled in, but a cipher plugin could not be located") );
372 // in the hope that it wasn't really encrypted...
373 return QByteArray( fileData );
376 if (m_userCancelled) {
377 return QByteArray();
381 QByteArray decryptedData;
382 if (! m_haveGoodPassword ) {
383 getPasswordFromWallet();
384 checkPassword( entry, fileData, &decryptedData );
387 do {
388 if (! m_haveGoodPassword ) {
389 getPasswordFromUser();
392 if (m_userCancelled) {
393 return QByteArray();
396 checkPassword( entry, fileData, &decryptedData );
397 if ( !m_haveGoodPassword ) {
398 KMessageBox::information( 0, i18n("The password is not correct."), i18n("Incorrect password") );
399 } else {
400 // kDebug(OooDebug) << "Have good password";
403 } while ( ( ! m_haveGoodPassword ) && ( ! m_userCancelled ) );
405 if ( m_haveGoodPassword ) {
406 QIODevice *decompresserDevice = KFilterDev::device( new QBuffer( &decryptedData, 0 ), "application/x-gzip", true );
407 if( !decompresserDevice ) {
408 kDebug(OooDebug) << "Couldn't create decompressor";
409 // hopefully it isn't compressed then!
410 return QByteArray( fileData );
413 static_cast<KFilterDev*>( decompresserDevice )->setSkipHeaders( );
415 decompresserDevice->open( QIODevice::ReadOnly );
417 return decompresserDevice->readAll();
419 } else {
420 return QByteArray( fileData );
422 #else
423 // TODO: This should have a proper parent
424 KMessageBox::error( 0, i18n("This document is encrypted, but Okular was compiled without crypto support. This document will probably not open.") );
425 // this is equivalent to what happened before all this Manifest stuff :-)
426 return QByteArray( fileData );
427 #endif