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