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 <kstandarddirs.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 
61 namespace HeaderParsing
62 {
63 bool parseAddress( const char* & scursor, const char * const send,
64  KMime::Types::Address & result, bool isCRLF=false );
65 bool parseAddressList( const char* & scursor, const char * const send,
66  TQValueList<KMime::Types::Address> & result, bool isCRLF=false );
67 }
68 
69 namespace
70 {
71 TQString getHostName();
72 }
73 
74 struct 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 
85 TQString KAMail::i18n_NeedFromEmailAddress()
86 { return i18n("A 'From' email address must be configured in order to execute email alarms."); }
87 
88 TQString KAMail::i18n_sent_mail()
89 { return i18n("KMail folder name: this should be translated the same as in kmail", "sent-mail"); }
90 
91 KPIM::IdentityManager* KAMail::mIdentityManager = 0;
92 KPIM::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 */
105 bool 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 */
236 TQString 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 = kapp->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 */
292 TQString 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 */
336 bool KAMail::callKMail(const TQByteArray& callData, const TQCString& iface, const TQCString& function, const TQCString& funcType)
337 {
338  TQCString replyType;
339  TQByteArray replyData;
340  if (!kapp->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 */
377 TQString 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(kapp->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 */
406 TQString 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 */
523 void 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 */
552 bool 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 */
561 uint 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 */
584 TQString 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 */
595 TQString 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 */
623 TQString 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 */
638 TQString 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 /*
661 TQString 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 */
690 int 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 */
739 TQString 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 */
774 TQString 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 */
812 int 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 */
832 bool 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 */
850 char* 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 */
922 TQStringList 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 */
936 TQString 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 (kapp->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 
957 namespace
958 {
959 /******************************************************************************
960 * Get the local system's host name.
961 */
962 TQString 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 
978 namespace HeaderParsing
979 {
980 
981 using namespace KMime;
982 using namespace KMime::Types;
983 using namespace KMime::HeaderParsing;
984 
985 /******************************************************************************
986 * New function.
987 * Allow a local user name to be specified as an email address.
988 */
989 bool 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 */
1024 bool 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 */
1073 bool 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