Don't create an AgentProgressMonitor instance every time this method is called; one...
[kdepim.git] / messageviewer / util.cpp
blob70eb82cddd9c201fa30d19f75c481707eb65a2ed
1 /*******************************************************************************
2 **
3 ** Filename : util
4 ** Created on : 03 April, 2005
5 ** Copyright : (c) 2005 Till Adam
6 ** Email : <adam@kde.org>
7 **
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
35 ** your version.
37 *******************************************************************************/
38 #include "util.h"
40 #include "iconnamecache.h"
41 #include "nodehelper.h"
43 #include "messagecore/globalsettings.h"
44 #include "messagecore/nodehelper.h"
45 #include "messagecore/stringutil.h"
47 #include <KMime/Message>
49 #include <kcharsets.h>
50 #include <KFileDialog>
51 #include <kglobal.h>
52 #include <klocale.h>
53 #include <kmessagebox.h>
54 #include <kio/netaccess.h>
55 #include <kdebug.h>
56 #include <KMimeType>
57 #include <KTemporaryFile>
59 #include <QTextCodec>
60 #include <QWidget>
62 using namespace MessageViewer;
64 bool Util::checkOverwrite( const KUrl &url, QWidget *w )
66 if ( KIO::NetAccess::exists( url, KIO::NetAccess::DestinationSide, w ) ) {
67 if ( KMessageBox::Cancel == KMessageBox::warningContinueCancel(
69 i18n( "A file named \"%1\" already exists. "
70 "Are you sure you want to overwrite it?", url.prettyUrl() ),
71 i18n( "Overwrite File?" ),
72 KStandardGuiItem::overwrite() ) )
73 return false;
75 return true;
78 QString Util::fileNameForMimetype( const QString &mimeType, int iconSize,
79 const QString &fallbackFileName1,
80 const QString &fallbackFileName2 )
82 QString fileName;
83 KMimeType::Ptr mime = KMimeType::mimeType( mimeType, KMimeType::ResolveAliases );
84 if ( mime ) {
85 fileName = mime->iconName();
86 } else {
87 kWarning() << "unknown mimetype" << mimeType;
90 if ( fileName.isEmpty() )
92 fileName = fallbackFileName1;
93 if ( fileName.isEmpty() )
94 fileName = fallbackFileName2;
95 if ( !fileName.isEmpty() ) {
96 fileName = KMimeType::findByPath( "/tmp/" + fileName, 0, true )->iconName();
100 return IconNameCache::instance()->iconPath( fileName, iconSize );
103 #ifdef Q_WS_MACX
104 #include <QDesktopServices>
105 #endif
107 bool Util::handleUrlOnMac( const KUrl& url )
109 #ifdef Q_WS_MACX
110 QDesktopServices::openUrl( url );
111 return true;
112 #else
113 Q_UNUSED( url );
114 return false;
115 #endif
118 QList<KMime::Content*> Util::allContents( const KMime::Content *message )
120 KMime::Content::List result;
121 KMime::Content *child = MessageCore::NodeHelper::firstChild( message );
122 if ( child ) {
123 result += child;
124 result += allContents( child );
126 KMime::Content *next = MessageCore::NodeHelper::nextSibling( message );
127 if ( next ) {
128 result += next;
129 result += allContents( next );
132 return result;
135 QList<KMime::Content*> Util::extractAttachments( const KMime::Message *message )
137 KMime::Content::List contents = allContents( message );
138 for ( KMime::Content::List::iterator it = contents.begin();
139 it != contents.end(); ) {
140 // only body parts which have a filename or a name parameter (except for
141 // the root node for which name is set to the message's subject) are
142 // considered attachments
143 KMime::Content* content = *it;
144 if ( content->contentDisposition()->filename().trimmed().isEmpty() &&
145 ( content->contentType()->name().trimmed().isEmpty() ||
146 content == message ) ) {
147 KMime::Content::List::iterator delIt = it;
148 ++it;
149 contents.erase( delIt );
150 } else {
151 ++it;
154 return contents;
157 bool Util::saveContents( QWidget *parent, const QList<KMime::Content*> &contents )
159 KUrl url, dirUrl;
160 if ( contents.count() > 1 ) {
161 // get the dir
162 dirUrl = KFileDialog::getExistingDirectoryUrl( KUrl( "kfiledialog:///saveAttachment" ),
163 parent,
164 i18n( "Save Attachments To" ) );
165 if ( !dirUrl.isValid() ) {
166 return false;
169 // we may not get a slash-terminated url out of KFileDialog
170 dirUrl.adjustPath( KUrl::AddTrailingSlash );
172 else {
173 // only one item, get the desired filename
174 KMime::Content *content = contents.first();
175 QString fileName = NodeHelper::fileName( content );
176 fileName = MessageCore::StringUtil::cleanFileName( fileName );
177 if ( fileName.isEmpty() ) {
178 fileName = i18nc( "filename for an unnamed attachment", "attachment.1" );
180 url = KFileDialog::getSaveUrl( KUrl( "kfiledialog:///saveAttachment/" + fileName ),
181 QString(),
182 parent,
183 i18n( "Save Attachment" ) );
184 if ( url.isEmpty() ) {
185 return false;
189 QMap< QString, int > renameNumbering;
191 bool globalResult = true;
192 int unnamedAtmCount = 0;
193 bool overwriteAll = false;
194 foreach( KMime::Content *content, contents ) {
195 KUrl curUrl;
196 if ( !dirUrl.isEmpty() ) {
197 curUrl = dirUrl;
198 QString fileName = MessageViewer::NodeHelper::fileName( content );
199 fileName = MessageCore::StringUtil::cleanFileName( fileName );
200 if ( fileName.isEmpty() ) {
201 ++unnamedAtmCount;
202 fileName = i18nc( "filename for the %1-th unnamed attachment",
203 "attachment.%1", unnamedAtmCount );
205 curUrl.setFileName( fileName );
206 } else {
207 curUrl = url;
210 if ( !curUrl.isEmpty() ) {
212 // Rename the file if we have already saved one with the same name:
213 // try appending a number before extension (e.g. "pic.jpg" => "pic_2.jpg")
214 QString origFile = curUrl.fileName();
215 QString file = origFile;
217 while ( renameNumbering.contains(file) ) {
218 file = origFile;
219 int num = renameNumbering[file] + 1;
220 int dotIdx = file.lastIndexOf('.');
221 file = file.insert( (dotIdx>=0) ? dotIdx : file.length(), QString("_") + QString::number(num) );
223 curUrl.setFileName(file);
225 // Increment the counter for both the old and the new filename
226 if ( !renameNumbering.contains(origFile))
227 renameNumbering[origFile] = 1;
228 else
229 renameNumbering[origFile]++;
231 if ( file != origFile ) {
232 if ( !renameNumbering.contains(file))
233 renameNumbering[file] = 1;
234 else
235 renameNumbering[file]++;
239 if ( !overwriteAll && KIO::NetAccess::exists( curUrl, KIO::NetAccess::DestinationSide, parent ) ) {
240 if ( contents.count() == 1 ) {
241 if ( KMessageBox::warningContinueCancel( parent,
242 i18n( "A file named <br><filename>%1</filename><br>already exists.<br><br>Do you want to overwrite it?",
243 curUrl.fileName() ),
244 i18n( "File Already Exists" ), KGuiItem(i18n("&Overwrite")) ) == KMessageBox::Cancel) {
245 continue;
248 else {
249 int button = KMessageBox::warningYesNoCancel(
250 parent,
251 i18n( "A file named <br><filename>%1</filename><br>already exists.<br><br>Do you want to overwrite it?",
252 curUrl.fileName() ),
253 i18n( "File Already Exists" ), KGuiItem(i18n("&Overwrite")),
254 KGuiItem(i18n("Overwrite &All")) );
255 if ( button == KMessageBox::Cancel )
256 continue;
257 else if ( button == KMessageBox::No )
258 overwriteAll = true;
261 // save
262 const bool result = saveContent( parent, content, curUrl );
263 if ( !result )
264 globalResult = result;
268 return globalResult;
271 bool Util::saveContent( QWidget *parent, KMime::Content* content, const KUrl& url )
273 // FIXME: This is all horribly broken. First of all, creating a NodeHelper and then immediatley
274 // reading out the encryption/signature state will not work at all.
275 // Then, topLevel() will not work for attachments that are inside encrypted parts.
276 // What should actually be done is either passing in an ObjectTreeParser that has already
277 // parsed the message, or creating an OTP here (which would have the downside that the
278 // password dialog for decrypting messages is shown twice)
279 #if 0 // totally broken
280 KMime::Content *topContent = content->topLevel();
281 MessageViewer::NodeHelper *mNodeHelper = new MessageViewer::NodeHelper;
282 bool bSaveEncrypted = false;
283 bool bEncryptedParts = mNodeHelper->encryptionState( content ) != MessageViewer::KMMsgNotEncrypted;
284 if( bEncryptedParts )
285 if( KMessageBox::questionYesNo( parent,
286 i18n( "The part %1 of the message is encrypted. Do you want to keep the encryption when saving?",
287 url.fileName() ),
288 i18n( "KMail Question" ), KGuiItem(i18n("Keep Encryption")), KGuiItem(i18n("Do Not Keep")) ) ==
289 KMessageBox::Yes )
290 bSaveEncrypted = true;
292 bool bSaveWithSig = true;
293 if(mNodeHelper->signatureState( content ) != MessageViewer::KMMsgNotSigned )
294 if( KMessageBox::questionYesNo( parent,
295 i18n( "The part %1 of the message is signed. Do you want to keep the signature when saving?",
296 url.fileName() ),
297 i18n( "KMail Question" ), KGuiItem(i18n("Keep Signature")), KGuiItem(i18n("Do Not Keep")) ) !=
298 KMessageBox::Yes )
299 bSaveWithSig = false;
301 QByteArray data;
302 if( bSaveEncrypted || !bEncryptedParts) {
303 KMime::Content *dataNode = content;
304 QByteArray rawReplyString;
305 bool gotRawReplyString = false;
306 if ( !bSaveWithSig ) {
307 if ( topContent->contentType()->mimeType() == "multipart/signed" ) {
308 // carefully look for the part that is *not* the signature part:
309 if ( ObjectTreeParser::findType( topContent, "application/pgp-signature", true, false ) ) {
310 dataNode = ObjectTreeParser::findTypeNot( topContent, "application", "pgp-signature", true, false );
311 } else if ( ObjectTreeParser::findType( topContent, "application/pkcs7-mime" , true, false ) ) {
312 dataNode = ObjectTreeParser::findTypeNot( topContent, "application", "pkcs7-mime", true, false );
313 } else {
314 dataNode = ObjectTreeParser::findTypeNot( topContent, "multipart", "", true, false );
316 } else {
317 EmptySource emptySource;
318 ObjectTreeParser otp( &emptySource, 0, 0,false, false, false );
320 // process this node and all it's siblings and descendants
321 mNodeHelper->setNodeUnprocessed( dataNode, true );
322 otp.parseObjectTree( dataNode );
324 rawReplyString = otp.rawReplyString();
325 gotRawReplyString = true;
328 QByteArray cstr = gotRawReplyString
329 ? rawReplyString
330 : dataNode->decodedContent();
331 data = KMime::CRLFtoLF( cstr );
333 #else
334 const QByteArray data = content->decodedContent();
335 kWarning() << "Port the encryption/signature handling when saving a KMime::Content.";
336 #endif
337 QDataStream ds;
338 QFile file;
339 KTemporaryFile tf;
340 if ( url.isLocalFile() )
342 // save directly
343 file.setFileName( url.toLocalFile() );
344 if ( !file.open( QIODevice::WriteOnly ) )
346 KMessageBox::error( parent,
347 i18nc( "1 = file name, 2 = error string",
348 "<qt>Could not write to the file<br><filename>%1</filename><br><br>%2",
349 file.fileName(),
350 file.errorString() ),
351 i18n( "Error saving attachment" ) );
352 return false;
355 const int permissions = MessageViewer::Util::getWritePermissions();
356 if ( permissions >= 0 )
357 fchmod( file.handle(), permissions );
359 ds.setDevice( &file );
360 } else
362 // tmp file for upload
363 tf.open();
364 ds.setDevice( &tf );
367 if ( ds.writeRawData( data.data(), data.size() ) == -1)
369 QFile *f = static_cast<QFile *>( ds.device() );
370 KMessageBox::error( parent,
371 i18nc( "1 = file name, 2 = error string",
372 "<qt>Could not write to the file<br><filename>%1</filename><br><br>%2",
373 f->fileName(),
374 f->errorString() ),
375 i18n( "Error saving attachment" ) );
376 return false;
379 if ( !url.isLocalFile() )
381 // QTemporaryFile::fileName() is only defined while the file is open
382 QString tfName = tf.fileName();
383 tf.close();
384 if ( !KIO::NetAccess::upload( tfName, url, parent ) )
386 KMessageBox::error( parent,
387 i18nc( "1 = file name, 2 = error string",
388 "<qt>Could not write to the file<br><filename>%1</filename><br><br>%2",
389 url.prettyUrl(),
390 KIO::NetAccess::lastErrorString() ),
391 i18n( "Error saving attachment" ) );
392 return false;
395 else
396 file.close();
398 #if 0
399 mNodeHelper->removeTempFiles();
400 delete mNodeHelper;
401 #endif
402 return true;
406 int Util::getWritePermissions()
408 // #79685, #232001 by default use the umask the user defined, but let it be configurable
409 if ( MessageCore::GlobalSettings::self()->disregardUmask() ) {
410 return S_IRUSR | S_IWUSR;
411 } else {
412 return -1;