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         QString from = data.from;
00376         from.replace(QRegExp("^.*<"), QString::null).replace(QRegExp(">.*$"), QString::null);
00377         message = QString::fromLatin1(buff);
00378         message += QString::fromLatin1("\nMessage-Id: <%1.%2.%3>\n").arg(timenow).arg(tod.tv_usec).arg(from);
00379     }
00380     message += QString::fromLatin1("From: ") + data.from;
00381     message += QString::fromLatin1("\nTo: ") + data.event.emailAddresses(", ");
00382     if (!data.bcc.isEmpty())
00383         message += QString::fromLatin1("\nBcc: ") + data.bcc;
00384     message += QString::fromLatin1("\nSubject: ") + data.event.emailSubject();
00385     message += QString::fromLatin1("\nX-Mailer: %1/" KALARM_VERSION).arg(kapp->aboutData()->programName());
00386     return message;
00387 }
00388 
00389 /******************************************************************************
00390 * Append the body and attachments to the email text.
00391 * Reply = reason for error
00392 *       = 0 if successful.
00393 */
00394 QString KAMail::appendBodyAttachments(QString& message, const KAEvent& event)
00395 {
00396     static const char* textMimeTypes[] = {
00397         "application/x-sh", "application/x-csh", "application/x-shellscript",
00398         "application/x-nawk", "application/x-gawk", "application/x-awk",
00399         "application/x-perl", "application/x-desktop",
00400         0
00401     };
00402     QStringList attachments = event.emailAttachments();
00403     if (!attachments.count())
00404     {
00405         // There are no attachments, so simply append the message body
00406         message += "\n\n";
00407         message += event.message();
00408     }
00409     else
00410     {
00411         // There are attachments, so the message must be in MIME format
00412         // Create a boundary string
00413         time_t timenow;
00414         time(&timenow);
00415         QCString boundary;
00416         boundary.sprintf("------------_%lu_-%lx=", 2*timenow, timenow);
00417         message += QString::fromLatin1("\nMIME-Version: 1.0");
00418         message += QString::fromLatin1("\nContent-Type: multipart/mixed;\n  boundary=\"%1\"\n").arg(boundary);
00419 
00420         if (!event.message().isEmpty())
00421         {
00422             // There is a message body
00423             message += QString::fromLatin1("\n--%1\nContent-Type: text/plain\nContent-Transfer-Encoding: 8bit\n\n").arg(boundary);
00424             message += event.message();
00425         }
00426 
00427         // Append each attachment in turn
00428         QString attachError = i18n("Error attaching file:\n%1");
00429         for (QStringList::Iterator at = attachments.begin();  at != attachments.end();  ++at)
00430         {
00431             QString attachment = (*at).local8Bit();
00432             KURL url(attachment);
00433             url.cleanPath();
00434             KIO::UDSEntry uds;
00435             if (!KIO::NetAccess::stat(url, uds, MainWindow::mainMainWindow())) {
00436                 kdError(5950) << "KAMail::appendBodyAttachments(): not found: " << attachment << endl;
00437                 return i18n("Attachment not found:\n%1").arg(attachment);
00438             }
00439             KFileItem fi(uds, url);
00440             if (fi.isDir()  ||  !fi.isReadable()) {
00441                 kdError(5950) << "KAMail::appendBodyAttachments(): not file/not readable: " << attachment << endl;
00442                 return attachError.arg(attachment);
00443             }
00444 
00445             // Check if the attachment is a text file
00446             QString mimeType = fi.mimetype();
00447             bool text = mimeType.startsWith("text/");
00448             if (!text)
00449             {
00450                 for (int i = 0;  !text && textMimeTypes[i];  ++i)
00451                     text = (mimeType == textMimeTypes[i]);
00452             }
00453 
00454             message += QString::fromLatin1("\n--%1").arg(boundary);
00455             message += QString::fromLatin1("\nContent-Type: %2; name=\"%3\"").arg(mimeType).arg(fi.text());
00456             message += QString::fromLatin1("\nContent-Transfer-Encoding: %1").arg(QString::fromLatin1(text ? "8bit" : "BASE64"));
00457             message += QString::fromLatin1("\nContent-Disposition: attachment; filename=\"%4\"\n\n").arg(fi.text());
00458 
00459             // Read the file contents
00460             QString tmpFile;
00461             if (!KIO::NetAccess::download(url, tmpFile, MainWindow::mainMainWindow())) {
00462                 kdError(5950) << "KAMail::appendBodyAttachments(): load failure: " << attachment << endl;
00463                 return attachError.arg(attachment);
00464             }
00465             QFile file(tmpFile);
00466             if (!file.open(IO_ReadOnly) ) {
00467                 kdDebug(5950) << "KAMail::appendBodyAttachments() tmp load error: " << attachment << endl;
00468                 return attachError.arg(attachment);
00469             }
00470             QIODevice::Offset size = file.size();
00471             char* contents = new char [size + 1];
00472             Q_LONG bytes = file.readBlock(contents, size);
00473             file.close();
00474             contents[size] = 0;
00475             bool atterror = false;
00476             if (bytes == -1  ||  (QIODevice::Offset)bytes < size) {
00477                 kdDebug(5950) << "KAMail::appendBodyAttachments() read error: " << attachment << endl;
00478                 atterror = true;
00479             }
00480             else if (text)
00481             {
00482                 // Text attachment doesn't need conversion
00483                 message += contents;
00484             }
00485             else
00486             {
00487                 // Convert the attachment to BASE64 encoding
00488                 QIODevice::Offset base64Size;
00489                 char* base64 = base64Encode(contents, size, base64Size);
00490                 if (base64Size == (QIODevice::Offset)-1) {
00491                     kdDebug(5950) << "KAMail::appendBodyAttachments() base64 buffer overflow: " << attachment << endl;
00492                     atterror = true;
00493                 }
00494                 else
00495                     message += QString::fromLatin1(base64, base64Size);
00496                 delete[] base64;
00497             }
00498             delete[] contents;
00499             if (atterror)
00500                 return attachError.arg(attachment);
00501         }
00502         message += QString::fromLatin1("\n--%1--\n.\n").arg(boundary);
00503     }
00504     return QString::null;
00505 }
00506 
00507 /******************************************************************************
00508 * If any of the destination email addresses are non-local, display a
00509 * notification message saying that an email has been queued for sending.
00510 */
00511 void KAMail::notifyQueued(const KAEvent& event)
00512 {
00513     KMime::Types::Address addr;
00514     QString localhost = QString::fromLatin1("localhost");
00515     QString hostname  = getHostName();
00516     const EmailAddressList& addresses = event.emailAddresses();
00517     for (QValueList<KCal::Person>::ConstIterator it = addresses.begin();  it != addresses.end();  ++it)
00518     {
00519         QCString email = (*it).email().local8Bit();
00520         const char* em = email;
00521         if (!email.isEmpty()
00522         &&  HeaderParsing::parseAddress(em, em + email.length(), addr))
00523         {
00524             QString domain = addr.mailboxList.first().addrSpec.domain;
00525             if (!domain.isEmpty()  &&  domain != localhost  &&  domain != hostname)
00526             {
00527                 QString text = (Preferences::emailClient() == Preferences::KMAIL)
00528                              ? i18n("An email has been queued to be sent by KMail")
00529                              : i18n("An email has been queued to be sent");
00530                 KMessageBox::information(0, text, QString::null, Preferences::EMAIL_QUEUED_NOTIFY);
00531                 return;
00532             }
00533         }
00534     }
00535 }
00536 
00537 /******************************************************************************
00538 *  Return whether any KMail identities exist.
00539 */
00540 bool KAMail::identitiesExist()
00541 {
00542     identityManager();    // create identity manager if not already done
00543     return mIdentityManager->begin() != mIdentityManager->end();
00544 }
00545 
00546 /******************************************************************************
00547 *  Fetch the user's email address configured in the KDE Control Centre.
00548 */
00549 QString KAMail::controlCentreAddress()
00550 {
00551     KEMailSettings e;
00552     return e.getSetting(KEMailSettings::EmailAddress);
00553 }
00554 
00555 /******************************************************************************
00556 *  Parse a list of email addresses, optionally containing display names,
00557 *  entered by the user.
00558 *  Reply = the invalid item if error, else empty string.
00559 */
00560 QString KAMail::convertAddresses(const QString& items, EmailAddressList& list)
00561 {
00562     list.clear();
00563     QCString addrs = items.local8Bit();
00564     const char* ad = static_cast<const char*>(addrs);
00565 
00566     // parse an address-list
00567     QValueList<KMime::Types::Address> maybeAddressList;
00568     if (!HeaderParsing::parseAddressList(ad, ad + addrs.length(), maybeAddressList))
00569         return QString::fromLocal8Bit(ad);    // return the address in error
00570 
00571     // extract the mailboxes and complain if there are groups
00572     for (QValueList<KMime::Types::Address>::ConstIterator it = maybeAddressList.begin();
00573          it != maybeAddressList.end();  ++it)
00574     {
00575         QString bad = convertAddress(*it, list);
00576         if (!bad.isEmpty())
00577             return bad;
00578     }
00579     return QString::null;
00580 }
00581 
00582 #if 0
00583 /******************************************************************************
00584 *  Parse an email address, optionally containing display name, entered by the
00585 *  user, and append it to the specified list.
00586 *  Reply = the invalid item if error, else empty string.
00587 */
00588 QString KAMail::convertAddress(const QString& item, EmailAddressList& list)
00589 {
00590     QCString addr = item.local8Bit();
00591     const char* ad = static_cast<const char*>(addr);
00592     KMime::Types::Address maybeAddress;
00593     if (!HeaderParsing::parseAddress(ad, ad + addr.length(), maybeAddress))
00594         return item;     // error
00595     return convertAddress(maybeAddress, list);
00596 }
00597 #endif
00598 
00599 /******************************************************************************
00600 *  Convert a single KMime::Types address to a KCal::Person instance and append
00601 *  it to the specified list.
00602 */
00603 QString KAMail::convertAddress(KMime::Types::Address addr, EmailAddressList& list)
00604 {
00605     if (!addr.displayName.isEmpty())
00606     {
00607         kdDebug(5950) << "mailbox groups not allowed! Name: \"" << addr.displayName << "\"" << endl;
00608         return addr.displayName;
00609     }
00610     const QValueList<KMime::Types::Mailbox>& mblist = addr.mailboxList;
00611     for (QValueList<KMime::Types::Mailbox>::ConstIterator mb = mblist.begin();
00612          mb != mblist.end();  ++mb)
00613     {
00614         QString addrPart = (*mb).addrSpec.localPart;
00615         if (!(*mb).addrSpec.domain.isEmpty())
00616         {
00617             addrPart += QChar('@');
00618             addrPart += (*mb).addrSpec.domain;
00619         }
00620         list += KCal::Person((*mb).displayName, addrPart);
00621     }
00622     return QString::null;
00623 }
00624 
00625 /*
00626 QString KAMail::convertAddresses(const QString& items, QStringList& list)
00627 {
00628     EmailAddressList addrs;
00629     QString item = convertAddresses(items, addrs);
00630     if (!item.isEmpty())
00631         return item;
00632     for (EmailAddressList::Iterator ad = addrs.begin();  ad != addrs.end();  ++ad)
00633     {
00634         item = (*ad).fullName().local8Bit();
00635         switch (checkAddress(item))
00636         {
00637             case 1:      // OK
00638                 list += item;
00639                 break;
00640             case 0:      // null address
00641                 break;
00642             case -1:     // invalid address
00643                 return item;
00644         }
00645     }
00646     return QString::null;
00647 }*/
00648 
00649 /******************************************************************************
00650 *  Check the validity of an email address.
00651 *  Because internal email addresses don't have to abide by the usual internet
00652 *  email address rules, only some basic checks are made.
00653 *  Reply = 1 if alright, 0 if empty, -1 if error.
00654 */
00655 int KAMail::checkAddress(QString& address)
00656 {
00657     address = address.stripWhiteSpace();
00658     // Check that there are no list separator characters present
00659     if (address.find(',') >= 0  ||  address.find(';') >= 0)
00660         return -1;
00661     int n = address.length();
00662     if (!n)
00663         return 0;
00664     int start = 0;
00665     int end   = n - 1;
00666     if (address[end] == '>')
00667     {
00668         // The email address is in <...>
00669         if ((start = address.find('<')) < 0)
00670             return -1;
00671         ++start;
00672         --end;
00673     }
00674     int i = address.find('@', start);
00675     if (i >= 0)
00676     {
00677         if (i == start  ||  i == end)          // check @ isn't the first or last character
00678 //      ||  address.find('@', i + 1) >= 0)    // check for multiple @ characters
00679             return -1;
00680     }
00681 /*  else
00682     {
00683         // Allow the @ character to be missing if it's a local user
00684         if (!getpwnam(address.mid(start, end - start + 1).local8Bit()))
00685             return false;
00686     }
00687     for (int i = start;  i <= end;  ++i)
00688     {
00689         char ch = address[i].latin1();
00690         if (ch == '.'  ||  ch == '@'  ||  ch == '-'  ||  ch == '_'
00691         ||  (ch >= 'A' && ch <= 'Z')  ||  (ch >= 'a' && ch <= 'z')
00692         ||  (ch >= '0' && ch <= '9'))
00693             continue;
00694         return false;
00695     }*/
00696     return 1;
00697 }
00698 
00699 /******************************************************************************
00700 *  Convert a comma or semicolon delimited list of attachments into a
00701 *  QStringList. The items are checked for validity.
00702 *  Reply = the invalid item if error, else empty string.
00703 */
00704 QString KAMail::convertAttachments(const QString& items, QStringList& list)
00705 {
00706     KURL url;
00707     list.clear();
00708     int length = items.length();
00709     for (int next = 0;  next < length;  )
00710     {
00711         // Find the first delimiter character (, or ;)
00712         int i = items.find(',', next);
00713         if (i < 0)
00714             i = items.length();
00715         int sc = items.find(';', next);
00716         if (sc < 0)
00717             sc = items.length();
00718         if (sc < i)
00719             i = sc;
00720         QString item = items.mid(next, i - next).stripWhiteSpace();
00721         switch (checkAttachment(item))
00722         {
00723             case 1:   list += item;  break;
00724             case 0:   break;          // empty attachment name
00725             case -1:
00726             default:  return item;    // error
00727         }
00728         next = i + 1;
00729     }
00730     return QString::null;
00731 }
00732 
00733 #if 0
00734 /******************************************************************************
00735 *  Convert a comma or semicolon delimited list of attachments into a
00736 *  KURL::List. The items are checked for validity.
00737 *  Reply = the invalid item if error, else empty string.
00738 */
00739 QString KAMail::convertAttachments(const QString& items, KURL::List& list)
00740 {
00741     KURL url;
00742     list.clear();
00743     QCString addrs = items.local8Bit();
00744     int length = items.length();
00745     for (int next = 0;  next < length;  )
00746     {
00747         // Find the first delimiter character (, or ;)
00748         int i = items.find(',', next);
00749         if (i < 0)
00750             i = items.length();
00751         int sc = items.find(';', next);
00752         if (sc < 0)
00753             sc = items.length();
00754         if (sc < i)
00755             i = sc;
00756         QString item = items.mid(next, i - next);
00757         switch (checkAttachment(item, &url))
00758         {
00759             case 1:   list += url;  break;
00760             case 0:   break;          // empty attachment name
00761             case -1:
00762             default:  return item;    // error
00763         }
00764         next = i + 1;
00765     }
00766     return QString::null;
00767 }
00768 #endif
00769 
00770 /******************************************************************************
00771 *  Check for the existence of the attachment file.
00772 *  If non-null, '*url' receives the KURL of the attachment.
00773 *  Reply = 1 if attachment exists
00774 *        = 0 if null name
00775 *        = -1 if doesn't exist.
00776 */
00777 int KAMail::checkAttachment(QString& attachment, KURL* url)
00778 {
00779     attachment = attachment.stripWhiteSpace();
00780     if (attachment.isEmpty())
00781     {
00782         if (url)
00783             *url = KURL();
00784         return 0;
00785     }
00786     // Check that the file exists
00787     KURL u = KURL::fromPathOrURL(attachment);
00788     u.cleanPath();
00789     if (url)
00790         *url = u;
00791     return checkAttachment(u) ? 1 : -1;
00792 }
00793 
00794 /******************************************************************************
00795 *  Check for the existence of the attachment file.
00796 */
00797 bool KAMail::checkAttachment(const KURL& url)
00798 {
00799     KIO::UDSEntry uds;
00800     if (!KIO::NetAccess::stat(url, uds, MainWindow::mainMainWindow()))
00801         return false;       // doesn't exist
00802     KFileItem fi(uds, url);
00803     if (fi.isDir()  ||  !fi.isReadable())
00804         return false;
00805     return true;
00806 }
00807 
00808 
00809 /******************************************************************************
00810 *  Convert a block of memory to Base64 encoding.
00811 *  'outSize' is set to the number of bytes used in the returned block, or to
00812 *            -1 if overflow.
00813 *  Reply = BASE64 buffer, which the caller must delete[] afterwards.
00814 */
00815 char* KAMail::base64Encode(const char* in, QIODevice::Offset size, QIODevice::Offset& outSize)
00816 {
00817     const int MAX_LINELEN = 72;
00818     static unsigned char dtable[65] =
00819         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
00820         "abcdefghijklmnopqrstuvwxyz"
00821         "0123456789+/";
00822 
00823     char* out = new char [2*size + 5];
00824     outSize = (QIODevice::Offset)-1;
00825     QIODevice::Offset outIndex = 0;
00826     int lineLength = 0;
00827     for (QIODevice::Offset inIndex = 0;  inIndex < size;  )
00828     {
00829         unsigned char igroup[3];
00830         int n;
00831         for (n = 0;  n < 3;  ++n)
00832         {
00833             if (inIndex < size)
00834                 igroup[n] = (unsigned char)in[inIndex++];
00835             else
00836             {
00837                 igroup[n] = igroup[2] = 0;
00838                 break;
00839             }
00840         }
00841 
00842         if (n > 0)
00843         {
00844             unsigned char ogroup[4];
00845             ogroup[0] = dtable[igroup[0] >> 2];
00846             ogroup[1] = dtable[((igroup[0] & 3) << 4) | (igroup[1] >> 4)];
00847             ogroup[2] = dtable[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)];
00848             ogroup[3] = dtable[igroup[2] & 0x3F];
00849 
00850             if (n < 3)
00851             {
00852                 ogroup[3] = '=';
00853                 if (n < 2)
00854                     ogroup[2] = '=';
00855             }
00856             if (outIndex >= size*2)
00857             {
00858                 delete[] out;
00859                 return 0;
00860             }
00861             for (int i = 0;  i < 4;  ++i)
00862             {
00863                 if (lineLength >= MAX_LINELEN)
00864                 {
00865                     out[outIndex++] = '\r';
00866                     out[outIndex++] = '\n';
00867                     lineLength = 0;
00868                 }
00869                 out[outIndex++] = ogroup[i];
00870                 ++lineLength;
00871             }
00872         }
00873     }
00874 
00875     if (outIndex + 2 < size*2)
00876     {
00877         out[outIndex++] = '\r';
00878         out[outIndex++] = '\n';
00879     }
00880     outSize = outIndex;
00881     return out;
00882 }
00883 
00884 /******************************************************************************
00885 * Set the appropriate error messages for a given error string.
00886 */
00887 QStringList KAMail::errors(const QString& err, bool sendfail)
00888 {
00889     QString error1 = sendfail ? i18n("Failed to send email")
00890                               : i18n("Error copying sent email to KMail %1 folder").arg(i18n_sent_mail());
00891     if (err.isEmpty())
00892         return QStringList(error1);
00893     QStringList errs(QString::fromLatin1("%1:").arg(error1));
00894     errs += err;
00895     return errs;
00896 }
00897 
00898 /******************************************************************************
00899 *  Get the body of an email, given its serial number.
00900 */
00901 QString KAMail::getMailBody(Q_UINT32 serialNumber)
00902 {
00903     // Get the body of the email from KMail
00904     QCString    replyType;
00905     QByteArray  replyData;
00906     QByteArray  data;
00907     QDataStream arg(data, IO_WriteOnly);
00908     arg << serialNumber;
00909     arg << (int)0;
00910     QString body;
00911     if (kapp->dcopClient()->call("kmail", "KMailIface", "getDecodedBodyPart(Q_UINT32,int)", data, replyType, replyData)
00912     &&  replyType == "QString")
00913     {
00914         QDataStream reply_stream(replyData, IO_ReadOnly);
00915         reply_stream >> body;
00916     }
00917     else
00918         kdDebug(5950) << "KAMail::getMailBody(): kmail getDecodedBodyPart() call failed\n";
00919     return body;
00920 }
00921 
00922 namespace
00923 {
00924 /******************************************************************************
00925 * Get the local system's host name.
00926 */
00927 QString getHostName()
00928 {
00929         char hname[256];
00930         if (gethostname(hname, sizeof(hname)))
00931                 return QString::null;
00932         return QString::fromLocal8Bit(hname);
00933 }
00934 }
00935 
00936 
00937 /*=============================================================================
00938 =  HeaderParsing :  modified and additional functions.
00939 =  The following functions are modified from, or additional to, those in
00940 =  libkdenetwork kmime_header_parsing.cpp.
00941 =============================================================================*/
00942 
00943 namespace HeaderParsing
00944 {
00945 
00946 using namespace KMime;
00947 using namespace KMime::Types;
00948 using namespace KMime::HeaderParsing;
00949 
00950 /******************************************************************************
00951 *  New function.
00952 *  Allow a local user name to be specified as an email address.
00953 */
00954 bool parseUserName( const char* & scursor, const char * const send,
00955                     QString & result, bool isCRLF ) {
00956 
00957   QString maybeLocalPart;
00958   QString tmp;
00959 
00960   if ( scursor != send ) {
00961     // first, eat any whitespace
00962     eatCFWS( scursor, send, isCRLF );
00963 
00964     char ch = *scursor++;
00965     switch ( ch ) {
00966     case '.': // dot
00967     case '@':
00968     case '"': // quoted-string
00969       return false;
00970 
00971     default: // atom
00972       scursor--; // re-set scursor to point to ch again
00973       tmp = QString::null;
00974       if ( parseAtom( scursor, send, result, false /* no 8bit */ ) ) {
00975         if (getpwnam(result.local8Bit()))
00976           return true;
00977       }
00978       return false; // parseAtom can only fail if the first char is non-atext.
00979     }
00980   }
00981   return false;
00982 }
00983 
00984 /******************************************************************************
00985 *  Modified function.
00986 *  Allow a local user name to be specified as an email address, and reinstate
00987 *  the original scursor on error return.
00988 */
00989 bool parseAddress( const char* & scursor, const char * const send,
00990            Address & result, bool isCRLF ) {
00991   // address       := mailbox / group
00992 
00993   eatCFWS( scursor, send, isCRLF );
00994   if ( scursor == send ) return false;
00995 
00996   // first try if it's a single mailbox:
00997   Mailbox maybeMailbox;
00998   const char * oldscursor = scursor;
00999   if ( parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) {
01000     // yes, it is:
01001     result.displayName = QString::null;
01002     result.mailboxList.append( maybeMailbox );
01003     return true;
01004   }
01005   scursor = oldscursor;
01006 
01007   // KAlarm: Allow a local user name to be specified
01008   // no, it's not a single mailbox. Try if it's a local user name:
01009   QString maybeUserName;
01010   if ( parseUserName( scursor, send, maybeUserName, isCRLF ) ) {
01011     // yes, it is:
01012     maybeMailbox.displayName = QString::null;
01013     maybeMailbox.addrSpec.localPart = maybeUserName;
01014     maybeMailbox.addrSpec.domain = QString::null;
01015     result.displayName = QString::null;
01016     result.mailboxList.append( maybeMailbox );
01017     return true;
01018   }
01019   scursor = oldscursor;
01020 
01021   Address maybeAddress;
01022 
01023   // no, it's not a single mailbox. Try if it's a group:
01024   if ( !parseGroup( scursor, send, maybeAddress, isCRLF ) )
01025   {
01026     scursor = oldscursor;   // KAlarm: reinstate original scursor on error return
01027     return false;
01028   }
01029 
01030   result = maybeAddress;
01031   return true;
01032 }
01033 
01034 /******************************************************************************
01035 *  Modified function.
01036 *  Allow either ',' or ';' to be used as an email address separator.
01037 */
01038 bool parseAddressList( const char* & scursor, const char * const send,
01039                QValueList<Address> & result, bool isCRLF ) {
01040   while ( scursor != send ) {
01041     eatCFWS( scursor, send, isCRLF );
01042     // end of header: this is OK.
01043     if ( scursor == send ) return true;
01044     // empty entry: ignore:
01045     if ( *scursor == ',' || *scursor == ';' ) { scursor++; continue; }   // KAlarm: allow ';' as address separator
01046 
01047     // parse one entry
01048     Address maybeAddress;
01049     if ( !parseAddress( scursor, send, maybeAddress, isCRLF ) ) return false;
01050     result.append( maybeAddress );
01051 
01052     eatCFWS( scursor, send, isCRLF );
01053     // end of header: this is OK.
01054     if ( scursor == send ) return true;
01055     // comma separating entries: eat it.
01056     if ( *scursor == ',' || *scursor == ';' ) scursor++;   // KAlarm: allow ';' as address separator
01057   }
01058   return true;
01059 }
01060 
01061 } // namespace HeaderParsing
KDE Home | KDE Accessibility Home | Description of Access Keys