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 *******************************************************************************/
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>
53 #include <kmessagebox.h>
54 #include <kio/netaccess.h>
57 #include <KTemporaryFile>
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() ) )
78 QString
Util::fileNameForMimetype( const QString
&mimeType
, int iconSize
,
79 const QString
&fallbackFileName1
,
80 const QString
&fallbackFileName2
)
83 KMimeType::Ptr mime
= KMimeType::mimeType( mimeType
, KMimeType::ResolveAliases
);
85 fileName
= mime
->iconName();
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
);
104 #include <QDesktopServices>
107 bool Util::handleUrlOnMac( const KUrl
& url
)
110 QDesktopServices::openUrl( url
);
118 QList
<KMime::Content
*> Util::allContents( const KMime::Content
*message
)
120 KMime::Content::List result
;
121 KMime::Content
*child
= MessageCore::NodeHelper::firstChild( message
);
124 result
+= allContents( child
);
126 KMime::Content
*next
= MessageCore::NodeHelper::nextSibling( message
);
129 result
+= allContents( next
);
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
;
149 contents
.erase( delIt
);
157 bool Util::saveContents( QWidget
*parent
, const QList
<KMime::Content
*> &contents
)
160 if ( contents
.count() > 1 ) {
162 dirUrl
= KFileDialog::getExistingDirectoryUrl( KUrl( "kfiledialog:///saveAttachment" ),
164 i18n( "Save Attachments To" ) );
165 if ( !dirUrl
.isValid() ) {
169 // we may not get a slash-terminated url out of KFileDialog
170 dirUrl
.adjustPath( KUrl::AddTrailingSlash
);
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
),
183 i18n( "Save Attachment" ) );
184 if ( url
.isEmpty() ) {
189 QMap
< QString
, int > renameNumbering
;
191 bool globalResult
= true;
192 int unnamedAtmCount
= 0;
193 bool overwriteAll
= false;
194 foreach( KMime::Content
*content
, contents
) {
196 if ( !dirUrl
.isEmpty() ) {
198 QString fileName
= MessageViewer::NodeHelper::fileName( content
);
199 fileName
= MessageCore::StringUtil::cleanFileName( fileName
);
200 if ( fileName
.isEmpty() ) {
202 fileName
= i18nc( "filename for the %1-th unnamed attachment",
203 "attachment.%1", unnamedAtmCount
);
205 curUrl
.setFileName( fileName
);
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
) ) {
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;
229 renameNumbering
[origFile
]++;
231 if ( file
!= origFile
) {
232 if ( !renameNumbering
.contains(file
))
233 renameNumbering
[file
] = 1;
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?",
244 i18n( "File Already Exists" ), KGuiItem(i18n("&Overwrite")) ) == KMessageBox::Cancel
) {
249 int button
= KMessageBox::warningYesNoCancel(
251 i18n( "A file named <br><filename>%1</filename><br>already exists.<br><br>Do you want to overwrite it?",
253 i18n( "File Already Exists" ), KGuiItem(i18n("&Overwrite")),
254 KGuiItem(i18n("Overwrite &All")) );
255 if ( button
== KMessageBox::Cancel
)
257 else if ( button
== KMessageBox::No
)
262 const bool result
= saveContent( parent
, content
, curUrl
);
264 globalResult
= result
;
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?",
288 i18n( "KMail Question" ), KGuiItem(i18n("Keep Encryption")), KGuiItem(i18n("Do Not Keep")) ) ==
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?",
297 i18n( "KMail Question" ), KGuiItem(i18n("Keep Signature")), KGuiItem(i18n("Do Not Keep")) ) !=
299 bSaveWithSig
= false;
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 );
314 dataNode
= ObjectTreeParser::findTypeNot( topContent
, "multipart", "", true, false );
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
330 : dataNode
->decodedContent();
331 data
= KMime::CRLFtoLF( cstr
);
334 const QByteArray data
= content
->decodedContent();
335 kWarning() << "Port the encryption/signature handling when saving a KMime::Content.";
340 if ( url
.isLocalFile() )
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",
350 file
.errorString() ),
351 i18n( "Error saving attachment" ) );
355 const int permissions
= MessageViewer::Util::getWritePermissions();
356 if ( permissions
>= 0 )
357 fchmod( file
.handle(), permissions
);
359 ds
.setDevice( &file
);
362 // tmp file for upload
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",
375 i18n( "Error saving attachment" ) );
379 if ( !url
.isLocalFile() )
381 // QTemporaryFile::fileName() is only defined while the file is open
382 QString tfName
= tf
.fileName();
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",
390 KIO::NetAccess::lastErrorString() ),
391 i18n( "Error saving attachment" ) );
399 mNodeHelper
->removeTempFiles();
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
;