diff options
Diffstat (limited to 'libkmime')
102 files changed, 13310 insertions, 0 deletions
diff --git a/libkmime/AUTHORS.kmime b/libkmime/AUTHORS.kmime new file mode 100644 index 000000000..9ff6ef978 --- /dev/null +++ b/libkmime/AUTHORS.kmime @@ -0,0 +1,16 @@ +Developers: +* Marc Mutz <mutz@kde.org> + Current maintainer + +* Christian Thurner <cthurner@freepage.de> + Original author and current maintainer of KNode + +* Christian Gebauer + Original author + +Developers, which may have contributed code: +(please tell me) +* Mathias Waack <mathias@atoll-net.de> +* Dirk Mueller <mueller@kde.org> +* Matthias Kalle Dalheimer <kalle@kde.org> + diff --git a/libkmime/CLASSTREE.bodyparts b/libkmime/CLASSTREE.bodyparts new file mode 100644 index 000000000..eeaa7726a --- /dev/null +++ b/libkmime/CLASSTREE.bodyparts @@ -0,0 +1,31 @@ + ++ Content + + Message + + NewsArticle + + Mail + + BodyPart + + GMultiPart + + MultiPart::Mixed + + MultiPart::Alternative + + MultiPart::Related + + MultiPart::Report + + MultiPart::Encrypted + + MultiPart::Signed + + GMessage + + Message::RFC822 + + Message::Partial + + Message::ExternalBody + + Message::DeliveryStatus + + GText + + Text::Plain + + Text::Enriched + + Text::HTML + + Text::RFC822Headers + + GApplication + + Application::OctetStream + + GImage + + GVideo + + GAudio + + GModel + + diff --git a/libkmime/CLASSTREE.headers b/libkmime/CLASSTREE.headers new file mode 100644 index 000000000..210abe473 --- /dev/null +++ b/libkmime/CLASSTREE.headers @@ -0,0 +1,87 @@ +* indicates abstract classes (in namespace Generics) ++ on leaves: indicates non-trivial subclassing +<blank line> indicates progress ;-) + +r indicates rfc2822/822 standard headers +m indicates rfc2045/et al. proposed standard headers +u indicates usefor headers + ++ Base* // defines common interface + + GUnstructured* // Base Class for unstructured header fields + + Generic+ // can hold any header field +r + Subject +r + Comment +m + ContentDescription + + Organization // must also be registered as "Organisation" + + UserAgent + + GStructured* // Base Class for structured header fields + + GAddress* // Base Class for address-type header fields + + MailboxList* // doesn't allow groups: From +r + From + + SingleMailbox* // only allows a single mailbox: Sender +r + Sender + + AddressList* // allows groups: ReplyTo, To, Cc, Bcc +r + ReplyTo +r + To +r + Cc +r + Bcc + + MailCopiesTo // ??? listed in usefor ??? +r + ReturnPath+ // allows only a single angle-addr + + GIdent* // Base Class for "Identification Fields" + + GSingleIdent* // allows only a single msg-id +r + MessageID +m + ContentID +u? + Supersedes +r + InReplyTo +r + References + + GToken* // Base Class for single-token headers: CTE +m + ContentTransferEncoding + + GDotAtom* // Base Class for single-dot-atom headers: MIME-Version +m + MIMEVersion + + GPhraseList* // Base Class for phrase list headers: Keywords +r + Keywords + + GParametrized* // Base Class for parametrizable headers: Content-type + + GContentType* +m + ContentType + + GTokenWithParameterList* +m + ContentDisposition + + + GDate* // Base class for date headers: Date +r + Date + + GNameValPairs* // Base Class for name-value list headers: Received +r + Received + + +common interface: + +bool isValid() +bool isEmpty() + +const char* type(); + +Entity* parent(); +void setParent( Content* ); + + +// +// input/output: +// + +// 7bit: +QCString as7BitString() +bool from7BitString( const QCString & ) // deep copy +bool from7BitString( const char * begin, const char * end ) // shallow +bool from7BitString( const char * begin, int length ) // shallow +bool from7BitString( QByteArray &, int pos, int len ) // shallow +QStringList validate( const QCString & str ); + + +// for dialogs: +QString asUnicodeString() +bool fromUnicodeString() +QStringList validate( const QString & str ); + +// for reader display: +QString asHtmlString() +bool fromHtmlString() // ??? + diff --git a/libkmime/DESIGN.kmime b/libkmime/DESIGN.kmime new file mode 100644 index 000000000..c949ec78a --- /dev/null +++ b/libkmime/DESIGN.kmime @@ -0,0 +1,63 @@ +NOTE: Most of this stuff is still not represented by code... + +Here are the very basic ideas on which the KMime design is built: + +1. A message is parsed into an object tree (think DOM) where the nodes +are essentially entities (container for 1 header and 1 body), headers, +bodies, and header fields. Each of these nodes can be filled +independently of each other (for composing a message or for successive +loading from an IMAP server). + +2. Each headerfield type corresponds to a single class (eg. for +Content-Type, there's KMime::Headers::ContentType), that can parse it's +specific content and can format it in html (for display). + But there is KMime::Headers::Generic for all the unimportant or +unrecognized fields. It just stores the type and the (unstructured) +content. + +3. Each body part type likewise corresponds to a single class (eg. for +text/plain, there'd be a KMime::BodyParts::Text::Plain and for +multipart/signed a KMime::BodyParts::Multipart::Signed), which can +decode the content (ie. base64, encrypted) and make it available as a +QMimeSource (so that e.g. pictures shown inlined need not be saved to +tempfiles first). + For (HTML) display, they can either output an inlined version +(text/jpeg -> <img src="cid:...">; text/plain -> <p +class="quoted">...</p>; you get the idea...) or an "attachment" version +(a small table with information about the part). + +4. Some body part classes can act as a proxy for other classes: Eg. in +the case of an attached HTML document with images, the corresponding +KMime::BodyParts::Multipart::Related would have an "effective mimetype" +of text/html and would be as good a a real Text::Html (which it holds +inside), as far as it's behavior is concerned. Likewise, a +Multipart::Signed would have the "effective mimetype" of the contained +object. Or think of Message::ExternalBody + +5. Multipart::Signed and Multipart::Encrypted would be another kind of +proxy, too: They would look at the protocol parameter of the +Content-Type header field to decide which one of (possibly more than +one) cryptographic backends to use. Those backend plugins would only +need to provide encrypt/sign/verify/decrypt services (each taking or +outputting the bodies of the two nested body parts), plus meta +information (such as the micalg parameter and for the mimetypes of the +nested body parts). + +6. There are already three plugin interfaces planned for KMime: + a. for additional headers + b. for additional body parts (mimetypes) + c. for additional crypto backends +They are planned to be simple, so that we can expect a flood of them for +any mime type you can imagine :-) Thus, they will not allow for +runtime-unloading (at least not in their first incarnation). You can +load them at runtime, but to unload them, you'd need to restart KMail. + They basically provide a factory each, which will be registered in +{field,mime,protocol} type -> factory maps. These maps are used by +KMime::Header and KMime::Entity to construct the right headerfields +resp. bodypart object for the field/body at hand. + +Eventually, KMail and KNode will also share a reader and composer, +which will be KParts, so that e.g. the reader can be used to read +mails inside konqueror. Also, both KParts can be used in PIM +applications. The long-term goal is to unify KNode and KMail, so that +they are only thin frontends to a common messaging library. diff --git a/libkmime/Makefile.am b/libkmime/Makefile.am new file mode 100644 index 000000000..c5ee7b2df --- /dev/null +++ b/libkmime/Makefile.am @@ -0,0 +1,32 @@ +INCLUDES= $(all_includes) + +lib_LTLIBRARIES = libkmime.la + +libkmime_la_SOURCES = \ + kmime_charfreq.cpp \ + kmime_util.cpp \ + kmime_mdn.cpp \ + kmime_codecs.cpp \ + kmime_codec_base64.cpp \ + kmime_codec_uuencode.cpp \ + kmime_codec_qp.cpp \ + kmime_codec_identity.cpp \ + kmime_parsers.cpp \ + kmime_header_parsing.cpp \ + kmime_content.cpp \ + kmime_headers.cpp \ + kmime_message.cpp \ + kmime_newsarticle.cpp \ + boolflags.cpp \ + kqcstringsplitter.cpp + +libkmime_la_LDFLAGS = $(all_libraries) -no-undefined -version-info 4:0:2 + +libkmime_la_LIBADD = $(LIB_KDECORE) $(LIB_POLL) + +METASOURCES = AUTO + +messages: + $(XGETTEXT) *.cpp *.h -o $(podir)/libkmime.pot + +include $(top_srcdir)/admin/Doxyfile.am diff --git a/libkmime/README b/libkmime/README new file mode 100644 index 000000000..3a8e61195 --- /dev/null +++ b/libkmime/README @@ -0,0 +1,7 @@ +Alpha version of the new KMime message handling class. +*Do not use.* Will be made into it's own library when it's finished. +Maintainers: Marc Mutz <mutz@kde.org> + Christian Gebauer + Christian Thurner +License: GPL v2 / GPL + diff --git a/libkmime/boolflags.cpp b/libkmime/boolflags.cpp new file mode 100644 index 000000000..2110acbde --- /dev/null +++ b/libkmime/boolflags.cpp @@ -0,0 +1,61 @@ +/* + boolflags.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ + +#include "boolflags.h" + +void BoolFlags::set(unsigned int i, bool b) +{ + if(i>15) return; + + unsigned char p; //bitmask + int n; + + if(i<8) { //first byte + p=(1 << i); + n=0; + } + else { //second byte + p=(1 << ( i-8 )); + n=1; + } + + if(b) + bits[n] = bits[n] | p; + else + bits[n] = bits[n] & (255-p); +} + + +bool BoolFlags::get(unsigned int i) +{ + if(i>15) return false; + + unsigned char p; //bitmask + int n; + + if(i<8) { //first byte + p=(1 << i); + n=0; + } + else { //second byte + p=(1 << i-8); + n=1; + } + + return ( (bits[n] & p)>0 ); +} + + diff --git a/libkmime/boolflags.h b/libkmime/boolflags.h new file mode 100644 index 000000000..71cf669aa --- /dev/null +++ b/libkmime/boolflags.h @@ -0,0 +1,43 @@ +/* + boolflags.h + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ + +#ifndef __KMIME_BOOLFLAGS_H__ +#define __KMIME_BOOLFLAGS_H__ + +#include <kdepimmacros.h> + +/** This class stores boolean values in single bytes. + It provides a similar functionality as QBitArray + but requires much less memory. + We use it to store the flags of an article + @internal +*/ +class KDE_EXPORT BoolFlags { + +public: + BoolFlags() { clear(); } + ~BoolFlags() {} + + void set(unsigned int i, bool b=true); + bool get(unsigned int i); + void clear() { bits[0]=0; bits[1]=0; } + unsigned char *data() { return bits; } + +protected: + unsigned char bits[2]; //space for 16 flags +}; + +#endif // __KMIME_BOOLFLAGS_H__ diff --git a/libkmime/configure.in.in b/libkmime/configure.in.in new file mode 100644 index 000000000..8277f3fdc --- /dev/null +++ b/libkmime/configure.in.in @@ -0,0 +1,23 @@ +AC_CACHE_CHECK(for timezone variable, ac_cv_var_timezone, + AC_TRY_COMPILE([ + #include <time.h> + ], [ + timezone = 1; + ], ac_cv_var_timezone=yes, ac_cv_var_timezone=no)) +if test $ac_cv_var_timezone = yes; then + AC_DEFINE(HAVE_TIMEZONE, 1, [define if you have a timezone variable]) +fi +AC_CACHE_CHECK(for tm_gmtoff in struct tm, ac_cv_struct_tm_gmtoff, + AC_TRY_COMPILE([ + #include <time.h> + ], [ + struct tm tm; + tm.tm_gmtoff = 1; + ], ac_cv_struct_tm_gmtoff=yes, ac_cv_struct_tm_gmtoff=no)) +if test $ac_cv_struct_tm_gmtoff = yes; then + AC_DEFINE(HAVE_TM_GMTOFF, 1, [Define if you have a tm_gmtoff member in struct tm]) +fi + + +AC_CHECK_FUNCS(memrchr) +AC_CHECK_FUNCS(putc_unlocked) diff --git a/libkmime/kmime_charfreq.cpp b/libkmime/kmime_charfreq.cpp new file mode 100644 index 000000000..ea3e42289 --- /dev/null +++ b/libkmime/kmime_charfreq.cpp @@ -0,0 +1,176 @@ +/* + kmime_charfreq.cpp + + KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ + +#include "kmime_charfreq.h" + +namespace KMime { + +CharFreq::CharFreq( const QByteArray & buf ) + : NUL(0), + CTL(0), + CR(0), LF(0), + CRLF(0), + printable(0), + eightBit(0), + total(0), + lineMin(0xffffffff), + lineMax(0), + mTrailingWS(false), + mLeadingFrom(false) +{ + if ( !buf.isEmpty() ) + count( buf.data(), buf.size() ); +} + +CharFreq::CharFreq( const char * buf, size_t len ) + : NUL(0), + CTL(0), + CR(0), LF(0), + CRLF(0), + printable(0), + eightBit(0), + total(0), + lineMin(0xffffffff), + lineMax(0), + mTrailingWS(false), + mLeadingFrom(false) +{ + if ( buf && len > 0 ) + count( buf, len ); +} + +static inline bool isWS( char ch ) { return ( ch == '\t' || ch == ' ' ); } + +void CharFreq::count( const char * it, size_t len ) { + + const char * end = it + len; + uint currentLineLength = 0; + // initialize the prevChar with LF so that From_ detection works w/o + // special-casing: + char prevChar = '\n'; + char prevPrevChar = 0; + + for ( ; it != end ; ++it ) { + ++currentLineLength; + switch ( *it ) { + case '\0': ++NUL; break; + case '\r': ++CR; break; + case '\n': ++LF; + if ( prevChar == '\r' ) { --currentLineLength; ++CRLF; } + if ( currentLineLength >= lineMax ) lineMax = currentLineLength-1; + if ( currentLineLength <= lineMin ) lineMin = currentLineLength-1; + if ( !mTrailingWS ) + if ( isWS( prevChar ) || ( prevChar == '\r' && isWS( prevPrevChar ) ) ) + mTrailingWS = true; + currentLineLength = 0; + break; + case 'F': // check for lines starting with From_ if not found already: + if ( !mLeadingFrom ) + if ( prevChar == '\n' && end - it >= 5 && !qstrncmp( "From ", it, 5 ) ) + mLeadingFrom = true; + ++printable; + break; + default: + { + uchar c = *it; + if ( c == '\t' || c >= ' ' && c <= '~' ) + ++printable; + else if ( c == 127 || c < ' ' ) + ++CTL; + else + ++eightBit; + } + } + prevPrevChar = prevChar; + prevChar = *it; + } + + // consider the length of the last line + if ( currentLineLength >= lineMax ) lineMax = currentLineLength; + if ( currentLineLength <= lineMin ) lineMin = currentLineLength; + + // check whether the last character is tab or space + if ( isWS( prevChar ) ) + mTrailingWS = true; + + total = len; +} + +bool CharFreq::isEightBitData() const { + return type() == EightBitData; +} + +bool CharFreq::isEightBitText() const { + return type() == EightBitText; +} + +bool CharFreq::isSevenBitData() const { + return type() == SevenBitData; +} + +bool CharFreq::isSevenBitText() const { + return type() == SevenBitText; +} + +bool CharFreq::hasTrailingWhitespace() const { + return mTrailingWS; +} + +bool CharFreq::hasLeadingFrom() const { + return mLeadingFrom; +} + +CharFreq::Type CharFreq::type() const { +#if 0 + qDebug( "Total: %d; NUL: %d; CTL: %d;\n" + "CR: %d; LF: %d; CRLF: %d;\n" + "lineMin: %d; lineMax: %d;\n" + "printable: %d; eightBit: %d;\n" + "trailing whitespace: %s;\n" + "leading 'From ': %s;\n", + total, NUL, CTL, CR, LF, CRLF, lineMin, lineMax, + printable, eightBit, + mTrailingWS ? "yes" : "no" , mLeadingFrom ? "yes" : "no" ); +#endif + if ( NUL ) // must be binary + return Binary; + + // doesn't contain NUL's: + if ( eightBit ) { + if ( lineMax > 988 ) return EightBitData; // not allowed in 8bit + if ( CR != CRLF || controlCodesRatio() > 0.2 ) return EightBitData; + return EightBitText; + } + + // doesn't contain NUL's, nor 8bit chars: + if ( lineMax > 988 ) return SevenBitData; + if ( CR != CRLF || controlCodesRatio() > 0.2 ) return SevenBitData; + + // no NUL, no 8bit chars, no excessive CTLs and no lines > 998 chars: + return SevenBitText; +} + +float CharFreq::printableRatio() const { + if ( total ) return float(printable) / float(total); + else return 0; +} + +float CharFreq::controlCodesRatio() const { + if ( total ) return float(CTL) / float(total); + else return 0; +} + +} // namespace KMime + + diff --git a/libkmime/kmime_charfreq.h b/libkmime/kmime_charfreq.h new file mode 100644 index 000000000..12d2035cf --- /dev/null +++ b/libkmime/kmime_charfreq.h @@ -0,0 +1,68 @@ +/* -*- c++ -*- + kmime_charfreq.h + + KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ +#ifndef __KMIME_CHARFREQ_H__ +#define __KMIME_CHARFREQ_H__ + +#include <qcstring.h> +#include <kdepimmacros.h> +#undef None + +namespace KMime { + +class KDE_EXPORT CharFreq { +public: + CharFreq( const QByteArray & buf ); + CharFreq( const char * buf, size_t len ); + + enum Type { None = 0, EightBitData, Binary = EightBitData, + SevenBitData, EightBitText, SevenBitText }; + + Type type() const; + bool isEightBitData() const; + bool isEightBitText() const; + bool isSevenBitData() const; + bool isSevenBitText() const; + /** Returns true if buf has trailing whitespace, i.e. if any line ends + with space (' ') or tab ('\t'). */ + bool hasTrailingWhitespace() const; + /** Returns true if buf contains a line that starts with "From ". */ + bool hasLeadingFrom() const; + /** Returns the percentage of printable characters: printable/total. + If total == 0, the result is undefined. */ + float printableRatio() const; + /** Returns the percentage of control code (CTLs): CTL/total. + If total == 0, the result is undefined. */ + float controlCodesRatio() const; + +protected: + uint NUL; // count of NUL chars + uint CTL; // count of CTLs (incl. DEL, excl. CR, LF, HT) + uint CR, LF; // count of CRs and LFs + uint CRLF; // count of LFs, preceded by CRs + uint printable; // count of printable US-ASCII chars (SPC..~) + uint eightBit; // count of other latin1 chars (those with 8th bit set) + uint total; + uint lineMin; + uint lineMax; + bool mTrailingWS; // does the buffer contain trailing whitespace? + bool mLeadingFrom; // does the buffer contain lines starting with "From "? + +private: + void count( const char * buf, size_t len ); +}; + +} // namespace KMime + +#endif /* __KMIME_CHARFREQ_H__ */ diff --git a/libkmime/kmime_codec_base64.cpp b/libkmime/kmime_codec_base64.cpp new file mode 100644 index 000000000..e14e396ab --- /dev/null +++ b/libkmime/kmime_codec_base64.cpp @@ -0,0 +1,405 @@ +/* -*- c++ -*- + kmime_codec_base64.cpp + + This file is part of KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001 Marc Mutz <mutz@kde.org> + + KMime is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMime is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this library with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "kmime_codec_base64.h" + +#include <kdebug.h> + +#include <cassert> + +using namespace KMime; + +namespace KMime { + +// codec for base64 as specified in RFC 2045 + //class Base64Codec; + //class Base64Decoder; + //class Base64Encoder; + +// codec for the B encoding as specified in RFC 2047 + //class Rfc2047BEncodingCodec; + //class Rfc2047BEncodingEncoder; + //class Rfc2047BEncodingDecoder; + + +static const uchar base64DecodeMap[128] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, + + 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, + + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64 +}; + +static const char base64EncodeMap[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' +}; + + +class Base64Decoder : public Decoder { + uint mStepNo; + uchar mOutbits; + bool mSawPadding : 1; + +protected: + friend class Base64Codec; + Base64Decoder( bool withCRLF=false ) + : Decoder( withCRLF ), mStepNo(0), mOutbits(0), + mSawPadding(false) {} + +public: + virtual ~Base64Decoder() {} + + bool decode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ); + // ### really needs no finishing??? + bool finish( char* & /*dcursor*/, const char * const /*dend*/ ) { return true; } +}; + + + +class Base64Encoder : public Encoder { + uint mStepNo; + /** number of already written base64-quartets on current line */ + uint mWrittenPacketsOnThisLine; + uchar mNextbits; + bool mInsideFinishing : 1; + +protected: + friend class Rfc2047BEncodingCodec; + friend class Rfc2047BEncodingEncoder; + friend class Base64Codec; + Base64Encoder( bool withCRLF=false ) + : Encoder( withCRLF ), mStepNo(0), mWrittenPacketsOnThisLine(0), + mNextbits(0), mInsideFinishing(false) {} + + bool generic_finish( char* & dcursor, const char * const dend, + bool withLFatEnd ); + +public: + virtual ~Base64Encoder() {} + + bool encode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ); + + bool finish( char* & dcursor, const char * const dend ); + +protected: + bool writeBase64( uchar ch, char* & dcursor, const char * const dend ) { + return write( base64EncodeMap[ ch ], dcursor, dend ); + } +}; + + + +class Rfc2047BEncodingEncoder : public Base64Encoder { +protected: + friend class Rfc2047BEncodingCodec; + Rfc2047BEncodingEncoder( bool withCRLF=false ) + : Base64Encoder( withCRLF ) {}; +public: + bool encode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ); + bool finish( char* & dcursor, const char * const dend ); +}; + + +Encoder * Base64Codec::makeEncoder( bool withCRLF ) const { + return new Base64Encoder( withCRLF ); +} + +Decoder * Base64Codec::makeDecoder( bool withCRLF ) const { + return new Base64Decoder( withCRLF ); +} + +Encoder * Rfc2047BEncodingCodec::makeEncoder( bool withCRLF ) const { + return new Rfc2047BEncodingEncoder( withCRLF ); +} + + /********************************************************/ + /********************************************************/ + /********************************************************/ + + +bool Base64Decoder::decode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ) +{ + while ( dcursor != dend && scursor != send ) { + uchar ch = *scursor++; + uchar value; + + // try converting ch to a 6-bit value: + if ( ch < 128 ) + value = base64DecodeMap[ ch ]; + else + value = 64; + + // ch isn't of the base64 alphabet, check for other significant chars: + if ( value >= 64 ) { + if ( ch == '=' ) { + // padding: + if ( mStepNo == 0 || mStepNo == 1) { + if (!mSawPadding) { + // malformed + kdWarning() << "Base64Decoder: unexpected padding " + "character in input stream" << endl; + } + mSawPadding = true; + break; + } else if ( mStepNo == 2 ) { + // ok, there should be another one + } else if ( mStepNo == 3 ) { + // ok, end of encoded stream + mSawPadding = true; + break; + } + mSawPadding = true; + mStepNo = (mStepNo + 1) % 4; + continue; + } else { + // non-base64 alphabet + continue; + } + } + + if ( mSawPadding ) { + kdWarning() << "Base64Decoder: Embedded padding character " + "encountered!" << endl; + return true; + } + + // add the new bits to the output stream and flush full octets: + switch ( mStepNo ) { + case 0: + mOutbits = value << 2; + break; + case 1: + *dcursor++ = (char)(mOutbits | value >> 4); + mOutbits = value << 4; + break; + case 2: + *dcursor++ = (char)(mOutbits | value >> 2); + mOutbits = value << 6; + break; + case 3: + *dcursor++ = (char)(mOutbits | value); + mOutbits = 0; + break; + default: + assert( 0 ); + } + mStepNo = (mStepNo + 1) % 4; + } + + // return false when caller should call us again: + return (scursor == send); +} // Base64Decoder::decode() + + + +bool Base64Encoder::encode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ) { + const uint maxPacketsPerLine = 76 / 4; + + // detect when the caller doesn't adhere to our rules: + if ( mInsideFinishing ) return true; + + while ( scursor != send && dcursor != dend ) { + // properly empty the output buffer before starting something new: + // ### fixme: we can optimize this away, since the buffer isn't + // written to anyway (most of the time) + if ( mOutputBufferCursor && !flushOutputBuffer( dcursor, dend ) ) + return (scursor == send); + + uchar ch = *scursor++; + // mNextbits // (part of) value of next sextet + + // check for line length; + if ( mStepNo == 0 && mWrittenPacketsOnThisLine >= maxPacketsPerLine ) { + writeCRLF( dcursor, dend ); + mWrittenPacketsOnThisLine = 0; + } + + // depending on mStepNo, extract value and mNextbits from the + // octet stream: + switch ( mStepNo ) { + case 0: + assert( mNextbits == 0 ); + writeBase64( ch >> 2, dcursor, dend ); // top-most 6 bits -> output + mNextbits = (ch & 0x3) << 4; // 0..1 bits -> 4..5 in mNextbits + break; + case 1: + assert( (mNextbits & ~0x30) == 0 ); + writeBase64( mNextbits | ch >> 4, dcursor, dend ); // 4..7 bits -> 0..3 in value + mNextbits = (ch & 0xf) << 2; // 0..3 bits -> 2..5 in mNextbits + break; + case 2: + assert( (mNextbits & ~0x3C) == 0 ); + writeBase64( mNextbits | ch >> 6, dcursor, dend ); // 6..7 bits -> 0..1 in value + writeBase64( ch & 0x3F, dcursor, dend ); // 0..5 bits -> output + mNextbits = 0; + mWrittenPacketsOnThisLine++; + break; + default: + assert( 0 ); + } + mStepNo = ( mStepNo + 1 ) % 3; + } + + if ( mOutputBufferCursor ) flushOutputBuffer( dcursor, dend ); + + return (scursor == send); +} + + +bool Rfc2047BEncodingEncoder::encode( const char* & scursor, + const char * const send, + char* & dcursor, + const char * const dend ) +{ + // detect when the caller doesn't adhere to our rules: + if ( mInsideFinishing ) return true; + + while ( scursor != send && dcursor != dend ) { + // properly empty the output buffer before starting something new: + // ### fixme: we can optimize this away, since the buffer isn't + // written to anyway (most of the time) + if ( mOutputBufferCursor && !flushOutputBuffer( dcursor, dend ) ) + return (scursor == send); + + uchar ch = *scursor++; + // mNextbits // (part of) value of next sextet + + // depending on mStepNo, extract value and mNextbits from the + // octet stream: + switch ( mStepNo ) { + case 0: + assert( mNextbits == 0 ); + writeBase64( ch >> 2, dcursor, dend ); // top-most 6 bits -> output + mNextbits = (ch & 0x3) << 4; // 0..1 bits -> 4..5 in mNextbits + break; + case 1: + assert( (mNextbits & ~0x30) == 0 ); + writeBase64( mNextbits | ch >> 4, dcursor, dend ); // 4..7 bits -> 0..3 in value + mNextbits = (ch & 0xf) << 2; // 0..3 bits -> 2..5 in mNextbits + break; + case 2: + assert( (mNextbits & ~0x3C) == 0 ); + writeBase64( mNextbits | ch >> 6, dcursor, dend ); // 6..7 bits -> 0..1 in value + writeBase64( ch & 0x3F, dcursor, dend ); // 0..5 bits -> output + mNextbits = 0; + break; + default: + assert( 0 ); + } + mStepNo = ( mStepNo + 1 ) % 3; + } + + if ( mOutputBufferCursor ) flushOutputBuffer( dcursor, dend ); + + return (scursor == send); +} + + +bool Base64Encoder::finish( char* & dcursor, const char * const dend ) { + return generic_finish( dcursor, dend, true ); +} + +bool Rfc2047BEncodingEncoder::finish( char* & dcursor, + const char * const dend ) { + return generic_finish( dcursor, dend, false ); +} + +bool Base64Encoder::generic_finish( char* & dcursor, const char * const dend, + bool withLFatEnd ) +{ + if ( mInsideFinishing ) + return flushOutputBuffer( dcursor, dend ); + + if ( mOutputBufferCursor && !flushOutputBuffer( dcursor, dend ) ) + return false; + + mInsideFinishing = true; + + // + // writing out the last mNextbits... + // + switch ( mStepNo ) { + case 1: // 2 mNextbits waiting to be written. Needs two padding chars: + case 2: // 4 or 6 mNextbits waiting to be written. Completes a block + writeBase64( mNextbits, dcursor, dend ); + mNextbits = 0; + break; + case 0: // no padding, nothing to be written, except possibly the CRLF + assert( mNextbits == 0 ); + break; + default: + assert( 0 ); + } + + // + // adding padding... + // + switch( mStepNo ) { + case 1: + write( '=', dcursor, dend ); + // fall through: + case 2: + write( '=', dcursor, dend ); + // fall through: + case 0: // completed an quartet - add CRLF + if ( withLFatEnd ) + writeCRLF( dcursor, dend ); + return flushOutputBuffer( dcursor, dend ); + default: + assert( 0 ); + } +return true; // asserts get compiled out +} + + + + + + +} // namespace KMime diff --git a/libkmime/kmime_codec_base64.h b/libkmime/kmime_codec_base64.h new file mode 100644 index 000000000..11fa04b73 --- /dev/null +++ b/libkmime/kmime_codec_base64.h @@ -0,0 +1,112 @@ +/* -*- c++ -*- + kmime_codec_base64.h + + This file is part of KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org> + + KMime is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMime is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this library with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KMIME_CODEC_BASE64__ +#define __KMIME_CODEC_BASE64__ + +#include "kmime_codecs.h" + +namespace KMime { + +class Base64Codec : public Codec { +protected: + friend class Codec; + Base64Codec() : Codec() {} + +public: + virtual ~Base64Codec() {} + + const char * name() const { + return "base64"; + } + + int maxEncodedSizeFor( int insize, bool withCRLF=false ) const { + // first, the total number of 4-char packets will be: + int totalNumPackets = ( insize + 2 ) / 3; + // now, after every 76/4'th packet there needs to be a linebreak: + int numLineBreaks = totalNumPackets / (76/4); + // and at the very end, too: + ++numLineBreaks; + // putting it all together, we have: + return 4 * totalNumPackets + ( withCRLF ? 2 : 1 ) * numLineBreaks; + } + + int maxDecodedSizeFor( int insize, bool withCRLF=false ) const { + // assuming all characters are part of the base64 stream (which + // does almost never hold due to required linebreaking; but + // additional non-base64 chars don't affect the output size), each + // 4-tupel of them becomes a 3-tupel in the decoded octet + // stream. So: + int result = ( ( insize + 3 ) / 4 ) * 3; + // but all of them may be \n, so + if ( withCRLF ) + result *= 2; // :-o + + return result; + } + + Encoder * makeEncoder( bool withCRLF=false ) const; + Decoder * makeDecoder( bool withCRLF=false ) const; +}; + + + +class Rfc2047BEncodingCodec : public Base64Codec { +protected: + friend class Codec; + Rfc2047BEncodingCodec() + : Base64Codec() {} + +public: + virtual ~Rfc2047BEncodingCodec() {} + + const char * name() const { return "b"; } + + int maxEncodedSizeFor( int insize, bool withCRLF=false ) const { + (void)withCRLF; // keep compiler happy + // Each (begun) 3-octet triple becomes a 4 char quartet, so: + return ( ( insize + 2 ) / 3 ) * 4; + } + + int maxDecodedSizeFor( int insize, bool withCRLF=false ) const { + (void)withCRLF; // keep compiler happy + // Each 4-char quartet becomes a 3-octet triple, the last one + // possibly even less. So: + return ( ( insize + 3 ) / 4 ) * 3; + } + + Encoder * makeEncoder( bool withCRLF=false ) const; +}; + + +} // namespace KMime + +#endif // __KMIME_CODEC_BASE64__ diff --git a/libkmime/kmime_codec_identity.cpp b/libkmime/kmime_codec_identity.cpp new file mode 100644 index 000000000..973ec360c --- /dev/null +++ b/libkmime/kmime_codec_identity.cpp @@ -0,0 +1,109 @@ +/* -*- c++ -*- + kmime_codec_identity.cpp + + This file is part of KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2004 Marc Mutz <mutz@kde.org> + + KMime is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMime is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this library with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "kmime_codec_identity.h" + +#include <kdebug.h> +#include <kglobal.h> + +#include <cassert> +#include <cstring> + +using namespace KMime; + +namespace KMime { + + +class IdentityEnDecoder : public Encoder, public Decoder { +protected: + friend class IdentityCodec; + IdentityEnDecoder( bool withCRLF ) + : Encoder( false ) + { + kdWarning( withCRLF, 5100 ) << "IdentityEnDecoder: withCRLF isn't yet supported!" << endl; + } + +public: + ~IdentityEnDecoder() {} + + bool encode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ) { + return decode( scursor, send, dcursor, dend ); + } + bool decode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ); + bool finish( char* & /*dcursor*/, const char * const /*dend*/ ) { return true; } +}; + + +Encoder * IdentityCodec::makeEncoder( bool withCRLF ) const { + return new IdentityEnDecoder( withCRLF ); +} + +Decoder * IdentityCodec::makeDecoder( bool withCRLF ) const { + return new IdentityEnDecoder( withCRLF ); +} + + + /********************************************************/ + /********************************************************/ + /********************************************************/ + + + +bool IdentityEnDecoder::decode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ) +{ + const int size = kMin( send - scursor, dcursor - dend ); + if ( size > 0 ) { + std::memmove( dcursor, scursor, size ); + dcursor += size; + scursor += size; + } + return scursor == send; +} + +QByteArray IdentityCodec::encode( const QByteArray & src, bool withCRLF ) const { + kdWarning( withCRLF, 5100 ) << "IdentityCodec::encode(): withCRLF not yet supported!" << endl; + return src; +} + +QByteArray IdentityCodec::decode( const QByteArray & src, bool withCRLF ) const { + kdWarning( withCRLF, 5100 ) << "IdentityCodec::decode(): withCRLF not yet supported!" << endl; + return src; +} + +QCString IdentityCodec::encodeToQCString( const QByteArray & src, bool withCRLF ) const { + kdWarning( withCRLF, 5100 ) << "IdentityCodec::encodeToQCString(): withCRLF not yet supported!" << endl; + return QCString( src.data(), src.size() + 1 ); +} + +} // namespace KMime diff --git a/libkmime/kmime_codec_identity.h b/libkmime/kmime_codec_identity.h new file mode 100644 index 000000000..54f5b8fdd --- /dev/null +++ b/libkmime/kmime_codec_identity.h @@ -0,0 +1,116 @@ +/* -*- c++ -*- + kmime_codec_identity.h + + This file is part of KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2004 Marc Mutz <mutz@kde.org> + + KMime is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMime is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this library with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KMIME_CODEC_IDENTITY_H__ +#define __KMIME_CODEC_IDENTITY_H__ + +#include "kmime_codecs.h" + +namespace KMime { + +class IdentityCodec : public Codec { +protected: + friend class Codec; + IdentityCodec() : Codec() {} + +public: + ~IdentityCodec() {} + + QByteArray encode( const QByteArray & src, bool withCRLF ) const; + QCString encodeToQCString( const QByteArray & src, bool withCRLF ) const; + QByteArray decode( const QByteArray & src, bool withCRLF ) const; + + int maxEncodedSizeFor( int insize, bool withCRLF ) const { + if ( withCRLF ) + return 2 * insize; + else + return insize; + } + + int maxDecodedSizeFor( int insize, bool withCRLF ) const { + if ( withCRLF ) + return 2 * insize; + else + return insize; + } + + Encoder * makeEncoder( bool withCRLF=false ) const; + Decoder * makeDecoder( bool withCRLF=false ) const; +}; + +class SevenBitCodec : public IdentityCodec { +protected: + friend class Codec; + SevenBitCodec() : IdentityCodec() {} + +public: + ~SevenBitCodec() {} + + const char * name() const { return "7bit"; } +}; + +class EightBitCodec : public IdentityCodec { +protected: + friend class Codec; + EightBitCodec() : IdentityCodec() {} + +public: + ~EightBitCodec() {} + + const char * name() const { return "8bit"; } +}; + +class BinaryCodec : public IdentityCodec { +protected: + friend class Codec; + BinaryCodec() : IdentityCodec() {} + +public: + ~BinaryCodec() {} + + const char * name() const { return "binary"; } + + int maxEncodedSizeFor( int insize, bool ) { + return insize; + } + int maxDecodedSizeFor( int insize, bool ) const { + return insize; + } + + QCString encodeToQCString( const QByteArray &, bool ) const { + return QCString(); + } + +}; + +} // namespace KMime + +#endif // __KMIME_CODEC_IDENTITY_H__ diff --git a/libkmime/kmime_codec_qp.cpp b/libkmime/kmime_codec_qp.cpp new file mode 100644 index 000000000..c867a6346 --- /dev/null +++ b/libkmime/kmime_codec_qp.cpp @@ -0,0 +1,644 @@ +/* -*- c++ -*- + kmime_codec_qp.cpp + + This file is part of KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2002 Marc Mutz <mutz@kde.org> + + KMime is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMime is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this library with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "kmime_codec_qp.h" + +#include "kmime_util.h" + +#include <kdebug.h> + +#include <cassert> + +using namespace KMime; + +namespace KMime { + +// some helpful functions: + +static inline char binToHex( uchar value ) { + if ( value > 9 ) + return value + 'A' - 10; + else + return value + '0'; +} + +static inline uchar highNibble( uchar ch ) { + return ch >> 4; +} + +static inline uchar lowNibble( uchar ch ) { + return ch & 0xF; +} + +static inline bool keep( uchar ch ) { + // no CTLs, except HT and not '?' + return !( ch < ' ' && ch != '\t' || ch == '?' ); +} + +// +// QuotedPrintableCodec +// + +class QuotedPrintableEncoder : public Encoder { + char mInputBuffer[16]; + uchar mCurrentLineLength; // 0..76 + uchar mAccu; + uint mInputBufferReadCursor : 4; // 0..15 + uint mInputBufferWriteCursor : 4; // 0..15 + enum { + Never, AtBOL, Definitely + } mAccuNeedsEncoding : 2; + bool mSawLineEnd : 1; + bool mSawCR : 1; + bool mFinishing : 1; + bool mFinished : 1; +protected: + friend class QuotedPrintableCodec; + QuotedPrintableEncoder( bool withCRLF=false ) + : Encoder( withCRLF ), mCurrentLineLength(0), mAccu(0), + mInputBufferReadCursor(0), mInputBufferWriteCursor(0), + mAccuNeedsEncoding(Never), + mSawLineEnd(false), mSawCR(false), mFinishing(false), + mFinished(false) {} + + bool needsEncoding( uchar ch ) { + return ( ch > '~' || ch < ' ' && ch != '\t' || ch == '=' ); + } + bool needsEncodingAtEOL( uchar ch ) { + return ( ch == ' ' || ch == '\t' ); + } + bool needsEncodingAtBOL( uchar ch ) { + return ( ch == 'F' || ch == '.' || ch == '-' ); + } + bool fillInputBuffer( const char* & scursor, const char * const send ); + bool processNextChar(); + void createOutputBuffer( char* & dcursor, const char * const dend ); +public: + virtual ~QuotedPrintableEncoder() {} + + bool encode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ); + + bool finish( char* & dcursor, const char * const dend ); +}; + + +class QuotedPrintableDecoder : public Decoder { + const char mEscapeChar; + char mBadChar; + /** @p accu holds the msb nibble of the hexchar or zero. */ + uchar mAccu; + /** @p insideHexChar is true iff we're inside an hexchar (=XY). + Together with @ref mAccu, we can build this states: + @li @p insideHexChar == @p false: + normal text + @li @p insideHexChar == @p true, @p mAccu == 0: + saw the leading '=' + @li @p insideHexChar == @p true, @p mAccu != 0: + saw the first nibble '=X' + */ + const bool mQEncoding; + bool mInsideHexChar; + bool mFlushing; + bool mExpectLF; + bool mHaveAccu; +protected: + friend class QuotedPrintableCodec; + friend class Rfc2047QEncodingCodec; + friend class Rfc2231EncodingCodec; + QuotedPrintableDecoder( bool withCRLF=false, + bool aQEncoding=false, char aEscapeChar='=' ) + : Decoder( withCRLF ), + mEscapeChar(aEscapeChar), + mBadChar(0), + mAccu(0), + mQEncoding(aQEncoding), + mInsideHexChar(false), + mFlushing(false), + mExpectLF(false), + mHaveAccu(false) {} +public: + virtual ~QuotedPrintableDecoder() {} + + bool decode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ); + // ### really no finishing needed??? + bool finish( char* &, const char * const ) { return true; } +}; + + +class Rfc2047QEncodingEncoder : public Encoder { + uchar mAccu; + uchar mStepNo; + const char mEscapeChar; + bool mInsideFinishing : 1; +protected: + friend class Rfc2047QEncodingCodec; + friend class Rfc2231EncodingCodec; + Rfc2047QEncodingEncoder( bool withCRLF=false, char aEscapeChar='=' ) + : Encoder( withCRLF ), + mAccu(0), mStepNo(0), mEscapeChar( aEscapeChar ), + mInsideFinishing( false ) + { + // else an optimization in ::encode might break. + assert( aEscapeChar == '=' || aEscapeChar == '%' ); + } + + // this code assumes that isEText( mEscapeChar ) == false! + bool needsEncoding( uchar ch ) { + if ( ch > 'z' ) return true; // {|}~ DEL and 8bit chars need + if ( !isEText( ch ) ) return true; // all but a-zA-Z0-9!/*+- need, too + if ( mEscapeChar == '%' && ( ch == '*' || ch == '/' ) ) + return true; // not allowed in rfc2231 encoding + return false; + } + +public: + virtual ~Rfc2047QEncodingEncoder() {} + + bool encode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ); + bool finish( char* & dcursor, const char * const dend ); +}; + +// this doesn't access any member variables, so it can be defined static +// but then we can't call it from virtual functions +static int QuotedPrintableDecoder_maxDecodedSizeFor( int insize, bool withCRLF ) { + // all chars unencoded: + int result = insize; + // but maybe all of them are \n and we need to make them \r\n :-o + if ( withCRLF ) + result += insize; + + // there might be an accu plus escape + result += 2; + + return result; +} + +Encoder * QuotedPrintableCodec::makeEncoder( bool withCRLF ) const { + return new QuotedPrintableEncoder( withCRLF ); +} + +Decoder * QuotedPrintableCodec::makeDecoder( bool withCRLF ) const { + return new QuotedPrintableDecoder( withCRLF ); +} + +int QuotedPrintableCodec::maxDecodedSizeFor( int insize, bool withCRLF ) const { + return QuotedPrintableDecoder_maxDecodedSizeFor(insize, withCRLF); +} + +Encoder * Rfc2047QEncodingCodec::makeEncoder( bool withCRLF ) const { + return new Rfc2047QEncodingEncoder( withCRLF ); +} + +Decoder * Rfc2047QEncodingCodec::makeDecoder( bool withCRLF ) const { + return new QuotedPrintableDecoder( withCRLF, true ); +} + +int Rfc2047QEncodingCodec::maxDecodedSizeFor( int insize, bool withCRLF ) const { + return QuotedPrintableDecoder_maxDecodedSizeFor(insize, withCRLF); +} + +Encoder * Rfc2231EncodingCodec::makeEncoder( bool withCRLF ) const { + return new Rfc2047QEncodingEncoder( withCRLF, '%' ); +} + +Decoder * Rfc2231EncodingCodec::makeDecoder( bool withCRLF ) const { + return new QuotedPrintableDecoder( withCRLF, true, '%' ); +} + +int Rfc2231EncodingCodec::maxDecodedSizeFor( int insize, bool withCRLF ) const { + return QuotedPrintableDecoder_maxDecodedSizeFor(insize, withCRLF); +} + + /********************************************************/ + /********************************************************/ + /********************************************************/ + +bool QuotedPrintableDecoder::decode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ) { + if ( mWithCRLF ) + kdWarning() << "CRLF output for decoders isn't yet supported!" << endl; + + while ( scursor != send && dcursor != dend ) { + if ( mFlushing ) { + // we have to flush chars in the aftermath of an decoding + // error. The way to request a flush is to + // - store the offending character in mBadChar and + // - set mFlushing to true. + // The supported cases are (H: hexchar, X: bad char): + // =X, =HX, CR + // mBadChar is only written out if it is not by itself illegal in + // quoted-printable (e.g. CTLs, 8Bits). + // A fast way to suppress mBadChar output is to set it to NUL. + if ( mInsideHexChar ) { + // output '=' + *dcursor++ = mEscapeChar; + mInsideHexChar = false; + } else if ( mHaveAccu ) { + // output the high nibble of the accumulator: + *dcursor++ = binToHex( highNibble( mAccu ) ); + mHaveAccu = false; + mAccu = 0; + } else { + // output mBadChar + assert( mAccu == 0 ); + if ( mBadChar ) { + if ( mBadChar >= '>' && mBadChar <= '~' || + mBadChar >= '!' && mBadChar <= '<' ) + *dcursor++ = mBadChar; + mBadChar = 0; + } + mFlushing = false; + } + continue; + } + assert( mBadChar == 0 ); + + uchar ch = *scursor++; + uchar value = 255; + + if ( mExpectLF && ch != '\n' ) { + kdWarning() << "QuotedPrintableDecoder: " + "illegally formed soft linebreak or lonely CR!" << endl; + mInsideHexChar = false; + mExpectLF = false; + assert( mAccu == 0 ); + } + + if ( mInsideHexChar ) { + // next char(s) represent nibble instead of itself: + if ( ch <= '9' ) { + if ( ch >= '0' ) { + value = ch - '0'; + } else { + switch ( ch ) { + case '\r': + mExpectLF = true; + break; + case '\n': + // soft line break, but only if mAccu is NUL. + if ( !mHaveAccu ) { + mExpectLF = false; + mInsideHexChar = false; + break; + } + // else fall through + default: + kdWarning() << "QuotedPrintableDecoder: " + "illegally formed hex char! Outputting verbatim." << endl; + mBadChar = ch; + mFlushing = true; + } + continue; + } + } else { // ch > '9' + if ( ch <= 'F' ) { + if ( ch >= 'A' ) { + value = 10 + ch - 'A'; + } else { // [:-@] + mBadChar = ch; + mFlushing = true; + continue; + } + } else { // ch > 'F' + if ( ch <= 'f' && ch >= 'a' ) { + value = 10 + ch - 'a'; + } else { + mBadChar = ch; + mFlushing = true; + continue; + } + } + } + + assert( value < 16 ); + assert( mBadChar == 0 ); + assert( !mExpectLF ); + + if ( mHaveAccu ) { + *dcursor++ = char( mAccu | value ); + mAccu = 0; + mHaveAccu = false; + mInsideHexChar = false; + } else { + mHaveAccu = true; + mAccu = value << 4; + } + } else { // not mInsideHexChar + if ( ch <= '~' && ch >= ' ' || ch == '\t' ) { + if ( ch == mEscapeChar ) { + mInsideHexChar = true; + } else if ( mQEncoding && ch == '_' ) { + *dcursor++ = char(0x20); + } else { + *dcursor++ = char(ch); + } + } else if ( ch == '\n' ) { + *dcursor++ = '\n'; + mExpectLF = false; + } else if ( ch == '\r' ) { + mExpectLF = true; + } else { + kdWarning() << "QuotedPrintableDecoder: " << ch << + " illegal character in input stream! Ignoring." << endl; + } + } + } + + return (scursor == send); +} + +bool QuotedPrintableEncoder::fillInputBuffer( const char* & scursor, + const char * const send ) { + // Don't read more if there's still a tail of a line in the buffer: + if ( mSawLineEnd ) + return true; + + // Read until the buffer is full or we have found CRLF or LF (which + // don't end up in the input buffer): + for ( ; ( mInputBufferWriteCursor + 1 ) % 16 != mInputBufferReadCursor + && scursor != send ; mInputBufferWriteCursor++ ) { + char ch = *scursor++; + if ( ch == '\r' ) { + mSawCR = true; + } else if ( ch == '\n' ) { + // remove the CR from the input buffer (if any) and return that + // we found a line ending: + if ( mSawCR ) { + mSawCR = false; + assert( mInputBufferWriteCursor != mInputBufferReadCursor ); + mInputBufferWriteCursor--; + } + mSawLineEnd = true; + return true; // saw CRLF or LF + } else { + mSawCR = false; + } + mInputBuffer[ mInputBufferWriteCursor ] = ch; + } + mSawLineEnd = false; + return false; // didn't see a line ending... +} + +bool QuotedPrintableEncoder::processNextChar() { + + // If we process a buffer which doesn't end in a line break, we + // can't process all of it, since the next chars that will be read + // could be a line break. So we empty the buffer only until a fixed + // number of chars is left (except when mFinishing, which means that + // the data doesn't end in newline): + const int minBufferFillWithoutLineEnd = 4; + + assert( mOutputBufferCursor == 0 ); + + int bufferFill = int(mInputBufferWriteCursor) - int(mInputBufferReadCursor) ; + if ( bufferFill < 0 ) + bufferFill += 16; + + assert( bufferFill >=0 && bufferFill <= 15 ); + + if ( !mFinishing && !mSawLineEnd && + bufferFill < minBufferFillWithoutLineEnd ) + return false; + + // buffer is empty, return false: + if ( mInputBufferReadCursor == mInputBufferWriteCursor ) + return false; + + // Real processing goes here: + mAccu = mInputBuffer[ mInputBufferReadCursor++ ]; + if ( needsEncoding( mAccu ) ) // always needs encoding or + mAccuNeedsEncoding = Definitely; + else if ( ( mSawLineEnd || mFinishing ) // needs encoding at end of line + && bufferFill == 1 // or end of buffer + && needsEncodingAtEOL( mAccu ) ) + mAccuNeedsEncoding = Definitely; + else if ( needsEncodingAtBOL( mAccu ) ) + mAccuNeedsEncoding = AtBOL; + else + // never needs encoding + mAccuNeedsEncoding = Never; + + return true; +} + +// Outputs processed (verbatim or hex-encoded) chars and inserts soft +// line breaks as necessary. Depends on processNextChar's directions +// on whether or not to encode the current char, and whether or not +// the current char is the last one in it's input line: +void QuotedPrintableEncoder::createOutputBuffer( char* & dcursor, + const char * const dend ) +{ + const int maxLineLength = 76; // rfc 2045 + + assert( mOutputBufferCursor == 0 ); + + bool lastOneOnThisLine = mSawLineEnd + && mInputBufferReadCursor == mInputBufferWriteCursor; + + int neededSpace = 1; + if ( mAccuNeedsEncoding == Definitely) + neededSpace = 3; + + // reserve space for the soft hyphen (=) + if ( !lastOneOnThisLine ) + neededSpace++; + + if ( mCurrentLineLength > maxLineLength - neededSpace ) { + // current line too short, insert soft line break: + write( '=', dcursor, dend ); + writeCRLF( dcursor, dend ); + mCurrentLineLength = 0; + } + + if ( Never == mAccuNeedsEncoding || + AtBOL == mAccuNeedsEncoding && mCurrentLineLength != 0 ) { + write( mAccu, dcursor, dend ); + mCurrentLineLength++; + } else { + write( '=', dcursor, dend ); + write( binToHex( highNibble( mAccu ) ), dcursor, dend ); + write( binToHex( lowNibble( mAccu ) ), dcursor, dend ); + mCurrentLineLength += 3; + } +} + + +bool QuotedPrintableEncoder::encode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ) +{ + // support probing by the caller: + if ( mFinishing ) return true; + + while ( scursor != send && dcursor != dend ) { + if ( mOutputBufferCursor && !flushOutputBuffer( dcursor, dend ) ) + return (scursor == send); + + assert( mOutputBufferCursor == 0 ); + + // fill input buffer until eol has been reached or until the + // buffer is full, whatever comes first: + fillInputBuffer( scursor, send ); + + if ( processNextChar() ) + // there was one... + createOutputBuffer( dcursor, dend ); + else if ( mSawLineEnd && + mInputBufferWriteCursor == mInputBufferReadCursor ) { + // load a hard line break into output buffer: + writeCRLF( dcursor, dend ); + // signal fillInputBuffer() we are ready for the next line: + mSawLineEnd = false; + mCurrentLineLength = 0; + } else + // we are supposedly finished with this input block: + break; + } + + // make sure we write as much as possible and don't stop _writing_ + // just because we have no more _input_: + if ( mOutputBufferCursor ) flushOutputBuffer( dcursor, dend ); + + return (scursor == send); + +} // encode + +bool QuotedPrintableEncoder::finish( char* & dcursor, + const char * const dend ) { + mFinishing = true; + + if ( mFinished ) + return flushOutputBuffer( dcursor, dend ); + + while ( dcursor != dend ) { + if ( mOutputBufferCursor && !flushOutputBuffer( dcursor, dend ) ) + return false; + + assert( mOutputBufferCursor == 0 ); + + if ( processNextChar() ) + // there was one... + createOutputBuffer( dcursor, dend ); + else if ( mSawLineEnd && + mInputBufferWriteCursor == mInputBufferReadCursor ) { + // load a hard line break into output buffer: + writeCRLF( dcursor, dend ); + mSawLineEnd = false; + mCurrentLineLength = 0; + } else { + mFinished = true; + return flushOutputBuffer( dcursor, dend ); + } + } + + return mFinished && !mOutputBufferCursor; + +} // finish + + +bool Rfc2047QEncodingEncoder::encode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ) +{ + if ( mInsideFinishing ) return true; + + while ( scursor != send && dcursor != dend ) { + uchar value; + switch ( mStepNo ) { + case 0: + // read the next char and decide if and how do encode: + mAccu = *scursor++; + if ( !needsEncoding( mAccu ) ) { + *dcursor++ = char(mAccu); + } else if ( mEscapeChar == '=' && mAccu == 0x20 ) { + // shortcut encoding for 0x20 (latin-1/us-ascii SPACE) + // (not for rfc2231 encoding) + *dcursor++ = '_'; + } else { + // needs =XY encoding - write escape char: + *dcursor++ = mEscapeChar; + mStepNo = 1; + } + continue; + case 1: + // extract hi-nibble: + value = highNibble(mAccu); + mStepNo = 2; + break; + case 2: + // extract lo-nibble: + value = lowNibble(mAccu); + mStepNo = 0; + break; + default: assert( 0 ); + } + + // and write: + *dcursor++ = binToHex( value ); + } + + return (scursor == send); +} // encode + +#include <qstring.h> + +bool Rfc2047QEncodingEncoder::finish( char* & dcursor, const char * const dend ) { + mInsideFinishing = true; + + // write the last bits of mAccu, if any: + while ( mStepNo != 0 && dcursor != dend ) { + uchar value; + switch ( mStepNo ) { + case 1: + // extract hi-nibble: + value = highNibble(mAccu); + mStepNo = 2; + break; + case 2: + // extract lo-nibble: + value = lowNibble(mAccu); + mStepNo = 0; + break; + default: assert( 0 ); + } + + // and write: + *dcursor++ = binToHex( value ); + } + + return mStepNo == 0; +} + + + + +} // namespace KMime diff --git a/libkmime/kmime_codec_qp.h b/libkmime/kmime_codec_qp.h new file mode 100644 index 000000000..c067fcffe --- /dev/null +++ b/libkmime/kmime_codec_qp.h @@ -0,0 +1,121 @@ +/* -*- c++ -*- + kmime_codec_qp.h + + This file is part of KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org> + + KMime is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMime is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this library with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KMIME_CODEC_QP__ +#define __KMIME_CODEC_QP__ + +#include "kmime_codecs.h" + +namespace KMime { + + +class QuotedPrintableCodec : public Codec { +protected: + friend class Codec; + QuotedPrintableCodec() : Codec() {} + +public: + virtual ~QuotedPrintableCodec() {} + + const char * name() const { + return "quoted-printable"; + } + + int maxEncodedSizeFor( int insize, bool withCRLF=false ) const { + // all chars encoded: + int result = 3*insize; + // then after 25 hexchars comes a soft linebreak: =(\r)\n + result += (withCRLF ? 3 : 2) * (insize/25); + + return result; + } + + int maxDecodedSizeFor( int insize, bool withCRLF=false ) const; + + Encoder * makeEncoder( bool withCRLF=false ) const; + Decoder * makeDecoder( bool withCRLF=false ) const; +}; + + +class Rfc2047QEncodingCodec : public Codec { +protected: + friend class Codec; + Rfc2047QEncodingCodec() : Codec() {} + +public: + virtual ~Rfc2047QEncodingCodec() {} + + const char * name() const { + return "q"; + } + + int maxEncodedSizeFor( int insize, bool withCRLF=false ) const { + (void)withCRLF; // keep compiler happy + // this one is simple: We don't do linebreaking, so all that can + // happen is that every char needs encoding, so: + return 3*insize; + } + + int maxDecodedSizeFor( int insize, bool withCRLF=false ) const; + + Encoder * makeEncoder( bool withCRLF=false ) const; + Decoder * makeDecoder( bool withCRLF=false ) const; +}; + + +class Rfc2231EncodingCodec : public Codec { +protected: + friend class Codec; + Rfc2231EncodingCodec() : Codec() {} + +public: + virtual ~Rfc2231EncodingCodec() {} + + const char * name() const { + return "x-kmime-rfc2231"; + } + + int maxEncodedSizeFor( int insize, bool withCRLF=false ) const { + (void)withCRLF; // keep compiler happy + // same as for "q" encoding: + return 3*insize; + } + + int maxDecodedSizeFor( int insize, bool withCRLF=false ) const; + + Encoder * makeEncoder( bool withCRLF=false ) const; + Decoder * makeDecoder( bool withCRLF=false ) const; +}; + + +} // namespace KMime + +#endif // __KMIME_CODEC_QP__ diff --git a/libkmime/kmime_codec_uuencode.cpp b/libkmime/kmime_codec_uuencode.cpp new file mode 100644 index 000000000..2c6152a95 --- /dev/null +++ b/libkmime/kmime_codec_uuencode.cpp @@ -0,0 +1,240 @@ +/* -*- c++ -*- + kmime_codec_uuencode.cpp + + This file is part of KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2002 Marc Mutz <mutz@kde.org> + + KMime is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMime is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this library with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "kmime_codec_uuencode.h" + +#include <kdebug.h> + +#include <cassert> + +using namespace KMime; + +namespace KMime { + + +class UUDecoder : public Decoder { + uint mStepNo; + uchar mAnnouncedOctetCount; // (on current line) + uchar mCurrentOctetCount; // (on current line) + uchar mOutbits; + bool mLastWasCRLF : 1; + bool mSawBegin : 1; // whether we already saw ^begin... + uint mIntoBeginLine : 3; // count #chars we compared against "begin" 0..5 + bool mSawEnd : 1; // whether we already saw ^end... + uint mIntoEndLine : 2; // count #chars we compared against "end" 0..3 + + void searchForBegin( const char* & scursor, const char * const send ); + +protected: + friend class UUCodec; + UUDecoder( bool withCRLF=false ) + : Decoder( withCRLF ), mStepNo(0), + mAnnouncedOctetCount(0), mCurrentOctetCount(0), + mOutbits(0), mLastWasCRLF(true), + mSawBegin(false), mIntoBeginLine(0), + mSawEnd(false), mIntoEndLine(0) {} + +public: + virtual ~UUDecoder() {} + + bool decode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ); + // ### really needs no finishing??? + bool finish( char* & /*dcursor*/, const char * const /*dend*/ ) { return true; } +}; + + + +Encoder * UUCodec::makeEncoder( bool ) const { + return 0; // encoding not supported +} + +Decoder * UUCodec::makeDecoder( bool withCRLF ) const { + return new UUDecoder( withCRLF ); +} + + + /********************************************************/ + /********************************************************/ + /********************************************************/ + + + +void UUDecoder::searchForBegin( const char* & scursor, const char * const send ) { + static const char begin[] = "begin\n"; + static const uint beginLength = 5; // sic! + + assert( !mSawBegin || mIntoBeginLine > 0 ); + + while ( scursor != send ) { + uchar ch = *scursor++; + if ( ch == begin[mIntoBeginLine] ) { + if ( mIntoBeginLine < beginLength ) { + // found another char + ++mIntoBeginLine; + if ( mIntoBeginLine == beginLength ) + mSawBegin = true; // "begin" complete, now search the next \n... + } else /* mIntoBeginLine == beginLength */ { + // found '\n': begin line complete + mLastWasCRLF = true; + mIntoBeginLine = 0; + return; + } + } else if ( mSawBegin ) { + // OK, skip stuff until the next \n + } else { + kdWarning() << "UUDecoder: garbage before \"begin\", resetting parser" + << endl; + mIntoBeginLine = 0; + } + } + +} + + +// uuencoding just shifts all 6-bit octets by 32 (SP/' '), except NUL, +// which gets mapped to 0x60 +static inline uchar uuDecode( uchar c ) { + return ( c - ' ' ) // undo shift and + & 0x3F; // map 0x40 (0x60-' ') to 0... +} + + +bool UUDecoder::decode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ) +{ + // First, check whether we still need to find the "begin" line: + if ( !mSawBegin || mIntoBeginLine != 0 ) + searchForBegin( scursor, send ); + // or if we are past the end line: + else if ( mSawEnd ) { + scursor = send; // do nothing anymore... + return true; + } + + while ( dcursor != dend && scursor != send ) { + uchar ch = *scursor++; + uchar value; + + // Check whether we need to look for the "end" line: + if ( mIntoEndLine > 0 ) { + static const char end[] = "end"; + static const uint endLength = 3; + + if ( ch == end[mIntoEndLine] ) { + ++mIntoEndLine; + if ( mIntoEndLine == endLength ) { + mSawEnd = true; + scursor = send; // shortcut to the end + return true; + } + continue; + } else { + kdWarning() << "UUDecoder: invalid line octet count looks like \"end\" (mIntoEndLine = " << mIntoEndLine << " )!" << endl; + mIntoEndLine = 0; + // fall through... + } + } + + // Normal parsing: + + // The first char of a line is an encoding of the length of the + // current line. We simply ignore it: + if ( mLastWasCRLF ) { + // reset char-per-line counter: + mLastWasCRLF = false; + mCurrentOctetCount = 0; + + // try to decode the chars-on-this-line announcement: + if ( ch == 'e' ) // maybe the beginning of the "end"? ;-) + mIntoEndLine = 1; + else if ( ch > 0x60 ) + {} // ### invalid line length char: what shall we do?? + else if ( ch > ' ' ) + mAnnouncedOctetCount = uuDecode( ch ); + else if ( ch == '\n' ) + mLastWasCRLF = true; // oops, empty line + + continue; + } + + // try converting ch to a 6-bit value: + if ( ch > 0x60 ) + continue; // invalid char + else if ( ch > ' ' ) + value = uuDecode( ch ); + else if ( ch == '\n' ) { // line end + mLastWasCRLF = true; + continue; + } else + continue; + + // add the new bits to the output stream and flush full octets: + switch ( mStepNo ) { + case 0: + mOutbits = value << 2; + break; + case 1: + if ( mCurrentOctetCount < mAnnouncedOctetCount ) + *dcursor++ = (char)(mOutbits | value >> 4); + ++mCurrentOctetCount; + mOutbits = value << 4; + break; + case 2: + if ( mCurrentOctetCount < mAnnouncedOctetCount ) + *dcursor++ = (char)(mOutbits | value >> 2); + ++mCurrentOctetCount; + mOutbits = value << 6; + break; + case 3: + if ( mCurrentOctetCount < mAnnouncedOctetCount ) + *dcursor++ = (char)(mOutbits | value); + ++mCurrentOctetCount; + mOutbits = 0; + break; + default: + assert( 0 ); + } + mStepNo = (mStepNo + 1) % 4; + + // check whether we ran over the announced octet count for this line: + kdWarning( mCurrentOctetCount == mAnnouncedOctetCount + 1 ) + << "UUDecoder: mismatch between announced (" + << mAnnouncedOctetCount << ") and actual line octet count!" << endl; + + } + + // return false when caller should call us again: + return (scursor == send); +} // UUDecoder::decode() + + +} // namespace KMime diff --git a/libkmime/kmime_codec_uuencode.h b/libkmime/kmime_codec_uuencode.h new file mode 100644 index 000000000..4bccec391 --- /dev/null +++ b/libkmime/kmime_codec_uuencode.h @@ -0,0 +1,76 @@ +/* -*- c++ -*- + kmime_codec_uuencode.h + + This file is part of KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2002 Marc Mutz <mutz@kde.org> + + KMime is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMime is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this library with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KMIME_CODEC_UUENCODE_H__ +#define __KMIME_CODEC_UUENCODE_H__ + +#include "kmime_codecs.h" + +namespace KMime { + +class UUCodec : public Codec { +protected: + friend class Codec; + UUCodec() : Codec() {} + +public: + virtual ~UUCodec() {} + + const char * name() const { + return "x-uuencode"; + } + + int maxEncodedSizeFor( int insize, bool withCRLF=false ) const { + (void)withCRLF; + return insize; // we have no encoder! + } + + int maxDecodedSizeFor( int insize, bool withCRLF=false ) const { + // assuming all characters are part of the uuencode stream (which + // does almost never hold due to required linebreaking; but + // additional non-uu chars don't affect the output size), each + // 4-tupel of them becomes a 3-tupel in the decoded octet + // stream. So: + int result = ( ( insize + 3 ) / 4 ) * 3; + // but all of them may be \n, so + if ( withCRLF ) + result *= 2; // :-o + + return result; + } + + Encoder * makeEncoder( bool withCRLF=false ) const; + Decoder * makeDecoder( bool withCRLF=false ) const; +}; + +} // namespace KMime + +#endif // __KMIME_CODEC_UUENCODE_H__ diff --git a/libkmime/kmime_codecs.cpp b/libkmime/kmime_codecs.cpp new file mode 100644 index 000000000..fdb385e58 --- /dev/null +++ b/libkmime/kmime_codecs.cpp @@ -0,0 +1,241 @@ +/* -*- c++ -*- + kmime_codecs.cpp + + This file is part of KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org> + + KMime is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMime is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this library with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "kmime_codecs.h" +#include "kmime_util.h" + +#include "kmime_codec_base64.h" +#include "kmime_codec_qp.h" +#include "kmime_codec_uuencode.h" +#include "kmime_codec_identity.h" + +#include <kdebug.h> + +#include <qcstring.h> +#include <kstaticdeleter.h> + +#include <cassert> +#include <cstring> + +using namespace KMime; + +namespace KMime { + +// global list of KMime::Codec's +QAsciiDict<Codec>* Codec::all = 0; +static KStaticDeleter<QAsciiDict<Codec> > sdAll; +#if defined(QT_THREAD_SUPPORT) +QMutex* Codec::dictLock = 0; +static KStaticDeleter<QMutex> sdDictLock; +#endif + +void Codec::fillDictionary() { + + all->setAutoDelete(true); + + //all->insert( "7bit", new SevenBitCodec() ); + //all->insert( "8bit", new EightBitCodec() ); + all->insert( "base64", new Base64Codec() ); + all->insert( "quoted-printable", new QuotedPrintableCodec() ); + all->insert( "b", new Rfc2047BEncodingCodec() ); + all->insert( "q", new Rfc2047QEncodingCodec() ); + all->insert( "x-kmime-rfc2231", new Rfc2231EncodingCodec() ); + all->insert( "x-uuencode", new UUCodec() ); + //all->insert( "binary", new BinaryCodec() ); + +} + +Codec * Codec::codecForName( const char * name ) { +#if defined(QT_THREAD_SUPPORT) + if ( !dictLock ) + sdDictLock.setObject( dictLock, new QMutex ); + dictLock->lock(); // protect "all" +#endif + if ( !all ) { + sdAll.setObject( all, new QAsciiDict<Codec>( 11, false /* case-insensitive */) ); + fillDictionary(); + } + Codec * codec = (*all)[ name ]; +#if defined(QT_THREAD_SUPPORT) + dictLock->unlock(); +#endif + + if ( !codec ) + kdDebug() << "Unknown codec \"" << name << "\" requested!" << endl; + + return codec; +} + +Codec * Codec::codecForName( const QCString & name ) { + return codecForName( name.data() ); +} + +bool Codec::encode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend, + bool withCRLF ) const +{ + // get an encoder: + Encoder * enc = makeEncoder( withCRLF ); + assert( enc ); + + // encode and check for output buffer overflow: + while ( !enc->encode( scursor, send, dcursor, dend ) ) + if ( dcursor == dend ) { + delete enc; + return false; // not enough space in output buffer + } + + // finish and check for output buffer overflow: + while ( !enc->finish( dcursor, dend ) ) + if ( dcursor == dend ) { + delete enc; + return false; // not enough space in output buffer + } + + // cleanup and return: + delete enc; + return true; // successfully encoded. +} + +QByteArray Codec::encode( const QByteArray & src, bool withCRLF ) const +{ + // allocate buffer for the worst case: + QByteArray result( maxEncodedSizeFor( src.size(), withCRLF ) ); + + // set up iterators: + QByteArray::ConstIterator iit = src.begin(); + QByteArray::ConstIterator iend = src.end(); + QByteArray::Iterator oit = result.begin(); + QByteArray::ConstIterator oend = result.end(); + + // encode + if ( !encode( iit, iend, oit, oend, withCRLF ) ) + kdFatal() << name() << " codec lies about it's mEncodedSizeFor()" + << endl; + + // shrink result to actual size: + result.truncate( oit - result.begin() ); + + return result; +} + +QCString Codec::encodeToQCString( const QByteArray & src, bool withCRLF ) const +{ + // allocate buffer for the worst case (remember to add one for the trailing NUL) + QCString result( maxEncodedSizeFor( src.size(), withCRLF ) + 1 ); + + // set up iterators: + QByteArray::ConstIterator iit = src.begin(); + QByteArray::ConstIterator iend = src.end(); + QByteArray::Iterator oit = result.begin(); + QByteArray::ConstIterator oend = result.end() - 1; + + // encode + if ( !encode( iit, iend, oit, oend, withCRLF ) ) + kdFatal() << name() << " codec lies about it's mEncodedSizeFor()" + << endl; + + // shrink result to actual size: + result.truncate( oit - result.begin() ); + + return result; +} + +QByteArray Codec::decode( const QByteArray & src, bool withCRLF ) const +{ + // allocate buffer for the worst case: + QByteArray result( maxDecodedSizeFor( src.size(), withCRLF ) ); + + // set up iterators: + QByteArray::ConstIterator iit = src.begin(); + QByteArray::ConstIterator iend = src.end(); + QByteArray::Iterator oit = result.begin(); + QByteArray::ConstIterator oend = result.end(); + + // decode + if ( !decode( iit, iend, oit, oend, withCRLF ) ) + kdFatal() << name() << " codec lies about it's maxDecodedSizeFor()" + << endl; + + // shrink result to actual size: + result.truncate( oit - result.begin() ); + + return result; +} + +bool Codec::decode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend, + bool withCRLF ) const +{ + // get a decoder: + Decoder * dec = makeDecoder( withCRLF ); + assert( dec ); + + // decode and check for output buffer overflow: + while ( !dec->decode( scursor, send, dcursor, dend ) ) + if ( dcursor == dend ) { + delete dec; + return false; // not enough space in output buffer + } + + // finish and check for output buffer overflow: + while ( !dec->finish( dcursor, dend ) ) + if ( dcursor == dend ) { + delete dec; + return false; // not enough space in output buffer + } + + // cleanup and return: + delete dec; + return true; // successfully encoded. +} + +// write as much as possible off the output buffer. Return true if +// flushing was complete, false if some chars could not be flushed. +bool Encoder::flushOutputBuffer( char* & dcursor, const char * const dend ) { + int i; + // copy output buffer to output stream: + for ( i = 0 ; dcursor != dend && i < mOutputBufferCursor ; ++i ) + *dcursor++ = mOutputBuffer[i]; + + // calculate the number of missing chars: + int numCharsLeft = mOutputBufferCursor - i; + // push the remaining chars to the begin of the buffer: + if ( numCharsLeft ) + qmemmove( mOutputBuffer, mOutputBuffer + i, numCharsLeft ); + // adjust cursor: + mOutputBufferCursor = numCharsLeft; + + return !numCharsLeft; +} + + +} // namespace KMime diff --git a/libkmime/kmime_codecs.h b/libkmime/kmime_codecs.h new file mode 100644 index 000000000..df2c6a560 --- /dev/null +++ b/libkmime/kmime_codecs.h @@ -0,0 +1,367 @@ +/* -*- c++ -*- + kmime_codecs.h + + This file is part of KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org> + + KMime is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMime is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this library with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KMIME_CODECS__ +#define __KMIME_CODECS__ + +#include <qasciidict.h> +#if defined(QT_THREAD_SUPPORT) +# include <qmutex.h> +#endif + +#include <qcstring.h> // QByteArray + +#include <kdebug.h> // for kdFatal() +#include <kdepimmacros.h> + +namespace KMime { + +// forward declarations: +class Encoder; +class Decoder; + +/** Abstract base class of codecs like base64 and + quoted-printable. It's a singleton. + + @short Codecs for common mail transfer encodings. + @author Marc Mutz <mutz@kde.org> +*/ +class KDE_EXPORT Codec { +protected: + + static QAsciiDict<Codec>* all; +#if defined(QT_THREAD_SUPPORT) + static QMutex* dictLock; +#endif + + Codec() {} +private: + static void fillDictionary(); + +public: + static Codec * codecForName( const char * name ); + static Codec * codecForName( const QCString & name ); + + virtual int maxEncodedSizeFor( int insize, bool withCRLF=false ) const = 0; + virtual int maxDecodedSizeFor( int insize, bool withCRLF=false ) const = 0; + + virtual Encoder * makeEncoder( bool withCRLF=false ) const = 0; + virtual Decoder * makeDecoder( bool withCRLF=false ) const = 0; + + /** + * Convenience wrapper that can be used for small chunks of data + * when you can provide a large enough buffer. The default + * implementation creates an Encoder and uses it. + * + * Encodes a chunk of bytes starting at @p scursor and extending to + * @p send into the buffer described by @p dcursor and @p dend. + * + * This function doesn't support chaining of blocks. The returned + * block cannot be added to, but you don't need to finalize it, too. + * + * Example usage (@p in contains the input data): + * <pre> + * KMime::Codec * codec = KMime::Codec::codecForName( "base64" ); + * kdFatal( !codec ) << "no base64 codec found!?" << endl; + * QByteArray out( in.size()*1.4 ); // crude maximal size of b64 encoding + * QByteArray::Iterator iit = in.begin(); + * QByteArray::Iterator oit = out.begin(); + * if ( !codec->encode( iit, in.end(), oit, out.end() ) ) { + * kdDebug() << "output buffer too small" << endl; + * return; + * } + * kdDebug() << "Size of encoded data: " << oit - out.begin() << endl; + * </pre> + * + * @param scursor/send begin and end of input buffer + * @param dcursor/dend begin and end of output buffer + * @param withCRLF If true, make the lineends CRLF, else make them LF only. + * + * @return false if the encoded data didn't fit into the output + * buffer. + **/ + virtual bool encode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend, + bool withCRLF=false ) const; + + /** + * Convenience wrapper that can be used for small chunks of data + * when you can provide a large enough buffer. The default + * implementation creates a Decoder and uses it. + * + * Decodes a chunk of bytes starting at @p scursor and extending to + * @p send into the buffer described by @p dcursor and @p dend. + * + * This function doesn't support chaining of blocks. The returned + * block cannot be added to, but you don't need to finalize it, too. + * + * Example usage (@p in contains the input data): + * <pre> + * KMime::Codec * codec = KMime::Codec::codecForName( "base64" ); + * kdFatal( !codec ) << "no base64 codec found!?" << endl; + * QByteArray out( in.size() ); // good guess for any encoding... + * QByteArray::Iterator iit = in.begin(); + * QByteArray::Iterator oit = out.begin(); + * if ( !codec->decode( iit, in.end(), oit, out.end() ) ) { + * kdDebug() << "output buffer too small" << endl; + * return; + * } + * kdDebug() << "Size of decoded data: " << oit - out.begin() << endl; + * </pre> + * + * @param scursor/send begin and end of input buffer + * @param dcursor/dend begin and end of output buffer + * @param withCRLF If true, make the lineends CRLF, else make them LF only. + * + * @return false if the decoded data didn't fit into the output + * buffer. + **/ + virtual bool decode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend, + bool withCRLF=false ) const; + + /** + * Even more convenient, but also a bit slower and more memory + * intensive, since it allocates storage for the worst case and then + * shrinks the result QByteArray to the actual size again. + * + * For use with small @p src. + **/ + virtual QByteArray encode( const QByteArray & src, bool withCRLF=false ) const; + + /** + * Even more convenient, but also a bit slower and more memory + * intensive, since it allocates storage for the worst case and then + * shrinks the result QCString to the actual size again. + * + * For use with small @p src. + * + * This method only works for codecs whose output is in the 8bit + * domain (ie. not in the binary domain). Codecs that do not fall + * into this category will return a null QCString. + **/ + virtual QCString encodeToQCString( const QByteArray & src, bool withCRLF=false ) const; + + /** + * Even more convenient, but also a bit slower and more memory + * intensive, since it allocates storage for the worst case and then + * shrinks the result QByteArray to the actual size again. + * + * For use with small @p src. + **/ + virtual QByteArray decode( const QByteArray & src, bool withCRLF=false ) const; + + /** + * @return the name of the encoding. Guaranteed to be lowercase. + */ + virtual const char * name() const = 0; + + virtual ~Codec() {} + +}; + +/** + * Stateful decoder class, modelled after QTextDecoder. + * + * @section Overview + * + * KMime decoders are designed to be able to process encoded data in + * chunks of arbitrary size and to work with output buffers of also + * arbitrary size. They maintain any state necessary to go on where + * the previous call left off. + * + * The class consists of only two methods of interest: see decode, + * which decodes an input block and finalize, which flushes any + * remaining data to the output stream. + * + * Typically, you will create a decoder instance, call decode as + * often as necessary, then call finalize (most often a single + * call suffices, but it might be that during that call the output + * buffer is filled, so you should be prepared to call finalize + * as often as necessary, ie. until it returns @p true). + * + * @section Return Values + * + * Both methods return @p true to indicate that they've finished their + * job. For decode, a return value of @p true means that the + * current input block has been finished (@p false most often means + * that the output buffer is full, but that isn't required + * behavior. The decode call is free to return at arbitrary + * times during processing). + * + * For finalize, a return value of @p true means that all data + * implicitly or explicitly stored in the decoder instance has been + * flushed to the output buffer. A @p false return value should be + * interpreted as "check if the output buffer is full and call me + * again", just as with decode. + * + * @section Usage Pattern + * + * Since the decoder maintains state, you can only use it once. After + * a sequence of input blocks has been processed, you finalize + * the output and then delete the decoder instance. If you want to + * process another input block sequence, you create a new instance. + * + * Typical usage (@p in contains the (base64-encoded) input data), + * taking into account all the conventions detailed above: + * + * <pre> + * KMime::Codec * codec = KMime::Codec::codecForName( "base64" ); + * kdFatal( !codec ) << "No codec found for base64!" << endl; + * KMime::Decoder * dec = codec->makeDecoder(); + * assert( dec ); // should not happen + * QByteArray out( 256 ); // small buffer is enough ;-) + * QByteArray::Iterator iit = in.begin(); + * QByteArray::Iterator oit = out.begin(); + * // decode the chunk + * while ( !dec->decode( iit, in.end(), oit, out.end() ) ) + * if ( oit == out.end() ) { // output buffer full, process contents + * do_something_with( out ); + * oit = out.begin(); + * } + * // repeat while loop for each input block + * // ... + * // finish (flush remaining data from decoder): + * while ( !dec->finish( oit, out.end() ) ) + * if ( oit == out.end() ) { // output buffer full, process contents + * do_something_with( out ); + * oit = out.begin(); + * } + * // now process last chunk: + * out.resize( oit - out.begin() ); + * do_something_with( out ); + * // _delete_ the decoder, but not the codec: + * delete dec; + * </pre> + * + * @short Stateful CTE decoder class + * @author Marc Mutz <mutz@kde.org> + **/ +class Decoder { +protected: + friend class Codec; + /** + * Protected constructor. Use KMime::Codec::makeDecoder to + * create an instance. The bool parameter determines whether lines + * end with CRLF (true) or LF (false, default). + **/ + Decoder( bool withCRLF=false ) + : mWithCRLF( withCRLF ) {} +public: + virtual ~Decoder() {} + + /** Decode a chunk of data, maintaining state information between + * calls. See class decumentation for calling conventions. + **/ + virtual bool decode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ) = 0; + /** Call this method to finalize the output stream. Writes all + * remaining data and resets the decoder. See KMime::Codec for + * calling conventions. + **/ + virtual bool finish( char* & dcursor, const char * const dend ) = 0; + +protected: + const bool mWithCRLF; +}; + +/** Stateful encoder class, modelled after QTextEncoder. + @short Stateful encoder class + @author Marc Mutz <mutz@kde.org> +*/ +class Encoder { +protected: + friend class Codec; + /** Protected constructor. Use KMime::Codec::makeEncoder if you + want one. The bool parameter determines whether lines end with + CRLF (true) or LF (false, default). */ + Encoder( bool withCRLF=false ) + : mOutputBufferCursor( 0 ), mWithCRLF( withCRLF ) {} +public: + virtual ~Encoder() {} + + /** Encode a chunk of data, maintaining state information between + calls. See KMime::Codec for calling conventions. */ + virtual bool encode( const char* & scursor, const char * const send, + char* & dcursor, const char * const dend ) = 0; + + /** Call this method to finalize the output stream. Writes all + remaining data and resets the encoder. See KMime::Codec for + calling conventions. */ + virtual bool finish( char* & dcursor, const char * const dend ) = 0; + +protected: + /** Space in the output buffer */ + enum { maxBufferedChars = 8 }; + + /** Writes @p ch to the output stream or the output buffer, + depending on whether or not the output stream has space left. + @return true if written to the output stream, false if buffered. */ + bool write( char ch, char* & dcursor, const char * const dend ) { + if ( dcursor != dend ) { + // if there's space in the output stream, write there: + *dcursor++ = ch; + return true; + } else { + // else buffer the output: + kdFatal( mOutputBufferCursor >= maxBufferedChars ) + << "KMime::Encoder: internal buffer overflow!" << endl; + mOutputBuffer[ mOutputBufferCursor++ ] = ch; + return false; + } + } + + /** Writes characters from the output buffer to the output stream. + Implementations of encode and finish should call this + at the very beginning and for each iteration of the while loop. + @return true if all chars could be written, false otherwise */ + bool flushOutputBuffer( char* & dcursor, const char * const dend ); + + /** Convenience function. Outputs LF or CRLF, based on the state of + mWithCRLF */ + bool writeCRLF( char* & dcursor, const char * const dend ) { + if ( mWithCRLF ) + write( '\r', dcursor, dend ); + return write( '\n', dcursor, dend ); + } + +private: + /** An output buffer to simplyfy some codecs. Use with write + and flushOutputBuffer */ + char mOutputBuffer[ maxBufferedChars ]; +protected: + uchar mOutputBufferCursor; + const bool mWithCRLF; +}; + +} // namespace KMime + +#endif // __KMIME_CODECS__ diff --git a/libkmime/kmime_content.cpp b/libkmime/kmime_content.cpp new file mode 100644 index 000000000..e450d8022 --- /dev/null +++ b/libkmime/kmime_content.cpp @@ -0,0 +1,897 @@ +/* + kmime_content.cpp + + KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001 the KMime authors. + See file AUTHORS for details + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ +#include "kmime_content.h" +#include "kmime_parsers.h" + +#include <kcharsets.h> +#include <kmdcodec.h> +#include <kglobal.h> +#include <klocale.h> +#include <kdebug.h> + +#include <qtextcodec.h> + +using namespace KMime; + +namespace KMime { + +Content::Content() + : c_ontents(0), h_eaders(0), f_orceDefaultCS(false) +{ + d_efaultCS = cachedCharset("ISO-8859-1"); +} + + +Content::Content(const QCString &h, const QCString &b) + : c_ontents(0), h_eaders(0), f_orceDefaultCS(false) +{ + d_efaultCS = cachedCharset("ISO-8859-1"); + h_ead=h.copy(); + b_ody=b.copy(); +} + + +Content::~Content() +{ + delete c_ontents; + delete h_eaders; +} + + +void Content::setContent(QStrList *l) +{ + //qDebug("Content::setContent(QStrList *l) : start"); + h_ead.resize(0); + b_ody.resize(0); + + //usage of textstreams is much faster than simply appending the strings + QTextStream hts(h_ead, IO_WriteOnly), + bts(b_ody, IO_WriteOnly); + hts.setEncoding(QTextStream::Latin1); + bts.setEncoding(QTextStream::Latin1); + + bool isHead=true; + for(char *line=l->first(); line; line=l->next()) { + if(isHead && line[0]=='\0') { + isHead=false; + continue; + } + if(isHead) + hts << line << "\n"; + else + bts << line << "\n"; + } + + //terminate strings + hts << '\0'; + bts << '\0'; + + //qDebug("Content::setContent(QStrList *l) : finished"); +} + + +void Content::setContent(const QCString &s) +{ + int pos=s.find("\n\n", 0); + if(pos>-1) { + h_ead=s.left(++pos); //header *must* end with "\n" !! + b_ody=s.mid(pos+1, s.length()-pos-1); + } + else + h_ead=s; +} + + +//parse the message, split multiple parts +void Content::parse() +{ + //qDebug("void Content::parse() : start"); + delete h_eaders; + h_eaders=0; + + // check this part has already been partioned into subparts. + // if this is the case, we will not try to reparse the body + // of this part. + if ((b_ody.size() == 0) && (c_ontents != 0) && !c_ontents->isEmpty()) { + // reparse all sub parts + for(Content *c=c_ontents->first(); c; c=c_ontents->next()) + c->parse(); + return; + } + + delete c_ontents; + c_ontents=0; + + Headers::ContentType *ct=contentType(); + QCString tmp; + Content *c; + Headers::contentCategory cat; + + // just "text" as mimetype is suspicious, perhaps this article was + // generated by broken software, better check for uuencoded binaries + if (ct->mimeType()=="text") + ct->setMimeType("invalid/invalid"); + + if(ct->isText()) + return; //nothing to do + + if(ct->isMultipart()) { //this is a multipart message + tmp=ct->boundary(); //get boundary-parameter + + if(!tmp.isEmpty()) { + Parser::MultiPart mpp(b_ody, tmp); + if(mpp.parse()) { //at least one part found + + c_ontents=new List(); + c_ontents->setAutoDelete(true); + + if(ct->isSubtype("alternative")) //examine category for the sub-parts + cat=Headers::CCalternativePart; + else + cat=Headers::CCmixedPart; //default to "mixed" + + QCStringList parts=mpp.parts(); + QCStringList::Iterator it; + for(it=parts.begin(); it!=parts.end(); ++it) { //create a new Content for every part + c=new Content(); + c->setContent(*it); + c->parse(); + c->contentType()->setCategory(cat); //set category of the sub-part + c_ontents->append(c); + //qDebug("part:\n%s\n\n%s", c->h_ead.data(), c->b_ody.left(100).data()); + } + + //the whole content is now split into single parts, so it's safe delete the message-body + b_ody.resize(0); + } + else { //sh*t, the parsing failed so we have to treat the message as "text/plain" instead + ct->setMimeType("text/plain"); + ct->setCharset("US-ASCII"); + } + } + } + else if (ct->mimeType()=="invalid/invalid") { //non-mime body => check for uuencoded content + Parser::UUEncoded uup(b_ody, rawHeader("Subject")); + + if(uup.parse()) { // yep, it is uuencoded + + if(uup.isPartial()) { // this seems to be only a part of the message so we treat it as "message/partial" + ct->setMimeType("message/partial"); + //ct->setId(uniqueString()); not needed yet + ct->setPartialParams(uup.partialCount(), uup.partialNumber()); + contentTransferEncoding()->setCte(Headers::CE7Bit); + } + else { //it's a complete message => treat as "multipart/mixed" + //the whole content is now split into single parts, so it's safe to delete the message-body + b_ody.resize(0); + + //binary parts + for (unsigned int i=0;i<uup.binaryParts().count();i++) { + c=new Content(); + //generate content with mime-compliant headers + tmp="Content-Type: "; + tmp += uup.mimeTypes().at(i); + tmp += "; name=\""; + tmp += uup.filenames().at(i); + tmp += "\"\nContent-Transfer-Encoding: x-uuencode\nContent-Disposition: attachment; filename=\""; + tmp += uup.filenames().at(i); + tmp += "\"\n\n"; + tmp += uup.binaryParts().at(i); + c->setContent(tmp); + addContent(c); + } + + if(c_ontents && c_ontents->first()) { //readd the plain text before the uuencoded part + c_ontents->first()->setContent("Content-Type: text/plain\nContent-Transfer-Encoding: 7Bit\n\n"+uup.textPart()); + c_ontents->first()->contentType()->setMimeType("text/plain"); + } + } + } else { + Parser::YENCEncoded yenc(b_ody); + + if ( yenc.parse()) { + /* If it is partial, just assume there is exactly one decoded part, + * and make this that part */ + if (yenc.isPartial()) { + ct->setMimeType("message/partial"); + //ct->setId(uniqueString()); not needed yet + ct->setPartialParams(yenc.partialCount(), yenc.partialNumber()); + contentTransferEncoding()->setCte(Headers::CEbinary); + } + else { //it's a complete message => treat as "multipart/mixed" + //the whole content is now split into single parts, so it's safe to delete the message-body + b_ody.resize(0); + + //binary parts + for (unsigned int i=0;i<yenc.binaryParts().count();i++) { + c=new Content(); + //generate content with mime-compliant headers + tmp="Content-Type: "; + tmp += yenc.mimeTypes().at(i); + tmp += "; name=\""; + tmp += yenc.filenames().at(i); + tmp += "\"\nContent-Transfer-Encoding: binary\nContent-Disposition: attachment; filename=\""; + tmp += yenc.filenames().at(i); + tmp += "\"\n\n"; + c->setContent(tmp); + + // the bodies of yenc message parts are binary data, not null-terminated strings: + QByteArray body = yenc.binaryParts()[i]; + QCString body_string(body.size()); + memcpy(body_string.data(), body.data(), body.size()); + c->setBody(body_string); + + addContent(c); + } + + if(c_ontents && c_ontents->first()) { //readd the plain text before the uuencoded part + c_ontents->first()->setContent("Content-Type: text/plain\nContent-Transfer-Encoding: 7Bit\n\n"+yenc.textPart()); + c_ontents->first()->contentType()->setMimeType("text/plain"); + } + } + } + else { //no, this doesn't look like uuencoded stuff => we treat it as "text/plain" + ct->setMimeType("text/plain"); + } + } + } + + //qDebug("void Content::parse() : finished"); +} + + +void Content::assemble() +{ + QCString newHead=""; + + //Content-Type + newHead+=contentType()->as7BitString()+"\n"; + + //Content-Transfer-Encoding + newHead+=contentTransferEncoding()->as7BitString()+"\n"; + + //Content-Description + Headers::Base *h=contentDescription(false); + if(h) + newHead+=h->as7BitString()+"\n"; + + //Content-Disposition + h=contentDisposition(false); + if(h) + newHead+=h->as7BitString()+"\n"; + + h_ead=newHead; +} + + +void Content::clear() +{ + delete h_eaders; + h_eaders=0; + delete c_ontents; + c_ontents=0; + h_ead.resize(0); + b_ody.resize(0); +} + + +QCString Content::encodedContent(bool useCrLf) +{ + QCString e; + + // hack to convert articles with uuencoded or yencoded binaries into + // proper mime-compliant articles + if(c_ontents && !c_ontents->isEmpty()) { + bool convertNonMimeBinaries=false; + + // reencode non-mime binaries... + for(Content *c=c_ontents->first(); c; c=c_ontents->next()) { + if ((c->contentTransferEncoding(true)->cte()==Headers::CEuuenc) || + (c->contentTransferEncoding(true)->cte()==Headers::CEbinary)) { + convertNonMimeBinaries=true; + c->b_ody = KCodecs::base64Encode(c->decodedContent(), true); + c->b_ody.append("\n"); + c->contentTransferEncoding(true)->setCte(Headers::CEbase64); + c->contentTransferEncoding(true)->setDecoded(false); + c->removeHeader("Content-Description"); + c->assemble(); + } + } + + // add proper mime headers... + if (convertNonMimeBinaries) { + h_ead.replace(QRegExp("MIME-Version: .*\\n"),""); + h_ead.replace(QRegExp("Content-Type: .*\\n"),""); + h_ead.replace(QRegExp("Content-Transfer-Encoding: .*\\n"),""); + h_ead+="MIME-Version: 1.0\n"; + h_ead+=contentType(true)->as7BitString()+"\n"; + h_ead+=contentTransferEncoding(true)->as7BitString()+"\n"; + } + } + + //head + e=h_ead.copy(); + e+="\n"; + + //body + if(!b_ody.isEmpty()) { //this message contains only one part + Headers::CTEncoding *enc=contentTransferEncoding(); + + if(enc->needToEncode()) { + if(enc->cte()==Headers::CEquPr) { + QByteArray temp(b_ody.length()); + memcpy(temp.data(), b_ody.data(), b_ody.length()); + e+=KCodecs::quotedPrintableEncode(temp, false); + } else { + e+=KCodecs::base64Encode(b_ody, true); + e+="\n"; + } + } + else + e+=b_ody; + } + else if(c_ontents && !c_ontents->isEmpty()) { //this is a multipart message + Headers::ContentType *ct=contentType(); + QCString boundary="\n--"+ct->boundary(); + + //add all (encoded) contents separated by boundaries + for(Content *c=c_ontents->first(); c; c=c_ontents->next()) { + e+=boundary+"\n"; + e+=c->encodedContent(false); // don't convert LFs here, we do that later!!!!! + } + //finally append the closing boundary + e+=boundary+"--\n"; + }; + + if(useCrLf) + return LFtoCRLF(e); + else + return e; +} + + +QByteArray Content::decodedContent() +{ + QByteArray temp, ret; + Headers::CTEncoding *ec=contentTransferEncoding(); + bool removeTrailingNewline=false; + int size=ec->cte()==Headers::CEbinary ? b_ody.size() : b_ody.length(); + + if (size==0) + return ret; + + temp.resize(size); + memcpy(temp.data(), b_ody.data(), size); + + if(ec->decoded()) { + ret = temp; + removeTrailingNewline=true; + } else { + switch(ec->cte()) { + case Headers::CEbase64 : + KCodecs::base64Decode(temp, ret); + break; + case Headers::CEquPr : + ret = KCodecs::quotedPrintableDecode(b_ody); + ret.resize(ret.size()-1); // remove null-char + removeTrailingNewline=true; + break; + case Headers::CEuuenc : + KCodecs::uudecode(temp, ret); + break; + case Headers::CEbinary : + ret = temp; + removeTrailingNewline=false; + break; + default : + ret = temp; + removeTrailingNewline=true; + } + } + + if (removeTrailingNewline && (ret.size()>0) && (ret[ret.size()-1] == '\n')) + ret.resize(ret.size()-1); + + return ret; +} + + +void Content::decodedText(QString &s, bool trimText, + bool removeTrailingNewlines) +{ + if(!decodeText()) //this is not a text content !! + return; + + bool ok=true; + QTextCodec *codec=KGlobal::charsets()->codecForName(contentType()->charset(),ok); + + s=codec->toUnicode(b_ody.data(), b_ody.length()); + + if (trimText && removeTrailingNewlines) { + int i; + for (i=s.length()-1; i>=0; i--) + if (!s[i].isSpace()) + break; + s.truncate(i+1); + } else { + if (s.right(1)=="\n") + s.truncate(s.length()-1); // remove trailing new-line + } +} + + +void Content::decodedText(QStringList &l, bool trimText, + bool removeTrailingNewlines) +{ + if(!decodeText()) //this is not a text content !! + return; + + QString unicode; + bool ok=true; + + QTextCodec *codec=KGlobal::charsets()->codecForName(contentType()->charset(),ok); + + unicode=codec->toUnicode(b_ody.data(), b_ody.length()); + + if (trimText && removeTrailingNewlines) { + int i; + for (i=unicode.length()-1; i>=0; i--) + if (!unicode[i].isSpace()) + break; + unicode.truncate(i+1); + } else { + if (unicode.right(1)=="\n") + unicode.truncate(unicode.length()-1); // remove trailing new-line + } + + l=QStringList::split('\n', unicode, true); //split the string at linebreaks +} + + +void Content::fromUnicodeString(const QString &s) +{ + bool ok=true; + QTextCodec *codec=KGlobal::charsets()->codecForName(contentType()->charset(),ok); + + if(!ok) { // no suitable codec found => try local settings and hope the best ;-) + codec=KGlobal::locale()->codecForEncoding(); + QCString chset=KGlobal::locale()->encoding(); + contentType()->setCharset(chset); + } + + b_ody=codec->fromUnicode(s); + contentTransferEncoding()->setDecoded(true); //text is always decoded +} + + +Content* Content::textContent() +{ + Content *ret=0; + + //return the first content with mimetype=text/* + if(contentType()->isText()) + ret=this; + else if(c_ontents) + for(Content *c=c_ontents->first(); c; c=c_ontents->next()) + if( (ret=c->textContent())!=0 ) + break; + + return ret; +} + + +void Content::attachments(Content::List *dst, bool incAlternatives) +{ + dst->setAutoDelete(false); //don't delete the contents + + if(!c_ontents) + dst->append(this); + else { + for(Content *c=c_ontents->first(); c; c=c_ontents->next()) { + if( !incAlternatives && c->contentType()->category()==Headers::CCalternativePart) + continue; + else + c->attachments(dst, incAlternatives); + } + } + + if(type()!=ATmimeContent) { // this is the toplevel article + Content *text=textContent(); + if(text) + dst->removeRef(text); + } +} + + +void Content::addContent(Content *c, bool prepend) +{ + if(!c_ontents) { // this message is not multipart yet + c_ontents=new List(); + c_ontents->setAutoDelete(true); + + // first we convert the body to a content + Content *main=new Content(); + + //the Mime-Headers are needed, so we move them to the new content + if(h_eaders) { + + main->h_eaders=new Headers::Base::List(); + main->h_eaders->setAutoDelete(true); + + Headers::Base::List srcHdrs=(*h_eaders); + srcHdrs.setAutoDelete(false); + int idx=0; + for(Headers::Base *h=srcHdrs.first(); h; h=srcHdrs.next()) { + if(h->isMimeHeader()) { + //remove from this content + idx=h_eaders->findRef(h); + h_eaders->take(idx); + //append to new content + main->h_eaders->append(h); + } + } + } + + //"main" is now part of a multipart/mixed message + main->contentType()->setCategory(Headers::CCmixedPart); + + //the head of "main" is empty, so we assemble it + main->assemble(); + + //now we can copy the body and append the new content; + main->b_ody=b_ody.copy(); + c_ontents->append(main); + b_ody.resize(0); //not longer needed + + + //finally we have to convert this article to "multipart/mixed" + Headers::ContentType *ct=contentType(); + ct->setMimeType("multipart/mixed"); + ct->setBoundary(multiPartBoundary()); + ct->setCategory(Headers::CCcontainer); + contentTransferEncoding()->clear(); // 7Bit, decoded + + } + //here we actually add the content + if(prepend) + c_ontents->insert(0, c); + else + c_ontents->append(c); +} + + +void Content::removeContent(Content *c, bool del) +{ + if(!c_ontents) // what the .. + return; + + int idx=0; + if(del) + c_ontents->removeRef(c); + else { + idx=c_ontents->findRef(c); + c_ontents->take(idx); + } + + //only one content left => turn this message in a single-part + if(c_ontents->count()==1) { + Content *main=c_ontents->first(); + + //first we have to move the mime-headers + if(main->h_eaders) { + if(!h_eaders) { + h_eaders=new Headers::Base::List(); + h_eaders->setAutoDelete(true); + } + + Headers::Base::List mainHdrs=(*(main->h_eaders)); + mainHdrs.setAutoDelete(false); + + for(Headers::Base *h=mainHdrs.first(); h; h=mainHdrs.next()) { + if(h->isMimeHeader()) { + removeHeader(h->type()); //remove the old header first + h_eaders->append(h); //now append the new one + idx=main->h_eaders->findRef(h); + main->h_eaders->take(idx); //remove from the old content + kdDebug(5003) << "Content::removeContent(Content *c, bool del) : mime-header moved: " + << h->as7BitString() << endl; + } + } + } + + //now we can copy the body + b_ody=main->b_ody.copy(); + + //finally we can delete the content list + delete c_ontents; + c_ontents=0; + } +} + + +void Content::changeEncoding(Headers::contentEncoding e) +{ + Headers::CTEncoding *enc=contentTransferEncoding(); + if(enc->cte()==e) //nothing to do + return; + + if(decodeText()) + enc->setCte(e); // text is not encoded until it's sent or saved so we just set the new encoding + else { // this content contains non textual data, that has to be re-encoded + + if(e!=Headers::CEbase64) { + //kdWarning(5003) << "Content::changeEncoding() : non textual data and encoding != base64 - this should not happen\n => forcing base64" << endl; + e=Headers::CEbase64; + } + + if(enc->cte()!=e) { // ok, we reencode the content using base64 + b_ody = KCodecs::base64Encode(decodedContent(), true); + b_ody.append("\n"); + enc->setCte(e); //set encoding + enc->setDecoded(false); + } + } +} + + +void Content::toStream(QTextStream &ts, bool scrambleFromLines) +{ + QCString ret=encodedContent(false); + + if (scrambleFromLines) + ret.replace(QRegExp("\\n\\nFrom "), "\n\n>From "); + + ts << ret; +} + + +Headers::Generic* Content::getNextHeader(QCString &head) +{ + int pos1=-1, pos2=0, len=head.length()-1; + bool folded(false); + Headers::Generic *header=0; + + pos1 = head.find(": "); + + if (pos1>-1) { //there is another header + pos2=pos1+=2; //skip the name + + if (head[pos2]!='\n') { // check if the header is not empty + while(1) { + pos2=head.find("\n", pos2+1); + if(pos2==-1 || pos2==len || ( head[pos2+1]!=' ' && head[pos2+1]!='\t') ) //break if we reach the end of the string, honor folded lines + break; + else + folded = true; + } + } + + if(pos2<0) pos2=len+1; //take the rest of the string + + if (!folded) + header = new Headers::Generic(head.left(pos1-2), this, head.mid(pos1, pos2-pos1)); + else + header = new Headers::Generic(head.left(pos1-2), this, head.mid(pos1, pos2-pos1).replace(QRegExp("\\s*\\n\\s*")," ")); + + head.remove(0,pos2+1); + } + else { + head = ""; + } + + return header; +} + + +Headers::Base* Content::getHeaderByType(const char *type) +{ + if(!type) + return 0; + + Headers::Base *h=0; + //first we check if the requested header is already cached + if(h_eaders) + for(h=h_eaders->first(); h; h=h_eaders->next()) + if(h->is(type)) return h; //found + + //now we look for it in the article head + QCString raw=rawHeader(type); + if(!raw.isEmpty()) { //ok, we found it + //choose a suitable header class + if(strcasecmp("Message-Id", type)==0) + h=new Headers::MessageID(this, raw); + else if(strcasecmp("Subject", type)==0) + h=new Headers::Subject(this, raw); + else if(strcasecmp("Date", type)==0) + h=new Headers::Date(this, raw); + else if(strcasecmp("From", type)==0) + h=new Headers::From(this, raw); + else if(strcasecmp("Organization", type)==0) + h=new Headers::Organization(this, raw); + else if(strcasecmp("Reply-To", type)==0) + h=new Headers::ReplyTo(this, raw); + else if(strcasecmp("Mail-Copies-To", type)==0) + h=new Headers::MailCopiesTo(this, raw); + else if(strcasecmp("To", type)==0) + h=new Headers::To(this, raw); + else if(strcasecmp("CC", type)==0) + h=new Headers::CC(this, raw); + else if(strcasecmp("BCC", type)==0) + h=new Headers::BCC(this, raw); + else if(strcasecmp("Newsgroups", type)==0) + h=new Headers::Newsgroups(this, raw); + else if(strcasecmp("Followup-To", type)==0) + h=new Headers::FollowUpTo(this, raw); + else if(strcasecmp("References", type)==0) + h=new Headers::References(this, raw); + else if(strcasecmp("Lines", type)==0) + h=new Headers::Lines(this, raw); + else if(strcasecmp("Content-Type", type)==0) + h=new Headers::ContentType(this, raw); + else if(strcasecmp("Content-Transfer-Encoding", type)==0) + h=new Headers::CTEncoding(this, raw); + else if(strcasecmp("Content-Disposition", type)==0) + h=new Headers::CDisposition(this, raw); + else if(strcasecmp("Content-Description", type)==0) + h=new Headers::CDescription(this, raw); + else + h=new Headers::Generic(type, this, raw); + + if(!h_eaders) { + h_eaders=new Headers::Base::List(); + h_eaders->setAutoDelete(true); + } + + h_eaders->append(h); //add to cache + return h; + } + else + return 0; //header not found +} + + +void Content::setHeader(Headers::Base *h) +{ + if(!h) return; + removeHeader(h->type()); + if(!h_eaders) { + h_eaders=new Headers::Base::List(); + h_eaders->setAutoDelete(true); + } + h_eaders->append(h); +} + + +bool Content::removeHeader(const char *type) +{ + if(h_eaders) + for(Headers::Base *h=h_eaders->first(); h; h=h_eaders->next()) + if(h->is(type)) + return h_eaders->remove(); + + return false; +} + + +int Content::size() +{ + int ret=b_ody.length(); + + if(contentTransferEncoding()->cte()==Headers::CEbase64) + return (ret*3/4); //base64 => 6 bit per byte + + return ret; +} + + +int Content::storageSize() +{ + int s=h_ead.size(); + + if(!c_ontents) + s+=b_ody.size(); + else { + for(Content *c=c_ontents->first(); c; c=c_ontents->next()) + s+=c->storageSize(); + } + + return s; +} + + +int Content::lineCount() +{ + int ret=0; + if(type()==ATmimeContent) + ret+=h_ead.contains('\n'); + ret+=b_ody.contains('\n'); + + if(c_ontents && !c_ontents->isEmpty()) + for(Content *c=c_ontents->first(); c; c=c_ontents->next()) + ret+=c->lineCount(); + + return ret; +} + + +QCString Content::rawHeader(const char *name) +{ + return extractHeader(h_ead, name); +} + + +bool Content::decodeText() +{ + Headers::CTEncoding *enc=contentTransferEncoding(); + + if(!contentType()->isText()) + return false; //non textual data cannot be decoded here => use decodedContent() instead + if(enc->decoded()) + return true; //nothing to do + + switch(enc->cte()) { + case Headers::CEbase64 : + b_ody=KCodecs::base64Decode(b_ody); + b_ody.append("\n"); + break; + case Headers::CEquPr : + b_ody=KCodecs::quotedPrintableDecode(b_ody); + break; + case Headers::CEuuenc : + b_ody=KCodecs::uudecode(b_ody); + b_ody.append("\n"); + break; + case Headers::CEbinary : + b_ody=QCString(b_ody.data(), b_ody.size()+1); + b_ody.append("\n"); + default : + break; + } + + enc->setDecoded(true); + return true; +} + + +void Content::setDefaultCharset(const QCString &cs) +{ + d_efaultCS = KMime::cachedCharset(cs); + + if(c_ontents && !c_ontents->isEmpty()) + for(Content *c=c_ontents->first(); c; c=c_ontents->next()) + c->setDefaultCharset(cs); + + // reparse the part and its sub-parts in order + // to clear cached header values + parse(); +} + + +void Content::setForceDefaultCS(bool b) +{ + f_orceDefaultCS=b; + + if(c_ontents && !c_ontents->isEmpty()) + for(Content *c=c_ontents->first(); c; c=c_ontents->next()) + c->setForceDefaultCS(b); + + // reparse the part and its sub-parts in order + // to clear cached header values + parse(); +} + + +} // namespace KMime diff --git a/libkmime/kmime_content.h b/libkmime/kmime_content.h new file mode 100644 index 000000000..fb28b5e61 --- /dev/null +++ b/libkmime/kmime_content.h @@ -0,0 +1,170 @@ +/* + kmime_content.h + + KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001 the KMime authors. + See file AUTHORS for details + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ +#ifndef __KMIME_CONTENT_H__ +#define __KMIME_CONTENT_H__ + +//forward declarations +#if 0 +class KMime::Headers::Base; +class KMime::Headers::Generic; +class KMime::Headers::ContentType; +class KMime::Headers::CTEncoding; +class KMime::Headers::CDisposition; +class KMime::Headers::List; +#endif + +#include "kmime_util.h" +#include "kmime_headers.h" + +#include <qtextstream.h> + +namespace KMime { + + +/** Base class for messages in mime format + It contains all the enums, static functions + and parser-classes, that are needed for + mime handling */ + +class Base { + + public: + + //enums + enum articleType { ATmimeContent, + ATremote, + ATlocal }; + +}; + + +/** This class encapsulates a mime-encoded content. + It parses the given data and creates a tree-like + structure, that represents the structure of the + message */ + +class KDE_EXPORT Content : public Base { + + public: + typedef QPtrList<KMime::Content> List; + + Content(); + Content(const QCString &h, const QCString &b); + virtual ~Content(); + + //type + virtual articleType type() { return ATmimeContent; } + + //content handling + bool hasContent() { return ( !h_ead.isEmpty() && (!b_ody.isEmpty() || (c_ontents && !c_ontents->isEmpty())) ); } + void setContent(QStrList *l); + void setContent(const QCString &s); + virtual void parse(); + virtual void assemble(); + virtual void clear(); + + //header access + QCString head() { return h_ead; } + // extracts and removes the next header from head. The caller has to delete the returned header; + Headers::Generic* getNextHeader(QCString &head); + virtual Headers::Base* getHeaderByType(const char *type); + virtual void setHeader(Headers::Base *h); + virtual bool removeHeader(const char *type); + bool hasHeader(const char *type) { return (getHeaderByType(type)!=0); } + Headers::ContentType* contentType(bool create=true) { Headers::ContentType *p=0; return getHeaderInstance(p, create); } + Headers::CTEncoding* contentTransferEncoding(bool create=true) { Headers::CTEncoding *p=0; return getHeaderInstance(p, create); } + Headers::CDisposition* contentDisposition(bool create=true) { Headers::CDisposition *p=0; return getHeaderInstance(p, create); } + Headers::CDescription* contentDescription(bool create=true) { Headers::CDescription *p=0; return getHeaderInstance(p, create); } + + //content access + int size(); + int storageSize(); + int lineCount(); + QCString body() { return b_ody; } + void setBody( const QCString & str ) { b_ody = str; } + QCString encodedContent(bool useCrLf=false); + QByteArray decodedContent(); + void decodedText(QString &s, bool trimText=false, + bool removeTrailingNewlines=false); + void decodedText(QStringList &s, bool trimText=false, + bool removeTrailingNewlines=false); + void fromUnicodeString(const QString &s); + + Content* textContent(); + void attachments(List *dst, bool incAlternatives=false); + void addContent(Content *c, bool prepend=false); + void removeContent(Content *c, bool del=false); + void changeEncoding(Headers::contentEncoding e); + + //saves the encoded content to the given textstream + // scrambleFromLines: replace "\nFrom " with "\n>From ", this is + // needed to avoid problem with mbox-files + void toStream(QTextStream &ts, bool scrambleFromLines=false); + + // this charset is used for all headers and the body + // if the charset is not declared explictly + QCString defaultCharset() { return QCString(d_efaultCS); } + void setDefaultCharset(const QCString &cs); + + // use the default charset even if a different charset is + // declared in the article + bool forceDefaultCS() { return f_orceDefaultCS; } + + // enables/disables the force mode, housekeeping. + // works correctly only when the article is completely empty or + // completely loaded + virtual void setForceDefaultCS(bool b); + + + protected: + QCString rawHeader(const char *name); + bool decodeText(); + template <class T> T* getHeaderInstance(T *ptr, bool create); + + QCString h_ead, + b_ody; + List *c_ontents; + Headers::Base::List *h_eaders; + const char *d_efaultCS; + bool f_orceDefaultCS; + +}; + +// some compilers (for instance Compaq C++) need template inline functions +// here rather than in the *.cpp file + +template <class T> T* Content::getHeaderInstance(T *ptr, bool create) +{ + T dummy; //needed to access virtual member T::type() + + ptr=static_cast <T*> (getHeaderByType(dummy.type())); + if(!ptr && create) { //no such header found, but we need one => create it + ptr=new T(this); + if(!(h_eaders)) { + h_eaders=new Headers::Base::List(); + h_eaders->setAutoDelete(true); + } + h_eaders->append(ptr); + } + + return ptr; +} + + + +} // namespace KMime + +#endif // __KMIME_CONTENT_H__ diff --git a/libkmime/kmime_header_parsing.cpp b/libkmime/kmime_header_parsing.cpp new file mode 100644 index 000000000..1b67cee2b --- /dev/null +++ b/libkmime/kmime_header_parsing.cpp @@ -0,0 +1,1739 @@ +/* -*- c++ -*- + kmime_header_parsing.cpp + + This file is part of KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org> + + KMime is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMime is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this library with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include <config.h> +#include "kmime_header_parsing.h" + +#include "kmime_codecs.h" +#include "kmime_util.h" +#include "kmime_warning.h" + +#include <kglobal.h> +#include <kcharsets.h> + +#include <qtextcodec.h> +#include <qmap.h> +#include <qcstring.h> +#include <qstringlist.h> + +#include <ctype.h> // for isdigit +#include <cassert> + +using namespace KMime; +using namespace KMime::Types; + +namespace KMime { + +namespace Types { + + QString AddrSpec::asString() const { + bool needsQuotes = false; + QString result; + result.reserve( localPart.length() + domain.length() + 1 ); + for ( unsigned int i = 0 ; i < localPart.length() ; ++i ) { + const char ch = localPart[i].latin1(); + if ( ch == '.' || isAText( ch ) ) + result += ch; + else { + needsQuotes = true; + if ( ch == '\\' || ch == '"' ) + result += '\\'; + result += ch; + } + } + if ( needsQuotes ) + return '"' + result + "\"@" + domain; + else + return result + '@' + domain; + } + +} + +namespace HeaderParsing { + +// parse the encoded-word (scursor points to after the initial '=') +bool parseEncodedWord( const char* & scursor, const char * const send, + QString & result, QCString & language ) { + + // make sure the caller already did a bit of the work. + assert( *(scursor-1) == '=' ); + + // + // STEP 1: + // scan for the charset/language portion of the encoded-word + // + + char ch = *scursor++; + + if ( ch != '?' ) { + kdDebug() << "first" << endl; + KMIME_WARN_PREMATURE_END_OF(EncodedWord); + return false; + } + + // remember start of charset (ie. just after the initial "=?") and + // language (just after the first '*') fields: + const char * charsetStart = scursor; + const char * languageStart = 0; + + // find delimiting '?' (and the '*' separating charset and language + // tags, if any): + for ( ; scursor != send ; scursor++ ) + if ( *scursor == '?') + break; + else if ( *scursor == '*' && !languageStart ) + languageStart = scursor + 1; + + // not found? can't be an encoded-word! + if ( scursor == send || *scursor != '?' ) { + kdDebug() << "second" << endl; + KMIME_WARN_PREMATURE_END_OF(EncodedWord); + return false; + } + + // extract the language information, if any (if languageStart is 0, + // language will be null, too): + QCString maybeLanguage( languageStart, scursor - languageStart + 1 /*for NUL*/); + // extract charset information (keep in mind: the size given to the + // ctor is one off due to the \0 terminator): + QCString maybeCharset( charsetStart, ( languageStart ? languageStart : scursor + 1 ) - charsetStart ); + + // + // STEP 2: + // scan for the encoding portion of the encoded-word + // + + + // remember start of encoding (just _after_ the second '?'): + scursor++; + const char * encodingStart = scursor; + + // find next '?' (ending the encoding tag): + for ( ; scursor != send ; scursor++ ) + if ( *scursor == '?' ) break; + + // not found? Can't be an encoded-word! + if ( scursor == send || *scursor != '?' ) { + kdDebug() << "third" << endl; + KMIME_WARN_PREMATURE_END_OF(EncodedWord); + return false; + } + + // extract the encoding information: + QCString maybeEncoding( encodingStart, scursor - encodingStart + 1 ); + + + kdDebug() << "parseEncodedWord: found charset == \"" << maybeCharset + << "\"; language == \"" << maybeLanguage + << "\"; encoding == \"" << maybeEncoding << "\"" << endl; + + // + // STEP 3: + // scan for encoded-text portion of encoded-word + // + + + // remember start of encoded-text (just after the third '?'): + scursor++; + const char * encodedTextStart = scursor; + + // find next '?' (ending the encoded-text): + for ( ; scursor != send ; scursor++ ) + if ( *scursor == '?' ) break; + + // not found? Can't be an encoded-word! + // ### maybe evaluate it nonetheless if the rest is OK? + if ( scursor == send || *scursor != '?' ) { + kdDebug() << "fourth" << endl; + KMIME_WARN_PREMATURE_END_OF(EncodedWord); + return false; + } + scursor++; + // check for trailing '=': + if ( scursor == send || *scursor != '=' ) { + kdDebug() << "fifth" << endl; + KMIME_WARN_PREMATURE_END_OF(EncodedWord); + return false; + } + scursor++; + + // set end sentinel for encoded-text: + const char * const encodedTextEnd = scursor - 2; + + // + // STEP 4: + // setup decoders for the transfer encoding and the charset + // + + + // try if there's a codec for the encoding found: + Codec * codec = Codec::codecForName( maybeEncoding ); + if ( !codec ) { + KMIME_WARN_UNKNOWN(Encoding,maybeEncoding); + return false; + } + + // get an instance of a corresponding decoder: + Decoder * dec = codec->makeDecoder(); + assert( dec ); + + // try if there's a (text)codec for the charset found: + bool matchOK = false; + QTextCodec + *textCodec = KGlobal::charsets()->codecForName( maybeCharset, matchOK ); + + if ( !matchOK || !textCodec ) { + KMIME_WARN_UNKNOWN(Charset,maybeCharset); + delete dec; + return false; + }; + + kdDebug() << "mimeName(): \"" << textCodec->mimeName() << "\"" << endl; + + // allocate a temporary buffer to store the 8bit text: + int encodedTextLength = encodedTextEnd - encodedTextStart; + QByteArray buffer( codec->maxDecodedSizeFor( encodedTextLength ) ); + QByteArray::Iterator bit = buffer.begin(); + QByteArray::ConstIterator bend = buffer.end(); + + // + // STEP 5: + // do the actual decoding + // + + if ( !dec->decode( encodedTextStart, encodedTextEnd, bit, bend ) ) + KMIME_WARN << codec->name() << " codec lies about it's maxDecodedSizeFor( " + << encodedTextLength << " )\nresult may be truncated" << endl; + + result = textCodec->toUnicode( buffer.begin(), bit - buffer.begin() ); + + kdDebug() << "result now: \"" << result << "\"" << endl; + // cleanup: + delete dec; + language = maybeLanguage; + + return true; +} + +static inline void eatWhiteSpace( const char* & scursor, const char * const send ) { + while ( scursor != send + && ( *scursor == ' ' || *scursor == '\n' || + *scursor == '\t' || *scursor == '\r' ) ) + scursor++; +} + +bool parseAtom( const char * & scursor, const char * const send, + QString & result, bool allow8Bit ) +{ + QPair<const char*,int> maybeResult; + + if ( parseAtom( scursor, send, maybeResult, allow8Bit ) ) { + result += QString::fromLatin1( maybeResult.first, maybeResult.second ); + return true; + } + + return false; +} + +bool parseAtom( const char * & scursor, const char * const send, + QPair<const char*,int> & result, bool allow8Bit ) { + bool success = false; + const char * start = scursor; + + while ( scursor != send ) { + signed char ch = *scursor++; + if ( ch > 0 && isAText(ch) ) { + // AText: OK + success = true; + } else if ( allow8Bit && ch < 0 ) { + // 8bit char: not OK, but be tolerant. + KMIME_WARN_8BIT(ch); + success = true; + } else { + // CTL or special - marking the end of the atom: + // re-set sursor to point to the offending + // char and return: + scursor--; + break; + } + } + result.first = start; + result.second = scursor - start; + return success; +} + +bool parseToken( const char * & scursor, const char * const send, + QString & result, bool allow8Bit ) +{ + QPair<const char*,int> maybeResult; + + if ( parseToken( scursor, send, maybeResult, allow8Bit ) ) { + result += QString::fromLatin1( maybeResult.first, maybeResult.second ); + return true; + } + + return false; +} + +bool parseToken( const char * & scursor, const char * const send, + QPair<const char*,int> & result, bool allow8Bit ) +{ + bool success = false; + const char * start = scursor; + + while ( scursor != send ) { + signed char ch = *scursor++; + if ( ch > 0 && isTText(ch) ) { + // TText: OK + success = true; + } else if ( allow8Bit && ch < 0 ) { + // 8bit char: not OK, but be tolerant. + KMIME_WARN_8BIT(ch); + success = true; + } else { + // CTL or tspecial - marking the end of the atom: + // re-set sursor to point to the offending + // char and return: + scursor--; + break; + } + } + result.first = start; + result.second = scursor - start; + return success; +} + +#define READ_ch_OR_FAIL if ( scursor == send ) { \ + KMIME_WARN_PREMATURE_END_OF(GenericQuotedString); \ + return false; \ + } else { \ + ch = *scursor++; \ + } + +// known issues: +// +// - doesn't handle quoted CRLF + +bool parseGenericQuotedString( const char* & scursor, const char * const send, + QString & result, bool isCRLF, + const char openChar, const char closeChar ) +{ + char ch; + // We are in a quoted-string or domain-literal or comment and the + // cursor points to the first char after the openChar. + // We will apply unfolding and quoted-pair removal. + // We return when we either encounter the end or unescaped openChar + // or closeChar. + + assert( *(scursor-1) == openChar || *(scursor-1) == closeChar ); + + while ( scursor != send ) { + ch = *scursor++; + + if ( ch == closeChar || ch == openChar ) { + // end of quoted-string or another opening char: + // let caller decide what to do. + return true; + } + + switch( ch ) { + case '\\': // quoted-pair + // misses "\" CRLF LWSP-char handling, see rfc822, 3.4.5 + READ_ch_OR_FAIL; + KMIME_WARN_IF_8BIT(ch); + result += QChar(ch); + break; + case '\r': + // ### + // The case of lonely '\r' is easy to solve, as they're + // not part of Unix Line-ending conventions. + // But I see a problem if we are given Unix-native + // line-ending-mails, where we cannot determine anymore + // whether a given '\n' was part of a CRLF or was occurring + // on it's own. + READ_ch_OR_FAIL; + if ( ch != '\n' ) { + // CR on it's own... + KMIME_WARN_LONE(CR); + result += QChar('\r'); + scursor--; // points to after the '\r' again + } else { + // CRLF encountered. + // lookahead: check for folding + READ_ch_OR_FAIL; + if ( ch == ' ' || ch == '\t' ) { + // correct folding; + // position cursor behind the CRLF WSP (unfolding) + // and add the WSP to the result + result += QChar(ch); + } else { + // this is the "shouldn't happen"-case. There is a CRLF + // inside a quoted-string without it being part of FWS. + // We take it verbatim. + KMIME_WARN_NON_FOLDING(CRLF); + result += "\r\n"; + // the cursor is decremented again, so's we need not + // duplicate the whole switch here. "ch" could've been + // everything (incl. openChar or closeChar). + scursor--; + } + } + break; + case '\n': + // Note: CRLF has been handled above already! + // ### LF needs special treatment, depending on whether isCRLF + // is true (we can be sure a lonely '\n' was meant this way) or + // false ('\n' alone could have meant LF or CRLF in the original + // message. This parser assumes CRLF iff the LF is followed by + // either WSP (folding) or NULL (premature end of quoted-string; + // Should be fixed, since NULL is allowed as per rfc822). + READ_ch_OR_FAIL; + if ( !isCRLF && ( ch == ' ' || ch == '\t' ) ) { + // folding + // correct folding + result += QChar(ch); + } else { + // non-folding + KMIME_WARN_LONE(LF); + result += QChar('\n'); + // pos is decremented, so's we need not duplicate the whole + // switch here. ch could've been everything (incl. <">, "\"). + scursor--; + } + break; + default: + KMIME_WARN_IF_8BIT(ch); + result += QChar(ch); + } + } + + return false; +} + +// known issues: +// +// - doesn't handle encoded-word inside comments. + +bool parseComment( const char* & scursor, const char * const send, + QString & result, bool isCRLF, bool reallySave ) +{ + int commentNestingDepth = 1; + const char * afterLastClosingParenPos = 0; + QString maybeCmnt; + const char * oldscursor = scursor; + + assert( *(scursor-1) == '(' ); + + while ( commentNestingDepth ) { + QString cmntPart; + if ( parseGenericQuotedString( scursor, send, cmntPart, isCRLF, '(', ')' ) ) { + assert( *(scursor-1) == ')' || *(scursor-1) == '(' ); + // see the kdoc for above function for the possible conditions + // we have to check: + switch ( *(scursor-1) ) { + case ')': + if ( reallySave ) { + // add the chunk that's now surely inside the comment. + result += maybeCmnt; + result += cmntPart; + if ( commentNestingDepth > 1 ) // don't add the outermost ')'... + result += QChar(')'); + maybeCmnt = QString::null; + } + afterLastClosingParenPos = scursor; + --commentNestingDepth; + break; + case '(': + if ( reallySave ) { + // don't add to "result" yet, because we might find that we + // are already outside the (broken) comment... + maybeCmnt += cmntPart; + maybeCmnt += QChar('('); + } + ++commentNestingDepth; + break; + default: assert( 0 ); + } // switch + } else { + // !parseGenericQuotedString, ie. premature end + if ( afterLastClosingParenPos ) + scursor = afterLastClosingParenPos; + else + scursor = oldscursor; + return false; + } + } // while + + return true; +} + + +// known issues: none. + +bool parsePhrase( const char* & scursor, const char * const send, + QString & result, bool isCRLF ) +{ + enum { None, Phrase, Atom, EncodedWord, QuotedString } found = None; + QString tmp; + QCString lang; + const char * successfullyParsed = 0; + // only used by the encoded-word branch + const char * oldscursor; + // used to suppress whitespace between adjacent encoded-words + // (rfc2047, 6.2): + bool lastWasEncodedWord = false; + + while ( scursor != send ) { + char ch = *scursor++; + switch ( ch ) { + case '.': // broken, but allow for intorop's sake + if ( found == None ) { + --scursor; + return false; + } else { + if ( scursor != send && ( *scursor == ' ' || *scursor == '\t' ) ) + result += ". "; + else + result += '.'; + successfullyParsed = scursor; + } + break; + case '"': // quoted-string + tmp = QString::null; + if ( parseGenericQuotedString( scursor, send, tmp, isCRLF, '"', '"' ) ) { + successfullyParsed = scursor; + assert( *(scursor-1) == '"' ); + switch ( found ) { + case None: + found = QuotedString; + break; + case Phrase: + case Atom: + case EncodedWord: + case QuotedString: + found = Phrase; + result += QChar(' '); // rfc822, 3.4.4 + break; + default: + assert( 0 ); + } + lastWasEncodedWord = false; + result += tmp; + } else { + // premature end of quoted string. + // What to do? Return leading '"' as special? Return as quoted-string? + // We do the latter if we already found something, else signal failure. + if ( found == None ) { + return false; + } else { + result += QChar(' '); // rfc822, 3.4.4 + result += tmp; + return true; + } + } + break; + case '(': // comment + // parse it, but ignore content: + tmp = QString::null; + if ( parseComment( scursor, send, tmp, isCRLF, + false /*don't bother with the content*/ ) ) { + successfullyParsed = scursor; + lastWasEncodedWord = false; // strictly interpreting rfc2047, 6.2 + } else { + if ( found == None ) + return false; + else { + scursor = successfullyParsed; + return true; + } + } + break; + case '=': // encoded-word + tmp = QString::null; + oldscursor = scursor; + lang = 0; + if ( parseEncodedWord( scursor, send, tmp, lang ) ) { + successfullyParsed = scursor; + switch ( found ) { + case None: + found = EncodedWord; + break; + case Phrase: + case EncodedWord: + case Atom: + case QuotedString: + if ( !lastWasEncodedWord ) + result += QChar(' '); // rfc822, 3.4.4 + found = Phrase; + break; + default: assert( 0 ); + } + lastWasEncodedWord = true; + result += tmp; + break; + } else + // parse as atom: + scursor = oldscursor; + // fall though... + + default: //atom + tmp = QString::null; + scursor--; + if ( parseAtom( scursor, send, tmp, true /* allow 8bit */ ) ) { + successfullyParsed = scursor; + switch ( found ) { + case None: + found = Atom; + break; + case Phrase: + case Atom: + case EncodedWord: + case QuotedString: + found = Phrase; + result += QChar(' '); // rfc822, 3.4.4 + break; + default: + assert( 0 ); + } + lastWasEncodedWord = false; + result += tmp; + } else { + if ( found == None ) + return false; + else { + scursor = successfullyParsed; + return true; + } + } + } + eatWhiteSpace( scursor, send ); + } + + return ( found != None ); +} + + +bool parseDotAtom( const char* & scursor, const char * const send, + QString & result, bool isCRLF ) +{ + // always points to just after the last atom parsed: + const char * successfullyParsed; + + QString tmp; + if ( !parseAtom( scursor, send, tmp, false /* no 8bit */ ) ) + return false; + result += tmp; + successfullyParsed = scursor; + + while ( scursor != send ) { + eatCFWS( scursor, send, isCRLF ); + + // end of header or no '.' -> return + if ( scursor == send || *scursor != '.' ) return true; + scursor++; // eat '.' + + eatCFWS( scursor, send, isCRLF ); + + if ( scursor == send || !isAText( *scursor ) ) { + // end of header or no AText, but this time following a '.'!: + // reset cursor to just after last successfully parsed char and + // return: + scursor = successfullyParsed; + return true; + } + + // try to parse the next atom: + QString maybeAtom; + if ( !parseAtom( scursor, send, maybeAtom, false /*no 8bit*/ ) ) { + scursor = successfullyParsed; + return true; + } + + result += QChar('.'); + result += maybeAtom; + successfullyParsed = scursor; + } + + scursor = successfullyParsed; + return true; +} + + +void eatCFWS( const char* & scursor, const char * const send, bool isCRLF ) { + QString dummy; + + while ( scursor != send ) { + const char * oldscursor = scursor; + + char ch = *scursor++; + + switch( ch ) { + case ' ': + case '\t': // whitespace + case '\r': + case '\n': // folding + continue; + + case '(': // comment + if ( parseComment( scursor, send, dummy, isCRLF, false /*don't save*/ ) ) + continue; + scursor = oldscursor; + return; + + default: + scursor = oldscursor; + return; + } + + } +} + +bool parseDomain( const char* & scursor, const char * const send, + QString & result, bool isCRLF ) { + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + // domain := dot-atom / domain-literal / atom *("." atom) + // + // equivalent to: + // domain = dot-atom / domain-literal, + // since parseDotAtom does allow CFWS between atoms and dots + + if ( *scursor == '[' ) { + // domain-literal: + QString maybeDomainLiteral; + // eat '[': + scursor++; + while ( parseGenericQuotedString( scursor, send, maybeDomainLiteral, + isCRLF, '[', ']' ) ) { + if ( scursor == send ) { + // end of header: check for closing ']': + if ( *(scursor-1) == ']' ) { + // OK, last char was ']': + result = maybeDomainLiteral; + return true; + } else { + // not OK, domain-literal wasn't closed: + return false; + } + } + // we hit openChar in parseGenericQuotedString. + // include it in maybeDomainLiteral and keep on parsing: + if ( *(scursor-1) == '[' ) { + maybeDomainLiteral += QChar('['); + continue; + } + // OK, real end of domain-literal: + result = maybeDomainLiteral; + return true; + } + } else { + // dot-atom: + QString maybeDotAtom; + if ( parseDotAtom( scursor, send, maybeDotAtom, isCRLF ) ) { + result = maybeDotAtom; + return true; + } + } + return false; +} + +bool parseObsRoute( const char* & scursor, const char* const send, + QStringList & result, bool isCRLF, bool save ) { + while ( scursor != send ) { + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + // empty entry: + if ( *scursor == ',' ) { + scursor++; + if ( save ) result.append( QString::null ); + continue; + } + + // empty entry ending the list: + if ( *scursor == ':' ) { + scursor++; + if ( save ) result.append( QString::null ); + return true; + } + + // each non-empty entry must begin with '@': + if ( *scursor != '@' ) + return false; + else + scursor++; + + QString maybeDomain; + if ( !parseDomain( scursor, send, maybeDomain, isCRLF ) ) return false; + if ( save ) result.append( maybeDomain ); + + // eat the following (optional) comma: + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + if ( *scursor == ':' ) { scursor++; return true; } + if ( *scursor == ',' ) scursor++; + + } + + return false; +} + +bool parseAddrSpec( const char* & scursor, const char * const send, + AddrSpec & result, bool isCRLF ) { + // + // STEP 1: + // local-part := dot-atom / quoted-string / word *("." word) + // + // this is equivalent to: + // local-part := word *("." word) + + QString maybeLocalPart; + QString tmp; + + while ( scursor != send ) { + // first, eat any whitespace + eatCFWS( scursor, send, isCRLF ); + + char ch = *scursor++; + switch ( ch ) { + case '.': // dot + maybeLocalPart += QChar('.'); + break; + + case '@': + goto SAW_AT_SIGN; + break; + + case '"': // quoted-string + tmp = QString::null; + if ( parseGenericQuotedString( scursor, send, tmp, isCRLF, '"', '"' ) ) + maybeLocalPart += tmp; + else + return false; + break; + + default: // atom + scursor--; // re-set scursor to point to ch again + tmp = QString::null; + if ( parseAtom( scursor, send, tmp, false /* no 8bit */ ) ) + maybeLocalPart += tmp; + else + return false; // parseAtom can only fail if the first char is non-atext. + break; + } + } + + return false; + + + // + // STEP 2: + // domain + // + +SAW_AT_SIGN: + + assert( *(scursor-1) == '@' ); + + QString maybeDomain; + if ( !parseDomain( scursor, send, maybeDomain, isCRLF ) ) + return false; + + result.localPart = maybeLocalPart; + result.domain = maybeDomain; + + return true; +} + + +bool parseAngleAddr( const char* & scursor, const char * const send, + AddrSpec & result, bool isCRLF ) { + // first, we need an opening angle bracket: + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send || *scursor != '<' ) return false; + scursor++; // eat '<' + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + if ( *scursor == '@' || *scursor == ',' ) { + // obs-route: parse, but ignore: + KMIME_WARN << "obsolete source route found! ignoring." << endl; + QStringList dummy; + if ( !parseObsRoute( scursor, send, dummy, + isCRLF, false /* don't save */ ) ) + return false; + // angle-addr isn't complete until after the '>': + if ( scursor == send ) return false; + } + + // parse addr-spec: + AddrSpec maybeAddrSpec; + if ( !parseAddrSpec( scursor, send, maybeAddrSpec, isCRLF ) ) return false; + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send || *scursor != '>' ) return false; + scursor++; + + result = maybeAddrSpec; + return true; + +} + +bool parseMailbox( const char* & scursor, const char * const send, + Mailbox & result, bool isCRLF ) { + + // rfc: + // mailbox := addr-spec / ([ display-name ] angle-addr) + // us: + // mailbox := addr-spec / ([ display-name ] angle-addr) + // / (angle-addr "(" display-name ")") + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + AddrSpec maybeAddrSpec; + + // first, try if it's a vanilla addr-spec: + const char * oldscursor = scursor; + if ( parseAddrSpec( scursor, send, maybeAddrSpec, isCRLF ) ) { + result.displayName = QString::null; + result.addrSpec = maybeAddrSpec; + return true; + } + scursor = oldscursor; + + // second, see if there's a display-name: + QString maybeDisplayName; + if ( !parsePhrase( scursor, send, maybeDisplayName, isCRLF ) ) { + // failed: reset cursor, note absent display-name + maybeDisplayName = QString::null; + scursor = oldscursor; + } else { + // succeeded: eat CFWS + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + } + + // third, parse the angle-addr: + if ( !parseAngleAddr( scursor, send, maybeAddrSpec, isCRLF ) ) + return false; + + if ( maybeDisplayName.isNull() ) { + // check for the obsolete form of display-name (as comment): + eatWhiteSpace( scursor, send ); + if ( scursor != send && *scursor == '(' ) { + scursor++; + if ( !parseComment( scursor, send, maybeDisplayName, isCRLF, true /*keep*/ ) ) + return false; + } + } + + result.displayName = maybeDisplayName; + result.addrSpec = maybeAddrSpec; + return true; +} + +bool parseGroup( const char* & scursor, const char * const send, + Address & result, bool isCRLF ) { + // group := display-name ":" [ mailbox-list / CFWS ] ";" [CFWS] + // + // equivalent to: + // group := display-name ":" [ obs-mbox-list ] ";" + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + // get display-name: + QString maybeDisplayName; + if ( !parsePhrase( scursor, send, maybeDisplayName, isCRLF ) ) + return false; + + // get ":": + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send || *scursor != ':' ) return false; + + result.displayName = maybeDisplayName; + + // get obs-mbox-list (may contain empty entries): + scursor++; + while ( scursor != send ) { + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + // empty entry: + if ( *scursor == ',' ) { scursor++; continue; } + + // empty entry ending the list: + if ( *scursor == ';' ) { scursor++; return true; } + + Mailbox maybeMailbox; + if ( !parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) + return false; + result.mailboxList.append( maybeMailbox ); + + eatCFWS( scursor, send, isCRLF ); + // premature end: + if ( scursor == send ) return false; + // regular end of the list: + if ( *scursor == ';' ) { scursor++; return true; } + // eat regular list entry separator: + if ( *scursor == ',' ) scursor++; + } + return false; +} + + +bool parseAddress( const char* & scursor, const char * const send, + Address & result, bool isCRLF ) { + // address := mailbox / group + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + // first try if it's a single mailbox: + Mailbox maybeMailbox; + const char * oldscursor = scursor; + if ( parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) { + // yes, it is: + result.displayName = QString::null; + result.mailboxList.append( maybeMailbox ); + return true; + } + scursor = oldscursor; + + Address maybeAddress; + + // no, it's not a single mailbox. Try if it's a group: + if ( !parseGroup( scursor, send, maybeAddress, isCRLF ) ) + return false; + + result = maybeAddress; + return true; +} + +bool parseAddressList( const char* & scursor, const char * const send, + AddressList & result, bool isCRLF ) { + while ( scursor != send ) { + eatCFWS( scursor, send, isCRLF ); + // end of header: this is OK. + if ( scursor == send ) return true; + // empty entry: ignore: + if ( *scursor == ',' ) { scursor++; continue; } + + // parse one entry + Address maybeAddress; + if ( !parseAddress( scursor, send, maybeAddress, isCRLF ) ) return false; + result.append( maybeAddress ); + + eatCFWS( scursor, send, isCRLF ); + // end of header: this is OK. + if ( scursor == send ) return true; + // comma separating entries: eat it. + if ( *scursor == ',' ) scursor++; + } + return true; +} + + +static QString asterisk = QString::fromLatin1("*0*",1); +static QString asteriskZero = QString::fromLatin1("*0*",2); +//static QString asteriskZeroAsterisk = QString::fromLatin1("*0*",3); + +bool parseParameter( const char* & scursor, const char * const send, + QPair<QString,QStringOrQPair> & result, bool isCRLF ) { + // parameter = regular-parameter / extended-parameter + // regular-parameter = regular-parameter-name "=" value + // extended-parameter = + // value = token / quoted-string + // + // note that rfc2231 handling is out of the scope of this function. + // Therefore we return the attribute as QString and the value as + // (start,length) tupel if we see that the value is encoded + // (trailing asterisk), for parseParameterList to decode... + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + // + // parse the parameter name: + // + QString maybeAttribute; + if ( !parseToken( scursor, send, maybeAttribute, false /* no 8bit */ ) ) + return false; + + eatCFWS( scursor, send, isCRLF ); + // premature end: not OK (haven't seen '=' yet). + if ( scursor == send || *scursor != '=' ) return false; + scursor++; // eat '=' + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) { + // don't choke on attribute=, meaning the value was omitted: + if ( maybeAttribute.endsWith( asterisk ) ) { + KMIME_WARN << "attribute ends with \"*\", but value is empty! " + "Chopping away \"*\"." << endl; + maybeAttribute.truncate( maybeAttribute.length() - 1 ); + } + result = qMakePair( maybeAttribute.lower(), QStringOrQPair() ); + return true; + } + + const char * oldscursor = scursor; + + // + // parse the parameter value: + // + QStringOrQPair maybeValue; + if ( *scursor == '"' ) { + // value is a quoted-string: + scursor++; + if ( maybeAttribute.endsWith( asterisk ) ) { + // attributes ending with "*" designate extended-parameters, + // which cannot have quoted-strings as values. So we remove the + // trailing "*" to not confuse upper layers. + KMIME_WARN << "attribute ends with \"*\", but value is a quoted-string! " + "Chopping away \"*\"." << endl; + maybeAttribute.truncate( maybeAttribute.length() - 1 ); + } + + if ( !parseGenericQuotedString( scursor, send, maybeValue.qstring, isCRLF ) ) { + scursor = oldscursor; + result = qMakePair( maybeAttribute.lower(), QStringOrQPair() ); + return false; // this case needs further processing by upper layers!! + } + } else { + // value is a token: + if ( !parseToken( scursor, send, maybeValue.qpair, false /* no 8bit */ ) ) { + scursor = oldscursor; + result = qMakePair( maybeAttribute.lower(), QStringOrQPair() ); + return false; // this case needs further processing by upper layers!! + } + } + + result = qMakePair( maybeAttribute.lower(), maybeValue ); + return true; +} + + + +bool parseRawParameterList( const char* & scursor, const char * const send, + QMap<QString,QStringOrQPair> & result, + bool isCRLF ) { + // we use parseParameter() consecutively to obtain a map of raw + // attributes to raw values. "Raw" here means that we don't do + // rfc2231 decoding and concatenation. This is left to + // parseParameterList(), which will call this function. + // + // The main reason for making this chunk of code a separate + // (private) method is that we can deal with broken parameters + // _here_ and leave the rfc2231 handling solely to + // parseParameterList(), which will still be enough work. + + while ( scursor != send ) { + eatCFWS( scursor, send, isCRLF ); + // empty entry ending the list: OK. + if ( scursor == send ) return true; + // empty list entry: ignore. + if ( *scursor == ';' ) { scursor++; continue; } + + QPair<QString,QStringOrQPair> maybeParameter; + if ( !parseParameter( scursor, send, maybeParameter, isCRLF ) ) { + // we need to do a bit of work if the attribute is not + // NULL. These are the cases marked with "needs further + // processing" in parseParameter(). Specifically, parsing of the + // token or the quoted-string, which should represent the value, + // failed. We take the easy way out and simply search for the + // next ';' to start parsing again. (Another option would be to + // take the text between '=' and ';' as value) + if ( maybeParameter.first.isNull() ) return false; + while ( scursor != send ) { + if ( *scursor++ == ';' ) goto IS_SEMICOLON; + } + // scursor == send case: end of list. + return true; + IS_SEMICOLON: + // *scursor == ';' case: parse next entry. + continue; + } + // successful parsing brings us here: + result.insert( maybeParameter.first, maybeParameter.second ); + + eatCFWS( scursor, send, isCRLF ); + // end of header: ends list. + if ( scursor == send ) return true; + // regular separator: eat it. + if ( *scursor == ';' ) scursor++; + } + return true; +} + + +static void decodeRFC2231Value( Codec* & rfc2231Codec, + QTextCodec* & textcodec, + bool isContinuation, QString & value, + QPair<const char*,int> & source ) { + + // + // parse the raw value into (charset,language,text): + // + + const char * decBegin = source.first; + const char * decCursor = decBegin; + const char * decEnd = decCursor + source.second; + + if ( !isContinuation ) { + // find the first single quote + while ( decCursor != decEnd ) { + if ( *decCursor == '\'' ) break; + else decCursor++; + } + + if ( decCursor == decEnd ) { + // there wasn't a single single quote at all! + // take the whole value to be in latin-1: + KMIME_WARN << "No charset in extended-initial-value. " + "Assuming \"iso-8859-1\"." << endl; + value += QString::fromLatin1( decBegin, source.second ); + return; + } + + QCString charset( decBegin, decCursor - decBegin + 1 ); + + const char * oldDecCursor = ++decCursor; + // find the second single quote (we ignore the language tag): + while ( decCursor != decEnd ) { + if ( *decCursor == '\'' ) break; + else decCursor++; + } + if ( decCursor == decEnd ) { + KMIME_WARN << "No language in extended-initial-value. " + "Trying to recover." << endl; + decCursor = oldDecCursor; + } else + decCursor++; + + // decCursor now points to the start of the + // "extended-other-values": + + // + // get the decoders: + // + + bool matchOK = false; + textcodec = KGlobal::charsets()->codecForName( charset, matchOK ); + if ( !matchOK ) { + textcodec = 0; + KMIME_WARN_UNKNOWN(Charset,charset); + } + } + + if ( !rfc2231Codec ) { + rfc2231Codec = Codec::codecForName("x-kmime-rfc2231"); + assert( rfc2231Codec ); + } + + if ( !textcodec ) { + value += QString::fromLatin1( decCursor, decEnd - decCursor ); + return; + } + + Decoder * dec = rfc2231Codec->makeDecoder(); + assert( dec ); + + // + // do the decoding: + // + + QByteArray buffer( rfc2231Codec->maxDecodedSizeFor( decEnd - decCursor ) ); + QByteArray::Iterator bit = buffer.begin(); + QByteArray::ConstIterator bend = buffer.end(); + + if ( !dec->decode( decCursor, decEnd, bit, bend ) ) + KMIME_WARN << rfc2231Codec->name() + << " codec lies about it's maxDecodedSizeFor()\n" + "result may be truncated" << endl; + + value += textcodec->toUnicode( buffer.begin(), bit - buffer.begin() ); + + kdDebug() << "value now: \"" << value << "\"" << endl; + // cleanup: + delete dec; +} + +// known issues: +// - permutes rfc2231 continuations when the total number of parts +// exceeds 10 (other-sections then becomes *xy, ie. two digits) + +bool parseParameterList( const char* & scursor, const char * const send, + QMap<QString,QString> & result, bool isCRLF ) { + // parse the list into raw attribute-value pairs: + QMap<QString,QStringOrQPair> rawParameterList; + if (!parseRawParameterList( scursor, send, rawParameterList, isCRLF ) ) + return false; + + if ( rawParameterList.isEmpty() ) return true; + + // decode rfc 2231 continuations and alternate charset encoding: + + // NOTE: this code assumes that what QMapIterator delivers is sorted + // by the key! + + Codec * rfc2231Codec = 0; + QTextCodec * textcodec = 0; + QString attribute; + QString value; + enum Modes { NoMode = 0x0, Continued = 0x1, Encoded = 0x2 } mode; + + QMapIterator<QString,QStringOrQPair> it, end = rawParameterList.end(); + + for ( it = rawParameterList.begin() ; it != end ; ++it ) { + if ( attribute.isNull() || !it.key().startsWith( attribute ) ) { + // + // new attribute: + // + + // store the last attribute/value pair in the result map now: + if ( !attribute.isNull() ) result.insert( attribute, value ); + // and extract the information from the new raw attribute: + value = QString::null; + attribute = it.key(); + mode = NoMode; + // is the value encoded? + if ( attribute.endsWith( asterisk ) ) { + attribute.truncate( attribute.length() - 1 ); + mode = (Modes) ((int) mode | Encoded); + } + // is the value continued? + if ( attribute.endsWith( asteriskZero ) ) { + attribute.truncate( attribute.length() - 2 ); + mode = (Modes) ((int) mode | Continued); + } + // + // decode if necessary: + // + if ( mode & Encoded ) { + decodeRFC2231Value( rfc2231Codec, textcodec, + false, /* isn't continuation */ + value, (*it).qpair ); + } else { + // not encoded. + if ( (*it).qpair.first ) + value += QString::fromLatin1( (*it).qpair.first, (*it).qpair.second ); + else + value += (*it).qstring; + } + + // + // shortcut-processing when the value isn't encoded: + // + + if ( !(mode & Continued) ) { + // save result already: + result.insert( attribute, value ); + // force begin of a new attribute: + attribute = QString::null; + } + } else /* it.key().startsWith( attribute ) */ { + // + // continuation + // + + // ignore the section and trust QMap to have sorted the keys: + if ( it.key().endsWith( asterisk ) ) { + // encoded + decodeRFC2231Value( rfc2231Codec, textcodec, + true, /* is continuation */ + value, (*it).qpair ); + } else { + // not encoded + if ( (*it).qpair.first ) + value += QString::fromLatin1( (*it).qpair.first, (*it).qpair.second ); + else + value += (*it).qstring; + } + } + } + + // write last attr/value pair: + if ( !attribute.isNull() ) + result.insert( attribute, value ); + + return true; +} + +static const char * stdDayNames[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; +static const int stdDayNamesLen = sizeof stdDayNames / sizeof *stdDayNames; + +static bool parseDayName( const char* & scursor, const char * const send ) +{ + // check bounds: + if ( send - scursor < 3 ) return false; + + for ( int i = 0 ; i < stdDayNamesLen ; ++i ) + if ( qstrnicmp( scursor, stdDayNames[i], 3 ) == 0 ) { + scursor += 3; + kdDebug() << "found " << stdDayNames[i] << endl; + return true; + } + + return false; +} + + +static const char * stdMonthNames[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dez" +}; +static const int stdMonthNamesLen = + sizeof stdMonthNames / sizeof *stdMonthNames; + +static bool parseMonthName( const char* & scursor, const char * const send, + int & result ) +{ + // check bounds: + if ( send - scursor < 3 ) return false; + + for ( result = 0 ; result < stdMonthNamesLen ; ++result ) + if ( qstrnicmp( scursor, stdMonthNames[result], 3 ) == 0 ) { + scursor += 3; + return true; + } + + // not found: + return false; +} + +static const struct { + const char * tzName; + long int secsEastOfGMT; +} timeZones[] = { + // rfc 822 timezones: + { "GMT", 0 }, + { "UT", 0 }, + { "EDT", -4*3600 }, + { "EST", -5*3600 }, + { "MST", -5*3600 }, + { "CST", -6*3600 }, + { "MDT", -6*3600 }, + { "MST", -7*3600 }, + { "PDT", -7*3600 }, + { "PST", -8*3600 }, + // common, non-rfc-822 zones: + { "CET", 1*3600 }, + { "MET", 1*3600 }, + { "UTC", 0 }, + { "CEST", 2*3600 }, + { "BST", 1*3600 }, + // rfc 822 military timezones: + { "Z", 0 }, + { "A", -1*3600 }, + { "B", -2*3600 }, + { "C", -3*3600 }, + { "D", -4*3600 }, + { "E", -5*3600 }, + { "F", -6*3600 }, + { "G", -7*3600 }, + { "H", -8*3600 }, + { "I", -9*3600 }, + // J is not used! + { "K", -10*3600 }, + { "L", -11*3600 }, + { "M", -12*3600 }, + { "N", 1*3600 }, + { "O", 2*3600 }, + { "P", 3*3600 }, + { "Q", 4*3600 }, + { "R", 5*3600 }, + { "S", 6*3600 }, + { "T", 7*3600 }, + { "U", 8*3600 }, + { "V", 9*3600 }, + { "W", 10*3600 }, + { "X", 11*3600 }, + { "Y", 12*3600 }, +}; +static const int timeZonesLen = sizeof timeZones / sizeof *timeZones; + +static bool parseAlphaNumericTimeZone( const char* & scursor, + const char * const send, + long int & secsEastOfGMT, + bool & timeZoneKnown ) +{ + QPair<const char*,int> maybeTimeZone(0,0); + if ( !parseToken( scursor, send, maybeTimeZone, false /*no 8bit*/ ) ) + return false; + for ( int i = 0 ; i < timeZonesLen ; ++i ) + if ( qstrnicmp( timeZones[i].tzName, + maybeTimeZone.first, maybeTimeZone.second ) == 0 ) { + scursor += maybeTimeZone.second; + secsEastOfGMT = timeZones[i].secsEastOfGMT; + timeZoneKnown = true; + return true; + } + + // don't choke just because we don't happen to know the time zone + KMIME_WARN_UNKNOWN(time zone,QCString( maybeTimeZone.first, maybeTimeZone.second+1 )); + secsEastOfGMT = 0; + timeZoneKnown = false; + return true; +} + +// parse a number and return the number of digits parsed: +static int parseDigits( const char* & scursor, const char * const send, + int & result ) +{ + result = 0; + int digits = 0; + for ( ; scursor != send && isdigit( *scursor ) ; scursor++, digits++ ) { + result *= 10; + result += int( *scursor - '0' ); + } + return digits; +} + +static bool parseTimeOfDay( const char* & scursor, const char * const send, + int & hour, int & min, int & sec, bool isCRLF=false ) +{ + // time-of-day := 2DIGIT [CFWS] ":" [CFWS] 2DIGIT [ [CFWS] ":" 2DIGIT ] + + // + // 2DIGIT representing "hour": + // + if ( !parseDigits( scursor, send, hour ) ) return false; + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send || *scursor != ':' ) return false; + scursor++; // eat ':' + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + // + // 2DIGIT representing "minute": + // + if ( !parseDigits( scursor, send, min ) ) return false; + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return true; // seconds are optional + + // + // let's see if we have a 2DIGIT representing "second": + // + if ( *scursor == ':' ) { + // yepp, there are seconds: + scursor++; // eat ':' + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + if ( !parseDigits( scursor, send, sec ) ) return false; + } else { + sec = 0; + } + + return true; +} + + +bool parseTime( const char* & scursor, const char * send, + int & hour, int & min, int & sec, long int & secsEastOfGMT, + bool & timeZoneKnown, bool isCRLF ) +{ + // time := time-of-day CFWS ( zone / obs-zone ) + // + // obs-zone := "UT" / "GMT" / + // "EST" / "EDT" / ; -0500 / -0400 + // "CST" / "CDT" / ; -0600 / -0500 + // "MST" / "MDT" / ; -0700 / -0600 + // "PST" / "PDT" / ; -0800 / -0700 + // "A"-"I" / "a"-"i" / + // "K"-"Z" / "k"-"z" + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + if ( !parseTimeOfDay( scursor, send, hour, min, sec, isCRLF ) ) + return false; + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) { + timeZoneKnown = false; + secsEastOfGMT = 0; + return true; // allow missing timezone + } + + timeZoneKnown = true; + if ( *scursor == '+' || *scursor == '-' ) { + // remember and eat '-'/'+': + const char sign = *scursor++; + // numerical timezone: + int maybeTimeZone; + if ( parseDigits( scursor, send, maybeTimeZone ) != 4 ) return false; + secsEastOfGMT = 60 * ( maybeTimeZone / 100 * 60 + maybeTimeZone % 100 ); + if ( sign == '-' ) { + secsEastOfGMT *= -1; + if ( secsEastOfGMT == 0 ) + timeZoneKnown = false; // -0000 means indetermined tz + } + } else { + // maybe alphanumeric timezone: + if ( !parseAlphaNumericTimeZone( scursor, send, secsEastOfGMT, timeZoneKnown ) ) + return false; + } + return true; +} + + +bool parseDateTime( const char* & scursor, const char * const send, + Types::DateTime & result, bool isCRLF ) +{ + // Parsing date-time; strict mode: + // + // date-time := [ [CFWS] day-name [CFWS] "," ] ; wday + // (expanded) [CFWS] 1*2DIGIT CFWS month-name CFWS 2*DIGIT [CFWS] ; date + // time + // + // day-name := "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun" + // month-name := "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / + // "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dez" + + struct tm maybeDateTime = { +#ifdef HAVE_TM_GMTOFF + 0, 0, // initializers for members tm_gmtoff and tm_zone +#endif + 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + // + // let's see if there's a day-of-week: + // + if ( parseDayName( scursor, send ) ) { + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + // day-name should be followed by ',' but we treat it as optional: + if ( *scursor == ',' ) { + scursor++; // eat ',' + eatCFWS( scursor, send, isCRLF ); + } + } + + // + // 1*2DIGIT representing "day" (of month): + // + int maybeDay; + if ( !parseDigits( scursor, send, maybeDay ) ) return false; + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + // success: store maybeDay in maybeDateTime: + maybeDateTime.tm_mday = maybeDay; + + // + // month-name: + // + int maybeMonth = 0; + if ( !parseMonthName( scursor, send, maybeMonth ) ) return false; + if ( scursor == send ) return false; + assert( maybeMonth >= 0 ); assert( maybeMonth <= 11 ); + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + // success: store maybeMonth in maybeDateTime: + maybeDateTime.tm_mon = maybeMonth; + + // + // 2*DIGIT representing "year": + // + int maybeYear; + if ( !parseDigits( scursor, send, maybeYear ) ) return false; + // RFC 2822 4.3 processing: + if ( maybeYear < 50 ) + maybeYear += 2000; + else if ( maybeYear < 1000 ) + maybeYear += 1900; + // else keep as is + if ( maybeYear < 1900 ) return false; // rfc2822, 3.3 + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + // success: store maybeYear in maybeDateTime: + maybeDateTime.tm_year = maybeYear - 1900; + + // + // time + // + int maybeHour, maybeMinute, maybeSecond; + long int secsEastOfGMT; + bool timeZoneKnown = true; + + if ( !parseTime( scursor, send, + maybeHour, maybeMinute, maybeSecond, + secsEastOfGMT, timeZoneKnown, isCRLF ) ) + return false; + + // success: store everything in maybeDateTime: + maybeDateTime.tm_hour = maybeHour; + maybeDateTime.tm_min = maybeMinute; + maybeDateTime.tm_sec = maybeSecond; + maybeDateTime.tm_isdst = DateFormatter::isDaylight(); + // now put everything together and check if mktime(3) likes it: + result.time = mktime( &maybeDateTime ); + if ( result.time == (time_t)(-1) ) return false; + + // adjust to UTC/GMT: + //result.time -= secsEastOfGMT; + result.secsEastOfGMT = secsEastOfGMT; + result.timeZoneKnown = timeZoneKnown; + + return true; +} + +#if 0 +bool tryToMakeAnySenseOfDateString( const char* & scursor, + const char * const send, + time_t & result, bool isCRLF ) +{ + return false; +} +#endif + +} // namespace HeaderParsing + +} // namespace KMime diff --git a/libkmime/kmime_header_parsing.h b/libkmime/kmime_header_parsing.h new file mode 100644 index 000000000..326a7e1ba --- /dev/null +++ b/libkmime/kmime_header_parsing.h @@ -0,0 +1,196 @@ +/* -*- c++ -*- + kmime_header_parsing.h + + This file is part of KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org> + + KMime is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMime is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this library with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KMIME_HEADER_PARSING_H__ +#define __KMIME_HEADER_PARSING_H__ + +#include <qstring.h> +#include <qpair.h> +#include <qvaluelist.h> + +#include <time.h> + +#include <kdepimmacros.h> + +template <typename K, typename V> class QMap; +class QStringList; + +namespace KMime { + +namespace Types { + + // for when we can't make up our mind what to use... + struct KDE_EXPORT QStringOrQPair { + QStringOrQPair() : qstring(), qpair(0,0) {} + QString qstring; + QPair<const char*,int> qpair; + }; + + struct KDE_EXPORT AddrSpec { + QString asString() const; + QString localPart; + QString domain; + }; + typedef QValueList<AddrSpec> AddrSpecList; + + struct KDE_EXPORT Mailbox { + QString displayName; + AddrSpec addrSpec; + }; + typedef QValueList<Mailbox> MailboxList; + + struct KDE_EXPORT Address { + QString displayName; + MailboxList mailboxList; + }; + typedef QValueList<Address> AddressList; + + struct KDE_EXPORT DateTime { + time_t time; // secs since 1.1.1970, 0:00 UTC/GMT + long int secsEastOfGMT; // timezone + bool timeZoneKnown; // do we know the timezone? (e.g. on -0000) + }; + +} // namespace KMime::Types + +namespace HeaderParsing { + + /** Parse the encoded word in @p str pointed to by @p pos + (actually, @p pos-2, see below). + + @param str the source string + @param pos in: the starting position (must already point to the + character following the initial '=?'; + out: the new postion + @param ok only out: if true, the encoded-word was correct up + to and including the encoding specifier. The + encoded-text is quite generously parsed and @p ok + is still set to @p true when e.g. the encoded-word + appears to be truncated or contains whitespace. + @return the decoded string the encoded word represented. + */ + bool parseEncodedWord( const char* & scursor, const char * const send, + QString & result, QCString & language ) KDE_EXPORT; + // + // The parsing squad: + // + + /** You may or may not have already started parsing into the + atom. This function will go on where you left off. */ + bool parseAtom( const char* & scursor, const char * const send, + QString & result, bool allow8Bit=false ) KDE_EXPORT; + bool parseAtom( const char* & scursor, const char * const send, + QPair<const char*,int> & result, bool allow8Bit=false ) KDE_EXPORT; + /** You may or may not have already started parsing into the + token. This function will go on where you left off. */ + bool parseToken( const char* & scursor, const char * const send, + QString & result, bool allow8Bit=false ) KDE_EXPORT; + bool parseToken( const char* & scursor, const char * const send, + QPair<const char*,int> & result, bool allow8Bit=false ) KDE_EXPORT; + /** @p scursor must be positioned after the opening openChar. */ + bool parseGenericQuotedString( const char* & scursor, const char* const send, + QString & result, bool isCRLF, + const char openChar='"', + const char closeChar='"' ) KDE_EXPORT; + /** @p scursor must be positioned right after the opening '(' */ + bool parseComment( const char* & scursor, const char * const send, + QString & result, bool isCRLF=false, bool reallySave=true ) KDE_EXPORT; + /** You may or may not have already started parsing into the phrase, + but only if it starts with atext. If you setup this function to + parse a phrase starting with an encoded-word or quoted-string, + @p scursor has to point to the char introducing the encoded-word + or quoted-string, resp. */ + bool parsePhrase( const char* & scursor, const char * const send, + QString & result, bool isCRLF=false ) KDE_EXPORT; + /** You may or may not have already started parsing into the initial + atom, but not up to it's end. */ + bool parseDotAtom( const char* & scursor, const char * const send, + QString & result, bool isCRLF=false ) KDE_EXPORT; + + /** Eats comment-folding-white-space, skips whitespace, folding and + comments (even nested ones) and stops at the next non-CFWS + character. After calling this function, you should check whether + @p scursor == @p send (end of header reached). + + If a comment with unbalanced parantheses is encountered, @p + scursor is being positioned on the opening '(' of the outmost + comment. + */ + void eatCFWS( const char* & scursor, const char * const send, bool isCRLF ) KDE_EXPORT; + + bool parseDomain( const char* & scursor, const char * const send, + QString & result, bool isCRLF=false ) KDE_EXPORT; + bool parseObsRoute( const char* & scursor, const char * const send, + QStringList & result, + bool isCRLF=false, bool save=false ) KDE_EXPORT; + bool parseAddrSpec( const char* & scursor, const char * const send, + Types::AddrSpec & result, bool isCRLF=false ) KDE_EXPORT; + bool parseAngleAddr( const char* & scursor, const char * const send, + Types::AddrSpec & result, bool isCRLF=false ) KDE_EXPORT; + bool parseMailbox( const char* & scursor, const char * const send, + Types::Mailbox & result, bool isCRLF=false ) KDE_EXPORT; + bool parseGroup( const char* & scursor, const char * const send, + Types::Address & result, bool isCRLF=false ) KDE_EXPORT; + bool parseAddress( const char* & scursor, const char * const send, + Types::Address & result, bool isCRLF=false ) KDE_EXPORT; + bool parseAddressList( const char* & scursor, const char * const send, + Types::AddressList & result, bool isCRLF=false ) KDE_EXPORT; + + bool parseParameter( const char* & scursor, const char * const send, + QPair<QString,Types::QStringOrQPair> & result, + bool isCRLF=false ) KDE_EXPORT; + bool parseParameterList( const char* & scursor, const char * const send, + QMap<QString,QString> & result, bool isCRLF=false ) KDE_EXPORT; + + bool parseRawParameterList( const char* & scursor, const char * const send, + QMap<QString,Types::QStringOrQPair> & result, + bool isCRLF=false ) KDE_EXPORT; + + bool parseTime( const char* & scursor, const char * const send, + int & hour, int & min, int & sec, long int & secsEastOfGMT, + bool & timeZoneKnown, bool isCRLF=false ) KDE_EXPORT; + + bool parseDateTime( const char* & scursor, const char * const send, + Types::DateTime & result, bool isCRLF=false ) KDE_EXPORT; + +#if 0 + bool tryToMakeAnySenseOfDateString( const char* & scursor, + const char * const send, + time_t & result, bool isCRLF=false ); +#endif + +} // namespace HeaderParsing + +} // namespace KMime + + +#endif // __KMIME_HEADER_PARSING_H__ + diff --git a/libkmime/kmime_headers.cpp b/libkmime/kmime_headers.cpp new file mode 100644 index 000000000..7ae5b95bf --- /dev/null +++ b/libkmime/kmime_headers.cpp @@ -0,0 +1,1634 @@ +/* + kmime_headers.cpp + + KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001-2002 the KMime authors. + See file AUTHORS for details + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ + + +#include "kmime_headers.h" + +#include "kmime_util.h" +#include "kmime_content.h" +#include "kmime_codecs.h" +#include "kmime_header_parsing.h" +#include "kmime_warning.h" + +#include "kqcstringsplitter.h" + +#include <qtextcodec.h> +#include <qstring.h> +#include <qcstring.h> +#include <qstringlist.h> +#include <qvaluelist.h> + +#include <kglobal.h> +#include <kcharsets.h> +#include <krfcdate.h> + +#include <assert.h> + + +using namespace KMime; +using namespace KMime::Headers; +using namespace KMime::Types; +using namespace KMime::HeaderParsing; + +namespace KMime { +namespace Headers { +//-----<Base>---------------------------------- + +QCString Base::rfc2047Charset() +{ + if( (e_ncCS==0) || forceCS() ) + return defaultCS(); + else + return QCString(e_ncCS); +} + + +void Base::setRFC2047Charset(const QCString &cs) +{ + e_ncCS=cachedCharset(cs); +} + + +bool Base::forceCS() +{ + return ( p_arent!=0 ? p_arent->forceDefaultCS() : false ); +} + + +QCString Base::defaultCS() +{ + return ( p_arent!=0 ? p_arent->defaultCharset() : Latin1 ); +} + + +//-----</Base>--------------------------------- + +namespace Generics { + +//-----<GUnstructured>------------------------- + +void GUnstructured::from7BitString( const QCString & str ) +{ + d_ecoded = decodeRFC2047String( str, &e_ncCS, defaultCS(), forceCS() ); +} + +QCString GUnstructured::as7BitString( bool withHeaderType ) +{ + QCString result; + if ( withHeaderType ) + result = typeIntro(); + result += encodeRFC2047String( d_ecoded, e_ncCS ) ; + + return result; +} + +void GUnstructured::fromUnicodeString( const QString & str, + const QCString & suggestedCharset ) +{ + d_ecoded = str; + e_ncCS = cachedCharset( suggestedCharset ); +} + +QString GUnstructured::asUnicodeString() +{ + return d_ecoded; +} + +//-----</GUnstructured>------------------------- + + + +//-----<GStructured>------------------------- + +//-----</GStructured>------------------------- + + + + +//-----<GAddress>------------------------- + + +//-----</GAddress>------------------------- + + + +//-----<MailboxList>------------------------- + +bool MailboxList::parse( const char* & scursor, const char * const send, + bool isCRLF ) { + // examples: + // from := "From:" mailbox-list CRLF + // sender := "Sender:" mailbox CRLF + + // parse an address-list: + QValueList<Address> maybeAddressList; + if ( !parseAddressList( scursor, send, maybeAddressList, isCRLF ) ) + return false; + + mMailboxList.clear(); + + // extract the mailboxes and complain if there are groups: + QValueList<Address>::Iterator it; + for ( it = maybeAddressList.begin(); it != maybeAddressList.end() ; ++it ) { + if ( !(*it).displayName.isEmpty() ) { + KMIME_WARN << "mailbox groups in header disallowing them! Name: \"" + << (*it).displayName << "\"" << endl; + } + mMailboxList += (*it).mailboxList; + } + return true; +} + +//-----</MailboxList>------------------------- + + + +//-----<SingleMailbox>------------------------- + +bool SingleMailbox::parse( const char* & scursor, const char * const send, + bool isCRLF ) { + if ( !MailboxList::parse( scursor, send, isCRLF ) ) return false; + + if ( mMailboxList.count() > 1 ) { + KMIME_WARN << "multiple mailboxes in header allowing only a single one!" + << endl; + } + return true; +} + +//-----</SingleMailbox>------------------------- + + + +//-----<AddressList>------------------------- + +bool AddressList::parse( const char* & scursor, const char * const send, + bool isCRLF ) { + + QValueList<Address> maybeAddressList; + if ( !parseAddressList( scursor, send, maybeAddressList, isCRLF ) ) + return false; + + mAddressList = maybeAddressList; + return true; +} + +//-----</AddressList>------------------------- + + + +//-----<GToken>------------------------- + +bool GToken::parse( const char* & scursor, const char * const send, + bool isCRLF ) { + + eatCFWS( scursor, send, isCRLF ); + // must not be empty: + if ( scursor == send ) return false; + + QPair<const char*,int> maybeToken; + if ( !parseToken( scursor, send, maybeToken, false /* no 8bit chars */ ) ) + return false; + mToken = QCString( maybeToken.first, maybeToken.second ); + + // complain if trailing garbage is found: + eatCFWS( scursor, send, isCRLF ); + if ( scursor != send ) { + KMIME_WARN << "trailing garbage after token in header allowing " + "only a single token!" << endl; + } + return true; +} + +//-----</GToken>------------------------- + + + +//-----<GPhraseList>------------------------- + +bool GPhraseList::parse( const char* & scursor, const char * const send, + bool isCRLF ) { + + mPhraseList.clear(); + + while ( scursor != send ) { + eatCFWS( scursor, send, isCRLF ); + // empty entry ending the list: OK. + if ( scursor == send ) return true; + // empty entry: ignore. + if ( *scursor != ',' ) { scursor++; continue; } + + QString maybePhrase; + if ( !parsePhrase( scursor, send, maybePhrase, isCRLF ) ) + return false; + mPhraseList.append( maybePhrase ); + + eatCFWS( scursor, send, isCRLF ); + // non-empty entry ending the list: OK. + if ( scursor == send ) return true; + // comma separating the phrases: eat. + if ( *scursor != ',' ) scursor++; + } + return true; +} + +//-----</GPhraseList>------------------------- + + + +//-----<GDotAtom>------------------------- + +bool GDotAtom::parse( const char* & scursor, const char * const send, + bool isCRLF ) { + + QString maybeDotAtom; + if ( !parseDotAtom( scursor, send, maybeDotAtom, isCRLF ) ) + return false; + + mDotAtom = maybeDotAtom; + + eatCFWS( scursor, send, isCRLF ); + if ( scursor != send ) { + KMIME_WARN << "trailing garbage after dot-atom in header allowing " + "only a single dot-atom!" << endl; + } + return true; +} + +//-----</GDotAtom>------------------------- + + + +//-----<GParametrized>------------------------- + +//-----</GParametrized>------------------------- + + + + +//-----</GContentType>------------------------- + +bool GContentType::parse( const char* & scursor, const char * const send, + bool isCRLF ) { + + // content-type: type "/" subtype *(";" parameter) + + mMimeType = 0; + mMimeSubType = 0; + mParameterHash.clear(); + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) { + // empty header + return false; + } + + // + // type + // + + QPair<const char*,int> maybeMimeType; + if ( !parseToken( scursor, send, maybeMimeType, false /* no 8Bit */ ) ) + return false; + + mMimeType = QCString( maybeMimeType.first, maybeMimeType.second ).lower(); + + // + // subtype + // + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send || *scursor != '/' ) return false; + scursor++; + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + QPair<const char*,int> maybeSubType; + if ( !parseToken( scursor, send, maybeSubType, false /* no 8bit */ ) ) + return false; + + mMimeSubType = QCString( maybeSubType.first, maybeSubType.second ).lower(); + + // + // parameter list + // + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return true; // no parameters + + if ( *scursor != ';' ) return false; + scursor++; + + if ( !parseParameterList( scursor, send, mParameterHash, isCRLF ) ) + return false; + + return true; +} + +//-----</GContentType>------------------------- + + + +//-----<GTokenWithParameterList>------------------------- + +bool GCISTokenWithParameterList::parse( const char* & scursor, + const char * const send, bool isCRLF ) { + + mToken = 0; + mParameterHash.clear(); + + // + // token + // + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + QPair<const char*,int> maybeToken; + if ( !parseToken( scursor, send, maybeToken, false /* no 8Bit */ ) ) + return false; + + mToken = QCString( maybeToken.first, maybeToken.second ).lower(); + + // + // parameter list + // + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return true; // no parameters + + if ( *scursor != ';' ) return false; + scursor++; + + if ( !parseParameterList( scursor, send, mParameterHash, isCRLF ) ) + return false; + + return true; +} + +//-----</GTokenWithParameterList>------------------------- + + + +//-----<GIdent>------------------------- + +bool GIdent::parse( const char* & scursor, const char * const send, bool isCRLF ) { + + // msg-id := "<" id-left "@" id-right ">" + // id-left := dot-atom-text / no-fold-quote / local-part + // id-right := dot-atom-text / no-fold-literal / domain + // + // equivalent to: + // msg-id := angle-addr + + mMsgIdList.clear(); + + while ( scursor != send ) { + eatCFWS( scursor, send, isCRLF ); + // empty entry ending the list: OK. + if ( scursor == send ) return true; + // empty entry: ignore. + if ( *scursor == ',' ) { scursor++; continue; } + + AddrSpec maybeMsgId; + if ( !parseAngleAddr( scursor, send, maybeMsgId, isCRLF ) ) + return false; + mMsgIdList.append( maybeMsgId ); + + eatCFWS( scursor, send, isCRLF ); + // header end ending the list: OK. + if ( scursor == send ) return true; + // regular item separator: eat it. + if ( *scursor == ',' ) scursor++; + } + return true; +} + +//-----</GIdent>------------------------- + + + +//-----<GSingleIdent>------------------------- + +bool GSingleIdent::parse( const char* & scursor, const char * const send, bool isCRLF ) { + + if ( !GIdent::parse( scursor, send, isCRLF ) ) return false; + + if ( mMsgIdList.count() > 1 ) { + KMIME_WARN << "more than one msg-id in header " + "allowing only a single one!" << endl; + } + return true; +} + +//-----</GSingleIdent>------------------------- + + + + +} // namespace Generics + + +//-----<ReturnPath>------------------------- + +bool ReturnPath::parse( const char* & scursor, const char * const send, bool isCRLF ) { + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + const char * oldscursor = scursor; + + Mailbox maybeMailbox; + if ( !parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) { + // mailbox parsing failed, but check for empty brackets: + scursor = oldscursor; + if ( *scursor != '<' ) return false; + scursor++; + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send || *scursor != '>' ) return false; + scursor++; + + // prepare a Null mailbox: + AddrSpec emptyAddrSpec; + maybeMailbox.displayName = QString::null; + maybeMailbox.addrSpec = emptyAddrSpec; + } else + // check that there was no display-name: + if ( !maybeMailbox.displayName.isEmpty() ) { + KMIME_WARN << "display-name \"" << maybeMailbox.displayName + << "\" in Return-Path!" << endl; + } + + // see if that was all: + eatCFWS( scursor, send, isCRLF ); + // and warn if it wasn't: + if ( scursor != send ) { + KMIME_WARN << "trailing garbage after angle-addr in Return-Path!" << endl; + } + return true; +} + +//-----</ReturnPath>------------------------- + + + + +//-----<Generic>------------------------------- + +void Generic::setType(const char *type) +{ + if(t_ype) + delete[] t_ype; + if(type) { + t_ype=new char[strlen(type)+1]; + strcpy(t_ype, type); + } + else + t_ype=0; +} + +//-----<Generic>------------------------------- + + +#if !defined(KMIME_NEW_STYLE_CLASSTREE) +//-----<MessageID>----------------------------- + +void MessageID::from7BitString(const QCString &s) +{ + m_id=s; +} + + +QCString MessageID::as7BitString(bool incType) +{ + if(incType) + return ( typeIntro()+m_id ); + else + return m_id; +} + + +void MessageID::fromUnicodeString(const QString &s, const QCString&) +{ + m_id=s.latin1(); //Message-Ids can only contain us-ascii chars +} + + +QString MessageID::asUnicodeString() +{ + return QString::fromLatin1(m_id); +} + + +void MessageID::generate(const QCString &fqdn) +{ + m_id="<"+uniqueString()+"@"+fqdn+">"; +} + +//-----</MessageID>---------------------------- +#endif + + +//-----<Control>------------------------------- + +void Control::from7BitString(const QCString &s) +{ + c_trlMsg=s; +} + + +QCString Control::as7BitString(bool incType) +{ + if(incType) + return ( typeIntro()+c_trlMsg ); + else + return c_trlMsg; +} + + +void Control::fromUnicodeString(const QString &s, const QCString&) +{ + c_trlMsg=s.latin1(); +} + + +QString Control::asUnicodeString() +{ + return QString::fromLatin1(c_trlMsg); +} + +//-----</Control>------------------------------ + + + +#if !defined(KMIME_NEW_STYLE_CLASSTREE) +//-----<AddressField>-------------------------- +void AddressField::from7BitString(const QCString &s) +{ + int pos1=0, pos2=0, type=0; + QCString n; + + //so what do we have here ? + if(s.find( QRegExp("*@*(*)", false, true) )!=-1) type=2; // From: foo@bar.com (John Doe) + else if(s.find( QRegExp("*<*@*>", false, true) )!=-1) type=1; // From: John Doe <foo@bar.com> + else if(s.find( QRegExp("*@*", false, true) )!=-1) type=0; // From: foo@bar.com + else { //broken From header => just decode it + n_ame=decodeRFC2047String(s, &e_ncCS, defaultCS(), forceCS()); + return; + } + + switch(type) { + + case 0: + e_mail=s.copy(); + break; + + case 1: + pos1=0; + pos2=s.find('<'); + if(pos2!=-1) { + n=s.mid(pos1, pos2-pos1).stripWhiteSpace(); + pos1=pos2+1; + pos2=s.find('>', pos1); + if(pos2!=-1) + e_mail=s.mid(pos1, pos2-pos1).stripWhiteSpace(); + } + else return; + break; + + case 2: + pos1=0; + pos2=s.find('('); + if(pos2!=-1) { + e_mail=s.mid(pos1, pos2-pos1).stripWhiteSpace(); + pos1=pos2+1; + pos2=s.find(')', pos1); + if(pos2!=-1) + n=s.mid(pos1, pos2-pos1).stripWhiteSpace(); + } + break; + + default: break; + } + + if(!n.isEmpty()) { + removeQuots(n); + n_ame=decodeRFC2047String(n, &e_ncCS, defaultCS(), forceCS()); + } +} + + +QCString AddressField::as7BitString(bool incType) +{ + QCString ret; + + if(incType && type()[0]!='\0') + ret=typeIntro(); + + if(n_ame.isEmpty()) + ret+=e_mail; + else { + if (isUsAscii(n_ame)) { + QCString tmp(n_ame.latin1()); + addQuotes(tmp, false); + ret+=tmp; + } else { + ret+=encodeRFC2047String(n_ame, e_ncCS, true); + } + if (!e_mail.isEmpty()) + ret += " <"+e_mail+">"; + } + + return ret; +} + + +void AddressField::fromUnicodeString(const QString &s, const QCString &cs) +{ + int pos1=0, pos2=0, type=0; + QCString n; + + e_ncCS=cachedCharset(cs); + + //so what do we have here ? + if(s.find( QRegExp("*@*(*)", false, true) )!=-1) type=2; // From: foo@bar.com (John Doe) + else if(s.find( QRegExp("*<*@*>", false, true) )!=-1) type=1; // From: John Doe <foo@bar.com> + else if(s.find( QRegExp("*@*", false, true) )!=-1) type=0; // From: foo@bar.com + else { //broken From header => just copy it + n_ame=s; + return; + } + + switch(type) { + + case 0: + e_mail=s.latin1(); + break; + + case 1: + pos1=0; + pos2=s.find('<'); + if(pos2!=-1) { + n_ame=s.mid(pos1, pos2-pos1).stripWhiteSpace(); + pos1=pos2+1; + pos2=s.find('>', pos1); + if(pos2!=-1) + e_mail=s.mid(pos1, pos2-pos1).latin1(); + } + else return; + break; + + case 2: + pos1=0; + pos2=s.find('('); + if(pos2!=-1) { + e_mail=s.mid(pos1, pos2-pos1).stripWhiteSpace().latin1(); + pos1=pos2+1; + pos2=s.find(')', pos1); + if(pos2!=-1) + n_ame=s.mid(pos1, pos2-pos1).stripWhiteSpace(); + } + break; + + default: break; + } + + if(!n_ame.isEmpty()) + removeQuots(n_ame); +} + + +QString AddressField::asUnicodeString() +{ + if(n_ame.isEmpty()) + return QString(e_mail); + else { + QString s = n_ame; + if (!e_mail.isEmpty()) + s += " <"+e_mail+">"; + return s; + } +} + + +QCString AddressField::nameAs7Bit() +{ + return encodeRFC2047String(n_ame, e_ncCS); +} + + +void AddressField::setNameFrom7Bit(const QCString &s) +{ + n_ame=decodeRFC2047String(s, &e_ncCS, defaultCS(), forceCS()); +} + +//-----</AddressField>------------------------- +#endif + + +//-----<MailCopiesTo>-------------------------- + +bool MailCopiesTo::isValid() +{ + if (hasEmail()) + return true; + + if ((n_ame == "nobody") || + (n_ame == "never") || + (n_ame == "poster") || + (n_ame == "always")) + return true; + else + return false; +} + + +bool MailCopiesTo::alwaysCopy() +{ + return (hasEmail() || (n_ame == "poster") || (n_ame == "always")); +} + + +bool MailCopiesTo::neverCopy() +{ + return ((n_ame == "nobody") || (n_ame == "never")); +} + +//-----</MailCopiesTo>------------------------- + + + + +//-----<Date>---------------------------------- + +void Date::from7BitString(const QCString &s) +{ + t_ime=KRFCDate::parseDate(s); +} + + +QCString Date::as7BitString(bool incType) +{ + if(incType) + return ( typeIntro()+KRFCDate::rfc2822DateString(t_ime) ); + else + return QCString(KRFCDate::rfc2822DateString(t_ime)); +} + + +void Date::fromUnicodeString(const QString &s, const QCString&) +{ + from7BitString( QCString(s.latin1()) ); +} + + +QString Date::asUnicodeString() +{ + return QString::fromLatin1(as7BitString(false)); +} + + +QDateTime Date::qdt() +{ + QDateTime dt; + dt.setTime_t(t_ime); + return dt; +} + + +int Date::ageInDays() +{ + QDate today=QDate::currentDate(); + return ( qdt().date().daysTo(today) ); +} + +//-----</Date>--------------------------------- + + + +#if !defined(KMIME_NEW_STYLE_CLASSTREE) +//-----<To>------------------------------------ + +void To::from7BitString(const QCString &s) +{ + if(a_ddrList) + a_ddrList->clear(); + else { + a_ddrList=new QPtrList<AddressField>; + a_ddrList->setAutoDelete(true); + } + + KQCStringSplitter split; + split.init(s, ","); + bool splitOk=split.first(); + if(!splitOk) + a_ddrList->append( new AddressField(p_arent, s )); + else { + do { + a_ddrList->append( new AddressField(p_arent, split.string()) ); + } while(split.next()); + } + + e_ncCS=cachedCharset(a_ddrList->first()->rfc2047Charset()); +} + + +QCString To::as7BitString(bool incType) +{ + QCString ret; + + if(incType) + ret+=typeIntro(); + + if (a_ddrList) { + AddressField *it=a_ddrList->first(); + if (it) + ret+=it->as7BitString(false); + for (it=a_ddrList->next() ; it != 0; it=a_ddrList->next() ) + ret+=","+it->as7BitString(false); + } + + return ret; +} + + +void To::fromUnicodeString(const QString &s, const QCString &cs) +{ + if(a_ddrList) + a_ddrList->clear(); + else { + a_ddrList=new QPtrList<AddressField>; + a_ddrList->setAutoDelete(true); + } + + QStringList l=QStringList::split(",", s); + + QStringList::Iterator it=l.begin(); + for(; it!=l.end(); ++it) + a_ddrList->append(new AddressField( p_arent, (*it), cs )); + + e_ncCS=cachedCharset(cs); +} + + +QString To::asUnicodeString() +{ + if(!a_ddrList) + return QString::null; + + QString ret; + AddressField *it=a_ddrList->first(); + + if (it) + ret+=it->asUnicodeString(); + for (it=a_ddrList->next() ; it != 0; it=a_ddrList->next() ) + ret+=","+it->asUnicodeString(); + return ret; +} + + +void To::addAddress(const AddressField &a) +{ + if(!a_ddrList) { + a_ddrList=new QPtrList<AddressField>; + a_ddrList->setAutoDelete(true); + } + + AddressField *add=new AddressField(a); + add->setParent(p_arent); + a_ddrList->append(add); +} + + +void To::emails(QStrList *l) +{ + l->clear(); + + for (AddressField *it=a_ddrList->first(); it != 0; it=a_ddrList->next() ) + if( it->hasEmail() ) + l->append( it->email() ); +} + +void To::names(QStringList *l) +{ + l->clear(); + + for (AddressField *it=a_ddrList->first(); it != 0 ; it=a_ddrList->next() ) + if( it->hasName() ) + l->append( it->name() ); +} + +void To::displayNames(QStringList *l) +{ + l->clear(); + + for (AddressField *it=a_ddrList->first(); it != 0 ; it=a_ddrList->next() ) + l->append( it->asUnicodeString() ); +} + +//-----</To>----------------------------------- +#endif + + +//-----<Newsgroups>---------------------------- + +void Newsgroups::from7BitString(const QCString &s) +{ + g_roups=s; + e_ncCS=cachedCharset("UTF-8"); +} + + +QCString Newsgroups::as7BitString(bool incType) +{ + if(incType) + return (typeIntro()+g_roups); + else + return g_roups; +} + + +void Newsgroups::fromUnicodeString(const QString &s, const QCString&) +{ + g_roups=s.utf8(); + e_ncCS=cachedCharset("UTF-8"); +} + + +QString Newsgroups::asUnicodeString() +{ + return QString::fromUtf8(g_roups); +} + + +QCString Newsgroups::firstGroup() +{ + int pos=0; + if(!g_roups.isEmpty()) { + pos=g_roups.find(','); + if(pos==-1) + return g_roups; + else + return g_roups.left(pos); + } + else + return QCString(); +} + + +QStringList Newsgroups::getGroups() +{ + QStringList temp = QStringList::split(',', g_roups); + QStringList ret; + QString s; + + for (QStringList::Iterator it = temp.begin(); it != temp.end(); ++it ) { + s = (*it).simplifyWhiteSpace(); + ret.append(s); + } + + return ret; +} + +//-----</Newsgroups>--------------------------- + + + +//-----<Lines>--------------------------------- + +void Lines::from7BitString(const QCString &s) +{ + l_ines=s.toInt(); + e_ncCS=cachedCharset(Latin1); +} + + +QCString Lines::as7BitString(bool incType) +{ + QCString num; + num.setNum(l_ines); + + if(incType) + return ( typeIntro()+num ); + else + return num; +} + + +void Lines::fromUnicodeString(const QString &s, const QCString&) +{ + l_ines=s.toInt(); + e_ncCS=cachedCharset(Latin1); +} + + +QString Lines::asUnicodeString() +{ + QString num; + num.setNum(l_ines); + + return num; +} + +//-----</Lines>-------------------------------- + + + +#if !defined(KMIME_NEW_STYLE_CLASSTREE) +//-----<References>---------------------------- + +void References::from7BitString(const QCString &s) +{ + r_ef=s; + e_ncCS=cachedCharset(Latin1); +} + + +QCString References::as7BitString(bool incType) +{ + if(incType) + return ( typeIntro()+r_ef ); + else + return r_ef; +} + + +void References::fromUnicodeString(const QString &s, const QCString&) +{ + r_ef=s.latin1(); + e_ncCS=cachedCharset(Latin1); +} + + +QString References::asUnicodeString() +{ + return QString::fromLatin1(r_ef); +} + + +int References::count() +{ + int cnt1=0, cnt2=0; + unsigned int r_efLen=r_ef.length(); + char *dataPtr=r_ef.data(); + for(unsigned int i=0; i<r_efLen; i++) { + if(dataPtr[i]=='<') cnt1++; + else if(dataPtr[i]=='>') cnt2++; + } + + if(cnt1<cnt2) return cnt1; + else return cnt2; +} + + +QCString References::first() +{ + p_os=-1; + return next(); +} + + +QCString References::next() +{ + int pos1=0, pos2=0; + QCString ret; + + if(p_os!=0) { + pos2=r_ef.findRev('>', p_os); + p_os=0; + if(pos2!=-1) { + pos1=r_ef.findRev('<', pos2); + if(pos1!=-1) { + ret=r_ef.mid(pos1, pos2-pos1+1); + p_os=pos1; + } + } + } + return ret; +} + + +QCString References::at(unsigned int i) +{ + QCString ret; + int pos1=0, pos2=0; + unsigned int cnt=0; + + while(pos1!=-1 && cnt < i+1) { + pos2=pos1-1; + pos1=r_ef.findRev('<', pos2); + cnt++; + } + + if(pos1!=-1) { + pos2=r_ef.find('>', pos1); + if(pos2!=-1) + ret=r_ef.mid(pos1, pos2-pos1+1); + } + + return ret; +} + + +void References::append(const QCString &s) +{ + QString temp=r_ef.data(); + temp += " "; + temp += s.data(); + QStringList lst=QStringList::split(' ',temp); + QRegExp exp("^<.+@.+>$"); + + // remove bogus references + QStringList::Iterator it = lst.begin(); + while (it != lst.end()) { + if (-1==(*it).find(exp)) + it = lst.remove(it); + else + it++; + } + + if (lst.isEmpty()) { + r_ef = s.copy(); // shouldn't happen... + return; + } else + r_ef = ""; + + temp = lst.first(); // include the first id + r_ef = temp.latin1(); + lst.remove(temp); // avoids duplicates + int insPos = r_ef.length(); + + for (int i=1;i<=3;i++) { // include the last three ids + if (!lst.isEmpty()) { + temp = lst.last(); + r_ef.insert(insPos,(QString(" %1").arg(temp)).latin1()); + lst.remove(temp); + } else + break; + } + + while (!lst.isEmpty()) { // now insert the rest, up to 1000 characters + temp = lst.last(); + if ((15+r_ef.length()+temp.length())<1000) { + r_ef.insert(insPos,(QString(" %1").arg(temp)).latin1()); + lst.remove(temp); + } else + return; + } +} + +//-----</References>--------------------------- +#endif + + +//-----<UserAgent>----------------------------- + +void UserAgent::from7BitString(const QCString &s) +{ + u_agent=s; + e_ncCS=cachedCharset(Latin1); +} + + +QCString UserAgent::as7BitString(bool incType) +{ + if(incType) + return ( typeIntro()+u_agent ); + else + return u_agent; +} + + +void UserAgent::fromUnicodeString(const QString &s, const QCString&) +{ + u_agent=s.latin1(); + e_ncCS=cachedCharset(Latin1); +} + + +QString UserAgent::asUnicodeString() +{ + return QString::fromLatin1(u_agent); +} + +//-----</UserAgent>---------------------------- + + + +#if !defined(KMIME_NEW_STYLE_CLASSTREE) +//-----<Content-Type>-------------------------- + +void ContentType::from7BitString(const QCString &s) +{ + int pos=s.find(';'); + + if(pos==-1) + m_imeType=s.simplifyWhiteSpace(); + else { + m_imeType=s.left(pos).simplifyWhiteSpace(); + p_arams=s.mid(pos, s.length()-pos).simplifyWhiteSpace(); + } + + if(isMultipart()) + c_ategory=CCcontainer; + else + c_ategory=CCsingle; + + e_ncCS=cachedCharset(Latin1); +} + + +QCString ContentType::as7BitString(bool incType) +{ + if(incType) + return (typeIntro()+m_imeType+p_arams); + else + return (m_imeType+p_arams); +} + + +void ContentType::fromUnicodeString(const QString &s, const QCString&) +{ + from7BitString( QCString(s.latin1()) ); +} + + +QString ContentType::asUnicodeString() +{ + return QString::fromLatin1(as7BitString(false)); +} + + +QCString ContentType::mediaType() +{ + int pos=m_imeType.find('/'); + if(pos==-1) + return m_imeType; + else + return m_imeType.left(pos); +} + + +QCString ContentType::subType() +{ + int pos=m_imeType.find('/'); + if(pos==-1) + return QCString(); + else + return m_imeType.mid(pos, m_imeType.length()-pos); +} + + +void ContentType::setMimeType(const QCString &s) +{ + p_arams.resize(0); + m_imeType=s; + + if(isMultipart()) + c_ategory=CCcontainer; + else + c_ategory=CCsingle; +} + + +bool ContentType::isMediatype(const char *s) +{ + return ( strncasecmp(m_imeType.data(), s, strlen(s)) ); +} + + +bool ContentType::isSubtype(const char *s) +{ + char *c=strchr(m_imeType.data(), '/'); + + if( (c==0) || (*(c+1)=='\0') ) + return false; + else + return ( strcasecmp(c+1, s)==0 ); +} + + +bool ContentType::isText() +{ + return (strncasecmp(m_imeType.data(), "text", 4)==0); +} + + +bool ContentType::isPlainText() +{ + return (strcasecmp(m_imeType.data(), "text/plain")==0); +} + + +bool ContentType::isHTMLText() +{ + return (strcasecmp(m_imeType.data(), "text/html")==0); +} + + +bool ContentType::isImage() +{ + return (strncasecmp(m_imeType.data(), "image", 5)==0); +} + + +bool ContentType::isMultipart() +{ + return (strncasecmp(m_imeType.data(), "multipart", 9)==0); +} + + +bool ContentType::isPartial() +{ + return (strcasecmp(m_imeType.data(), "message/partial")==0); +} + + +QCString ContentType::charset() +{ + QCString ret=getParameter("charset"); + if( ret.isEmpty() || forceCS() ) { //we return the default-charset if necessary + ret=defaultCS(); + } + return ret; +} + + +void ContentType::setCharset(const QCString &s) +{ + setParameter("charset", s); +} + + +QCString ContentType::boundary() +{ + return getParameter("boundary"); +} + + +void ContentType::setBoundary(const QCString &s) +{ + setParameter("boundary", s, true); +} + + +QString ContentType::name() +{ + const char *dummy=0; + return ( decodeRFC2047String(getParameter("name"), &dummy, defaultCS(), forceCS()) ); +} + + +void ContentType::setName(const QString &s, const QCString &cs) +{ + e_ncCS=cs; + + if (isUsAscii(s)) { + QCString tmp(s.latin1()); + addQuotes(tmp, true); + setParameter("name", tmp, false); + } else { + // FIXME: encoded words can't be enclosed in quotes!! + setParameter("name", encodeRFC2047String(s, cs), true); + } +} + + +QCString ContentType::id() +{ + return (getParameter("id")); +} + + +void ContentType::setId(const QCString &s) +{ + setParameter("id", s, true); +} + + +int ContentType::partialNumber() +{ + QCString p=getParameter("number"); + if(!p.isEmpty()) + return p.toInt(); + else + return -1; +} + + +int ContentType::partialCount() +{ + QCString p=getParameter("total"); + if(!p.isEmpty()) + return p.toInt(); + else + return -1; +} + + +void ContentType::setPartialParams(int total, int number) +{ + QCString num; + num.setNum(number); + setParameter("number", num); + num.setNum(total); + setParameter("total", num); +} + + +QCString ContentType::getParameter(const char *name) +{ + QCString ret; + int pos1=0, pos2=0; + pos1=p_arams.find(name, 0, false); + if(pos1!=-1) { + if( (pos2=p_arams.find(';', pos1))==-1 ) + pos2=p_arams.length(); + pos1+=strlen(name)+1; + ret=p_arams.mid(pos1, pos2-pos1); + removeQuots(ret); + } + return ret; +} + + +void ContentType::setParameter(const QCString &name, const QCString &value, bool doubleQuotes) +{ + int pos1=0, pos2=0; + QCString param; + + if(doubleQuotes) + param=name+"=\""+value+"\""; + else + param=name+"="+value; + + pos1=p_arams.find(name, 0, false); + if(pos1==-1) { + p_arams+="; "+param; + } + else { + pos2=p_arams.find(';', pos1); + if(pos2==-1) + pos2=p_arams.length(); + p_arams.remove(pos1, pos2-pos1); + p_arams.insert(pos1, param); + } +} + +//-----</Content-Type>------------------------- + + + +//-----<CTEncoding>---------------------------- + +typedef struct { const char *s; int e; } encTableType; + +static const encTableType encTable[] = { { "7Bit", CE7Bit }, + { "8Bit", CE8Bit }, + { "quoted-printable", CEquPr }, + { "base64", CEbase64 }, + { "x-uuencode", CEuuenc }, + { "binary", CEbinary }, + { 0, 0} }; + + +void CTEncoding::from7BitString(const QCString &s) +{ + QCString stripped(s.simplifyWhiteSpace()); + c_te=CE7Bit; + for(int i=0; encTable[i].s!=0; i++) + if(strcasecmp(stripped.data(), encTable[i].s)==0) { + c_te=(contentEncoding)encTable[i].e; + break; + } + d_ecoded=( c_te==CE7Bit || c_te==CE8Bit ); + + e_ncCS=cachedCharset(Latin1); +} + + +QCString CTEncoding::as7BitString(bool incType) +{ + QCString str; + for(int i=0; encTable[i].s!=0; i++) + if(c_te==encTable[i].e) { + str=encTable[i].s; + break; + } + + if(incType) + return ( typeIntro()+str ); + else + return str; +} + + +void CTEncoding::fromUnicodeString(const QString &s, const QCString&) +{ + from7BitString( QCString(s.latin1()) ); +} + + +QString CTEncoding::asUnicodeString() +{ + return QString::fromLatin1(as7BitString(false)); +} + +//-----</CTEncoding>--------------------------- + + + +//-----<CDisposition>-------------------------- + +void CDisposition::from7BitString(const QCString &s) +{ + if(strncasecmp(s.data(), "attachment", 10)==0) + d_isp=CDattachment; + else d_isp=CDinline; + + int pos=s.find("filename=", 0, false); + QCString fn; + if(pos>-1) { + pos+=9; + fn=s.mid(pos, s.length()-pos); + removeQuots(fn); + f_ilename=decodeRFC2047String(fn, &e_ncCS, defaultCS(), forceCS()); + } +} + + +QCString CDisposition::as7BitString(bool incType) +{ + QCString ret; + if(d_isp==CDattachment) + ret="attachment"; + else + ret="inline"; + + if(!f_ilename.isEmpty()) { + if (isUsAscii(f_ilename)) { + QCString tmp(f_ilename.latin1()); + addQuotes(tmp, true); + ret+="; filename="+tmp; + } else { + // FIXME: encoded words can't be enclosed in quotes!! + ret+="; filename=\""+encodeRFC2047String(f_ilename, e_ncCS)+"\""; + } + } + + if(incType) + return ( typeIntro()+ret ); + else + return ret; +} + + +void CDisposition::fromUnicodeString(const QString &s, const QCString &cs) +{ + if(strncasecmp(s.latin1(), "attachment", 10)==0) + d_isp=CDattachment; + else d_isp=CDinline; + + int pos=s.find("filename=", 0, false); + if(pos>-1) { + pos+=9; + f_ilename=s.mid(pos, s.length()-pos); + removeQuots(f_ilename); + } + + e_ncCS=cachedCharset(cs); +} + + +QString CDisposition::asUnicodeString() +{ + QString ret; + if(d_isp==CDattachment) + ret="attachment"; + else + ret="inline"; + + if(!f_ilename.isEmpty()) + ret+="; filename=\""+f_ilename+"\""; + + return ret; +} + +//-----</CDisposition>------------------------- +#endif +} // namespace Headers + +} // namespace KMime diff --git a/libkmime/kmime_headers.h b/libkmime/kmime_headers.h new file mode 100644 index 000000000..779cd341a --- /dev/null +++ b/libkmime/kmime_headers.h @@ -0,0 +1,861 @@ +/* -*- c++ -* + kmime_headers.h + + KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001-2002 the KMime authors. + See file AUTHORS for details + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2.0 as + published by the Free Software Foundation. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ +#ifndef __KMIME_HEADERS_H__ +#define __KMIME_HEADERS_H__ + +// Content: +// +// - header's base class defining the common interface +// - generic base classes for different types of fields +// - incompatible, GStructured-based field classes +// - compatible, GUnstructured-based field classes + +#include "kmime_header_parsing.h" + +#include <qstring.h> +#include <qstrlist.h> +#include <qstringlist.h> +#include <qregexp.h> +#include <qdatetime.h> +#include <qasciidict.h> +#include <qmap.h> +#include <qptrlist.h> + +#include <time.h> + +#include <kdepimmacros.h> + +namespace KMime { + +//forward declaration +class Content; + +namespace Headers { + + +enum contentCategory { CCsingle, + CCcontainer, + CCmixedPart, + CCalternativePart }; + +enum contentEncoding { CE7Bit, + CE8Bit, + CEquPr, + CEbase64, + CEuuenc, + CEbinary }; + +enum contentDisposition { CDinline, + CDattachment, + CDparallel }; + +//often used charset +static const QCString Latin1("ISO-8859-1"); + +#define mk_trivial_subclass_with_name( subclass, subclassName, baseclass ) \ +class subclass : public Generics::baseclass { \ +public: \ + subclass() : Generics::baseclass() {} \ + subclass( Content * p ) : Generics::baseclass( p ) {} \ + subclass( Content * p, const QCString & s ) \ + : Generics::baseclass( p ) { from7BitString( s ); } \ + subclass( Content * p, const QString & s, const QCString & cs ) \ + : Generics::baseclass( p ) { fromUnicodeString( s, cs ); } \ + ~subclass() {} \ + \ + const char * type() const { return #subclassName; } \ +} + +#define mk_trivial_subclass( subclass, baseclass ) \ +mk_trivial_subclass_with_name( subclass, subclass, baseclass ) + +#define mk_parsing_subclass_with_name( subclass, subclassName, baseclass ) \ +class subclass : public Generics::baseclass { \ +public: \ + subclass() : Generics::baseclass() {} \ + subclass( Content * p ) : Generics::baseclass( p ) {} \ + subclass( Content * p, const QCString & s ) \ + : Generics::baseclass( p ) { from7BitString( s ); } \ + subclass( Content * p, const QString & s, const QCString & cs ) \ + : Generics::baseclass( p ) { fromUnicodeString( s, cs ); } \ + ~subclass() {} \ + \ + const char * type() const { return #subclassName; } \ +protected: \ + bool parse( const char* & scursor, const char * const send, bool isCRLF=false ); \ +} + +#define mk_parsing_subclass( subclass, baseclass ) \ +mk_parsing_subclass_with_name( subclass, subclass, baseclass ) + +// +// +// HEADER'S BASE CLASS. DEFINES THE COMMON INTERFACE +// +// + +/** Baseclass of all header-classes. It represents a + header-field as described in RFC-822. */ +class KDE_EXPORT Base { + + public: + typedef QPtrList<Base> List; + + /** Create an empty header. */ + Base() : e_ncCS(0), p_arent(0) {} + + /** Create an empty header with a parent-content. */ + Base(KMime::Content *parent) : e_ncCS(0), p_arent(parent) {} + + /** Destructor */ + virtual ~Base() {} + + /** Return the parent of this header. */ + KMime::Content* parent() { return p_arent; } + + /** Set the parent for this header. */ + void setParent(KMime::Content *p) { p_arent=p; } + + /** Parse the given string. Take care of RFC2047-encoded + strings. A default charset is given. If the last parameter + is true the default charset is used in any case */ + virtual void from7BitString(const QCString&) {} + + /** Return the encoded header. The parameter specifies + whether the header-type should be included. */ + virtual QCString as7BitString(bool=true) { return QCString(); } + + /** Return the charset that is used for RFC2047-encoding */ + QCString rfc2047Charset(); + + /** Set the charset for RFC2047-encoding */ + void setRFC2047Charset(const QCString &cs); + + /** Return the default charset */ + QCString defaultCS(); + + /** Return if the default charset is mandatory */ + bool forceCS(); + + /** Parse the given string and set the charset. */ + virtual void fromUnicodeString(const QString&, const QCString&) {} + + /** Return the decoded content of the header without + the header-type. */ + virtual QString asUnicodeString() { return QString(); } + + /** Delete */ + virtual void clear() {} + + /** Do we have data? */ + virtual bool isEmpty() { return false; } + + /** Return the type of this header (e.g. "From") */ + virtual const char* type() { return ""; } + + /** Check if this header is of type t. */ + bool is(const char* t) { return (strcasecmp(t, type())==0); } + + /** Check if this header is a MIME header */ + bool isMimeHeader() { return (strncasecmp(type(), "Content-", 8)==0); } + + /** Check if this header is a X-Header */ + bool isXHeader() { return (strncmp(type(), "X-", 2)==0); } + + protected: + QCString typeIntro() { return (QCString(type())+": "); } + + const char *e_ncCS; + Content *p_arent; + +}; + + +// +// +// GENERIC BASE CLASSES FOR DIFFERENT TYPES OF FIELDS +// +// + +namespace Generics { + +/** Abstract base class for unstructured header fields + (e.g. "Subject", "Comment", "Content-description"). + + Features: Decodes the header according to RFC2047, incl. RFC2231 + extensions to encoded-words. + + Subclasses need only re-implement @p const @p char* @p type(). + + A macro to automate this is named +\code + MK_TRIVIAL_GUnstructured_SUBCLASS(classname,headername); +\endcode + + The ContentDescription class then reads: +\code + MK_TRIVIAL_GUnstructured_SUBCLASS(ContentDescription,Content-Description); +\endcode +*/ + + // known issues: + // - uses old decodeRFC2047String function, instead of our own... + +class KDE_EXPORT GUnstructured : public Base { + +public: + GUnstructured() : Base() {} + GUnstructured( Content * p ) : Base( p ) {} + GUnstructured( Content * p, const QCString & s ) + : Base( p ) { from7BitString(s); } + GUnstructured( Content * p, const QString & s, const QCString & cs ) + : Base( p ) { fromUnicodeString( s, cs ); } + ~GUnstructured() {} + + virtual void from7BitString( const QCString& str ); + virtual QCString as7BitString( bool withHeaderType=true ); + + virtual void fromUnicodeString( const QString & str, + const QCString & suggestedCharset); + virtual QString asUnicodeString(); + + virtual void clear() { d_ecoded.truncate(0); } + virtual bool isEmpty() { return (d_ecoded.isEmpty()); } + +private: + QString d_ecoded; +}; + +/** This is the base class for all structured header fields. It + contains parsing methods for all basic token types found in + rfc2822. + + @section Parsing + + At the basic level, there are tokens & tspecials (rfc2045), + atoms & specials, quoted-strings, domain-literals (all rfc822) and + encoded-words (rfc2047). + + As a special token, we have the comment. It is one of the basic + tokens defined in rfc822, but it's parsing relies in part on the + basic token parsers (e.g. comments may contain encoded-words). + Also, most upper-level parsers (notably those for phrase and + dot-atom) choose to ignore any comment when parsing. + + Then there are the real composite tokens, which are made up of one + or more of the basic tokens (and semantically invisible comments): + phrases (rfc822 with rfc2047) and dot-atoms (rfc2822). + + This finishes the list of supported token types. Subclasses will + provide support for more higher-level tokens, where necessary, + using these parsers. + + @short Base class for structured header fields. + @author Marc Mutz <mutz@kde.org> +*/ + +class KDE_EXPORT GStructured : public Base { +public: + GStructured() : Base() {} + GStructured( Content * p ) : Base( p ) {} + GStructured( Content * p, const QCString & s ) + : Base( p ) { from7BitString(s); } + GStructured( Content * p, const QString & s, const QCString & cs ) + : Base( p ) { fromUnicodeString( s, cs ); } + ~GStructured() {} + + +protected: +#if 0 + // the assembly squad: + + bool writeAtom( char* & dcursor, const char * const dend, const QString & input ); + bool writeAtom( char* & dcursor, const char * const dend, + const QPair<const char*,int> & input ); + bool writeToken( char* & dcursor, const char * const dend, const QString & input ); + bool writeToken( char* & dcursor, const char * const dend, + const QPair<const char*int> & input ); + + bool writeGenericQuotedString( char* & dcursor, const char * const dend, + const QString & input, bool withCRLF=false ); + bool writeComment( char* & dcursor, const char * const dend, + const QString & input, bool withCRLF=false ); + bool writePhrase( char* & dcursor, const char * const dend, + const QString & input, bool withCRLF=false ); + bool writeDotAtom( char* & dcursor, const char * const dend, + const QString & input, bool withCRLF=false ); +#endif +}; + + +class KDE_EXPORT GAddress : public GStructured { +public: + GAddress() : GStructured() {} + GAddress( Content * p ) : GStructured( p ) {} + GAddress( Content * p, const QCString & s ) + : GStructured( p ) { from7BitString(s); } + GAddress( Content * p, const QString & s, const QCString & cs ) + : GStructured( p ) { fromUnicodeString( s, cs ); } + ~GAddress() {} + +protected: +}; + + +/** Base class for headers that deal with (possibly multiple) + addresses, but don't allow groups: */ +class KDE_EXPORT MailboxList : public GAddress { +public: + MailboxList() : GAddress() {} + MailboxList( Content * p ) : GAddress( p ) {} + MailboxList( Content * p, const QCString & s ) + : GAddress( p ) { from7BitString(s); } + MailboxList( Content * p, const QString & s, const QCString & cs ) + : GAddress( p ) { fromUnicodeString( s, cs ); } + ~MailboxList() {} + +protected: + bool parse( const char* & scursor, const char * const send, bool isCRLF=false ); + + /** The list of mailboxes */ + QValueList<Types::Mailbox> mMailboxList; +}; + + +/** Base class for headers that deal with exactly one mailbox + (e.g. Sender) */ +mk_parsing_subclass(SingleMailbox,MailboxList); + +/** Base class for headers that deal with (possibly multiple) + addresses, allowing groups. */ +class KDE_EXPORT AddressList : public GAddress { +public: + AddressList() : GAddress() {} + AddressList( Content * p ) : GAddress( p ) {} + AddressList( Content * p, const QCString & s ) + : GAddress( p ) { from7BitString(s); } + AddressList( Content * p, const QString & s, const QCString & cs ) + : GAddress( p ) { fromUnicodeString( s, cs ); } + ~AddressList() {} + +protected: + bool parse( const char* & scursor, const char * const send, bool isCRLF=false ); + + /** The list of addresses */ + QValueList<Types::Address> mAddressList; +}; + +/** Base class for headers which deal with a list of msg-id's */ +class KDE_EXPORT GIdent : public GAddress { +public: + GIdent() : GAddress() {} + GIdent( Content * p ) : GAddress( p ) {} + GIdent( Content * p, const QCString & s ) + : GAddress( p ) { from7BitString(s); } + GIdent( Content * p, const QString & s, const QCString & cs ) + : GAddress( p ) { fromUnicodeString( s, cs ); } + ~GIdent() {} + +protected: + bool parse( const char* & scursor, const char * const send, bool isCRLF=false ); + + /** The list of msg-id's */ + QValueList<Types::AddrSpec> mMsgIdList; +}; + +/** Base class for headers which deal with a list of msg-id's */ +mk_parsing_subclass(GSingleIdent,GIdent); + +/** Base class for headers which deal with a single atom. */ +class KDE_EXPORT GToken : public GStructured { +public: + GToken() : GStructured() {} + GToken( Content * p ) : GStructured( p ) {} + GToken( Content * p, const QCString & s ) + : GStructured( p ) { from7BitString(s); } + GToken( Content * p, const QString & s, const QCString & cs ) + : GStructured( p ) { fromUnicodeString( s, cs ); } + ~GToken() {} + +protected: + bool parse( const char* & scursor, const char * const send, bool isCRLF=false ); + + QCString mToken; +}; + + +class KDE_EXPORT GPhraseList : public GStructured { +public: + GPhraseList() : GStructured() {} + GPhraseList( Content * p ) : GStructured( p ) {} + GPhraseList( Content * p, const QCString & s ) + : GStructured( p ) { from7BitString(s); } + GPhraseList( Content * p, const QString & s, const QCString & cs ) + : GStructured( p ) { fromUnicodeString( s, cs ); } + ~GPhraseList() {} + +protected: + bool parse( const char* & scursor, const char * const send, bool isCRLF=false ); + + QStringList mPhraseList; +}; + +class KDE_EXPORT GDotAtom : public GStructured { +public: + GDotAtom() : GStructured() {} + GDotAtom( Content * p ) : GStructured( p ) {} + GDotAtom( Content * p, const QCString & s ) + : GStructured( p ) { from7BitString(s); } + GDotAtom( Content * p, const QString & s, const QCString & cs ) + : GStructured( p ) { fromUnicodeString( s, cs ); } + ~GDotAtom() {} + +protected: + bool parse( const char* & scursor, const char * const send, bool isCRLF=false ); + + QString mDotAtom; +}; + +class KDE_EXPORT GParametrized : public GStructured { +public: + GParametrized() : GStructured() {} + GParametrized( Content * p ) : GStructured( p ) {} + GParametrized( Content * p, const QCString & s ) + : GStructured( p ) { from7BitString(s); } + GParametrized( Content * p, const QString & s, const QCString & cs ) + : GStructured( p ) { fromUnicodeString( s, cs ); } + ~GParametrized() {} + +protected: + QMap<QString,QString> mParameterHash; + +private: +}; + +class KDE_EXPORT GContentType : public GParametrized { +public: + GContentType() : GParametrized() {} + GContentType( Content * p ) : GParametrized( p ) {} + GContentType( Content * p, const QCString & s ) + : GParametrized( p ) { from7BitString(s); } + GContentType( Content * p, const QString & s, const QCString & cs ) + : GParametrized( p ) { fromUnicodeString( s, cs ); } + ~GContentType() {} + +protected: + bool parse( const char* & scursor, const char * const send, bool isCRLF=false ); + + QCString mMimeType; + QCString mMimeSubType; +}; + + +class KDE_EXPORT GCISTokenWithParameterList : public GParametrized { +public: + GCISTokenWithParameterList() : GParametrized() {} + GCISTokenWithParameterList( Content * p ) : GParametrized( p ) {} + GCISTokenWithParameterList( Content * p, const QCString & s ) + : GParametrized( p ) { from7BitString(s); } + GCISTokenWithParameterList( Content * p, const QString & s, const QCString & cs ) + : GParametrized( p ) { fromUnicodeString( s, cs ); } + ~GCISTokenWithParameterList() {} + +protected: + bool parse( const char* & scursor, const char * const send, bool isCRLF=false ); + + QCString mToken; +}; + + +} // namespace Generics + +// +// +// INCOMPATIBLE, GSTRUCTURED-BASED FIELDS: +// +// + + +/** Represents the Return-Path header field. */ +class KDE_EXPORT ReturnPath : public Generics::GAddress { +public: + ReturnPath() : Generics::GAddress() {} + ReturnPath( Content * p ) : Generics::GAddress( p ) {} + ReturnPath( Content * p, const QCString & s ) + : Generics::GAddress( p ) { from7BitString(s); } + ReturnPath( Content * p, const QString & s, const QCString & cs ) + : Generics::GAddress( p ) { fromUnicodeString( s, cs ); } + ~ReturnPath() {} + + const char * type() const { return "Return-Path"; } + +protected: + bool parse( const char* & scursor, const char * const send, bool isCRLF=false ); +}; + +#if defined(KMIME_NEW_STYLE_CLASSTREE) +// classes whose names collide with earlier ones: + +// GAddress et al.: + +// rfc(2)822 headers: +mk_trivial_subclass(From,MailboxList); +mk_trivial_subclass(Sender,SingleMailbox); +mk_trivial_subclass_with_name(ReplyTo,Reply-To,AddressList); +mk_trivial_subclass(Cc,AddressList); +mk_trivial_subclass(Bcc,AddressList); +// usefor headers: +mk_trivial_subclass_with_name(MailCopiesTo,Mail-Copies-To,AddressList); + +// GToken: + +mk_trivial_subclass_with_name(ContentTransferEncoding, + Content-Transfer-Encoding,GToken); + +// GPhraseList: + +mk_trivial_subclass(Keywords,GPhraseList); + +// GDotAtom: + +mk_trivial_subclass_with_name(MIMEVersion,MIME-Version,GDotAtom); + +// GIdent: + +mk_trivial_subclass_with_name(MessageID,Message-ID,GSingleIdent); +mk_trivial_subclass_with_name(ContentID,Content-ID,GSingleIdent); +mk_trivial_subclass(Supersedes,GSingleIdent); +mk_trivial_subclass_with_name(InReplyTo,In-Reply-To,GIdent); +mk_trivial_subclass(References,GIdent); + +// GContentType: + +mk_trivial_subclass_with_name(ContentType,ContentType,GContentType); + +// GCISTokenWithParameterList: + +mk_trivial_subclass_with_name(ContentDisposition,Content-Disposition, + GCISTokenWithParameterList); + + +#endif + + +// +// +// COMPATIBLE GUNSTRUCTURED-BASED FIELDS: +// +// + + +/** Represents an arbitrary header, that can contain + any header-field. + Adds a type over GUnstructured. + @see GUnstructured +*/ +class KDE_EXPORT Generic : public Generics::GUnstructured { + + public: + Generic() : Generics::GUnstructured(), t_ype(0) {} + Generic(const char *t) + : Generics::GUnstructured(), t_ype(0) { setType(t); } + Generic(const char *t, Content *p) + : Generics::GUnstructured( p ), t_ype(0) { setType(t); } + Generic(const char *t, Content *p, const QCString &s) + : Generics::GUnstructured( p, s ), t_ype(0) { setType(t); } + Generic(const char *t, Content *p, const QString &s, const QCString &cs) + : Generics::GUnstructured( p, s, cs ), t_ype(0) { setType(t); } + ~Generic() { delete[] t_ype; } + + virtual void clear() { delete[] t_ype; GUnstructured::clear(); } + virtual bool isEmpty() { return (t_ype==0 || GUnstructured::isEmpty()); } + virtual const char* type() { return t_ype; } + void setType(const char *type); + + protected: + char *t_ype; + +}; + + +/** Represents a "Subject" header */ +class KDE_EXPORT Subject : public Generics::GUnstructured { + + public: + Subject() : Generics::GUnstructured() {} + Subject( Content * p ) : Generics::GUnstructured( p ) {} + Subject( Content * p, const QCString & s ) + : Generics::GUnstructured( p, s ) {} + Subject( Content * p, const QString & s, const QCString & cs ) + : Generics::GUnstructured( p, s, cs ) {} + ~Subject() {} + + virtual const char* type() { return "Subject"; } + + bool isReply() { + return ( asUnicodeString().find( QString("Re:"), 0, false ) == 0 ); + } +}; + +/** Represents a "Organization" header */ +class KDE_EXPORT Organization : public Generics::GUnstructured { + + public: + Organization() : Generics::GUnstructured() {} + Organization( Content * p ) : Generics::GUnstructured( p ) {} + Organization( Content * p, const QCString & s ) + : Generics::GUnstructured( p, s ) {}; + Organization( Content * p, const QString & s, const QCString & cs) + : Generics::GUnstructured( p, s, cs ) {} + ~Organization() {} + + virtual const char* type() { return "Organization"; } + +}; + +// +// +// NOT YET CONVERTED STUFF BELOW: +// +// + + + +/** Represents a "Control" header */ +class KDE_EXPORT Control : public Base { + + public: + Control() : Base() {} + Control(Content *p) : Base(p) {} + Control(Content *p, const QCString &s) : Base(p) { from7BitString(s); } + Control(Content *p, const QString &s) : Base(p) { fromUnicodeString(s, Latin1); } + ~Control() {} + + virtual void from7BitString(const QCString &s); + virtual QCString as7BitString(bool incType=true); + virtual void fromUnicodeString(const QString &s, const QCString&); + virtual QString asUnicodeString(); + virtual void clear() { c_trlMsg.truncate(0); } + virtual bool isEmpty() { return (c_trlMsg.isEmpty()); } + virtual const char* type() { return "Control"; } + + bool isCancel() { return (c_trlMsg.find("cancel", 0, false)!=-1); } + + protected: + QCString c_trlMsg; + +}; + +/** Represents a "Date" header */ +class KDE_EXPORT Date : public Base { + + public: + Date() : Base(), t_ime(0) {} + Date(Content *p) : Base(p), t_ime(0) {} + Date(Content *p, time_t t) : Base(p), t_ime(t) {} + Date(Content *p, const QCString &s) : Base(p) { from7BitString(s); } + Date(Content *p, const QString &s) : Base(p) { fromUnicodeString(s, Latin1); } + ~Date() {} + + virtual void from7BitString(const QCString &s); + virtual QCString as7BitString(bool incType=true); + virtual void fromUnicodeString(const QString &s, const QCString&); + virtual QString asUnicodeString(); + virtual void clear() { t_ime=0; } + virtual bool isEmpty() { return (t_ime==0); } + virtual const char* type() { return "Date"; } + + time_t unixTime() { return t_ime; } + void setUnixTime(time_t t) { t_ime=t; } + void setUnixTime() { t_ime=time(0); } + QDateTime qdt(); + int ageInDays(); + + protected: + time_t t_ime; + +}; + + +/** Represents a "Newsgroups" header */ +class KDE_EXPORT Newsgroups : public Base { + + public: + Newsgroups() : Base() {} + Newsgroups(Content *p) : Base(p) {} + Newsgroups(Content *p, const QCString &s) : Base(p) { from7BitString(s); } + Newsgroups(Content *p, const QString &s) : Base(p) { fromUnicodeString(s, Latin1); } + ~Newsgroups() {} + + virtual void from7BitString(const QCString &s); + virtual QCString as7BitString(bool incType=true); + virtual void fromUnicodeString(const QString &s, const QCString&); + virtual QString asUnicodeString(); + virtual void clear() { g_roups.resize(0); } + virtual bool isEmpty() { return g_roups.isEmpty(); } + virtual const char* type() { return "Newsgroups"; } + + QCString firstGroup(); + bool isCrossposted() { return ( g_roups.find(',')>-1 ); } + QStringList getGroups(); + + protected: + QCString g_roups; + +}; + + +/** Represents a "Followup-To" header */ +class KDE_EXPORT FollowUpTo : public Newsgroups { + + public: + FollowUpTo() : Newsgroups() {} + FollowUpTo(Content *p) : Newsgroups(p) {} + FollowUpTo(Content *p, const QCString &s) : Newsgroups(p,s) {} + FollowUpTo(Content *p, const QString &s) : Newsgroups(p,s) {} + ~FollowUpTo() {} + + virtual const char* type() { return "Followup-To"; } + +}; + + +/** Represents a "Lines" header */ +class KDE_EXPORT Lines : public Base { + + public: + Lines() : Base(),l_ines(-1) {} + Lines(Content *p) : Base(p),l_ines(-1) {} + Lines(Content *p, unsigned int i) : Base(p),l_ines(i) {} + Lines(Content *p, const QCString &s) : Base(p) { from7BitString(s); } + Lines(Content *p, const QString &s) : Base(p) { fromUnicodeString(s, Latin1); } + ~Lines() {} + + virtual void from7BitString(const QCString &s); + virtual QCString as7BitString(bool incType=true); + virtual void fromUnicodeString(const QString &s, const QCString&); + virtual QString asUnicodeString(); + virtual void clear() { l_ines=-1; } + virtual bool isEmpty() { return (l_ines==-1); } + virtual const char* type() { return "Lines"; } + + int numberOfLines() { return l_ines; } + void setNumberOfLines(int i) { l_ines=i; } + + protected: + int l_ines; + +}; + + + +/** Represents a "User-Agent" header */ +class KDE_EXPORT UserAgent : public Base { + + public: + UserAgent() : Base() {} + UserAgent(Content *p) : Base(p) {} + UserAgent(Content *p, const QCString &s) : Base(p) { from7BitString(s); } + UserAgent(Content *p, const QString &s) : Base(p) { fromUnicodeString(s, Latin1); } + ~UserAgent() {} + + virtual void from7BitString(const QCString &s); + virtual QCString as7BitString(bool incType=true); + virtual void fromUnicodeString(const QString &s, const QCString&); + virtual QString asUnicodeString(); + virtual void clear() { u_agent.resize(0); } + virtual bool isEmpty() { return (u_agent.isEmpty()); } + virtual const char* type() { return "User-Agent"; } + + protected: + QCString u_agent; + +}; + + +#if !defined(KMIME_NEW_STYLE_CLASSTREE) +#include "kmime_headers_obs.h" +#endif +} //namespace Headers + +#if 0 +typedef Headers::Base* (*headerCreator)(void); + +/** This is a factory for KMime::Headers. You can create new header + objects by type with @ref create and @ref upgrade an existing + @ref Headers::Generic to a specialized header object. + + If you are a header class author, you can register your class + (let's call it Foo) so: + <pre> + + </pre> + + @short Factory for KMime::Headers + @author Marc Mutz <mutz@kde.org> + @see KMime::Headers::Base KMime::Headers::Generic +*/ + +class HeaderFactory : public QAsciiDict<headerCreator> +{ +private: + HeaderFactory(); + ~HeaderFactory() {} + static QAsciiDict + +public: + /** Create a new header object of type @p aType, or a fitting + generic substitute, if available and known + */ + static Headers::Base* create( const char* aType ) + { + if (!s_elf) + s_elf = new HeaderFactory; + headerCreator * hc = (*s_elf)[aType]; + if ( !hc ) + return 0; + else + return (*hc)(); + } + + /** This is a wrapper around the above function, provided for + convenience. It differs from the above only in what arguments it + takes. + */ + static Headers::Base* create( const QCString& aType ) + { + return create( aType.data() ); + } + + /** Consume @p aType and build a header object that corresponds to + the type that @p aType->type() returns. + @param aType generic header to upgrade. This will be deleted + if necessary, so don't use references to it after + calling this function. + @return A corresponding header object (if available), or a generic + object for this kind of header (if known), or @p aType (else). + @see Headers::Generic create + */ + static Headers::Base* upgrade( Headers::Generic* aType ) { (void)aType; return new Headers::Base; } + +}; + +#endif + +} //namespace KMime + + +#endif // __KMIME_HEADERS_H__ + diff --git a/libkmime/kmime_headers_obs.h b/libkmime/kmime_headers_obs.h new file mode 100644 index 000000000..41be1692a --- /dev/null +++ b/libkmime/kmime_headers_obs.h @@ -0,0 +1,372 @@ +/* + kmime_headers.h + + KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001 the KMime authors. + See file AUTHORS for details + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ +#ifndef __KMIME_HEADERS_OBS_H__ +#define __KMIME_HEADERS_OBS_H__ + +#if defined(KMIME_NEW_STYPE_CLASSTREE) +#error You cannot use this file with the new header classes! +#endif + +#include <kdepimmacros.h> + +/** Represents a "Message-Id" header */ +class KDE_EXPORT MessageID : public Base { + + public: + MessageID() : Base() {} + MessageID(Content *p) : Base(p) {} + MessageID(Content *p, const QCString &s) : Base(p) { from7BitString(s); } + MessageID(Content *p, const QString &s) : Base(p) { fromUnicodeString(s, Latin1); } + ~MessageID() {} + + virtual void from7BitString(const QCString &s); + virtual QCString as7BitString(bool incType=true); + virtual void fromUnicodeString(const QString &s, const QCString&); + virtual QString asUnicodeString(); + virtual void clear() { m_id.resize(0); } + virtual bool isEmpty() { return (m_id.isEmpty()); } + virtual const char* type() { return "Message-Id"; } + + void generate(const QCString &fqdn); + + protected: + QCString m_id; + +}; + +/** Represents a "Supersedes" header */ +class KDE_EXPORT Supersedes : public MessageID { + + public: + Supersedes() : MessageID() {} + Supersedes(Content *p) : MessageID(p) {} + Supersedes(Content *p, const QCString &s) : MessageID(p,s) {} + Supersedes(Content *p, const QString &s) : MessageID(p,s) {} + ~Supersedes() {} + + virtual const char* type() { return "Supersedes"; } + +}; + +/** This class encapsulates an address-field, containing + an email-address and a real name */ +class KDE_EXPORT AddressField : public Base { + + public: + AddressField() : Base() {} + AddressField(Content *p) : Base(p) {} + AddressField(Content *p, const QCString &s) : Base(p) { from7BitString(s); } + AddressField(Content *p, const QString &s, const QCString &cs) : Base(p) { fromUnicodeString(s, cs); } + AddressField(const AddressField &a): Base(a.p_arent) { n_ame=a.n_ame; e_mail=a.e_mail.copy(); e_ncCS=a.e_ncCS; } + ~AddressField() {} + + AddressField& operator=(const AddressField &a) { n_ame=a.n_ame; e_mail=a.e_mail.copy(); e_ncCS=a.e_ncCS; return (*this); } + + virtual void from7BitString(const QCString &s); + virtual QCString as7BitString(bool incType=true); + virtual void fromUnicodeString(const QString &s, const QCString &cs); + virtual QString asUnicodeString(); + virtual void clear() { n_ame.truncate(0); e_mail.resize(0); } + virtual bool isEmpty() { return (e_mail.isEmpty() && n_ame.isEmpty()); } + + bool hasName() { return ( !n_ame.isEmpty() ); } + bool hasEmail() { return ( !e_mail.isEmpty() ); } + QString name() { return n_ame; } + QCString nameAs7Bit(); + QCString email() { return e_mail; } + void setName(const QString &s) { n_ame=s; } + void setNameFrom7Bit(const QCString &s); + void setEmail(const QCString &s) { e_mail=s; } + + protected: + QString n_ame; + QCString e_mail; +}; +typedef QPtrList<AddressField> ObsAddressList; + +/** Represent a "From" header */ +class KDE_EXPORT From : public AddressField { + + public: + From() : AddressField() {} + From(Content *p) : AddressField(p) {} + From(Content *p, const QCString &s) : AddressField(p,s) {} + From(Content *p, const QString &s, const QCString &cs) : AddressField(p,s,cs) {} + ~From() {} + + virtual const char* type() { return "From"; } +}; + + +/** Represents a "Reply-To" header */ +class KDE_EXPORT ReplyTo : public AddressField { + + public: + ReplyTo() : AddressField() {} + ReplyTo(Content *p) : AddressField(p) {} + ReplyTo(Content *p, const QCString &s) : AddressField(p,s) {} + ReplyTo(Content *p, const QString &s, const QCString &cs) : AddressField(p,s,cs) {} + ~ReplyTo() {} + + virtual const char* type() { return "Reply-To"; } + +}; + + +/** Represents a "Mail-Copies-To" header + http://www.newsreaders.com/misc/mail-copies-to.html */ +class KDE_EXPORT MailCopiesTo : public AddressField { + + public: + MailCopiesTo() : AddressField() {} + MailCopiesTo(Content *p) : AddressField(p) {} + MailCopiesTo(Content *p, const QCString &s) : AddressField(p,s) {} + MailCopiesTo(Content *p, const QString &s, const QCString &cs) : AddressField(p,s,cs) {} + ~MailCopiesTo() {} + + bool isValid(); + bool alwaysCopy(); + bool neverCopy(); + + virtual const char* type() { return "Mail-Copies-To"; } + +}; + +/** Represents a "To" header */ +class KDE_EXPORT To : public Base { + + public: + To() : Base(),a_ddrList(0) {} + To(Content *p) : Base(p),a_ddrList(0) {} + To(Content *p, const QCString &s) : Base(p),a_ddrList(0) { from7BitString(s); } + To(Content *p, const QString &s, const QCString &cs) : Base(p),a_ddrList(0) { fromUnicodeString(s,cs); } + ~To() { delete a_ddrList; } + + virtual void from7BitString(const QCString &s); + virtual QCString as7BitString(bool incType=true); + virtual void fromUnicodeString(const QString &s, const QCString &cs); + virtual QString asUnicodeString(); + virtual void clear() { delete a_ddrList; a_ddrList=0; } + virtual bool isEmpty() { return (!a_ddrList || a_ddrList->isEmpty() + || a_ddrList->first()->isEmpty()); } + virtual const char* type() { return "To"; } + + void addAddress(const AddressField &a); + void emails(QStrList *l); + void names(QStringList *l); + void displayNames(QStringList *l); + + protected: + ObsAddressList *a_ddrList; + +}; + + +/** Represents a "CC" header */ +class KDE_EXPORT CC : public To { + + public: + CC() : To() {} + CC(Content *p) : To(p) {} + CC(Content *p, const QCString &s) : To(p,s) {} + CC(Content *p, const QString &s, const QCString &cs) : To(p,s,cs) {} + ~CC() {} + + virtual const char* type() { return "CC"; } + +}; + + +/** Represents a "BCC" header */ +class KDE_EXPORT BCC : public To { + + public: + BCC() : To() {} + BCC(Content *p) : To(p) {} + BCC(Content *p, const QCString &s) : To(p,s) {} + BCC(Content *p, const QString &s, const QCString &cs) : To(p,s,cs) {} + ~BCC() {} + + virtual const char* type() { return "BCC"; } + +}; + +/** Represents a "References" header */ +class KDE_EXPORT References : public Base { + + public: + References() : Base(),p_os(-1) {} + References(Content *p) : Base(p),p_os(-1) {} + References(Content *p, const QCString &s) : Base(p),p_os(-1) { from7BitString(s); } + References(Content *p, const QString &s) : Base(p),p_os(-1) { fromUnicodeString(s, Latin1); } + ~References() {} + + virtual void from7BitString(const QCString &s); + virtual QCString as7BitString(bool incType=true); + virtual void fromUnicodeString(const QString &s, const QCString&); + virtual QString asUnicodeString(); + virtual void clear() { r_ef.resize(0); p_os=0; } + virtual bool isEmpty() { return (r_ef.isEmpty()); } + virtual const char* type() { return "References"; } + + int count(); + QCString first(); + QCString next(); + QCString at(unsigned int i); + void append(const QCString &s); + + protected: + QCString r_ef; + int p_os; + +}; + +/** Represents a "Content-Type" header */ +class KDE_EXPORT ContentType : public Base { + + public: + ContentType() : Base(),m_imeType("invalid/invalid"),c_ategory(CCsingle) {} + ContentType(Content *p) : Base(p),m_imeType("invalid/invalid"),c_ategory(CCsingle) {} + ContentType(Content *p, const QCString &s) : Base(p) { from7BitString(s); } + ContentType(Content *p, const QString &s) : Base(p) { fromUnicodeString(s, Latin1); } + ~ContentType() {} + + virtual void from7BitString(const QCString &s); + virtual QCString as7BitString(bool incType=true); + virtual void fromUnicodeString(const QString &s, const QCString&); + virtual QString asUnicodeString(); + virtual void clear() { m_imeType.resize(0); p_arams.resize(0); } + virtual bool isEmpty() { return (m_imeType.isEmpty()); } + virtual const char* type() { return "Content-Type"; } + + + //mime-type handling + QCString mimeType() { return m_imeType; } + QCString mediaType(); + QCString subType(); + void setMimeType(const QCString &s); + bool isMediatype(const char *s); + bool isSubtype(const char *s); + bool isText(); + bool isPlainText(); + bool isHTMLText(); + bool isImage(); + bool isMultipart(); + bool isPartial(); + + //parameter handling + QCString charset(); + void setCharset(const QCString &s); + QCString boundary(); + void setBoundary(const QCString &s); + QString name(); + void setName(const QString &s, const QCString &cs); + QCString id(); + void setId(const QCString &s); + int partialNumber(); + int partialCount(); + void setPartialParams(int total, int number); + + //category + contentCategory category() { return c_ategory; } + void setCategory(contentCategory c) { c_ategory=c; } + + protected: + QCString getParameter(const char *name); + void setParameter(const QCString &name, const QCString &value, bool doubleQuotes=false); + QCString m_imeType, p_arams; + contentCategory c_ategory; + +}; + + +/** Represents a "Content-Transfer-Encoding" header */ +class KDE_EXPORT CTEncoding : public Base { + + public: + CTEncoding() : Base(),c_te(CE7Bit),d_ecoded(true) {} + CTEncoding(Content *p) : Base(p),c_te(CE7Bit),d_ecoded(true) {} + CTEncoding(Content *p, const QCString &s) : Base(p) { from7BitString(s); } + CTEncoding(Content *p, const QString &s) : Base(p) { fromUnicodeString(s, Latin1); } + ~CTEncoding() {} + + virtual void from7BitString(const QCString &s); + virtual QCString as7BitString(bool incType=true); + virtual void fromUnicodeString(const QString &s, const QCString&); + virtual QString asUnicodeString(); + virtual void clear() { d_ecoded=true; c_te=CE7Bit; } + virtual const char* type() { return "Content-Transfer-Encoding"; } + + contentEncoding cte() { return c_te; } + void setCte(contentEncoding e) { c_te=e; } + bool decoded() { return d_ecoded; } + void setDecoded(bool d=true) { d_ecoded=d; } + bool needToEncode() { return (d_ecoded && (c_te==CEquPr || c_te==CEbase64)); } + + protected: + contentEncoding c_te; + bool d_ecoded; + +}; + + +/** Represents a "Content-Disposition" header */ +class KDE_EXPORT CDisposition : public Base { + + public: + CDisposition() : Base(),d_isp(CDinline) {} + CDisposition(Content *p) : Base(p),d_isp(CDinline) {} + CDisposition(Content *p, const QCString &s) : Base(p) { from7BitString(s); } + CDisposition(Content *p, const QString &s, const QCString &cs) : Base(p) { fromUnicodeString(s, cs); } + ~CDisposition() {} + + virtual void from7BitString(const QCString &s); + virtual QCString as7BitString(bool incType=true); + virtual void fromUnicodeString(const QString &s, const QCString &cs); + virtual QString asUnicodeString(); + virtual void clear() { f_ilename.truncate(0); d_isp=CDinline; } + virtual const char* type() { return "Content-Disposition"; } + + contentDisposition disposition() { return d_isp; } + void setDisposition(contentDisposition d) { d_isp=d; } + bool isAttachment() { return (d_isp==CDattachment); } + + QString filename() { return f_ilename; } + void setFilename(const QString &s) { f_ilename=s; } + + protected: + contentDisposition d_isp; + QString f_ilename; + +}; + + +/** Represents a "Content-Description" header */ +class KDE_EXPORT CDescription : public Generics::GUnstructured { + + public: + CDescription() : Generics::GUnstructured() {} + CDescription( Content * p ) : Generics::GUnstructured( p ) {} + CDescription( Content * p, const QCString & s ) + : Generics::GUnstructured( p, s ) {}; + CDescription( Content * p, const QString & s, const QCString & cs ) + : Generics::GUnstructured( p, s, cs ) {} + ~CDescription() {} + + virtual const char* type() { return "Content-Description"; } +}; + +#endif // __KMIME_HEADERS_OBS_H__ diff --git a/libkmime/kmime_mdn.cpp b/libkmime/kmime_mdn.cpp new file mode 100644 index 000000000..678589d65 --- /dev/null +++ b/libkmime/kmime_mdn.cpp @@ -0,0 +1,262 @@ +/* -*- c++ -*- + kmime_mdn.cpp + + This file is part of KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2002 Marc Mutz <mutz@kde.org> + + KMime is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMime is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this library with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "kmime_mdn.h" + +#include "kmime_version.h" +#include "kmime_util.h" + +#include <klocale.h> +#include <kdebug.h> + +#include <qcstring.h> + +#include <unistd.h> // gethostname + +namespace KMime { + +namespace MDN { + + static const struct { + DispositionType dispositionType; + const char * string; + const char * description; + } dispositionTypes[] = { + { Displayed, "displayed", + I18N_NOOP("The message sent on ${date} to ${to} with subject " + "\"${subject}\" has been displayed. This is no guarantee that " + "the message has been read or understood.") }, + { Deleted, "deleted", + I18N_NOOP("The message sent on ${date} to ${to} with subject " + "\"${subject}\" has been deleted unseen. This is no guarantee " + "that the message will not be \"undeleted\" and nonetheless " + "read later on.") }, + { Dispatched, "dispatched", + I18N_NOOP("The message sent on ${date} to ${to} with subject " + "\"${subject}\" has been dispatched. This is no guarantee " + "that the message will not be read later on.") }, + { Processed, "processed", + I18N_NOOP("The message sent on ${date} to ${to} with subject " + "\"${subject}\" has been processed by some automatic means.") }, + { Denied, "denied", + I18N_NOOP("The message sent on ${date} to ${to} with subject " + "\"${subject}\" has been acted upon. The sender does not wish " + "to disclose more details to you than that.") }, + { Failed, "failed", + I18N_NOOP("Generation of a Message Disposition Notification for the " + "message sent on ${date} to ${to} with subject \"${subject}\" " + "failed. Reason is given in the Failure: header field below.") } + }; + + static const int numDispositionTypes + = sizeof dispositionTypes / sizeof *dispositionTypes; + + + static const char * stringFor( DispositionType d ) { + for ( int i = 0 ; i < numDispositionTypes ; ++i ) + if ( dispositionTypes[i].dispositionType == d ) + return dispositionTypes[i].string; + return 0; + } + + + // + // disposition-modifier + // + static const struct { + DispositionModifier dispositionModifier; + const char * string; + } dispositionModifiers[] = { + { Error, "error" }, + { Warning, "warning" }, + { Superseded, "superseded" }, + { Expired, "expired" }, + { MailboxTerminated, "mailbox-terminated" } + }; + + static const int numDispositionModifiers + = sizeof dispositionModifiers / sizeof * dispositionModifiers; + + + static const char * stringFor( DispositionModifier m ) { + for ( int i = 0 ; i < numDispositionModifiers ; ++i ) + if ( dispositionModifiers[i].dispositionModifier == m ) + return dispositionModifiers[i].string; + return 0; + } + + // + // action-mode (part of disposition-mode) + // + + static const struct { + ActionMode actionMode; + const char * string; + } actionModes[] = { + { ManualAction, "manual-action" }, + { AutomaticAction, "automatic-action" } + }; + + static const int numActionModes = sizeof actionModes / sizeof *actionModes; + + static const char * stringFor( ActionMode a ) { + for ( int i = 0 ; i < numActionModes ; ++i ) + if ( actionModes[i].actionMode == a ) + return actionModes[i].string; + return 0; + } + + + // + // sending-mode (part of disposition-mode) + // + + static const struct { + SendingMode sendingMode; + const char * string; + } sendingModes[] = { + { SentManually, "MDN-sent-manually" }, + { SentAutomatically, "MDN-sent-automatically" } + }; + + static const int numSendingModes = sizeof sendingModes / sizeof *sendingModes; + + static const char * stringFor( SendingMode s ) { + for ( int i = 0 ; i < numSendingModes ; ++i ) + if ( sendingModes[i].sendingMode == s ) + return sendingModes[i].string; + return 0; + } + + static QCString dispositionField( DispositionType d, ActionMode a, SendingMode s, + const QValueList<DispositionModifier> & m ) { + + // mandatory parts: Disposition: foo/baz; bar + QCString result = "Disposition: "; + result += stringFor( a ); + result += "/"; + result += stringFor( s ); + result += "; "; + result += stringFor( d ); + + // optional parts: Disposition: foo/baz; bar/mod1,mod2,mod3 + bool first = true; + for ( QValueList<DispositionModifier>::const_iterator mt = m.begin() ; + mt != m.end() ; ++mt ) { + if ( first ) { + result += "/"; + first = false; + } else { + result += ","; + } + result += stringFor( *mt ); + } + return result + "\n"; + } + + static QCString finalRecipient( const QString & recipient ) { + if ( recipient.isEmpty() ) + return QCString(); + else + return "Final-Recipient: rfc822; " + + encodeRFC2047String( recipient, "utf-8" ) + "\n"; + } + + static QCString orginalRecipient( const QCString & recipient ) { + if ( recipient.isEmpty() ) + return QCString(); + else + return "Original-Recipient: " + recipient + "\n"; + } + + static QCString originalMessageID( const QCString & msgid ) { + if ( msgid.isEmpty() ) + return QCString(); + else + return "Original-Message-ID: " + msgid + "\n"; + } + + static QCString reportingUAField() { + char hostName[256]; + if ( gethostname( hostName, 255 ) ) + hostName[0] = '\0'; // gethostname failed: pretend empty string + else + hostName[255] = '\0'; // gethostname may have returned 255 chars (man page) + return QCString("Reporting-UA: ") + hostName + + "; KMime " KMIME_VERSION_STRING "\n"; + } + + QCString dispositionNotificationBodyContent( const QString & r, + const QCString & o, + const QCString & omid, + DispositionType d, + ActionMode a, + SendingMode s, + const QValueList<DispositionModifier> & m, + const QString & special ) + { + // in Perl: chomp(special) + QString spec; + if ( special.endsWith("\n") ) + spec = special.left( special.length() - 1 ); + else + spec = special; + + // std headers: + QCString result = reportingUAField(); + result += orginalRecipient( o ); + result += finalRecipient( r ); + result += originalMessageID( omid ); + result += dispositionField( d, a, s, m ); + + // headers that are only present for certain disposition {types,modifiers}: + if ( d == Failed ) + result += "Failure: " + encodeRFC2047String( spec, "utf-8" ) + "\n"; + else if ( m.contains( Error ) ) + result += "Error: " + encodeRFC2047String( spec, "utf-8" ) + "\n"; + else if ( m.contains( Warning ) ) + result += "Warning: " + encodeRFC2047String( spec, "utf-8" ) + "\n"; + + return result; + } + + QString descriptionFor( DispositionType d, + const QValueList<DispositionModifier> & ) { + for ( int i = 0 ; i < numDispositionTypes ; ++i ) + if ( dispositionTypes[i].dispositionType == d ) + return i18n( dispositionTypes[i].description ); + kdWarning() << "KMime::MDN::descriptionFor(): No such disposition type: " + << (int)d << endl; + return QString::null; + } + +} // namespace MDN +} // namespace KMime diff --git a/libkmime/kmime_mdn.h b/libkmime/kmime_mdn.h new file mode 100644 index 000000000..2fd76e6d7 --- /dev/null +++ b/libkmime/kmime_mdn.h @@ -0,0 +1,202 @@ +/* -*- c++ -*- + kmime_mdn.h + + This file is part of KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2002 Marc Mutz <mutz@kde.org> + + KMime is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMime is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this library with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KMIME_MDN_H__ +#define __KMIME_MDN_H__ + +#include <qvaluelist.h> +#include <qstring.h> + +#include <kdepimmacros.h> + +class QCString; + +namespace KMime { + + namespace MDN { + + /** The following disposition-types are defined: + + @li Displayed The message has been displayed by the UA to someone + reading the recipient's mailbox. There is + no guarantee that the content has been + read or understood. + + @li Dispatched The message has been sent somewhere in some manner + (e.g., printed, faxed, forwarded) without + necessarily having been previously + displayed to the user. The user may or + may not see the message later. + + @li Processed The message has been processed in some manner (i.e., + by some sort of rules or server) without + being displayed to the user. The user may + or may not see the message later, or there + may not even be a human user associated + with the mailbox. + + @li Deleted The message has been deleted. The recipient may or + may not have seen the message. The + recipient might "undelete" the message at + a later time and read the message. + + @li Denied The recipient does not wish the sender to be informed + of the message's disposition. A UA may + also siliently ignore message disposition + requests in this situation. + + @li Failed A failure occurred that prevented the proper + generation of an MDN. More information + about the cause of the failure may be + contained in a Failure field. The + "failed" disposition type is not to be + used for the situation in which there is + is some problem in processing the message + other than interpreting the request for an + MDN. The "processed" or other disposition + type with appropriate disposition + modifiers is to be used in such + situations. + + IOW: @p Displayed when - well -displayed + @p Dispatched when forwarding unseen ( == new ) + @p Processed (maybe) when piping unseen, but probably never used + @p Deleted when deleting unseen + @p Denied on user command + @p Failed on Disposition-Notification-Options containing + unknown required options. ( == @em any required options ) + @p Failed needs a description in the @p special parameter. + **/ + enum DispositionType { + Displayed, Read = Displayed, + Deleted, + Dispatched, Forwarded = Dispatched, + Processed, + Denied, + Failed + }; + /** The following disposition modifiers are defined: + + @li Error An error of some sort occurred + that prevented successful + processing of the message. + Further information is contained + in an Error field. + + @li Warning The message was successfully + processed but some sort of + exceptional condition occurred. + Further information is contained + in a Warning field. + + @li Superseded The message has been + automatically rendered obsolete by + another message received. The + recipient may still access and + read the message later. + + @li Expired The message has reached its + expiration date and has been + automatically removed from the + recipient's mailbox. + + @li MailboxTerminated The recipient's mailbox has been + terminated and all message in it + automatically removed. + **/ + enum DispositionModifier { + Error, + Warning, + Superseded, + Expired, + MailboxTerminated + }; +\ + /** The following disposition modes are defined: + + @li ManualAction The disposition described by the disposition type + was a result of an explicit instruction by + the user rather than some sort of + automatically performed action. + + @li AutomaticAction The disposition described by the disposition + type was a result of an automatic action, + rather than an explicit instruction by the + user for this message. + + IOW: @p ManualAction for user-driven actions, + @p AutomanticAction for filtering. + **/ + enum ActionMode { + ManualAction, + AutomaticAction + }; + + /** + @li SentManually The user explicitly gave permission for this + particular MDN to be sent. + + @li SentAutomatically The MDN was sent because the MUA had + previously been configured to do so + automatically. + + IOW: @p SentManually for when we have asked the user + @p SentAutomatically when we use the default specified by the user + **/ + enum SendingMode { + SentManually, + SentAutomatically + }; + + /** Main function. Generates the content of the + message/disposition-notification body part. */ + KDE_EXPORT extern QCString dispositionNotificationBodyContent( const QString & finalRecipient, + const QCString & originalRecipient, + const QCString & originalMsgID, + DispositionType disposition, + ActionMode actionMode, + SendingMode sendingMode, + const QValueList<DispositionModifier> & dispositionModifers + =QValueList<DispositionModifier>(), + const QString & special=QString::null ); + + KDE_EXPORT extern QString descriptionFor( DispositionType d, + const QValueList<DispositionModifier> & m + =QValueList<DispositionModifier>() ); + + enum ReturnContent { Nothing, All, HeadersOnly }; + + + } // namespace MDN + +} // namespace KMime + +#endif // __KMIME_MDN_H__ diff --git a/libkmime/kmime_message.cpp b/libkmime/kmime_message.cpp new file mode 100644 index 000000000..2bfbf91a6 --- /dev/null +++ b/libkmime/kmime_message.cpp @@ -0,0 +1,168 @@ +/* + kmime_message.cpp + + KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001 the KMime authors. + See file AUTHORS for details + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ + +#include "kmime_message.h" + +using namespace KMime; + +namespace KMime { + +Message::Message() +{ + s_ubject.setParent(this); + d_ate.setParent(this); +} + +Message::~Message() {} + +void Message::parse() +{ + Content::parse(); + + QCString raw; + if( !(raw=rawHeader(s_ubject.type())).isEmpty() ) + s_ubject.from7BitString(raw); + + if( !(raw=rawHeader(d_ate.type())).isEmpty() ) + d_ate.from7BitString(raw); +} + + +void Message::assemble() +{ + Headers::Base *h; + QCString newHead=""; + + //Message-ID + if( (h=messageID(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //From + h=from(); // "From" is mandatory + newHead+=h->as7BitString()+"\n"; + + //Subject + h=subject(); // "Subject" is mandatory + newHead+=h->as7BitString()+"\n"; + + //To + if( (h=to(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //Cc + if( (h=cc(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //Reply-To + if( (h=replyTo(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //Date + h=date(); // "Date" is mandatory + newHead+=h->as7BitString()+"\n"; + + //References + if( (h=references(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //Organization + if( (h=organization(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //UserAgent + if( (h=userAgent(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //Mime-Version + newHead+="MIME-Version: 1.0\n"; + + //Content-Type + newHead+=contentType()->as7BitString()+"\n"; + + //Content-Transfer-Encoding + newHead+=contentTransferEncoding()->as7BitString()+"\n"; + + //X-Headers + int pos=h_ead.find("\nX-"); + if(pos>-1) //we already have some x-headers => "recycle" them + newHead+=h_ead.mid(pos+1, h_ead.length()-pos-1); + else if(h_eaders && !h_eaders->isEmpty()) { + for(h=h_eaders->first(); h; h=h_eaders->next()) { + if( h->isXHeader() && (strncasecmp(h->type(), "X-KNode", 7)!=0) ) + newHead+=h->as7BitString()+"\n"; + } + } + + h_ead=newHead; +} + + +void Message::clear() +{ + s_ubject.clear(); + d_ate.clear(); + f_lags.clear(); + Content::clear(); +} + + +Headers::Base* Message::getHeaderByType(const char *type) +{ + if(strcasecmp("Subject", type)==0) { + if(s_ubject.isEmpty()) return 0; + else return &s_ubject; + } + else if(strcasecmp("Date", type)==0){ + if(d_ate.isEmpty()) return 0; + else return &d_ate; + } + else + return Content::getHeaderByType(type); +} + + +void Message::setHeader(Headers::Base *h) +{ + bool del=true; + if(h->is("Subject")) + s_ubject.fromUnicodeString(h->asUnicodeString(), h->rfc2047Charset()); + else if(h->is("Date")) + d_ate.setUnixTime( (static_cast<Headers::Date*>(h))->unixTime() ); + else { + del=false; + Content::setHeader(h); + } + + if(del) delete h; +} + + +bool Message::removeHeader(const char *type) +{ + if(strcasecmp("Subject", type)==0) + s_ubject.clear(); + else if(strcasecmp("Date", type)==0) + d_ate.clear(); + else + return Content::removeHeader(type); + + return true; +} + + + + +} // namespace KMime diff --git a/libkmime/kmime_message.h b/libkmime/kmime_message.h new file mode 100644 index 000000000..1209d420e --- /dev/null +++ b/libkmime/kmime_message.h @@ -0,0 +1,67 @@ +/* + kmime_message.h + + KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001 the KMime authors. + See file AUTHORS for details + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ +#ifndef __KMIME_MESSAGE_H__ +#define __KMIME_MESSAGE_H__ + +#include "kmime_content.h" +#include "kmime_headers.h" +#include "boolflags.h" + +namespace KMime { + +class KDE_EXPORT Message : public Content { + +public: + typedef QPtrList<Message> List; + + /** Constructor. Creates an empty message. */ + Message(); + ~Message(); + + //content handling + virtual void parse(); + virtual void assemble(); + virtual void clear(); + + //header access + virtual KMime::Headers::Base* getHeaderByType(const char *type); + virtual void setHeader(KMime::Headers::Base *h); + virtual bool removeHeader(const char *type); + + virtual KMime::Headers::MessageID* messageID(bool create=true) { KMime::Headers::MessageID *p=0; return getHeaderInstance(p, create); } + virtual KMime::Headers::Subject* subject(bool create=true) { if(!create && s_ubject.isEmpty()) return 0; return &s_ubject; } + virtual KMime::Headers::Date* date(bool create=true) { if(!create && d_ate.isEmpty()) return 0;return &d_ate; } + virtual KMime::Headers::From* from(bool create=true) { KMime::Headers::From *p=0; return getHeaderInstance(p, create); } + virtual KMime::Headers::Organization* organization(bool create=true) { KMime::Headers::Organization *p=0; return getHeaderInstance(p, create); } + virtual KMime::Headers::ReplyTo* replyTo(bool create=true) { KMime::Headers::ReplyTo *p=0; return getHeaderInstance(p, create); } + virtual KMime::Headers::To* to(bool create=true) { KMime::Headers::To *p=0; return getHeaderInstance(p, create); } + virtual KMime::Headers::CC* cc(bool create=true) { KMime::Headers::CC *p=0; return getHeaderInstance(p, create); } + virtual KMime::Headers::BCC* bcc(bool create=true) { KMime::Headers::BCC *p=0; return getHeaderInstance(p, create); } + virtual KMime::Headers::References* references(bool create=true) { KMime::Headers::References *p=0; return getHeaderInstance(p, create); } + virtual KMime::Headers::UserAgent* userAgent(bool create=true) { KMime::Headers::UserAgent *p=0; return getHeaderInstance(p, create); } + +protected: + //hardcoded headers + KMime::Headers::Subject s_ubject; + KMime::Headers::Date d_ate; + BoolFlags f_lags; // some status info + + +}; // class Message + +} // namespace KMime + +#endif // __KMIME_MESSAGE_H__ diff --git a/libkmime/kmime_newsarticle.cpp b/libkmime/kmime_newsarticle.cpp new file mode 100644 index 000000000..a60b07df8 --- /dev/null +++ b/libkmime/kmime_newsarticle.cpp @@ -0,0 +1,160 @@ +/* + kmime_newsarticle.cpp + + KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001 the KMime authors. + See file AUTHORS for details + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ +#include "kmime_newsarticle.h" + +using namespace KMime; + +namespace KMime { + +void NewsArticle::parse() +{ + Message::parse(); + + QCString raw; + + if( !(raw=rawHeader(l_ines.type())).isEmpty() ) + l_ines.from7BitString(raw); +} + +void NewsArticle::assemble() +{ + Headers::Base *h; + QCString newHead=""; + + //Message-ID + if( (h=messageID(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //Control + if( (h=control(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //Supersedes + if( (h=supersedes(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //From + h=from(); // "From" is mandatory + newHead+=h->as7BitString()+"\n"; + + //Subject + h=subject(); // "Subject" is mandatory + newHead+=h->as7BitString()+"\n"; + + //To + if( (h=to(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //Newsgroups + if( (h=newsgroups(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //Followup-To + if( (h=followUpTo(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //Reply-To + if( (h=replyTo(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //Mail-Copies-To + if( (h=mailCopiesTo(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //Date + h=date(); // "Date" is mandatory + newHead+=h->as7BitString()+"\n"; + + //References + if( (h=references(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //Lines + h=lines(); // "Lines" is mandatory + newHead+=h->as7BitString()+"\n"; + + //Organization + if( (h=organization(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //User-Agent + if( (h=userAgent(false))!=0 ) + newHead+=h->as7BitString()+"\n"; + + //Mime-Version + newHead+="MIME-Version: 1.0\n"; + + //Content-Type + newHead+=contentType()->as7BitString()+"\n"; + + //Content-Transfer-Encoding + newHead+=contentTransferEncoding()->as7BitString()+"\n"; + + //X-Headers + int pos=h_ead.find("\nX-"); + if(pos>-1) //we already have some x-headers => "recycle" them + newHead+=h_ead.mid(pos+1, h_ead.length()-pos); + else if(h_eaders && !h_eaders->isEmpty()) { + for(h=h_eaders->first(); h; h=h_eaders->next()) { + if( h->isXHeader() && (strncasecmp(h->type(), "X-KNode", 7)!=0) ) + newHead+=h->as7BitString()+"\n"; + } + } + + h_ead=newHead; +} + +void NewsArticle::clear() +{ + l_ines.clear(); + Message::clear(); +} + +Headers::Base * NewsArticle::getHeaderByType(const char * type) +{ + if(strcasecmp("Lines", type)==0) { + if(l_ines.isEmpty()) return 0; + else return &l_ines; + } else + return Message::getHeaderByType(type); +} + +void NewsArticle::setHeader(Headers::Base *h) +{ + bool del=true; + if(h->is("Lines")) + l_ines.setNumberOfLines( (static_cast<Headers::Lines*>(h))->numberOfLines() ); + else { + del=false; + Message::setHeader(h); + } + + if(del) delete h; +} + + +bool NewsArticle::removeHeader(const char *type) +{ + if(strcasecmp("Lines", type)==0) + l_ines.clear(); + else + return Message::removeHeader(type); + + return true; +} + + +} // namespace KMime diff --git a/libkmime/kmime_newsarticle.h b/libkmime/kmime_newsarticle.h new file mode 100644 index 000000000..57e3733a7 --- /dev/null +++ b/libkmime/kmime_newsarticle.h @@ -0,0 +1,55 @@ +/* + kmime_newsarticle.h + + KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001 the KMime authors. + See file AUTHORS for details + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ +#ifndef __KMIME_NEWSARTICLE_H__ +#define __KMIME_NEWSARTICLE_H__ + +#include "kmime_message.h" + +#include <kdepimmacros.h> + +namespace KMime { + +class KDE_EXPORT NewsArticle : public Message { + +public: + + NewsArticle() : Message() { l_ines.setParent(this); } + ~NewsArticle() {}; + + virtual void parse(); + virtual void assemble(); + virtual void clear(); + + virtual KMime::Headers::Base * getHeaderByType(const char* type); + virtual void setHeader(KMime::Headers::Base *h); + virtual bool removeHeader(const char * type); + + virtual KMime::Headers::Control* control(bool create=true) { KMime::Headers::Control *p=0; return getHeaderInstance(p, create); } + virtual KMime::Headers::Supersedes* supersedes(bool create=true) { KMime::Headers::Supersedes *p=0; return getHeaderInstance(p, create); } + virtual KMime::Headers::MailCopiesTo* mailCopiesTo(bool create=true) { KMime::Headers::MailCopiesTo *p=0; return getHeaderInstance(p, create); } + virtual KMime::Headers::Newsgroups* newsgroups(bool create=true) { KMime::Headers::Newsgroups *p=0; return getHeaderInstance(p, create); } + virtual KMime::Headers::FollowUpTo* followUpTo(bool create=true) { KMime::Headers::FollowUpTo *p=0; return getHeaderInstance(p, create); } + virtual KMime::Headers::Lines* lines(bool create=true) { if(!create && l_ines.isEmpty()) return 0; return &l_ines; } + + +protected: + KMime::Headers::Lines l_ines; + +}; // class NewsArticle + +} // namespace KMime + +#endif // __KMIME_NEWSARTICLE_H__ diff --git a/libkmime/kmime_parsers.cpp b/libkmime/kmime_parsers.cpp new file mode 100644 index 000000000..181ce6772 --- /dev/null +++ b/libkmime/kmime_parsers.cpp @@ -0,0 +1,466 @@ +/* + kmime_parsers.cpp + + KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001 the KMime authors. + See file AUTHORS for details + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ +#include "kmime_parsers.h" + +#include <qregexp.h> + +using namespace KMime::Parser; + +namespace KMime { +namespace Parser { + + +MultiPart::MultiPart(const QCString &src, const QCString &boundary) +{ + s_rc=src; + b_oundary=boundary; +} + + +bool MultiPart::parse() +{ + QCString b="--"+b_oundary, part; + int pos1=0, pos2=0, blen=b.length(); + + p_arts.clear(); + + //find the first valid boundary + while(1) { + if( (pos1=s_rc.find(b, pos1))==-1 || pos1==0 || s_rc[pos1-1]=='\n' ) //valid boundary found or no boundary at all + break; + pos1+=blen; //boundary found but not valid => skip it; + } + + if(pos1>-1) { + pos1+=blen; + if(s_rc[pos1]=='-' && s_rc[pos1+1]=='-') // the only valid boundary is the end-boundary - this message is *really* broken + pos1=-1; //we give up + else if( (pos1-blen)>1 ) //preamble present + p_reamble=s_rc.left(pos1-blen); + } + + + while(pos1>-1 && pos2>-1) { + + //skip the rest of the line for the first boundary - the message-part starts here + if( (pos1=s_rc.find('\n', pos1))>-1 ) { //now search the next linebreak + //now find the next valid boundary + pos2=++pos1; //pos1 and pos2 point now to the beginning of the next line after the boundary + while(1) { + if( (pos2=s_rc.find(b, pos2))==-1 || s_rc[pos2-1]=='\n' ) //valid boundary or no more boundaries found + break; + pos2+=blen; //boundary is invalid => skip it; + } + + if(pos2==-1) { // no more boundaries found + part=s_rc.mid(pos1, s_rc.length()-pos1); //take the rest of the string + p_arts.append(part); + pos1=-1; + pos2=-1; //break; + } + else { + part=s_rc.mid(pos1, pos2-pos1 - 1 ); // pos2 - 1 (\n) is part of the boundary (see RFC 2046, section 5.1.1) + p_arts.append(part); + pos2+=blen; //pos2 points now to the first charakter after the boundary + if(s_rc[pos2]=='-' && s_rc[pos2+1]=='-') { //end-boundary + pos1=pos2+2; //pos1 points now to the character directly after the end-boundary + if( (pos1=s_rc.find('\n', pos1))>-1 ) //skipt the rest of this line + e_pilouge=s_rc.mid(pos1+1, s_rc.length()-pos1-1); //everything after the end-boundary is considered as the epilouge + pos1=-1; + pos2=-1; //break + } + else { + pos1=pos2; //the search continues ... + } + } + } + } + + return (!p_arts.isEmpty()); +} + +//============================================================================================ + + +NonMimeParser::NonMimeParser(const QCString &src) : + s_rc(src), p_artNr(-1), t_otalNr(-1) +{} + +/** + * try to guess the mimetype from the file-extension + */ +QCString NonMimeParser::guessMimeType(const QCString& fileName) +{ + QCString tmp, mimeType; + int pos; + + if(!fileName.isEmpty()) { + pos=fileName.findRev('.'); + if(pos++ != -1) { + tmp=fileName.mid(pos, fileName.length()-pos).upper(); + if(tmp=="JPG" || tmp=="JPEG") mimeType="image/jpeg"; + else if(tmp=="GIF") mimeType="image/gif"; + else if(tmp=="PNG") mimeType="image/png"; + else if(tmp=="TIFF" || tmp=="TIF") mimeType="image/tiff"; + else if(tmp=="XPM") mimeType="image/x-xpm"; + else if(tmp=="XBM") mimeType="image/x-xbm"; + else if(tmp=="BMP") mimeType="image/x-bmp"; + else if(tmp=="TXT" || + tmp=="ASC" || + tmp=="H" || + tmp=="C" || + tmp=="CC" || + tmp=="CPP") mimeType="text/plain"; + else if(tmp=="HTML" || tmp=="HTM") mimeType="text/html"; + else mimeType="application/octet-stream"; + } + else mimeType="application/octet-stream"; + } + else mimeType="application/octet-stream"; + + return mimeType; +} + +//============================================================================================ + + +UUEncoded::UUEncoded(const QCString &src, const QCString &subject) : + NonMimeParser(src), s_ubject(subject) +{} + + +bool UUEncoded::parse() +{ + int currentPos=0; + bool success=true, firstIteration=true; + + while (success) { + int beginPos=currentPos, uuStart=currentPos, endPos=0, lineCount=0, MCount=0, pos=0, len=0; + bool containsBegin=false, containsEnd=false; + QCString tmp,fileName; + + if( (beginPos=s_rc.find(QRegExp("begin [0-9][0-9][0-9]"),currentPos))>-1 && (beginPos==0 || s_rc.at(beginPos-1)=='\n') ) { + containsBegin=true; + uuStart=s_rc.find('\n', beginPos); + if(uuStart==-1) {//no more line breaks found, we give up + success = false; + break; + } else + uuStart++; //points now at the beginning of the next line + } + else beginPos=currentPos; + + if ( (endPos=s_rc.find("\nend",(uuStart>0)? uuStart-1:0))==-1 ) + endPos=s_rc.length(); //no end found + else + containsEnd=true; + + if ((containsBegin && containsEnd) || firstIteration) { + + //printf("beginPos=%d , uuStart=%d , endPos=%d\n", beginPos, uuStart, endPos); + //all lines in a uuencoded text start with 'M' + for(int idx=uuStart; idx<endPos; idx++) + if(s_rc[idx]=='\n') { + lineCount++; + if(idx+1<endPos && s_rc[idx+1]=='M') { + idx++; + MCount++; + } + } + + //printf("lineCount=%d , MCount=%d\n", lineCount, MCount); + if( MCount==0 || (lineCount-MCount)>10 || + ((!containsBegin || !containsEnd) && (MCount<15)) ) { // harder check for splitted-articles + success = false; + break; //too many "non-M-Lines" found, we give up + } + + if( (!containsBegin || !containsEnd) && s_ubject) { // message may be split up => parse subject + QRegExp rx("[0-9]+/[0-9]+"); + pos=rx.search(QString(s_ubject), 0); + len=rx.matchedLength(); + if(pos!=-1) { + tmp=s_ubject.mid(pos, len); + pos=tmp.find('/'); + p_artNr=tmp.left(pos).toInt(); + t_otalNr=tmp.right(tmp.length()-pos-1).toInt(); + } else { + success = false; + break; //no "part-numbers" found in the subject, we give up + } + } + + //everything before "begin" is text + if(beginPos>0) + t_ext.append(s_rc.mid(currentPos,beginPos-currentPos)); + + if(containsBegin) + fileName = s_rc.mid(beginPos+10, uuStart-beginPos-11); //everything between "begin ### " and the next LF is considered as the filename + else + fileName = ""; + f_ilenames.append(fileName); + b_ins.append(s_rc.mid(uuStart, endPos-uuStart+1)); //everything beetween "begin" and "end" is uuencoded + m_imeTypes.append(guessMimeType(fileName)); + firstIteration=false; + + int next = s_rc.find('\n', endPos+1); + if(next==-1) { //no more line breaks found, we give up + success = false; + break; + } else + next++; //points now at the beginning of the next line + currentPos = next; + + } else { + success = false; + } + } + + // append trailing text part of the article + t_ext.append(s_rc.right(s_rc.length()-currentPos)); + + return ((b_ins.count()>0) || isPartial()); +} + + +//============================================================================================ + + +YENCEncoded::YENCEncoded(const QCString &src) : + NonMimeParser(src) +{} + + +bool YENCEncoded::yencMeta(QCString& src, const QCString& name, int* value) +{ + bool found = false; + QCString sought=name + "="; + + int iPos=src.find( sought); + if (iPos>-1) { + int pos1=src.find(' ', iPos); + int pos2=src.find('\r', iPos); + int pos3=src.find('\t', iPos); + int pos4=src.find('\n', iPos); + if (pos2>=0 && (pos1<0 || pos1>pos2)) + pos1=pos2; + if (pos3>=0 && (pos1<0 || pos1>pos3)) + pos1=pos3; + if (pos4>=0 && (pos1<0 || pos1>pos4)) + pos1=pos4; + iPos=src.findRev( '=', pos1)+1; + if (iPos<pos1) { + char c=src.at( iPos); + if ( c>='0' && c<='9') { + found=true; + *value=src.mid( iPos, pos1-iPos).toInt(); + } + } + } + return found; +} + + +bool YENCEncoded::parse() +{ + int currentPos=0; + bool success=true; + + while (success) { + int beginPos=currentPos, yencStart=currentPos; + bool containsPart=false; + QCString fileName,mimeType; + + if ((beginPos=s_rc.find("=ybegin ", currentPos))>-1 && ( beginPos==0 || s_rc.at( beginPos-1)=='\n') ) { + yencStart=s_rc.find( '\n', beginPos); + if (yencStart==-1) { // no more line breaks found, give up + success = false; + break; + } else { + yencStart++; + if (s_rc.find("=ypart", yencStart)==yencStart) { + containsPart=true; + yencStart=s_rc.find( '\n', yencStart); + if ( yencStart== -1) { + success=false; + break; + } + yencStart++; + } + } + // Try to identify yenc meta data + + // Filenames can contain any embedded chars until end of line + QCString meta=s_rc.mid(beginPos, yencStart-beginPos); + int namePos=meta.find("name="); + if (namePos== -1) { + success=false; + break; + } + int eolPos=meta.find('\r', namePos); + if (eolPos== -1) + eolPos=meta.find('\n', namePos); + if (eolPos== -1) { + success=false; + break; + } + fileName=meta.mid(namePos+5, eolPos-(namePos+5)); + + // Other metadata is integer + int yencLine; + if (!yencMeta(meta, "line", ¥cLine)) { + success=false; + break; + } + int yencSize; + if (!yencMeta( meta, "size", ¥cSize)) { + success=false; + break; + } + + int partBegin, partEnd; + if (containsPart) { + if (!yencMeta(meta, "part", &p_artNr)) { + success=false; + break; + } + if (!yencMeta(meta, "begin", &partBegin) || ! + yencMeta(meta, "end", &partEnd)) { + success=false; + break; + } + if (!yencMeta(meta, "total", &t_otalNr)) + t_otalNr=p_artNr+1; + if (yencSize==partEnd-partBegin+1) + t_otalNr=1; else + yencSize=partEnd-partBegin+1; + } + + // We have a valid yenc header; now we extract the binary data + int totalSize=0; + int pos=yencStart; + int len=s_rc.length(); + bool lineStart=true; + int lineLength=0; + bool containsEnd=false; + QByteArray binary = QByteArray(yencSize); + while (pos<len) { + int ch=s_rc.at(pos); + if (ch<0) + ch+=256; + if (ch=='\r') + { + if (lineLength!=yencLine && totalSize!=yencSize) + break; + pos++; + } + else if (ch=='\n') + { + lineStart=true; + lineLength=0; + pos++; + } + else + { + if (ch=='=') + { + if (pos+1<len) + { + ch=s_rc.at( pos+1); + if (lineStart && ch=='y') + { + containsEnd=true; + break; + } + pos+=2; + ch-=64+42; + if (ch<0) + ch+=256; + if (totalSize>=yencSize) + break; + binary.at(totalSize++)=ch; + lineLength++; + } + else + break; + } + else + { + ch-=42; + if (ch<0) + ch+=256; + if (totalSize>=yencSize) + break; + binary.at(totalSize++)=ch; + lineLength++; + pos++; + } + lineStart=false; + } + } + + if (!containsEnd) + { + success=false; + break; + } + if (totalSize!=yencSize) + { + success=false; + break; + } + + // pos now points to =yend; get end data + eolPos=s_rc.find('\n', pos); + if (eolPos== -1) + { + success=false; + break; + } + meta=s_rc.mid(pos, eolPos-pos); + if (!yencMeta(meta, "size", &totalSize)) + { + success=false; + break; + } + if (totalSize!=yencSize) + { + success=false; + break; + } + + f_ilenames.append(fileName); + m_imeTypes.append(guessMimeType( fileName)); + b_ins.append(binary); + + //everything before "begin" is text + if(beginPos>0) + t_ext.append(s_rc.mid(currentPos,beginPos-currentPos)); + currentPos = eolPos+1; + + } else { + success = false; + } + } + + // append trailing text part of the article + t_ext.append(s_rc.right(s_rc.length()-currentPos)); + + return b_ins.count()>0; +} + +} // namespace Parser +} // namespace KMime diff --git a/libkmime/kmime_parsers.h b/libkmime/kmime_parsers.h new file mode 100644 index 000000000..752bed804 --- /dev/null +++ b/libkmime/kmime_parsers.h @@ -0,0 +1,115 @@ +/* + kmime_parsers.h + + KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001 the KMime authors. + See file AUTHORS for details + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ +#ifndef __KMIME_PARSERS__ +#define __KMIME_PARSERS__ + +#include <qvaluelist.h> +#include <qcstring.h> +#include <qstrlist.h> + +namespace KMime { + +namespace Parser { + +/** Helper-class: splits a multipart-message into single + parts as described in RFC 2046 + @internal +*/ +class MultiPart { + +public: + MultiPart(const QCString &src, const QCString &boundary); + ~MultiPart() {}; + + bool parse(); + QValueList<QCString> parts() { return p_arts; } + QCString preamble() { return p_reamble; } + QCString epilouge() { return e_pilouge; } + +protected: + QCString s_rc, b_oundary, p_reamble, e_pilouge; + QValueList<QCString> p_arts; +}; + + +/** Helper-class: abstract base class of all parsers for + non-mime binary data (uuencoded, yenc) + @internal +*/ +class NonMimeParser { + +public: + NonMimeParser(const QCString &src); + virtual ~NonMimeParser() {}; + virtual bool parse() = 0; + bool isPartial() { return (p_artNr>-1 && t_otalNr>-1 && t_otalNr!=1); } + int partialNumber() { return p_artNr; } + int partialCount() { return t_otalNr; } + bool hasTextPart() { return (t_ext.length()>1); } + QCString textPart() { return t_ext; } + QStrList binaryParts() { return b_ins; } + QStrList filenames() { return f_ilenames; } + QStrList mimeTypes() { return m_imeTypes; } + +protected: + static QCString guessMimeType(const QCString& fileName); + + QCString s_rc, t_ext; + QStrList b_ins, f_ilenames, m_imeTypes; + int p_artNr, t_otalNr; +}; + + +/** Helper-class: tries to extract the data from a possibly + uuencoded message + @internal +*/ +class UUEncoded : public NonMimeParser { + +public: + UUEncoded(const QCString &src, const QCString &subject); + + virtual bool parse(); + +protected: + QCString s_ubject; +}; + + + +/** Helper-class: tries to extract the data from a possibly + yenc encoded message + @internal +*/ +class YENCEncoded : public NonMimeParser { + +public: + YENCEncoded(const QCString &src); + + virtual bool parse(); + QValueList<QByteArray> binaryParts() { return b_ins; } + +protected: + QValueList<QByteArray> b_ins; + static bool yencMeta( QCString& src, const QCString& name, int* value); +}; + + +} // namespace Parser + +} // namespace KMime + +#endif // __KMIME_PARSERS__ diff --git a/libkmime/kmime_util.cpp b/libkmime/kmime_util.cpp new file mode 100644 index 000000000..8923a5b0a --- /dev/null +++ b/libkmime/kmime_util.cpp @@ -0,0 +1,806 @@ +/* + kmime_util.cpp + + KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001 the KMime authors. + See file AUTHORS for details + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "kmime_util.h" + +#include <kmdcodec.h> // for KCodec::{quotedPrintableDe,base64{En,De}}code +#include <kglobal.h> +#include <klocale.h> +#include <kcharsets.h> +#include <kdeversion.h> +#if KDE_IS_VERSION( 3, 1, 90 ) +#include <kcalendarsystem.h> +#endif + +#include <qtextcodec.h> +#include <qstrlist.h> // for QStrIList +#include <qregexp.h> + +#include <stdlib.h> +#include <ctype.h> +#include <time.h> // for time() +#include <unistd.h> // for getpid() + +using namespace KMime; + +namespace KMime { + +QStrIList c_harsetCache; +QStrIList l_anguageCache; + +const char* cachedCharset(const QCString &name) +{ + int idx=c_harsetCache.find(name.data()); + if(idx>-1) + return c_harsetCache.at(idx); + + c_harsetCache.append(name.upper().data()); + //kdDebug() << "KNMimeBase::cachedCharset() number of cs " << c_harsetCache.count() << endl; + return c_harsetCache.last(); +} + +const char* cachedLanguage(const QCString &name) +{ + int idx=l_anguageCache.find(name.data()); + if(idx>-1) + return l_anguageCache.at(idx); + + l_anguageCache.append(name.upper().data()); + //kdDebug() << "KNMimeBase::cachedCharset() number of cs " << c_harsetCache.count() << endl; + return l_anguageCache.last(); +} + +bool isUsAscii(const QString &s) +{ + uint sLength = s.length(); + for (uint i=0; i<sLength; i++) + if (s.at(i).latin1()<=0) // c==0: non-latin1, c<0: non-us-ascii + return false; + + return true; +} + +// "(),.:;<>@[\] +const uchar specialsMap[16] = { + 0x00, 0x00, 0x00, 0x00, // CTLs + 0x20, 0xCA, 0x00, 0x3A, // SPACE ... '?' + 0x80, 0x00, 0x00, 0x1C, // '@' ... '_' + 0x00, 0x00, 0x00, 0x00 // '`' ... DEL +}; + +// "(),:;<>@[\]/=? +const uchar tSpecialsMap[16] = { + 0x00, 0x00, 0x00, 0x00, // CTLs + 0x20, 0xC9, 0x00, 0x3F, // SPACE ... '?' + 0x80, 0x00, 0x00, 0x1C, // '@' ... '_' + 0x00, 0x00, 0x00, 0x00 // '`' ... DEL +}; + +// all except specials, CTLs, SPACE. +const uchar aTextMap[16] = { + 0x00, 0x00, 0x00, 0x00, + 0x5F, 0x35, 0xFF, 0xC5, + 0x7F, 0xFF, 0xFF, 0xE3, + 0xFF, 0xFF, 0xFF, 0xFE +}; + +// all except tspecials, CTLs, SPACE. +const uchar tTextMap[16] = { + 0x00, 0x00, 0x00, 0x00, + 0x5F, 0x36, 0xFF, 0xC0, + 0x7F, 0xFF, 0xFF, 0xE3, + 0xFF, 0xFF, 0xFF, 0xFE +}; + +// none except a-zA-Z0-9!*+-/ +const uchar eTextMap[16] = { + 0x00, 0x00, 0x00, 0x00, + 0x40, 0x35, 0xFF, 0xC0, + 0x7F, 0xFF, 0xFF, 0xE0, + 0x7F, 0xFF, 0xFF, 0xE0 +}; + +#if defined(_AIX) && defined(truncate) +#undef truncate +#endif + +QString decodeRFC2047String(const QCString &src, const char **usedCS, + const QCString &defaultCS, bool forceCS) +{ + QCString result, str; + QCString declaredCS; + char *pos, *dest, *beg, *end, *mid, *endOfLastEncWord=0; + char encoding = '\0'; + bool valid, onlySpacesSinceLastWord=false; + const int maxLen=400; + int i; + + if(src.find("=?") < 0) + result = src.copy(); + else { + result.truncate(src.length()); + for (pos=src.data(), dest=result.data(); *pos; pos++) + { + if (pos[0]!='=' || pos[1]!='?') + { + *dest++ = *pos; + if (onlySpacesSinceLastWord) + onlySpacesSinceLastWord = (pos[0]==' ' || pos[1]=='\t'); + continue; + } + beg = pos+2; + end = beg; + valid = TRUE; + // parse charset name + declaredCS=""; + for (i=2,pos+=2; i<maxLen && (*pos!='?'&&(ispunct(*pos)||isalnum(*pos))); i++) { + declaredCS+=(*pos); + pos++; + } + if (*pos!='?' || i<4 || i>=maxLen) valid = FALSE; + else + { + // get encoding and check delimiting question marks + encoding = toupper(pos[1]); + if (pos[2]!='?' || (encoding!='Q' && encoding!='B')) + valid = FALSE; + pos+=3; + i+=3; + } + if (valid) + { + mid = pos; + // search for end of encoded part + while (i<maxLen && *pos && !(*pos=='?' && *(pos+1)=='=')) + { + i++; + pos++; + } + end = pos+2;//end now points to the first char after the encoded string + if (i>=maxLen || !*pos) valid = FALSE; + } + + if (valid) { + // cut all linear-white space between two encoded words + if (onlySpacesSinceLastWord) + dest=endOfLastEncWord; + + if (mid < pos) { + str = QCString(mid, (int)(pos - mid + 1)); + if (encoding == 'Q') + { + // decode quoted printable text + for (i=str.length()-1; i>=0; i--) + if (str[i]=='_') str[i]=' '; + str = KCodecs::quotedPrintableDecode(str); + } + else + { + str = KCodecs::base64Decode(str); + } + if (!str.isNull()) { + for (i=0; str[i]; i++) { + *dest++ = str[i]; + } + } + } + + endOfLastEncWord=dest; + onlySpacesSinceLastWord=true; + + pos = end -1; + } + else + { + pos = beg - 2; + *dest++ = *pos++; + *dest++ = *pos; + } + } + *dest = '\0'; + } + + //find suitable QTextCodec + QTextCodec *codec=0; + bool ok=true; + if (forceCS || declaredCS.isEmpty()) { + codec=KGlobal::charsets()->codecForName(defaultCS); + (*usedCS)=cachedCharset(defaultCS); + } + else { + codec=KGlobal::charsets()->codecForName(declaredCS, ok); + if(!ok) { //no suitable codec found => use default charset + codec=KGlobal::charsets()->codecForName(defaultCS); + (*usedCS)=cachedCharset(defaultCS); + } + else + (*usedCS)=cachedCharset(declaredCS); + } + + return codec->toUnicode(result.data(), result.length()); +} + +QString decodeRFC2047String(const QCString &src) +{ + const char *usedCS; + return decodeRFC2047String(src, &usedCS, "utf-8", false); +} + +QCString encodeRFC2047String(const QString &src, const char *charset, + bool addressHeader, bool allow8BitHeaders) +{ + QCString encoded8Bit, result, usedCS; + unsigned int start=0,end=0; + bool nonAscii=false, ok=true, useQEncoding=false; + QTextCodec *codec=0; + + usedCS=charset; + codec=KGlobal::charsets()->codecForName(usedCS, ok); + + if(!ok) { + //no codec available => try local8Bit and hope the best ;-) + usedCS=KGlobal::locale()->encoding(); + codec=KGlobal::charsets()->codecForName(usedCS, ok); + } + + if (usedCS.find("8859-")>=0) // use "B"-Encoding for non iso-8859-x charsets + useQEncoding=true; + + encoded8Bit=codec->fromUnicode(src); + + if(allow8BitHeaders) + return encoded8Bit; + + uint encoded8BitLength = encoded8Bit.length(); + for (unsigned int i=0; i<encoded8BitLength; i++) { + if (encoded8Bit[i]==' ') // encoding starts at word boundaries + start = i+1; + + // encode escape character, for japanese encodings... + if (((signed char)encoded8Bit[i]<0) || (encoded8Bit[i] == '\033') || + (addressHeader && (strchr("\"()<>@,.;:\\[]=",encoded8Bit[i])!=0))) { + end = start; // non us-ascii char found, now we determine where to stop encoding + nonAscii=true; + break; + } + } + + if (nonAscii) { + while ((end<encoded8Bit.length())&&(encoded8Bit[end]!=' ')) // we encode complete words + end++; + + for (unsigned int x=end;x<encoded8Bit.length();x++) + if (((signed char)encoded8Bit[x]<0) || (encoded8Bit[x] == '\033') || + (addressHeader && (strchr("\"()<>@,.;:\\[]=",encoded8Bit[x])!=0))) { + end = encoded8Bit.length(); // we found another non-ascii word + + while ((end<encoded8Bit.length())&&(encoded8Bit[end]!=' ')) // we encode complete words + end++; + } + + result = encoded8Bit.left(start)+"=?"+usedCS; + + if (useQEncoding) { + result += "?Q?"; + + char c,hexcode; // implementation of the "Q"-encoding described in RFC 2047 + for (unsigned int i=start;i<end;i++) { + c = encoded8Bit[i]; + if (c == ' ') // make the result readable with not MIME-capable readers + result+='_'; + else + if (((c>='a')&&(c<='z'))|| // paranoid mode, we encode *all* special characters to avoid problems + ((c>='A')&&(c<='Z'))|| // with "From" & "To" headers + ((c>='0')&&(c<='9'))) + result+=c; + else { + result += "="; // "stolen" from KMail ;-) + hexcode = ((c & 0xF0) >> 4) + 48; + if (hexcode >= 58) hexcode += 7; + result += hexcode; + hexcode = (c & 0x0F) + 48; + if (hexcode >= 58) hexcode += 7; + result += hexcode; + } + } + } else { + result += "?B?"+KCodecs::base64Encode(encoded8Bit.mid(start,end-start), false); + } + + result +="?="; + result += encoded8Bit.right(encoded8Bit.length()-end); + } + else + result = encoded8Bit; + + return result; +} + +QCString uniqueString() +{ + static char chars[] = "0123456789abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + time_t now; + QCString ret; + char p[11]; + int pos, ran; + unsigned int timeval; + + p[10]='\0'; + now=time(0); + ran=1+(int) (1000.0*rand()/(RAND_MAX+1.0)); + timeval=(now/ran)+getpid(); + + for(int i=0; i<10; i++){ + pos=(int) (61.0*rand()/(RAND_MAX+1.0)); + //kdDebug(5003) << pos << endl; + p[i]=chars[pos]; + } + ret.sprintf("%d.%s", timeval, p); + + return ret; +} + + +QCString multiPartBoundary() +{ + QCString ret; + ret="nextPart"+uniqueString(); + return ret; +} + +QCString extractHeader(const QCString &src, const char *name) +{ + QCString n=QCString(name)+":"; + int pos1=-1, pos2=0, len=src.length()-1; + bool folded(false); + + if (n.lower() == src.left(n.length()).lower()) { + pos1 = 0; + } else { + n.prepend("\n"); + pos1 = src.find(n,0,false); + } + + if (pos1>-1) { //there is a header with the given name + pos1+=n.length(); //skip the name + // skip the usual space after the colon + if ( src.at( pos1 ) == ' ' ) + ++pos1; + pos2=pos1; + + if (src[pos2]!='\n') { // check if the header is not empty + while(1) { + pos2=src.find("\n", pos2+1); + if(pos2==-1 || pos2==len || ( src[pos2+1]!=' ' && src[pos2+1]!='\t') ) //break if we reach the end of the string, honor folded lines + break; + else + folded = true; + } + } + + if(pos2<0) pos2=len+1; //take the rest of the string + + if (!folded) + return src.mid(pos1, pos2-pos1); + else + return (src.mid(pos1, pos2-pos1).replace(QRegExp("\\s*\\n\\s*")," ")); + } + else { + return QCString(0); //header not found + } +} + + +QCString CRLFtoLF(const QCString &s) +{ + QCString ret=s.copy(); + ret.replace(QRegExp("\\r\\n"), "\n"); + return ret; +} + + +QCString CRLFtoLF(const char *s) +{ + QCString ret=s; + ret.replace(QRegExp("\\r\\n"), "\n"); + return ret; +} + + +QCString LFtoCRLF(const QCString &s) +{ + QCString ret=s.copy(); + ret.replace(QRegExp("\\n"), "\r\n"); + return ret; +} + + +void removeQuots(QCString &str) +{ + bool inQuote=false; + + for (int i=0; i < (int)str.length(); i++) { + if (str[i] == '"') { + str.remove(i,1); + i--; + inQuote = !inQuote; + } else { + if (inQuote && (str[i] == '\\')) + str.remove(i,1); + } + } +} + + +void removeQuots(QString &str) +{ + bool inQuote=false; + + for (int i=0; i < (int)str.length(); i++) { + if (str[i] == '"') { + str.remove(i,1); + i--; + inQuote = !inQuote; + } else { + if (inQuote && (str[i] == '\\')) + str.remove(i,1); + } + } +} + + +void addQuotes(QCString &str, bool forceQuotes) +{ + bool needsQuotes=false; + for (unsigned int i=0; i < str.length(); i++) { + if (strchr("()<>@,.;:[]=\\\"",str[i])!=0) + needsQuotes = true; + if (str[i]=='\\' || str[i]=='\"') { + str.insert(i, '\\'); + i++; + } + } + + if (needsQuotes || forceQuotes) { + str.insert(0,'\"'); + str.append("\""); + } +} + +int DateFormatter::mDaylight = -1; +DateFormatter::DateFormatter(FormatType fType) + : mFormat( fType ), mCurrentTime( 0 ) +{ + +} + +DateFormatter::~DateFormatter() +{/*empty*/} + +DateFormatter::FormatType +DateFormatter::getFormat() const +{ + return mFormat; +} + +void +DateFormatter::setFormat( FormatType t ) +{ + mFormat = t; +} + +QString +DateFormatter::dateString( time_t otime , const QString& lang , + bool shortFormat, bool includeSecs ) const +{ + switch ( mFormat ) { + case Fancy: + return fancy( otime ); + break; + case Localized: + return localized( otime, shortFormat, includeSecs, lang ); + break; + case CTime: + return cTime( otime ); + break; + case Iso: + return isoDate( otime ); + break; + case Custom: + return custom( otime ); + break; + } + return QString::null; +} + +QString +DateFormatter::dateString(const QDateTime& dtime, const QString& lang, + bool shortFormat, bool includeSecs ) const +{ + return DateFormatter::dateString( qdateToTimeT(dtime), lang, shortFormat, includeSecs ); +} + +QCString +DateFormatter::rfc2822(time_t otime) const +{ + QDateTime tmp; + QCString ret; + + tmp.setTime_t(otime); + + ret = tmp.toString("ddd, dd MMM yyyy hh:mm:ss ").latin1(); + ret += zone(otime); + + return ret; +} + +QString +DateFormatter::custom(time_t t) const +{ + if ( mCustomFormat.isEmpty() ) + return QString::null; + + int z = mCustomFormat.find("Z"); + QDateTime d; + QString ret = mCustomFormat; + + d.setTime_t(t); + if ( z != -1 ) { + ret.replace(z,1,zone(t)); + } + + ret = d.toString(ret); + + return ret; +} + +void +DateFormatter::setCustomFormat(const QString& format) +{ + mCustomFormat = format; + mFormat = Custom; +} + +QString +DateFormatter::getCustomFormat() const +{ + return mCustomFormat; +} + + +QCString +DateFormatter::zone(time_t otime) const +{ + QCString ret; +#if defined(HAVE_TIMEZONE) || defined(HAVE_TM_GMTOFF) + struct tm *local = localtime( &otime ); +#endif + +#if defined(HAVE_TIMEZONE) + + //hmm, could make hours & mins static + int secs = abs(timezone); + int neg = (timezone>0)?1:0; + int hours = secs/3600; + int mins = (secs - hours*3600)/60; + + // adjust to daylight + if ( local->tm_isdst > 0 ) { + mDaylight = 1; + if ( neg ) + --hours; + else + ++hours; + } else + mDaylight = 0; + + ret.sprintf("%c%.2d%.2d",(neg)?'-':'+', hours, mins); + +#elif defined(HAVE_TM_GMTOFF) + + int secs = abs( local->tm_gmtoff ); + int neg = (local->tm_gmtoff<0)?1:0; //no, I don't know why it's backwards :o + int hours = secs/3600; + int mins = (secs - hours*3600)/60; + + if ( local->tm_isdst > 0 ) + mDaylight = 1; + else + mDaylight = 0; + + ret.sprintf("%c%.2d%.2d",(neg)?'-':'+', hours, mins); + +#else + + QDateTime d1 = QDateTime::fromString( asctime(gmtime(&otime)) ); + QDateTime d2 = QDateTime::fromString( asctime(localtime(&otime)) ); + int secs = d1.secsTo(d2); + int neg = (secs<0)?1:0; + secs = abs(secs); + int hours = secs/3600; + int mins = (secs - hours*3600)/60; + // daylight should be already taken care of here + ret.sprintf("%c%.2d%.2d",(neg)?'-':'+', hours, mins); + +#endif /* HAVE_TIMEZONE */ + + return ret; +} + +time_t +DateFormatter::qdateToTimeT(const QDateTime& dt) const +{ + QDateTime epoch( QDate(1970, 1,1), QTime(00,00,00) ); + time_t otime; + time( &otime ); + + QDateTime d1 = QDateTime::fromString( asctime(gmtime(&otime)) ); + QDateTime d2 = QDateTime::fromString( asctime(localtime(&otime)) ); + time_t drf = epoch.secsTo( dt ) - d1.secsTo( d2 ); + + return drf; +} + +QString +DateFormatter::fancy(time_t otime) const +{ + KLocale *locale = KGlobal::locale(); + + if ( otime <= 0 ) + return i18n( "unknown" ); + + if ( !mCurrentTime ) { + time( &mCurrentTime ); + mDate.setTime_t( mCurrentTime ); + } + + QDateTime old; + old.setTime_t( otime ); + + // not more than an hour in the future + if ( mCurrentTime + 60 * 60 >= otime ) { + time_t diff = mCurrentTime - otime; + + if ( diff < 24 * 60 * 60 ) { + if ( old.date().year() == mDate.date().year() && + old.date().dayOfYear() == mDate.date().dayOfYear() ) + return i18n( "Today %1" ).arg( locale-> + formatTime( old.time(), true ) ); + } + if ( diff < 2 * 24 * 60 * 60 ) { + QDateTime yesterday( mDate.addDays( -1 ) ); + if ( old.date().year() == yesterday.date().year() && + old.date().dayOfYear() == yesterday.date().dayOfYear() ) + return i18n( "Yesterday %1" ).arg( locale-> + formatTime( old.time(), true) ); + } + for ( int i = 3; i < 7; i++ ) + if ( diff < i * 24 * 60 * 60 ) { + QDateTime weekday( mDate.addDays( -i + 1 ) ); + if ( old.date().year() == weekday.date().year() && + old.date().dayOfYear() == weekday.date().dayOfYear() ) + return i18n( "1. weekday, 2. time", "%1 %2" ). +#if KDE_IS_VERSION( 3, 1, 90 ) + arg( locale->calendar()->weekDayName( old.date() ) ). +#else + arg( locale->weekDayName( old.date().dayOfWeek() ) ). +#endif + arg( locale->formatTime( old.time(), true) ); + } + } + + return locale->formatDateTime( old ); + +} + +QString +DateFormatter::localized(time_t otime, bool shortFormat, bool includeSecs, + const QString& localeLanguage ) const +{ + QDateTime tmp; + QString ret; + KLocale *locale = KGlobal::locale(); + + tmp.setTime_t( otime ); + + + if ( !localeLanguage.isEmpty() ) { + locale=new KLocale(localeLanguage); + locale->setLanguage(localeLanguage); + locale->setCountry(localeLanguage); + ret = locale->formatDateTime( tmp, shortFormat, includeSecs ); + delete locale; + } else { + ret = locale->formatDateTime( tmp, shortFormat, includeSecs ); + } + + return ret; +} + +QString +DateFormatter::cTime(time_t otime) const +{ + return QString::fromLatin1( ctime( &otime ) ).stripWhiteSpace() ; +} + +QString +DateFormatter::isoDate(time_t otime) const +{ + char cstr[64]; + strftime( cstr, 63, "%Y-%m-%d %H:%M:%S", localtime(&otime) ); + return QString( cstr ); +} + + +void +DateFormatter::reset() +{ + mCurrentTime = 0; +} + +QString +DateFormatter::formatDate(DateFormatter::FormatType t, time_t otime, + const QString& data, bool shortFormat, bool includeSecs ) +{ + DateFormatter f( t ); + if ( t == DateFormatter::Custom ) { + f.setCustomFormat( data ); + } + return f.dateString( otime, data, shortFormat, includeSecs ); +} + +QString +DateFormatter::formatCurrentDate( DateFormatter::FormatType t, const QString& data, + bool shortFormat, bool includeSecs ) +{ + DateFormatter f( t ); + if ( t == DateFormatter::Custom ) { + f.setCustomFormat( data ); + } + return f.dateString( time(0), data, shortFormat, includeSecs ); +} + +QCString +DateFormatter::rfc2822FormatDate( time_t t ) +{ + DateFormatter f; + return f.rfc2822( t ); +} + +bool +DateFormatter::isDaylight() +{ + if ( mDaylight == -1 ) { + time_t ntime = time( 0 ); + struct tm *local = localtime( &ntime ); + if ( local->tm_isdst > 0 ) { + mDaylight = 1; + return true; + } else { + mDaylight = 0; + return false; + } + } else if ( mDaylight != 0 ) + return true; + else + return false; +} + +} // namespace KMime diff --git a/libkmime/kmime_util.h b/libkmime/kmime_util.h new file mode 100644 index 000000000..ed8710c92 --- /dev/null +++ b/libkmime/kmime_util.h @@ -0,0 +1,340 @@ +/* -*- c++ -*- + kmime_util.h + + KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2001 the KMime authors. + See file AUTHORS for details + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ +#ifndef __KMIME_UTIL_H__ +#define __KMIME_UTIL_H__ + +#include "qdatetime.h" +#include "qstring.h" +#include "qcstring.h" +#include "qvaluelist.h" +#include "time.h" +#include <kdepimmacros.h> + +typedef QValueList<QCString> QCStringList; + +namespace KMime { + + /** Consult the charset cache. Only used for reducing mem usage by + keeping strings in a common repository.*/ + extern const char* cachedCharset(const QCString &name) KDE_EXPORT; + + /** Consult the language cache. Only used for reducing mem usage by + keeping strings in a common repository.*/ + extern const char* cachedLanguage(const QCString &name) KDE_EXPORT; + + /** checks whether @p s contains any non-us-ascii characters */ + extern bool isUsAscii(const QString &s) KDE_EXPORT; + + inline bool isOfSet(const uchar map[16], unsigned char ch) { + Q_ASSERT( ch < 128 ); + return ( map[ ch/8 ] & 0x80 >> ch%8 ); + } + + extern const uchar specialsMap[16]; + extern const uchar tSpecialsMap[16]; + extern const uchar aTextMap[16]; + extern const uchar tTextMap[16]; + extern const uchar eTextMap[16]; + + inline bool isSpecial(char ch) { + return isOfSet( specialsMap, ch ); + } + inline bool isTSpecial(char ch) { + return isOfSet( tSpecialsMap, ch ); + } + inline bool isAText(char ch) { + return isOfSet( aTextMap, ch ); + } + inline bool isTText(char ch) { + return isOfSet( tTextMap, ch ); + } + inline bool isEText(char ch) { + return isOfSet( eTextMap, ch ); + } + + /** Decode string @p src according to RFC2047 (ie. the + =?charset?[qb]?encoded?= construct). + @param src source string. + @param usedCS the detected charset is returned here + @param defaultCS the charset to use in case the detected + one isn't known to us. + @param forceCS force the use of the default charset. + @return the decoded string. + */ + extern QString decodeRFC2047String(const QCString &src, const char **usedCS, + const QCString &defaultCS, bool forceCS) KDE_EXPORT; + + /** Decode string @p src according to RFC2047 (ie. the + =?charset?[qb]?encoded?= construct). + @param src source string. + @return the decoded string. + */ + extern QString decodeRFC2047String(const QCString &src) KDE_EXPORT; + + /** Encode string @p src according to RFC2047 using charset + @p charset. + @param src source string. + @param charset charset to use. + @param addressheader if this flag is true, all special chars + like <,>,[,],... will be encoded, too. + @param allow8BitHeaders if this flag is true, 8Bit headers + are allowed. + @return the encoded string. + */ + extern QCString encodeRFC2047String(const QString &src, const char *charset, + bool addressHeader=false, bool allow8bitHeaders=false) KDE_EXPORT; + + /** Uses current time, pid and random numbers to construct a string + that aims to be unique on a per-host basis (ie. for the local + part of a message-id or for multipart boundaries. + @return the unique string. + @see multiPartBoundary + */ + extern QCString uniqueString() KDE_EXPORT; + + /** Constructs a random string (sans leading/trailing "--") that can + be used as a multipart delimiter (ie. as @p boundary parameter + to a multipart/... content-type). + @return the randomized string. + @see uniqueString + */ + extern QCString multiPartBoundary() KDE_EXPORT; + + /** Tries to extract the header with name @p name from the string + @p src, unfolding it if necessary. + @param src the source string. + @param name the name of the header to search for. + @return the first instance of the header @p name in @p src + or a null QCString if no such header was found. + */ + extern QCString extractHeader(const QCString &src, const char *name) KDE_EXPORT; + /** Converts all occurrences of "\r\n" (CRLF) in @p s to "\n" (LF). + + This function is expensive and should be used only if the mail + will be stored locally. All decode functions can cope with both + line endings. + @param s source string containing CRLF's + @return the string with CRLF's substitued for LF's + @see CRLFtoLF(const char*) LFtoCRLF + */ + extern QCString CRLFtoLF(const QCString &s) KDE_EXPORT; + /** Converts all occurrences of "\r\n" (CRLF) in @p s to "\n" (LF). + + This function is expensive and should be used only if the mail + will be stored locally. All decode functions can cope with both + line endings. + @param s source string containing CRLF's + @return the string with CRLF's substitued for LF's + @see CRLFtoLF(const QCString&) LFtoCRLF + */ + extern QCString CRLFtoLF(const char *s) KDE_EXPORT; + /** Converts all occurrences of "\n" (LF) in @p s to "\r\n" (CRLF). + + This function is expensive and should be used only if the mail + will be transmitted as an RFC822 message later. All decode + functions can cope with and all encode functions can optionally + produce both line endings, which is much faster. + + @param s source string containing CRLF's + @return the string with CRLF's substitued for LF's + @see CRLFtoLF(const QCString&) LFtoCRLF + */ + extern QCString LFtoCRLF(const QCString &s) KDE_EXPORT; + + /** Removes quote (DQUOTE) characters and decodes "quoted-pairs" + (ie. backslash-escaped characters) + @param str the string to work on. + @see addQuotes + */ + KDE_EXPORT extern void removeQuots(QCString &str); + /** Removes quote (DQUOTE) characters and decodes "quoted-pairs" + (ie. backslash-escaped characters) + @param str the string to work on. + @see addQuotes + */ + KDE_EXPORT extern void removeQuots(QString &str); + /** Converts the given string into a quoted-string if + the string contains any special characters + (ie. one of ()<>@,.;:[]=\"). + @param str us-ascii string to work on. + @param forceQuotes if @p true, always add quote characters. + */ + KDE_EXPORT extern void addQuotes(QCString &str, bool forceQuotes); + + + /** + * @short class abstracting date formatting + * + * DateFormatter deals with different kinds of date + * display formats. The formats supported by the class include: + * <ul> + * <li> fancy "Today 02:08:35" + * <li> ctime "Sun Mar 31 02:08:35 2002" + * <li> localized "2002-03-31 02:08" + * <li> iso "2002-03-31 02:08:35" + * <li> rfc2822 "Sun, 31 Mar 2002 02:08:35 -0500" + * <li> custom "whatever you like" + * </ul> + * + * + */ + class KDE_EXPORT DateFormatter { + public: + enum FormatType { + CTime, //< ctime "Sun Mar 31 02:08:35 2002" + Localized, //< localized "2002-03-31 02:08" + Fancy, //< fancy "Today 02:08:35" + Iso, //< iso "2002-03-31 02:08:35" + Custom //< custom "whatever you like" + }; + + /** + * constructor + * @param fType default format used by the class + */ + DateFormatter(FormatType fType = DateFormatter::Fancy); + + ~DateFormatter(); + + /** + * returns the currently set format + */ + FormatType getFormat() const; + /** + * sets the currently used format + */ + void setFormat(FormatType t); + + /** + * returns formatted date string in a currently + * set format. + * @param otime time to format + * @param lang used <em>only</em> by the Localized format, sets the used language + * @param shortFormat used <em>only</em> by the Localized format, is passed to KLocale::formatDateTime + * @param includeSecs used <em>only</em> by the Localized format, is passed to KLocale::formatDateTime + */ + QString dateString(time_t otime, const QString& lang = QString::null, + bool shortFormat = true, bool includeSecs=false) const; + /** + * overloaded, does exactly what #dateString does (it's slower) + */ + QString dateString(const QDateTime& dtime, const QString& lang = QString::null, + bool shortFormat = true, bool includeSecs=false) const; + + + /** + * makes the class use the custom format for + * date to string conversions. + * Method accepts the same arguments + * as QDateTime::toString method and adds + * "Z" expression which is substituted with the + * RFC-822 style numeric timezone (-0500) + * @param format the custom format + */ + void setCustomFormat(const QString& format); + QString getCustomFormat() const; + + /** + * returns rfc2822 formatted string + * @param otime time to use for formatting + */ + QCString rfc2822(time_t otime) const; + /** + * resets the internal clock + */ + void reset(); + + //statics + /** convenience function dateString + * @param t specifies the FormatType to use + * @param time time to format + * @param data is either the format when FormatType is Custom, or language + * when FormatType is Localized + * @param shortFormat used <em>only</em> by the Localized format, is passed to KLocale::formatDateTime + * @param includeSecs used <em>only</em> by the Localized format, is passed to KLocale::formatDateTime + */ + static QString formatDate( DateFormatter::FormatType t, time_t time, + const QString& data = QString::null, + bool shortFormat = true, bool includeSecs=false); + + /** convenience function, same as #formatDate + * but returns the current time formatted + * @param t specifies the FormatType to use + * @param data is either the format when FormatType is Custom, or language + * when FormatType is Localized + * @param shortFormat used <em>only</em> by the Localized format, is passed to KLocale::formatDateTime + * @param includeSecs used <em>only</em> by the Localized format, is passed to KLocale::formatDateTime + */ + static QString formatCurrentDate( DateFormatter::FormatType t, + const QString& data = QString::null, + bool shortFormat = true, bool includeSecs=false); + + /** convenience function, same as #rfc2822 */ + static QCString rfc2822FormatDate( time_t time ); + static bool isDaylight(); + protected: + /** + * returns fancy formatted date string + * @param otime time to format + * @internal + */ + QString fancy(time_t otime) const ; + /** + * returns localized formatted date string + * @param otime time to format + * @param shortFormat + * @param includeSecs + * @param localeLanguage language used for formatting + * @internal + */ + QString localized(time_t otime, bool shortFormat = true, bool includeSecs = false, + const QString& localeLanguage=QString::null ) const; + /** + * returns string as formatted with ctime function + * @internal + */ + QString cTime(time_t otime) const; + /** + * returns a string in the "%Y-%m-%d %H:%M:%S" format + * @internal + */ + QString isoDate(time_t otime) const; + + /** + * returns date formatted with the earlier + * given custom format + * @param t time used for formatting + * @internal + */ + QString custom(time_t t) const; + /** + * returns a string identifying the timezone (eg."-0500") + * @internal + */ + QCString zone(time_t otime) const; + + time_t qdateToTimeT(const QDateTime& dt) const; + private: + FormatType mFormat; + mutable time_t mCurrentTime; + mutable QDateTime mDate; + QString mCustomFormat; + static int mDaylight; + }; + +} // namespace KMime + +#endif /* __KMIME_UTIL_H__ */ diff --git a/libkmime/kmime_version.h b/libkmime/kmime_version.h new file mode 100644 index 000000000..fbab89a3f --- /dev/null +++ b/libkmime/kmime_version.h @@ -0,0 +1,10 @@ +#ifndef __KMIME_VERSION_H__ +#define __KMIME_VERSION_H__ + +#define KMIME_MAJOR 0; +#define KMIME_MINOR 1; +#define KMIME_PATCHLEVEL 0; +#define KMIME_VERSION (KMIME_MAJOR * 100 + KMIME_MINOR * 10 + KMIME_PATCHLEVEL) +#define KMIME_VERSION_STRING "0.1.0" + +#endif // __KMIME_VERSION_H__ diff --git a/libkmime/kmime_warning.h b/libkmime/kmime_warning.h new file mode 100644 index 000000000..97b714a9e --- /dev/null +++ b/libkmime/kmime_warning.h @@ -0,0 +1,32 @@ +#ifndef KMIME_NO_WARNING +# include <kdebug.h> +# define KMIME_WARN kdWarning(5100) << "Tokenizer Warning: " +# define KMIME_WARN_UNKNOWN(x,y) KMIME_WARN << "unknown " #x ": \"" \ + << y << "\"" << endl; +# define KMIME_WARN_UNKNOWN_ENCODING KMIME_WARN << "unknown encoding in " \ + "RFC 2047 encoded-word (only know 'q' and 'b')" << endl; +# define KMIME_WARN_UNKNOWN_CHARSET(c) KMIME_WARN << "unknown charset \"" \ + << c << "\" in RFC 2047 encoded-word" << endl; +# define KMIME_WARN_8BIT(ch) KMIME_WARN \ + << "8Bit character '" << QString(QChar(ch)) << "'" << endl +# define KMIME_WARN_IF_8BIT(ch) if ( (unsigned char)(ch) > 127 ) \ + { KMIME_WARN_8BIT(ch); } +# define KMIME_WARN_PREMATURE_END_OF(x) KMIME_WARN \ + << "Premature end of " #x << endl +# define KMIME_WARN_LONE(x) KMIME_WARN << "Lonely " #x " character" << endl +# define KMIME_WARN_NON_FOLDING(x) KMIME_WARN << "Non-folding " #x << endl +# define KMIME_WARN_CTL_OUTSIDE_QS(x) KMIME_WARN << "Control character " \ + #x " outside quoted-string" << endl +# define KMIME_WARN_INVALID_X_IN_Y(X,Y) KMIME_WARN << "Invalid character '" \ + QString(QChar(X)) << "' in " #Y << endl; +# define KMIME_WARN_TOO_LONG(x) KMIME_WARN << #x \ + " too long or missing delimiter" << endl; +#else +# define KMIME_NOP do {} while (0) +# define KMIME_WARN_8BIT(ch) KMIME_NOP +# define KMIME_WARN_IF_8BIT(ch) KMIME_NOP +# define KMIME_WARN_PREMATURE_END_OF(x) KMIME_NOP +# define KMIME_WARN_LONE(x) KMIME_NOP +# define KMIME_WARN_NON_FOLDING(x) KMIME_NOP +# define KMIME_WARN_CTL_OUTSIDE_QS(x) KMIME_NOP +#endif diff --git a/libkmime/kqcstringsplitter.cpp b/libkmime/kqcstringsplitter.cpp new file mode 100644 index 000000000..fdd6dff09 --- /dev/null +++ b/libkmime/kqcstringsplitter.cpp @@ -0,0 +1,161 @@ +/* + kqcstringsplitter.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ + +#include "kqcstringsplitter.h" + +KQCStringSplitter::KQCStringSplitter() +{ + reset(); +} + + + + +KQCStringSplitter::~KQCStringSplitter() +{ +} + + + +void KQCStringSplitter::init(const QCString &str, const char *s) +{ + sep=s; + src=str; +} + + + +void KQCStringSplitter::init(const char *str, const char *s) +{ + sep=s; + src=str; +} + +bool KQCStringSplitter::first() +{ + /*int plus; + if(incSep) plus=sep.length(); + else plus=0; */ + + start=0; + + end=src.find(sep, start); + + if(end!=-1) { + dst=src.mid(start, end); + return true; + } + else { + start=src.length(); + end=start; + return false; + } + +} + + + +bool KQCStringSplitter::last() +{ + /*int startplus, endplus; + + if(incSep) { + startplus=0; + endplus=sep.length(); + } + else { + startplus=sep.length(); + endplus=0; + }*/ + + end=src.length(); + + start=src.findRev(sep,end); + + if(start!=-1) { + dst=src.mid(start, end-start); + return true; + } + else return false; + + +} + + + +bool KQCStringSplitter::next() +{ + /*int plus; + if(incSep) plus=sep.length(); + else plus=0;*/ + + start=end+1; + + if(start< (int) src.length()) { + + end=src.find(sep, start); + + if(end!=-1) { + dst=src.mid(start, end-start); + } + else { + dst=src.mid(start, src.length()-start); + start=src.length(); + end=src.length(); + } + + return true; + } + else return false; + +} + + + +bool KQCStringSplitter::prev() +{ + /*int startplus, endplus; + + if(incSep) { + startplus=0; + endplus=sep.length(); + } + else { + startplus=sep.length(); + endplus=0; + }*/ + + end=start-1; + + if(end>0) { + + start=src.findRev(sep,end); + + if(start!=-1) + dst=src.mid(start, end-start); + + else { + dst=src.mid(0, end+1); + end=0; + start=0; + } + + return true; + } + else return false; + +} + diff --git a/libkmime/kqcstringsplitter.h b/libkmime/kqcstringsplitter.h new file mode 100644 index 000000000..14947643b --- /dev/null +++ b/libkmime/kqcstringsplitter.h @@ -0,0 +1,52 @@ +/* + kqcstringsplitter.h + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ + +#ifndef KQCSTRINGSPLITTER_H +#define KQCSTRINGSPLITTER_H + +#include <qcstring.h> + +#include <kdepimmacros.h> + +class KDE_EXPORT KQCStringSplitter { + + public: + KQCStringSplitter(); + ~KQCStringSplitter(); + + void reset() { start=0; end=0; sep=""; incSep=false;} + + void init(const QCString &str, const char *s); + void init(const char *str, const char *s); + void setIncludeSep(bool inc) { incSep=inc; } + + bool first(); + bool last(); + + bool next(); + bool prev(); + + QCString& string() { return dst; } + const QCString& source() { return src; } + + private: + QCString src, dst, sep; + int start,end; + bool incSep; + +}; + +#endif diff --git a/libkmime/tests/Makefile.am b/libkmime/tests/Makefile.am new file mode 100644 index 000000000..643d99889 --- /dev/null +++ b/libkmime/tests/Makefile.am @@ -0,0 +1,16 @@ +AM_CPPFLAGS = -I$(top_srcdir)/libkmime $(all_includes) +LDADD = ../libkmime.la + +# test programs: +check_PROGRAMS = test_kmime_header_parsing \ + test_charfreq \ + test_mdn \ + test_dates \ + test_kmime_codec + +test_kmime_codec_SOURCES = test_kmime_codec.cpp +test_charfreq_SOURCES = test_charfreq.cpp +test_mdn_SOURCES = test_mdn.cpp +test_dates_SOURCES = test_dates.cpp +test_kmime_header_parsing_SOURCES = test_kmime_header_parsing.cpp + diff --git a/libkmime/tests/data/codec_b/basic-decode.b b/libkmime/tests/data/codec_b/basic-decode.b new file mode 100644 index 000000000..742532dca --- /dev/null +++ b/libkmime/tests/data/codec_b/basic-decode.b @@ -0,0 +1 @@ +AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==
\ No newline at end of file diff --git a/libkmime/tests/data/codec_b/basic-decode.b.expected b/libkmime/tests/data/codec_b/basic-decode.b.expected Binary files differnew file mode 100644 index 000000000..c86626638 --- /dev/null +++ b/libkmime/tests/data/codec_b/basic-decode.b.expected diff --git a/libkmime/tests/data/codec_b/basic-encode b/libkmime/tests/data/codec_b/basic-encode Binary files differnew file mode 100644 index 000000000..c86626638 --- /dev/null +++ b/libkmime/tests/data/codec_b/basic-encode diff --git a/libkmime/tests/data/codec_b/basic-encode.expected b/libkmime/tests/data/codec_b/basic-encode.expected new file mode 100644 index 000000000..742532dca --- /dev/null +++ b/libkmime/tests/data/codec_b/basic-encode.expected @@ -0,0 +1 @@ +AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==
\ No newline at end of file diff --git a/libkmime/tests/data/codec_b/null-decode.b b/libkmime/tests/data/codec_b/null-decode.b new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/libkmime/tests/data/codec_b/null-decode.b diff --git a/libkmime/tests/data/codec_b/null-decode.b.expected b/libkmime/tests/data/codec_b/null-decode.b.expected new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/libkmime/tests/data/codec_b/null-decode.b.expected diff --git a/libkmime/tests/data/codec_b/null-encode b/libkmime/tests/data/codec_b/null-encode new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/libkmime/tests/data/codec_b/null-encode diff --git a/libkmime/tests/data/codec_b/null-encode.expected b/libkmime/tests/data/codec_b/null-encode.expected new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/libkmime/tests/data/codec_b/null-encode.expected diff --git a/libkmime/tests/data/codec_b/padding0 b/libkmime/tests/data/codec_b/padding0 new file mode 100644 index 000000000..f2ba8f84a --- /dev/null +++ b/libkmime/tests/data/codec_b/padding0 @@ -0,0 +1 @@ +abc
\ No newline at end of file diff --git a/libkmime/tests/data/codec_b/padding0.expected b/libkmime/tests/data/codec_b/padding0.expected new file mode 100644 index 000000000..83ee822d5 --- /dev/null +++ b/libkmime/tests/data/codec_b/padding0.expected @@ -0,0 +1 @@ +YWJj
\ No newline at end of file diff --git a/libkmime/tests/data/codec_b/padding1 b/libkmime/tests/data/codec_b/padding1 new file mode 100644 index 000000000..9ae9e86b7 --- /dev/null +++ b/libkmime/tests/data/codec_b/padding1 @@ -0,0 +1 @@ +ab
\ No newline at end of file diff --git a/libkmime/tests/data/codec_b/padding1.expected b/libkmime/tests/data/codec_b/padding1.expected new file mode 100644 index 000000000..ea0a9ede2 --- /dev/null +++ b/libkmime/tests/data/codec_b/padding1.expected @@ -0,0 +1 @@ +YWI=
\ No newline at end of file diff --git a/libkmime/tests/data/codec_b/padding2 b/libkmime/tests/data/codec_b/padding2 new file mode 100644 index 000000000..2e65efe2a --- /dev/null +++ b/libkmime/tests/data/codec_b/padding2 @@ -0,0 +1 @@ +a
\ No newline at end of file diff --git a/libkmime/tests/data/codec_b/padding2.expected b/libkmime/tests/data/codec_b/padding2.expected new file mode 100644 index 000000000..3b15ffb5e --- /dev/null +++ b/libkmime/tests/data/codec_b/padding2.expected @@ -0,0 +1 @@ +YQ==
\ No newline at end of file diff --git a/libkmime/tests/data/codec_base64/basic-decode.base64 b/libkmime/tests/data/codec_base64/basic-decode.base64 new file mode 100644 index 000000000..da6724734 --- /dev/null +++ b/libkmime/tests/data/codec_base64/basic-decode.base64 @@ -0,0 +1,5 @@ +AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4 +OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3Bx +cnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmq +q6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj +5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w== diff --git a/libkmime/tests/data/codec_base64/basic-decode.base64.expected b/libkmime/tests/data/codec_base64/basic-decode.base64.expected Binary files differnew file mode 100644 index 000000000..c86626638 --- /dev/null +++ b/libkmime/tests/data/codec_base64/basic-decode.base64.expected diff --git a/libkmime/tests/data/codec_base64/basic-encode b/libkmime/tests/data/codec_base64/basic-encode Binary files differnew file mode 100644 index 000000000..c86626638 --- /dev/null +++ b/libkmime/tests/data/codec_base64/basic-encode diff --git a/libkmime/tests/data/codec_base64/basic-encode.expected b/libkmime/tests/data/codec_base64/basic-encode.expected new file mode 100644 index 000000000..da6724734 --- /dev/null +++ b/libkmime/tests/data/codec_base64/basic-encode.expected @@ -0,0 +1,5 @@ +AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4 +OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3Bx +cnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmq +q6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj +5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w== diff --git a/libkmime/tests/data/codec_base64/corrupt.base64 b/libkmime/tests/data/codec_base64/corrupt.base64 new file mode 100644 index 000000000..1d13f2431 --- /dev/null +++ b/libkmime/tests/data/codec_base64/corrupt.base64 @@ -0,0 +1,5 @@ +A AECAwQ-FBgcICQoLDA0OD&xAREhM +UFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4 +OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmq +q6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIüycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj +5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==ghfh
\ No newline at end of file diff --git a/libkmime/tests/data/codec_base64/corrupt.base64.expected b/libkmime/tests/data/codec_base64/corrupt.base64.expected Binary files differnew file mode 100644 index 000000000..c86626638 --- /dev/null +++ b/libkmime/tests/data/codec_base64/corrupt.base64.expected diff --git a/libkmime/tests/data/codec_base64/very_small b/libkmime/tests/data/codec_base64/very_small new file mode 100644 index 000000000..3cacc0b93 --- /dev/null +++ b/libkmime/tests/data/codec_base64/very_small @@ -0,0 +1 @@ +12
\ No newline at end of file diff --git a/libkmime/tests/data/codec_base64/very_small.expected b/libkmime/tests/data/codec_base64/very_small.expected new file mode 100644 index 000000000..7f9ade1ac --- /dev/null +++ b/libkmime/tests/data/codec_base64/very_small.expected @@ -0,0 +1 @@ +MTI= diff --git a/libkmime/tests/data/codec_q/all-encoded.q b/libkmime/tests/data/codec_q/all-encoded.q new file mode 100644 index 000000000..a421bb40d --- /dev/null +++ b/libkmime/tests/data/codec_q/all-encoded.q @@ -0,0 +1 @@ +=00=01=02=03=04=05=06=07=08=09=0A=0B=0C=0D=0E=0F=10=11=12=13=14=15=16=17=18=19=1A=1B=1C=1D=1E=1F=20=21=22=23=24=25=26=27=28=29=2A=2B=2C=2D=2E=2F=30=31=32=33=34=35=36=37=38=39=3A=3B=3C=3D=3E=3F=40=41=42=43=44=45=46=47=48=49=4A=4B=4C=4D=4E=4F=50=51=52=53=54=55=56=57=58=59=5A=5B=5C=5D=5E=5F=60=61=62=63=64=65=66=67=68=69=6A=6B=6C=6D=6E=6F=70=71=72=73=74=75=76=77=78=79=7A=7B=7C=7D=7E=7F=80=81=82=83=84=85=86=87=88=89=8A=8B=8C=8D=8E=8F=90=91=92=93=94=95=96=97=98=99=9A=9B=9C=9D=9E=9F=A0=A1=A2=A3=A4=A5=A6=A7=A8=A9=AA=AB=AC=AD=AE=AF=B0=B1=B2=B3=B4=B5=B6=B7=B8=B9=BA=BB=BC=BD=BE=BF=C0=C1=C2=C3=C4=C5=C6=C7=C8=C9=CA=CB=CC=CD=CE=CF=D0=D1=D2=D3=D4=D5=D6=D7=D8=D9=DA=DB=DC=DD=DE=DF=E0=E1=E2=E3=E4=E5=E6=E7=E8=E9=EA=EB=EC=ED=EE=EF=F0=F1=F2=F3=F4=F5=F6=F7=F8=F9=FA=FB=FC=FD=FE=FF
\ No newline at end of file diff --git a/libkmime/tests/data/codec_q/all-encoded.q.expected b/libkmime/tests/data/codec_q/all-encoded.q.expected Binary files differnew file mode 100644 index 000000000..c86626638 --- /dev/null +++ b/libkmime/tests/data/codec_q/all-encoded.q.expected diff --git a/libkmime/tests/data/codec_q/basic-encode b/libkmime/tests/data/codec_q/basic-encode Binary files differnew file mode 100644 index 000000000..c86626638 --- /dev/null +++ b/libkmime/tests/data/codec_q/basic-encode diff --git a/libkmime/tests/data/codec_q/basic-encode.expected b/libkmime/tests/data/codec_q/basic-encode.expected new file mode 100644 index 000000000..8f051a201 --- /dev/null +++ b/libkmime/tests/data/codec_q/basic-encode.expected @@ -0,0 +1 @@ +=00=01=02=03=04=05=06=07=08=09=0A=0B=0C=0D=0E=0F=10=11=12=13=14=15=16=17=18=19=1A=1B=1C=1D=1E=1F_!=22=23=24=25=26=27=28=29*+=2C-=2E/0123456789=3A=3B=3C=3D=3E=3F=40ABCDEFGHIJKLMNOPQRSTUVWXYZ=5B=5C=5D=5E=5F=60abcdefghijklmnopqrstuvwxyz=7B=7C=7D=7E=7F=80=81=82=83=84=85=86=87=88=89=8A=8B=8C=8D=8E=8F=90=91=92=93=94=95=96=97=98=99=9A=9B=9C=9D=9E=9F=A0=A1=A2=A3=A4=A5=A6=A7=A8=A9=AA=AB=AC=AD=AE=AF=B0=B1=B2=B3=B4=B5=B6=B7=B8=B9=BA=BB=BC=BD=BE=BF=C0=C1=C2=C3=C4=C5=C6=C7=C8=C9=CA=CB=CC=CD=CE=CF=D0=D1=D2=D3=D4=D5=D6=D7=D8=D9=DA=DB=DC=DD=DE=DF=E0=E1=E2=E3=E4=E5=E6=E7=E8=E9=EA=EB=EC=ED=EE=EF=F0=F1=F2=F3=F4=F5=F6=F7=F8=F9=FA=FB=FC=FD=FE=FF
\ No newline at end of file diff --git a/libkmime/tests/data/codec_q/nothing-encoded.q b/libkmime/tests/data/codec_q/nothing-encoded.q Binary files differnew file mode 100644 index 000000000..c86626638 --- /dev/null +++ b/libkmime/tests/data/codec_q/nothing-encoded.q diff --git a/libkmime/tests/data/codec_q/nothing-encoded.q.expected b/libkmime/tests/data/codec_q/nothing-encoded.q.expected new file mode 100644 index 000000000..8323fc635 --- /dev/null +++ b/libkmime/tests/data/codec_q/nothing-encoded.q.expected @@ -0,0 +1,2 @@ + + !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^ `abcdefghijklmnopqrstuvwxyz{|}~
\ No newline at end of file diff --git a/libkmime/tests/data/codec_q/null-decode.q b/libkmime/tests/data/codec_q/null-decode.q new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/libkmime/tests/data/codec_q/null-decode.q diff --git a/libkmime/tests/data/codec_q/null-decode.q.expected b/libkmime/tests/data/codec_q/null-decode.q.expected new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/libkmime/tests/data/codec_q/null-decode.q.expected diff --git a/libkmime/tests/data/codec_q/null-encode b/libkmime/tests/data/codec_q/null-encode new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/libkmime/tests/data/codec_q/null-encode diff --git a/libkmime/tests/data/codec_q/null-encode.expected b/libkmime/tests/data/codec_q/null-encode.expected new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/libkmime/tests/data/codec_q/null-encode.expected diff --git a/libkmime/tests/data/codec_quoted-printable/basic-decode.quoted-printable b/libkmime/tests/data/codec_quoted-printable/basic-decode.quoted-printable new file mode 100644 index 000000000..47b7e654e --- /dev/null +++ b/libkmime/tests/data/codec_quoted-printable/basic-decode.quoted-printable @@ -0,0 +1,9 @@ +=00=01=02=03=04=05=06=07=08=09 +=0B=0C=0D=0E=0F=10=11=12=13=14=15=16=17=18=19=1A=1B=1C=1D=1E=1F !"#$%&'()*+= +,-./0123456789:;<=3D>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrst= +uvwxyz{|}~=7F=80=81=82=83=84=85=86=87=88=89=8A=8B=8C=8D=8E=8F=90=91=92=93= +=94=95=96=97=98=99=9A=9B=9C=9D=9E=9F=A0=A1=A2=A3=A4=A5=A6=A7=A8=A9=AA=AB=AC= +=AD=AE=AF=B0=B1=B2=B3=B4=B5=B6=B7=B8=B9=BA=BB=BC=BD=BE=BF=C0=C1=C2=C3=C4=C5= +=C6=C7=C8=C9=CA=CB=CC=CD=CE=CF=D0=D1=D2=D3=D4=D5=D6=D7=D8=D9=DA=DB=DC=DD=DE= +=DF=E0=E1=E2=E3=E4=E5=E6=E7=E8=E9=EA=EB=EC=ED=EE=EF=F0=F1=F2=F3=F4=F5=F6=F7= +=F8=F9=FA=FB=FC=FD=FE=FF
\ No newline at end of file diff --git a/libkmime/tests/data/codec_quoted-printable/basic-decode.quoted-printable.expected b/libkmime/tests/data/codec_quoted-printable/basic-decode.quoted-printable.expected Binary files differnew file mode 100644 index 000000000..c86626638 --- /dev/null +++ b/libkmime/tests/data/codec_quoted-printable/basic-decode.quoted-printable.expected diff --git a/libkmime/tests/data/codec_quoted-printable/basic-encode b/libkmime/tests/data/codec_quoted-printable/basic-encode Binary files differnew file mode 100644 index 000000000..c86626638 --- /dev/null +++ b/libkmime/tests/data/codec_quoted-printable/basic-encode diff --git a/libkmime/tests/data/codec_quoted-printable/basic-encode.expected b/libkmime/tests/data/codec_quoted-printable/basic-encode.expected new file mode 100644 index 000000000..47b7e654e --- /dev/null +++ b/libkmime/tests/data/codec_quoted-printable/basic-encode.expected @@ -0,0 +1,9 @@ +=00=01=02=03=04=05=06=07=08=09 +=0B=0C=0D=0E=0F=10=11=12=13=14=15=16=17=18=19=1A=1B=1C=1D=1E=1F !"#$%&'()*+= +,-./0123456789:;<=3D>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrst= +uvwxyz{|}~=7F=80=81=82=83=84=85=86=87=88=89=8A=8B=8C=8D=8E=8F=90=91=92=93= +=94=95=96=97=98=99=9A=9B=9C=9D=9E=9F=A0=A1=A2=A3=A4=A5=A6=A7=A8=A9=AA=AB=AC= +=AD=AE=AF=B0=B1=B2=B3=B4=B5=B6=B7=B8=B9=BA=BB=BC=BD=BE=BF=C0=C1=C2=C3=C4=C5= +=C6=C7=C8=C9=CA=CB=CC=CD=CE=CF=D0=D1=D2=D3=D4=D5=D6=D7=D8=D9=DA=DB=DC=DD=DE= +=DF=E0=E1=E2=E3=E4=E5=E6=E7=E8=E9=EA=EB=EC=ED=EE=EF=F0=F1=F2=F3=F4=F5=F6=F7= +=F8=F9=FA=FB=FC=FD=FE=FF
\ No newline at end of file diff --git a/libkmime/tests/data/codec_quoted-printable/corrupt.quoted-printable b/libkmime/tests/data/codec_quoted-printable/corrupt.quoted-printable new file mode 100644 index 000000000..ca67337f8 --- /dev/null +++ b/libkmime/tests/data/codec_quoted-printable/corrupt.quoted-printable @@ -0,0 +1,28 @@ +Trailing spaces should be stripped: +Here, only five trailing spaces should appear: =20 +Trailing tabs should be stripped: +Here, only five trailing spaces should appear: =20 +Trailing mixture of tabs and spaces should be stripped: +Trailing mixture of tabs and spaces should be stripped: +Tab, space, space, tab: =09 +Space, tab, tab, space: =20 + +Trailing spaces should= + be stripped: +Trailing tabs should= + be stripped: +Trailing mixture of tabs= + and spaces= + should be stripped: +Tab, space, space, tab: = +Space, tab, tab, space: = +The end. + +A =3D wasn't properly encoded (should be kept): APE=MAN MAN=APE +A =3D wasn't properly encoded (lowercase): ape=man man=ape +Lowercase hexchars: =bb=a1=4b=44=45 =72=75=6c=65=7a=21=ab +Mixed-case hexchars: =Bb=A1=4B=44=45 =72=75=6C=65=7A=21=aB +A misplaced (unencoded =3D), followed by whitespace: = not at end! +Two consecutive =3D at the end of the line: == +Same, followed by trailing whitespace: == +A misplaced (unencoded =3D), as the ultimate character: =
\ No newline at end of file diff --git a/libkmime/tests/data/codec_quoted-printable/corrupt.quoted-printable.expected b/libkmime/tests/data/codec_quoted-printable/corrupt.quoted-printable.expected new file mode 100644 index 000000000..18993c21f --- /dev/null +++ b/libkmime/tests/data/codec_quoted-printable/corrupt.quoted-printable.expected @@ -0,0 +1,20 @@ +Trailing spaces should be stripped: +Here, only five trailing spaces should appear: +Trailing tabs should be stripped: +Here, only five trailing spaces should appear: +Trailing mixture of tabs and spaces should be stripped: +Trailing mixture of tabs and spaces should be stripped: +Tab, space, space, tab: +Space, tab, tab, space: + +Trailing spaces should be stripped: +Trailing tabs should be stripped: +Trailing mixture of tabs and spaces should be stripped: +Tab, space, space, tab: Space, tab, tab, space: The end. + +A = wasn't properly encoded (should be kept): APE=MAN MAN=APE +A = wasn't properly encoded (lowercase): ape=man man=ape +Lowercase hexchars: »¡KDE rulez!« +Mixed-case hexchars: »¡KDE rulez!« +A misplaced (unencoded =), followed by whitespace: = not at end! +Two consecutive = at the end of the line: =Same, followed by trailing whitespace: =A misplaced (unencoded =), as the ultimate character: =
\ No newline at end of file diff --git a/libkmime/tests/data/codec_quoted-printable/wrap b/libkmime/tests/data/codec_quoted-printable/wrap new file mode 100644 index 000000000..49e8a9ccd --- /dev/null +++ b/libkmime/tests/data/codec_quoted-printable/wrap @@ -0,0 +1,44 @@ +This is a line without a special char at the end. +This is a line with a space at the end. +This is a line with multiple spaces at the end. +This is a line with a tab at the end. +This is a line with an umlaut at the end.ä +This is a line with an umlaut and a space at the end.ä +This is a line with an umlaut and a tab at the end.ä +From This is a line with From at the beginning. +.This is a line with a dot at the beginning. +-This is a line with a dash at the beginning. + +This is a very long line (ä ) which just happens to be wrapped so that a From appears at the beginning of the second line. Furthermore, this break. makes a dot appear as the first character on the third line. + +Just long enough: xxxxxxxx This is a line without a special char at the end. +Just too long: xxxxxxxxxxxx This is a line without a special char at the end. +xxxxxxxxxxxxxxxxxxxxxxxxxxxx This is a line without a special char at the end. +xxxxxxxxxxxxxxxxxxxxxxxxxxxxx This is a line without a special char at the end. + +Just long enough: xxxxxxxxxxxxxxx This is a line with a space at the end. +Just too long: xxxxxxxxxxxxxxxxxxx This is a line with a space at the end. +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx This is a line with a space at the end. +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx This is a line with a space at the end. + +Just long enough: xxxxxxxxxxxxxxxxx This is a line with a tab at the end. +Just too long: xxxxxxxxxxxxxxxxxxxxx This is a line with a tab at the end. +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx This is a line with a tab at the end. +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx This is a line with a tab at the end. + +Just long enough: xxxxxxxxxxxxx This is a line with an umlaut at the end.ä +Just too long: xxxxxxxxxxxxxxxxx This is a line with an umlaut at the end.ä +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx This is a line with an umlaut at the end.ä +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx This is a line with an umlaut at the end.ä + +Just long enough This is a line with an umlaut and a space at the end.ä +Just too long: xx This is a line with an umlaut and a space at the end.ä +xxxxxxxxxxxxxxxxxx This is a line with an umlaut and a space at the end.ä +xxxxxxxxxxxxxxxxxxx This is a line with an umlaut and a space at the end.ä + +Just long enough: This is a line with an umlaut and a tab at the end.ä +Just too long: xxxx This is a line with an umlaut and a tab at the end.ä +xxxxxxxxxxxxxxxxxxxx This is a line with an umlaut and a tab at the end.ä +xxxxxxxxxxxxxxxxxxxxx This is a line with an umlaut and a tab at the end.ä + +This line has a space at the end and ends the buffer
\ No newline at end of file diff --git a/libkmime/tests/data/codec_quoted-printable/wrap.expected b/libkmime/tests/data/codec_quoted-printable/wrap.expected new file mode 100644 index 000000000..b95c65523 --- /dev/null +++ b/libkmime/tests/data/codec_quoted-printable/wrap.expected @@ -0,0 +1,64 @@ +This is a line without a special char at the end. +This is a line with a space at the end.=20 +This is a line with multiple spaces at the end. =20 +This is a line with a tab at the end.=09 +This is a line with an umlaut at the end.=E4 +This is a line with an umlaut and a space at the end.=E4=20 +This is a line with an umlaut and a tab at the end.=E4=09 +=46rom This is a line with From at the beginning. +=2EThis is a line with a dot at the beginning. +=2DThis is a line with a dash at the beginning. + +This is a very long line (=E4 ) which just happens to be wrapped so that a = +=46rom appears at the beginning of the second line. Furthermore, this break= +=2E makes a dot appear as the first character on the third line. + +Just long enough: xxxxxxxx This is a line without a special char at the end. +Just too long: xxxxxxxxxxxx This is a line without a special char at the en= +d. +xxxxxxxxxxxxxxxxxxxxxxxxxxxx This is a line without a special char at the e= +nd. +xxxxxxxxxxxxxxxxxxxxxxxxxxxxx This is a line without a special char at the = +end. + +Just long enough: xxxxxxxxxxxxxxx This is a line with a space at the end.=20 +Just too long: xxxxxxxxxxxxxxxxxxx This is a line with a space at the end.= +=20 +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx This is a line with a space at the end.= +=20 +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx This is a line with a space at the end= +=2E=20 + +Just long enough: xxxxxxxxxxxxxxxxx This is a line with a tab at the end.=09 +Just too long: xxxxxxxxxxxxxxxxxxxxx This is a line with a tab at the end.= +=09 +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx This is a line with a tab at the end.= +=09 +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx This is a line with a tab at the end= +=2E=09 + +Just long enough: xxxxxxxxxxxxx This is a line with an umlaut at the end.=E4 +Just too long: xxxxxxxxxxxxxxxxx This is a line with an umlaut at the end.= +=E4 +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx This is a line with an umlaut at the end.= +=E4 +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx This is a line with an umlaut at the end= +=2E=E4 + +Just long enough This is a line with an umlaut and a space at the end.=E4=20 +Just too long: xx This is a line with an umlaut and a space at the end.=E4= +=20 +xxxxxxxxxxxxxxxxxx This is a line with an umlaut and a space at the end.=E4= +=20 +xxxxxxxxxxxxxxxxxxx This is a line with an umlaut and a space at the end.= +=E4=20 + +Just long enough: This is a line with an umlaut and a tab at the end.=E4=09 +Just too long: xxxx This is a line with an umlaut and a tab at the end.=E4= +=09 +xxxxxxxxxxxxxxxxxxxx This is a line with an umlaut and a tab at the end.=E4= +=09 +xxxxxxxxxxxxxxxxxxxxx This is a line with an umlaut and a tab at the end.= +=E4=09 + +This line has a space at the end and ends the buffer=20
\ No newline at end of file diff --git a/libkmime/tests/data/codec_x-kmime-rfc2231/all-encoded.x-kmime-rfc2231 b/libkmime/tests/data/codec_x-kmime-rfc2231/all-encoded.x-kmime-rfc2231 new file mode 100644 index 000000000..03bc48165 --- /dev/null +++ b/libkmime/tests/data/codec_x-kmime-rfc2231/all-encoded.x-kmime-rfc2231 @@ -0,0 +1 @@ +%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%30%31%32%33%34%35%36%37%38%39%3A%3B%3C%3D%3E%3F%40%41%42%43%44%45%46%47%48%49%4A%4B%4C%4D%4E%4F%50%51%52%53%54%55%56%57%58%59%5A%5B%5C%5D%5E%5F%60%61%62%63%64%65%66%67%68%69%6A%6B%6C%6D%6E%6F%70%71%72%73%74%75%76%77%78%79%7A%7B%7C%7D%7E%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF
\ No newline at end of file diff --git a/libkmime/tests/data/codec_x-kmime-rfc2231/all-encoded.x-kmime-rfc2231.expected b/libkmime/tests/data/codec_x-kmime-rfc2231/all-encoded.x-kmime-rfc2231.expected Binary files differnew file mode 100644 index 000000000..c86626638 --- /dev/null +++ b/libkmime/tests/data/codec_x-kmime-rfc2231/all-encoded.x-kmime-rfc2231.expected diff --git a/libkmime/tests/data/codec_x-kmime-rfc2231/basic-encode b/libkmime/tests/data/codec_x-kmime-rfc2231/basic-encode Binary files differnew file mode 100644 index 000000000..c86626638 --- /dev/null +++ b/libkmime/tests/data/codec_x-kmime-rfc2231/basic-encode diff --git a/libkmime/tests/data/codec_x-kmime-rfc2231/basic-encode.expected b/libkmime/tests/data/codec_x-kmime-rfc2231/basic-encode.expected new file mode 100644 index 000000000..61c504b7b --- /dev/null +++ b/libkmime/tests/data/codec_x-kmime-rfc2231/basic-encode.expected @@ -0,0 +1 @@ +%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20!%22%23%24%25%26%27%28%29%2A+%2C-%2E%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E%5F%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D%7E%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF
\ No newline at end of file diff --git a/libkmime/tests/data/codec_x-kmime-rfc2231/nothing-encoded.x-kmime-rfc2231 b/libkmime/tests/data/codec_x-kmime-rfc2231/nothing-encoded.x-kmime-rfc2231 Binary files differnew file mode 100644 index 000000000..c86626638 --- /dev/null +++ b/libkmime/tests/data/codec_x-kmime-rfc2231/nothing-encoded.x-kmime-rfc2231 diff --git a/libkmime/tests/data/codec_x-kmime-rfc2231/nothing-encoded.x-kmime-rfc2231.expected b/libkmime/tests/data/codec_x-kmime-rfc2231/nothing-encoded.x-kmime-rfc2231.expected new file mode 100644 index 000000000..8323fc635 --- /dev/null +++ b/libkmime/tests/data/codec_x-kmime-rfc2231/nothing-encoded.x-kmime-rfc2231.expected @@ -0,0 +1,2 @@ + + !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^ `abcdefghijklmnopqrstuvwxyz{|}~
\ No newline at end of file diff --git a/libkmime/tests/data/codec_x-kmime-rfc2231/null-decode.x-kmime-rfc2231 b/libkmime/tests/data/codec_x-kmime-rfc2231/null-decode.x-kmime-rfc2231 new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/libkmime/tests/data/codec_x-kmime-rfc2231/null-decode.x-kmime-rfc2231 diff --git a/libkmime/tests/data/codec_x-kmime-rfc2231/null-decode.x-kmime-rfc2231.expected b/libkmime/tests/data/codec_x-kmime-rfc2231/null-decode.x-kmime-rfc2231.expected new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/libkmime/tests/data/codec_x-kmime-rfc2231/null-decode.x-kmime-rfc2231.expected diff --git a/libkmime/tests/data/codec_x-kmime-rfc2231/null-encode b/libkmime/tests/data/codec_x-kmime-rfc2231/null-encode new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/libkmime/tests/data/codec_x-kmime-rfc2231/null-encode diff --git a/libkmime/tests/data/codec_x-kmime-rfc2231/null-encode.expected b/libkmime/tests/data/codec_x-kmime-rfc2231/null-encode.expected new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/libkmime/tests/data/codec_x-kmime-rfc2231/null-encode.expected diff --git a/libkmime/tests/data/codec_x-uuencode/basic-decode.x-uuencode b/libkmime/tests/data/codec_x-uuencode/basic-decode.x-uuencode new file mode 100644 index 000000000..365e61ddf --- /dev/null +++ b/libkmime/tests/data/codec_x-uuencode/basic-decode.x-uuencode @@ -0,0 +1,9 @@ +begin 664 foo +M``$"`P0%!@<("0H+#`T.#Q`1$A,4%187&!D:&QP='A\@(2(C)"4F)R@I*BLL +M+2XO,#$R,S0U-C<X.3H[/#T^/T!!0D-$149'2$E*2TQ-3D]045)35%565UA9 +M6EM<75Y?8&%B8V1E9F=H:6IK;&UN;W!Q<G-T=79W>'EZ>WQ]?G^`@8*#A(6& +MAXB)BHN,C8Z/D)&2DY25EI>8F9J;G)V>GZ"AHJ.DI::GJ*FJJZRMKJ^PL;*S +MM+6VM[BYNKN\O;Z_P,'"P\3%QL?(R<K+S,W.S]#1TM/4U=;7V-G:V]S=WM_@ +?X>+CY.7FY^CIZNOL[>[O\/'R\_3U]O?X^?K[_/W^_P`` +` +end diff --git a/libkmime/tests/data/codec_x-uuencode/basic-decode.x-uuencode.expected b/libkmime/tests/data/codec_x-uuencode/basic-decode.x-uuencode.expected Binary files differnew file mode 100644 index 000000000..c86626638 --- /dev/null +++ b/libkmime/tests/data/codec_x-uuencode/basic-decode.x-uuencode.expected diff --git a/libkmime/tests/gen_decode_map.pl b/libkmime/tests/gen_decode_map.pl new file mode 100755 index 000000000..131aef1e7 --- /dev/null +++ b/libkmime/tests/gen_decode_map.pl @@ -0,0 +1,17 @@ +#!/usr/bin/perl -w + +use strict; + +my @encodingMap = ( 'A' .. 'Z', 'a' .. 'z', '0' .. '9', '+', '/' ); +my @decodingMap = (64) x 128; + +my $len = scalar @encodingMap; +for ( my $i = 0 ; $i < $len ; $i++ ) { + my $value = ord $encodingMap[$i]; + $decodingMap[$value] = $i; +} + +for ( my $i = 0 ; $i < 128 ; $i += 16 ) { + print " ", join( ", ", @decodingMap[$i..($i+7)] ), ", ", + join( ", ", @decodingMap[($i+8)..($i+15)] ), ",\n"; +} diff --git a/libkmime/tests/run_codec_tests b/libkmime/tests/run_codec_tests new file mode 100755 index 000000000..62908eeec --- /dev/null +++ b/libkmime/tests/run_codec_tests @@ -0,0 +1,93 @@ +#!/bin/bash + +TEST="./test_kmime_codec" +#BUFFER_SIZES="$(seq 7)" +#BUFFER_SIZES="$(seq 5) 15 16 17 23 24 25 31 32 33 4096" +BUFFER_SIZES="$(seq 17) 23 24 25 31 32 33 4096" +USAGE_PATTERNS="chunkwise kio" + +oldDirName="" +encoding="" +action="" +inFile="" +resultFile="" + +totalTests=0 +passedTests=0 +failedTests=0 + +# loop over all .expected files in the directory +# specified by the single argument: + +for i in $(find "$@" -type f -name '*.expected'); do + dirName="$(dirname "$i")" + if [ "$dirName" != "$oldDirName" ]; then + oldDirName="$dirName" + # new directory: extract encoding + encoding="$(basename "$dirName")" + encoding="${encoding#codec_}" + echo Entering directory \"$dirName\". + echo Using encoding \"$encoding\". + fi + + testName="$(basename "$i")" + testName="${testName%.expected}" + echo "testName=$testName" + inFile="$dirName/$testName" + echo "inFile=$inFile" + if [ "${testName%.$encoding}" != "$testName" ]; then + testName="${testName%.$encoding}" + action="--decode" + else + #testName stays the same + action="--encode" + fi + resultFile="$dirName/$testName.result" + echo "resultFile=$resultFile" + echo performing ${action#--} test \"$testName\" + for usagePattern in $(echo $USAGE_PATTERNS); do + for insize in -1 $(echo $BUFFER_SIZES); do + for outsize in $(echo $BUFFER_SIZES); do + let "totalTests++" + if [ "$insize" == "-1" ]; then + #omit --input-buffer-size + $TEST $action $encoding \ + --usage-pattern $usagePattern \ + --output-buffer-size $outsize \ + --outfile "$resultFile" \ + "$inFile" \ + > /dev/null 2> /dev/null + else + $TEST $action $encoding \ + --usage-pattern $usagePattern \ + --input-buffer-size $insize \ + --output-buffer-size $outsize \ + --outfile "$resultFile" \ + "$inFile" \ + > /dev/null 2> /dev/null + fi + result=$? + case $result in + 0) + if cmp "$i" "$resultFile" >/dev/null 2>/dev/null ; then + let "passedTests++" + else + let "failedTests++" + mv "$resultFile" "$resultfile.failed.$usagePattern.$insize-$outsize" + fi + ;; + *) + let "failedTests++" + mv "$resultFile" "$resultFile.failed.$usagePattern.$insize-$outsize-$result" || touch "$resultFile.failed.$insize-$outsize-$result" + ;; + esac + rm -rf "$resultFile" + done + done + done + if [ $totalTests != $passedTests ]; then + echo "some tests failed." + fi +done + +echo "TOTAL: $totalTests; PASSED: $passedTests; FAILED: $failedTests;" diff --git a/libkmime/tests/test_charfreq.cpp b/libkmime/tests/test_charfreq.cpp new file mode 100644 index 000000000..c5b708819 --- /dev/null +++ b/libkmime/tests/test_charfreq.cpp @@ -0,0 +1,41 @@ +// test program for KMime::CharFreq. +// compile with g++ -I$QTDIR/include -L$QTDIR/lib -lqt(-mt) \ +// -o test_charfreq test_charfreq.cpp + +#include "../kmime_charfreq.cpp" + +#include <iostream> + +#include <qfile.h> + +using namespace std; +using namespace KMime; + +static const char * typeToString( int type ) { + switch ( type ) { + case CharFreq::EightBitData: + return "eight bit data (binary)"; + case CharFreq::EightBitText: + return "eight bit text"; + case CharFreq::SevenBitData: + return "seven bit data"; + case CharFreq::SevenBitText: + return "seven bit text"; + default: + return "unknown type"; + } +} + +int main( int argc, char **argv ) { + for ( int i = 1 /*not program*/ ; i < argc ; i++ ) { + QFile in( argv[i] ); + if ( !in.open( IO_ReadOnly ) ) { + cerr << argv[i] << ": does not exist!" << endl; + continue; + } + QByteArray ba = in.readAll(); + CharFreq cf( ba ); + cout << argv[i] << ": " << typeToString(cf.type()) << endl; + } + return 0; +} diff --git a/libkmime/tests/test_dates.cpp b/libkmime/tests/test_dates.cpp new file mode 100644 index 000000000..f70269282 --- /dev/null +++ b/libkmime/tests/test_dates.cpp @@ -0,0 +1,93 @@ +#include <kmime_util.h> +#include <kmime_header_parsing.h> +#include <kdebug.h> +#include <kinstance.h> +using namespace KMime; + + +int +main() +{ + KInstance app("# "); + DateFormatter t; + + time_t ntime = time(0); + kdDebug()<<"Time now:"<<endl; + kdDebug()<<"\tFancy : \t"<<t.dateString(ntime)<<endl; + t.setFormat(DateFormatter::Localized); + kdDebug()<<"\tLocalized : \t"<<t.dateString(ntime)<<endl; + t.setFormat(DateFormatter::CTime); + kdDebug()<<"\tCTime : \t"<<t.dateString(ntime)<<endl; + t.setFormat(DateFormatter::Iso); + kdDebug()<<"\tIso : \t"<<t.dateString(ntime)<<endl; + kdDebug()<<"\trfc2822 : \t"<<t.rfc2822(ntime)<<endl; + QString rfcd = t.rfc2822(ntime); + Types::DateTime dt; + QDateTime qdt; + const char *str = rfcd.latin1(); + if ( HeaderParsing::parseDateTime( str, str + rfcd.length(), dt ) ) { + kdDebug()<<"@@@ ntime = "<<(ntime)<<", dt = "<<(dt.time)<<endl; + qdt.setTime_t( dt.time ); + kdDebug()<<"@@@ qq = "<< qdt.toString("ddd, dd MMM yyyy hh:mm:ss") <<endl; + kdDebug()<<"@@@ rfc2822 : "<<t.rfc2822(dt.time)<<endl; + } + QString ddd = "Mon, 05 Aug 2002 01:57:51 -0700"; + str = ddd.latin1(); + if ( HeaderParsing::parseDateTime( str, str + ddd.length(), dt ) ) { + kdDebug()<<"dt = "<<(dt.time)<<endl; + kdDebug()<<"@@@ rfc2822 : "<<t.rfc2822(dt.time)<<endl; + } + + t.setCustomFormat("MMMM dddd yyyy Z"); + kdDebug()<<"\tCustom : \t"<<t.dateString(ntime)<<endl; + + ntime -= (24 * 3600 + 1); + kdDebug()<<"Time 24 hours and 1 second ago:"<<endl; + t.setFormat( DateFormatter::Fancy ); + kdDebug()<<"\tFancy : \t"<<t.dateString(ntime)<<endl; + t.setFormat(DateFormatter::Localized); + kdDebug()<<"\tLocalized : \t"<<t.dateString(ntime)<<endl; + t.setFormat(DateFormatter::CTime); + kdDebug()<<"\tCTime : \t"<<t.dateString(ntime)<<endl; + t.setFormat(DateFormatter::Iso); + kdDebug()<<"\tIso : \t"<<t.dateString(ntime)<<endl; + kdDebug()<<"\trfc2822 : \t"<<t.rfc2822(ntime)<<endl; + t.setCustomFormat("MMMM dddd Z yyyy"); + kdDebug()<<"\tCustom : \t"<<t.dateString(ntime)<<endl; + + t.setFormat(DateFormatter::Fancy); + ntime -= (24*3600 *30 + 59); + kdDebug()<<"Time 31 days and 1 minute ago:"<<endl; + kdDebug()<<"\tFancy : \t"<<t.dateString(ntime)<<endl; + t.setFormat(DateFormatter::Localized); + kdDebug()<<"\tLocalized : \t"<<t.dateString(ntime)<<endl; + t.setFormat(DateFormatter::CTime); + kdDebug()<<"\tCTime : \t"<<t.dateString(ntime)<<endl; + t.setFormat(DateFormatter::Iso); + kdDebug()<<"\tIso : \t"<<t.dateString(ntime)<<endl; + kdDebug()<<"\trfc2822 : \t"<<t.rfc2822(ntime)<<endl; + t.setCustomFormat("MMMM Z dddd yyyy"); + kdDebug()<<"\tCustom : \t"<<t.dateString(ntime)<<endl; + + + kdDebug()<<"Static functions (dates like in the last test):"<<endl; + kdDebug()<<"\tFancy : \t"<< DateFormatter::formatDate( DateFormatter::Fancy, ntime) <<endl; + kdDebug()<<"\tLocalized : \t"<< DateFormatter::formatDate( DateFormatter::Localized, ntime) <<endl; + kdDebug()<<"\tCTime : \t"<< DateFormatter::formatDate( DateFormatter::CTime, ntime ) <<endl; + kdDebug()<<"\tIso : \t"<< DateFormatter::formatDate( DateFormatter::Iso, ntime ) <<endl; + kdDebug()<<"\trfc2822 : \t"<< DateFormatter::rfc2822FormatDate( ntime ) <<endl; + kdDebug()<<"\tCustom : \t"<< DateFormatter::formatDate( DateFormatter::Custom, ntime, + "Z MMMM dddd yyyy") <<endl; + t.setFormat(DateFormatter::Fancy); + kdDebug()<<"QDateTime taking: (dates as in first test)"<<endl; + kdDebug()<<"\tFancy : \t"<<t.dateString((QDateTime::currentDateTime()))<<endl; + t.setFormat(DateFormatter::Localized); + kdDebug()<<"\tLocalized : \t"<<t.dateString(QDateTime::currentDateTime())<<endl; + t.setFormat(DateFormatter::CTime); + kdDebug()<<"\tCTime : \t"<<t.dateString(QDateTime::currentDateTime())<<endl; + t.setFormat(DateFormatter::Iso); + kdDebug()<<"\tIso : \t"<<t.dateString(QDateTime::currentDateTime())<<endl; + t.setCustomFormat("MMMM d dddd yyyy Z"); + kdDebug()<<"\tCustom : \t"<<t.dateString(QDateTime::currentDateTime())<<endl; + +} diff --git a/libkmime/tests/test_kmime_codec.cpp b/libkmime/tests/test_kmime_codec.cpp new file mode 100644 index 000000000..f13b2b434 --- /dev/null +++ b/libkmime/tests/test_kmime_codec.cpp @@ -0,0 +1,449 @@ +/* test program for KMime::Codec's: + compile with: + g++ -I$QTDIR/include -I$KDEDIR/include -L$QTDIR/lib -L$KDEDIR/lib \ + -lqt-mt -lkdecore -lkdenetwork -O2 -pthread -DQT_THREAD_SUPPORT \ + -o test_kmime_codec{,.cpp} +*/ + +// return codes: +#define USAGE_DISPLAYED 1 +#define UNKNOWN_CODEC 2 +#define INFILE_READ_ERR 3 +#define OUTFILE_WRITE_ERR 4 + +#include <../kmime_codecs.h> + +#include <kdebug.h> + +#include <cstdlib> +#include <iostream> + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include <getopt.h> +#include <cassert> + +#include <qfile.h> +#include <qcstring.h> // QByteArray + +using namespace KMime; +using namespace std; + +static struct option long_options[] = { + { "encode", 1, 0, 0 }, + { "decode", 1, 0, 0 }, + { "output-buffer-size", 1, 0, 0 }, + { "input-buffer-size", 1, 0, 0 }, + { "outfile", 1, 0, 0 }, + { "with-crlf", 0, 0, 0 }, + { "iterations", 1, 0, 0 }, + { "without-finish", 0, 0, 0 }, + { "verbose", 0, 0, 0 }, + { "usage-pattern", 1, 0, 0 }, + { 0, 0, 0, 0 } +}; + +void usage( const char * msg=0 ) { + if ( msg && *msg ) + cerr << msg << endl; + cerr << "usage: test_kmime_codec (--encode|--decode) " + "<encoding-name> [options] infile\n" + "where options include:\n\n" + " --outfile <outfile> write output into file <outfile>\n" + " --output-buffer-size <size> en/decode into chunks of <size> bytes\n" + " default: 4096\n" + " --input-buffer-size <size> en/decode from chunks of <size> bytes\n" + " default: slurp in whole file\n" + " --with-crlf use CRLF instead of LF in output\n" + " --iterations <number> do more than one iteration\n" + " default: 1\n" + " --usage-pattern { kio | chunkwise | convenience-qba }\n" + " use a certain usage pattern to be tested\n" + " (default: chunkwise)\n" + " --without-finish don't call the finish() method\n" + " --verbose output detailed progress information\n" + << endl; + exit(USAGE_DISPLAYED); +} + +void missingParameterTo( const char * option ) { + cerr << "Missing or malformed parameter to " << option << endl; + usage(); +} + +static enum { Kio = 0, ChunkWise = 1, ConvenienceQBA = 3 } +pattern = ChunkWise; +static int outbufsize = 4096; +static int inbufsize = -1; // whole file +static bool writing = false; +static bool withCRLF = false; +static bool withFinish = true; +static bool verbose = false; + +void encode_decode_kio( bool, const Codec *, const QByteArray &, QFile & ); +void encode_decode_chunkwise( bool, const Codec *, + const QByteArray &, QFile & ); +void encode_decode_convenience_qba( bool, const Codec *, const QByteArray &, + QFile & ); + +int main( int argc, char * argv[] ) { + + int iterations = 1; + bool encode = false; + bool decode = false; + QCString outfilename, infilename; + QCString encodingName; + + // options parsing: + while( 1 ) { + int option_index = 0; + if ( getopt_long( argc, argv, "", long_options, &option_index ) ) + break; + switch ( option_index ) { + case 0: // encode + if ( !optarg || !*optarg ) missingParameterTo( "--encode." ); + encode = true; + encodingName = QCString(optarg); + break; + case 1: // decode + if ( !optarg || !*optarg ) missingParameterTo( "--decode" ); + decode = true; + encodingName = QCString(optarg); + break; + case 2: // output-buffer-size + if ( !optarg || (outbufsize = atoi( optarg )) < 1 ) + missingParameterTo( "--output-buffer-size" ); + break; + case 3: // input-buffer-size + if ( !optarg || (inbufsize = atoi( optarg )) < 1 ) + missingParameterTo( "--input-buffer-size" ); + break; + case 4: // outfile + if ( !optarg || !*optarg ) missingParameterTo( "--outfile" ); + outfilename = QCString(optarg); + writing = true; + break; + case 5: // with-crlf + withCRLF = true; + break; + case 6: // iterations + if ( !optarg || (iterations = atoi( optarg )) < 1 ) + missingParameterTo( "--iterations" ); + break; + case 7: // without-finish + withFinish = false; + break; + case 8: // verbose + verbose = true; + break; + case 9: // usage-pattern + if ( !qstricmp( "kio", optarg ) ) + pattern = Kio; + else if ( !qstricmp( "chunkwise", optarg ) ) + pattern = ChunkWise; + else if ( !qstricmp( "convenience-qba", optarg ) ) + pattern = ConvenienceQBA; + else { + cerr << "Unknown usage pattern \"" << optarg << "\"" << endl; + usage(); + } + break; + default: usage( "Unknown option" ); + } + } + + if ( !decode && !encode ) + usage( "You must specify exactly one of --encode, --decode." ); + if ( decode && encode ) + usage( "You must specify exactly one of --encode, --decode."); + + if ( verbose ) { + if ( encode ) + kdDebug() << "encoding as " << encodingName << endl; + else if ( decode ) + kdDebug() << "decoding " << encodingName << endl; + } + + if ( optind != argc - 1 ) usage(); + + QFile infile( argv[ optind ] ); + if (!infile.exists()) { + kdDebug() << "infile \"" << infile.name() << "\" does not exist!" << endl; + return INFILE_READ_ERR; + } + if (!infile.open( IO_ReadOnly )) { + kdDebug() << "cannot open " << infile.name() << " for reading!" + << endl; + return INFILE_READ_ERR; + } + + QFile outfile( outfilename ); + if ( !outfilename.isEmpty() ) { + if (!outfile.open( IO_WriteOnly|IO_Truncate )) { + kdDebug() << "cannot open " << outfile.name() << " for writing!" + << endl; + return OUTFILE_WRITE_ERR; + } + } + + if ( verbose ) { + kdDebug() << "using output buffer size of " << outbufsize << endl; + kdDebug() << "using input buffer size of " << inbufsize << endl; + } + if ( !withFinish ) + kdWarning() << "omitting finish calls. Results may be truncated!" << endl; + + if ( inbufsize <= 0 ) + inbufsize = infile.size(); + + // get a codec. Don't delete it later!! + kdDebug( verbose ) << "obtaining codec for \"" + << encodingName << "\"" << endl; + Codec * codec = Codec::codecForName( encodingName ); + if ( !codec ) { + kdDebug() << "unknown codec \"" << encodingName << "\"" << endl; + return UNKNOWN_CODEC; + } + + QByteArray infile_buffer = infile.readAll(); + + for ( int i = 0 ; i < iterations ; ++i ) { + kdDebug( verbose ) << "starting iteration " << i+1 + << " of " << iterations << endl; + switch ( pattern ) { + case ChunkWise: + encode_decode_chunkwise( encode, codec, infile_buffer, outfile ); + break; + case Kio: + encode_decode_kio( encode, codec, infile_buffer, outfile ); + break; + case ConvenienceQBA: + encode_decode_convenience_qba( encode, codec, infile_buffer, outfile ); + break; + default: + usage(); + } + } + + return 0; +} + +void encode_decode_convenience_qba( bool encode, const Codec * codec, + const QByteArray & infile_buffer, + QFile & outfile ) +{ + QByteArray out; + if ( encode ) + out = codec->encode( infile_buffer, withCRLF ); + else + out = codec->decode( infile_buffer, withCRLF ); + if ( writing ) { + Q_LONG written = outfile.writeBlock( out ); + assert( written == (Q_LONG)out.size() ); + } +} + +void encode_kio_internal( Encoder * enc, QByteArray::ConstIterator & iit, + QByteArray::ConstIterator & iend, + QByteArray & out ) +{ + out.resize( outbufsize ); + QByteArray::Iterator oit = out.begin(); + QByteArray::ConstIterator oend = out.end(); + + while ( !enc->encode( iit, iend, oit, oend ) ) + if ( oit == oend ) return; + + while ( !enc->finish( oit, oend ) ) + if ( oit == oend ) return; + + out.truncate( oit - out.begin() ); +} + +void decode_kio_internal( Decoder * dec, QByteArray::ConstIterator & iit, + QByteArray::ConstIterator & iend, + QByteArray & out ) { + out.resize( outbufsize ); + QByteArray::Iterator oit = out.begin(); + QByteArray::ConstIterator oend = out.end(); + + while ( !dec->decode( iit, iend, oit, oend ) ) + if ( oit == oend ) return; + + while ( !dec->finish( oit, oend ) ) + if ( oit == oend ) return; + + out.truncate( oit - out.begin() ); +} + +void encode_decode_kio( bool encode, const Codec * codec, + const QByteArray & infile_buffer, QFile & outfile ) +{ + + Encoder * enc = 0; + Decoder * dec = 0; + + // Get an encoder. This one you have to delete! + if ( encode ) { + enc = codec->makeEncoder( withCRLF ); + assert( enc ); + } else { + dec = codec->makeDecoder( withCRLF ); + assert( dec ); + } + + QByteArray::ConstIterator iit = infile_buffer.begin(); + QByteArray::ConstIterator iend = infile_buffer.end(); + + QByteArray out; + do { + out = QByteArray(); + if ( encode ) + encode_kio_internal( enc, iit, iend, out ); + else + decode_kio_internal( dec, iit, iend, out ); + if ( writing && out.size() ) { + Q_LONG written = outfile.writeBlock( out ); + assert( written == (Q_LONG)out.size() ); + } + } while ( out.size() ); + + if ( encode ) + delete enc; + else + delete dec; +} + +void encode_decode_chunkwise( bool encode, const Codec * codec, + const QByteArray & infile_buffer, QFile & outfile ) +{ + Encoder * enc = 0; + Decoder * dec = 0; + + + QByteArray indata( inbufsize ); + QByteArray outdata( outbufsize ); + + // we're going to need this below: +#define write_full_outdata_then_reset do { \ + kdDebug( verbose ) << " flushing output buffer." << endl; \ + if ( writing ) { \ + Q_LONG outlen = outfile.writeBlock( outdata.data(), \ + outdata.size() ); \ + if ( outlen != (int)outdata.size() ) \ + exit(OUTFILE_WRITE_ERR); \ + } \ + oit = outdata.begin(); \ + } while ( false ) + +#define report_status(x,y) do { \ + kdDebug( verbose ) << " " #x "() returned " #y " after processing " \ + << iit - indata.begin() << " bytes of input.\n" \ + << " output iterator now at position " \ + << oit - outdata.begin() << " of " \ + << outdata.size() << endl; \ + } while ( false ) + +#define report_finish_status(y) do { \ + kdDebug( verbose ) << " finish() returned " #y "\n" \ + << " output iterator now at position " \ + << oit - outdata.begin() << " of " \ + << outdata.size() << endl; \ + } while ( false ) + + + // Initialize the output iterators: + QByteArray::Iterator oit = outdata.begin(); + QByteArray::Iterator oend = outdata.end(); + + // Get an encoder. This one you have to delete! + if ( encode ) { + enc = codec->makeEncoder( withCRLF ); + assert( enc ); + } else { + dec = codec->makeDecoder( withCRLF ); + assert( dec ); + } + + // + // Loop over input chunks: + // + uint offset = 0; + while ( offset < infile_buffer.size() ) { + uint reallyRead = QMIN( indata.size(), infile_buffer.size() - offset ); + indata.duplicate( infile_buffer.begin() + offset, reallyRead ); + offset += reallyRead; + + kdDebug( verbose ) << " read " << reallyRead << " bytes (max: " + << indata.size() << ") from input." << endl; + + // setup input iterators: + QByteArray::ConstIterator iit = indata.begin(); + QByteArray::ConstIterator iend = indata.begin() + reallyRead; + + if ( encode ) { + // + // Loop over encode() calls: + // + while ( !enc->encode( iit, iend, oit, oend ) ) { + report_status( encode, false ); + if ( oit == oend ) + // output buffer full: + write_full_outdata_then_reset; + } + report_status( encode, true ); + } else { + // + // Loop over decode() calls: + // + while ( !dec->decode( iit, iend, oit, oend ) ) { + report_status( decode, false ); + if ( oit == oend ) + // output buffer full: + write_full_outdata_then_reset; + } + report_status( decode, true ); + } + } // end loop over input chunks + + // + // Now finish the encoding/decoding: + // (same loops as above, just s/encode|decode/finish()) + // + if ( withFinish ) + if ( encode ) { + while ( !enc->finish( oit, oend ) ) { + report_finish_status( false ); + if ( oit == oend ) + write_full_outdata_then_reset; + } + report_finish_status( true ); + } else { + while ( !dec->finish( oit, oend ) ) { + report_finish_status( false ); + if ( oit == oend ) + write_full_outdata_then_reset; + } + report_finish_status( true ); + } + + // + // Write out last (partial) output chunk: + // + if ( writing ) { + Q_LONG outlen = outfile.writeBlock( outdata.data(), + oit - outdata.begin() ); + if ( outlen != oit - outdata.begin() ) + exit(OUTFILE_WRITE_ERR); + } + + // + // Delete en/decoder: + // + if ( encode ) + delete enc; + else + delete dec; +} + diff --git a/libkmime/tests/test_kmime_header_parsing.cpp b/libkmime/tests/test_kmime_header_parsing.cpp new file mode 100644 index 000000000..3839bff7e --- /dev/null +++ b/libkmime/tests/test_kmime_header_parsing.cpp @@ -0,0 +1,431 @@ + +#include <../kmime_headers.h> +#include <../kmime_header_parsing.h> + +#include <kinstance.h> + +#include <qfile.h> +#include <qcstring.h> +//#include <qstring.h> + +//#include <stdio.h> +#include <iostream> +#include <cstdlib> +#include <cassert> + +#define _GNU_SOURCE +#include <getopt.h> + +using namespace KMime::HeaderParsing; +using namespace std; + +static const char * tokenTypes[] = { + "encoded-word", + "atom", + "token", + "quoted-string", + "domain-literal", + "comment", + "phrase", + "dot-atom", + "domain", + "obs-route", + "addr-spec", + "angle-addr", + "mailbox", + "group", + "address", + "address-list", + "parameter", + "raw-parameter-list", + "parameter-list", + "time", + "date-time" +}; +static const int tokenTypesLen = sizeof tokenTypes / sizeof *tokenTypes; + +void usage( const char * msg=0 ) { + if ( msg && *msg ) + cerr << msg << endl; + cerr << + "usage: test_kmime_header_parsing " + "(--token <tokentype>|--headerfield <fieldtype>|--header)\n" + "\n" + " --token <tokentype> interpret input as <tokentype> and output\n" + " (-t) in parsed form. Currently defined values of\n" + " <tokentype> are:" << endl; + for ( int i = 0 ; i < tokenTypesLen ; ++i ) + cerr << " " << tokenTypes[i] << endl; + cerr << "\n" + " --headerfield <fieldtype> interpret input as header field <fieldtype>\n" + " (-f) and output in parsed form.\n" + "\n" + " --header parse an RFC2822 header. Iterates over all\n" + " (-h) header fields and outputs them in parsed form." << endl; + exit(1); +} + +ostream & operator<<( ostream & stream, const QString & str ) { + return stream << str.utf8().data(); +} + +int main( int argc, char * argv[] ) { + if ( argc == 1 || argc > 3 ) usage(); + // + // process options: + // + enum { None, Token, HeaderField, Header } action = None; + const char * argument = 0; + bool withCRLF = false; + while( true ) { + int option_index = 0; + static const struct option long_options[] = { + // actions: + { "token", 1, 0, 't' }, + { "headerfield", 1, 0, 'f' }, + { "header", 0, 0, 'h' }, + { "crlf", 0, 0, 'c' }, + { 0, 0, 0, 0 } + }; + + int c = getopt_long( argc, argv, "cf:ht:", long_options, &option_index ); + if ( c == -1 ) break; + + switch ( c ) { + case 'c': // --crlf + withCRLF = true; + break; + case 't': // --token <tokentype> + action = Token; + argument = optarg; + break; + case 'f': // --headerfield <headertype> + usage( "--headerfield is not yet implemented!" ); + break; + case 'h': // --header + usage( "--header is not yet implemented!" ); + break; + default: + usage( "unknown option encountered!" ); + } + } + + if ( optind < argc ) usage( "non-option argument encountered!" ); + + assert( action == Token ); + + int index; + for ( index = 0 ; index < tokenTypesLen ; ++index ) + if ( !qstricmp( tokenTypes[index], argument ) ) break; + + if ( index >= tokenTypesLen ) usage( "unknown token type" ); + + KInstance instance( "test_kmime_header_parsing" ); + + QFile stdIn; + stdIn.open( IO_ReadOnly, stdin ); + const QByteArray indata = stdIn.readAll(); + stdIn.close(); + QByteArray::ConstIterator iit = indata.begin(); + const QByteArray::ConstIterator iend = indata.end(); + + switch ( index ) { + case 0: + { // encoded-word + QString result; + QCString language; + // must have checked for initial '=' already: + bool ok = indata.size() >= 1 && *iit++ == '=' && + parseEncodedWord( iit, iend, result, language ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result:\n" << result << endl + << "language:\n" << language.data() << endl; + } + break; + case 1: + { // atom + QString result = "with 8bit: "; + bool ok = parseAtom( iit, iend, result, true ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result:\n" << result << endl; + + result = "without 8bit: "; +#ifdef COMPILE_FAIL + ok = parseAtom( indata.begin(), iend, result, false ); +#else + iit = indata.begin(); + ok = parseAtom( iit, iend, result, false ); +#endif + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result:\n" << result << endl; + } + break; + case 2: + { // token + QString result = "with 8bit: "; + bool ok = parseToken( iit, iend, result, true ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result:\n" << result << endl; + + result = "without 8bit: "; +#ifdef COMPILE_FAIL + ok = parseToken( indata.begin(), iend, result, false ); +#else + iit = indata.begin(); + ok = parseToken( iit, iend, result, false ); +#endif + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result:\n" << result << endl; + } + break; + case 3: + { // quoted-string + QString result; + // must have checked for initial '"' already: + bool ok = *iit++ == '"' && + parseGenericQuotedString( iit, iend, result, withCRLF, '"', '"' ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result:\n" << result << endl; + } + break; + case 4: + { // domain-literal + QString result; + // must have checked for initial '[' already: + bool ok = *iit++ == '[' && + parseGenericQuotedString( iit, iend, result, withCRLF, '[', ']' ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result:\n" << result << endl; + } + break; + case 5: + { // comment + QString result; + // must have checked for initial '(' already: + bool ok = *iit++ == '(' && + parseComment( iit, iend, result, withCRLF, true ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result:\n" << result << endl; + } + break; + case 6: + { // phrase + QString result; + bool ok = parsePhrase( iit, iend, result, withCRLF ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result:\n" << result << endl; + } + break; + case 7: + { // dot-atom + QString result; + bool ok = parseDotAtom( iit, iend, result, withCRLF ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result:\n" << result << endl; + } + break; + case 8: + { // domain + QString result; + bool ok = parseDomain( iit, iend, result, withCRLF ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result:\n" << result << endl; + } + break; + case 9: + { // obs-route + QStringList result; + bool ok = parseObsRoute( iit, iend, result, withCRLF, true /*save*/ ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result: " << result.count() << " domains:" << endl; + for ( QStringList::ConstIterator it = result.begin() ; + it != result.end() ; ++it ) + cout << (*it) << endl; + } + break; + case 10: + { // addr-spec + KMime::Types::AddrSpec result; + bool ok = parseAddrSpec( iit, iend, result, withCRLF ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result.localPart:\n" << result.localPart << endl + << "result.domain:\n" << result.domain << endl; + } + break; + case 11: + { // angle-addr + KMime::Types::AddrSpec result; + bool ok = parseAngleAddr( iit, iend, result, withCRLF ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result.localPart:\n" << result.localPart << endl + << "result.domain:\n" << result.domain << endl; + } + break; + case 12: + { // mailbox + KMime::Types::Mailbox result; + bool ok = parseMailbox( iit, iend, result, withCRLF ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result.displayName:\n" << result.displayName << endl + << "result.addrSpec.localPart:\n" << result.addrSpec.localPart << endl + << "result.addrSpec.domain:\n" << result.addrSpec.domain << endl; + } + break; + case 13: + { // group + KMime::Types::Address result; + bool ok = parseGroup( iit, iend, result, withCRLF ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result.displayName:\n" << result.displayName << endl; + int i = 0; + for ( QValueList<KMime::Types::Mailbox>::ConstIterator + it = result.mailboxList.begin(); + it != result.mailboxList.end() ; ++it, ++i ) + cout << "result.mailboxList[" << i << "].displayName:\n" + << (*it).displayName << endl + << "result.mailboxList[" << i << "].addrSpec.localPart:\n" + << (*it).addrSpec.localPart << endl + << "result.mailboxList[" << i << "].addrSpec.domain:\n" + << (*it).addrSpec.domain << endl; + } + break; + case 14: + { // address + KMime::Types::Address result; + bool ok = parseAddress( iit, iend, result, withCRLF ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result.displayName:\n" << endl; + int i = 0; + for ( QValueList<KMime::Types::Mailbox>::ConstIterator + it = result.mailboxList.begin(); + it != result.mailboxList.end() ; ++it, ++i ) + cout << "result.mailboxList[" << i << "].displayName:\n" + << (*it).displayName << endl + << "result.mailboxList[" << i << "].addrSpec.localPart:\n" + << (*it).addrSpec.localPart << endl + << "result.mailboxList[" << i << "].addrSpec.domain:\n" + << (*it).addrSpec.domain << endl; + } + break; + case 15: + { // address-list + QValueList<KMime::Types::Address> result; + bool ok = parseAddressList( iit, iend, result, withCRLF ); + + cout << ( ok ? "OK" : "BAD" ) << endl; + int j = 0; + for ( QValueList<KMime::Types::Address>::ConstIterator + jt = result.begin() ; jt != result.end() ; ++jt, ++j ) { + cout << "result[" << j << "].displayName:\n" + << (*jt).displayName << endl; + int i = 0; + for ( QValueList<KMime::Types::Mailbox>::ConstIterator + it = (*jt).mailboxList.begin(); + it != (*jt).mailboxList.end() ; ++it, ++i ) + cout << "result[" << j << "].mailboxList[" << i << "].displayName:\n" + << (*it).displayName << endl + << "result[" << j << "].mailboxList[" << i << "].addrSpec.localPart:\n" + << (*it).addrSpec.localPart << endl + << "result[" << j << "].mailboxList[" << i << "].addrSpec.domain:\n" + << (*it).addrSpec.domain << endl; + } + } + break; + case 16: + { // parameter + QPair<QString,KMime::Types::QStringOrQPair> result; + bool ok = parseParameter( iit, iend, result, withCRLF ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result.first (attribute):\n" << result.first << endl + << "result.second.qstring (value):\n" << result.second.qstring << endl + << "result.second.qpair (value):\n" + << QCString( result.second.qpair.first, + result.second.qpair.second+1 ).data() << endl; + } + break; + case 17: + { // raw-parameter-list + QMap<QString,KMime::Types::QStringOrQPair> result; + bool ok = parseRawParameterList( iit, iend, result, withCRLF ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result: " << result.count() << " raw parameters:" << endl; + int i = 0; + for ( QMap<QString,KMime::Types::QStringOrQPair>::ConstIterator + it = result.begin() ; it != result.end() ; ++it, ++i ) + cout << "result[" << i << "].key() (attribute):\n" + << it.key() << endl + << "result[" << i << "].data().qstring (value):\n" + << it.data().qstring << endl + << "result[" << i << "].data().qpair (value):\n" + << QCString( it.data().qpair.first, + it.data().qpair.second+1 ).data() << endl; + } + break; + case 18: + { // parameter-list + QMap<QString,QString> result; + bool ok = parseParameterList( iit, iend, result, withCRLF ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result: " << result.count() << " parameters:" << endl; + int i = 0; + for ( QMap<QString,QString>::Iterator it = result.begin() ; + it != result.end() ; ++it, ++i ) + cout << "result[" << i << "].key() (attribute):\n" + << it.key() << endl + << "result[" << i << "].data() (value):\n" + << it.data() << endl; + } + break; + case 19: + { // time + int hour, mins, secs; + long int secsEastOfGMT; + bool timeZoneKnown = true; + + bool ok = parseTime( iit, iend, hour, mins, secs, + secsEastOfGMT, timeZoneKnown, withCRLF ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result.hour: " << hour << endl + << "result.mins: " << mins << endl + << "result.secs: " << secs << endl + << "result.secsEastOfGMT: " << secsEastOfGMT << endl + << "result.timeZoneKnown: " << timeZoneKnown << endl; + } + break; + case 20: + { // date-time + KMime::Types::DateTime result; + bool ok = parseDateTime( iit, iend, result, withCRLF ); + + cout << ( ok ? "OK" : "BAD" ) << endl + << "result.time (in local timezone): " << ctime( &(result.time) ) + << "result.secsEastOfGMT: " << result.secsEastOfGMT + << " (" << result.secsEastOfGMT/60 << "mins)" << endl + << "result.timeZoneKnown: " << result.timeZoneKnown << endl; + } + break; + default: + assert( 0 ); + } +} diff --git a/libkmime/tests/test_mdn.cpp b/libkmime/tests/test_mdn.cpp new file mode 100644 index 000000000..1bb387f1d --- /dev/null +++ b/libkmime/tests/test_mdn.cpp @@ -0,0 +1,151 @@ +#include <kmime_mdn.h> +using namespace KMime::MDN; + +#include <qcstring.h> +#include <qstring.h> +#include <qvaluelist.h> + +#include <iostream> +using std::cout; +using std::cerr; +#include <cstdlib> +using std::exit; +using std::endl; + +#define _GNU_SOURCE 1 +#include <getopt.h> + +void usage( const char * msg=0 ) { + if ( msg ) + cerr << msg << endl; + cerr << "usage: test_mdn <options>\n" + "where options include the following:" << endl + << "FIXME" << endl; + exit( 1 ); +} + +int main( int argc, char * argv[] ) { + + QString finalRecipient; + QString originalRecipient; + QCString originalMessageId; + ActionMode actionMode = ManualAction; + SendingMode sendingMode = SentManually; + DispositionType dispositionType = Displayed; + QValueList<DispositionModifier> dispositionModifiers; + QString special; + + while ( true ) { + int option_index = 0; + static const struct option long_options[] = { + { "action-mode", 1, 0, 'a' }, + { "disposition-type", 1, 0, 'd' }, + { "final-recipient", 1, 0, 'f' }, + { "original-message-id", 1, 0, 'i' }, + { "disposition-modifiers", 1, 0, 'm' }, + { "original-recipient", 1, 0, 'o' }, + { "sending-mode", 1, 0, 's' }, + { 0, 0, 0, 0 } + }; + + int c = getopt_long( argc, argv, "a:d:f:i:m:o:s:", + long_options, &option_index ); + if ( c == -1 ) break; + +#define EQUALS(x) !qstricmp( optarg, x ) + + switch ( c ) { + + case 'a': // --action-mode + if ( EQUALS( "manual-action" ) ) + actionMode = ManualAction; + else if ( EQUALS( "automatic-action" ) ) + actionMode = AutomaticAction; + else + usage( "unknown action mode!" ); + break; + + case 'd': // --disposition-type + if ( EQUALS( "displayed" ) ) + dispositionType = Displayed; + else if ( EQUALS( "deleted" ) ) + dispositionType = Deleted; + else if ( EQUALS( "dispatched" ) ) + dispositionType = Dispatched; + else if ( EQUALS( "processed" ) ) + dispositionType = Processed; + else if ( EQUALS( "denied" ) ) + dispositionType = Denied; + else if ( EQUALS( "failed" ) ) + dispositionType = Failed; + else + usage( "unknown disposition type!" ); + break; + + case 'f': // --final-recipient + if ( optarg && *optarg ) + finalRecipient = QString::fromUtf8( optarg ); + else + usage( "--final-recipient is missing a value" ); + break; + + case 'i': // --original-message-id + if ( optarg && *optarg ) + originalMessageId = optarg; + else + usage( "--original-message-id is missing a value" ); + break; + + case 'm': // --disposition-modifier + if ( EQUALS( "error" ) ) + dispositionModifiers << Error; + else if ( EQUALS( "warning" ) ) + dispositionModifiers << Warning; + else if ( EQUALS( "superseded" ) ) + dispositionModifiers << Superseded; + else if ( EQUALS( "expired" ) ) + dispositionModifiers << Expired; + else if ( EQUALS( "mailbox-terminated" ) ) + dispositionModifiers << MailboxTerminated; + else + usage( "unknwon disposition modifier!" ); + break; + + case 'o': // --original-recipient + if ( optarg && *optarg ) + originalRecipient = QString::fromUtf8( optarg ); + else + usage( "--original-recipient is missing a value" ); + break; + + case 's': // --sending-mode + if ( EQUALS( "MDN-sent-manually" ) ) + sendingMode = SentManually; + else if ( EQUALS( "MDN-sent-automatically" ) ) + sendingMode = SentAutomatically; + else + usage( "unknown sending mode" ); + break; + + default: + usage( "unknown option encountered!" ); + } + } + + if ( optind < argc ) + special = QString::fromUtf8( argv[optind++] ); + if ( optind < argc ) + usage( "too many arguments!" ); + + QCString result = dispositionNotificationBodyContent( finalRecipient, + originalRecipient.latin1(), + originalMessageId, + dispositionType, + actionMode, + sendingMode, + dispositionModifiers, + special ); + cout << "Result:\n" << result.data(); + + return 0; +} |