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