kalarm

kamail.cpp

00001 /*
00002  *  kamail.cpp  -  email functions
00003  *  Program:  kalarm
00004  *  Copyright © 2002-2005,2008 by David Jarvie <djarvie@kde.org>
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; either version 2 of the License, or
00009  *  (at your option) any later version.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License along
00017  *  with this program; if not, write to the Free Software Foundation, Inc.,
00018  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00019  */
00020 
00021 #include "kalarm.h"
00022 
00023 #include <stdlib.h>
00024 #include <unistd.h>
00025 #include <time.h>
00026 #include <sys/stat.h>
00027 #include <sys/time.h>
00028 #include <pwd.h>
00029 
00030 #include <tqfile.h>
00031 #include <tqregexp.h>
00032 
00033 #include <kstandarddirs.h>
00034 #include <dcopclient.h>
00035 #include <dcopref.h>
00036 #include <tdemessagebox.h>
00037 #include <kprocess.h>
00038 #include <tdelocale.h>
00039 #include <tdeaboutdata.h>
00040 #include <tdefileitem.h>
00041 #include <tdeio/netaccess.h>
00042 #include <tdetempfile.h>
00043 #include <tdeemailsettings.h>
00044 #include <kdebug.h>
00045 
00046 #include <libkpimidentities/identitymanager.h>
00047 #include <libkpimidentities/identity.h>
00048 #include <libemailfunctions/email.h>
00049 #include <libkcal/person.h>
00050 
00051 #include <kmime_header_parsing.h>
00052 
00053 #include "alarmevent.h"
00054 #include "functions.h"
00055 #include "kalarmapp.h"
00056 #include "mainwindow.h"
00057 #include "preferences.h"
00058 #include "kamail.h"
00059 
00060 
00061 namespace HeaderParsing
00062 {
00063 bool parseAddress( const char* & scursor, const char * const send,
00064                    KMime::Types::Address & result, bool isCRLF=false );
00065 bool parseAddressList( const char* & scursor, const char * const send,
00066                        TQValueList<KMime::Types::Address> & result, bool isCRLF=false );
00067 }
00068 
00069 namespace
00070 {
00071 TQString getHostName();
00072 }
00073 
00074 struct KAMailData
00075 {
00076     KAMailData(const KAEvent& e, const TQString& fr, const TQString& bc, bool allownotify)
00077                      : event(e), from(fr), bcc(bc), allowNotify(allownotify) { }
00078     const KAEvent& event;
00079     TQString        from;
00080     TQString        bcc;
00081     bool           allowNotify;
00082 };
00083 
00084 
00085 TQString KAMail::i18n_NeedFromEmailAddress()
00086 { return i18n("A 'From' email address must be configured in order to execute email alarms."); }
00087 
00088 TQString KAMail::i18n_sent_mail()
00089 { return i18n("KMail folder name: this should be translated the same as in kmail", "sent-mail"); }
00090 
00091 KPIM::IdentityManager* KAMail::mIdentityManager = 0;
00092 KPIM::IdentityManager* KAMail::identityManager()
00093 {
00094     if (!mIdentityManager)
00095         mIdentityManager = new KPIM::IdentityManager(true);   // create a read-only kmail identity manager
00096     return mIdentityManager;
00097 }
00098 
00099 
00100 /******************************************************************************
00101 * Send the email message specified in an event.
00102 * Reply = true if the message was sent - 'errmsgs' may contain copy error messages.
00103 *       = false if the message was not sent - 'errmsgs' contains the error messages.
00104 */
00105 bool KAMail::send(const KAEvent& event, TQStringList& errmsgs, bool allowNotify)
00106 {
00107     TQString err;
00108     TQString from;
00109     KPIM::Identity identity;
00110     if (!event.emailFromId())
00111         from = Preferences::emailAddress();
00112     else
00113     {
00114         identity = mIdentityManager->identityForUoid(event.emailFromId());
00115         if (identity.isNull())
00116         {
00117             kdError(5950) << "KAMail::send(): identity" << event.emailFromId() << "not found" << endl;
00118             errmsgs = errors(i18n("Invalid 'From' email address.\nKMail identity '%1' not found.").arg(event.emailFromId()));
00119             return false;
00120         }
00121         from = identity.fullEmailAddr();
00122         if (from.isEmpty())
00123         {
00124             kdError(5950) << "KAMail::send(): identity" << identity.identityName() << "uoid" << identity.uoid() << ": no email address" << endl;
00125             errmsgs = errors(i18n("Invalid 'From' email address.\nEmail identity '%1' has no email address").arg(identity.identityName()));
00126             return false;
00127         }
00128     }
00129     if (from.isEmpty())
00130     {
00131         switch (Preferences::emailFrom())
00132         {
00133             case Preferences::MAIL_FROM_KMAIL:
00134                 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."));
00135                 break;
00136             case Preferences::MAIL_FROM_CONTROL_CENTRE:
00137                 errmsgs = errors(i18n("No 'From' email address is configured.\nPlease set it in the Trinity Control Center or in the KAlarm Preferences dialog."));
00138                 break;
00139             case Preferences::MAIL_FROM_ADDR:
00140             default:
00141                 errmsgs = errors(i18n("No 'From' email address is configured.\nPlease set it in the KAlarm Preferences dialog."));
00142                 break;
00143         }
00144         return false;
00145     }
00146     KAMailData data(event, from,
00147                     (event.emailBcc() ? Preferences::emailBccAddress() : TQString()),
00148                     allowNotify);
00149     kdDebug(5950) << "KAlarmApp::sendEmail(): To: " << event.emailAddresses(", ")
00150                   << "\nSubject: " << event.emailSubject() << endl;
00151 
00152     if (Preferences::emailClient() == Preferences::SENDMAIL)
00153     {
00154         // Use sendmail to send the message
00155         TQString textComplete;
00156         TQString command = TDEStandardDirs::findExe(TQString::fromLatin1("sendmail"),
00157                                                  TQString::fromLatin1("/sbin:/usr/sbin:/usr/lib"));
00158         if (!command.isNull())
00159         {
00160             command += TQString::fromLatin1(" -f ");
00161             command += KPIM::getEmailAddress(from);
00162             command += TQString::fromLatin1(" -oi -t ");
00163             textComplete = initHeaders(data, false);
00164         }
00165         else
00166         {
00167             command = TDEStandardDirs::findExe(TQString::fromLatin1("mail"));
00168             if (command.isNull())
00169             {
00170                 errmsgs = errors(i18n("%1 not found").arg(TQString::fromLatin1("sendmail"))); // give up
00171                 return false;
00172             }
00173 
00174             command += TQString::fromLatin1(" -s ");
00175             command += KShellProcess::quote(event.emailSubject());
00176 
00177             if (!data.bcc.isEmpty())
00178             {
00179                 command += TQString::fromLatin1(" -b ");
00180                 command += KShellProcess::quote(data.bcc);
00181             }
00182 
00183             command += ' ';
00184             command += event.emailAddresses(" "); // locally provided, okay
00185         }
00186 
00187         // Add the body and attachments to the message.
00188         // (Sendmail requires attachments to have already been included in the message.)
00189         err = appendBodyAttachments(textComplete, event);
00190         if (!err.isNull())
00191         {
00192             errmsgs = errors(err);
00193             return false;
00194         }
00195 
00196         // Execute the send command
00197         FILE* fd = popen(command.local8Bit(), "w");
00198         if (!fd)
00199         {
00200             kdError(5950) << "KAMail::send(): Unable to open a pipe to " << command << endl;
00201             errmsgs = errors();
00202             return false;
00203         }
00204         fwrite(textComplete.local8Bit(), textComplete.length(), 1, fd);
00205         pclose(fd);
00206 
00207         if (Preferences::emailCopyToKMail())
00208         {
00209             // Create a copy of the sent email in KMail's 'Sent-mail' folder
00210             err = addToKMailFolder(data, "sent-mail", true);
00211             if (!err.isNull())
00212                 errmsgs = errors(err, false);    // not a fatal error - continue
00213         }
00214 
00215         if (allowNotify)
00216             notifyQueued(event);
00217     }
00218     else
00219     {
00220         // Use KMail to send the message
00221         err = sendKMail(data);
00222         if (!err.isNull())
00223         {
00224             errmsgs = errors(err);
00225             return false;
00226         }
00227     }
00228     return true;
00229 }
00230 
00231 /******************************************************************************
00232 * Send the email message via KMail.
00233 * Reply = reason for failure (which may be the empty string)
00234 *       = null string if success.
00235 */
00236 TQString KAMail::sendKMail(const KAMailData& data)
00237 {
00238     TQString err = KAlarm::runKMail(true);
00239     if (!err.isNull())
00240         return err;
00241 
00242     // KMail is now running. Determine which DCOP call to use.
00243     bool useSend = false;
00244     TQCString sendFunction = "sendMessage(TQString,TQString,TQString,TQString,TQString,TQString,KURL::List)";
00245     QCStringList funcs = kapp->dcopClient()->remoteFunctions("kmail", "MailTransportServiceIface");
00246     for (QCStringList::Iterator it=funcs.begin();  it != funcs.end() && !useSend;  ++it)
00247     {
00248         TQCString func = DCOPClient::normalizeFunctionSignature(*it);
00249         if (func.left(5) == "bool ")
00250         {
00251             func = func.mid(5);
00252             func.replace(TQRegExp(" [0-9A-Za-z_:]+"), "");
00253             useSend = (func == sendFunction);
00254         }
00255     }
00256 
00257     TQByteArray  callData;
00258     TQDataStream arg(callData, IO_WriteOnly);
00259     kdDebug(5950) << "KAMail::sendKMail(): using " << (useSend ? "sendMessage()" : "dcopAddMessage()") << endl;
00260     if (useSend)
00261     {
00262         // This version of KMail has the sendMessage() function,
00263         // which transmits the message immediately.
00264         arg << data.from;
00265         arg << data.event.emailAddresses(", ");
00266         arg << "";    // CC:
00267         arg << data.bcc;
00268         arg << data.event.emailSubject();
00269         arg << data.event.message();
00270         arg << KURL::List(data.event.emailAttachments());
00271         if (!callKMail(callData, "MailTransportServiceIface", sendFunction, "bool"))
00272             return i18n("Error calling KMail");
00273     }
00274     else
00275     {
00276         // KMail is an older version, so use dcopAddMessage()
00277         // to add the message to the outbox for later transmission.
00278         err = addToKMailFolder(data, "outbox", false);
00279         if (!err.isNull())
00280             return err;
00281     }
00282     if (data.allowNotify)
00283         notifyQueued(data.event);
00284     return TQString();
00285 }
00286 
00287 /******************************************************************************
00288 * Add the message to a KMail folder.
00289 * Reply = reason for failure (which may be the empty string)
00290 *       = null string if success.
00291 */
00292 TQString KAMail::addToKMailFolder(const KAMailData& data, const char* folder, bool checkKmailRunning)
00293 {
00294     TQString err;
00295     if (checkKmailRunning)
00296         err = KAlarm::runKMail(true);
00297     if (err.isNull())
00298     {
00299         TQString message = initHeaders(data, true);
00300         err = appendBodyAttachments(message, data.event);
00301         if (!err.isNull())
00302             return err;
00303 
00304         // Write to a temporary file for feeding to KMail
00305         KTempFile tmpFile;
00306         tmpFile.setAutoDelete(true);     // delete file when it is destructed
00307         TQTextStream* stream = tmpFile.textStream();
00308         if (!stream)
00309         {
00310             kdError(5950) << "KAMail::addToKMailFolder(" << folder << "): Unable to open a temporary mail file" << endl;
00311             return TQString("");
00312         }
00313         *stream << message;
00314         tmpFile.close();
00315         if (tmpFile.status())
00316         {
00317             kdError(5950) << "KAMail::addToKMailFolder(" << folder << "): Error " << tmpFile.status() << " writing to temporary mail file" << endl;
00318             return TQString("");
00319         }
00320 
00321         // Notify KMail of the message in the temporary file
00322         TQByteArray  callData;
00323         TQDataStream arg(callData, IO_WriteOnly);
00324         arg << TQString::fromLatin1(folder) << tmpFile.name();
00325         if (callKMail(callData, "KMailIface", "dcopAddMessage(TQString,TQString)", "int"))
00326             return TQString();
00327         err = i18n("Error calling KMail");
00328     }
00329     kdError(5950) << "KAMail::addToKMailFolder(" << folder << "): " << err << endl;
00330     return err;
00331 }
00332 
00333 /******************************************************************************
00334 * Call KMail via DCOP. The DCOP function must return an 'int'.
00335 */
00336 bool KAMail::callKMail(const TQByteArray& callData, const TQCString& iface, const TQCString& function, const TQCString& funcType)
00337 {
00338     TQCString   replyType;
00339     TQByteArray replyData;
00340     if (!kapp->dcopClient()->call("kmail", iface, function, callData, replyType, replyData)
00341     ||  replyType != funcType)
00342     {
00343         TQCString funcname = function;
00344         funcname.replace(TQRegExp("(.+$"), "()");
00345         kdError(5950) << "KAMail::callKMail(): kmail " << funcname << " call failed\n";;
00346         return false;
00347     }
00348     TQDataStream replyStream(replyData, IO_ReadOnly);
00349     TQCString funcname = function;
00350     funcname.replace(TQRegExp("(.+$"), "()");
00351     if (replyType == "int")
00352     {
00353         int result;
00354         replyStream >> result;
00355         if (result <= 0)
00356         {
00357             kdError(5950) << "KAMail::callKMail(): kmail " << funcname << " call returned error code = " << result << endl;
00358             return false;
00359         }
00360     }
00361     else if (replyType == "bool")
00362     {
00363         bool result;
00364         replyStream >> result;
00365         if (!result)
00366         {
00367             kdError(5950) << "KAMail::callKMail(): kmail " << funcname << " call returned error\n";
00368             return false;
00369         }
00370     }
00371     return true;
00372 }
00373 
00374 /******************************************************************************
00375 * Create the headers part of the email.
00376 */
00377 TQString KAMail::initHeaders(const KAMailData& data, bool dateId)
00378 {
00379     TQString message;
00380     if (dateId)
00381     {
00382         struct timeval tod;
00383         gettimeofday(&tod, 0);
00384         time_t timenow = tod.tv_sec;
00385         char buff[64];
00386         strftime(buff, sizeof(buff), "Date: %a, %d %b %Y %H:%M:%S %z", localtime(&timenow));
00387         TQString from = data.from;
00388         from.replace(TQRegExp("^.*<"), TQString()).replace(TQRegExp(">.*$"), TQString());
00389         message = TQString::fromLatin1(buff);
00390         message += TQString::fromLatin1("\nMessage-Id: <%1.%2.%3>\n").arg(timenow).arg(tod.tv_usec).arg(from);
00391     }
00392     message += TQString::fromLatin1("From: ") + data.from;
00393     message += TQString::fromLatin1("\nTo: ") + data.event.emailAddresses(", ");
00394     if (!data.bcc.isEmpty())
00395         message += TQString::fromLatin1("\nBcc: ") + data.bcc;
00396     message += TQString::fromLatin1("\nSubject: ") + data.event.emailSubject();
00397     message += TQString::fromLatin1("\nX-Mailer: %1/" KALARM_VERSION).arg(kapp->aboutData()->programName());
00398     return message;
00399 }
00400 
00401 /******************************************************************************
00402 * Append the body and attachments to the email text.
00403 * Reply = reason for error
00404 *       = 0 if successful.
00405 */
00406 TQString KAMail::appendBodyAttachments(TQString& message, const KAEvent& event)
00407 {
00408     static const char* textMimeTypes[] = {
00409         "application/x-sh", "application/x-csh", "application/x-shellscript",
00410         "application/x-nawk", "application/x-gawk", "application/x-awk",
00411         "application/x-perl", "application/x-desktop",
00412         0
00413     };
00414     TQStringList attachments = event.emailAttachments();
00415     if (!attachments.count())
00416     {
00417         // There are no attachments, so simply append the message body
00418         message += "\n\n";
00419         message += event.message();
00420     }
00421     else
00422     {
00423         // There are attachments, so the message must be in MIME format
00424         // Create a boundary string
00425         time_t timenow;
00426         time(&timenow);
00427         TQCString boundary;
00428         boundary.sprintf("------------_%lu_-%lx=", 2*timenow, timenow);
00429         message += TQString::fromLatin1("\nMIME-Version: 1.0");
00430         message += TQString::fromLatin1("\nContent-Type: multipart/mixed;\n  boundary=\"%1\"\n").arg(boundary.data());
00431 
00432         if (!event.message().isEmpty())
00433         {
00434             // There is a message body
00435             message += TQString::fromLatin1("\n--%1\nContent-Type: text/plain\nContent-Transfer-Encoding: 8bit\n\n").arg(boundary.data());
00436             message += event.message();
00437         }
00438 
00439         // Append each attachment in turn
00440         TQString attachError = i18n("Error attaching file:\n%1");
00441         for (TQStringList::Iterator at = attachments.begin();  at != attachments.end();  ++at)
00442         {
00443             TQString attachment = (*at).local8Bit();
00444             KURL url(attachment);
00445             url.cleanPath();
00446             TDEIO::UDSEntry uds;
00447             if (!TDEIO::NetAccess::stat(url, uds, MainWindow::mainMainWindow())) {
00448                 kdError(5950) << "KAMail::appendBodyAttachments(): not found: " << attachment << endl;
00449                 return i18n("Attachment not found:\n%1").arg(attachment);
00450             }
00451             KFileItem fi(uds, url);
00452             if (fi.isDir()  ||  !fi.isReadable()) {
00453                 kdError(5950) << "KAMail::appendBodyAttachments(): not file/not readable: " << attachment << endl;
00454                 return attachError.arg(attachment);
00455             }
00456 
00457             // Check if the attachment is a text file
00458             TQString mimeType = fi.mimetype();
00459             bool text = mimeType.startsWith("text/");
00460             if (!text)
00461             {
00462                 for (int i = 0;  !text && textMimeTypes[i];  ++i)
00463                     text = (mimeType == textMimeTypes[i]);
00464             }
00465 
00466             message += TQString::fromLatin1("\n--%1").arg(boundary.data());
00467             message += TQString::fromLatin1("\nContent-Type: %2; name=\"%3\"").arg(mimeType).arg(fi.text());
00468             message += TQString::fromLatin1("\nContent-Transfer-Encoding: %1").arg(TQString::fromLatin1(text ? "8bit" : "BASE64"));
00469             message += TQString::fromLatin1("\nContent-Disposition: attachment; filename=\"%4\"\n\n").arg(fi.text());
00470 
00471             // Read the file contents
00472             TQString tmpFile;
00473             if (!TDEIO::NetAccess::download(url, tmpFile, MainWindow::mainMainWindow())) {
00474                 kdError(5950) << "KAMail::appendBodyAttachments(): load failure: " << attachment << endl;
00475                 return attachError.arg(attachment);
00476             }
00477             TQFile file(tmpFile);
00478             if (!file.open(IO_ReadOnly) ) {
00479                 kdDebug(5950) << "KAMail::appendBodyAttachments() tmp load error: " << attachment << endl;
00480                 return attachError.arg(attachment);
00481             }
00482             TQIODevice::Offset size = file.size();
00483             char* contents = new char [size + 1];
00484             TQ_LONG bytes = file.readBlock(contents, size);
00485             file.close();
00486             contents[size] = 0;
00487             bool atterror = false;
00488             if (bytes == -1  ||  (TQIODevice::Offset)bytes < size) {
00489                 kdDebug(5950) << "KAMail::appendBodyAttachments() read error: " << attachment << endl;
00490                 atterror = true;
00491             }
00492             else if (text)
00493             {
00494                 // Text attachment doesn't need conversion
00495                 message += contents;
00496             }
00497             else
00498             {
00499                 // Convert the attachment to BASE64 encoding
00500                 TQIODevice::Offset base64Size;
00501                 char* base64 = base64Encode(contents, size, base64Size);
00502                 if (base64Size == (TQIODevice::Offset)-1) {
00503                     kdDebug(5950) << "KAMail::appendBodyAttachments() base64 buffer overflow: " << attachment << endl;
00504                     atterror = true;
00505                 }
00506                 else
00507                     message += TQString::fromLatin1(base64, base64Size);
00508                 delete[] base64;
00509             }
00510             delete[] contents;
00511             if (atterror)
00512                 return attachError.arg(attachment);
00513         }
00514         message += TQString::fromLatin1("\n--%1--\n.\n").arg(boundary.data());
00515     }
00516     return TQString();
00517 }
00518 
00519 /******************************************************************************
00520 * If any of the destination email addresses are non-local, display a
00521 * notification message saying that an email has been queued for sending.
00522 */
00523 void KAMail::notifyQueued(const KAEvent& event)
00524 {
00525     KMime::Types::Address addr;
00526     TQString localhost = TQString::fromLatin1("localhost");
00527     TQString hostname  = getHostName();
00528     const EmailAddressList& addresses = event.emailAddresses();
00529     for (TQValueList<KCal::Person>::ConstIterator it = addresses.begin();  it != addresses.end();  ++it)
00530     {
00531         TQCString email = (*it).email().local8Bit();
00532         const char* em = email;
00533         if (!email.isEmpty()
00534         &&  HeaderParsing::parseAddress(em, em + email.length(), addr))
00535         {
00536             TQString domain = addr.mailboxList.first().addrSpec.domain;
00537             if (!domain.isEmpty()  &&  domain != localhost  &&  domain != hostname)
00538             {
00539                 TQString text = (Preferences::emailClient() == Preferences::KMAIL)
00540                              ? i18n("An email has been queued to be sent by KMail")
00541                              : i18n("An email has been queued to be sent");
00542                 KMessageBox::information(0, text, TQString(), Preferences::EMAIL_QUEUED_NOTIFY);
00543                 return;
00544             }
00545         }
00546     }
00547 }
00548 
00549 /******************************************************************************
00550 *  Return whether any KMail identities exist.
00551 */
00552 bool KAMail::identitiesExist()
00553 {
00554     identityManager();    // create identity manager if not already done
00555     return mIdentityManager->begin() != mIdentityManager->end();
00556 }
00557  
00558 /******************************************************************************
00559 *  Fetch the uoid of an email identity name or uoid string.
00560 */
00561 uint KAMail::identityUoid(const TQString& identityUoidOrName)
00562 {
00563     bool ok;
00564     uint id = identityUoidOrName.toUInt(&ok);
00565     if (!ok  ||  identityManager()->identityForUoid(id).isNull())
00566     {
00567         identityManager();   // fetch it if not already done
00568         for (KPIM::IdentityManager::ConstIterator it = mIdentityManager->begin();
00569              it != mIdentityManager->end();  ++it)
00570         {
00571             if ((*it).identityName() == identityUoidOrName)
00572             {
00573                 id = (*it).uoid();
00574                 break;
00575             }
00576         }
00577     }
00578     return id;
00579 }
00580 
00581 /******************************************************************************
00582 *  Fetch the user's email address configured in the TDE Control Centre.
00583 */
00584 TQString KAMail::controlCentreAddress()
00585 {
00586     KEMailSettings e;
00587     return e.getSetting(KEMailSettings::EmailAddress);
00588 }
00589 
00590 /******************************************************************************
00591 *  Parse a list of email addresses, optionally containing display names,
00592 *  entered by the user.
00593 *  Reply = the invalid item if error, else empty string.
00594 */
00595 TQString KAMail::convertAddresses(const TQString& items, EmailAddressList& list)
00596 {
00597     list.clear();
00598     TQCString addrs = items.local8Bit();
00599     const char* ad = static_cast<const char*>(addrs);
00600 
00601     // parse an address-list
00602     TQValueList<KMime::Types::Address> maybeAddressList;
00603     if (!HeaderParsing::parseAddressList(ad, ad + addrs.length(), maybeAddressList))
00604         return TQString::fromLocal8Bit(ad);    // return the address in error
00605 
00606     // extract the mailboxes and complain if there are groups
00607     for (TQValueList<KMime::Types::Address>::ConstIterator it = maybeAddressList.begin();
00608          it != maybeAddressList.end();  ++it)
00609     {
00610         TQString bad = convertAddress(*it, list);
00611         if (!bad.isEmpty())
00612             return bad;
00613     }
00614     return TQString();
00615 }
00616 
00617 #if 0
00618 /******************************************************************************
00619 *  Parse an email address, optionally containing display name, entered by the
00620 *  user, and append it to the specified list.
00621 *  Reply = the invalid item if error, else empty string.
00622 */
00623 TQString KAMail::convertAddress(const TQString& item, EmailAddressList& list)
00624 {
00625     TQCString addr = item.local8Bit();
00626     const char* ad = static_cast<const char*>(addr);
00627     KMime::Types::Address maybeAddress;
00628     if (!HeaderParsing::parseAddress(ad, ad + addr.length(), maybeAddress))
00629         return item;     // error
00630     return convertAddress(maybeAddress, list);
00631 }
00632 #endif
00633 
00634 /******************************************************************************
00635 *  Convert a single KMime::Types address to a KCal::Person instance and append
00636 *  it to the specified list.
00637 */
00638 TQString KAMail::convertAddress(KMime::Types::Address addr, EmailAddressList& list)
00639 {
00640     if (!addr.displayName.isEmpty())
00641     {
00642         kdDebug(5950) << "mailbox groups not allowed! Name: \"" << addr.displayName << "\"" << endl;
00643         return addr.displayName;
00644     }
00645     const TQValueList<KMime::Types::Mailbox>& mblist = addr.mailboxList;
00646     for (TQValueList<KMime::Types::Mailbox>::ConstIterator mb = mblist.begin();
00647          mb != mblist.end();  ++mb)
00648     {
00649         TQString addrPart = (*mb).addrSpec.localPart;
00650         if (!(*mb).addrSpec.domain.isEmpty())
00651         {
00652             addrPart += TQChar('@');
00653             addrPart += (*mb).addrSpec.domain;
00654         }
00655         list += KCal::Person((*mb).displayName, addrPart);
00656     }
00657     return TQString();
00658 }
00659 
00660 /*
00661 TQString KAMail::convertAddresses(const TQString& items, TQStringList& list)
00662 {
00663     EmailAddressList addrs;
00664     TQString item = convertAddresses(items, addrs);
00665     if (!item.isEmpty())
00666         return item;
00667     for (EmailAddressList::Iterator ad = addrs.begin();  ad != addrs.end();  ++ad)
00668     {
00669         item = (*ad).fullName().local8Bit();
00670         switch (checkAddress(item))
00671         {
00672             case 1:      // OK
00673                 list += item;
00674                 break;
00675             case 0:      // null address
00676                 break;
00677             case -1:     // invalid address
00678                 return item;
00679         }
00680     }
00681     return TQString();
00682 }*/
00683 
00684 /******************************************************************************
00685 *  Check the validity of an email address.
00686 *  Because internal email addresses don't have to abide by the usual internet
00687 *  email address rules, only some basic checks are made.
00688 *  Reply = 1 if alright, 0 if empty, -1 if error.
00689 */
00690 int KAMail::checkAddress(TQString& address)
00691 {
00692     address = address.stripWhiteSpace();
00693     // Check that there are no list separator characters present
00694     if (address.find(',') >= 0  ||  address.find(';') >= 0)
00695         return -1;
00696     int n = address.length();
00697     if (!n)
00698         return 0;
00699     int start = 0;
00700     int end   = n - 1;
00701     if (address[end] == '>')
00702     {
00703         // The email address is in <...>
00704         if ((start = address.find('<')) < 0)
00705             return -1;
00706         ++start;
00707         --end;
00708     }
00709     int i = address.find('@', start);
00710     if (i >= 0)
00711     {
00712         if (i == start  ||  i == end)          // check @ isn't the first or last character
00713 //      ||  address.find('@', i + 1) >= 0)    // check for multiple @ characters
00714             return -1;
00715     }
00716 /*  else
00717     {
00718         // Allow the @ character to be missing if it's a local user
00719         if (!getpwnam(address.mid(start, end - start + 1).local8Bit()))
00720             return false;
00721     }
00722     for (int i = start;  i <= end;  ++i)
00723     {
00724         char ch = address[i].latin1();
00725         if (ch == '.'  ||  ch == '@'  ||  ch == '-'  ||  ch == '_'
00726         ||  (ch >= 'A' && ch <= 'Z')  ||  (ch >= 'a' && ch <= 'z')
00727         ||  (ch >= '0' && ch <= '9'))
00728             continue;
00729         return false;
00730     }*/
00731     return 1;
00732 }
00733 
00734 /******************************************************************************
00735 *  Convert a comma or semicolon delimited list of attachments into a
00736 *  TQStringList. The items are checked for validity.
00737 *  Reply = the invalid item if error, else empty string.
00738 */
00739 TQString KAMail::convertAttachments(const TQString& items, TQStringList& list)
00740 {
00741     KURL url;
00742     list.clear();
00743     int length = items.length();
00744     for (int next = 0;  next < length;  )
00745     {
00746         // Find the first delimiter character (, or ;)
00747         int i = items.find(',', next);
00748         if (i < 0)
00749             i = items.length();
00750         int sc = items.find(';', next);
00751         if (sc < 0)
00752             sc = items.length();
00753         if (sc < i)
00754             i = sc;
00755         TQString item = items.mid(next, i - next).stripWhiteSpace();
00756         switch (checkAttachment(item))
00757         {
00758             case 1:   list += item;  break;
00759             case 0:   break;          // empty attachment name
00760             case -1:
00761             default:  return item;    // error
00762         }
00763         next = i + 1;
00764     }
00765     return TQString();
00766 }
00767 
00768 #if 0
00769 /******************************************************************************
00770 *  Convert a comma or semicolon delimited list of attachments into a
00771 *  KURL::List. The items are checked for validity.
00772 *  Reply = the invalid item if error, else empty string.
00773 */
00774 TQString KAMail::convertAttachments(const TQString& items, KURL::List& list)
00775 {
00776     KURL url;
00777     list.clear();
00778     TQCString addrs = items.local8Bit();
00779     int length = items.length();
00780     for (int next = 0;  next < length;  )
00781     {
00782         // Find the first delimiter character (, or ;)
00783         int i = items.find(',', next);
00784         if (i < 0)
00785             i = items.length();
00786         int sc = items.find(';', next);
00787         if (sc < 0)
00788             sc = items.length();
00789         if (sc < i)
00790             i = sc;
00791         TQString item = items.mid(next, i - next);
00792         switch (checkAttachment(item, &url))
00793         {
00794             case 1:   list += url;  break;
00795             case 0:   break;          // empty attachment name
00796             case -1:
00797             default:  return item;    // error
00798         }
00799         next = i + 1;
00800     }
00801     return TQString();
00802 }
00803 #endif
00804 
00805 /******************************************************************************
00806 *  Check for the existence of the attachment file.
00807 *  If non-null, '*url' receives the KURL of the attachment.
00808 *  Reply = 1 if attachment exists
00809 *        = 0 if null name
00810 *        = -1 if doesn't exist.
00811 */
00812 int KAMail::checkAttachment(TQString& attachment, KURL* url)
00813 {
00814     attachment = attachment.stripWhiteSpace();
00815     if (attachment.isEmpty())
00816     {
00817         if (url)
00818             *url = KURL();
00819         return 0;
00820     }
00821     // Check that the file exists
00822     KURL u = KURL::fromPathOrURL(attachment);
00823     u.cleanPath();
00824     if (url)
00825         *url = u;
00826     return checkAttachment(u) ? 1 : -1;
00827 }
00828 
00829 /******************************************************************************
00830 *  Check for the existence of the attachment file.
00831 */
00832 bool KAMail::checkAttachment(const KURL& url)
00833 {
00834     TDEIO::UDSEntry uds;
00835     if (!TDEIO::NetAccess::stat(url, uds, MainWindow::mainMainWindow()))
00836         return false;       // doesn't exist
00837     KFileItem fi(uds, url);
00838     if (fi.isDir()  ||  !fi.isReadable())
00839         return false;
00840     return true;
00841 }
00842 
00843 
00844 /******************************************************************************
00845 *  Convert a block of memory to Base64 encoding.
00846 *  'outSize' is set to the number of bytes used in the returned block, or to
00847 *            -1 if overflow.
00848 *  Reply = BASE64 buffer, which the caller must delete[] afterwards.
00849 */
00850 char* KAMail::base64Encode(const char* in, TQIODevice::Offset size, TQIODevice::Offset& outSize)
00851 {
00852     const int MAX_LINELEN = 72;
00853     static unsigned char dtable[65] =
00854         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
00855         "abcdefghijklmnopqrstuvwxyz"
00856         "0123456789+/";
00857 
00858     char* out = new char [2*size + 5];
00859     outSize = (TQIODevice::Offset)-1;
00860     TQIODevice::Offset outIndex = 0;
00861     int lineLength = 0;
00862     for (TQIODevice::Offset inIndex = 0;  inIndex < size;  )
00863     {
00864         unsigned char igroup[3];
00865         int n;
00866         for (n = 0;  n < 3;  ++n)
00867         {
00868             if (inIndex < size)
00869                 igroup[n] = (unsigned char)in[inIndex++];
00870             else
00871             {
00872                 igroup[n] = igroup[2] = 0;
00873                 break;
00874             }
00875         }
00876 
00877         if (n > 0)
00878         {
00879             unsigned char ogroup[4];
00880             ogroup[0] = dtable[igroup[0] >> 2];
00881             ogroup[1] = dtable[((igroup[0] & 3) << 4) | (igroup[1] >> 4)];
00882             ogroup[2] = dtable[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)];
00883             ogroup[3] = dtable[igroup[2] & 0x3F];
00884 
00885             if (n < 3)
00886             {
00887                 ogroup[3] = '=';
00888                 if (n < 2)
00889                     ogroup[2] = '=';
00890             }
00891             if (outIndex >= size*2)
00892             {
00893                 delete[] out;
00894                 return 0;
00895             }
00896             for (int i = 0;  i < 4;  ++i)
00897             {
00898                 if (lineLength >= MAX_LINELEN)
00899                 {
00900                     out[outIndex++] = '\r';
00901                     out[outIndex++] = '\n';
00902                     lineLength = 0;
00903                 }
00904                 out[outIndex++] = ogroup[i];
00905                 ++lineLength;
00906             }
00907         }
00908     }
00909 
00910     if (outIndex + 2 < size*2)
00911     {
00912         out[outIndex++] = '\r';
00913         out[outIndex++] = '\n';
00914     }
00915     outSize = outIndex;
00916     return out;
00917 }
00918 
00919 /******************************************************************************
00920 * Set the appropriate error messages for a given error string.
00921 */
00922 TQStringList KAMail::errors(const TQString& err, bool sendfail)
00923 {
00924     TQString error1 = sendfail ? i18n("Failed to send email")
00925                               : i18n("Error copying sent email to KMail %1 folder").arg(i18n_sent_mail());
00926     if (err.isEmpty())
00927         return TQStringList(error1);
00928     TQStringList errs(TQString::fromLatin1("%1:").arg(error1));
00929     errs += err;
00930     return errs;
00931 }
00932 
00933 /******************************************************************************
00934 *  Get the body of an email, given its serial number.
00935 */
00936 TQString KAMail::getMailBody(TQ_UINT32 serialNumber)
00937 {
00938     // Get the body of the email from KMail
00939     TQCString    replyType;
00940     TQByteArray  replyData;
00941     TQByteArray  data;
00942     TQDataStream arg(data, IO_WriteOnly);
00943     arg << serialNumber;
00944     arg << (int)0;
00945     TQString body;
00946     if (kapp->dcopClient()->call("kmail", "KMailIface", "getDecodedBodyPart(TQ_UINT32,int)", data, replyType, replyData)
00947     &&  replyType == TQSTRING_OBJECT_NAME_STRING)
00948     {
00949         TQDataStream reply_stream(replyData, IO_ReadOnly);
00950         reply_stream >> body;
00951     }
00952     else
00953         kdDebug(5950) << "KAMail::getMailBody(): kmail getDecodedBodyPart() call failed\n";
00954     return body;
00955 }
00956 
00957 namespace
00958 {
00959 /******************************************************************************
00960 * Get the local system's host name.
00961 */
00962 TQString getHostName()
00963 {
00964         char hname[256];
00965         if (gethostname(hname, sizeof(hname)))
00966                 return TQString();
00967         return TQString::fromLocal8Bit(hname);
00968 }
00969 }
00970 
00971 
00972 /*=============================================================================
00973 =  HeaderParsing :  modified and additional functions.
00974 =  The following functions are modified from, or additional to, those in
00975 =  libtdenetwork kmime_header_parsing.cpp.
00976 =============================================================================*/
00977 
00978 namespace HeaderParsing
00979 {
00980 
00981 using namespace KMime;
00982 using namespace KMime::Types;
00983 using namespace KMime::HeaderParsing;
00984 
00985 /******************************************************************************
00986 *  New function.
00987 *  Allow a local user name to be specified as an email address.
00988 */
00989 bool parseUserName( const char* & scursor, const char * const send,
00990                     TQString & result, bool isCRLF ) {
00991 
00992   TQString maybeLocalPart;
00993   TQString tmp;
00994 
00995   if ( scursor != send ) {
00996     // first, eat any whitespace
00997     eatCFWS( scursor, send, isCRLF );
00998 
00999     char ch = *scursor++;
01000     switch ( ch ) {
01001     case '.': // dot
01002     case '@':
01003     case '"': // quoted-string
01004       return false;
01005 
01006     default: // atom
01007       scursor--; // re-set scursor to point to ch again
01008       tmp = TQString();
01009       if ( parseAtom( scursor, send, result, false /* no 8bit */ ) ) {
01010         if (getpwnam(result.local8Bit()))
01011           return true;
01012       }
01013       return false; // parseAtom can only fail if the first char is non-atext.
01014     }
01015   }
01016   return false;
01017 }
01018 
01019 /******************************************************************************
01020 *  Modified function.
01021 *  Allow a local user name to be specified as an email address, and reinstate
01022 *  the original scursor on error return.
01023 */
01024 bool parseAddress( const char* & scursor, const char * const send,
01025            Address & result, bool isCRLF ) {
01026   // address       := mailbox / group
01027 
01028   eatCFWS( scursor, send, isCRLF );
01029   if ( scursor == send ) return false;
01030 
01031   // first try if it's a single mailbox:
01032   Mailbox maybeMailbox;
01033   const char * oldscursor = scursor;
01034   if ( parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) {
01035     // yes, it is:
01036     result.displayName = TQString();
01037     result.mailboxList.append( maybeMailbox );
01038     return true;
01039   }
01040   scursor = oldscursor;
01041 
01042   // KAlarm: Allow a local user name to be specified
01043   // no, it's not a single mailbox. Try if it's a local user name:
01044   TQString maybeUserName;
01045   if ( parseUserName( scursor, send, maybeUserName, isCRLF ) ) {
01046     // yes, it is:
01047     maybeMailbox.displayName = TQString();
01048     maybeMailbox.addrSpec.localPart = maybeUserName;
01049     maybeMailbox.addrSpec.domain = TQString();
01050     result.displayName = TQString();
01051     result.mailboxList.append( maybeMailbox );
01052     return true;
01053   }
01054   scursor = oldscursor;
01055 
01056   Address maybeAddress;
01057 
01058   // no, it's not a single mailbox. Try if it's a group:
01059   if ( !parseGroup( scursor, send, maybeAddress, isCRLF ) )
01060   {
01061     scursor = oldscursor;   // KAlarm: reinstate original scursor on error return
01062     return false;
01063   }
01064 
01065   result = maybeAddress;
01066   return true;
01067 }
01068 
01069 /******************************************************************************
01070 *  Modified function.
01071 *  Allow either ',' or ';' to be used as an email address separator.
01072 */
01073 bool parseAddressList( const char* & scursor, const char * const send,
01074                TQValueList<Address> & result, bool isCRLF ) {
01075   while ( scursor != send ) {
01076     eatCFWS( scursor, send, isCRLF );
01077     // end of header: this is OK.
01078     if ( scursor == send ) return true;
01079     // empty entry: ignore:
01080     if ( *scursor == ',' || *scursor == ';' ) { scursor++; continue; }   // KAlarm: allow ';' as address separator
01081 
01082     // parse one entry
01083     Address maybeAddress;
01084     if ( !parseAddress( scursor, send, maybeAddress, isCRLF ) ) return false;
01085     result.append( maybeAddress );
01086 
01087     eatCFWS( scursor, send, isCRLF );
01088     // end of header: this is OK.
01089     if ( scursor == send ) return true;
01090     // comma separating entries: eat it.
01091     if ( *scursor == ',' || *scursor == ';' ) scursor++;   // KAlarm: allow ';' as address separator
01092   }
01093   return true;
01094 }
01095 
01096 } // namespace HeaderParsing