kalarm

kamail.cpp
1/*
2 * kamail.cpp - email functions
3 * Program: kalarm
4 * Copyright © 2002-2005,2008 by David Jarvie <djarvie@kde.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20
21#include "kalarm.h"
22
23#include <stdlib.h>
24#include <unistd.h>
25#include <time.h>
26#include <sys/stat.h>
27#include <sys/time.h>
28#include <pwd.h>
29
30#include <tqfile.h>
31#include <tqregexp.h>
32
33#include <tdestandarddirs.h>
34#include <dcopclient.h>
35#include <dcopref.h>
36#include <tdemessagebox.h>
37#include <tdeprocess.h>
38#include <tdelocale.h>
39#include <tdeaboutdata.h>
40#include <tdefileitem.h>
41#include <tdeio/netaccess.h>
42#include <tdetempfile.h>
43#include <tdeemailsettings.h>
44#include <kdebug.h>
45
46#include <libkpimidentities/identitymanager.h>
47#include <libkpimidentities/identity.h>
48#include <libemailfunctions/email.h>
49#include <libkcal/person.h>
50
51#include <kmime_header_parsing.h>
52
53#include "alarmevent.h"
54#include "functions.h"
55#include "kalarmapp.h"
56#include "mainwindow.h"
57#include "preferences.h"
58#include "kamail.h"
59
60
61namespace HeaderParsing
62{
63bool parseAddress( const char* & scursor, const char * const send,
64 KMime::Types::Address & result, bool isCRLF=false );
65bool parseAddressList( const char* & scursor, const char * const send,
66 TQValueList<KMime::Types::Address> & result, bool isCRLF=false );
67}
68
69namespace
70{
71TQString getHostName();
72}
73
74struct KAMailData
75{
76 KAMailData(const KAEvent& e, const TQString& fr, const TQString& bc, bool allownotify)
77 : event(e), from(fr), bcc(bc), allowNotify(allownotify) { }
78 const KAEvent& event;
79 TQString from;
80 TQString bcc;
81 bool allowNotify;
82};
83
84
85TQString KAMail::i18n_NeedFromEmailAddress()
86{ return i18n("A 'From' email address must be configured in order to execute email alarms."); }
87
88TQString KAMail::i18n_sent_mail()
89{ return i18n("KMail folder name: this should be translated the same as in kmail", "sent-mail"); }
90
91KPIM::IdentityManager* KAMail::mIdentityManager = 0;
92KPIM::IdentityManager* KAMail::identityManager()
93{
94 if (!mIdentityManager)
95 mIdentityManager = new KPIM::IdentityManager(true); // create a read-only kmail identity manager
96 return mIdentityManager;
97}
98
99
100/******************************************************************************
101* Send the email message specified in an event.
102* Reply = true if the message was sent - 'errmsgs' may contain copy error messages.
103* = false if the message was not sent - 'errmsgs' contains the error messages.
104*/
105bool KAMail::send(const KAEvent& event, TQStringList& errmsgs, bool allowNotify)
106{
107 TQString err;
108 TQString from;
109 KPIM::Identity identity;
110 if (!event.emailFromId())
111 from = Preferences::emailAddress();
112 else
113 {
114 identity = mIdentityManager->identityForUoid(event.emailFromId());
115 if (identity.isNull())
116 {
117 kdError(5950) << "KAMail::send(): identity" << event.emailFromId() << "not found" << endl;
118 errmsgs = errors(i18n("Invalid 'From' email address.\nKMail identity '%1' not found.").arg(event.emailFromId()));
119 return false;
120 }
121 from = identity.fullEmailAddr();
122 if (from.isEmpty())
123 {
124 kdError(5950) << "KAMail::send(): identity" << identity.identityName() << "uoid" << identity.uoid() << ": no email address" << endl;
125 errmsgs = errors(i18n("Invalid 'From' email address.\nEmail identity '%1' has no email address").arg(identity.identityName()));
126 return false;
127 }
128 }
129 if (from.isEmpty())
130 {
131 switch (Preferences::emailFrom())
132 {
133 case Preferences::MAIL_FROM_KMAIL:
134 errmsgs = errors(i18n("No 'From' email address is configured (no default KMail identity found)\nPlease set it in KMail or in the KAlarm Preferences dialog."));
135 break;
136 case Preferences::MAIL_FROM_CONTROL_CENTRE:
137 errmsgs = errors(i18n("No 'From' email address is configured.\nPlease set it in the Trinity Control Center or in the KAlarm Preferences dialog."));
138 break;
139 case Preferences::MAIL_FROM_ADDR:
140 default:
141 errmsgs = errors(i18n("No 'From' email address is configured.\nPlease set it in the KAlarm Preferences dialog."));
142 break;
143 }
144 return false;
145 }
146 KAMailData data(event, from,
147 (event.emailBcc() ? Preferences::emailBccAddress() : TQString()),
148 allowNotify);
149 kdDebug(5950) << "KAlarmApp::sendEmail(): To: " << event.emailAddresses(", ")
150 << "\nSubject: " << event.emailSubject() << endl;
151
152 if (Preferences::emailClient() == Preferences::SENDMAIL)
153 {
154 // Use sendmail to send the message
155 TQString textComplete;
156 TQString command = TDEStandardDirs::findExe(TQString::fromLatin1("sendmail"),
157 TQString::fromLatin1("/sbin:/usr/sbin:/usr/lib"));
158 if (!command.isNull())
159 {
160 command += TQString::fromLatin1(" -f ");
161 command += KPIM::getEmailAddress(from);
162 command += TQString::fromLatin1(" -oi -t ");
163 textComplete = initHeaders(data, false);
164 }
165 else
166 {
167 command = TDEStandardDirs::findExe(TQString::fromLatin1("mail"));
168 if (command.isNull())
169 {
170 errmsgs = errors(i18n("%1 not found").arg(TQString::fromLatin1("sendmail"))); // give up
171 return false;
172 }
173
174 command += TQString::fromLatin1(" -s ");
175 command += KShellProcess::quote(event.emailSubject());
176
177 if (!data.bcc.isEmpty())
178 {
179 command += TQString::fromLatin1(" -b ");
180 command += KShellProcess::quote(data.bcc);
181 }
182
183 command += ' ';
184 command += event.emailAddresses(" "); // locally provided, okay
185 }
186
187 // Add the body and attachments to the message.
188 // (Sendmail requires attachments to have already been included in the message.)
189 err = appendBodyAttachments(textComplete, event);
190 if (!err.isNull())
191 {
192 errmsgs = errors(err);
193 return false;
194 }
195
196 // Execute the send command
197 FILE* fd = popen(command.local8Bit(), "w");
198 if (!fd)
199 {
200 kdError(5950) << "KAMail::send(): Unable to open a pipe to " << command << endl;
201 errmsgs = errors();
202 return false;
203 }
204 fwrite(textComplete.local8Bit(), textComplete.length(), 1, fd);
205 pclose(fd);
206
207 if (Preferences::emailCopyToKMail())
208 {
209 // Create a copy of the sent email in KMail's 'Sent-mail' folder
210 err = addToKMailFolder(data, "sent-mail", true);
211 if (!err.isNull())
212 errmsgs = errors(err, false); // not a fatal error - continue
213 }
214
215 if (allowNotify)
216 notifyQueued(event);
217 }
218 else
219 {
220 // Use KMail to send the message
221 err = sendKMail(data);
222 if (!err.isNull())
223 {
224 errmsgs = errors(err);
225 return false;
226 }
227 }
228 return true;
229}
230
231/******************************************************************************
232* Send the email message via KMail.
233* Reply = reason for failure (which may be the empty string)
234* = null string if success.
235*/
236TQString KAMail::sendKMail(const KAMailData& data)
237{
238 TQString err = KAlarm::runKMail(true);
239 if (!err.isNull())
240 return err;
241
242 // KMail is now running. Determine which DCOP call to use.
243 bool useSend = false;
244 TQCString sendFunction = "sendMessage(TQString,TQString,TQString,TQString,TQString,TQString,KURL::List)";
245 QCStringList funcs = tdeApp->dcopClient()->remoteFunctions("kmail", "MailTransportServiceIface");
246 for (QCStringList::Iterator it=funcs.begin(); it != funcs.end() && !useSend; ++it)
247 {
248 TQCString func = DCOPClient::normalizeFunctionSignature(*it);
249 if (func.left(5) == "bool ")
250 {
251 func = func.mid(5);
252 func.replace(TQRegExp(" [0-9A-Za-z_:]+"), "");
253 useSend = (func == sendFunction);
254 }
255 }
256
257 TQByteArray callData;
258 TQDataStream arg(callData, IO_WriteOnly);
259 kdDebug(5950) << "KAMail::sendKMail(): using " << (useSend ? "sendMessage()" : "dcopAddMessage()") << endl;
260 if (useSend)
261 {
262 // This version of KMail has the sendMessage() function,
263 // which transmits the message immediately.
264 arg << data.from;
265 arg << data.event.emailAddresses(", ");
266 arg << ""; // CC:
267 arg << data.bcc;
268 arg << data.event.emailSubject();
269 arg << data.event.message();
270 arg << KURL::List(data.event.emailAttachments());
271 if (!callKMail(callData, "MailTransportServiceIface", sendFunction, "bool"))
272 return i18n("Error calling KMail");
273 }
274 else
275 {
276 // KMail is an older version, so use dcopAddMessage()
277 // to add the message to the outbox for later transmission.
278 err = addToKMailFolder(data, "outbox", false);
279 if (!err.isNull())
280 return err;
281 }
282 if (data.allowNotify)
283 notifyQueued(data.event);
284 return TQString();
285}
286
287/******************************************************************************
288* Add the message to a KMail folder.
289* Reply = reason for failure (which may be the empty string)
290* = null string if success.
291*/
292TQString KAMail::addToKMailFolder(const KAMailData& data, const char* folder, bool checkKmailRunning)
293{
294 TQString err;
295 if (checkKmailRunning)
296 err = KAlarm::runKMail(true);
297 if (err.isNull())
298 {
299 TQString message = initHeaders(data, true);
300 err = appendBodyAttachments(message, data.event);
301 if (!err.isNull())
302 return err;
303
304 // Write to a temporary file for feeding to KMail
305 KTempFile tmpFile;
306 tmpFile.setAutoDelete(true); // delete file when it is destructed
307 TQTextStream* stream = tmpFile.textStream();
308 if (!stream)
309 {
310 kdError(5950) << "KAMail::addToKMailFolder(" << folder << "): Unable to open a temporary mail file" << endl;
311 return TQString("");
312 }
313 *stream << message;
314 tmpFile.close();
315 if (tmpFile.status())
316 {
317 kdError(5950) << "KAMail::addToKMailFolder(" << folder << "): Error " << tmpFile.status() << " writing to temporary mail file" << endl;
318 return TQString("");
319 }
320
321 // Notify KMail of the message in the temporary file
322 TQByteArray callData;
323 TQDataStream arg(callData, IO_WriteOnly);
324 arg << TQString::fromLatin1(folder) << tmpFile.name();
325 if (callKMail(callData, "KMailIface", "dcopAddMessage(TQString,TQString)", "int"))
326 return TQString();
327 err = i18n("Error calling KMail");
328 }
329 kdError(5950) << "KAMail::addToKMailFolder(" << folder << "): " << err << endl;
330 return err;
331}
332
333/******************************************************************************
334* Call KMail via DCOP. The DCOP function must return an 'int'.
335*/
336bool KAMail::callKMail(const TQByteArray& callData, const TQCString& iface, const TQCString& function, const TQCString& funcType)
337{
338 TQCString replyType;
339 TQByteArray replyData;
340 if (!tdeApp->dcopClient()->call("kmail", iface, function, callData, replyType, replyData)
341 || replyType != funcType)
342 {
343 TQCString funcname = function;
344 funcname.replace(TQRegExp("(.+$"), "()");
345 kdError(5950) << "KAMail::callKMail(): kmail " << funcname << " call failed\n";;
346 return false;
347 }
348 TQDataStream replyStream(replyData, IO_ReadOnly);
349 TQCString funcname = function;
350 funcname.replace(TQRegExp("(.+$"), "()");
351 if (replyType == "int")
352 {
353 int result;
354 replyStream >> result;
355 if (result <= 0)
356 {
357 kdError(5950) << "KAMail::callKMail(): kmail " << funcname << " call returned error code = " << result << endl;
358 return false;
359 }
360 }
361 else if (replyType == "bool")
362 {
363 bool result;
364 replyStream >> result;
365 if (!result)
366 {
367 kdError(5950) << "KAMail::callKMail(): kmail " << funcname << " call returned error\n";
368 return false;
369 }
370 }
371 return true;
372}
373
374/******************************************************************************
375* Create the headers part of the email.
376*/
377TQString KAMail::initHeaders(const KAMailData& data, bool dateId)
378{
379 TQString message;
380 if (dateId)
381 {
382 struct timeval tod;
383 gettimeofday(&tod, 0);
384 time_t timenow = tod.tv_sec;
385 char buff[64];
386 strftime(buff, sizeof(buff), "Date: %a, %d %b %Y %H:%M:%S %z", localtime(&timenow));
387 TQString from = data.from;
388 from.replace(TQRegExp("^.*<"), TQString()).replace(TQRegExp(">.*$"), TQString());
389 message = TQString::fromLatin1(buff);
390 message += TQString::fromLatin1("\nMessage-Id: <%1.%2.%3>\n").arg(timenow).arg(tod.tv_usec).arg(from);
391 }
392 message += TQString::fromLatin1("From: ") + data.from;
393 message += TQString::fromLatin1("\nTo: ") + data.event.emailAddresses(", ");
394 if (!data.bcc.isEmpty())
395 message += TQString::fromLatin1("\nBcc: ") + data.bcc;
396 message += TQString::fromLatin1("\nSubject: ") + data.event.emailSubject();
397 message += TQString::fromLatin1("\nX-Mailer: %1/" KALARM_VERSION).arg(tdeApp->aboutData()->programName());
398 return message;
399}
400
401/******************************************************************************
402* Append the body and attachments to the email text.
403* Reply = reason for error
404* = 0 if successful.
405*/
406TQString KAMail::appendBodyAttachments(TQString& message, const KAEvent& event)
407{
408 static const char* textMimeTypes[] = {
409 "application/x-sh", "application/x-csh", "application/x-shellscript",
410 "application/x-nawk", "application/x-gawk", "application/x-awk",
411 "application/x-perl", "application/x-desktop",
412 0
413 };
414 TQStringList attachments = event.emailAttachments();
415 if (!attachments.count())
416 {
417 // There are no attachments, so simply append the message body
418 message += "\n\n";
419 message += event.message();
420 }
421 else
422 {
423 // There are attachments, so the message must be in MIME format
424 // Create a boundary string
425 time_t timenow;
426 time(&timenow);
427 TQCString boundary;
428 boundary.sprintf("------------_%lu_-%lx=", 2*timenow, timenow);
429 message += TQString::fromLatin1("\nMIME-Version: 1.0");
430 message += TQString::fromLatin1("\nContent-Type: multipart/mixed;\n boundary=\"%1\"\n").arg(boundary.data());
431
432 if (!event.message().isEmpty())
433 {
434 // There is a message body
435 message += TQString::fromLatin1("\n--%1\nContent-Type: text/plain\nContent-Transfer-Encoding: 8bit\n\n").arg(boundary.data());
436 message += event.message();
437 }
438
439 // Append each attachment in turn
440 TQString attachError = i18n("Error attaching file:\n%1");
441 for (TQStringList::Iterator at = attachments.begin(); at != attachments.end(); ++at)
442 {
443 TQString attachment = (*at).local8Bit();
444 KURL url(attachment);
445 url.cleanPath();
446 TDEIO::UDSEntry uds;
447 if (!TDEIO::NetAccess::stat(url, uds, MainWindow::mainMainWindow())) {
448 kdError(5950) << "KAMail::appendBodyAttachments(): not found: " << attachment << endl;
449 return i18n("Attachment not found:\n%1").arg(attachment);
450 }
451 KFileItem fi(uds, url);
452 if (fi.isDir() || !fi.isReadable()) {
453 kdError(5950) << "KAMail::appendBodyAttachments(): not file/not readable: " << attachment << endl;
454 return attachError.arg(attachment);
455 }
456
457 // Check if the attachment is a text file
458 TQString mimeType = fi.mimetype();
459 bool text = mimeType.startsWith("text/");
460 if (!text)
461 {
462 for (int i = 0; !text && textMimeTypes[i]; ++i)
463 text = (mimeType == textMimeTypes[i]);
464 }
465
466 message += TQString::fromLatin1("\n--%1").arg(boundary.data());
467 message += TQString::fromLatin1("\nContent-Type: %2; name=\"%3\"").arg(mimeType).arg(fi.text());
468 message += TQString::fromLatin1("\nContent-Transfer-Encoding: %1").arg(TQString::fromLatin1(text ? "8bit" : "BASE64"));
469 message += TQString::fromLatin1("\nContent-Disposition: attachment; filename=\"%4\"\n\n").arg(fi.text());
470
471 // Read the file contents
472 TQString tmpFile;
473 if (!TDEIO::NetAccess::download(url, tmpFile, MainWindow::mainMainWindow())) {
474 kdError(5950) << "KAMail::appendBodyAttachments(): load failure: " << attachment << endl;
475 return attachError.arg(attachment);
476 }
477 TQFile file(tmpFile);
478 if (!file.open(IO_ReadOnly) ) {
479 kdDebug(5950) << "KAMail::appendBodyAttachments() tmp load error: " << attachment << endl;
480 return attachError.arg(attachment);
481 }
482 TQIODevice::Offset size = file.size();
483 char* contents = new char [size + 1];
484 TQ_LONG bytes = file.readBlock(contents, size);
485 file.close();
486 contents[size] = 0;
487 bool atterror = false;
488 if (bytes == -1 || (TQIODevice::Offset)bytes < size) {
489 kdDebug(5950) << "KAMail::appendBodyAttachments() read error: " << attachment << endl;
490 atterror = true;
491 }
492 else if (text)
493 {
494 // Text attachment doesn't need conversion
495 message += contents;
496 }
497 else
498 {
499 // Convert the attachment to BASE64 encoding
500 TQIODevice::Offset base64Size;
501 char* base64 = base64Encode(contents, size, base64Size);
502 if (base64Size == (TQIODevice::Offset)-1) {
503 kdDebug(5950) << "KAMail::appendBodyAttachments() base64 buffer overflow: " << attachment << endl;
504 atterror = true;
505 }
506 else
507 message += TQString::fromLatin1(base64, base64Size);
508 delete[] base64;
509 }
510 delete[] contents;
511 if (atterror)
512 return attachError.arg(attachment);
513 }
514 message += TQString::fromLatin1("\n--%1--\n.\n").arg(boundary.data());
515 }
516 return TQString();
517}
518
519/******************************************************************************
520* If any of the destination email addresses are non-local, display a
521* notification message saying that an email has been queued for sending.
522*/
523void KAMail::notifyQueued(const KAEvent& event)
524{
525 KMime::Types::Address addr;
526 TQString localhost = TQString::fromLatin1("localhost");
527 TQString hostname = getHostName();
528 const EmailAddressList& addresses = event.emailAddresses();
529 for (TQValueList<KCal::Person>::ConstIterator it = addresses.begin(); it != addresses.end(); ++it)
530 {
531 TQCString email = (*it).email().local8Bit();
532 const char* em = email;
533 if (!email.isEmpty()
534 && HeaderParsing::parseAddress(em, em + email.length(), addr))
535 {
536 TQString domain = addr.mailboxList.first().addrSpec.domain;
537 if (!domain.isEmpty() && domain != localhost && domain != hostname)
538 {
539 TQString text = (Preferences::emailClient() == Preferences::KMAIL)
540 ? i18n("An email has been queued to be sent by KMail")
541 : i18n("An email has been queued to be sent");
542 KMessageBox::information(0, text, TQString(), Preferences::EMAIL_QUEUED_NOTIFY);
543 return;
544 }
545 }
546 }
547}
548
549/******************************************************************************
550* Return whether any KMail identities exist.
551*/
552bool KAMail::identitiesExist()
553{
554 identityManager(); // create identity manager if not already done
555 return mIdentityManager->begin() != mIdentityManager->end();
556}
557
558/******************************************************************************
559* Fetch the uoid of an email identity name or uoid string.
560*/
561uint KAMail::identityUoid(const TQString& identityUoidOrName)
562{
563 bool ok;
564 uint id = identityUoidOrName.toUInt(&ok);
565 if (!ok || identityManager()->identityForUoid(id).isNull())
566 {
567 identityManager(); // fetch it if not already done
568 for (KPIM::IdentityManager::ConstIterator it = mIdentityManager->begin();
569 it != mIdentityManager->end(); ++it)
570 {
571 if ((*it).identityName() == identityUoidOrName)
572 {
573 id = (*it).uoid();
574 break;
575 }
576 }
577 }
578 return id;
579}
580
581/******************************************************************************
582* Fetch the user's email address configured in the TDE Control Centre.
583*/
584TQString KAMail::controlCentreAddress()
585{
586 KEMailSettings e;
587 return e.getSetting(KEMailSettings::EmailAddress);
588}
589
590/******************************************************************************
591* Parse a list of email addresses, optionally containing display names,
592* entered by the user.
593* Reply = the invalid item if error, else empty string.
594*/
595TQString KAMail::convertAddresses(const TQString& items, EmailAddressList& list)
596{
597 list.clear();
598 TQCString addrs = items.local8Bit();
599 const char* ad = static_cast<const char*>(addrs);
600
601 // parse an address-list
602 TQValueList<KMime::Types::Address> maybeAddressList;
603 if (!HeaderParsing::parseAddressList(ad, ad + addrs.length(), maybeAddressList))
604 return TQString::fromLocal8Bit(ad); // return the address in error
605
606 // extract the mailboxes and complain if there are groups
607 for (TQValueList<KMime::Types::Address>::ConstIterator it = maybeAddressList.begin();
608 it != maybeAddressList.end(); ++it)
609 {
610 TQString bad = convertAddress(*it, list);
611 if (!bad.isEmpty())
612 return bad;
613 }
614 return TQString();
615}
616
617#if 0
618/******************************************************************************
619* Parse an email address, optionally containing display name, entered by the
620* user, and append it to the specified list.
621* Reply = the invalid item if error, else empty string.
622*/
623TQString KAMail::convertAddress(const TQString& item, EmailAddressList& list)
624{
625 TQCString addr = item.local8Bit();
626 const char* ad = static_cast<const char*>(addr);
627 KMime::Types::Address maybeAddress;
628 if (!HeaderParsing::parseAddress(ad, ad + addr.length(), maybeAddress))
629 return item; // error
630 return convertAddress(maybeAddress, list);
631}
632#endif
633
634/******************************************************************************
635* Convert a single KMime::Types address to a KCal::Person instance and append
636* it to the specified list.
637*/
638TQString KAMail::convertAddress(KMime::Types::Address addr, EmailAddressList& list)
639{
640 if (!addr.displayName.isEmpty())
641 {
642 kdDebug(5950) << "mailbox groups not allowed! Name: \"" << addr.displayName << "\"" << endl;
643 return addr.displayName;
644 }
645 const TQValueList<KMime::Types::Mailbox>& mblist = addr.mailboxList;
646 for (TQValueList<KMime::Types::Mailbox>::ConstIterator mb = mblist.begin();
647 mb != mblist.end(); ++mb)
648 {
649 TQString addrPart = (*mb).addrSpec.localPart;
650 if (!(*mb).addrSpec.domain.isEmpty())
651 {
652 addrPart += TQChar('@');
653 addrPart += (*mb).addrSpec.domain;
654 }
655 list += KCal::Person((*mb).displayName, addrPart);
656 }
657 return TQString();
658}
659
660/*
661TQString KAMail::convertAddresses(const TQString& items, TQStringList& list)
662{
663 EmailAddressList addrs;
664 TQString item = convertAddresses(items, addrs);
665 if (!item.isEmpty())
666 return item;
667 for (EmailAddressList::Iterator ad = addrs.begin(); ad != addrs.end(); ++ad)
668 {
669 item = (*ad).fullName().local8Bit();
670 switch (checkAddress(item))
671 {
672 case 1: // OK
673 list += item;
674 break;
675 case 0: // null address
676 break;
677 case -1: // invalid address
678 return item;
679 }
680 }
681 return TQString();
682}*/
683
684/******************************************************************************
685* Check the validity of an email address.
686* Because internal email addresses don't have to abide by the usual internet
687* email address rules, only some basic checks are made.
688* Reply = 1 if alright, 0 if empty, -1 if error.
689*/
690int KAMail::checkAddress(TQString& address)
691{
692 address = address.stripWhiteSpace();
693 // Check that there are no list separator characters present
694 if (address.find(',') >= 0 || address.find(';') >= 0)
695 return -1;
696 int n = address.length();
697 if (!n)
698 return 0;
699 int start = 0;
700 int end = n - 1;
701 if (address[end] == '>')
702 {
703 // The email address is in <...>
704 if ((start = address.find('<')) < 0)
705 return -1;
706 ++start;
707 --end;
708 }
709 int i = address.find('@', start);
710 if (i >= 0)
711 {
712 if (i == start || i == end) // check @ isn't the first or last character
713// || address.find('@', i + 1) >= 0) // check for multiple @ characters
714 return -1;
715 }
716/* else
717 {
718 // Allow the @ character to be missing if it's a local user
719 if (!getpwnam(address.mid(start, end - start + 1).local8Bit()))
720 return false;
721 }
722 for (int i = start; i <= end; ++i)
723 {
724 char ch = address[i].latin1();
725 if (ch == '.' || ch == '@' || ch == '-' || ch == '_'
726 || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
727 || (ch >= '0' && ch <= '9'))
728 continue;
729 return false;
730 }*/
731 return 1;
732}
733
734/******************************************************************************
735* Convert a comma or semicolon delimited list of attachments into a
736* TQStringList. The items are checked for validity.
737* Reply = the invalid item if error, else empty string.
738*/
739TQString KAMail::convertAttachments(const TQString& items, TQStringList& list)
740{
741 KURL url;
742 list.clear();
743 int length = items.length();
744 for (int next = 0; next < length; )
745 {
746 // Find the first delimiter character (, or ;)
747 int i = items.find(',', next);
748 if (i < 0)
749 i = items.length();
750 int sc = items.find(';', next);
751 if (sc < 0)
752 sc = items.length();
753 if (sc < i)
754 i = sc;
755 TQString item = items.mid(next, i - next).stripWhiteSpace();
756 switch (checkAttachment(item))
757 {
758 case 1: list += item; break;
759 case 0: break; // empty attachment name
760 case -1:
761 default: return item; // error
762 }
763 next = i + 1;
764 }
765 return TQString();
766}
767
768#if 0
769/******************************************************************************
770* Convert a comma or semicolon delimited list of attachments into a
771* KURL::List. The items are checked for validity.
772* Reply = the invalid item if error, else empty string.
773*/
774TQString KAMail::convertAttachments(const TQString& items, KURL::List& list)
775{
776 KURL url;
777 list.clear();
778 TQCString addrs = items.local8Bit();
779 int length = items.length();
780 for (int next = 0; next < length; )
781 {
782 // Find the first delimiter character (, or ;)
783 int i = items.find(',', next);
784 if (i < 0)
785 i = items.length();
786 int sc = items.find(';', next);
787 if (sc < 0)
788 sc = items.length();
789 if (sc < i)
790 i = sc;
791 TQString item = items.mid(next, i - next);
792 switch (checkAttachment(item, &url))
793 {
794 case 1: list += url; break;
795 case 0: break; // empty attachment name
796 case -1:
797 default: return item; // error
798 }
799 next = i + 1;
800 }
801 return TQString();
802}
803#endif
804
805/******************************************************************************
806* Check for the existence of the attachment file.
807* If non-null, '*url' receives the KURL of the attachment.
808* Reply = 1 if attachment exists
809* = 0 if null name
810* = -1 if doesn't exist.
811*/
812int KAMail::checkAttachment(TQString& attachment, KURL* url)
813{
814 attachment = attachment.stripWhiteSpace();
815 if (attachment.isEmpty())
816 {
817 if (url)
818 *url = KURL();
819 return 0;
820 }
821 // Check that the file exists
822 KURL u = KURL::fromPathOrURL(attachment);
823 u.cleanPath();
824 if (url)
825 *url = u;
826 return checkAttachment(u) ? 1 : -1;
827}
828
829/******************************************************************************
830* Check for the existence of the attachment file.
831*/
832bool KAMail::checkAttachment(const KURL& url)
833{
834 TDEIO::UDSEntry uds;
835 if (!TDEIO::NetAccess::stat(url, uds, MainWindow::mainMainWindow()))
836 return false; // doesn't exist
837 KFileItem fi(uds, url);
838 if (fi.isDir() || !fi.isReadable())
839 return false;
840 return true;
841}
842
843
844/******************************************************************************
845* Convert a block of memory to Base64 encoding.
846* 'outSize' is set to the number of bytes used in the returned block, or to
847* -1 if overflow.
848* Reply = BASE64 buffer, which the caller must delete[] afterwards.
849*/
850char* KAMail::base64Encode(const char* in, TQIODevice::Offset size, TQIODevice::Offset& outSize)
851{
852 const int MAX_LINELEN = 72;
853 static unsigned char dtable[65] =
854 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
855 "abcdefghijklmnopqrstuvwxyz"
856 "0123456789+/";
857
858 char* out = new char [2*size + 5];
859 outSize = (TQIODevice::Offset)-1;
860 TQIODevice::Offset outIndex = 0;
861 int lineLength = 0;
862 for (TQIODevice::Offset inIndex = 0; inIndex < size; )
863 {
864 unsigned char igroup[3];
865 int n;
866 for (n = 0; n < 3; ++n)
867 {
868 if (inIndex < size)
869 igroup[n] = (unsigned char)in[inIndex++];
870 else
871 {
872 igroup[n] = igroup[2] = 0;
873 break;
874 }
875 }
876
877 if (n > 0)
878 {
879 unsigned char ogroup[4];
880 ogroup[0] = dtable[igroup[0] >> 2];
881 ogroup[1] = dtable[((igroup[0] & 3) << 4) | (igroup[1] >> 4)];
882 ogroup[2] = dtable[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)];
883 ogroup[3] = dtable[igroup[2] & 0x3F];
884
885 if (n < 3)
886 {
887 ogroup[3] = '=';
888 if (n < 2)
889 ogroup[2] = '=';
890 }
891 if (outIndex >= size*2)
892 {
893 delete[] out;
894 return 0;
895 }
896 for (int i = 0; i < 4; ++i)
897 {
898 if (lineLength >= MAX_LINELEN)
899 {
900 out[outIndex++] = '\r';
901 out[outIndex++] = '\n';
902 lineLength = 0;
903 }
904 out[outIndex++] = ogroup[i];
905 ++lineLength;
906 }
907 }
908 }
909
910 if (outIndex + 2 < size*2)
911 {
912 out[outIndex++] = '\r';
913 out[outIndex++] = '\n';
914 }
915 outSize = outIndex;
916 return out;
917}
918
919/******************************************************************************
920* Set the appropriate error messages for a given error string.
921*/
922TQStringList KAMail::errors(const TQString& err, bool sendfail)
923{
924 TQString error1 = sendfail ? i18n("Failed to send email")
925 : i18n("Error copying sent email to KMail %1 folder").arg(i18n_sent_mail());
926 if (err.isEmpty())
927 return TQStringList(error1);
928 TQStringList errs(TQString::fromLatin1("%1:").arg(error1));
929 errs += err;
930 return errs;
931}
932
933/******************************************************************************
934* Get the body of an email, given its serial number.
935*/
936TQString KAMail::getMailBody(TQ_UINT32 serialNumber)
937{
938 // Get the body of the email from KMail
939 TQCString replyType;
940 TQByteArray replyData;
941 TQByteArray data;
942 TQDataStream arg(data, IO_WriteOnly);
943 arg << serialNumber;
944 arg << (int)0;
945 TQString body;
946 if (tdeApp->dcopClient()->call("kmail", "KMailIface", "getDecodedBodyPart(TQ_UINT32,int)", data, replyType, replyData)
947 && replyType == "TQString")
948 {
949 TQDataStream reply_stream(replyData, IO_ReadOnly);
950 reply_stream >> body;
951 }
952 else
953 kdDebug(5950) << "KAMail::getMailBody(): kmail getDecodedBodyPart() call failed\n";
954 return body;
955}
956
957namespace
958{
959/******************************************************************************
960* Get the local system's host name.
961*/
962TQString getHostName()
963{
964 char hname[256];
965 if (gethostname(hname, sizeof(hname)))
966 return TQString();
967 return TQString::fromLocal8Bit(hname);
968}
969}
970
971
972/*=============================================================================
973= HeaderParsing : modified and additional functions.
974= The following functions are modified from, or additional to, those in
975= libtdenetwork kmime_header_parsing.cpp.
976=============================================================================*/
977
978namespace HeaderParsing
979{
980
981using namespace KMime;
982using namespace KMime::Types;
983using namespace KMime::HeaderParsing;
984
985/******************************************************************************
986* New function.
987* Allow a local user name to be specified as an email address.
988*/
989bool parseUserName( const char* & scursor, const char * const send,
990 TQString & result, bool isCRLF ) {
991
992 TQString maybeLocalPart;
993 TQString tmp;
994
995 if ( scursor != send ) {
996 // first, eat any whitespace
997 eatCFWS( scursor, send, isCRLF );
998
999 char ch = *scursor++;
1000 switch ( ch ) {
1001 case '.': // dot
1002 case '@':
1003 case '"': // quoted-string
1004 return false;
1005
1006 default: // atom
1007 scursor--; // re-set scursor to point to ch again
1008 tmp = TQString();
1009 if ( parseAtom( scursor, send, result, false /* no 8bit */ ) ) {
1010 if (getpwnam(result.local8Bit()))
1011 return true;
1012 }
1013 return false; // parseAtom can only fail if the first char is non-atext.
1014 }
1015 }
1016 return false;
1017}
1018
1019/******************************************************************************
1020* Modified function.
1021* Allow a local user name to be specified as an email address, and reinstate
1022* the original scursor on error return.
1023*/
1024bool parseAddress( const char* & scursor, const char * const send,
1025 Address & result, bool isCRLF ) {
1026 // address := mailbox / group
1027
1028 eatCFWS( scursor, send, isCRLF );
1029 if ( scursor == send ) return false;
1030
1031 // first try if it's a single mailbox:
1032 Mailbox maybeMailbox;
1033 const char * oldscursor = scursor;
1034 if ( parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) {
1035 // yes, it is:
1036 result.displayName = TQString();
1037 result.mailboxList.append( maybeMailbox );
1038 return true;
1039 }
1040 scursor = oldscursor;
1041
1042 // KAlarm: Allow a local user name to be specified
1043 // no, it's not a single mailbox. Try if it's a local user name:
1044 TQString maybeUserName;
1045 if ( parseUserName( scursor, send, maybeUserName, isCRLF ) ) {
1046 // yes, it is:
1047 maybeMailbox.displayName = TQString();
1048 maybeMailbox.addrSpec.localPart = maybeUserName;
1049 maybeMailbox.addrSpec.domain = TQString();
1050 result.displayName = TQString();
1051 result.mailboxList.append( maybeMailbox );
1052 return true;
1053 }
1054 scursor = oldscursor;
1055
1056 Address maybeAddress;
1057
1058 // no, it's not a single mailbox. Try if it's a group:
1059 if ( !parseGroup( scursor, send, maybeAddress, isCRLF ) )
1060 {
1061 scursor = oldscursor; // KAlarm: reinstate original scursor on error return
1062 return false;
1063 }
1064
1065 result = maybeAddress;
1066 return true;
1067}
1068
1069/******************************************************************************
1070* Modified function.
1071* Allow either ',' or ';' to be used as an email address separator.
1072*/
1073bool parseAddressList( const char* & scursor, const char * const send,
1074 TQValueList<Address> & result, bool isCRLF ) {
1075 while ( scursor != send ) {
1076 eatCFWS( scursor, send, isCRLF );
1077 // end of header: this is OK.
1078 if ( scursor == send ) return true;
1079 // empty entry: ignore:
1080 if ( *scursor == ',' || *scursor == ';' ) { scursor++; continue; } // KAlarm: allow ';' as address separator
1081
1082 // parse one entry
1083 Address maybeAddress;
1084 if ( !parseAddress( scursor, send, maybeAddress, isCRLF ) ) return false;
1085 result.append( maybeAddress );
1086
1087 eatCFWS( scursor, send, isCRLF );
1088 // end of header: this is OK.
1089 if ( scursor == send ) return true;
1090 // comma separating entries: eat it.
1091 if ( *scursor == ',' || *scursor == ';' ) scursor++; // KAlarm: allow ';' as address separator
1092 }
1093 return true;
1094}
1095
1096} // namespace HeaderParsing
represents calendar alarms and events
KAEvent corresponds to a KCal::Event instance.
Definition: alarmevent.h:232
miscellaneous functions
the KAlarm application object
main application window