kalarm

kamail.cpp

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