1 /*******************************************************************************
4 ** Created on : 03 April, 2005
5 ** Copyright : (c) 2005 Till Adam
6 ** Email : <adam@kde.org>
8 *******************************************************************************/
10 /*******************************************************************************
12 ** This program is free software; you can redistribute it and/or modify
13 ** it under the terms of the GNU General Public License as published by
14 ** the Free Software Foundation; either version 2 of the License, or
15 ** (at your option) any later version.
17 ** It is distributed in the hope that it will be useful, but
18 ** WITHOUT ANY WARRANTY; without even the implied warranty of
19 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 ** General Public License for more details.
22 ** You should have received a copy of the GNU General Public License
23 ** along with this program; if not, write to the Free Software
24 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
26 ** In addition, as a special exception, the copyright holders give
27 ** permission to link the code of this program with any edition of
28 ** the Qt library by Trolltech AS, Norway (or with modified versions
29 ** of Qt that use the same license as Qt), and distribute linked
30 ** combinations including the two. You must obey the GNU General
31 ** Public License in all respects for all of the code used other than
32 ** Qt. If you modify this file, you may extend this exception to
33 ** your version of the file, but you are not obligated to do so. If
34 ** you do not wish to do so, delete this exception statement from
37 *******************************************************************************/
38 #include <config-messageviewer.h>
42 #include "iconnamecache.h"
43 #include "nodehelper.h"
44 #include "renamefiledialog.h"
46 #include "messagecore/globalsettings.h"
47 #include "messagecore/nodehelper.h"
48 #include "messagecore/stringutil.h"
50 #include <akonadi/item.h>
53 #include <kmbox/mbox.h>
55 #include <KMime/Message>
57 #include <kcharsets.h>
58 #include <KFileDialog>
61 #include <kmessagebox.h>
62 #include <kio/netaccess.h>
65 #include <KTemporaryFile>
70 using namespace MessageViewer
;
72 bool Util::checkOverwrite( const KUrl
&url
, QWidget
*w
)
74 if ( KIO::NetAccess::exists( url
, KIO::NetAccess::DestinationSide
, w
) ) {
75 if ( KMessageBox::Cancel
== KMessageBox::warningContinueCancel(
77 i18n( "A file named \"%1\" already exists. "
78 "Are you sure you want to overwrite it?", url
.prettyUrl() ),
79 i18n( "Overwrite File?" ),
80 KStandardGuiItem::overwrite() ) )
86 QString
Util::fileNameForMimetype( const QString
&mimeType
, int iconSize
,
87 const QString
&fallbackFileName1
,
88 const QString
&fallbackFileName2
)
91 KMimeType::Ptr mime
= KMimeType::mimeType( mimeType
, KMimeType::ResolveAliases
);
93 fileName
= mime
->iconName();
95 fileName
= QLatin1String( "unknown" );
96 kWarning() << "unknown mimetype" << mimeType
;
99 if ( fileName
.isEmpty() )
101 fileName
= fallbackFileName1
;
102 if ( fileName
.isEmpty() )
103 fileName
= fallbackFileName2
;
104 if ( !fileName
.isEmpty() ) {
105 fileName
= KMimeType::findByPath( "/tmp/" + fileName
, 0, true )->iconName();
109 return IconNameCache::instance()->iconPath( fileName
, iconSize
);
112 #if defined Q_WS_WIN || defined Q_WS_MACX
113 #include <QDesktopServices>
116 bool Util::handleUrlWithQDesktopServices( const KUrl
& url
)
118 #if defined Q_WS_WIN || defined Q_WS_MACX
119 QDesktopServices::openUrl( url
);
127 QList
<KMime::Content
*> Util::allContents( const KMime::Content
*message
)
129 KMime::Content::List result
;
130 KMime::Content
*child
= MessageCore::NodeHelper::firstChild( message
);
133 result
+= allContents( child
);
135 KMime::Content
*next
= MessageCore::NodeHelper::nextSibling( message
);
138 result
+= allContents( next
);
144 QList
<KMime::Content
*> Util::extractAttachments( const KMime::Message
*message
)
146 const KMime::Content::List contents
= allContents( message
);
147 KMime::Content::List result
;
148 for ( KMime::Content::List::const_iterator it
= contents
.constBegin();
149 it
!= contents
.constEnd(); ) {
150 KMime::Content
* content
= *it
;
151 if ( content
->contentDisposition()->filename().trimmed().isEmpty() &&
152 ( content
->contentType()->name().trimmed().isEmpty() ||
153 content
== message
) ) {
164 bool Util::saveContents( QWidget
*parent
, const QList
<KMime::Content
*> &contents
)
167 if ( contents
.count() > 1 ) {
169 dirUrl
= KFileDialog::getExistingDirectoryUrl( KUrl( "kfiledialog:///saveAttachment" ),
171 i18n( "Save Attachments To" ) );
172 if ( !dirUrl
.isValid() ) {
176 // we may not get a slash-terminated url out of KFileDialog
177 dirUrl
.adjustPath( KUrl::AddTrailingSlash
);
180 // only one item, get the desired filename
181 KMime::Content
*content
= contents
.first();
182 QString fileName
= NodeHelper::fileName( content
);
183 fileName
= MessageCore::StringUtil::cleanFileName( fileName
);
184 if ( fileName
.isEmpty() ) {
185 fileName
= i18nc( "filename for an unnamed attachment", "attachment.1" );
187 url
= KFileDialog::getSaveUrl( KUrl( "kfiledialog:///saveAttachment/" + fileName
),
190 i18n( "Save Attachment" ) );
191 if ( url
.isEmpty() ) {
196 QMap
< QString
, int > renameNumbering
;
198 bool globalResult
= true;
199 int unnamedAtmCount
= 0;
200 MessageViewer::RenameFileDialog::RenameFileDialogResult result
= MessageViewer::RenameFileDialog::RENAMEFILE_IGNORE
;
201 foreach( KMime::Content
*content
, contents
) {
203 if ( !dirUrl
.isEmpty() ) {
205 QString fileName
= MessageViewer::NodeHelper::fileName( content
);
206 fileName
= MessageCore::StringUtil::cleanFileName( fileName
);
207 if ( fileName
.isEmpty() ) {
209 fileName
= i18nc( "filename for the %1-th unnamed attachment",
210 "attachment.%1", unnamedAtmCount
);
212 curUrl
.setFileName( fileName
);
217 if ( !curUrl
.isEmpty() ) {
219 // Rename the file if we have already saved one with the same name:
220 // try appending a number before extension (e.g. "pic.jpg" => "pic_2.jpg")
221 QString origFile
= curUrl
.fileName();
222 QString file
= origFile
;
224 while ( renameNumbering
.contains(file
) ) {
226 int num
= renameNumbering
[file
] + 1;
227 int dotIdx
= file
.lastIndexOf('.');
228 file
= file
.insert( (dotIdx
>=0) ? dotIdx
: file
.length(), QString("_") + QString::number(num
) );
230 curUrl
.setFileName(file
);
232 // Increment the counter for both the old and the new filename
233 if ( !renameNumbering
.contains(origFile
))
234 renameNumbering
[origFile
] = 1;
236 renameNumbering
[origFile
]++;
238 if ( file
!= origFile
) {
239 if ( !renameNumbering
.contains(file
))
240 renameNumbering
[file
] = 1;
242 renameNumbering
[file
]++;
246 if( !(result
== MessageViewer::RenameFileDialog::RENAMEFILE_OVERWRITEALL
||
247 result
== MessageViewer::RenameFileDialog::RENAMEFILE_IGNOREALL
))
249 if ( KIO::NetAccess::exists( curUrl
, KIO::NetAccess::DestinationSide
, parent
) ) {
250 if ( contents
.count() == 1 ) {
251 RenameFileDialog
*dlg
= new RenameFileDialog(curUrl
,false, parent
);
252 result
= static_cast<MessageViewer::RenameFileDialog::RenameFileDialogResult
>(dlg
->exec());
253 if ( result
== MessageViewer::RenameFileDialog::RENAMEFILE_IGNORE
)
258 else if ( result
== MessageViewer::RenameFileDialog::RENAMEFILE_RENAME
)
260 curUrl
= dlg
->newName();
265 RenameFileDialog
*dlg
= new RenameFileDialog(curUrl
,true, parent
);
266 result
= static_cast<MessageViewer::RenameFileDialog::RenameFileDialogResult
>(dlg
->exec());
268 if ( result
== MessageViewer::RenameFileDialog::RENAMEFILE_IGNORE
||
269 result
== MessageViewer::RenameFileDialog::RENAMEFILE_IGNOREALL
)
274 else if ( result
== MessageViewer::RenameFileDialog::RENAMEFILE_RENAME
)
276 curUrl
= dlg
->newName();
283 if( result
!= MessageViewer::RenameFileDialog::RENAMEFILE_IGNOREALL
) {
284 const bool result
= saveContent( parent
, content
, curUrl
);
286 globalResult
= result
;
294 bool Util::saveContent( QWidget
*parent
, KMime::Content
* content
, const KUrl
& url
)
296 // FIXME: This is all horribly broken. First of all, creating a NodeHelper and then immediatley
297 // reading out the encryption/signature state will not work at all.
298 // Then, topLevel() will not work for attachments that are inside encrypted parts.
299 // What should actually be done is either passing in an ObjectTreeParser that has already
300 // parsed the message, or creating an OTP here (which would have the downside that the
301 // password dialog for decrypting messages is shown twice)
302 #if 0 // totally broken
303 KMime::Content
*topContent
= content
->topLevel();
304 MessageViewer::NodeHelper
*mNodeHelper
= new MessageViewer::NodeHelper
;
305 bool bSaveEncrypted
= false;
306 bool bEncryptedParts
= mNodeHelper
->encryptionState( content
) != MessageViewer::KMMsgNotEncrypted
;
307 if( bEncryptedParts
)
308 if( KMessageBox::questionYesNo( parent
,
309 i18n( "The part %1 of the message is encrypted. Do you want to keep the encryption when saving?",
311 i18n( "KMail Question" ), KGuiItem(i18n("Keep Encryption")), KGuiItem(i18n("Do Not Keep")) ) ==
313 bSaveEncrypted
= true;
315 bool bSaveWithSig
= true;
316 if(mNodeHelper
->signatureState( content
) != MessageViewer::KMMsgNotSigned
)
317 if( KMessageBox::questionYesNo( parent
,
318 i18n( "The part %1 of the message is signed. Do you want to keep the signature when saving?",
320 i18n( "KMail Question" ), KGuiItem(i18n("Keep Signature")), KGuiItem(i18n("Do Not Keep")) ) !=
322 bSaveWithSig
= false;
325 if( bSaveEncrypted
|| !bEncryptedParts
) {
326 KMime::Content
*dataNode
= content
;
327 QByteArray rawDecryptedBody
;
328 bool gotRawDecryptedBody
= false;
329 if ( !bSaveWithSig
) {
330 if ( topContent
->contentType()->mimeType() == "multipart/signed" ) {
331 // carefully look for the part that is *not* the signature part:
332 if ( ObjectTreeParser::findType( topContent
, "application/pgp-signature", true, false ) ) {
333 dataNode
= ObjectTreeParser::findTypeNot( topContent
, "application", "pgp-signature", true, false );
334 } else if ( ObjectTreeParser::findType( topContent
, "application/pkcs7-mime" , true, false ) ) {
335 dataNode
= ObjectTreeParser::findTypeNot( topContent
, "application", "pkcs7-mime", true, false );
337 dataNode
= ObjectTreeParser::findTypeNot( topContent
, "multipart", "", true, false );
340 EmptySource emptySource
;
341 ObjectTreeParser
otp( &emptySource
, 0, 0, false, false );
343 // process this node and all it's siblings and descendants
344 mNodeHelper
->setNodeUnprocessed( dataNode
, true );
345 otp
.parseObjectTree( dataNode
);
347 rawDecryptedBody
= otp
.rawDecryptedBody();
348 gotRawDecryptedBody
= true;
351 QByteArray cstr
= gotRawDecryptedBody
353 : dataNode
->decodedContent();
354 data
= KMime::CRLFtoLF( cstr
);
357 const QByteArray data
= content
->decodedContent();
358 kWarning() << "Port the encryption/signature handling when saving a KMime::Content.";
363 if ( url
.isLocalFile() )
366 file
.setFileName( url
.toLocalFile() );
367 if ( !file
.open( QIODevice::WriteOnly
) )
369 KMessageBox::error( parent
,
370 i18nc( "1 = file name, 2 = error string",
371 "<qt>Could not write to the file<br><filename>%1</filename><br><br>%2",
373 file
.errorString() ),
374 i18n( "Error saving attachment" ) );
378 const int permissions
= MessageViewer::Util::getWritePermissions();
379 if ( permissions
>= 0 )
380 fchmod( file
.handle(), permissions
);
382 ds
.setDevice( &file
);
385 // tmp file for upload
390 const int bytesWritten
= ds
.writeRawData( data
.data(), data
.size() );
391 if ( bytesWritten
!= data
.size() ) {
392 QFile
*f
= static_cast<QFile
*>( ds
.device() );
393 KMessageBox::error( parent
,
394 i18nc( "1 = file name, 2 = error string",
395 "<qt>Could not write to the file<br><filename>%1</filename><br><br>%2",
398 i18n( "Error saving attachment" ) );
399 // Remove the newly created empty or partial file
404 if ( !url
.isLocalFile() )
406 // QTemporaryFile::fileName() is only defined while the file is open
407 QString tfName
= tf
.fileName();
409 if ( !KIO::NetAccess::upload( tfName
, url
, parent
) )
411 KMessageBox::error( parent
,
412 i18nc( "1 = file name, 2 = error string",
413 "<qt>Could not write to the file<br><filename>%1</filename><br><br>%2",
415 KIO::NetAccess::lastErrorString() ),
416 i18n( "Error saving attachment" ) );
424 mNodeHelper
->removeTempFiles();
431 int Util::getWritePermissions()
433 // #79685, #232001 by default use the umask the user defined, but let it be configurable
434 if ( MessageCore::GlobalSettings::self()->disregardUmask() ) {
435 return S_IRUSR
| S_IWUSR
;
441 bool Util::saveAttachments( const KMime::Content::List
& contents
, QWidget
*parent
)
443 if ( contents
.isEmpty() ) {
444 KMessageBox::information( parent
, i18n( "Found no attachments to save." ) );
448 return Util::saveContents( parent
, contents
);
451 bool Util::saveMessageInMbox( const QList
<Akonadi::Item
>& retrievedMsgs
, QWidget
*parent
)
455 if ( retrievedMsgs
.isEmpty() )
457 const Akonadi::Item msgBase
= retrievedMsgs
.first();
459 fileName
= MessageCore::StringUtil::cleanFileName(MessageViewer::NodeHelper::cleanSubject ( msgBase
.payload
<KMime::Message::Ptr
>().get() ).trimmed() );
461 if ( !fileName
.endsWith( QLatin1String( ".mbox" ) ) )
464 const QString filter
= i18n( "*.mbox|email messages (*.mbox)\n*|all files (*)" );
465 const KUrl url
= KFileDialog::getSaveUrl( KUrl::fromPath( fileName
), filter
, parent
);
470 const QString localFileName
= url
.toLocalFile();
471 if ( localFileName
.isEmpty() )
475 if ( !mbox
.load( localFileName
) ) {
480 foreach ( const Akonadi::Item
&item
, retrievedMsgs
) {
481 if ( item
.hasPayload
<KMime::Message::Ptr
>() ) {
482 mbox
.appendMessage( item
.payload
<KMime::Message::Ptr
>() );
486 if ( !mbox
.save() ) {