summaryrefslogtreecommitdiffstats
path: root/libkmime
diff options
context:
space:
mode:
Diffstat (limited to 'libkmime')
-rw-r--r--libkmime/AUTHORS.kmime16
-rw-r--r--libkmime/CLASSTREE.bodyparts31
-rw-r--r--libkmime/CLASSTREE.headers87
-rw-r--r--libkmime/DESIGN.kmime63
-rw-r--r--libkmime/Makefile.am32
-rw-r--r--libkmime/README7
-rw-r--r--libkmime/boolflags.cpp61
-rw-r--r--libkmime/boolflags.h43
-rw-r--r--libkmime/configure.in.in23
-rw-r--r--libkmime/kmime_charfreq.cpp176
-rw-r--r--libkmime/kmime_charfreq.h68
-rw-r--r--libkmime/kmime_codec_base64.cpp405
-rw-r--r--libkmime/kmime_codec_base64.h112
-rw-r--r--libkmime/kmime_codec_identity.cpp109
-rw-r--r--libkmime/kmime_codec_identity.h116
-rw-r--r--libkmime/kmime_codec_qp.cpp644
-rw-r--r--libkmime/kmime_codec_qp.h121
-rw-r--r--libkmime/kmime_codec_uuencode.cpp240
-rw-r--r--libkmime/kmime_codec_uuencode.h76
-rw-r--r--libkmime/kmime_codecs.cpp241
-rw-r--r--libkmime/kmime_codecs.h367
-rw-r--r--libkmime/kmime_content.cpp897
-rw-r--r--libkmime/kmime_content.h170
-rw-r--r--libkmime/kmime_header_parsing.cpp1739
-rw-r--r--libkmime/kmime_header_parsing.h196
-rw-r--r--libkmime/kmime_headers.cpp1634
-rw-r--r--libkmime/kmime_headers.h861
-rw-r--r--libkmime/kmime_headers_obs.h372
-rw-r--r--libkmime/kmime_mdn.cpp262
-rw-r--r--libkmime/kmime_mdn.h202
-rw-r--r--libkmime/kmime_message.cpp168
-rw-r--r--libkmime/kmime_message.h67
-rw-r--r--libkmime/kmime_newsarticle.cpp160
-rw-r--r--libkmime/kmime_newsarticle.h55
-rw-r--r--libkmime/kmime_parsers.cpp466
-rw-r--r--libkmime/kmime_parsers.h115
-rw-r--r--libkmime/kmime_util.cpp806
-rw-r--r--libkmime/kmime_util.h340
-rw-r--r--libkmime/kmime_version.h10
-rw-r--r--libkmime/kmime_warning.h32
-rw-r--r--libkmime/kqcstringsplitter.cpp161
-rw-r--r--libkmime/kqcstringsplitter.h52
-rw-r--r--libkmime/tests/Makefile.am16
-rw-r--r--libkmime/tests/data/codec_b/basic-decode.b1
-rw-r--r--libkmime/tests/data/codec_b/basic-decode.b.expectedbin0 -> 256 bytes
-rw-r--r--libkmime/tests/data/codec_b/basic-encodebin0 -> 256 bytes
-rw-r--r--libkmime/tests/data/codec_b/basic-encode.expected1
-rw-r--r--libkmime/tests/data/codec_b/null-decode.b0
-rw-r--r--libkmime/tests/data/codec_b/null-decode.b.expected0
-rw-r--r--libkmime/tests/data/codec_b/null-encode0
-rw-r--r--libkmime/tests/data/codec_b/null-encode.expected0
-rw-r--r--libkmime/tests/data/codec_b/padding01
-rw-r--r--libkmime/tests/data/codec_b/padding0.expected1
-rw-r--r--libkmime/tests/data/codec_b/padding11
-rw-r--r--libkmime/tests/data/codec_b/padding1.expected1
-rw-r--r--libkmime/tests/data/codec_b/padding21
-rw-r--r--libkmime/tests/data/codec_b/padding2.expected1
-rw-r--r--libkmime/tests/data/codec_base64/basic-decode.base645
-rw-r--r--libkmime/tests/data/codec_base64/basic-decode.base64.expectedbin0 -> 256 bytes
-rw-r--r--libkmime/tests/data/codec_base64/basic-encodebin0 -> 256 bytes
-rw-r--r--libkmime/tests/data/codec_base64/basic-encode.expected5
-rw-r--r--libkmime/tests/data/codec_base64/corrupt.base645
-rw-r--r--libkmime/tests/data/codec_base64/corrupt.base64.expectedbin0 -> 256 bytes
-rw-r--r--libkmime/tests/data/codec_base64/very_small1
-rw-r--r--libkmime/tests/data/codec_base64/very_small.expected1
-rw-r--r--libkmime/tests/data/codec_q/all-encoded.q1
-rw-r--r--libkmime/tests/data/codec_q/all-encoded.q.expectedbin0 -> 256 bytes
-rw-r--r--libkmime/tests/data/codec_q/basic-encodebin0 -> 256 bytes
-rw-r--r--libkmime/tests/data/codec_q/basic-encode.expected1
-rw-r--r--libkmime/tests/data/codec_q/nothing-encoded.qbin0 -> 256 bytes
-rw-r--r--libkmime/tests/data/codec_q/nothing-encoded.q.expected2
-rw-r--r--libkmime/tests/data/codec_q/null-decode.q0
-rw-r--r--libkmime/tests/data/codec_q/null-decode.q.expected0
-rw-r--r--libkmime/tests/data/codec_q/null-encode0
-rw-r--r--libkmime/tests/data/codec_q/null-encode.expected0
-rw-r--r--libkmime/tests/data/codec_quoted-printable/basic-decode.quoted-printable9
-rw-r--r--libkmime/tests/data/codec_quoted-printable/basic-decode.quoted-printable.expectedbin0 -> 256 bytes
-rw-r--r--libkmime/tests/data/codec_quoted-printable/basic-encodebin0 -> 256 bytes
-rw-r--r--libkmime/tests/data/codec_quoted-printable/basic-encode.expected9
-rw-r--r--libkmime/tests/data/codec_quoted-printable/corrupt.quoted-printable28
-rw-r--r--libkmime/tests/data/codec_quoted-printable/corrupt.quoted-printable.expected20
-rw-r--r--libkmime/tests/data/codec_quoted-printable/wrap44
-rw-r--r--libkmime/tests/data/codec_quoted-printable/wrap.expected64
-rw-r--r--libkmime/tests/data/codec_x-kmime-rfc2231/all-encoded.x-kmime-rfc22311
-rw-r--r--libkmime/tests/data/codec_x-kmime-rfc2231/all-encoded.x-kmime-rfc2231.expectedbin0 -> 256 bytes
-rw-r--r--libkmime/tests/data/codec_x-kmime-rfc2231/basic-encodebin0 -> 256 bytes
-rw-r--r--libkmime/tests/data/codec_x-kmime-rfc2231/basic-encode.expected1
-rw-r--r--libkmime/tests/data/codec_x-kmime-rfc2231/nothing-encoded.x-kmime-rfc2231bin0 -> 256 bytes
-rw-r--r--libkmime/tests/data/codec_x-kmime-rfc2231/nothing-encoded.x-kmime-rfc2231.expected2
-rw-r--r--libkmime/tests/data/codec_x-kmime-rfc2231/null-decode.x-kmime-rfc22310
-rw-r--r--libkmime/tests/data/codec_x-kmime-rfc2231/null-decode.x-kmime-rfc2231.expected0
-rw-r--r--libkmime/tests/data/codec_x-kmime-rfc2231/null-encode0
-rw-r--r--libkmime/tests/data/codec_x-kmime-rfc2231/null-encode.expected0
-rw-r--r--libkmime/tests/data/codec_x-uuencode/basic-decode.x-uuencode9
-rw-r--r--libkmime/tests/data/codec_x-uuencode/basic-decode.x-uuencode.expectedbin0 -> 256 bytes
-rwxr-xr-xlibkmime/tests/gen_decode_map.pl17
-rwxr-xr-xlibkmime/tests/run_codec_tests93
-rw-r--r--libkmime/tests/test_charfreq.cpp41
-rw-r--r--libkmime/tests/test_dates.cpp93
-rw-r--r--libkmime/tests/test_kmime_codec.cpp449
-rw-r--r--libkmime/tests/test_kmime_header_parsing.cpp431
-rw-r--r--libkmime/tests/test_mdn.cpp151
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", &yencLine)) {
+ success=false;
+ break;
+ }
+ int yencSize;
+ if (!yencMeta( meta, "size", &yencSize)) {
+ 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
new file mode 100644
index 000000000..c86626638
--- /dev/null
+++ b/libkmime/tests/data/codec_b/basic-decode.b.expected
Binary files differ
diff --git a/libkmime/tests/data/codec_b/basic-encode b/libkmime/tests/data/codec_b/basic-encode
new file mode 100644
index 000000000..c86626638
--- /dev/null
+++ b/libkmime/tests/data/codec_b/basic-encode
Binary files differ
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
new file mode 100644
index 000000000..c86626638
--- /dev/null
+++ b/libkmime/tests/data/codec_base64/basic-decode.base64.expected
Binary files differ
diff --git a/libkmime/tests/data/codec_base64/basic-encode b/libkmime/tests/data/codec_base64/basic-encode
new file mode 100644
index 000000000..c86626638
--- /dev/null
+++ b/libkmime/tests/data/codec_base64/basic-encode
Binary files differ
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
new file mode 100644
index 000000000..c86626638
--- /dev/null
+++ b/libkmime/tests/data/codec_base64/corrupt.base64.expected
Binary files differ
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
new file mode 100644
index 000000000..c86626638
--- /dev/null
+++ b/libkmime/tests/data/codec_q/all-encoded.q.expected
Binary files differ
diff --git a/libkmime/tests/data/codec_q/basic-encode b/libkmime/tests/data/codec_q/basic-encode
new file mode 100644
index 000000000..c86626638
--- /dev/null
+++ b/libkmime/tests/data/codec_q/basic-encode
Binary files differ
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
new file mode 100644
index 000000000..c86626638
--- /dev/null
+++ b/libkmime/tests/data/codec_q/nothing-encoded.q
Binary files differ
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
new file mode 100644
index 000000000..c86626638
--- /dev/null
+++ b/libkmime/tests/data/codec_quoted-printable/basic-decode.quoted-printable.expected
Binary files differ
diff --git a/libkmime/tests/data/codec_quoted-printable/basic-encode b/libkmime/tests/data/codec_quoted-printable/basic-encode
new file mode 100644
index 000000000..c86626638
--- /dev/null
+++ b/libkmime/tests/data/codec_quoted-printable/basic-encode
Binary files differ
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
new file mode 100644
index 000000000..c86626638
--- /dev/null
+++ b/libkmime/tests/data/codec_x-kmime-rfc2231/all-encoded.x-kmime-rfc2231.expected
Binary files differ
diff --git a/libkmime/tests/data/codec_x-kmime-rfc2231/basic-encode b/libkmime/tests/data/codec_x-kmime-rfc2231/basic-encode
new file mode 100644
index 000000000..c86626638
--- /dev/null
+++ b/libkmime/tests/data/codec_x-kmime-rfc2231/basic-encode
Binary files differ
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
new file mode 100644
index 000000000..c86626638
--- /dev/null
+++ b/libkmime/tests/data/codec_x-kmime-rfc2231/nothing-encoded.x-kmime-rfc2231
Binary files differ
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
new file mode 100644
index 000000000..c86626638
--- /dev/null
+++ b/libkmime/tests/data/codec_x-uuencode/basic-decode.x-uuencode.expected
Binary files differ
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;
+}