summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2012-06-25 23:57:04 (GMT)
committerChristian Mollekopf <chrigi_1@fastmail.fm>2012-06-25 23:57:04 (GMT)
commitfce7890292ba8ffb868db104ecc49e7e91b7cddd (patch)
treea73884105db117d5d8cb1e26b1e77647e1bb2f98
parente21e62c14f5aae99d46643065fcb86e2e3abe230 (diff)
downloadlibcalendaring-fce7890292ba8ffb868db104ecc49e7e91b7cddd.tar.gz
Initial import of kmime from
commit b54a325116b194da090f900c9a538710759eb303 Author: Stephen Kelly <steveire@gmail.com> Date: Sun May 6 20:44:53 2012 +0200 Revert "Port to const QRegExp API." This reverts commit 0ca0dfc7e0ca8095efd0b060d1d5e26ac9ceb379. The qtbase commit requiring this was reverted.
-rw-r--r--kmime/.krazy1
-rw-r--r--kmime/CMakeLists.txt53
-rw-r--r--kmime/Mainpage.dox1201
-rw-r--r--kmime/Messages.sh2
-rw-r--r--kmime/TODO22
-rw-r--r--kmime/boolflags.cpp77
-rw-r--r--kmime/boolflags.h95
-rw-r--r--kmime/config-kmime.h.cmake5
-rw-r--r--kmime/kautodeletehash.cpp22
-rw-r--r--kmime/kautodeletehash.h75
-rw-r--r--kmime/kmime_charfreq.cpp252
-rw-r--r--kmime/kmime_charfreq.h185
-rw-r--r--kmime/kmime_codec_base64.cpp418
-rw-r--r--kmime/kmime_codec_base64.h194
-rw-r--r--kmime/kmime_codec_identity.cpp109
-rw-r--r--kmime/kmime_codec_identity.h214
-rw-r--r--kmime/kmime_codec_qp.cpp740
-rw-r--r--kmime/kmime_codec_qp.h228
-rw-r--r--kmime/kmime_codec_uuencode.cpp244
-rw-r--r--kmime/kmime_codec_uuencode.h114
-rw-r--r--kmime/kmime_codecs.cpp233
-rw-r--r--kmime/kmime_codecs.h511
-rw-r--r--kmime/kmime_content.cpp1169
-rw-r--r--kmime/kmime_content.h846
-rw-r--r--kmime/kmime_content_p.h85
-rw-r--r--kmime/kmime_contentindex.cpp120
-rw-r--r--kmime/kmime_contentindex.h139
-rw-r--r--kmime/kmime_dateformatter.cpp336
-rw-r--r--kmime/kmime_dateformatter.h295
-rw-r--r--kmime/kmime_export.h50
-rw-r--r--kmime/kmime_header_parsing.cpp2216
-rw-r--r--kmime/kmime_header_parsing.h398
-rw-r--r--kmime/kmime_header_parsing_p.h39
-rw-r--r--kmime/kmime_headerfactory.cpp117
-rw-r--r--kmime/kmime_headerfactory_p.h95
-rw-r--r--kmime/kmime_headers.cpp2295
-rw-r--r--kmime/kmime_headers.h1508
-rw-r--r--kmime/kmime_headers_p.h182
-rw-r--r--kmime/kmime_mdn.cpp283
-rw-r--r--kmime/kmime_mdn.h196
-rw-r--r--kmime/kmime_message.cpp171
-rw-r--r--kmime/kmime_message.h238
-rw-r--r--kmime/kmime_message_p.h43
-rw-r--r--kmime/kmime_newsarticle.cpp112
-rw-r--r--kmime/kmime_newsarticle.h125
-rw-r--r--kmime/kmime_parsers.cpp506
-rw-r--r--kmime/kmime_parsers.h130
-rw-r--r--kmime/kmime_util.cpp1009
-rw-r--r--kmime/kmime_util.h433
-rw-r--r--kmime/kmime_util_p.h63
-rw-r--r--kmime/kmime_version.h32
-rw-r--r--kmime/kmime_warning.h60
-rw-r--r--kmime/tests/CMakeLists.txt4
-rw-r--r--kmime/tests/auto/CMakeLists.txt23
-rw-r--r--kmime/tests/auto/base64benchmark.cpp108
-rw-r--r--kmime/tests/auto/charfreqtest.cpp154
-rw-r--r--kmime/tests/auto/charfreqtest.h36
-rw-r--r--kmime/tests/auto/codectest.cpp99
-rw-r--r--kmime/tests/auto/codectest.h33
-rw-r--r--kmime/tests/auto/contentindextest.cpp90
-rw-r--r--kmime/tests/auto/contentindextest.h34
-rw-r--r--kmime/tests/auto/contenttest.cpp745
-rw-r--r--kmime/tests/auto/contenttest.h49
-rw-r--r--kmime/tests/auto/headerfactorytest.cpp165
-rw-r--r--kmime/tests/auto/headerfactorytest.h37
-rw-r--r--kmime/tests/auto/headertest.cpp985
-rw-r--r--kmime/tests/auto/headertest.h58
-rw-r--r--kmime/tests/auto/messagetest.cpp600
-rw-r--r--kmime/tests/auto/messagetest.h54
-rw-r--r--kmime/tests/auto/rfc2047test.cpp122
-rw-r--r--kmime/tests/auto/rfc2047test.h34
-rw-r--r--kmime/tests/auto/rfc2231test.cpp119
-rw-r--r--kmime/tests/auto/rfc2231test.h34
-rw-r--r--kmime/tests/auto/sizetest.cpp112
-rw-r--r--kmime/tests/auto/utiltest.cpp198
-rw-r--r--kmime/tests/auto/utiltest.h39
-rw-r--r--kmime/tests/data/codec_b/basic-decode.b1
-rw-r--r--kmime/tests/data/codec_b/basic-decode.b.expectedbin0 -> 256 bytes
-rw-r--r--kmime/tests/data/codec_b/basic-encodebin0 -> 256 bytes
-rw-r--r--kmime/tests/data/codec_b/basic-encode.expected1
-rw-r--r--kmime/tests/data/codec_b/null-decode.b0
-rw-r--r--kmime/tests/data/codec_b/null-decode.b.expected0
-rw-r--r--kmime/tests/data/codec_b/null-encode0
-rw-r--r--kmime/tests/data/codec_b/null-encode.expected0
-rw-r--r--kmime/tests/data/codec_b/padding0-encode1
-rw-r--r--kmime/tests/data/codec_b/padding0-encode.expected1
-rw-r--r--kmime/tests/data/codec_b/padding1-encode1
-rw-r--r--kmime/tests/data/codec_b/padding1-encode.expected1
-rw-r--r--kmime/tests/data/codec_b/padding2-encode1
-rw-r--r--kmime/tests/data/codec_b/padding2-encode.expected1
-rw-r--r--kmime/tests/data/codec_base64/basic-decode.base645
-rw-r--r--kmime/tests/data/codec_base64/basic-decode.base64.expectedbin0 -> 256 bytes
-rw-r--r--kmime/tests/data/codec_base64/basic-encodebin0 -> 256 bytes
-rw-r--r--kmime/tests/data/codec_base64/basic-encode.expected5
-rw-r--r--kmime/tests/data/codec_base64/corrupt.base645
-rw-r--r--kmime/tests/data/codec_base64/corrupt.base64.expectedbin0 -> 256 bytes
-rw-r--r--kmime/tests/data/codec_base64/very_small-encode1
-rw-r--r--kmime/tests/data/codec_base64/very_small-encode.expected1
-rw-r--r--kmime/tests/data/codec_q/all-encoded-decode.q1
-rw-r--r--kmime/tests/data/codec_q/all-encoded-decode.q.expectedbin0 -> 256 bytes
-rw-r--r--kmime/tests/data/codec_q/basic-encodebin0 -> 256 bytes
-rw-r--r--kmime/tests/data/codec_q/basic-encode.expected1
-rw-r--r--kmime/tests/data/codec_q/null-decode.q0
-rw-r--r--kmime/tests/data/codec_q/null-decode.q.expected0
-rw-r--r--kmime/tests/data/codec_q/null-encode0
-rw-r--r--kmime/tests/data/codec_q/null-encode.expected0
-rw-r--r--kmime/tests/data/codec_quoted-printable/basic-decode.quoted-printable9
-rw-r--r--kmime/tests/data/codec_quoted-printable/basic-decode.quoted-printable.expectedbin0 -> 256 bytes
-rw-r--r--kmime/tests/data/codec_quoted-printable/basic-encodebin0 -> 256 bytes
-rw-r--r--kmime/tests/data/codec_quoted-printable/basic-encode.expected9
-rw-r--r--kmime/tests/data/codec_quoted-printable/corrupt.quoted-printable7
-rw-r--r--kmime/tests/data/codec_quoted-printable/corrupt.quoted-printable.expected6
-rw-r--r--kmime/tests/data/codec_quoted-printable/corrupt2.quoted-printable1
-rw-r--r--kmime/tests/data/codec_quoted-printable/corrupt2.quoted-printable.expected1
-rw-r--r--kmime/tests/data/codec_quoted-printable/corrupt3.quoted-printable1
-rw-r--r--kmime/tests/data/codec_quoted-printable/corrupt3.quoted-printable.expected1
-rw-r--r--kmime/tests/data/codec_quoted-printable/corrupt4.quoted-printable1
-rw-r--r--kmime/tests/data/codec_quoted-printable/corrupt4.quoted-printable.expected1
-rw-r--r--kmime/tests/data/codec_quoted-printable/wrap-encode44
-rw-r--r--kmime/tests/data/codec_quoted-printable/wrap-encode.expected64
-rw-r--r--kmime/tests/data/codec_x-kmime-rfc2231/all-encoded.x-kmime-rfc2231-decode1
-rw-r--r--kmime/tests/data/codec_x-kmime-rfc2231/all-encoded.x-kmime-rfc2231-decode.expectedbin0 -> 256 bytes
-rw-r--r--kmime/tests/data/codec_x-kmime-rfc2231/basic-encodebin0 -> 256 bytes
-rw-r--r--kmime/tests/data/codec_x-kmime-rfc2231/basic-encode.expected1
-rw-r--r--kmime/tests/data/codec_x-kmime-rfc2231/null-decode.x-kmime-rfc22310
-rw-r--r--kmime/tests/data/codec_x-kmime-rfc2231/null-decode.x-kmime-rfc2231.expected0
-rw-r--r--kmime/tests/data/codec_x-kmime-rfc2231/null-encode0
-rw-r--r--kmime/tests/data/codec_x-kmime-rfc2231/null-encode.expected0
-rw-r--r--kmime/tests/data/codec_x-uuencode/basic-decode.x-uuencode9
-rw-r--r--kmime/tests/data/codec_x-uuencode/basic-decode.x-uuencode.expectedbin0 -> 256 bytes
-rw-r--r--kmime/tests/data/mails/broken-content-disposition.mbox50
-rw-r--r--kmime/tests/data/mails/dfaure-crash.mbox5
-rw-r--r--kmime/tests/data/mails/encoding-crash.mbox12
-rw-r--r--kmime/tests/data/mails/issue3908.mbox38
-rw-r--r--kmime/tests/data/mails/outlook-attachment.mbox47
-rw-r--r--kmime/tests/data/mails/simple-encapsulated.mbox72
-rwxr-xr-xkmime/tests/gen_decode_map.pl17
-rw-r--r--kmime/tests/manual/CMakeLists.txt37
-rw-r--r--kmime/tests/manual/test_charfreq.cpp40
-rw-r--r--kmime/tests/manual/test_dates.cpp98
-rw-r--r--kmime/tests/manual/test_kmime_header_parsing.cpp431
-rw-r--r--kmime/tests/manual/test_mdn.cpp150
142 files changed, 23398 insertions, 0 deletions
diff --git a/kmime/.krazy b/kmime/.krazy
new file mode 100644
index 0000000..0b16e7f
--- /dev/null
+++ b/kmime/.krazy
@@ -0,0 +1 @@
+SKIP /tests/
diff --git a/kmime/CMakeLists.txt b/kmime/CMakeLists.txt
new file mode 100644
index 0000000..4bb9f8a
--- /dev/null
+++ b/kmime/CMakeLists.txt
@@ -0,0 +1,53 @@
+add_subdirectory( tests )
+add_definitions( -DKDE_DEFAULT_DEBUG_AREA=5320 -DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII )
+include(CheckTimezone)
+configure_file (config-kmime.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kmime.h)
+set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" )
+
+########### next target ###############
+
+set(kmime_LIB_SRCS
+ 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_headerfactory.cpp
+ kmime_content.cpp
+ kmime_contentindex.cpp
+ kmime_headers.cpp
+ kmime_message.cpp
+ kmime_newsarticle.cpp
+ kmime_dateformatter.cpp
+ boolflags.cpp
+ kautodeletehash.cpp )
+
+
+kde4_add_library(kmime ${LIBRARY_TYPE} ${kmime_LIB_SRCS})
+
+target_link_libraries(kmime ${KDE4_KDECORE_LIBS} )
+
+set_target_properties(kmime PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION})
+
+install(TARGETS kmime EXPORT kdepimlibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS})
+
+install( FILES
+ boolflags.h
+ kmime_export.h
+ kmime_charfreq.h
+ kmime_codecs.h
+ kmime_content.h
+ kmime_contentindex.h
+ kmime_header_parsing.h
+ kmime_headers.h
+ kmime_message.h
+ kmime_mdn.h
+ kmime_newsarticle.h
+ kmime_dateformatter.h
+ kmime_util.h
+ DESTINATION ${INCLUDE_INSTALL_DIR}/kmime COMPONENT Devel)
diff --git a/kmime/Mainpage.dox b/kmime/Mainpage.dox
new file mode 100644
index 0000000..7f47e48
--- /dev/null
+++ b/kmime/Mainpage.dox
@@ -0,0 +1,1201 @@
+/**
+
+\mainpage The KMime Library
+
+\section introduction Introduction
+
+KMime is a library for handling mail messages and newsgroup articles. Both mail messages and
+newsgroup articles are based on the same standard called MIME, which stands for
+<b>Multipurpose Internet Mail Extensions</b>. In this document, the term \c message is used to
+refer to both mail messages and newsgroup articles.
+
+KMime deals solely with the in-memory representation of messages, topics such a transport or storage
+of messages are handled by other libraries, for example by
+<a href="http://api.kde.org/4.x-api/kdepimlibs-apidocs/mailtransport/html/index.html">the mailtransport library</a>
+or by <a href="http://api.kde.org/4.x-api/kdepimlibs-apidocs/kimap/html/index.html">the KIMAP library</a>.
+Similary, this library does not deal with displaying messages or advanced composing, for those there
+are the <a href="http://api.kde.org/4.x-api/kdepim-apidocs/messageviewer/html/index.html">messageviewer</a>
+and the <a href="http://websvn.kde.org/trunk/KDE/kdepim/messagecomposer/">messagecomposer</a>
+components in the KDEPIM module.
+
+KMime's main function is to parse, modify and assemble messages in-memory. In a
+\ref string-broken-down "later section", <i>parsing</i> and <i>assembling</i> is actually explained.
+KMime provides high-level classes that make these tasks easy.
+
+MIME is defined by various RFCs, see the \ref rfcs "RFC section" for a list of them.
+
+\section structure Structure of this document
+
+This document will first give an \ref mime-intro "introduction to the MIME specification", as it is
+essential to understand the basics of the structure of MIME messages for using this library.
+The introduction here is aimed at users of the library, it gives a broad overview with examples and
+omits some details. Developers who wish to modifiy KMime should read the
+\ref rfcs "corresponding RFCs" as well, but this is not necessary for library users.
+
+After the introduction to the MIME format, the two ways of representing a message in memory are
+discussed, the \ref string-broken-down "string representation and the broken down representation".
+
+This is followed by a section giving an
+\ref classes-overview "overview of the most important KMime classes".
+
+The last sections give a list of \ref rfcs "relevant RFCs" and provide links for
+\ref links "further reading".
+
+\section mime-intro Structure of MIME messages
+
+\subsection history A brief history of the MIME standard
+
+The MIME standard is quite new (1993), email and usenet existed way before the MIME standard came into
+existence. Because of this, the MIME standard has to keep backwards compatibility. The email
+standard before MIME lacked many capabilities like encodings other than ASCII or attachments. These
+and other things were later added by MIME. The standard for messages before MIME is defined in
+<a href="http://tools.ietf.org/html/rfc5322">RFC 5322</a>. In <a href="http://tools.ietf.org/html/rfc2045">RFC 2045</a>
+to <a href="http://tools.ietf.org/html/rfc2049">RFC 2049</a>, several backward-compatible extensions
+to the basic message format are defined, adding support for attachments, different encodings and many
+others.
+
+Actually, there is an even older standard, defined in <a href="http://tools.ietf.org/html/rfc733">RFC 733</a>
+(<i>Standard for the format of ARPA network text messages</i>, introduced in 1977).
+This standard is now obsoleted by RFC 5322, but backwards compatibilty is in some cases supported, as
+there are still messages in this format around.
+
+Since pre-MIME messages had no way to handle attachments, attachments were sometimes added to the message
+text in an <a href="http://en.wikipedia.org/wiki/Uuencoding">uuencoded</a> form. Although this is also
+obsolete, reading uuencoded attachments is still supported by KMime.
+
+After MIME was introduced, people realized that there is no way to have the filename of attachments
+encoded in anything different than ASCII. Thus, <a href="http://tools.ietf.org/html/rfc2231">RFC 2231</a>
+was introduced to allow abitrary encodings for parameter values, such as the attachment filename.
+
+\subsection examples MIME by examples
+
+In the following sections, MIME message examples are shown, examined and explained, starting with
+a simple message and proceeding to more interesting examples.
+You can get additional examples by simply viewing the source of your own messages in your mail client,
+or by having a look at the examples in the \ref rfcs "various RFCs".
+
+\subsubsection simple-mail A simple message
+
+\verbatim
+Subject: First Mail
+From: John Doe <john.doe@domain.com>
+Date: Sun, 21 Feb 2010 19:16:11 +0100
+MIME-Version: 1.0
+
+Hello World!
+\endverbatim
+The above example features a very simple message. The two main parts of this message are the \b header
+and the \b body, which are seperated by an empty line. The body contains the actual message content,
+and the header contains metadata about the message itself. The header consists of several <b>header fields</b>,
+each of them in their own line. Header fields are made up from the <b>header field name</b>, followed by a colon, followed
+by the <b>header field body</b>.
+
+The \b MIME-Version header field is mandatory for MIME messages. \b Subject,
+\b From and \b Date are important header fields, they are usually displayed in the message list of a
+mail client. The \c Subject header field can be anything, it does not have a special structure. It is a
+so-called \b unstructured header field. In contrast, the \c From and the \c Date header fields have
+to follow a special structure, they must be formed in a way that machines can parse. They are \b structured
+header fields. For example, a mail client needs to understand
+the \c Date header field so that it can sort the messages by date in the message list.
+The exact details of how the header field bodies of structured header fields should be
+formed are specified in an RFC.
+
+In this example, the \c From header contains a single email address. More precisly, a single email address is called
+a \b mailbox, which is made up of the <b>display name</b> (John Doe) and the <b>address specification</b> (john.doe@domain.com),
+which is enclosed in angle brackets. The \c addr-spec consists of the user name, the <b>local part</b>,
+and the \b domain name.
+
+Many header fields can contain multiple email addresses, for example the \c To field for messages with
+multiple recipients can have a comma-seperated list of mailboxes.
+A list of mailboxes, together with a display name for the list, forms a \b group, and multiple groups can form an
+<b>address list</b>. This is however rarely used, you'll most often see a simple list of plain mailboxes.
+
+There are many more possible header fields than shown in this example, and the header can even contain
+abitrary header fields, which usually are prefixed with \c X-, like \c X-Face.
+
+\subsubsection encodings Encodings and charsets
+
+\verbatim
+From: John Doe <john.doe@domain.com>
+Date: Mon, 22 Feb 2010 00:42:45 +0100
+MIME-Version: 1.0
+Content-Type: Text/Plain;
+ charset="iso-8859-1"
+Content-Transfer-Encoding: quoted-printable
+
+Gr=FCezi Welt!
+\endverbatim
+
+The above shows a message that is using a different \b charset than the standard \b US-ASCII charset. The
+message body contains the string "Grüezi Welt!", which is \b encoded in a special way.
+
+The \b content-type of this message is \b text/plain, which means that the message is simple text. Later,
+other content types will be introduced, such as \b text/html. If there is no \c Content-Type header
+field, it is assumed that the content-type is \c text/plain.
+
+Before MIME was introduced, all messages were limited to the US-ASCII charset. Only the
+lower 127 values of the bytes were allowed to be used, the so-called \b 7-bit range. Writing a message in
+another charset or using letters from the upper 127 byte values was not allowed.
+
+\par Charset Encoding
+
+When talking about charsets, it is important to understand how strings of text are converted to
+byte arrays, and the other way around. A message is nothing else than a big array of bytes.
+The bytes that form the body of the message somehow need to be interpreted as a text string. Interpreting
+a byte array as a text string is called \b decoding the text. Converting a text string to a byte array is called
+\b encoding the text. A \b codec (<b>co</b>der-<b>dec</b>oder) is a utility that can encode and decode text.
+In Qt, the class for text strings is QString, and the class for byte arrays is QByteArray. The base class
+of all codecs is QTextCodec.
+
+With the US-ASCII charset, encoding and decoding text is easy, one just has to look at an <a href="http://en.wikipedia.org/wiki/ASCII_table">
+ASCII table</a> to be able to convert text strings to byte arrays and byte arrays to text strings. For
+example, the letter 'A' is represented by a single byte with the value of 65. When encountering a byte
+with the value 84, we can look that up in the table and see that it represents the letter 'T'.
+With the US-ASCII charset, each letter is represented by exactly one byte, which is very convenient.
+Even better, all letters commonly used in English text have byte values below 127, so the 7-bit limit
+of messages is no problem for text encoded with the US-ASCII charset.
+Another example: The string "Hello World!" is represented by the following byte array:<br>
+<code>48 65 6C 6C 6F 20 57 6F 72 6C 64</code><br>
+Note that the byte values are written in hexadecimal form here, not in decimal as earlier.
+
+Now, what if we want to write a message that contains German umlauts or Chinese letters? Those
+are not in the ASCII table, therefore a different charset has to be used. There is a wealth of charsets
+to chose from. Not all charsets can handle all letters, for example the
+<a href="http://en.wikipedia.org/wiki/ISO-8859-1#ISO-8859-1">ISO-8859-1</a> charset can handle
+German umlauts, but can not handle Chinese or Arabic letters. The <a href="http://en.wikipedia.org/wiki/Unicode">
+Unicode standard</a> is an attempt to introduce charsets that can handle all known letters in the
+world, in all languages. Unicode actually has several charsets, for example <a href="http://en.wikipedia.org/wiki/UTF-8">UTF-8</a>
+and <a href="http://en.wikipedia.org/wiki/UTF-16">UTF-16</a>. In an ideal world, everyone would be using
+Unicode charsets, but for historic and legacy reasons, other charsets are still much in use.
+
+Charsets other than US-ASCII don't generally have as nice properties: A single letter can be represented
+by multiple bytes, and generally the byte values are not in the 7-bit range. Pay attention to the UTF-8
+charset: At first glance, it looks exactly like the US-ASCII charset, common latin letters like A - Z
+are encoded with the same byte values as with US-ASCII. However, letters other than A - Z are suddenly
+encoded with two or even more bytes. In general, one letter can be encoded in an abitrary number of bytes, depending
+on the charset. One can \b not rely on the <code>1 letter == 1 byte</code> assumption.
+
+Now, what should be done when the text string "Grüezi Welt!" should be sent in the body of a message?
+The first step is to chose a charset that can represent all letters. This already excludes US-ASCII.
+Once a charset is chosen, the text string is encoded into a byte array.
+"Grüezi Welt!" encoded with the ISO-8859-1 charset produces the following byte array:<br>
+<code>47 72 FC 65 7A 69 20 57 65 6C 74 21</code><br>
+The letter 'ü' here is encoded using a single byte with the value <code>FC</code>.
+The same string encoded with UTF-8 looks slightly different:<br>
+<code>47 72 C3 BC 65 7A 69 20 57 65 6C 74 21</code><br>
+Here, the letter 'ü' is encoded with two bytes, <code>C3 BC</code>. Still, one can see the similarity
+between the two charsets for the other letters.
+
+You can try this out yourself: Open your favorite text editor and enter some text with non-latin
+letters. Then save the file and view it in a hex editor to see how the text was converted to a
+byte array. Make sure to try out setting different charsets in your text editor.
+
+At this point, the text string is sucessfully converted to a byte array, using e.g. the ISO-8859-1
+charset. To indicate which charset was used, a \b Content-Type header field has to be added, with the correct
+\b charset parameter. In our example above, that was done. If the charset parameter of the \c Content-Type,
+or even the complete \c Content-Type header field is left out, the receiver can not know how to interpret
+the byte array! In these cases, the byte array is usually decoded incorrectly, and the text strings contain
+wrong letters or lots of questionmarks. There is even a special term for such wrongly decoded text,
+<a href="http://en.wikipedia.org/wiki/Mojibake">Mojibake</a>. It is important to always know what charset
+your byte array is encoded with, otherwise an attempt at decoding the byte array into a text string will fail and produce
+Mojibake. <b>There is no such thing as plain text!</b> If there is no \c Content-Type header field in
+a message, the message body should be interpreted as US-ASCII.
+
+To learn more about charsets and encodings, read
+<a href="http://www.joelonsoftware.com/articles/Unicode.html">The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)</a>
+and <a href="http://www.cs.tut.fi/~jkorpela/chars.html">A tutorial on character code issues</a>. Especially
+the first article should really be read, as the name indicates.
+
+\par Content Transfer Encoding
+
+Now, we can't use the byte array that was just created in a message. The string encoded with ISO-8859-1
+has the byte value <code>FC</code> for the letter 'ü', which is decimal value 252. However, as said earlier,
+messages are only valid when all bytes are in the 7-bit range, i.e. have byte value below 127.
+So what should we do for byte values that are greater than 127, how can they be added to messages? The solution
+for this is to use a <b>content transfer encoding</b> (CTE). A content transfer encoding takes a byte
+array as input and transforms it. The output is another byte array, but one which only uses byte values
+in the 7-bit range. One such content transfer encoding is <b>quoted-printable</b> (QTP), which is used in the
+above example. Quoted-printable is easy to understand: When encountering a byte that has a value greater
+than 127, it is simply replaced by a '=', followed by the hexadecimal code of the byte value, represented
+as letters and digits encoded with ASCII. This means
+that a byte with the value 252 is replaced with the ASCII string <code>=FC</code>, since <code>FC</code>
+is the hexadecimal value of 252. The ASCII string <code>=FC</code> itself is now three bytes big,
+<code>3D 46 43</code>. Therefore, the quoted-printable encoding replaces each byte outside of the 7-bit
+range with 3 new bytes. Decoding quoted-printable encoding is also easy: Each time a byte with the value
+<code>3D</code>, which is the letter '=' in ASCII, is encountered, the next two following bytes are interpreted
+as the hex value of the resulting byte. The quoted-printable encoding was invented to make reading the
+byte array easy for humans.
+
+The quoted-printable encoding is not a good choice when the input byte array contains lots of bytes
+outside the 7-bit range, as the resulting byte array will be three times as big in the worst case,
+which is a waste of space. Therefore another content transfer encoding was introduced, \b Base64.
+The details of the base64 encoding are too much to write about here; refer to the
+<a href="http://en.wikipedia.org/wiki/Base64">Wikipedia article</a>
+or the <a href="http://tools.ietf.org/html/rfc2045#section-6.8">RFC</a> for details.
+As an example, the ISO-8859-1 encoded text string "Grüezi Welt!" is, after encoding it with base64,
+represented by the following ASCII string: <code>R3L8ZXppIFdlbHQh</code>.
+To express the same in byte arrays: The byte array <code>47 72 FC 65 7A 69 20 57 65 6C 74 21</code>
+is, after encoding it with base64,
+represented by the byte array <code>52 33 4C 38 5A 58 70 70 49 46 64 6C 62 48 51 68</code>
+
+There are two other content transfer encodings besides quoted printable and base64: \b 7-bit and
+\b 8-bit. 7-bit is just a marker to indicate that no content transfer encoding is used. This is the
+case when the byte array is already completley in the 7-bit range, for example when writing English
+text using the US-ASCII charset. 8-bit is also a marker to indicate that no content transfer encoding
+was used. This time, not because it was not necessary, but because of a special exception, byte values
+outside of the 7-bit range are allowed. For example, some SMTP servers support the
+<a href="http://tools.ietf.org/html/rfc1652">8BITMIME</a> extension, which indicates that they accept
+bytes outside of the 7-bit range. In this case, one can simply use the byte arrays as-is, without using
+any content transfer encoding. Creating messages with 8-bit content transfer encoding is currently not
+supported by KMime. The advantage of 8-bit is that there is no overhead in size, unlike with
+base64 or even quoted-printable.
+
+When using one of the 4 contents transfer encodings, i.e. quoted-printable, base64, 7-bit or 8-bit, this
+has to be indicated in the header field \b Content-Transfer-Encoding. If the header field is left out,
+it is assumed that the content transfer encoding is 7-bit. The example above uses quoted-printable.
+
+\verbatim
+From: John Doe <john.doe@domain.com>
+Date: Mon, 22 Feb 2010 00:42:45 +0100
+MIME-Version: 1.0
+Content-Type: Text/Plain;
+ charset="iso-8859-1"
+Content-Transfer-Encoding: base64
+
+R3L8ZXppIFdlbHQh
+\endverbatim
+The same example, this time encoded with the base64 content transfer encoding.
+
+\verbatim
+From: John Doe <john.doe@domain.com>
+Date: Mon, 22 Feb 2010 00:42:45 +0100
+MIME-Version: 1.0
+Content-Type: Text/Plain;
+ charset="utf-8"
+Content-Transfer-Encoding: base64
+
+R3LDvGV6aSBXZWx0IQ==
+\endverbatim
+Again the same example, this time using UTF-8 as the charset.
+
+\verbatim
+From: John Doe <john.doe@domain.com>
+Date: Mon, 22 Feb 2010 00:42:45 +0100
+MIME-Version: 1.0
+Content-Type: Text/Plain;
+ charset="utf-8"
+Content-Transfer-Encoding: quoted-printable
+
+Gr=C3=BCezi Welt!
+\endverbatim
+The example with a combination of UTF-8 and quoted-printable CTE. As said somewhere above, with the
+UTF-8 encoding, the letter 'ü' is represented by the two bytes <code>C3 BC</code>.
+
+\verbatim
+From: John Doe <john.doe@domain.com>
+Date: Mon, 22 Feb 2010 00:42:45 +0100
+MIME-Version: 1.0
+Content-Type: Text/Plain;
+ charset="utf-8"
+Content-Transfer-Encoding: 7-bit
+
+Hello World
+\endverbatim
+A different example, showing 7-bit content transfer encoding. Although the UTF-8 charset has lots
+of letters that are represented by bytes outside of the 7-bit range, the string "Hello World" can
+be fully represented in the 7-bit range here, even with UTF-8.
+
+In the \ref links "further reading" section, you will find links to web applications that demonstrate
+encodings and charsets.
+
+\par Conclusion
+
+When adding a text string to the body of a message, it needs to be encoded twice: First, the encoding of the charset
+needs to be applied, which transforms the text string into a byte array. Afterwards, the content transfer
+encoding has to be applied, which transforms the byte array from the first step into a byte array that
+only has bytes in the 7-bit range.
+
+When decoding, the same has to be done, in reverse: One first has decode the byte array with the content transfer encoding, to get a byte
+array that has all 256 possible byte values. Afterwards, the resulting byte array needs to be decoded
+with the correct charset, to transform it into a text string. For those two decoding steps, one has to
+look at the \c Content-Type and the \c Content-Transfer-Encoding header fields to find the correct
+charset and CTE for decoding.
+
+It is important to always keep the charset and the content transfer encoding in mind. Byte arrays and
+strings are not to be confused. Byte arrays that are encoded with a CTE are not to be confused with
+byte arrays that are \b not encoded with a CTE.
+
+This section showed how to use different charsets in the <i>body</i> of a message. The next section will
+show what to do when another charset is needed in one of the <i>header</i> field bodies.
+
+\subsubsection header-encoding Encoding in Header Fields
+
+In the last section, we discussed how to use different charsets in the body of a message. But what if
+a different charset needs to be added to one of the header fields? For example one might want to write
+a mail to a mailbox with the display name "András Manţia" and with the subject "Grüezi!".
+
+The header fields are limited to characters in the 7-bit range, and are interpreted as US-ASCII.
+That means the header field names, such as "From: ", are all encoded in US-ASCII. The header field
+bodies, such as the "1.0" of \c MIME-Version, are also encoded with US-ASCII. This is mandated by
+<a href="http://tools.ietf.org/html/rfc5322#section-2">the RFC</a>.
+
+The \c Content-Type and the \c Content-Transfer-Encoding header fields only apply to the message body,
+they have no meaning for other header fields.
+
+This means that any letter in a different charset has to be encoded in some way to statisfy the RFC.
+Letters with a different charset are only allowed in some of the header field bodies, the header field
+names always have to be in US-ASCII.
+
+\verbatim
+From: Thomas McGuire <thomas@domain.com>
+Subject: =?iso-8859-1?q?Gr=FCezi!?=
+Date: Mon, 22 Feb 2010 14:34:01 +0100
+MIME-Version: 1.0
+To: =?utf-8?q?Andr=C3=A1s?= =?utf-8?q?_Man=C5=A3ia?= <andras@domain.com>
+Content-Type: Text/Plain;
+ charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+
+bla bla bla
+\endverbatim
+
+The above example shows how text that is encoded with a different charset than US-ASCII is handled
+in the message header. This can be seen in the bodies of the \c Subject header field and the \c To header field.
+In this example, the body of the message is unimportant, it is just "bla bla bla" in US-ASCII.
+The way the header field bodies are encoded is sometimes referred to as a <b>RFC2047 string</b> or as an <b>encoded word</b>, which has
+the origin in the <a href="http://tools.ietf.org/html/rfc2047">RFC</a> where this encoding scheme is defined.
+RFC2047 strings are only allowed in some of the header fields, like \c Subject and in the display name
+of mailboxes in header fields like \c From and \c To. In other header fields, such as \c Date and
+\c MIME-Version, they are not allowed, but they wouldn't make much sense there anyway, since those are
+structured header fields with a clearly defined structure.
+
+RFC2047 strings start with "=?" and end with "?=". Between those markers, they consists of three parts:
+\li The charset, such as "iso-8859-1"
+\li The encoding, which is "q" or "b"
+\li The encoded text
+
+These three parts are sperated with a '?'. Encoding the third part, the text, is very similar to how
+text strings in the message body are encoded: First, the text string is encoded to a byte array using
+the charset encoding. Afterwards, the second encoding is used on the result, to ensure that all resulting
+bytes are within the 7-bit range.
+
+The <i>second encoding</i> here is almost identical to the content transfer encoding. There are two
+possible encodings, \b b and \b q. The \c b encoding is the same as the base64 encoding of the content
+transfer encoding. The \c q encoding is very similar to the quoted-printable encoding of the content
+transfer encoding, but with some little differences that are described in
+<a href="http://tools.ietf.org/html/rfc2047#section-4.2">the RFC</a>.
+
+Let's examine the subject of the message, <code>=?iso-8859-1?q?Gr=FCezi!?=</code>, in detail:<br>
+The first part of the RFC2027 string is the charset, so it is ISO-8859-1 in this case. The second part
+is the encoding, which is the \c q encoding here. The last part is the encoded text, which is
+<code>Gr=FCezi!</code>. As with the quoted-printable encoding, "=FC" is the encoding for the byte with
+the value <code>FC</code>, which in the ISO-8859-1 charset is the letter 'ü'. The complete decoded
+text is therefore "Grüezi!".
+
+Each RFC2047 string in the header can use a different charset: In this example, the \c Subject uses ISO-8859-1,
+\c To uses UTF-8 and the message body uses US-ASCII.
+
+In the \c To header field, two RFC2047 strings are used. A single, bigger, RFC2047 string for the whole
+display name could also have been used. In this case, the second RFC2047 string starts with an underscore,
+which is decoded as a space in the \c q encoding. The space between the two RFC2047 strings is ignored,
+it is just used to seperate the two encoded words.
+
+There are some restriction on RFC2047 strings: They are not allowed to be longer than 75 characters,
+which means two or more encoded words have to be used for long text strings. Also, there are some
+restrictions on where RFC2047 strings are allowed; most importantly, the address specification must no
+not be encoded, to be backwards compatible. For further details, refer to the RFC.
+
+\subsubsection multipart-mixed Messages with attachments
+
+Until now, we only looked at messages that had a single text part as the message body. In this section,
+we'll examine messages with attachments.
+
+\verbatim
+From: frank@domain.com
+To: greg@domain.com
+Subject: Nice Photo
+Date: Sun, 28 Feb 2010 19:57:00 +0100
+MIME-Version: 1.0
+Content-Type: Multipart/Mixed;
+ boundary="Boundary-00=_8xriL5W6LSj00Ly"
+
+--Boundary-00=_8xriL5W6LSj00Ly
+Content-Type: Text/Plain;
+ charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+
+Hi Greg,
+
+attached you'll find a nice photo.
+
+--Boundary-00=_8xriL5W6LSj00Ly
+Content-Type: image/jpeg;
+ name="test.jpeg"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment;
+ filename="test.jpeg"
+
+/9j/4AAQSkZJRgABAQAAAQABAAD/4Q3XRXhpZgAASUkqAAgAAAAHAAsAAgAPAAAAYgAAAAABBAAB
+[SNIP 800 lines]
+ze5CdSH2Z8yTatHSV2veW0rKzeq30//Z
+
+--Boundary-00=_8xriL5W6LSj00Ly--
+
+\endverbatim
+<i>Note: Since the image in this message would be really big, most of it is omitted / snipped here.</i>
+
+The above example consists of two parts: A normal text part and an image attachment. Messages that
+consist of multiple parts are called \b multipart messages. The top-level content-type therefore is
+<b>multipart/mixed</b>. \c Mixed simply means that the following parts have no relation to each other,
+it is just a random mixture of parts. Later, we will look at other types, such as \c multipart/alternative
+or \c multipart/related. A \b part is sometimes also called \b node, \b content or <b>MIME part</b>.
+
+Each MIME part of the message is seperated by a \b boundary, and that boundary
+is specified in the top-level content-type header as a parameter. In the message body, the boundary
+is prefixed with \c "--", and the last boundary is suffixed with \c "--", so that the end of the message can
+be detected. When creating a message, care must be taken that the boundary appears nowhere else in the
+message, for example in the text part, as the parser would get confused by this.
+
+A MIME part begins right after the boundary. It consists of a <b>MIME header</b> and a <b>MIME body</b>, which
+are seperated by an empty line. The MIME header should not be confused with the message header: The
+message header contains metadata about the whole message, like subject and date. The MIME header only
+contains metadata about the specific MIME part, like the content type of the MIME part. MIME header
+field names always start with \c "Content-".
+The example above shows the three most important MIME header fields, usually those are the only ones
+used. The top-level header of a message actually mixes the message metadata and the MIME metadata into one header: In this
+example, the header contains the \c Date header field, which is an ordinary header field, and it contains
+the \c Content-Type header field, which is a MIME header field.
+
+MIME parts can be nested, and therefore form a tree. The above example has the following tree:
+\verbatim
+multipart/mixed
+|- text/plain
+\- image/jpeg
+\endverbatim
+The \c text/plain node is therefore a \b child of the \c multipart/mixed mode. The \c multipart/mixed node
+is a \b parent of the other two nodes. The \c image/jpeg node is a \b sibling of the \c text/plain node.
+\c Multipart nodes are the only nodes that have children, other nodes are \b leaf nodes.
+The body of a multipart node consists of all complete child nodes (MIME header and MIME body), seperated
+by the boundary.
+
+Each MIME part can have a different content transfer encoding. In the above example, the text part has
+a \c 7bit CTE, while the image part has a \c base64 CTE. The multipart/mixed node does not specifiy
+a CTE, multipart nodes always have \c 7bit as the CTE. This is because the body of multipart nodes can
+only consist of bytes in the 7 bit range: The boundary is 7 bit, the MIME headers are 7 bit, and the
+MIME bodies are already ancoded with the CTE of the child MIME part, and are therefore also 7 bit. This means
+no CTE for multipart nodes is necessary.
+
+The MIME part for the image does not specify a charset parameter in the content type header field. This
+is because the body of that MIME part will not be interpreted as a text string, therefore the byte array
+does not need to be decoded to a string. Instead, the byte array is interpreted as an image, by an image
+renderer. The message viewer application passes the MIME part body as a byte array to the image renderer.
+The content type consists of a <b>media type</b> and a <b>subtype</b>. For example, the content type
+\c "text/html" has the media type "text" and the subtype "html". Only nodes that have the media type "text"
+need to specify a charset, as those nodes are the only nodes of which the body is interpreted as a text string.
+
+The only header field not yet encountered in previous sections is the \b Content-Disposition header field,
+which is defined in a <a href="http://tools.ietf.org/html/rfc2183">seperate RFC</a>. It describes how
+the message viewer application should display the MIME part. In the case of the image part, is should
+be presented as an attachment. The \b filename parameter tells the message viewer application which filename
+should be used by default when the user saves the attachment to disk.
+
+The content type header field for the image MIME part has a \b name parameter, which is similar to the
+\c filename parameter of the \c Content-Disposition header field. The difference is that \c name refers
+to the name of the complete MIME part, whereas \c filename refers to the name of the attachment. The
+\c name paramter of the \c Content-Type header field in this case is superfluous and only exists for
+backwards compatibility, and can be ignored;
+the \c filename parameter of the \c Content-Disposition header field should be prefered when it is present.
+
+\verbatim
+From: Thomas McGuire <thomas@domain.com>
+To: sebastian@domain.com
+Subject: Help with SPARQL
+Date: Sun, 28 Feb 2010 21:57:51 +0100
+MIME-Version: 1.0
+Content-Type: Multipart/Mixed;
+ boundary="Boundary-00=_PjtiLU2PvHpvp/R"
+
+--Boundary-00=_PjtiLU2PvHpvp/R
+Content-Type: Text/Plain;
+ charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+
+Hi Sebastian,
+
+I have a problem with a SPARQL query, can you help me debug this? Attached is
+the query and a screenshot showing the result.
+
+--Boundary-00=_PjtiLU2PvHpvp/R
+Content-Type: text/plain;
+ charset="UTF-8";
+ name="query.txt"
+Content-Transfer-Encoding: 7bit
+Content-Disposition: attachment;
+ filename="query.txt"
+
+prefix nco:<http://www.semanticdesktop.org/ontologies/2007/03/22/nco#>
+
+SELECT ?person
+WHERE {
+ ?person a nco:PersonContact .
+ ?person nco:birthDate ?birthDate .
+}"
+--Boundary-00=_PjtiLU2PvHpvp/R
+Content-Type: image/png;
+ name="screenshot.png"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment;
+ filename="screenshot.png"
+
+AAAAyAAAAAEBBAABAAAAyAAAAA0BAgATAAAAcQAAABIBAwABAAAAAQAAADEBAgAPAAAAhAAAAGmH
+[SNIP]
+YXJlLmpwZWcAZGlnaUthbS0w
+
+--Boundary-00=_PjtiLU2PvHpvp/R--
+\endverbatim
+The above example message consists of three MIME parts: The main text part and two attachments.
+One attachment has the media type \c text, therefore a charset parameter is necessary to correctly
+display it. The MIME tree looks like this:
+\verbatim
+multipart/mixed
+|- text/plain
+|- text/plain
+\- image/jpeg
+\endverbatim
+
+\subsubsection multipart-alternative HTML Messages
+
+\verbatim
+From: Thomas McGuire <thomas@domain.com>
+Subject: HTML test
+Date: Thu, 4 Mar 2010 13:59:18 +0100
+MIME-Version: 1.0
+Content-Type: multipart/alternative;
+ boundary="Boundary-01=_m66jLd2/vZrH5oe"
+Content-Transfer-Encoding: 7bit
+
+--Boundary-01=_m66jLd2/vZrH5oe
+Content-Type: text/plain;
+ charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+
+Hello World
+
+--Boundary-01=_m66jLd2/vZrH5oe
+Content-Type: text/html;
+ charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html>
+ <head></head>
+ <body>
+ Hello <b>World</b>
+ </body>
+</html>
+--Boundary-01=_m66jLd2/vZrH5oe--
+\endverbatim
+
+The above example is a simple HTML message, it consists of a plain text and a HTML part, which are
+in a \b multipart/alternative container. The message has the following structure:
+\verbatim
+multipart/alternative
+|- text/plain
+\- text/html
+\endverbatim
+The HTML part and the plain text part have the identical content, except that the HTML part contains
+additional markup, in this case for displaying the word \c World in bold. Since those parts are in a
+multipart/alternative container, the message viewer application can freely chose which part it displays.
+Some users might prefer reading the message in HTML format, some might prefer reading the message
+in plain text format.
+
+Of course, a HTML message could also consist only of a single \c text/html, without the multipart/alternative
+container and therefore without an alternative plain text part. However, people prefering the plain
+text version wouldn't like this, especially if their mail client has no HTML engine and they would see
+the HTML source including all tags only. Therefore, HTML messages should always include an alternative plain text part.
+
+HTML messages can of course also contain attachments. In this case, the message contains both a
+multipart/alternative and a multipart/mixed node, for example with the following structure, for a HTML
+message that has an image attachment:
+\verbatim
+multipart/mixed
+|- multipart/alternative
+| |- text/plain
+| \- text/html
+\- image/png
+\endverbatim
+
+The message itself would look like this:
+\verbatim
+From: Thomas McGuire <thomas@domain.com>
+Subject: HTML message with an attachment
+Date: Thu, 4 Mar 2010 15:20:26 +0100
+MIME-Version: 1.0
+Content-Type: Multipart/Mixed;
+ boundary="Boundary-00=_qG8jLwWCwkUfJV1"
+
+--Boundary-00=_qG8jLwWCwkUfJV1
+Content-Type: multipart/alternative;
+ boundary="Boundary-01=_qG8jLfs1FRmlOhl"
+Content-Transfer-Encoding: 7bit
+
+--Boundary-01=_qG8jLfs1FRmlOhl
+Content-Type: text/plain;
+ charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+
+Hello World
+
+--Boundary-01=_qG8jLfs1FRmlOhl
+Content-Type: text/html;
+ charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html>
+ <head></head>
+ <body>
+ Hello <b>World</b>
+ </body>
+</html>
+--Boundary-01=_qG8jLfs1FRmlOhl--
+
+--Boundary-00=_qG8jLwWCwkUfJV1
+Content-Type: image/png;
+ name="test.png"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment;
+ filename="test.png"
+
+iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAA8SAAAPEgEhm/IzAAAC
+[SNIP]
+eFkXsFgBMG4fJhYlx+iyB3cLpNZwYr/iP7teTwNYa7DZAAAAAElFTkSuQmCC
+
+--Boundary-00=_qG8jLwWCwkUfJV1--
+
+\endverbatim
+
+\subsubsection multipart-related HTML Messages with Inline Images
+
+HTML has support for showing images, with the \c img tag. Such an image is shown at the place where
+the \c img tag occurs, which is called an <b>inline image</b>. Note that inline images are different
+from images that are just normal attachments: Normal attachments are always shown at the beginning or
+at the end of the message, while inline images are shown in-place. In HTML, the \c img tag points to an
+image file that is either a file on disk or an URL to an image on the Internet. To make inline images
+work with MIME messages, a different mechanism is needed, since the image is not a file on disk or on
+the Internet, but a MIME part somewhere in the same message. As specified in
+<a href="http://tools.ietf.org/html/rfc2557">RFC 2557</a>, the way this can be done is by refering
+to a \b Content-ID in the \c img tag, and marking the MIME part that is the image with that content
+ID as well.
+
+An example will probably be more clear than this explaination:
+\verbatim
+From: Thomas McGuire <thomas@domain.com>
+Subject: Inine Image Test
+Date: Thu, 4 Mar 2010 16:54:53 +0100
+MIME-Version: 1.0
+Content-Type: multipart/related;
+ boundary="Boundary-02=_Nf9jLpJ2aGp5RQK"
+Content-Transfer-Encoding: 7bit
+
+--Boundary-02=_Nf9jLpJ2aGp5RQK
+Content-Type: multipart/alternative;
+ boundary="Boundary-01=_Nf9jLZ6aPhm3WrN"
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+--Boundary-01=_Nf9jLZ6aPhm3WrN
+Content-Type: text/plain;
+ charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+
+Text before image
+
+Text after image
+
+--Boundary-01=_Nf9jLZ6aPhm3WrN
+Content-Type: text/html;
+ charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html>
+ <head></head>
+ <body>
+ Text before image<br>
+ <img src="cid:547730348@KDE" /><br>
+ Text after image
+ </body>
+</html>
+--Boundary-01=_Nf9jLZ6aPhm3WrN--
+
+--Boundary-02=_Nf9jLpJ2aGp5RQK
+Content-Type: image/png;
+ name="test.png"
+Content-Transfer-Encoding: base64
+Content-Id: <547730348@KDE>
+
+iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAIAAAAiOjnJAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAg
+[SNIP]
+AABJRU5ErkJggg==
+--Boundary-02=_Nf9jLpJ2aGp5RQK--
+\endverbatim
+
+The first thing you'll notice in this example probably is that it has a \b multipart/related node with
+the following structure:
+\verbatim
+multipart/related
+|- multipart/alternative
+| |- text/plain
+| \- text/html
+\- image/png
+\endverbatim
+
+When the HTML part has inline image, the HTML part and its image part both have to be children of a
+multipart/related container, like in this example.
+In this case, the \c img tag has the source \c cid:547730348@KDE, which is a placeholder that refers
+to the Content-Id header of another part. The image part contains exactly that value in its \c Content-Id
+header, and therefore a message viewer application can connect both.
+
+The plain text part can not have inline images, therefore its text might seem a bit confusing.
+
+HTML messages with inline images can of course also have attachments, in which the message structure
+becomes a mix of multipart/related, multipart/alternative and multipart/mixed. The following example
+shows the structure of a message with two inline images and one \c .tar.gz attachment:
+\verbatim
+multipart/mixed
+|- multipart/related
+| |- multipart/alternative
+| | |- text/plain
+| | \- text/html
+| |- image/png
+| \- image/png
+\- application/x-compressed-tar
+\endverbatim
+
+The structure of MIME messages can get arbitrarily complex, the above is just one relativley simply example.
+The nesting of multipart nodes can get much deeper, there is no restriction on nesting levels.
+
+\subsubsection encapsulated Encapsulated messages
+
+Encapsulated messages are messages which are attachments to another message. The most common example
+is a forwareded mail, like in this example:
+\verbatim
+From: Frank <frank@domain.com>
+To: Bob <bob@domain.com>
+Subject: Fwd: Blub
+MIME-Version: 1.0
+Content-Type: Multipart/Mixed;
+ boundary="Boundary-00=_sX+jLVPkV1bLFdZ"
+
+--Boundary-00=_sX+jLVPkV1bLFdZ
+Content-Type: text/plain;
+ charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+
+Hi Bob,
+
+hereby I forward you an interesting message from Greg.
+
+--Boundary-00=_sX+jLVPkV1bLFdZ
+Content-Type: message/rfc822;
+ name="forwarded message"
+Content-Transfer-Encoding: 7bit
+Content-Description: Forwarded Message
+Content-Disposition: inline
+
+From: Greg <greg@domain.com>
+To: Frank <frank@domain.com>
+Subject: Blub
+MIME-Version: 1.0
+Content-Type: Text/Plain;
+ charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+
+Bla Bla Bla
+
+--Boundary-00=_sX+jLVPkV1bLFdZ--
+\endverbatim
+
+\verbatim
+multipart/mixed
+|- text/plain
+\- message/rfc822
+ \- text/plain
+\endverbatim
+
+The attached message is treated like any other attachment, and therefore the top-level content type
+is multipart/mixed.
+The most interesting part is the \c message/rfc822 MIME part. As usual, it has some MIME headers, like
+\c Content-Type or \c Content-Disposition, followed by the MIME body. The MIME body in this case is
+the attached message. Since it is a message, it consists of a header and a body itself.
+Therefore, the \c message/rfc822 MIME part appears to have two headers; in reality, it is the normal
+MIME header and the message header of the encapsulated message. The message header and the message body
+are both in the MIME body of the \c message/rfc822 MIME part.
+
+\subsubsection crypto Signed and Encryped Messages
+
+MIME messages can be cryptographically signed and/or encrypted. The format for those messages is
+defined in <a href="http://tools.ietf.org/html/rfc1847">RFC 1847</a>, which specifies two new
+multipart subtypes, \b multipart/signed and \b multipart/encrypted. The crypto format of these new
+security multiparts is defined in additional RFCs; the most common formats are
+<a href="http://tools.ietf.org/html/rfc3156">OpenPGP</a> and
+<a href="http://tools.ietf.org/html/rfc2633">S/MIME</a>. Both formats use the principle of
+<a href="http://en.wikipedia.org/wiki/Public-key_cryptography">public-key cryptography</a>. OpenPGP
+uses \b keys, and S/MIME uses \b certificates. For easier text flow, only the term \c key will be used
+for both keys and certificates in the text below.
+
+Security multiparts only sign or encrypt a specifc MIME part. The consequence is that the message headers
+can not be signed or encrypted. Also this means that it is possible to sign or encrypt only some of
+the MIME parts of a message, while leaving other MIME parts unsigned or unencrypted. Furthermore, it
+is possible to sign or encrypt different MIME parts with different crypto formats. As you can see,
+security multiparts are very flexible.
+
+Security multiparts are not supported by KMime. However, it is possible for applications to use KMime
+when providing support for crypto messages. For example, the
+<a href="http://api.kde.org/4.x-api/kdepim-apidocs/messageviewer/html/index.html">messageviewer</a>
+component in KDEPIM supports signed and encrypted MIME parts, and the
+<a href="http://websvn.kde.org/trunk/KDE/kdepim/messagecomposer/">messagecomposer</a> library can create
+such messages.
+
+Signed MIME parts are signed with the private key of the sender, everybody who has the
+public key of the sender can verifiy the signature. Encrypted MIME parts are encrypted with the public
+key of the receiver, and only the receiver, who is the sole person possessing the private key, can decrypt
+it. Sending an encrypted message to multiple recipients therefore means that the message has to be sent
+multiple times, once for each receiver, as each message needs to be encrypted with a different key.
+
+\par Signed MIME parts
+
+A multipart/signed MIME part has exactly two children: The first child is the content that is signed,
+and the second child is the signature.
+
+\verbatim
+From: Thomas McGuire <thomas@domain.com>
+Subject: My Subject
+Date: Mon, 15 Mar 2010 12:20:16 +0100
+MIME-Version: 1.0
+Content-Type: multipart/signed;
+ boundary="nextPart2567247.O5e8xBmMpa";
+ protocol="application/pgp-signature";
+ micalg=pgp-sha1
+Content-Transfer-Encoding: 7bit
+
+--nextPart2567247.O5e8xBmMpa
+Content-Type: Text/Plain;
+ charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+
+Simple message
+
+--nextPart2567247.O5e8xBmMpa
+Content-Type: application/pgp-signature; name=signature.asc
+Content-Description: This is a digitally signed message part.
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v2.0.14 (GNU/Linux)
+
+iEYEABECAAYFAkueF/UACgkQKglv3sO8a1MdTACgnBEP6ZUal931Vwu7PyiXT1bn
+Zr0Anj4bAI9JhHEDiwA/iwrWGfSC+Nlz
+=d2ol
+-----END PGP SIGNATURE-----
+--nextPart2567247.O5e8xBmMpa--
+\endverbatim
+
+\verbatim
+multipart/signed
+|- text/plain
+\- application/pgp-signature
+\endverbatim
+The example here uses the OpenPGP format to sign a simply plain text message. Here, the text/plain
+MIME part is signed, and the application/pgp-signature MIME part contains the signature data, which in
+this case is ASCII-armored.
+
+As said above, it is possible to sign only some MIME parts. A message which has a image/jpeg attachment
+that is signed, but a main text part is not signed, has the following MIME structure:
+\verbatim
+multipart/mixed
+|- text/plain
+\- multipart/signed
+ |- image/jpeg
+ \- application/pgp-signature
+\endverbatim
+
+It is possible to sign multipart parts as well. Consider the above example that has a plain text part
+and an image attachment. Those two parts can be signed together, with the following structure:
+\verbatim
+multipart/signed
+|- multipart/mixed
+| |- text/plain
+| \- image/jpeg
+\- application/pgp-signature
+\endverbatim
+
+Signed messages in the S/MIME format use a different content type for the signature data, like here:
+\verbatim
+multipart/signed
+|- text/plain
+\- application/x-pkcs7-signature
+\endverbatim
+
+\par Encrypted MIME parts
+
+Multipart/encrypted MIME parts also have exactly two children: The first child contains metadata about
+the encrypted data, such as a version number. The second child then contains the actual encrypted data.
+
+\verbatim
+From: someone@domain.com
+To: Thomas McGuire <thomas@domain.com>
+Subject: Encrypted message
+Date: Mon, 15 Mar 2010 12:50:16 +0100
+MIME-Version: 1.0
+Content-Type: multipart/encrypted;
+ boundary="nextPart2726747.j47xUGTWKg";
+ protocol="application/pgp-encrypted"
+Content-Transfer-Encoding: 7bit
+
+--nextPart2726747.j47xUGTWKg
+Content-Type: application/pgp-encrypted
+Content-Disposition: attachment
+
+Version: 1
+--nextPart2726747.j47xUGTWKg
+Content-Type: application/octet-stream
+Content-Disposition: inline; filename="msg.asc"
+
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v2.0.14 (GNU/Linux)
+
+hQIOA8p5rdC5CBNfEAf+NZVzVq48C1r5opOOiWV96+FUzIWuMQ6u8fzFgI7YVyCn
+[SNIP]
+=reNr
+--nextPart2726747.j47xUGTWKg--
+-----END PGP MESSAGE-----
+\endverbatim
+
+\verbatim
+multipart/encrypted
+|- application/pgp-encrypted
+\- application/octet-stream
+\endverbatim
+
+The encrypted data is contained in the \c application/octet-stream MIME part. Without decrypting
+the data, it is unknown what the original content type of the encrypted MIME data is! The encrypted
+data could be a simple text/plain MIME part, an image attachment, or a multipart part. The encrypted
+data contains both the MIME header and the MIME body of the original MIME part, as the header is needed
+to know the content type of the data. The data could as well by of content type multipart/signed, in
+which case the message would be both signed and encrypted.
+
+\par Inline cryto formats
+
+Although using the security multiparts \c multipart/signed and \c multipart/encrypted is the recommended
+standard, there are other possibilities to sign or encrypt a message. The most common methods are
+<b>Inline OpenPGP</b> and <b>S/MIME Opaque</b>.
+
+For inline OpenPGP messages, the crypto data is contained inlined in the actual MIME part. For example,
+a message with a signed text/plain part might look like this:
+\verbatim
+From: someone@domain.com
+To: someoneelse@domain.com
+Subject: Inline OpenPGP test
+MIME-Version: 1.0
+Content-Type: text/plain;
+ charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+Inline OpenPGP signed example.
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v2.0.14 (GNU/Linux)
+
+iEYEARECAAYFAkueJ2EACgkQKglv3sO8a1MS3QCfcsYnJG7uYQxzxz6J5cPF7lHz
+WIoAn3PjVPlWibu02dfdFObwd2eJ1jAW
+=p3uO
+-----END PGP SIGNATURE-----
+
+\endverbatim
+
+Encrypted inline OpenPGP works in a similar way. Opaque S/MIME messages are also similar: For signed
+MIME parts, both the signature and the signed data are contained in a single MIME part with a content
+type of \c application/pkcs7-mime.
+
+As security multiparts are preferred over inline OpenPGP and over opaque S/MIME, I won't go into more
+detail here.
+
+\subsubsection misc Miscellaneous Points about Messages
+
+\par Line Breaks
+
+Each line in a MIME message has to end with a \b CRLF, which is a carriage return followed by a
+newline, which is the escape sequence<code>\\r\\n</code>. CR and LF may not appear in other places in
+a MIME message. Special care needs to be taken with encoded linebreaks in binary data, and with
+distinguishing soft and hard line breaks when converting between different content transfer encodings.
+For more details, have a look at the RFCs.
+
+While the official format is to have a CRLF at the end of each line, KMime only expects a single LF
+for its in-memory storage. Therefore, when loading a message from disk or from a server into KMime, the CRLFs need
+to be converted to LFs first, for example with KMime::CRLFtoLF(). The opposite needs to be done when
+storing a KMime message somewhere.
+
+Lines should not be longer than 78 characters and may not be longer than 998 characters.
+
+\par Header Folding and CFWS
+
+Header fields can span multiple lines, which was already shown in some of the examples above where
+the parameters of the header field value were in the next line. The header field is said to be
+\b folded in this case. In general, header fields can be folded whenever whitespace (\b WS) occurs.
+
+Header field values can contain \b comments; these comments are semantically invisible and have no
+meaning. Comments are surrouned by parentheses.
+\verbatim
+Date: Thu, 13
+ Feb 1969 23:32 -0330 (Newfoundland Time)
+\endverbatim
+
+This example shows a folded header that also has a comment (<i>Newfoundland Time</i>). The date header is a structured header
+field, and therefore it has to obey to a defined syntax; however, adding comments and whitespace is
+allowed almost anywhere, and they are ignored when parsing the message. Comments and whitespace where
+folding is allowed is sometimes referred to as \b CFWS. Any occurence of CFWS is semantically regarded
+as a single space.
+
+\section string-broken-down The two in-memory representations of messages
+
+There are two representations of messages in memory. The first is called <b>string representation</b>
+and the other one is called <b>broken-down representation</b>.
+
+String representation is somehow misnamed,
+a better term would be <c>byte array representation</c>. The string representation is just a big array of
+bytes in memory, and those bytes make up the encoded mail. The string representation is what is stored
+on disk or what is received from an IMAP server, for example.
+
+With the broken-down representation, the mail is <i>broken down</i> into smaller structures. For example,
+instead of having a single byte array for all headers, the broken-down structure has a list of individual headers,
+and each header in that list is again broken down into a structure. While the string representation
+is just an array of 7 bit characters that might be encoded, the broken-down representations contain the
+decoded text strings.
+
+As an example, conside the byte array
+\verbatim
+"Hugo Maier" <hugo.maier@mailer.domain>
+\endverbatim
+
+Although this is just a bunch of 7 bit characters, a human immediatley recognizes the broken-down structure and
+sees that the display name is "Hugo Maier" and that the localpart of the email address is "hugo.maier".
+To illustrate, the broken-down structure could be stored in a structure like this:
+\verbatim
+struct Mailbox
+{
+ QString displayName;
+ QByteArray addressSpec;
+};
+\endverbatim
+The address spec actually could be broken down further into a localpart and a domain.
+The process of converting the string representation to a broken-down representation is called \b parsing, and
+the reverse is called \b assembling.
+Parsing a message is necessary when wanting to access or modify the broken-down structure. For example, when sending a mail,
+the address spec of a mailbox needs to be passed to the SMTP server, which means that the recipient headers need to
+be parsed in order to access that information. Another example is the message list in an mail application, where the
+broken-down structure of a mail is needed
+to display information like subject, sender and date in the list.
+On the other hand, assembling a message is for example done in the composer of a mail application, where the mail information
+is available in a broken-down form in the composer window, and is then assembled into a final MIME message that is then sent with SMTP.
+
+Parsing is often quite tricky, you should always use the methods from KMime instead of writing parsing
+routines yourself. Even the simple mailbox example above is in pratice difficult to parse, as many things like comments
+and escaped characters need to be taken into consideration.
+The same is true for assembling: In the above case, one could be tempted to assemble the mailbox by simply
+writting code like this:
+\verbatim
+QByteArray stringRepresentation = '"' + displayName + "\" <" + addressSpec + ">";
+\endverbatim
+However, just like with parsing, you shouldn't be doing assembling yourself. In the above case, for example,
+the display name might contain non-ASCII characters, and RFC2047 encoding would need to be applied. So use
+KMime for assembling in all cases.
+
+When parsing a message and assembling it afterwards, the result might not be the same as the original byte
+array. For example, comments in header fields are ignored during parsing and not stored in the broken-down
+structure, therefore the assembled message will also not contain comments.
+
+Messages in memory are usually stored in a broken-down structure so that it is easy to to access and
+manipulate the message. On disk and on servers, messages are stored in string representation.
+
+\section classes-overview Overview of KMime classes
+
+KMime has basically two sets of classes: Classes for headers and classes for MIME
+parts. A MIME part is represented by \c KMime::Content. A Content can be parsed from a string representation
+and also be assembled from the broken-down representation again. If parsed, it has a list of sub-contents (in case of multipart contents) and a
+list of headers. If the Content is not parsed, it stores the headers and the body in a byte array, which can be accessed
+with head() and body().
+There is also a class \c KMime::Message, which basically is a thin wrapper around Content for the top-level
+MIME part. Message also contains convenience methods to access the message headers.
+
+For headers, there is a class hierachy, with \c KMime::Headers::Base as the base class, and
+\c KMime::Headers::Generics::Structured and \c KMime::Headers::Generics::Unstructured in the next levels. Unstructured is
+for headers that don't have a defined structure, like Subject, whereas Structured headers have a
+specific structure, like Date. The header classes have methods to parse headers, like from7BitString(),
+and to assemble them, like as7BitString(). Once a header is parsed, the classes provide access to the
+broken-down structures, for example the Date header has a method dateTime().
+The parsing in from7BitString() is usually handled by a protected parse() function, which in turn call
+parsing functions for different types, like parseAddressList() or parseAddrSpec() from the \c KMime::HeaderParsing
+namespace.
+
+When modifing messages, the message is first parsed into a broken-down representation. This broken-down
+representation can then be accessed and modified with the appropriate functions. After changing the broken-down
+structure, it needs to be assembled again to get the modified string representation.
+
+KMime also comes with some codes for handling base64 and quoted-printable encoding, with \c KMime::Codec
+as the base class.
+
+\section rfcs RFCs
+
+\li <a href="http://tools.ietf.org/html/rfc5322">RFC 5322</a>: Internet Message Format
+\li <a href="http://tools.ietf.org/html/rfc5536">RFC 5536</a>: Netnews Article Format
+\li <a href="http://tools.ietf.org/html/rfc2045">RFC 2045</a>: Multipurpose Internet Mail Extensions (MIME), Part 1: Format of Internet Message Bodies
+\li <a href="http://tools.ietf.org/html/rfc2046">RFC 2046</a>: Multipurpose Internet Mail Extensions (MIME), Part 2: Media Types
+\li <a href="http://tools.ietf.org/html/rfc2047">RFC 2047</a>: Multipurpose Internet Mail Extensions (MIME), Part 3: Message Header Extensions for Non-ASCII Text
+\li <a href="http://tools.ietf.org/html/rfc2048">RFC 2048</a>: Multipurpose Internet Mail Extensions (MIME), Part 4: Registration Procedures
+\li <a href="http://tools.ietf.org/html/rfc2049">RFC 2049</a>: Multipurpose Internet Mail Extensions (MIME), Part 5: Conformance Criteria and Examples
+\li <a href="http://tools.ietf.org/html/rfc2231">RFC 2231</a>: MIME Parameter Value and Encoded Word Extensions: Character Sets, Languages, and Continuations
+\li <a href="http://tools.ietf.org/html/rfc2183">RFC 2183</a>: Communicating Presentation Information in Internet Message: The Content-Disposition Header Field
+\li <a href="http://tools.ietf.org/html/rfc2557">RFC 2557</a>: MIME Encapsulation of Aggregate Documents, such as HTML (MHTML)
+\li <a href="http://tools.ietf.org/html/rfc1847">RFC 1847</a>: Security Multiparts for MIME: Multipart/Signed and Multipart/Encrypted
+\li <a href="http://tools.ietf.org/html/rfc3851">RFC 3851</a>: S/MIME Version 3 Message Specification
+\li <a href="http://tools.ietf.org/html/rfc3156">RFC 3156</a>: MIME Security with OpenPGP
+\li <a href="http://tools.ietf.org/html/rfc2298">RFC 2298</a>: An Extensible Message Format for Message Disposition Notifications
+\li <a href="http://tools.ietf.org/html/rfc2646">RFC 2646</a>: The Text/Plain Format Parameter (not supported by KMime)
+
+\section links Further Reading
+\li <a href="http://en.wikipedia.org/wiki/MIME">Wikipedia article on MIME</a>\n
+\li <a href="http://www.joelonsoftware.com/articles/Unicode.html">The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)</a>
+\li <a href="http://www.cs.tut.fi/~jkorpela/chars.html">A tutorial on character code issues</a>
+\li <a href="http://www.motobit.com/util/base64-decoder-encoder.asp">Online Base64 encoder and decoder</a>
+\li <a href="http://www.motobit.com/util/quoted-printable-encoder.asp">Online quoted-printable encoder</a>
+\li <a href="http://www.motobit.com/util/quoted-printable-decoder.asp">Online quoted-printable decoder</a>
+\li <a href="http://www.motobit.com/util/charset-codepage-conversion.asp">Online charset converter</a>
+\li <a href="http://en.wikipedia.org/wiki/Public-key_cryptography">Wikipedia article on public-key cryptography</a>
+
+\authors
+
+The major authors of this library are:
+\li Christian Gebauer
+\li Volker Krause \<vkrause@kde.org\>
+\li Marc Mutz \<mutz@kde.org\>
+\li Christian Thurner \<cthurner@freepage.de\>
+\li Tom Albers \<tomalbers@kde.nl\>
+\li Thomas McGuire \<mcguire@kde.org\>
+
+This document was written by:\n
+\li Thomas McGuire \<mcguire@kde.org\>
+
+\maintainers
+\li Volker Krause \<vkrause@kde.org\>
+\li Marc Mutz \<mutz@kde.org\>
+
+\licenses
+\lgpl
+
+*/
+
+// DOXYGEN_PROJECTNAME=KMIME Library
diff --git a/kmime/Messages.sh b/kmime/Messages.sh
new file mode 100644
index 0000000..5734e6f
--- /dev/null
+++ b/kmime/Messages.sh
@@ -0,0 +1,2 @@
+#! /bin/sh
+$XGETTEXT *.cpp *.h -o $podir/libkmime.pot
diff --git a/kmime/TODO b/kmime/TODO
new file mode 100644
index 0000000..7d6151d
--- /dev/null
+++ b/kmime/TODO
@@ -0,0 +1,22 @@
+Things we still need to work on in the library
+==============================================
+
++ Finish apidox
+ : the code generation macros in kmime_headers.h are a problem for doxygen
+
++ D-pointers
+
++ Use KDateTime::toString() as much as possible in DateFormatter class.
+ If KDateTime ever supports fancy format, we should be able to replace
+ DateFormatter completely with KDateTime::toString().
+
++ do we still need our own KAutoDeleteHash in Qt4/KDE4?
+
++ Lots of methods we might want to de-inline
+
++ Remove extra namespaces. probably only KMime namespace needed.
+
++ There are also methods, eg. in kmime_header_parsing.h, which don't need to
+ be part of the public API
+
++ More tests and unit-tests
diff --git a/kmime/boolflags.cpp b/kmime/boolflags.cpp
new file mode 100644
index 0000000..95d6981
--- /dev/null
+++ b/kmime/boolflags.cpp
@@ -0,0 +1,77 @@
+/*
+ Copyright (c) 1999-2001 the KMime authors.
+ See file AUTHORS for details
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling MIME data and
+ defines the BoolFlags class.
+
+ @brief
+ Defines the BoolFlags class.
+
+ @author see AUTHORS file.
+*/
+
+#include "boolflags.h"
+
+using namespace KMime;
+
+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 ) {
+ mBits[n] = mBits[n] | p;
+ } else {
+ mBits[n] = mBits[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 ( ( mBits[n] & p ) > 0 );
+}
diff --git a/kmime/boolflags.h b/kmime/boolflags.h
new file mode 100644
index 0000000..fcd9cb3
--- /dev/null
+++ b/kmime/boolflags.h
@@ -0,0 +1,95 @@
+/*
+ Copyright (c) 1999-2001 the KMime authors.
+ See file AUTHORS for details
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the BoolFlags class.
+
+ @brief
+ Defines the BoolFlags class.
+*/
+
+#ifndef __KMIME_BOOLFLAGS_H__
+#define __KMIME_BOOLFLAGS_H__
+
+#include "kmime_export.h"
+
+namespace KMime {
+
+/**
+ @brief
+ Provides a class for storing boolean values in single bytes.
+
+ This class provides functionality similar to QBitArray but requires
+ much less memory. Only 16-bits (or 2-bytes) can be stored.
+*/
+// TODO: KDE5: BIC: Remove this class, it is unused.
+class KMIME_EXPORT BoolFlags {
+
+ public:
+ /**
+ Constructs an empty 2-byte flag storage.
+ */
+ BoolFlags() { clear(); }
+
+ /**
+ Destroys the flag storage.
+ */
+ ~BoolFlags() {}
+
+ /**
+ Sets bit number @p i to the value @p b.
+
+ @param i is the bit number. Valid values are 0 through 15.
+ Higher values will be silently ignored.
+ @param b is the value to set for bit @p i.
+ */
+ void set( unsigned int i, bool b=true );
+
+ /**
+ Get bit number @p i.
+
+ @param i is the bit number. Valid values are 0 through 15.
+ Higher values all return @c false.
+ @return Value of the single bit @p i.
+ Invalid bit numbers return @c false.
+ */
+ bool get( unsigned int i );
+
+ /**
+ Sets all bits to false.
+ */
+ void clear() { mBits[0]=0; mBits[1]=0; }
+
+ /**
+ Returns a pointer to the data structure used to store the bits.
+ */
+ unsigned char *data() { return mBits; }
+
+ private:
+ /**
+ Two bytes (at least) of storage for the bits.
+ */
+ unsigned char mBits[2]; //space for 16 flags
+};
+
+} //namespace KMime
+
+#endif // __KMIME_BOOLFLAGS_H__
diff --git a/kmime/config-kmime.h.cmake b/kmime/config-kmime.h.cmake
new file mode 100644
index 0000000..7b7ef6d
--- /dev/null
+++ b/kmime/config-kmime.h.cmake
@@ -0,0 +1,5 @@
+/* Define if you have a timezone variable */
+#cmakedefine HAVE_TIMEZONE 1
+
+/* Define if you have a tm_gmtoff member in struct tm */
+#cmakedefine HAVE_TM_GMTOFF 1
diff --git a/kmime/kautodeletehash.cpp b/kmime/kautodeletehash.cpp
new file mode 100644
index 0000000..44ecd30
--- /dev/null
+++ b/kmime/kautodeletehash.cpp
@@ -0,0 +1,22 @@
+/*
+ This file is part of libkdepim.
+
+ Copyright (c) 2005 Ingo Kloecker <kloecker@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kautodeletehash.h"
diff --git a/kmime/kautodeletehash.h b/kmime/kautodeletehash.h
new file mode 100644
index 0000000..67cb1dd
--- /dev/null
+++ b/kmime/kautodeletehash.h
@@ -0,0 +1,75 @@
+/*
+ This file is part of libkdepim.
+
+ Copyright (c) 2005 Ingo Kloecker <kloecker@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef __KMIME_KAUTODELETEHASH__
+#define __KMIME_KAUTODELETEHASH__
+
+#include <QtCore/QHash>
+
+namespace KMime {
+
+/**
+ * The KAutoDeleteHash class is a convenience QHash subclass that provides
+ * automatic deletion of the values in the destructor. Apart from this
+ * KAutoDeleteHash behaves exactly like QHash<Key, T *>.
+ *
+ * Since the automatic deletion is restricted to the destruction of the hash
+ * you have take care of the deletion of values you remove or replace yourself.
+ * To replace a value in the hash by another value use
+ * @code
+ * delete hash.take( key );
+ * hash.insert( key, value );
+ * @endcode
+ * and to remove a value from the hash use
+ * @code
+ * delete hash.take( key );
+ * @endcode
+ *
+ * @author Ingo Kl&ouml;cker \<kloecker@kde.org\>
+ */
+template <class Key, class T>
+class KAutoDeleteHash : public QHash<Key, T *>
+{
+public:
+ /**
+ * Constructs an empty hash.
+ */
+ KAutoDeleteHash() {}
+ /**
+ * Constructs a copy of @p other (which can be a QHash or a KAutoDeleteHash).
+ */
+ KAutoDeleteHash( const QHash<Key, T *> &other ) : QHash<Key, T *>( other ) {}
+
+ /**
+ * Destroys the hash and deletes all values. References to the values in the
+ * hash and all iterators of this hash become invalid.
+ */
+ ~KAutoDeleteHash() { while ( ! QHash<Key, T *>::isEmpty() ) {
+ T *value = *QHash<Key, T *>::begin();
+ this->erase( QHash<Key, T *>::begin() );
+ delete value;
+ }
+ }
+};
+
+} // namespace KMime
+
+#endif /* __KMIME_KAUTODELETEHASH__ */
diff --git a/kmime/kmime_charfreq.cpp b/kmime/kmime_charfreq.cpp
new file mode 100644
index 0000000..3ec6d05
--- /dev/null
+++ b/kmime/kmime_charfreq.cpp
@@ -0,0 +1,252 @@
+/*
+ kmime_charfreq.cpp
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+/**
+ @file
+ This file is part of the API for handling MIME data and
+ defines the CharFreq class.
+
+ @brief
+ Defines the CharFreq class.
+
+ @authors Marc Mutz \<mutz@kde.org\>
+*/
+
+#include "kmime_charfreq.h"
+
+using namespace KMime;
+
+/**
+ * Private class that helps to provide binary compatibility between releases.
+ * @internal
+ */
+//@cond PRIVATE
+//class KMime::CharFreq::Private
+//{
+// public:
+//};
+//@endcond
+
+CharFreq::CharFreq( const QByteArray &buf )
+ : mNUL( 0 ),
+ mCTL( 0 ),
+ mCR( 0 ), mLF( 0 ),
+ mCRLF( 0 ),
+ mPrintable( 0 ),
+ mEightBit( 0 ),
+ mTotal( 0 ),
+ mLineMin( 0xffffffff ),
+ mLineMax( 0 ),
+ mTrailingWS( false ),
+ mLeadingFrom( false )
+{
+ if ( !buf.isEmpty() ) {
+ count( buf.data(), buf.size() );
+ }
+}
+
+CharFreq::CharFreq( const char *buf, size_t len )
+ : mNUL( 0 ),
+ mCTL( 0 ),
+ mCR( 0 ), mLF( 0 ),
+ mCRLF( 0 ),
+ mPrintable( 0 ),
+ mEightBit( 0 ),
+ mTotal( 0 ),
+ mLineMin( 0xffffffff ),
+ mLineMax( 0 ),
+ mTrailingWS( false ),
+ mLeadingFrom( false )
+{
+ if ( buf && len > 0 ) {
+ count( buf, len );
+ }
+}
+
+//@cond PRIVATE
+static inline bool isWS( char ch )
+{
+ return ( ch == '\t' || ch == ' ' );
+}
+//@endcond
+
+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': ++mNUL; break;
+ case '\r': ++mCR; break;
+ case '\n': ++mLF;
+ if ( prevChar == '\r' ) {
+ --currentLineLength; ++mCRLF;
+ }
+ if ( currentLineLength >= mLineMax ) {
+ mLineMax = currentLineLength-1;
+ }
+ if ( currentLineLength <= mLineMin ) {
+ mLineMin = 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;
+ }
+ }
+ ++mPrintable;
+ break;
+ default:
+ {
+ uchar c = *it;
+ if ( c == '\t' || ( c >= ' ' && c <= '~' ) ) {
+ ++mPrintable;
+ } else if ( c == 127 || c < ' ' ) {
+ ++mCTL;
+ } else {
+ ++mEightBit;
+ }
+ }
+ }
+ prevPrevChar = prevChar;
+ prevChar = *it;
+ }
+
+ // consider the length of the last line
+ if ( currentLineLength >= mLineMax ) {
+ mLineMax = currentLineLength;
+ }
+ if ( currentLineLength <= mLineMin ) {
+ mLineMin = currentLineLength;
+ }
+
+ // check whether the last character is tab or space
+ if ( isWS( prevChar ) ) {
+ mTrailingWS = true;
+ }
+
+ mTotal = 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 ( mNUL ) { // must be binary
+ return Binary;
+ }
+
+ // doesn't contain NUL's:
+ if ( mEightBit ) {
+ if ( mLineMax > 988 ) {
+ return EightBitData; // not allowed in 8bit
+ }
+ if ( ( mLF != mCRLF && mCRLF > 0 ) || mCR != mCRLF || controlCodesRatio() > 0.2 ) {
+ return EightBitData;
+ }
+ return EightBitText;
+ }
+
+ // doesn't contain NUL's, nor 8bit chars:
+ if ( mLineMax > 988 ) {
+ return SevenBitData;
+ }
+ if ( ( mLF != mCRLF && mCRLF > 0 ) || mCR != mCRLF || 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 ( mTotal ) {
+ return float(mPrintable) / float(mTotal);
+ } else {
+ return 0;
+ }
+}
+
+float CharFreq::controlCodesRatio() const
+{
+ if ( mTotal ) {
+ return float(mCTL) / float(mTotal);
+ } else {
+ return 0;
+ }
+}
+
diff --git a/kmime/kmime_charfreq.h b/kmime/kmime_charfreq.h
new file mode 100644
index 0000000..46cfc81
--- /dev/null
+++ b/kmime/kmime_charfreq.h
@@ -0,0 +1,185 @@
+/* -*- c++ -*-
+ kmime_charfreq.h
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the CharFreq class.
+
+ @brief
+ Defines the CharFreq class.
+
+ @authors Marc Mutz \<mutz@kde.org\>
+
+ @glossary @anchor Eight-Bit @anchor eight-bit @b 8-bit:
+ Data that contains bytes with at least one value greater than 127, or at
+ least one NUL byte.
+
+ @glossary @anchor Eight-Bit-Binary @anchor eight-bit-binary @b 8-bit-binary:
+ Eight-bit data that contains a high percentage of non-ascii values,
+ or lines longer than 998 characters, or stray CRs, or NULs.
+
+ @glossary @anchor Eight-Bit-Text @anchor eight-bit-text @b 8-bit-text:
+ Eight-bit data that contains a high percentage of ascii values,
+ no lines longer than 998 characters, no NULs, and either only LFs or
+ only CRLFs.
+
+ @glossary @anchor Seven-Bit @anchor seven-bit @b 7-Bit:
+ Data that contains bytes with all values less than 128, and no NULs.
+
+ @glossary @anchor Seven-Bit-Binary @anchor seven-bit-binary @b 7-bit-binary:
+ Seven-bit data that contains a high percentage of non-ascii values,
+ or lines longer than 998 characters, or stray CRs.
+
+ @glossary @anchor Seven-Bit-Text @anchor seven-bit-text @b 7-bit-text:
+ Seven-bit data that contains a high percentage of ascii values,
+ no lines longer than 998 characters, and either only LFs, or only CRLFs.
+*/
+
+#ifndef __KMIME_CHARFREQ_H__
+#define __KMIME_CHARFREQ_H__
+
+#include <QtCore/QByteArray>
+#include "kmime_export.h"
+#undef None
+
+namespace KMime {
+
+/**
+ @brief
+ A class for performing basic data typing using frequency count heuristics.
+
+ This class performs character frequency counts on the provided data which
+ are used in heuristics to determine a basic data type. The data types are:
+
+ - @ref Eight-Bit-Binary
+ - @ref Eight-Bit-Text
+ - @ref Seven-Bit-Binary
+ - @ref Seven-Bit-Text
+*/
+class KMIME_EXPORT CharFreq
+{
+ public:
+ /**
+ Constructs a Character Frequency instance for a buffer @p buf of
+ QByteArray data.
+
+ @param buf is a QByteArray containing the data.
+ */
+ explicit CharFreq( const QByteArray &buf );
+
+ /**
+ Constructs a Character Frequency instance for a buffer @p buf of
+ chars of length @p len.
+
+ @param buf is a pointer to a character string containing the data.
+ @param len is the length of @p buf, in characters.
+ */
+ CharFreq( const char *buf, size_t len );
+
+ /**
+ The different types of data.
+ */
+ enum Type {
+ None = 0, /**< Unknown */
+ EightBitData, /**< 8bit binary */
+ Binary = EightBitData, /**< 8bit binary */
+ SevenBitData, /**< 7bit binary */
+ EightBitText, /**< 8bit text */
+ SevenBitText /**< 7bit text */
+ };
+
+ /**
+ Returns the data #Type as derived from the class heuristics.
+ */
+ Type type() const;
+
+ /**
+ Returns true if the data #Type is EightBitData; false otherwise.
+ */
+ bool isEightBitData() const;
+
+ /**
+ Returns true if the data #Type is EightBitText; false otherwise.
+ */
+ bool isEightBitText() const;
+
+ /**
+ Returns true if the data #Type is SevenBitData; false otherwise.
+ */
+ bool isSevenBitData() const;
+
+ /**
+ Returns true if the data #Type is SevenBitText; false otherwise.
+ */
+ bool isSevenBitText() const;
+
+ /**
+ Returns true if the data contains trailing whitespace. i.e.,
+ if any line ends with space (' ') or tab ('\\t').
+ */
+ bool hasTrailingWhitespace() const;
+
+ /**
+ Returns true if the data contains a line that starts with "From ".
+ */
+ bool hasLeadingFrom() const;
+
+ /**
+ Returns the percentage of printable characters in the data.
+ The result is undefined if the number of data characters is zero.
+ */
+ float printableRatio() const;
+
+ /**
+ Returns the percentage of control code characters (CTLs) in the data.
+ The result is undefined if the number of data characters is zero.
+ */
+ float controlCodesRatio() const;
+
+ private:
+ //@cond PRIVATE
+ uint mNUL; // count of NUL chars
+ uint mCTL; // count of CTLs (incl. DEL, excl. CR, LF, HT)
+ uint mCR; // count of CR chars
+ uint mLF; // count of LF chars
+ uint mCRLF; // count of LFs, preceded by CRs
+ uint mPrintable; // count of printable US-ASCII chars (SPC..~)
+ uint mEightBit; // count of other latin1 chars (those with 8th bit set)
+ uint mTotal; // count of all chars
+ uint mLineMin; // minimum line length
+ uint mLineMax; // maximum line length
+ bool mTrailingWS; // does the buffer contain trailing whitespace?
+ bool mLeadingFrom; // does the buffer contain lines starting with "From "?
+ //@endcond
+
+ /**
+ Performs the character frequency counts on the data.
+
+ @param buf is a pointer to a character string containing the data.
+ @param len is the length of @p buf, in characters.
+ */
+ void count( const char *buf, size_t len );
+};
+
+} // namespace KMime
+
+#endif /* __KMIME_CHARFREQ_H__ */
diff --git a/kmime/kmime_codec_base64.cpp b/kmime/kmime_codec_base64.cpp
new file mode 100644
index 0000000..edf666f
--- /dev/null
+++ b/kmime/kmime_codec_base64.cpp
@@ -0,0 +1,418 @@
+/* -*- c++ -*-
+ kmime_codec_base64.cpp
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the @ref Base64 and @ref RFC2047B @ref Codec classes.
+
+ @brief
+ Defines the Base64Codec and Rfc2047BEncodingCodec classes.
+
+ @authors Marc Mutz \<mutz@kde.org\>
+*/
+
+#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;
+
+//@cond PRIVATE
+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', '+', '/'
+};
+//@endcond
+
+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 )
+ {
+ Q_UNUSED( dcursor ); Q_UNUSED( 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
+ kWarning() << "Base64Decoder: unexpected padding"
+ "character in input stream";
+ }
+ 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 ) {
+ kWarning() << "Base64Decoder: Embedded padding character"
+ "encountered!";
+ 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/kmime/kmime_codec_base64.h b/kmime/kmime_codec_base64.h
new file mode 100644
index 0000000..7f9b251
--- /dev/null
+++ b/kmime/kmime_codec_base64.h
@@ -0,0 +1,194 @@
+/* -*- c++ -*-
+ kmime_codec_base64.h
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the @ref Base64 and @ref RFC2047B @ref Codec classes.
+
+ @brief
+ Defines the Base64Codec and Rfc2047BEncodingCodec classes.
+
+ @authors Marc Mutz \<mutz@kde.org\>
+
+ @glossary @anchor Base64 @anchor base64 @b base64:
+ a binary to text encoding scheme based on @ref RFC1421.
+
+ @glossary @anchor RFC1421 @anchor rfc1421 @b RFC @b 1421:
+ RFC that defines the <a href="http://tools.ietf.org/html/rfc1421">
+ Privacy Enhancement for Internet Electronic Mail: Part I:
+ Message Encryption and Authentication Procedures</a>.
+
+ @glossary @anchor RFC2045 @anchor rfc2045 @b RFC @b 2045:
+ RFC that defines the <a href="http://tools.ietf.org/html/rfc2045">
+ MIME Part One: Format of Internet Message Bodies</a>.
+
+ @glossary @anchor RFC2047 @anchor rfc2047 @b RFC @b 2047:
+ RFC that defines the <a href="http://tools.ietf.org/html/rfc2047">
+ MIME Part Three: Message Header Extensions for Non-ASCII Text</a>.
+
+ @glossary @anchor RFC2047B @anchor rfc2047b @b RFC @b 2047B:
+ Section 4.1 of @ref RFC2047.
+*/
+
+#ifndef __KMIME_CODEC_BASE64__
+#define __KMIME_CODEC_BASE64__
+
+#include "kmime_codecs.h"
+
+namespace KMime {
+
+/**
+ @brief
+ A class representing the @ref codec for @ref Base64 as specified in
+ @ref RFC2045
+*/
+class KMIME_EXPORT Base64Codec : public Codec
+{
+ protected:
+ friend class Codec;
+ /**
+ Constructs a Base64 codec.
+ */
+ Base64Codec() : Codec() {}
+
+ public:
+ /**
+ Destroys the codec.
+ */
+ virtual ~Base64Codec() {}
+
+ /**
+ @copydoc
+ Codec::name()
+ */
+ const char *name() const
+ { return "base64"; }
+
+ /**
+ @copydoc
+ Codec::maxEncodedSizeFor()
+ */
+ 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;
+ }
+
+ /**
+ @copydoc
+ Codec::maxDecodedSizeFor()
+ */
+ 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;
+ }
+
+ /**
+ @copydoc
+ Codec::makeEncoder()
+ */
+ Encoder *makeEncoder( bool withCRLF=false ) const;
+
+ /**
+ @copydoc
+ Codec::makeDecoder()
+ */
+ Decoder *makeDecoder( bool withCRLF=false ) const;
+};
+
+/**
+ @brief
+ A class representing the @ref codec for the B encoding as specified
+ in @ref RFC2047B.
+*/
+class KMIME_EXPORT Rfc2047BEncodingCodec : public Base64Codec
+{
+ protected:
+ friend class Codec;
+ /**
+ Constructs a RFC2047B codec.
+ */
+ Rfc2047BEncodingCodec() : Base64Codec() {}
+
+ public:
+ /**
+ Destroys the codec.
+ */
+ virtual ~Rfc2047BEncodingCodec() {}
+
+ /**
+ @copydoc
+ Codec::name()
+ */
+ const char *name() const
+ { return "b"; }
+
+ /**
+ @copydoc
+ Codec::maxEncodedSizeFor()
+ */
+ int maxEncodedSizeFor( int insize, bool withCRLF=false ) const
+ {
+ Q_UNUSED( withCRLF );
+ // Each (begun) 3-octet triple becomes a 4 char quartet, so:
+ return ( ( insize + 2 ) / 3 ) * 4;
+ }
+
+ /**
+ @copydoc
+ Codec::maxDecodedSizeFor()
+ */
+ int maxDecodedSizeFor( int insize, bool withCRLF=false ) const
+ {
+ Q_UNUSED( withCRLF );
+ // Each 4-char quartet becomes a 3-octet triple, the last one
+ // possibly even less. So:
+ return ( ( insize + 3 ) / 4 ) * 3;
+ }
+
+ /**
+ @copydoc
+ Codec::makeEncoder()
+ */
+ Encoder *makeEncoder( bool withCRLF=false ) const;
+};
+
+} // namespace KMime
+
+#endif // __KMIME_CODEC_BASE64__
diff --git a/kmime/kmime_codec_identity.cpp b/kmime/kmime_codec_identity.cpp
new file mode 100644
index 0000000..6713ae9
--- /dev/null
+++ b/kmime/kmime_codec_identity.cpp
@@ -0,0 +1,109 @@
+/* -*- c++ -*-
+ kmime_codec_identity.cpp
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the Identity, @ref seven-bit-text, @ref eight-bit-text,
+ and @ref eight-bit-binary @ref Codec classes.
+
+ @brief
+ Defines the classes IdentityCodec, SevenBitCodec, EightBitCodec,
+ and BinaryCodec.
+
+ @authors Marc Mutz \<mutz@kde.org\>
+*/
+
+#include "kmime_codec_identity.h"
+
+#include <kdebug.h>
+
+#include <QtCore/QByteArray>
+
+#include <cassert>
+#include <cstring>
+
+using namespace KMime;
+
+namespace KMime {
+
+class IdentityEnDecoder : public Encoder, public Decoder
+{
+ protected:
+ friend class IdentityCodec;
+ IdentityEnDecoder( bool withCRLF ): Encoder( false )
+ {
+ kWarning( withCRLF ) << "IdentityEnDecoder: withCRLF isn't yet supported!";
+ }
+
+ 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 )
+ { Q_UNUSED( dcursor ); Q_UNUSED( 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 = qMin( 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
+{
+ kWarning( withCRLF ) << "IdentityCodec::encode(): withCRLF not yet supported!";
+ return src;
+}
+
+QByteArray IdentityCodec::decode( const QByteArray &src, bool withCRLF ) const
+{
+ kWarning( withCRLF ) << "IdentityCodec::decode(): withCRLF not yet supported!";
+ return src;
+}
+
+} // namespace KMime
diff --git a/kmime/kmime_codec_identity.h b/kmime/kmime_codec_identity.h
new file mode 100644
index 0000000..65f137e
--- /dev/null
+++ b/kmime/kmime_codec_identity.h
@@ -0,0 +1,214 @@
+/* -*- c++ -*-
+ kmime_codec_identity.h
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the Identity, @ref seven-bit-text, @ref eight-bit-text,
+ and @ref eight-bit-binary @ref Codec classes.
+
+ @brief
+ Defines the classes IdentityCodec, SevenBitCodec, EightBitCodec,
+ and BinaryCodec.
+
+ @authors Marc Mutz \<mutz@kde.org\>
+*/
+
+#ifndef __KMIME_CODEC_IDENTITY_H__
+#define __KMIME_CODEC_IDENTITY_H__
+
+#include "kmime_codecs.h"
+
+class QByteArray;
+
+namespace KMime {
+
+/**
+ @brief
+ A class representing the Identify @ref codec.
+*/
+class KMIME_EXPORT IdentityCodec : public Codec
+{
+ protected:
+ friend class Codec;
+ /**
+ Constructs the Identity codec.
+ */
+ IdentityCodec() : Codec() {}
+
+ public:
+ /**
+ Destroys the codec.
+ */
+ ~IdentityCodec() {}
+
+ using Codec::encode;
+ using Codec::decode;
+
+ /**
+ @copydoc
+ QByteArray Codec::encode()
+ */
+ QByteArray encode( const QByteArray &src, bool withCRLF=false ) const;
+
+ /**
+ @copydoc
+ QByteArray Codec::decode()
+ */
+ QByteArray decode( const QByteArray &src, bool withCRLF=false ) const;
+
+ /**
+ @copydoc
+ Codec::maxEncodedSizeFor()
+ */
+ int maxEncodedSizeFor( int insize, bool withCRLF ) const
+ {
+ if ( withCRLF ) {
+ return 2 * insize;
+ } else {
+ return insize;
+ }
+ }
+
+ /**
+ @copydoc
+ Codec::maxDecodedSizeFor()
+ */
+ int maxDecodedSizeFor( int insize, bool withCRLF ) const
+ {
+ if ( withCRLF ) {
+ return 2 * insize;
+ } else {
+ return insize;
+ }
+ }
+
+ /**
+ @copydoc
+ Codec::makeEncoder()
+ */
+ Encoder *makeEncoder( bool withCRLF=false ) const;
+
+ /**
+ @copydoc
+ Codec::makeDecoder()
+ */
+ Decoder *makeDecoder( bool withCRLF=false ) const;
+};
+
+/**
+ @brief
+ A class representing the @ref codec for @ref seven-bit-text.
+*/
+class KMIME_EXPORT SevenBitCodec : public IdentityCodec
+{
+ protected:
+ friend class Codec;
+ /**
+ Constructs the 7-bit codec.
+ */
+ SevenBitCodec() : IdentityCodec() {}
+
+ public:
+ /**
+ Destroys the codec.
+ */
+ ~SevenBitCodec() {}
+
+ /**
+ @copydoc
+ Codec::name()
+ */
+ const char *name() const
+ { return "7bit"; }
+};
+
+/**
+ @brief
+ A class representing the @ref codec for @ref eight-bit-text.
+*/
+class KMIME_EXPORT EightBitCodec : public IdentityCodec
+{
+ protected:
+ friend class Codec;
+ /**
+ Constructs the 8-bit codec.
+ */
+ EightBitCodec() : IdentityCodec() {}
+
+ public:
+ /**
+ Destroys the codec.
+ */
+ ~EightBitCodec() {}
+
+ /**
+ @copydoc
+ Codec::name()
+ */
+ const char *name() const
+ { return "8bit"; }
+};
+
+/**
+ @brief
+ A class representing the @ref codec for @ref eight-bit-binary.
+*/
+class KMIME_EXPORT BinaryCodec : public IdentityCodec
+{
+ protected:
+ friend class Codec;
+ /**
+ Constructs the 8-bit-binary codec.
+ */
+ BinaryCodec() : IdentityCodec() {}
+
+ public:
+ /**
+ Destroys the codec.
+ */
+ ~BinaryCodec() {}
+
+ /**
+ @copydoc
+ Codec::name()
+ */
+ const char *name() const
+ { return "binary"; }
+
+ /**
+ @copydoc
+ Codec::maxEncodedSizeFor()
+ */
+ int maxEncodedSizeFor( int insize, bool withCRLF=false ) const
+ { Q_UNUSED( withCRLF ); return insize; }
+
+ /**
+ @copydoc
+ Codec::maxDecodedSizeFor()
+ */
+ int maxDecodedSizeFor( int insize, bool withCRLF=false ) const
+ { Q_UNUSED( withCRLF ); return insize; }
+};
+
+} // namespace KMime
+
+#endif // __KMIME_CODEC_IDENTITY_H__
diff --git a/kmime/kmime_codec_qp.cpp b/kmime/kmime_codec_qp.cpp
new file mode 100644
index 0000000..9736257
--- /dev/null
+++ b/kmime/kmime_codec_qp.cpp
@@ -0,0 +1,740 @@
+/* -*- c++ -*-
+ kmime_codec_qp.cpp
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the @ref QuotedPrintable, @ref RFC2047Q, and
+ @ref RFC2231 @ref Codec classes.
+
+ @brief
+ Defines the classes QuotedPrintableCodec, Rfc2047QEncodingCodec, and
+ Rfc2231EncodingCodec.
+
+ @authors Marc Mutz \<mutz@kde.org\>
+*/
+
+#include "kmime_codec_qp.h"
+#include "kmime_util.h"
+
+#include <kdebug.h>
+
+#include <cassert>
+
+using namespace KMime;
+
+namespace KMime {
+
+// some helpful functions:
+
+/**
+ Converts a 4-bit @p value into its hexadecimal characater representation.
+ So input of value [0,15] returns ['0','1',... 'F']. Input values
+ greater than 15 will produce undesired results.
+ @param value is an unsigned character containing the 4-bit input value.
+*/
+static inline char binToHex( uchar value )
+{
+ if ( value > 9 ) {
+ return value + 'A' - 10;
+ } else {
+ return value + '0';
+ }
+}
+
+/**
+ Returns the high-order 4 bits of an 8-bit value in another 8-bit value.
+ @param ch is an unsigned character containing the 8-bit input value.
+*/
+static inline uchar highNibble( uchar ch )
+{
+ return ch >> 4;
+}
+
+/**
+ Returns the low-order 4 bits of an 8-bit value in another 8-bit value.
+ @param ch is an unsigned character containing the 8-bit input value.
+*/
+static inline uchar lowNibble( uchar ch )
+{
+ return ch & 0xF;
+}
+
+/**
+ Returns true if the specified value is a not Control character or
+ question mark; else true.
+ @param ch is an unsigned character containing the 8-bit input value.
+*/
+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;
+ /** @p mLastChar holds the first char of an encoded char, so that
+ we are able to keep the first char if the second char is invalid. */
+ char mLastChar;
+ 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 ),
+ mLastChar( 0 ) {}
+ public:
+ virtual ~QuotedPrintableDecoder() {}
+
+ bool decode( const char* &scursor, const char * const send,
+ char* &dcursor, const char * const dend );
+ bool finish( char* & dcursor, const char * const dend );
+};
+
+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 ) {
+ kWarning() << "CRLF output for decoders isn't yet supported!";
+ }
+
+ 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++ = mLastChar;
+ mHaveAccu = false;
+ mAccu = 0;
+ } else {
+ // output mBadChar
+ assert( mAccu == 0 );
+ if ( mBadChar ) {
+ if ( mBadChar == '=' ) {
+ mInsideHexChar = true;
+ } else {
+ *dcursor++ = mBadChar;
+ }
+ mBadChar = 0;
+ }
+ mFlushing = false;
+ }
+ continue;
+ }
+ assert( mBadChar == 0 );
+
+ uchar ch = *scursor++;
+ uchar value = 255;
+
+ if ( mExpectLF && ch != '\n' ) {
+ kWarning() << "QuotedPrintableDecoder:"
+ "illegally formed soft linebreak or lonely CR!";
+ 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:
+ kWarning() << "QuotedPrintableDecoder:"
+ "illegally formed hex char! Outputting verbatim.";
+ 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;
+ mLastChar = ch;
+ }
+ } 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 {
+ //kWarning() << "QuotedPrintableDecoder:" << ch <<
+ // "illegal character in input stream!";
+ *dcursor++ = char( ch );
+ }
+ }
+ }
+
+ return scursor == send;
+}
+
+bool QuotedPrintableDecoder::finish( char* &dcursor, const char * const dend )
+{
+ while ( ( mInsideHexChar || mHaveAccu || mFlushing ) && dcursor != dend ) {
+ // we have to flush chars
+ if ( mInsideHexChar ) {
+ // output '='
+ *dcursor++ = mEscapeChar;
+ mInsideHexChar = false;
+ }
+ else if ( mHaveAccu ) {
+ // output the high nibble of the accumulator:
+ *dcursor++ = mLastChar;
+ mHaveAccu = false;
+ mAccu = 0;
+ } else {
+ // output mBadChar
+ assert( mAccu == 0 );
+ if ( mBadChar ) {
+ *dcursor++ = mBadChar;
+ mBadChar = 0;
+ }
+ mFlushing = false;
+ }
+ }
+
+ // return false if we are not finished yet; note that mInsideHexChar is always false
+ return !( mHaveAccu || mFlushing );
+}
+
+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 = 0;
+ 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 <QtCore/QString>
+
+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 = 0;
+ 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/kmime/kmime_codec_qp.h b/kmime/kmime_codec_qp.h
new file mode 100644
index 0000000..b1109d8
--- /dev/null
+++ b/kmime/kmime_codec_qp.h
@@ -0,0 +1,228 @@
+/* -*- c++ -*-
+ kmime_codec_qp.h
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the @ref QuotedPrintable, @ref RFC2047Q, and
+ @ref RFC2231 @ref Codec classes.
+
+ @brief
+ Defines the classes QuotedPrintableCodec, Rfc2047QEncodingCodec, and
+ Rfc2231EncodingCodec.
+
+ @authors Marc Mutz \<mutz@kde.org\>
+
+ @glossary @anchor QuotedPrintable @anchor quotedprintable @b quoted-printable:
+ a binary to text encoding scheme based on Section 6.7 of @ref RFC2045.
+
+ @glossary @anchor RFC2047Q @anchor rfc2047q @b RFC @b 2047Q:
+ Section 4.2 of @ref RFC2047.
+
+ @glossary @anchor RFC2231 @anchor rfc2231 @b RFC @b 2231:
+ RFC that defines the <a href="http://tools.ietf.org/html/rfc2231">
+ MIME Parameter Value and Encoded Word Extensions: Character Sets, Languages,
+ and Continuations</a>.
+*/
+
+#ifndef __KMIME_CODEC_QP__
+#define __KMIME_CODEC_QP__
+
+#include "kmime_codecs.h"
+
+namespace KMime {
+
+/**
+ @brief
+ A class representing the @ref codec for @ref QuotedPrintable as specified in
+ @ref RFC2045 (section 6.7).
+*/
+class KMIME_EXPORT QuotedPrintableCodec : public Codec
+{
+ protected:
+ friend class Codec;
+ /**
+ Constructs a QuotedPrintable codec.
+ */
+ QuotedPrintableCodec() : Codec() {}
+
+ public:
+ /**
+ Destroys the codec.
+ */
+ virtual ~QuotedPrintableCodec() {}
+
+ /**
+ @copydoc
+ Codec::name()
+ */
+ const char *name() const
+ { return "quoted-printable"; }
+
+ /**
+ @copydoc
+ Codec::maxEncodedSizeFor()
+ */
+ 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;
+ }
+
+ /**
+ @copydoc
+ Codec::maxDecodedSizeFor()
+ */
+ int maxDecodedSizeFor( int insize, bool withCRLF=false ) const;
+
+ /**
+ @copydoc
+ Codec::makeEncoder()
+ */
+ Encoder *makeEncoder( bool withCRLF=false ) const;
+
+ /**
+ @copydoc
+ Codec::makeDecoder()
+ */
+ Decoder *makeDecoder( bool withCRLF=false ) const;
+};
+
+/**
+ @brief
+ A class representing the @ref codec for the Q encoding as specified
+ in @ref RFC2047Q.
+*/
+class KMIME_EXPORT Rfc2047QEncodingCodec : public Codec
+{
+ protected:
+ friend class Codec;
+ /**
+ Constructs a RFC2047Q codec.
+ */
+ Rfc2047QEncodingCodec() : Codec() {}
+
+ public:
+ /**
+ Destroys the codec.
+ */
+ virtual ~Rfc2047QEncodingCodec() {}
+
+ /**
+ @copydoc
+ Codec::name()
+ */
+ const char *name() const
+ { return "q"; }
+
+ /**
+ @copydoc
+ Codec::maxEncodedSizeFor()
+ */
+ int maxEncodedSizeFor( int insize, bool withCRLF=false ) const
+ {
+ Q_UNUSED( withCRLF );
+ // this one is simple: We don't do linebreaking, so all that can
+ // happen is that every char needs encoding, so:
+ return 3 * insize;
+ }
+
+ /**
+ @copydoc
+ Codec::maxDecodedSizeFor()
+ */
+ int maxDecodedSizeFor( int insize, bool withCRLF=false ) const;
+
+ /**
+ @copydoc
+ Codec::makeEncoder()
+ */
+ Encoder *makeEncoder( bool withCRLF=false ) const;
+
+ /**
+ @copydoc
+ Codec::makeDecoder()
+ */
+ Decoder *makeDecoder( bool withCRLF=false ) const;
+};
+
+/**
+ @brief
+ A class representing the @ref codec for @ref RFC2231.
+*/
+class KMIME_EXPORT Rfc2231EncodingCodec : public Codec
+{
+ protected:
+ friend class Codec;
+ /**
+ Constructs a RFC2231 codec.
+ */
+ Rfc2231EncodingCodec() : Codec() {}
+
+ public:
+ /**
+ Destroys the codec.
+ */
+ virtual ~Rfc2231EncodingCodec() {}
+
+ /**
+ @copydoc
+ Codec::name()
+ */
+ const char *name() const
+ { return "x-kmime-rfc2231"; }
+
+ /**
+ @copydoc
+ Codec::maxEncodedSizeFor()
+ */
+ int maxEncodedSizeFor( int insize, bool withCRLF=false ) const
+ {
+ Q_UNUSED( withCRLF );
+ // same as for "q" encoding:
+ return 3 * insize;
+ }
+
+ /**
+ @copydoc
+ Codec::maxDecodedSizeFor()
+ */
+ int maxDecodedSizeFor( int insize, bool withCRLF=false ) const;
+
+ /**
+ @copydoc
+ Codec::makeEncoder()
+ */
+ Encoder *makeEncoder( bool withCRLF=false ) const;
+
+ /**
+ @copydoc
+ Codec::makeDecoder()
+ */
+ Decoder *makeDecoder( bool withCRLF=false ) const;
+};
+
+} // namespace KMime
+
+#endif // __KMIME_CODEC_QP__
diff --git a/kmime/kmime_codec_uuencode.cpp b/kmime/kmime_codec_uuencode.cpp
new file mode 100644
index 0000000..0b84610
--- /dev/null
+++ b/kmime/kmime_codec_uuencode.cpp
@@ -0,0 +1,244 @@
+/* -*- c++ -*-
+ kmime_codec_uuencode.cpp
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines a @ref uuencode @ref Codec class.
+
+ @brief
+ Defines the UUCodec class.
+
+ @authors Marc Mutz \<mutz@kde.org\>
+*/
+
+#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 )
+ { Q_UNUSED( dcursor ); Q_UNUSED( 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 {
+ kWarning() << "UUDecoder: garbage before \"begin\", resetting parser";
+ 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 );
+ } else if ( mSawEnd ) {
+ // or if we are past the end line:
+ 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 {
+ kWarning() << "UUDecoder: invalid line octet count looks like \"end\" (mIntoEndLine ="
+ << mIntoEndLine << ")!";
+ 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:
+ kWarning( mCurrentOctetCount == mAnnouncedOctetCount + 1 )
+ << "UUDecoder: mismatch between announced ("
+ << mAnnouncedOctetCount << ") and actual line octet count!";
+
+ }
+
+ // return false when caller should call us again:
+ return scursor == send;
+} // UUDecoder::decode()
+
+} // namespace KMime
diff --git a/kmime/kmime_codec_uuencode.h b/kmime/kmime_codec_uuencode.h
new file mode 100644
index 0000000..2586f91
--- /dev/null
+++ b/kmime/kmime_codec_uuencode.h
@@ -0,0 +1,114 @@
+/* -*- c++ -*-
+ kmime_codec_uuencode.h
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines a @ref uuencode @ref Codec class.
+
+ @brief
+ Defines the UUCodec class.
+
+ @authors Marc Mutz \<mutz@kde.org\>
+
+ @glossary @anchor UUEncode @anchor uuencode @b uuencode:
+ a binary to text encoding scheme. For more information, see the
+ <a href="http://en.wikipedia.org/wiki/Uuencode"> Wikipedia Uuencode page</a>.
+*/
+
+#ifndef __KMIME_CODEC_UUENCODE_H__
+#define __KMIME_CODEC_UUENCODE_H__
+
+#include "kmime_codecs.h"
+
+namespace KMime {
+
+/**
+ @brief
+ A class representing the @ref UUEncode @ref codec.
+*/
+class KMIME_EXPORT UUCodec : public Codec
+{
+ protected:
+ friend class Codec;
+ /**
+ Constructs a UUEncode codec.
+ */
+ UUCodec() : Codec() {}
+
+ public:
+ /**
+ Destroys the codec.
+ */
+ virtual ~UUCodec() {}
+
+ /**
+ @copydoc
+ Codec::name()
+ */
+ const char *name() const
+ { return "x-uuencode"; }
+
+ /**
+ @copydoc
+ Codec::maxEncodedSizeFor()
+ */
+ int maxEncodedSizeFor( int insize, bool withCRLF=false ) const
+ {
+ (void)withCRLF;
+ return insize; // we have no encoder!
+ }
+
+ /**
+ @copydoc
+ Codec::maxDecodedSizeFor()
+ */
+ 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;
+ }
+
+ /**
+ @copydoc
+ Codec::makeEncoder()
+ */
+ Encoder *makeEncoder( bool withCRLF=false ) const;
+
+ /**
+ @copydoc
+ Codec::makeEncoder()
+ */
+ Decoder *makeDecoder( bool withCRLF=false ) const;
+};
+
+} // namespace KMime
+
+#endif // __KMIME_CODEC_UUENCODE_H__
diff --git a/kmime/kmime_codecs.cpp b/kmime/kmime_codecs.cpp
new file mode 100644
index 0000000..449186a
--- /dev/null
+++ b/kmime/kmime_codecs.cpp
@@ -0,0 +1,233 @@
+/* -*- c++ -*-
+
+ KMime, the KDE Internet mail/usenet news message library.
+
+ Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling MIME data and
+ defines the Codec class.
+
+ @brief
+ Defines the Codec class.
+
+ @authors Marc Mutz \<mutz@kde.org\>
+*/
+
+#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 "kautodeletehash.h"
+
+#include <kascii.h>
+#include <kdebug.h>
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QMutex>
+
+#include <cassert>
+#include <cstring>
+#include <string.h>
+
+using namespace KMime;
+
+namespace KMime {
+
+// global list of KMime::Codec's
+//@cond PRIVATE
+KAutoDeleteHash<QByteArray, Codec> *Codec::all = 0;
+Q_GLOBAL_STATIC(QMutex, dictLock)
+//@endcond
+
+void Codec::cleanupCodec()
+{
+ delete all;
+ all = 0;
+}
+
+void Codec::fillDictionary()
+{
+ //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 )
+{
+ const QByteArray ba( name );
+ return codecForName( ba );
+}
+
+Codec *Codec::codecForName( const QByteArray &name )
+{
+ dictLock()->lock(); // protect "all"
+ if ( !all ) {
+ all = new KAutoDeleteHash<QByteArray, Codec>();
+ qAddPostRoutine(cleanupCodec);
+ fillDictionary();
+ }
+ QByteArray lowerName = name;
+ kAsciiToLower( lowerName.data() );
+ Codec *codec = (*all)[ lowerName ]; // FIXME: operator[] adds an entry into the hash
+ dictLock()->unlock();
+
+ if ( !codec ) {
+ kDebug() << "Unknown codec \"" << name << "\" requested!";
+ }
+
+ return codec;
+}
+
+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;
+ result.resize( 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 ) ) {
+ kFatal() << name() << "codec lies about it's mEncodedSizeFor()";
+ }
+
+ // 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;
+ result.resize( 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 ) ) {
+ kFatal() << name() << "codec lies about it's maxDecodedSizeFor()";
+ }
+
+ // 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 ) {
+ ::memmove( mOutputBuffer, mOutputBuffer + i, numCharsLeft );
+ }
+ // adjust cursor:
+ mOutputBufferCursor = numCharsLeft;
+
+ return !numCharsLeft;
+}
+
+} // namespace KMime
diff --git a/kmime/kmime_codecs.h b/kmime/kmime_codecs.h
new file mode 100644
index 0000000..4c4d019
--- /dev/null
+++ b/kmime/kmime_codecs.h
@@ -0,0 +1,511 @@
+/* -*- c++ -*-
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the Codec class.
+
+ @brief
+ Defines the classes Codec class.
+
+ @authors Marc Mutz \<mutz@kde.org\>
+
+ @glossary @anchor MIME @anchor mime @b MIME:
+ <b>Multipurpose Internet Mail Extensions</b> or @acronym MIME is an
+ Internet Standard that extends the format of e-mail to support text in
+ character sets other than US-ASCII, non-text attachments, multi-part message
+ bodies, and header information in non-ASCII character sets. Virtually all
+ human-written Internet e-mail and a fairly large proportion of automated
+ e-mail is transmitted via @acronym SMTP in MIME format. Internet e-mail is
+ so closely associated with the SMTP and MIME standards that it is sometimes
+ called SMTP/MIME e-mail. The content types defined by MIME standards are
+ also of growing importance outside of e-mail, such as in communication
+ protocols like @acronym HTTP for the World Wide Web. MIME is also a
+ fundamental component of communication protocols such as HTTP, which
+ requires that data be transmitted in the context of e-mail-like messages,
+ even though the data may not actually be e-mail.
+
+ @glossary @anchor codec @anchor codecs @anchor Codec @anchor Codecs @b codec:
+ a program capable of performing encoding and decoding on a digital data
+ stream. Codecs encode data for storage or encryption and decode it for
+ viewing or editing.
+
+ @glossary @anchor CRLF @b CRLF: a "Carriage Return (0x0D)" followed by a
+ "Line Feed (0x0A)", two ASCII control characters used to represent a
+ newline on some operating systems, notably DOS and Microsoft Windows.
+
+ @glossary @anchor LF @b LF: a "Line Feed (0x0A)" ASCII control character used
+ to represent a newline on some operating systems, notably Unix, Unix-like,
+ and Linux.
+*/
+
+#ifndef __KMIME_CODECS__
+#define __KMIME_CODECS__
+
+#include <QtCore/QByteArray>
+
+#include <kdebug.h> // for kFatal()
+
+#include "kmime_export.h"
+
+namespace KMime {
+
+template <class Key, class T> class KAutoDeleteHash;
+
+class Encoder;
+class Decoder;
+
+/**
+ @brief
+ An abstract base class of @ref codecs for common mail transfer encodings.
+
+ Provides an abstract base class of @ref codecs like base64 and quoted-printable.
+ Implemented as a singleton.
+*/
+class KMIME_EXPORT Codec
+{
+ protected:
+ //@cond PRIVATE
+ static KAutoDeleteHash<QByteArray, Codec> *all;
+ static void cleanupCodec();
+ //@endcond
+ /**
+ Contructs the codec.
+ */
+ Codec() {}
+
+ public:
+ /**
+ Returns a codec associated with the specified @p name.
+
+ @param name points to a character string containing a valid codec name.
+ */
+ static Codec *codecForName( const char *name );
+
+ /**
+ Returns a codec associated with the specified @p name.
+
+ @param name is a QByteArray containing a valid codec name.
+ */
+ static Codec *codecForName( const QByteArray &name );
+
+ /**
+ Computes the maximum size, in characters, needed for the encoding.
+
+ @param insize is the number of input characters to be encoded.
+ @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF.
+
+ @return the maximum number of characters in the encoding.
+ */
+ virtual int maxEncodedSizeFor( int insize, bool withCRLF=false ) const = 0;
+
+ /**
+ Computes the maximum size, in characters, needed for the deccoding.
+
+ @param insize is the number of input characters to be decoded.
+ @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF.
+
+ @return the maximum number of characters in the decoding.
+ */
+ virtual int maxDecodedSizeFor( int insize, bool withCRLF=false ) const = 0;
+
+ /**
+ Creates the encoder for the codec.
+
+ @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF.
+
+ @return a pointer to an instance of the codec's encoder.
+ */
+ virtual Encoder *makeEncoder( bool withCRLF=false ) const = 0;
+
+ /**
+ Creates the decoder for the codec.
+
+ @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF.
+
+ @return a pointer to an instance of the codec's decoder.
+ */
+ 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" );
+ kFatal( !codec ) << "no base64 codec found!?";
+ 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() ) ) {
+ kDebug() << "output buffer too small";
+ return;
+ }
+ kDebug() << "Size of encoded data:" << oit - out.begin();
+ </pre>
+
+ @param scursor is a pointer to the start of the input buffer.
+ @param send is a pointer to the end of the input buffer.
+ @param dcursor is a pointer to the start of the output buffer.
+ @param dend is a pointer to the end of the output buffer.
+ @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF.
+
+ @return false if the encoded data didn't fit into the output buffer;
+ true otherwise.
+ */
+ 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" );
+ kFatal( !codec ) << "no base64 codec found!?";
+ 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() ) ) {
+ kDebug() << "output buffer too small";
+ return;
+ }
+ kDebug() << "Size of decoded data:" << oit - out.begin();
+ </pre>
+
+ @param scursor is a pointer to the start of the input buffer.
+ @param send is a pointer to the end of the input buffer.
+ @param dcursor is a pointer to the start of the output buffer.
+ @param dend is a pointer to the end of the output buffer.
+ @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF.
+
+ @return false if the decoded data didn't fit into the output buffer;
+ true otherwise.
+ */
+ 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.
+
+ @param src is a QByteArray containing the data to encode.
+ @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF.
+ */
+ 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 QByteArray to the actual size again.
+
+ For use with small @p src.
+
+ @param src is a QByteArray containing the data to decode.
+ @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF.
+ */
+ virtual QByteArray decode( const QByteArray &src, bool withCRLF=false ) const;
+
+ /**
+ Returns the name of the encoding. Guaranteed to be lowercase.
+ */
+ virtual const char *name() const = 0;
+
+ /**
+ Destroys the codec.
+ */
+ virtual ~Codec() {}
+
+ private:
+ /**
+ Fills the KAutoDeleteHash with all the supported codecs.
+ */
+ static void fillDictionary();
+};
+
+/**
+ @brief Stateful CTE decoder class
+
+ 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" );
+ kFatal( !codec ) << "No codec found for base64!";
+ 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>
+*/
+class Decoder
+{
+ protected:
+ friend class Codec;
+ /**
+ Protected constructor. Use KMime::Codec::makeDecoder to create an
+ instance.
+
+ @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF.
+ */
+ Decoder( bool withCRLF=false )
+ : mWithCRLF( withCRLF ) {}
+
+ public:
+ /**
+ Destroys the decoder.
+ */
+ virtual ~Decoder() {}
+
+ /**
+ Decodes a chunk of data, maintaining state information between
+ calls. See class decumentation for calling conventions.
+
+ @param scursor is a pointer to the start of the input buffer.
+ @param send is a pointer to the end of the input buffer.
+ @param dcursor is a pointer to the start of the output buffer.
+ @param dend is a pointer to the end of the output buffer.
+ */
+ 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.
+
+ @param dcursor is a pointer to the start of the output buffer.
+ @param dend is a pointer to the end of the output buffer.
+ */
+ virtual bool finish( char* &dcursor, const char * const dend ) = 0;
+
+ protected:
+ //@cond PRIVATE
+ const bool mWithCRLF;
+ //@endcond
+};
+
+/**
+ @brief
+ Stateful encoder class.
+
+ Stateful encoder class, modeled after QTextEncoder.
+*/
+class Encoder
+{
+ protected:
+ friend class Codec;
+ /**
+ Protected constructor. Use KMime::Codec::makeEncoder if you want one.
+
+ @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF.
+ */
+ explicit Encoder( bool withCRLF=false )
+ : mOutputBufferCursor( 0 ), mWithCRLF( withCRLF ) {}
+
+ public:
+ /**
+ Destroys the encoder.
+ */
+ virtual ~Encoder() {}
+
+ /**
+ Encodes a chunk of data, maintaining state information between
+ calls. See KMime::Codec for calling conventions.
+
+ @param scursor is a pointer to the start of the input buffer.
+ @param send is a pointer to the end of the input buffer.
+ @param dcursor is a pointer to the start of the output buffer.
+ @param dend is a pointer to the end of the output buffer.
+ */
+ 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.
+
+ @param dcursor is a pointer to the start of the output buffer.
+ @param dend is a pointer to the end of the output buffer.
+ */
+ virtual bool finish( char* &dcursor, const char * const dend ) = 0;
+
+ protected:
+ /**
+ The maximum number of characters permitted in the output buffer.
+ */
+ enum {
+ maxBufferedChars = 8 /**< Eight */
+ };
+
+ /**
+ Writes character @p ch to the output stream or the output buffer,
+ depending on whether or not the output stream has space left.
+
+ @param ch is the character to write.
+ @param dcursor is a pointer to the start of the output buffer.
+ @param dend is a pointer to the end of the output buffer.
+
+ @return true if written to the output stream; else 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:
+ kFatal( mOutputBufferCursor >= maxBufferedChars )
+ << "KMime::Encoder: internal buffer overflow!";
+ 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.
+
+ @param dcursor is a pointer to the start of the output buffer.
+ @param dend is a pointer to the end of the output buffer.
+
+ @return true if all chars could be written, false otherwise
+ */
+ bool flushOutputBuffer( char* &dcursor, const char * const dend );
+
+ /**
+ Convenience function. Outputs @ref LF or @ref CRLF, based on the
+ state of mWithCRLF.
+
+ @param dcursor is a pointer to the start of the output buffer.
+ @param dend is a pointer to the end of the output buffer.
+ */
+ bool writeCRLF( char* &dcursor, const char * const dend )
+ {
+ if ( mWithCRLF ) {
+ write( '\r', dcursor, dend );
+ }
+ return write( '\n', dcursor, dend );
+ }
+
+ private:
+ /**
+ An output buffer to simplify some codecs.
+ Used with write() and flushOutputBuffer().
+ */
+ //@cond PRIVATE
+ char mOutputBuffer[ maxBufferedChars ];
+ //@endcond
+
+ protected:
+ //@cond PRIVATE
+ uchar mOutputBufferCursor;
+ const bool mWithCRLF;
+ //@endcond
+};
+
+} // namespace KMime
+
+#endif // __KMIME_CODECS__
diff --git a/kmime/kmime_content.cpp b/kmime/kmime_content.cpp
new file mode 100644
index 0000000..569be32
--- /dev/null
+++ b/kmime/kmime_content.cpp
@@ -0,0 +1,1169 @@
+/*
+ kmime_content.cpp
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001 the KMime authors.
+ See file AUTHORS for details
+ Copyright (c) 2006 Volker Krause <vkrause@kde.org>
+ Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the Content class.
+
+ @brief
+ Defines the Content class.
+
+ @authors the KMime authors (see AUTHORS file),
+ Volker Krause \<vkrause@kde.org\>
+*/
+
+#include "kmime_content.h"
+#include "kmime_content_p.h"
+#include "kmime_codecs.h"
+#include "kmime_message.h"
+#include "kmime_header_parsing.h"
+#include "kmime_header_parsing_p.h"
+#include "kmime_parsers.h"
+#include "kmime_util_p.h"
+
+#include <kcharsets.h>
+#include <kcodecs.h>
+#include <kglobal.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+#include <QtCore/QTextCodec>
+#include <QtCore/QTextStream>
+#include <QtCore/QByteArray>
+
+using namespace KMime;
+
+namespace KMime {
+
+Content::Content()
+ : d_ptr( new ContentPrivate( this ) )
+{
+}
+
+Content::Content( Content *parent )
+ : d_ptr( new ContentPrivate( this ) )
+{
+ d_ptr->parent = parent;
+}
+
+Content::Content( const QByteArray &h, const QByteArray &b )
+ : d_ptr( new ContentPrivate( this ) )
+{
+ d_ptr->head = h;
+ d_ptr->body = b;
+}
+
+Content::Content( const QByteArray &h, const QByteArray &b, Content *parent )
+ : d_ptr( new ContentPrivate( this ) )
+{
+ d_ptr->head = h;
+ d_ptr->body = b;
+ d_ptr->parent = parent;
+}
+
+Content::Content( ContentPrivate *d )
+ : d_ptr( d )
+{
+}
+
+Content::~Content()
+{
+ qDeleteAll( h_eaders );
+ h_eaders.clear();
+ delete d_ptr;
+ d_ptr = 0;
+}
+
+bool Content::hasContent() const
+{
+ return !d_ptr->head.isEmpty() || !d_ptr->body.isEmpty() || !d_ptr->contents().isEmpty();
+}
+
+void Content::setContent( const QList<QByteArray> &l )
+{
+ Q_D(Content);
+ //qDebug("Content::setContent( const QList<QByteArray> &l ) : start");
+ d->head.clear();
+ d->body.clear();
+
+ //usage of textstreams is much faster than simply appending the strings
+ QTextStream hts( &( d->head ), QIODevice::WriteOnly );
+ QTextStream bts( &( d->body ), QIODevice::WriteOnly );
+ hts.setCodec( "ISO 8859-1" );
+ bts.setCodec( "ISO 8859-1" );
+
+ bool isHead = true;
+ foreach ( const QByteArray& line, l ) {
+ if ( isHead && line.isEmpty() ) {
+ isHead = false;
+ continue;
+ }
+ if ( isHead ) {
+ hts << line << "\n";
+ } else {
+ bts << line << "\n";
+ }
+ }
+
+ //qDebug("Content::setContent( const QList<QByteArray> & l ) : finished");
+}
+
+void Content::setContent( const QByteArray &s )
+{
+ Q_D(Content);
+ KMime::HeaderParsing::extractHeaderAndBody( s, d->head, d->body );
+}
+
+QByteArray Content::head() const
+{
+ return d_ptr->head;
+}
+
+void Content::setHead( const QByteArray &head )
+{
+ d_ptr->head = head;
+ if ( !head.endsWith( '\n' ) )
+ d_ptr->head += '\n';
+}
+
+QByteArray Content::body() const
+{
+ return d_ptr->body;
+}
+
+void Content::setBody( const QByteArray &body )
+{
+ d_ptr->body = body;
+}
+
+QByteArray Content::preamble() const
+{
+ return d_ptr->preamble;
+}
+
+void Content::setPreamble( const QByteArray &preamble )
+{
+ d_ptr->preamble = preamble;
+}
+
+
+QByteArray Content::epilogue() const
+{
+ return d_ptr->epilogue;
+}
+
+void Content::setEpilogue( const QByteArray &epilogue )
+{
+ d_ptr->epilogue = epilogue;
+}
+
+void Content::parse()
+{
+ Q_D( Content );
+
+ // Clean up old headers and parse them again.
+ qDeleteAll( h_eaders );
+ h_eaders.clear();
+ h_eaders = HeaderParsing::parseHeaders( d->head );
+ foreach( Headers::Base *h, h_eaders ) {
+ h->setParent( this );
+ }
+
+ // If we are frozen, save the body as-is. This is done because parsing
+ // changes the content (it loses preambles and epilogues, converts uuencode->mime, etc.)
+ if( d->frozen ) {
+ d->frozenBody = d->body;
+ }
+
+ // Clean up old sub-Contents and parse them again.
+ qDeleteAll( d->multipartContents );
+ d->multipartContents.clear();
+ d->clearBodyMessage();
+ Headers::ContentType *ct = contentType();
+ if( ct->isText() ) {
+ // This content is either text, or of unknown type.
+
+ if( d->parseUuencoded() ) {
+ // This is actually uuencoded content generated by broken software.
+ } else if( d->parseYenc() ) {
+ // This is actually yenc content generated by broken software.
+ } else {
+ // This is just plain text.
+ }
+ } else if( ct->isMultipart() ) {
+ // This content claims to be MIME multipart.
+
+ if( d->parseMultipart() ) {
+ // This is actual MIME multipart content.
+ } else {
+ // Parsing failed; treat this content as "text/plain".
+ ct->setMimeType( "text/plain" );
+ ct->setCharset( "US-ASCII" );
+ }
+ } else {
+ // This content is something else, like an encapsulated message or a binary attachment
+ // or something like that
+ if ( bodyIsMessage() ) {
+ d->bodyAsMessage = Message::Ptr( new Message );
+ d->bodyAsMessage->setContent( d->body );
+ d->bodyAsMessage->setFrozen( d->frozen );
+ d->bodyAsMessage->parse();
+ d->bodyAsMessage->d_ptr->parent = this;
+
+ // Clear the body, as it is now represented by d->bodyAsMessage. This is the same behavior
+ // as with multipart contents, since parseMultipart() clears the body as well
+ d->body.clear();
+ }
+ }
+}
+
+bool Content::isFrozen() const
+{
+ return d_ptr->frozen;
+}
+
+void Content::setFrozen( bool frozen )
+{
+ d_ptr->frozen = frozen;
+}
+
+void Content::assemble()
+{
+ Q_D( Content );
+ if( d->frozen ) {
+ return;
+ }
+
+ d->head = assembleHeaders();
+ foreach( Content *c, contents() ) {
+ c->assemble();
+ }
+}
+
+QByteArray Content::assembleHeaders()
+{
+ QByteArray newHead;
+ foreach( const Headers::Base *h, h_eaders ) {
+ if( !h->isEmpty() ) {
+ newHead += h->as7BitString() + '\n';
+ }
+ }
+
+ return newHead;
+}
+
+void Content::clear()
+{
+ Q_D(Content);
+ qDeleteAll( h_eaders );
+ h_eaders.clear();
+ clearContents();
+ d->head.clear();
+ d->body.clear();
+}
+
+void Content::clearContents( bool del )
+{
+ Q_D(Content);
+ if( del ) {
+ qDeleteAll( d->multipartContents );
+ }
+ d->multipartContents.clear();
+ d->clearBodyMessage();
+}
+
+QByteArray Content::encodedContent( bool useCrLf )
+{
+ Q_D(Content);
+ QByteArray e;
+
+ // Head.
+ e = d->head;
+ e += '\n';
+ e += encodedBody();
+
+ if ( useCrLf ) {
+ return LFtoCRLF( e );
+ } else {
+ return e;
+ }
+}
+
+QByteArray Content::encodedBody()
+{
+ Q_D( Content );
+ QByteArray e;
+ // Body.
+ if( d->frozen ) {
+ // This Content is frozen.
+ if( d->frozenBody.isEmpty() ) {
+ // This Content has never been parsed.
+ e += d->body;
+ } else {
+ // Use the body as it was before parsing.
+ e += d->frozenBody;
+ }
+ } else if( bodyIsMessage() && d->bodyAsMessage ) {
+ // This is an encapsulated message
+ // No encoding needed, as the ContentTransferEncoding can only be 7bit
+ // for encapsulated messages
+ e += d->bodyAsMessage->encodedContent();
+ } else if( !d->body.isEmpty() ) {
+ // This is a single-part Content.
+ Headers::ContentTransferEncoding *enc = contentTransferEncoding();
+
+ if ( enc->needToEncode() ) {
+ if ( enc->encoding() == Headers::CEquPr ) {
+ e += KCodecs::quotedPrintableEncode( d->body, false );
+ } else {
+ e += KCodecs::base64Encode( d->body, true );
+ e += '\n';
+ }
+ } else {
+ e += d->body;
+ }
+ }
+
+ if ( !d->frozen && !d->multipartContents.isEmpty() ) {
+ // This is a multipart Content.
+ Headers::ContentType *ct=contentType();
+ QByteArray boundary = "\n--" + ct->boundary();
+
+ if ( !d->preamble.isEmpty() )
+ e += d->preamble;
+
+ //add all (encoded) contents separated by boundaries
+ foreach ( Content *c, d->multipartContents ) {
+ 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 ( !d->epilogue.isEmpty() )
+ e += d->epilogue;
+ };
+ return e;
+}
+
+QByteArray Content::decodedContent()
+{
+ QByteArray ret;
+ Headers::ContentTransferEncoding *ec=contentTransferEncoding();
+ bool removeTrailingNewline=false;
+
+ if ( d_ptr->body.length() == 0 ) {
+ return ret;
+ }
+
+ if ( ec->decoded() ) {
+ ret = d_ptr->body;
+ removeTrailingNewline = true;
+ } else {
+ switch( ec->encoding() ) {
+ case Headers::CEbase64 :
+ {
+ KMime::Codec *codec = KMime::Codec::codecForName( "base64" );
+ Q_ASSERT( codec );
+ ret.resize( codec->maxDecodedSizeFor( d_ptr->body.size() ) );
+ KMime::Decoder* decoder = codec->makeDecoder();
+ QByteArray::const_iterator inputIt = d_ptr->body.constBegin();
+ QByteArray::iterator resultIt = ret.begin();
+ decoder->decode( inputIt, d_ptr->body.constEnd(), resultIt, ret.end() );
+ ret.truncate( resultIt - ret.begin() );
+ break;
+ }
+ case Headers::CEquPr :
+ ret = KCodecs::quotedPrintableDecode( d_ptr->body );
+ removeTrailingNewline = true;
+ break;
+ case Headers::CEuuenc :
+ KCodecs::uudecode( d_ptr->body, ret );
+ break;
+ case Headers::CEbinary :
+ ret = d_ptr->body;
+ removeTrailingNewline = false;
+ break;
+ default :
+ ret = d_ptr->body;
+ removeTrailingNewline = true;
+ }
+ }
+
+ if ( removeTrailingNewline && ( ret.size() > 0 ) && ( ret[ret.size()-1] == '\n') ) {
+ ret.resize( ret.size() - 1 );
+ }
+
+ return ret;
+}
+
+QString Content::decodedText( bool trimText, bool removeTrailingNewlines )
+{
+ if ( !decodeText() ) { //this is not a text content !!
+ return QString();
+ }
+
+ bool ok = true;
+ QTextCodec *codec =
+ KCharsets::charsets()->codecForName( QLatin1String( contentType()->charset() ), ok );
+ if ( !ok || codec == NULL ) { // no suitable codec found => try local settings and hope the best ;-)
+ codec = KGlobal::locale()->codecForEncoding();
+ QByteArray chset = KGlobal::locale()->encoding();
+ contentType()->setCharset( chset );
+ }
+
+ QString s = codec->toUnicode( d_ptr->body.data(), d_ptr->body.length() );
+
+ if ( trimText || removeTrailingNewlines ) {
+ int i;
+ for ( i = s.length() - 1; i >= 0; --i ) {
+ if ( trimText ) {
+ if ( !s[i].isSpace() ) {
+ break;
+ }
+ }
+ else {
+ if ( s[i] != QLatin1Char( '\n' ) ) {
+ break;
+ }
+ }
+ }
+ s.truncate( i + 1 );
+ } else {
+ if ( s.right( 1 ) == QLatin1String( "\n" ) ) {
+ s.truncate( s.length() - 1 ); // remove trailing new-line
+ }
+ }
+
+ return s;
+}
+
+void Content::fromUnicodeString( const QString &s )
+{
+ bool ok = true;
+ QTextCodec *codec =
+ KCharsets::charsets()->codecForName( QLatin1String( contentType()->charset() ), ok );
+
+ if ( !ok ) { // no suitable codec found => try local settings and hope the best ;-)
+ codec = KGlobal::locale()->codecForEncoding();
+ QByteArray chset = KGlobal::locale()->encoding();
+ contentType()->setCharset( chset );
+ }
+
+ d_ptr->body = 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 {
+ foreach ( Content *c, d_ptr->contents() ) {
+ if ( ( ret = c->textContent() ) != 0 ) {
+ break;
+ }
+ }
+ }
+ return ret;
+}
+
+Content::List Content::attachments( bool incAlternatives )
+{
+ List attachments;
+ if ( d_ptr->contents().isEmpty() ) {
+ attachments.append( this );
+ } else {
+ foreach ( Content *c, d_ptr->contents() ) {
+ if ( !incAlternatives &&
+ c->contentType()->category() == Headers::CCalternativePart ) {
+ continue;
+ } else {
+ attachments += c->attachments( incAlternatives );
+ }
+ }
+ }
+
+ if ( isTopLevel() ) {
+ Content *text = textContent();
+ if ( text ) {
+ attachments.removeAll( text );
+ }
+ }
+ return attachments;
+}
+
+Content::List Content::contents() const
+{
+ return d_ptr->contents();
+}
+
+void Content::addContent( Content *c, bool prepend )
+{
+ Q_D( Content );
+
+ // This method makes no sense for encapsulated messages
+ Q_ASSERT( !bodyIsMessage() );
+
+ // If this message is single-part; make it multipart first.
+ if( d->multipartContents.isEmpty() && !contentType()->isMultipart() ) {
+ // The current body will be our first sub-Content.
+ Content *main = new Content( this );
+
+ // Move the MIME headers to the newly created sub-Content.
+ // NOTE: The other headers (RFC5322 headers like From:, To:, as well as X-headers
+ // are not moved to the subcontent; they remain with the top-level content.
+ for ( Headers::Base::List::iterator it = h_eaders.begin();
+ it != h_eaders.end(); ) {
+ if ( (*it)->isMimeHeader() ) {
+ // Add to new content.
+ main->setHeader( *it );
+ // Remove from this content.
+ it = h_eaders.erase( it );
+ } else {
+ ++it;
+ }
+ }
+
+ // Adjust the Content-Type of the newly created sub-Content.
+ main->contentType()->setCategory( Headers::CCmixedPart );
+
+ // Move the body to the new subcontent.
+ main->setBody( d->body );
+ d->body.clear();
+
+ // Add the subcontent.
+ d->multipartContents.append( main );
+
+ // Convert this content to "multipart/mixed".
+ Headers::ContentType *ct = contentType();
+ ct->setMimeType( "multipart/mixed" );
+ ct->setBoundary( multiPartBoundary() );
+ ct->setCategory( Headers::CCcontainer );
+ contentTransferEncoding()->clear(); // 7Bit, decoded.
+ }
+
+ // Add the new content.
+ if( prepend ) {
+ d->multipartContents.prepend( c );
+ } else {
+ d->multipartContents.append( c );
+ }
+
+ if( c->parent() != this ) {
+ // If the content was part of something else, this will remove it from there.
+ c->setParent( this );
+ }
+}
+
+void Content::removeContent( Content *c, bool del )
+{
+ Q_D( Content );
+ if ( d->multipartContents.isEmpty() || !d->multipartContents.contains( c ) ) {
+ return;
+ }
+
+ // This method makes no sense for encapsulated messages.
+ // Should be covered by the above assert already, though.
+ Q_ASSERT( !bodyIsMessage() );
+
+ d->multipartContents.removeAll( c );
+ if ( del ) {
+ delete c;
+ } else {
+ c->d_ptr->parent = 0;
+ }
+
+ // If only one content is left, turn this content into a single-part.
+ if( d->multipartContents.count() == 1 ) {
+ Content *main = d->multipartContents.first();
+
+ // Move all headers from the old subcontent to ourselves.
+ // NOTE: This also sets the new Content-Type.
+ foreach( Headers::Base *h, main->h_eaders ) {
+ setHeader( h ); // Will remove the old one if present.
+ }
+ main->h_eaders.clear();
+
+ // Move the body.
+ d->body = main->body();
+
+ // Delete the old subcontent.
+ delete main;
+ d->multipartContents.clear();
+ }
+}
+
+void Content::changeEncoding( Headers::contentEncoding e )
+{
+ // This method makes no sense for encapsulated messages, they are always 7bit
+ // encoded.
+ Q_ASSERT( !bodyIsMessage() );
+
+ Headers::ContentTransferEncoding *enc = contentTransferEncoding();
+ if( enc->encoding() == e ) {
+ // Nothing to do.
+ return;
+ }
+
+ if( decodeText() ) {
+ // This is textual content. Textual content is stored decoded.
+ Q_ASSERT( enc->decoded() );
+ enc->setEncoding( e );
+ } else {
+ // This is non-textual content. Re-encode it.
+ if( e == Headers::CEbase64 ) {
+ d_ptr->body = KCodecs::base64Encode( decodedContent(), true );
+ d_ptr->body.append( "\n" );
+ enc->setEncoding( e );
+ enc->setDecoded( false );
+ } else {
+ // It only makes sense to convert binary stuff to base64.
+ Q_ASSERT( false );
+ }
+ }
+}
+
+void Content::toStream( QTextStream &ts, bool scrambleFromLines )
+{
+ QByteArray ret = encodedContent( false );
+
+ if ( scrambleFromLines ) {
+ // FIXME Why are only From lines with a preceding empty line considered?
+ // And, of course, all lines starting with >*From have to be escaped
+ // because otherwise the transformation is not revertable.
+ ret.replace( "\n\nFrom ", "\n\n>From ");
+ }
+ ts << ret;
+}
+
+Headers::Generic *Content::getNextHeader( QByteArray &head )
+{
+ return d_ptr->nextHeader( head );
+}
+
+Headers::Generic *Content::nextHeader( QByteArray &head )
+{
+ return d_ptr->nextHeader( head );
+}
+
+Headers::Generic *ContentPrivate::nextHeader( QByteArray &_head )
+{
+ Headers::Base *header = HeaderParsing::extractFirstHeader( _head );
+ if ( !header ) {
+ return 0;
+ }
+ // Convert it from the real class to Generic.
+ Headers::Generic *ret = new Headers::Generic( header->type(), q_ptr );
+ ret->from7BitString( header->as7BitString() );
+ return ret;
+}
+
+Headers::Base *Content::getHeaderByType( const char *type )
+{
+ return headerByType( type );
+}
+
+Headers::Base *Content::headerByType( const char *type )
+{
+ Q_ASSERT( type && *type );
+
+ foreach( Headers::Base *h, h_eaders ) {
+ if( h->is( type ) ) {
+ return h; // Found.
+ }
+ }
+
+ return 0; // Not found.
+}
+
+Headers::Base::List Content::headersByType( const char *type )
+{
+ Q_ASSERT( type && *type );
+
+ Headers::Base::List result;
+
+ foreach( Headers::Base *h, h_eaders ) {
+ if( h->is( type ) ) {
+ result << h;
+ }
+ }
+
+ return result;
+}
+
+void Content::setHeader( Headers::Base *h )
+{
+ Q_ASSERT( h );
+ removeHeader( h->type() );
+ appendHeader( h );
+}
+
+void Content::appendHeader( Headers::Base *h )
+{
+ h_eaders.append( h );
+ h->setParent( this );
+}
+
+void Content::prependHeader( Headers::Base *h )
+{
+ h_eaders.prepend( h );
+ h->setParent( this );
+}
+
+bool Content::removeHeader( const char *type )
+{
+ for ( Headers::Base::List::iterator it = h_eaders.begin();
+ it != h_eaders.end(); ++it )
+ if ( (*it)->is(type) ) {
+ delete (*it);
+ h_eaders.erase( it );
+ return true;
+ }
+
+ return false;
+}
+
+bool Content::hasHeader( const char *type )
+{
+ return headerByType( type ) != 0;
+}
+
+int Content::size()
+{
+ int ret = d_ptr->body.length();
+
+ if ( contentTransferEncoding()->encoding() == Headers::CEbase64 ) {
+ KMime::Codec *codec = KMime::Codec::codecForName( "base64" );
+ return codec->maxEncodedSizeFor(ret);
+ }
+
+ // Not handling quoted-printable here since that requires actually
+ // converting the content, and that is O(size_of_content).
+ // For quoted-printable, this is only an approximate size.
+
+ return ret;
+}
+
+int Content::storageSize() const
+{
+ const Q_D(Content);
+ int s = d->head.size();
+
+ if ( d->contents().isEmpty() ) {
+ s += d->body.size();
+ } else {
+
+ // FIXME: This should take into account the boundary headers that are added in
+ // encodedContent!
+ foreach ( Content *c, d->contents() ) {
+ s += c->storageSize();
+ }
+ }
+
+ return s;
+}
+
+int Content::lineCount() const
+{
+ const Q_D(Content);
+ int ret = 0;
+ if ( !isTopLevel() ) {
+ ret += d->head.count( '\n' );
+ }
+ ret += d->body.count( '\n' );
+
+ foreach ( Content *c, d->contents() ) {
+ ret += c->lineCount();
+ }
+
+ return ret;
+}
+
+QByteArray Content::rawHeader( const char *name ) const
+{
+ return KMime::extractHeader( d_ptr->head, name );
+}
+
+QList<QByteArray> Content::rawHeaders( const char *name ) const
+{
+ return KMime::extractHeaders( d_ptr->head, name );
+}
+
+bool Content::decodeText()
+{
+ Q_D(Content);
+ Headers::ContentTransferEncoding *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->encoding() )
+ {
+ case Headers::CEbase64 :
+ d->body = KCodecs::base64Decode( d->body );
+ d->body.append( "\n" );
+ break;
+ case Headers::CEquPr :
+ d->body = KCodecs::quotedPrintableDecode( d->body );
+ break;
+ case Headers::CEuuenc :
+ d->body = KCodecs::uudecode( d->body );
+ d->body.append( "\n" );
+ break;
+ case Headers::CEbinary :
+ // nothing to decode
+ d->body.append( "\n" );
+ default :
+ break;
+ }
+
+ enc->setDecoded( true );
+ return true;
+}
+
+QByteArray Content::defaultCharset() const
+{
+ return d_ptr->defaultCS;
+}
+
+void Content::setDefaultCharset( const QByteArray &cs )
+{
+ d_ptr->defaultCS = KMime::cachedCharset( cs );
+
+ foreach ( Content *c, d_ptr->contents() ) {
+ c->setDefaultCharset( cs );
+ }
+
+ // reparse the part and its sub-parts in order
+ // to clear cached header values
+ parse();
+}
+
+bool Content::forceDefaultCharset() const
+{
+ return d_ptr->forceDefaultCS;
+}
+
+void Content::setForceDefaultCharset( bool b )
+{
+ d_ptr->forceDefaultCS = b;
+
+ foreach ( Content *c, d_ptr->contents() ) {
+ c->setForceDefaultCharset( b );
+ }
+
+ // reparse the part and its sub-parts in order
+ // to clear cached header values
+ parse();
+}
+
+Content * KMime::Content::content( const ContentIndex &index ) const
+{
+ if ( !index.isValid() ) {
+ return const_cast<KMime::Content*>( this );
+ }
+ ContentIndex idx = index;
+ unsigned int i = idx.pop() - 1; // one-based -> zero-based index
+ if ( i < (unsigned int)d_ptr->contents().size() ) {
+ return d_ptr->contents()[i]->content( idx );
+ } else {
+ return 0;
+ }
+}
+
+ContentIndex KMime::Content::indexForContent( Content * content ) const
+{
+ int i = d_ptr->contents().indexOf( content );
+ if ( i >= 0 ) {
+ ContentIndex ci;
+ ci.push( i + 1 ); // zero-based -> one-based index
+ return ci;
+ }
+ // not found, we need to search recursively
+ for ( int i = 0; i < d_ptr->contents().size(); ++i ) {
+ ContentIndex ci = d_ptr->contents()[i]->indexForContent( content );
+ if ( ci.isValid() ) {
+ // found it
+ ci.push( i + 1 ); // zero-based -> one-based index
+ return ci;
+ }
+ }
+ return ContentIndex(); // not found
+}
+
+bool Content::isTopLevel() const
+{
+ return d_ptr->parent == 0;
+}
+
+void Content::setParent( Content *parent )
+{
+ // Make sure the Content is only in the contents list of one parent object
+ Content *oldParent = d_ptr->parent;
+ if ( oldParent ) {
+ if ( !oldParent->contents().isEmpty() && oldParent->contents().contains( this ) ) {
+ oldParent->removeContent( this );
+ }
+ }
+
+ d_ptr->parent = parent;
+ if ( parent ) {
+ if ( !parent->contents().isEmpty() && !parent->contents().contains( this ) ) {
+ parent->addContent( this );
+ }
+ }
+}
+
+Content *Content::parent() const
+{
+ return d_ptr->parent;
+}
+
+Content *Content::topLevel() const
+{
+ Content *top = const_cast<Content*>(this);
+ Content *c = parent();
+ while ( c ) {
+ top = c;
+ c = c->parent();
+ }
+
+ return top;
+}
+
+ContentIndex Content::index() const
+{
+ Content* top = topLevel();
+ if ( top ) {
+ return top->indexForContent( const_cast<Content*>(this) );
+ }
+
+ return indexForContent( const_cast<Content*>(this) );
+}
+
+Message::Ptr Content::bodyAsMessage() const
+{
+ if ( bodyIsMessage() && d_ptr->bodyAsMessage ) {
+ return d_ptr->bodyAsMessage;
+ } else {
+ return Message::Ptr();
+ }
+}
+
+bool Content::bodyIsMessage() const
+{
+ // Use const_case here to work around API issue that neither header() nor hasHeader() are
+ // const, even though they should be
+ return const_cast<Content*>( this )->header<Headers::ContentType>( false ) &&
+ const_cast<Content*>( this )->header<Headers::ContentType>( true )
+ ->mimeType().toLower() == "message/rfc822";
+}
+
+// @cond PRIVATE
+#define kmime_mk_header_accessor( type, method ) \
+Headers::type *Content::method( bool create ) { \
+ return header<Headers::type>( create ); \
+}
+
+kmime_mk_header_accessor( ContentType, contentType )
+kmime_mk_header_accessor( ContentTransferEncoding, contentTransferEncoding )
+kmime_mk_header_accessor( ContentDisposition, contentDisposition )
+kmime_mk_header_accessor( ContentDescription, contentDescription )
+kmime_mk_header_accessor( ContentLocation, contentLocation )
+kmime_mk_header_accessor( ContentID, contentID )
+
+#undef kmime_mk_header_accessor
+// @endcond
+
+
+void ContentPrivate::clearBodyMessage()
+{
+ bodyAsMessage.reset();
+}
+
+Content::List ContentPrivate::contents() const
+{
+ Q_ASSERT( multipartContents.isEmpty() || !bodyAsMessage );
+ if ( bodyAsMessage )
+ return Content::List() << bodyAsMessage.get();
+ else
+ return multipartContents;
+}
+
+bool ContentPrivate::parseUuencoded()
+{
+ Q_Q( Content );
+ Parser::UUEncoded uup( body, KMime::extractHeader( head, "Subject" ) );
+ if( !uup.parse() ) {
+ return false; // Parsing failed.
+ }
+
+ Headers::ContentType *ct = q->contentType();
+ ct->clear();
+
+ 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() );
+ q->contentTransferEncoding()->setEncoding( Headers::CE7Bit );
+ } else {
+ // This is a complete message, so treat it as "multipart/mixed".
+ body.clear();
+ ct->setMimeType( "multipart/mixed" );
+ ct->setBoundary( multiPartBoundary() );
+ ct->setCategory( Headers::CCcontainer );
+ q->contentTransferEncoding()->clear(); // 7Bit, decoded.
+
+ // Add the plain text part first.
+ Q_ASSERT( multipartContents.count() == 0 );
+ {
+ Content *c = new Content( q );
+ c->contentType()->setMimeType( "text/plain" );
+ c->contentTransferEncoding()->setEncoding( Headers::CE7Bit );
+ c->setBody( uup.textPart() );
+ multipartContents.append( c );
+ }
+
+ // Now add each of the binary parts as sub-Contents.
+ for( int i = 0; i < uup.binaryParts().count(); ++i ) {
+ Content *c = new Content( q );
+ c->contentType()->setMimeType( uup.mimeTypes().at( i ) );
+ c->contentType()->setName( QLatin1String( uup.filenames().at( i ) ), QByteArray( /*charset*/ ) );
+ c->contentTransferEncoding()->setEncoding( Headers::CEuuenc );
+ c->contentTransferEncoding()->setDecoded( false );
+ c->contentDisposition()->setDisposition( Headers::CDattachment );
+ c->contentDisposition()->setFilename( QLatin1String( uup.filenames().at( i ) ) );
+ c->setBody( uup.binaryParts().at( i ) );
+ c->changeEncoding( Headers::CEbase64 ); // Convert to base64.
+ multipartContents.append( c );
+ }
+ }
+
+ return true; // Parsing successful.
+}
+
+bool ContentPrivate::parseYenc()
+{
+ Q_Q( Content );
+ Parser::YENCEncoded yenc( body );
+ if( !yenc.parse() ) {
+ return false; // Parsing failed.
+ }
+
+ Headers::ContentType *ct = q->contentType();
+ ct->clear();
+
+ if( yenc.isPartial() ) {
+ // Assume there is exactly one decoded part. Treat this as "message/partial".
+ ct->setMimeType( "message/partial" );
+ //ct->setId( uniqueString() ); not needed yet
+ ct->setPartialParams( yenc.partialCount(), yenc.partialNumber() );
+ q->contentTransferEncoding()->setEncoding( Headers::CEbinary );
+ q->changeEncoding( Headers::CEbase64 ); // Convert to base64.
+ } else {
+ // This is a complete message, so treat it as "multipart/mixed".
+ body.clear();
+ ct->setMimeType( "multipart/mixed" );
+ ct->setBoundary( multiPartBoundary() );
+ ct->setCategory( Headers::CCcontainer );
+ q->contentTransferEncoding()->clear(); // 7Bit, decoded.
+
+ // Add the plain text part first.
+ Q_ASSERT( multipartContents.count() == 0 );
+ {
+ Content *c = new Content( q );
+ c->contentType()->setMimeType( "text/plain" );
+ c->contentTransferEncoding()->setEncoding( Headers::CE7Bit );
+ c->setBody( yenc.textPart() );
+ multipartContents.append( c );
+ }
+
+ // Now add each of the binary parts as sub-Contents.
+ for ( int i=0; i<yenc.binaryParts().count(); i++ ) {
+ Content *c = new Content( q );
+ c->contentType()->setMimeType( yenc.mimeTypes().at( i ) );
+ c->contentType()->setName( QLatin1String( yenc.filenames().at( i ) ), QByteArray( /*charset*/ ) );
+ c->contentTransferEncoding()->setEncoding( Headers::CEbinary );
+ c->contentDisposition()->setDisposition( Headers::CDattachment );
+ c->contentDisposition()->setFilename( QLatin1String( yenc.filenames().at( i ) ) );
+ c->setBody( yenc.binaryParts().at( i ) ); // Yenc bodies are binary.
+ c->changeEncoding( Headers::CEbase64 ); // Convert to base64.
+ multipartContents.append( c );
+ }
+ }
+
+ return true; // Parsing successful.
+}
+
+bool ContentPrivate::parseMultipart()
+{
+ Q_Q( Content );
+ const Headers::ContentType *ct = q->contentType();
+ const QByteArray boundary = ct->boundary();
+ if( boundary.isEmpty() ) {
+ return false; // Parsing failed; invalid multipart content.
+ }
+ Parser::MultiPart mpp( body, boundary );
+ if( !mpp.parse() ) {
+ return false; // Parsing failed.
+ }
+
+ preamble = mpp.preamble();
+ epilogue = mpp.epilouge();
+
+ // Determine the category of the subparts (used in attachments()).
+ Headers::contentCategory cat;
+ if( ct->isSubtype( "alternative" ) ) {
+ cat = Headers::CCalternativePart;
+ } else {
+ cat = Headers::CCmixedPart; // Default to "mixed".
+ }
+
+ // Create a sub-Content for every part.
+ Q_ASSERT( multipartContents.isEmpty() );
+ body.clear();
+ QList<QByteArray> parts = mpp.parts();
+ foreach( const QByteArray &part, mpp.parts() ) {
+ Content *c = new Content( q );
+ c->setContent( part );
+ c->setFrozen( frozen );
+ c->parse();
+ c->contentType()->setCategory( cat );
+ multipartContents.append( c );
+ }
+
+ return true; // Parsing successful.
+}
+
+} // namespace KMime
diff --git a/kmime/kmime_content.h b/kmime/kmime_content.h
new file mode 100644
index 0000000..05f67c2
--- /dev/null
+++ b/kmime/kmime_content.h
@@ -0,0 +1,846 @@
+/*
+ kmime_content.h
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001 the KMime authors.
+ See file AUTHORS for details
+ Copyright (c) 2006 Volker Krause <vkrause@kde.org>
+ Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the Content class.
+
+ @brief
+ Defines the Content class.
+
+ @authors the KMime authors (see AUTHORS file),
+ Volker Krause \<vkrause@kde.org\>
+
+TODO: possible glossary terms:
+ content
+ encoding, transfer type, disposition, description
+ header
+ body
+ attachment
+ charset
+ article
+ string representation
+ broken-down object representation
+*/
+
+#ifndef __KMIME_CONTENT_H__
+#define __KMIME_CONTENT_H__
+
+#include "kmime_export.h"
+#include "kmime_contentindex.h"
+#include "kmime_util.h"
+#include "kmime_headers.h"
+
+#include <QtCore/QTextStream>
+#include <QtCore/QByteArray>
+#include <QtCore/QList>
+
+#include <boost/shared_ptr.hpp>
+
+
+namespace KMime {
+
+class ContentPrivate;
+class Message;
+
+/**
+ @brief
+ A class that encapsulates @ref MIME encoded Content.
+
+ A Content object holds two representations of a content:
+ - the string representation: This is the content encoded as a string ready
+ for transport. Accessible through the encodedContent() method.
+ - the broken-down representation: This is the tree of objects (headers,
+ sub-Contents and (if present) the encapsulated message) that this Content is made of.
+ Accessible through methods like header(), contents() and bodyAsMessage().
+
+ The parse() function updates the broken-down representation of the Content
+ from its string representation. Calling it is necessary to access the
+ headers, sub-Contents or the encapsulated message of this Content.
+
+ The assemble() function updates the string representation of the Content
+ from its broken-down representation. Calling it is necessary for
+ encodedContent() to reflect any changes made to the broken-down representation of the Content.
+
+ There are two basic types of a Content:
+ - A leaf Content: This is a content that is neither a multipart content nor an encapsulated
+ message. Because of this, it will not have any children, it has no sub-contents
+ and is therefore a leaf content.
+ Only leaf contents have a body that is not empty, i.e. functions that operate
+ on the body, such as body(), size() and decodedContent(), will work only on
+ leaf contents.
+ - A non-leaf Content: This is a content that itself doesn't have any body, but that does have
+ sub-contents.
+ This is the case for contents that are of mimetype multipart/ or of mimetype
+ message/rfc822. In case of a multipart content, contents() will return the
+ multipart child contents. In case of an encapsulated message, the message
+ can be accessed with bodyAsMessage(), and contents() will have one entry
+ that is the message as well.
+ On a non-leaf content, body() will have an empty return value and other
+ functions working on the body will not work.
+ A call to parse() is required before the child multipart contents or the
+ encapsulated message is created.
+*/
+/*
+ KDE5:
+ * Do not convert singlepart <-> multipart automatically.
+ * A bunch of methods probably don't need to be virtual (since they're not needed
+ in either Message or NewsArticle).
+*/
+class KMIME_EXPORT Content
+{
+ public:
+
+ /**
+ Describes a list of Content objects.
+ */
+ typedef QList<KMime::Content*> List;
+
+ /**
+ Creates an empty Content object.
+ */
+ Content();
+
+ /**
+ Creates an empty Content object with a specified parent.
+ @param parent the parent Content object
+ @since 4.3
+ */
+ explicit Content( Content* parent ); // KDE5: Merge with the above.
+
+ /**
+ Creates a Content object containing the given raw data.
+
+ @param head is a QByteArray containing the header data.
+ @param body is a QByteArray containing the body data.
+ */
+ Content( const QByteArray &head, const QByteArray &body );
+
+ /**
+ Creates a Content object containing the given raw data.
+
+ @param head is a QByteArray containing the header data.
+ @param body is a QByteArray containing the body data.
+ @param parent the parent Content object
+ @since 4.3
+ */
+ // KDE5: Merge with the above.
+ Content( const QByteArray &head, const QByteArray &body, Content *parent );
+
+ /**
+ Destroys this Content object.
+ */
+ virtual ~Content();
+
+ /**
+ Returns true if this Content object is not empty.
+ */
+ bool hasContent() const;
+
+ /**
+ Sets the Content to the given raw data, containing the Content head and
+ body separated by two linefeeds.
+
+ This method operates on the string representation of the Content. Call
+ parse() if you want to access individual headers, sub-Contents or the
+ encapsulated message.
+
+ @param l is a list of the raw Content data, split by lines.
+ */
+ void setContent( const QList<QByteArray> &l );
+
+ /**
+ Sets the Content to the given raw data, containing the Content head and
+ body separated by two linefeeds.
+
+ This method operates on the string representation of the Content. Call
+ parse() if you want to access individual headers, sub-Contents or the
+ encapsulated message.
+
+ @note The passed data must not contain any CRLF sequences, only LF.
+ Use CRLFtoLF for conversion before passing in the data.
+
+ @param s is a QByteArray containing the raw Content data.
+ */
+ void setContent( const QByteArray &s );
+
+ /**
+ * Parses the Content.
+ *
+ * This means the broken-down object representation of the Content is
+ * updated from the string representation of the Content.
+ *
+ * Call this if you want to access or change headers, sub-Contents or the encapsulated
+ * message.
+ *
+ * @note Calling parse() twice will not work for multipart contents or for contents of which
+ * the body is an encapsulated message. The reason is that the first parse() will delete
+ * the body, so there is no body to work on for the second call of parse().
+ *
+ * @note Calling this will reset the message returned by bodyAsMessage(), as
+ * the message is re-parsed as well.
+ * Also, all old sub-contents will be deleted, so any old Content pointer will become
+ * invalid.
+ */
+ virtual void parse();
+
+ /**
+ Returns whether this Content is frozen.
+ A frozen content is immutable, i.e. calling assemble() will never modify
+ its head or body, and encodedContent() will return the same data before
+ and after parsing.
+
+ @since 4.4.
+ @see setFrozen().
+ */
+ bool isFrozen() const;
+
+ /**
+ Freezes this Content if @p frozen is true; otherwise unfreezes it.
+
+ @since 4.4
+ @see isFrozen().
+ */
+ void setFrozen( bool frozen = true );
+
+ /**
+ Generates the MIME content.
+ This means the string representation of this Content is updated from the
+ broken-down object representation.
+ Call this if you have made changes to the content, and want
+ encodedContent() to reflect those changes.
+
+ @note assemble() has no effect if the Content isFrozen(). You may want
+ to freeze, for instance, signed sub-Contents, to make sure they are kept
+ unmodified.
+
+ @note If this content is an encapsulated message, i.e. bodyIsMessage() returns true,
+ then calling assemble() will also assemble the message returned by bodyAsMessage().
+
+ @warning assemble() may change the order of the headers, and other
+ details such as where folding occurs. This may break things like
+ signature verification, so you should *ONLY* call assemble() when you
+ have actually modified the content.
+ */
+ virtual void assemble();
+
+ /**
+ Clears the content, deleting all headers and sub-Contents.
+ */
+ // KDE5: make non-virtual.
+ virtual void clear();
+
+ /**
+ Removes all sub-Contents from this content. Deletes them if @p del is true.
+ This is different from calling removeContent() on each sub-Content, because
+ removeContent() will convert this to a single-part Content if only one
+ sub-Content is left. Calling clearContents() does NOT make this Content
+ single-part.
+
+ @param del Whether to delete the sub-Contents.
+ @see removeContent()
+ @since 4.4
+ */
+ void clearContents( bool del = true );
+
+ /**
+ Returns the Content header raw data.
+
+ @see setHead().
+ */
+ QByteArray head() const;
+
+ /**
+ Sets the Content header raw data.
+
+ This method operates on the string representation of the Content. Call
+ parse() if you want to access individual headers.
+
+ @param head is a QByteArray containing the header data.
+
+ @see head().
+ */
+ void setHead( const QByteArray &head );
+
+ /**
+ Extracts and removes the next header from @p head.
+ The caller is responsible for deleting the returned header.
+
+ @deprecated Use KMime::HeaderParsing::extractFirstHeader().
+ @param head is a QByteArray containing the header data.
+ */
+ KDE_DEPRECATED Headers::Generic *getNextHeader( QByteArray &head );
+
+ /**
+ Extracts and removes the next header from @p head.
+ The caller is responsible for deleting the returned header.
+ @since 4.2
+ @deprecated Use KMime::HeaderParsing::extractFirstHeader().
+ @param head is a QByteArray containing the header data.
+ */
+ // KDE5: Remove this. This method has nothing to do with *this object.
+ KDE_DEPRECATED Headers::Generic *nextHeader( QByteArray &head );
+
+ /**
+ Tries to find a @p type header in the Content and returns it.
+ @deprecated Use headerByType( const char * )
+ */
+ // KDE5: Make non-virtual.
+ KDE_DEPRECATED virtual Headers::Base *getHeaderByType( const char *type );
+
+ /**
+ Returns the first header of type @p type, if it exists. Otherwise returns 0.
+ Note that the returned header may be empty.
+ @since 4.2
+ */
+ // KDE5: Make non-virtual.
+ virtual Headers::Base *headerByType( const char *type );
+
+ /**
+ Returns the first header of type T, if it exists.
+ If the header does not exist and @p create is true, creates an empty header
+ and returns it. Otherwise returns 0.
+ Note that the returned header may be empty.
+ @param create Whether to create the header if it does not exist.
+ @since 4.4.
+
+ KDE5: BIC: FIXME: Why is the default argument false here? That is inconsistent with the
+ methods in KMime::Message!
+ */
+ template <typename T> T *header( bool create = false );
+
+ /**
+ Returns all @p type headers in the Content.
+ Take care that this result is not cached, so could be slow.
+ @since 4.2
+ */
+ virtual QList<Headers::Base*> headersByType( const char *type );
+
+ /**
+ Sets the specified header to this Content.
+ Any previous header of the same type is removed.
+ If you need multiple headers of the same type, use appendHeader() or
+ prependHeader().
+
+ @param h The header to set.
+ @see appendHeader()
+ @see removeHeader()
+ @since 4.4
+ */
+ // KDE5: make non-virtual.
+ virtual void setHeader( Headers::Base *h );
+
+ /**
+ Appends the specified header to the headers of this Content.
+ @param h The header to append.
+ @since 4.4
+ */
+ void appendHeader( Headers::Base *h );
+
+ /**
+ Prepends the specified header to the headers of this Content.
+ @param h The header to prepend.
+ @since 4.4
+ */
+ void prependHeader( Headers::Base *h );
+
+ /**
+ Searches for the first header of type @p type, and deletes it, removing
+ it from this Content.
+ @param type The type of the header to look for.
+ @return true if a header was found and removed.
+ */
+ // TODO probably provide removeHeader<T>() too.
+ // KDE5: make non-virtual.
+ virtual bool removeHeader( const char *type );
+
+ /**
+ @return true if this Content has a header of type @p type.
+ @param type The type of the header to look for.
+ */
+ // TODO probably provide hasHeader<T>() too.
+ // TODO: KDE5: make const
+ bool hasHeader( const char *type );
+
+ /**
+ Returns the Content-Type header.
+
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ Headers::ContentType *contentType( bool create = true );
+
+ /**
+ Returns the Content-Transfer-Encoding header.
+
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ Headers::ContentTransferEncoding *contentTransferEncoding( bool create = true );
+
+ /**
+ Returns the Content-Disposition header.
+
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ Headers::ContentDisposition *contentDisposition( bool create = true );
+
+ /**
+ Returns the Content-Description header.
+
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ Headers::ContentDescription *contentDescription( bool create = true );
+
+ /**
+ Returns the Content-Location header.
+
+ @param create If true, create the header if it doesn't exist yet.
+ @since 4.2
+ */
+ Headers::ContentLocation *contentLocation( bool create = true );
+
+ /**
+ Returns the Content-ID header.
+ @param create if true, create the header if it does not exist yet.
+ @since 4.4
+ */
+ Headers::ContentID *contentID( bool create = true );
+
+ /**
+ Returns the size of the Content body after encoding.
+ (If the encoding is quoted-printable, this is only an approximate size.)
+ This will return 0 for multipart contents or for encapsulated messages.
+ */
+ int size();
+
+ /**
+ Returns the size of this Content and all sub-Contents.
+ */
+ int storageSize() const;
+
+ /**
+ Line count of this Content and all sub-Contents.
+ */
+ int lineCount() const;
+
+ /**
+ Returns the Content body raw data.
+
+ Note that this will be empty for multipart contents or for encapsulated messages,
+ after parse() has been called.
+
+ @see setBody().
+ */
+ QByteArray body() const;
+
+ /**
+ Sets the Content body raw data.
+
+ This method operates on the string representation of the Content. Call
+ parse() if you want to access individual sub-Contents or the encapsulated message.
+
+ @param body is a QByteArray containing the body data.
+
+ @see body().
+ */
+ void setBody( const QByteArray &body );
+
+ /**
+ Returns the MIME preamble.
+
+ @return a QByteArray containing the MIME preamble.
+
+ @since 4.9
+ */
+ QByteArray preamble() const;
+
+ /**
+ Sets the MIME preamble.
+
+ @param preamble a QByteArray containing what will be used as the
+ MIME preamble.
+
+ @since 4.9
+ */
+
+ void setPreamble( const QByteArray &preamble );
+
+ /**
+ Returns the MIME preamble.
+
+ @return a QByteArray containing the MIME epilogue.
+
+ @since 4.9
+ */
+ QByteArray epilogue() const;
+
+ /**
+ Sets the MIME preamble.
+
+ @param epilogue a QByteArray containing what will be used as the
+ MIME epilogue.
+
+ @since 4.9
+ */
+ void setEpilogue( const QByteArray &epilogue );
+
+ /**
+ Returns a QByteArray containing the encoded Content, including the
+ Content header and all sub-Contents.
+
+ If you make changes to the broken-down representation of the message, be
+ sure to first call assemble() before calling encodedContent(), otherwise
+ the result will not be up-to-date.
+
+ If this content is an encapsulated message, i.e. bodyIsMessage() returns true,
+ then encodedContent() will use the message returned by bodyAsMessage() as the
+ body of the result, calling encodedContent() on the message.
+
+ @param useCrLf If true, use @ref CRLF instead of @ref LF for linefeeds.
+ */
+ QByteArray encodedContent( bool useCrLf = false );
+
+ /**
+ * Like encodedContent(), with the difference that only the body will be returned, i.e. the
+ * headers are excluded.
+ *
+ * @since 4.6
+ */
+ QByteArray encodedBody();
+
+ /**
+ * Returns the decoded Content body.
+ *
+ * Note that this will be empty for multipart contents or for encapsulated messages,
+ * after parse() has been called.
+ */
+ // TODO: KDE5: BIC: Rename this to decodedBody(), since only the body is returned.
+ // In contrast, setContent() sets the head and the body!
+ // Also, try to make this const.
+ QByteArray decodedContent();
+
+ /**
+ Returns the decoded text. Additional to decodedContent(), this also
+ applies charset decoding. If this is not a text Content, decodedText()
+ returns an empty QString.
+
+ @param trimText If true, then the decoded text will have all trailing
+ whitespace removed.
+ @param removeTrailingNewlines If true, then the decoded text will have
+ all consecutive trailing newlines removed.
+
+ The last trailing new line of the decoded text is always removed.
+
+ */
+ // TODO: KDE5: BIC: Convert to enums. Also, what if trimText = true but removeTrailingNewlines
+ // is false?
+ QString decodedText( bool trimText = false,
+ bool removeTrailingNewlines = false );
+
+ /**
+ Sets the Content body to the given string using charset of the content type.
+
+ If the charset can not be found, the system charset is taken and the content type header is
+ changed to that charset.
+ The charset of the content type header should be set to a charset that can encode the given
+ string before calling this method.
+
+ This method does not set the content transfer encoding automatically, it needs to be set
+ to a suitable value that can encode the given string before calling this method.
+
+ This method only makes sense for single-part contents, do not try to pass a multipart body
+ or an encapsulated message here, that wouldn't work.
+
+ @param s Unicode-encoded string.
+ */
+ void fromUnicodeString( const QString &s );
+
+ /**
+ Returns the first Content with mimetype text/.
+ */
+ Content *textContent();
+
+ /**
+ Returns a list of attachments.
+
+ @param incAlternatives If true, include multipart/alternative parts.
+ */
+ List attachments( bool incAlternatives = false );
+
+ /**
+ * For multipart contents, this will return a list of all multipart child contents.
+ * For contents that are of mimetype message/rfc822, this will return a list with one entry,
+ * and that entry is the encapsulated message, as it would be returned by bodyAsMessage().
+ */
+ List contents() const;
+
+ /**
+ Adds a new sub-Content. If the sub-Content is already part of another
+ Content object, it is removed from there and its parent is updated.
+ If the current Content object is single-part, it is converted to
+ multipart/mixed first.
+
+ @warning If the single-part to multipart conversion happens, all
+ pointers you may have into this object (such as headers) will become
+ invalid!
+
+ @param content The new sub-Content.
+ @param prepend If true, prepend to the Content list; otherwise append.
+ to the Content list.
+
+ @see removeContent().
+ */
+ // KDE5: Do not convert single-part->multipart automatically.
+ void addContent( Content *content, bool prepend = false );
+
+ /**
+ Removes the given sub-Content. If only one sub-Content is left, the
+ current Content object is converted into a single-part Content.
+
+ @warning If the multipart to single-part conversion happens, the head
+ and body of the single remaining sub-Content are copied over, and the
+ sub-Content is deleted. All pointers to it or into it (such as headers)
+ will become invalid!
+
+ @param content The Content to remove.
+ @param del If true, delete the removed Content object. Otherwise set its
+ parent to 0.
+
+ @see addContent().
+ @see clearContents().
+ */
+ // KDE5: Do not convert multipart->single-part automatically.
+ void removeContent( Content *content, bool del = false );
+
+ /**
+ Changes the encoding of this Content to @p e. If the Content is binary,
+ this actually re-encodes the data to use the new encoding.
+
+ @param e The new encoding to use.
+ */
+ void changeEncoding( Headers::contentEncoding e );
+
+ /**
+ Saves the encoded Content to the given textstream
+
+ @param ts is the stream where the Content should be written to.
+ @param scrambleFromLines: If true, replace "\nFrom " with "\n>From "
+ in the stream. This is needed to avoid problem with mbox-files
+ */
+ void toStream( QTextStream &ts, bool scrambleFromLines = false );
+
+ // NOTE: The charset methods below are accessed by the headers which
+ // have this Content as a parent.
+
+ /**
+ Returns the charset that is used to decode RFC2047 strings in all headers and to decode
+ the body if the charset is not declared explictly.
+ It is also used as the charset when encoding RFC2047 strings in headers.
+
+ @see setDefaultCharset()
+ */
+ // TODO: Split this up into a charset for encoding and one for decoding, and make the one for
+ // encoding UTF-8 by default.
+ QByteArray defaultCharset() const;
+
+ /**
+ Sets the default charset.
+
+ @param cs is a QByteArray containing the new default charset.
+
+ @see defaultCharset().
+ */
+ void setDefaultCharset( const QByteArray &cs );
+
+ /**
+ Use the default charset even if a different charset is
+ declared in the article.
+
+ @see setForceDefaultCharset().
+ */
+ bool forceDefaultCharset() const;
+
+ /**
+ Enables/disables the force mode, housekeeping.
+ works correctly only when the article is completely empty or
+ completely loaded.
+
+ @param b If true, force the default charset to be used.
+
+ @see forceDefaultCharset().
+ */
+ virtual void setForceDefaultCharset( bool b );
+
+ /**
+ Returns the Content specified by the given index.
+ If the index does not point to a Content, 0 is returned. If the index
+ is invalid (empty), this Content is returned.
+
+ @param index The Content index.
+ */
+ Content *content( const ContentIndex &index ) const;
+
+ /**
+ Returns the ContentIndex for the given Content, or an invalid index
+ if the Content is not found within the hierarchy.
+ @param content the Content object to search.
+ */
+ ContentIndex indexForContent( Content *content ) const;
+
+ /**
+ Returns true if this is the top-level node in the MIME tree. The top-level node is always
+ a Message or NewsArticle. However, a node can be a Message without being a top-level node when
+ it is an encapsulated message.
+ */
+ virtual bool isTopLevel() const;
+
+ /**
+ * Sets a new parent to the Content and add to its contents list. If it already had a parent, it is removed from the
+ * old parents contents list.
+ * @param parent the new parent
+ * @since 4.3
+ */
+ void setParent( Content *parent );
+
+ /**
+ * Returns the parent content object, or 0 if the content doesn't have a parent.
+ * @since 4.3
+ */
+ Content* parent() const;
+
+ /**
+ * Returns the toplevel content object, 0 if there is no such object.
+ * @since 4.3
+ */
+ Content* topLevel() const;
+
+ /**
+ * Returns the index of this Content based on the topLevel() object.
+ * @since 4.3
+ */
+ ContentIndex index() const;
+
+ /**
+ * @return true if this content is an encapsulated message, i.e. if it has the mimetype
+ * message/rfc822.
+ *
+ * @since 4.5
+ */
+ //AK_REVIEW: move to MessageViewer/ObjectTreeParser
+ bool bodyIsMessage() const;
+
+ /**
+ * If this content is an encapsulated message, in which case bodyIsMessage() will return
+ * true, the message represented by the body of this content will be returned.
+ * The returned message is already fully parsed.
+ * Calling this method is the aquivalent of calling contents().first() and casting the result
+ * to a KMime::Message*. bodyAsMessage() has the advantage that it will return a shared pointer
+ * that will not be destroyed when the container message is destroyed or re-parsed.
+ *
+ * The message that is returned here is created when calling parse(), so make sure to call
+ * parse() first. Since each parse() creates a new message object, a different message object
+ * will be returned each time you call parse().
+ *
+ * If you make changes to the returned message, you need to call assemble() on this content
+ * or on the message if you want that encodedContent() reflects these changes. This also means
+ * that calling assemble() on this content will assemble the returned message.
+ *
+ * @since 4.5
+ */
+ //AK_REVIEW: move to MessageViewer/ObjectTreeParser
+ boost::shared_ptr<Message> bodyAsMessage() const;
+
+ protected:
+ /**
+ Reimplement this method if you need to assemble additional headers in a
+ derived class. Don't forget to call the implementation of the base class.
+ @return The raw, assembled headers.
+ */
+ virtual QByteArray assembleHeaders();
+
+ /**
+ Returns the raw string representing the header of type @p name.
+ @deprecated Use KMime::extractHeader() directly instead.
+ */
+ KDE_DEPRECATED QByteArray rawHeader( const char *name ) const;
+
+ /**
+ Returns a list of raw strings representing all header of type @p name.
+ @deprecated Use KMime::extractHeaders() directly instead.
+ */
+ KDE_DEPRECATED QList<QByteArray> rawHeaders( const char *name ) const;
+
+ /**
+ Returns whether this object holds text content.
+ */
+ // KDE5: Not needed outside. Move to Private class.
+ bool decodeText();
+
+ /**
+ Returns the first header of type T, if it exists.
+ @deprecated Use header() instead.
+ */
+ template <class T> KDE_DEPRECATED T *headerInstance( T *ptr, bool create );
+
+ /**
+ The list of headers in this Content.
+ Do not use this directly.
+ */
+ // KDE5: Not needed outside. Move to Private class.
+ Headers::Base::List h_eaders;
+
+ //@cond PRIVATE
+ ContentPrivate *d_ptr;
+ explicit Content( ContentPrivate *d );
+ //@endcond
+
+ private:
+ Q_DECLARE_PRIVATE( Content )
+ Q_DISABLE_COPY( Content )
+};
+
+// some compilers (for instance Compaq C++) need template inline functions
+// here rather than in the *.cpp file
+
+template <class T> T *Content::headerInstance( T *ptr, bool create )
+{
+ return header<T>( create );
+}
+
+template <typename T> T *Content::header( bool create )
+{
+ Headers::Base *h = headerByType( T::staticType() );
+ if( h ) {
+ // Make sure the header is actually of the right type.
+ Q_ASSERT( dynamic_cast<T*>( h ) );
+ } else if( create ) {
+ h = new T( this );
+ setHeader( h );
+ }
+ return static_cast<T*>( h );
+}
+
+} // namespace KMime
+
+#endif // __KMIME_CONTENT_H__
diff --git a/kmime/kmime_content_p.h b/kmime/kmime_content_p.h
new file mode 100644
index 0000000..f09d293
--- /dev/null
+++ b/kmime/kmime_content_p.h
@@ -0,0 +1,85 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library 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 Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KMIME_CONTENT_P_H
+#define KMIME_CONTENT_P_H
+
+//@cond PRIVATE
+
+#include <boost/shared_ptr.hpp>
+
+namespace KMime {
+ class Message;
+ typedef boost::shared_ptr<Message> MessagePtr;
+}
+
+namespace KMime {
+
+class ContentPrivate
+{
+ public:
+ ContentPrivate( Content *q ) :
+ parent( 0 ),
+ q_ptr( q ),
+ forceDefaultCS( false ),
+ frozen( false )
+ {
+ defaultCS = KMime::cachedCharset( "ISO-8859-1" );
+ }
+
+ virtual ~ContentPrivate()
+ {
+ qDeleteAll( multipartContents );
+ multipartContents.clear();
+ }
+
+ bool parseUuencoded();
+ bool parseYenc();
+ bool parseMultipart();
+ Headers::Generic *nextHeader( QByteArray &head );
+ void clearBodyMessage();
+
+ // This one returns the normal multipartContents for multipart contents, but returns
+ // a list with just bodyAsMessage in it for contents that are encapsulated messages.
+ // That makes it possible to handle encapsulated messages in a transparent way.
+ Content::List contents() const;
+
+ QByteArray head;
+ QByteArray body;
+ QByteArray frozenBody;
+ QByteArray defaultCS;
+ QByteArray preamble;
+ QByteArray epilogue;
+ Content *parent;
+
+ Content::List multipartContents;
+ MessagePtr bodyAsMessage;
+
+ Content* q_ptr;
+ Q_DECLARE_PUBLIC( Content )
+
+ bool forceDefaultCS : 1;
+ bool frozen : 1;
+};
+
+}
+
+//@endcond
+
+#endif
diff --git a/kmime/kmime_contentindex.cpp b/kmime/kmime_contentindex.cpp
new file mode 100644
index 0000000..a5301e5
--- /dev/null
+++ b/kmime/kmime_contentindex.cpp
@@ -0,0 +1,120 @@
+/*
+ Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library 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 Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the ContentIndex class.
+
+ @brief
+ Defines the ContentIndex class.
+
+ @authors Volker Krause \<vkrause@kde.org\>
+*/
+
+#include "kmime_contentindex.h"
+
+#include <QHash>
+#include <QSharedData>
+#include <QtCore/QStringList>
+
+using namespace KMime;
+
+class ContentIndex::Private : public QSharedData
+{
+ public:
+ Private() {}
+ Private( const Private &other ) : QSharedData( other )
+ {
+ index = other.index;
+ }
+
+ QList<unsigned int> index;
+};
+
+KMime::ContentIndex::ContentIndex() : d( new Private )
+{
+}
+
+KMime::ContentIndex::ContentIndex( const QString &index ) : d( new Private )
+{
+ const QStringList l = index.split( QLatin1Char( '.' ) );
+ foreach ( const QString &s, l ) {
+ bool ok;
+ unsigned int i = s.toUInt( &ok );
+ if ( !ok ) {
+ d->index.clear();
+ break;
+ }
+ d->index.append( i );
+ }
+}
+
+ContentIndex::ContentIndex(const ContentIndex & other) : d( other.d )
+{
+}
+
+ContentIndex::~ContentIndex()
+{
+}
+
+bool KMime::ContentIndex::isValid() const
+{
+ return !d->index.isEmpty();
+}
+
+unsigned int KMime::ContentIndex::pop()
+{
+ return d->index.takeFirst();
+}
+
+void KMime::ContentIndex::push( unsigned int index )
+{
+ d->index.prepend( index );
+}
+
+QString KMime::ContentIndex::toString() const
+{
+ QStringList l;
+ foreach ( unsigned int i, d->index ) {
+ l.append( QString::number( i ) );
+ }
+ return l.join( QLatin1String( "." ) );
+}
+
+bool KMime::ContentIndex::operator ==( const ContentIndex &index ) const
+{
+ return d->index == index.d->index;
+}
+
+bool KMime::ContentIndex::operator !=( const ContentIndex &index ) const
+{
+ return d->index != index.d->index;
+}
+
+ContentIndex& ContentIndex::operator =(const ContentIndex & other)
+{
+ if ( this != &other )
+ d = other.d;
+ return *this;
+}
+
+uint qHash( const KMime::ContentIndex &index )
+{
+ return qHash( index.toString() );
+}
diff --git a/kmime/kmime_contentindex.h b/kmime/kmime_contentindex.h
new file mode 100644
index 0000000..b589444
--- /dev/null
+++ b/kmime/kmime_contentindex.h
@@ -0,0 +1,139 @@
+/*
+ Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library 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 Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the ContentIndex class.
+
+ @brief
+ Defines the ContentIndex class.
+
+ @authors Volker Krause \<vkrause@kde.org\>
+
+ @glossary @anchor RFC3501 @anchor rfc3501 @b RFC @b 3501:
+ RFC that defines the <a href="http://tools.ietf.org/html/rfc3501">
+ Internet Message Access Protocol (IMAP)</a>.
+*/
+
+#ifndef KMIME_CONTENTINDEX_H
+#define KMIME_CONTENTINDEX_H
+
+#include "kmime_export.h"
+
+#include <QtCore/QList>
+#include <QtCore/QSharedDataPointer>
+#include <QtCore/QString>
+
+
+namespace KMime {
+
+/**
+ @brief
+ A class to uniquely identify message parts (Content) in a hierarchy.
+
+ This class is implicitly shared.
+
+ Based on @ref RFC3501 section 6.4.5 and thus compatible with @acronym IMAP.
+*/
+class KMIME_EXPORT ContentIndex
+{
+ public:
+ /**
+ Creates an empty content index.
+ */
+ ContentIndex();
+
+ /**
+ Creates a content index based on the specified string representation.
+
+ @param index is a string representation of a message part index according
+ to @ref RFC3501 section 6.4.5.
+ */
+ explicit ContentIndex( const QString &index );
+
+ /**
+ Copy constructor.
+ */
+ ContentIndex( const ContentIndex &other );
+
+ /**
+ Destructor.
+ */
+ ~ContentIndex();
+
+ /**
+ Returns true if this index is non-empty (valid).
+ */
+ bool isValid() const;
+
+ /**
+ Removes and returns the top-most index. Used to recursively
+ descend into the message part hierarchy.
+
+ @see push().
+ */
+ unsigned int pop();
+
+ /**
+ Adds @p index to the content index. Used when ascending the message
+ part hierarchy.
+
+ @param index is the top-most content index part.
+
+ @see pop().
+ */
+ void push( unsigned int index );
+
+ /**
+ Returns a string representation of this content index according
+ to @ref RFC3501 section 6.4.5.
+ */
+ QString toString() const;
+
+ /**
+ Compares this with @p index for equality.
+
+ @param index is the content index to compare.
+ */
+ bool operator==( const ContentIndex &index ) const;
+
+ /**
+ Compares this with @p index for inequality.
+
+ @param index is the content index to compare.
+ */
+ bool operator!=( const ContentIndex &index ) const;
+
+ /**
+ Assignment operator.
+ */
+ ContentIndex& operator=( const ContentIndex &other );
+
+ private:
+ //@cond PRIVATE
+ class Private;
+ QSharedDataPointer<Private> d;
+ //@endcond
+};
+
+} //namespace KMime
+
+KMIME_EXPORT uint qHash( const KMime::ContentIndex& );
+
+#endif
diff --git a/kmime/kmime_dateformatter.cpp b/kmime/kmime_dateformatter.cpp
new file mode 100644
index 0000000..c9a471f
--- /dev/null
+++ b/kmime/kmime_dateformatter.cpp
@@ -0,0 +1,336 @@
+/*
+ kmime_dateformatter.cpp
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001 the KMime authors.
+ See file AUTHORS for details
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the DateFormatter class.
+
+ @brief
+ Defines the DateFormatter class.
+
+ @authors the KMime authors (see AUTHORS file)
+*/
+
+#include "kmime_dateformatter.h"
+
+#include <config-kmime.h>
+
+#include <stdlib.h> // for abs()
+
+#include <QtCore/QTextStream>
+
+#include <kglobal.h>
+#include <klocale.h>
+#include <kcalendarsystem.h>
+
+using namespace KMime;
+
+//@cond PRIVATE
+int DateFormatter::mDaylight = -1;
+//@endcond
+DateFormatter::DateFormatter( FormatType ftype )
+ : mFormat( ftype ), mTodayOneSecondBeforeMidnight( 0 )
+{
+}
+
+DateFormatter::~DateFormatter()
+{
+}
+
+DateFormatter::FormatType DateFormatter::format() const
+{
+ return mFormat;
+}
+
+void DateFormatter::setFormat( FormatType ftype )
+{
+ mFormat = ftype;
+}
+
+QString DateFormatter::dateString( time_t t , const QString &lang ,
+ bool shortFormat, bool includeSecs ) const
+{
+ switch ( mFormat ) {
+ case Fancy:
+ return fancy( t );
+ break;
+ case Localized:
+ return localized( t, shortFormat, includeSecs, lang );
+ break;
+ case CTime:
+ return cTime( t );
+ break;
+ case Iso:
+ return isoDate( t );
+ break;
+ case Rfc:
+ return rfc2822( t );
+ break;
+ case Custom:
+ return custom( t );
+ break;
+ }
+ return QString();
+}
+
+QString DateFormatter::dateString( const QDateTime &dt, const QString &lang,
+ bool shortFormat, bool includeSecs ) const
+{
+ return dateString( qdateToTimeT( dt ), lang, shortFormat, includeSecs );
+}
+
+QString DateFormatter::rfc2822( time_t t ) const
+{
+ QDateTime tmp;
+ QString ret;
+
+ tmp.setTime_t( t );
+
+ ret = tmp.toString( QLatin1String( "ddd, dd MMM yyyy hh:mm:ss " ) );
+ ret += QLatin1String( zone( t ) );
+
+ return ret;
+}
+
+QString DateFormatter::custom( time_t t ) const
+{
+ if ( mCustomFormat.isEmpty() ) {
+ return QString();
+ }
+
+ int z = mCustomFormat.indexOf( QLatin1Char( 'Z' ) );
+ QDateTime d;
+ QString ret = mCustomFormat;
+
+ d.setTime_t( t );
+ if ( z != -1 ) {
+ ret.replace( z, 1, QLatin1String( zone( t ) ) );
+ }
+
+ ret = d.toString( ret );
+
+ return ret;
+}
+
+void DateFormatter::setCustomFormat( const QString &format )
+{
+ mCustomFormat = format;
+ mFormat = Custom;
+}
+
+QString DateFormatter::customFormat() const
+{
+ return mCustomFormat;
+}
+
+QByteArray DateFormatter::zone( time_t t ) const
+{
+#if defined(HAVE_TIMEZONE) || defined(HAVE_TM_GMTOFF)
+ struct tm *local = localtime( &t );
+#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;
+ }
+
+#elif defined(HAVE_TM_GMTOFF)
+
+ int secs = abs( local->tm_gmtoff );
+ int neg = (local->tm_gmtoff < 0) ? 1 : 0;
+ int hours = secs / 3600;
+ int mins = (secs - hours * 3600) / 60;
+
+ if ( local->tm_isdst > 0 ) {
+ mDaylight = 1;
+ } else {
+ mDaylight = 0;
+ }
+
+#else
+
+ QDateTime d1 = QDateTime::fromString( QString::fromAscii( asctime( gmtime( &t ) ) ) );
+ QDateTime d2 = QDateTime::fromString( QString::fromAscii( asctime( localtime( &t ) ) ) );
+ 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
+
+#endif /* HAVE_TIMEZONE */
+
+ QByteArray ret;
+ QTextStream s( &ret, QIODevice::WriteOnly );
+ s << ( neg ? '-' : '+' )
+ << qSetFieldWidth( 2 ) << qSetPadChar( QLatin1Char( '0' ) ) << right << hours << mins;
+ //old code: ret.sprintf( "%c%.2d%.2d", (neg) ? '-' : '+', hours, mins );
+
+ return ret;
+}
+
+time_t DateFormatter::qdateToTimeT( const QDateTime &dt ) const
+{
+ QDateTime epoch( QDate( 1970, 1, 1 ), QTime( 00, 00, 00 ) );
+ time_t t;
+ time( &t );
+
+ QDateTime d1 = QDateTime::fromString( QLatin1String( asctime( gmtime( &t ) ) ) );
+ QDateTime d2 = QDateTime::fromString( QLatin1String( asctime( localtime( &t ) ) ) );
+ time_t drf = epoch.secsTo( dt ) - d1.secsTo( d2 );
+
+ return drf;
+}
+
+QString DateFormatter::fancy( time_t t ) const
+{
+ KLocale *locale = KGlobal::locale();
+
+ if ( t <= 0 ) {
+ return i18nc( "invalid time specified", "unknown" );
+ }
+
+ if ( mTodayOneSecondBeforeMidnight < time( 0 ) ) {
+ // determine time_t value of today 23:59:59
+ const QDateTime today( QDate::currentDate(), QTime( 23, 59, 59 ) );
+ mTodayOneSecondBeforeMidnight = today.toTime_t();
+ }
+
+ QDateTime old;
+ old.setTime_t( t );
+
+ if ( mTodayOneSecondBeforeMidnight >= t ) {
+ const time_t diff = mTodayOneSecondBeforeMidnight - t;
+ if ( diff < 7 * 24 * 60 * 60 ) {
+ if ( diff < 24 * 60 * 60 ) {
+ return i18n( "Today %1",
+ locale->formatTime( old.time(), true ) );
+ }
+ if ( diff < 2 * 24 * 60 * 60 ) {
+ return i18n( "Yesterday %1",
+ locale->formatTime( old.time(), true ) );
+ }
+ for ( int i = 3; i < 8; i++ ) {
+ if ( diff < i * 24 * 60 * 60 ) {
+ return i18nc( "1. weekday, 2. time", "%1 %2" ,
+ locale->calendar()->weekDayName( old.date() ) ,
+ locale->formatTime( old.time(), true ) );
+ }
+ }
+ }
+ }
+
+ return locale->formatDateTime( old );
+}
+
+QString DateFormatter::localized( time_t t, bool shortFormat, bool includeSecs,
+ const QString &lang ) const
+{
+ QDateTime tmp;
+ QString ret;
+ KLocale *locale = KGlobal::locale();
+
+ tmp.setTime_t( t );
+
+ if ( !lang.isEmpty() ) {
+ locale = new KLocale( lang, lang, lang);
+ ret = locale->formatDateTime( tmp, (shortFormat ? KLocale::ShortDate : KLocale::LongDate), includeSecs );
+ delete locale;
+ } else {
+ ret = locale->formatDateTime( tmp, (shortFormat ? KLocale::ShortDate : KLocale::LongDate), includeSecs );
+ }
+
+ return ret;
+}
+
+QString DateFormatter::cTime( time_t t ) const
+{
+ return QString::fromLatin1( ctime( &t ) ).trimmed();
+}
+
+QString DateFormatter::isoDate( time_t t ) const
+{
+ char cstr[64];
+ strftime( cstr, 63, "%Y-%m-%d %H:%M:%S", localtime( &t ) );
+ return QLatin1String( cstr );
+}
+
+void DateFormatter::reset()
+{
+ mTodayOneSecondBeforeMidnight = 0;
+}
+
+QString DateFormatter::formatDate( FormatType ftype, time_t t,
+ const QString &data, bool shortFormat,
+ bool includeSecs )
+{
+ DateFormatter f( ftype );
+ if ( ftype == Custom ) {
+ f.setCustomFormat( data );
+ }
+ return f.dateString( t, data, shortFormat, includeSecs );
+}
+
+QString DateFormatter::formatCurrentDate( FormatType ftype, const QString &data,
+ bool shortFormat, bool includeSecs )
+{
+ DateFormatter f( ftype );
+ if ( ftype == Custom ) {
+ f.setCustomFormat( data );
+ }
+ return f.dateString( time( 0 ), data, shortFormat, includeSecs );
+}
+
+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;
+ }
+}
diff --git a/kmime/kmime_dateformatter.h b/kmime/kmime_dateformatter.h
new file mode 100644
index 0000000..35b4431
--- /dev/null
+++ b/kmime/kmime_dateformatter.h
@@ -0,0 +1,295 @@
+/* -*- c++ -*-
+ kmime_dateformatter.h
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001 the KMime authors.
+ See file AUTHORS for details
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the DateFormatter class.
+
+ @brief
+ Defines the DateFormatter class.
+
+ @authors the KMime authors (see AUTHORS file)
+
+ @glossary @anchor RFC2822 @anchor rfc2822 @b RFC @b 2822:
+ RFC that defines the <a href="http://tools.ietf.org/html/rfc2822">
+ Internet Message Format</a>.
+
+ @glossary @anchor ISO8601 @anchor iso8601 @b ISO @b 8601:
+ International Standards Organization (ISO) standard that defines the
+ <a href="http://http://en.wikipedia.org/wiki/ISO_8601">
+ international standard for date and time representations</a>.
+
+ @glossary @anchor ctime @b ctime:
+ a Unix library call which returns the local time as a human readable
+ ASCII string of the form "Sun Mar 31 02:08:35 2002".
+*/
+
+#ifndef __KMIME_DATEFORMATTER_H__
+#define __KMIME_DATEFORMATTER_H__
+
+#include <time.h>
+#include <QtCore/QDateTime>
+#include <QtCore/QString>
+#include "kmime_export.h"
+
+namespace KMime {
+
+/**
+ @brief
+ A class for abstracting date formatting.
+
+ This class deals with different kinds of date display formats.
+ The formats supported include:
+ - @b fancy "Today 02:08:35"
+ - @b ctime as with the @ref ctime function, eg. "Sun Mar 31 02:08:35 2002"
+ - @b localized according to the control center setting, eg. "2002-03-31 02:08"
+ - @b iso according to the @ref ISO8601 standard, eg. "2002-03-31 02:08:35"
+ - @b rfc according to @ref RFC2822 (Section 3.3), eg. "Sun, 31 Mar 2002 02:08:35 -0500"
+ - @b custom "whatever you like"
+*/
+class KMIME_EXPORT DateFormatter
+{
+ public:
+ /**
+ The different types of date formats.
+ */
+ 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" */
+ Rfc, /**< rfc "Sun, 31 Mar 2002 02:08:35 -0500" */
+ Custom /**< custom "whatever you like" */
+ };
+
+ /**
+ Constructs a date formatter with a default #FormatType.
+
+ @param ftype is the default #FormatType to use.
+ */
+ explicit DateFormatter( FormatType ftype=DateFormatter::Fancy );
+
+ /**
+ Destroys the date formatter.
+ */
+ ~DateFormatter();
+
+ /**
+ Returns the #FormatType currently set.
+
+ @see setFormat().
+ */
+ FormatType format() const;
+
+ /**
+ Sets the date format to @p ftype.
+
+ @param ftype is the #FormatType.
+
+ @see format().
+ */
+ void setFormat( FormatType ftype );
+
+ /**
+ Constructs a formatted date string from time_t @p t.
+
+ @param t is the time_t to use for formatting.
+ @param lang is the language, only used if #FormatType is #Localized.
+ @param shortFormat if true, create the short version of the date string,
+ only used if #FormatType is #Localized.
+ @param includeSecs if true, include the seconds field in the date string,
+ only used if #FormatType is #Localized.
+
+ @return a QString containing the formatted date.
+ */
+ QString dateString( time_t t, const QString &lang=QString(),
+ bool shortFormat=true, bool includeSecs=false ) const;
+
+ /**
+ Constructs a formatted date string from QDateTime @p dtime.
+
+ @param dtime is the QDateTime to use for formatting.
+ @param lang is the language, only used if #FormatType is #Localized.
+ @param shortFormat if true, create the short version of the date string,
+ only used if #FormatType is #Localized.
+ @param includeSecs if true, include the seconds field in the date string,
+ only used if #FormatType is #Localized.
+
+ @return a QString containing the formatted date.
+ */
+ QString dateString( const QDateTime &dtime, const QString &lang=QString(),
+ bool shortFormat=true, bool includeSecs=false ) const;
+
+ /**
+ Sets the custom format for date to string conversions to @p format.
+ This method accepts the same arguments as QDateTime::toString(), but
+ also supports the "Z" expression which is substituted with the
+ @ref RFC2822 (Section 3.3) style numeric timezone (-0500).
+
+ @param format is a QString containing the custom format.
+
+ @see QDateTime::toString(), customFormat().
+ */
+ void setCustomFormat( const QString &format );
+
+ /**
+ Returns the custom format string.
+
+ @see setCustomFormat().
+ */
+ QString customFormat() const;
+
+ /**
+ Resets the cached current date used for calculating the fancy date.
+ This should be called whenever the current date changed, i.e. on midnight.
+ @deprecated Can be safely removed. The date change is taken care of internally (as of 4.3).
+ */
+ void reset();
+
+ //static methods
+ /**
+ Convenience function dateString
+
+ @param ftype is the #FormatType to use.
+ @param t is the time_t to use for formatting.
+ @param data is either the format when #FormatType is Custom,
+ or language when #FormatType is #Localized.
+ @param shortFormat if true, create the short version of the date string,
+ only used if #FormatType is #Localized.
+ @param includeSecs if true, include the seconds field in the date string,
+ only used if #FormatType is #Localized.
+
+ @return a QString containing the formatted date.
+ */
+ static QString formatDate( DateFormatter::FormatType ftype, time_t t,
+ const QString &data=QString(),
+ bool shortFormat=true,
+ bool includeSecs=false );
+
+ /**
+ Convenience function, same as formatDate() but returns the current time
+ formatted.
+
+ @param ftype is the #FormatType to use.
+ @param data is either the format when #FormatType is Custom,
+ or language when #FormatType is #Localized.
+ @param shortFormat if true, create the short version of the date string,
+ only used if #FormatType is #Localized.
+ @param includeSecs if true, include the seconds field in the date string,
+ only used if #FormatType is #Localized.
+
+ @return a QString containing the formatted date.
+ */
+ static QString formatCurrentDate( DateFormatter::FormatType ftype,
+ const QString &data=QString(),
+ bool shortFormat=true,
+ bool includeSecs=false );
+
+ /**
+ Returns true if the current time is on daylight savings time; else false.
+ */
+ static bool isDaylight();
+
+ protected:
+ /**
+ Returns a QString containing the specified time_t @p t formatted
+ using the #Fancy #FormatType.
+
+ @param t is the time_t to use for formatting.
+ */
+ QString fancy( time_t t ) const ;
+
+ /**
+ Returns a QString containing the specified time_t @p t formatted
+ using the #Localized #FormatType.
+
+ @param t is the time_t to use for formatting.
+ @param shortFormat if true, create the short version of the date string.
+ @param includeSecs if true, include the seconds field in the date string.
+ @param lang is a QString containing the language to use.
+ */
+ QString localized( time_t t, bool shortFormat=true,
+ bool includeSecs=false,
+ const QString &lang=QString() ) const;
+
+ /**
+ Returns a QString containing the specified time_t @p t formatted
+ with the ctime() function.
+
+ @param t is the time_t to use for formatting.
+ */
+ QString cTime( time_t t ) const;
+
+ /**
+ Returns a QString containing the specified time_t @p t in the
+ "%Y-%m-%d %H:%M:%S" #Iso #FormatType.
+
+ @param t is the time_t to use for formatting.
+ */
+ QString isoDate( time_t t ) const;
+
+ /**
+ Returns a QString containing the specified time_t @p t in the
+ #Rfc #FormatType.
+
+ @param t is the time_t to use for formatting.
+ */
+ QString rfc2822( time_t t ) const;
+
+ /**
+ Returns a QString containing the specified time_t @p t formatted
+ with a previously specified custom format.
+
+ @param t time used for formatting
+ */
+ QString custom( time_t t ) const;
+
+ /**
+ Returns a QString that identifies the timezone (eg."-0500")
+ of the specified time_t @p t.
+
+ @param t time to compute timezone from.
+ */
+ QByteArray zone( time_t t ) const;
+
+ /**
+ Converts QDateTime @p dt to a time_t value.
+
+ @param dt is the QDateTime to be converted.
+ @return the time_t equivalent of the specified QDateTime.
+ */
+ time_t qdateToTimeT( const QDateTime &dt ) const;
+
+ private:
+ //@cond PRIVATE
+ FormatType mFormat;
+ mutable time_t mTodayOneSecondBeforeMidnight;
+ mutable QDateTime mUnused; // KDE5: remove
+ QString mCustomFormat;
+ static int mDaylight;
+ //@endcond
+};
+
+} // namespace KMime
+
+#endif /* __KMIME_DATEFORMATTER_H__ */
diff --git a/kmime/kmime_export.h b/kmime/kmime_export.h
new file mode 100644
index 0000000..4fc0b3e
--- /dev/null
+++ b/kmime/kmime_export.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of kdepimlibs.
+ Copyright (c) 2006 Allen Winter <winter@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KMIME_EXPORT_H
+#define KMIME_EXPORT_H
+
+#include <kdemacros.h>
+
+#ifndef KMIME_EXPORT
+# if defined(KDEPIM_STATIC_LIBS)
+ /* No export/import for static libraries */
+# define KMIME_EXPORT
+# elif defined(MAKE_KMIME_LIB)
+ /* We are building this library */
+# define KMIME_EXPORT KDE_EXPORT
+# else
+ /* We are using this library */
+# define KMIME_EXPORT KDE_IMPORT
+# endif
+#endif
+
+# ifndef KMIME_EXPORT_DEPRECATED
+# define KMIME_EXPORT_DEPRECATED KDE_DEPRECATED KMIME_EXPORT
+# endif
+
+/**
+ @namespace KMime
+
+ @brief
+ Contains all the KMIME library global classes, objects, and functions.
+*/
+
+#endif
diff --git a/kmime/kmime_header_parsing.cpp b/kmime/kmime_header_parsing.cpp
new file mode 100644
index 0000000..200e79f
--- /dev/null
+++ b/kmime/kmime_header_parsing.cpp
@@ -0,0 +1,2216 @@
+/* -*- c++ -*-
+ kmime_header_parsing.cpp
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kmime_header_parsing.h"
+
+#include "kmime_codecs.h"
+#include "kmime_headerfactory_p.h"
+#include "kmime_headers.h"
+#include "kmime_util.h"
+#include "kmime_util_p.h"
+#include "kmime_dateformatter.h"
+#include "kmime_warning.h"
+
+#include <kcharsets.h>
+
+#include <QtCore/QTextCodec>
+#include <QtCore/QMap>
+#include <QtCore/QStringList>
+#include <QtCore/QUrl>
+
+#include <ctype.h> // for isdigit
+#include <cassert>
+
+using namespace KMime;
+using namespace KMime::Types;
+
+namespace KMime {
+
+namespace Types {
+
+// QUrl::fromAce is extremely expensive, so only use it when necessary.
+// Fortunately, the presence of IDNA is readily detected with a substring match...
+static inline QString QUrl_fromAce_wrapper( const QString & domain )
+{
+ if ( domain.contains( QLatin1String( "xn--" ) ) )
+ return QUrl::fromAce( domain.toLatin1() );
+ else
+ return domain;
+}
+
+static QString addr_spec_as_string( const AddrSpec & as, bool pretty )
+{
+ if ( as.isEmpty() ) {
+ return QString();
+ }
+
+ static QChar dotChar = QLatin1Char( '.' );
+ static QChar backslashChar = QLatin1Char( '\\' );
+ static QChar quoteChar = QLatin1Char( '"' );
+
+ bool needsQuotes = false;
+ QString result;
+ result.reserve( as.localPart.length() + as.domain.length() + 1 );
+ for ( int i = 0 ; i < as.localPart.length() ; ++i ) {
+ const QChar ch = as.localPart.at( i );
+ if ( ch == dotChar || isAText( ch.toLatin1() ) ) {
+ result += ch;
+ } else {
+ needsQuotes = true;
+ if ( ch == backslashChar || ch == quoteChar ) {
+ result += backslashChar;
+ }
+ result += ch;
+ }
+ }
+ const QString dom = pretty ? QUrl_fromAce_wrapper( as.domain ) : as.domain ;
+ if ( needsQuotes ) {
+ result = quoteChar + result + quoteChar;
+ }
+ if( dom.isEmpty() ) {
+ return result;
+ } else {
+ result += QLatin1Char( '@' );
+ result += dom;
+ return result;
+ }
+}
+
+QString AddrSpec::asString() const
+{
+ return addr_spec_as_string( *this, false );
+}
+
+QString AddrSpec::asPrettyString() const
+{
+ return addr_spec_as_string( *this, true );
+}
+
+bool AddrSpec::isEmpty() const
+{
+ return localPart.isEmpty() && domain.isEmpty();
+}
+
+QByteArray Mailbox::address() const
+{
+ QByteArray result;
+ const QString asString = addr_spec_as_string( mAddrSpec, false );
+ if ( !asString.isEmpty() ) {
+ result = asString.toLatin1();
+ }
+ return result;
+ //return mAddrSpec.asString().toLatin1();
+}
+
+AddrSpec Mailbox::addrSpec() const
+{
+ return mAddrSpec;
+}
+
+QString Mailbox::name() const
+{
+ return mDisplayName;
+}
+
+void Mailbox::setAddress( const AddrSpec &addr )
+{
+ mAddrSpec = addr;
+}
+
+void Mailbox::setAddress( const QByteArray &addr )
+{
+ const char *cursor = addr.constData();
+ if ( !HeaderParsing::parseAngleAddr( cursor,
+ cursor + addr.length(), mAddrSpec ) ) {
+ if ( !HeaderParsing::parseAddrSpec( cursor, cursor + addr.length(),
+ mAddrSpec ) ) {
+ kWarning() << "Invalid address";
+ return;
+ }
+ }
+}
+
+void Mailbox::setName( const QString &name )
+{
+ mDisplayName = removeBidiControlChars( name );
+}
+
+void Mailbox::setNameFrom7Bit( const QByteArray &name,
+ const QByteArray &defaultCharset )
+{
+ QByteArray cs;
+ setName( decodeRFC2047String( name, cs, defaultCharset, false ) );
+}
+
+bool Mailbox::hasAddress() const
+{
+ return !mAddrSpec.isEmpty();
+}
+
+bool Mailbox::hasName() const
+{
+ return !mDisplayName.isEmpty();
+}
+
+QString Mailbox::prettyAddress() const
+{
+ return prettyAddress( QuoteNever );
+}
+
+QString Mailbox::prettyAddress( Quoting quoting ) const
+{
+ if ( !hasName() ) {
+ return QLatin1String( address() );
+ }
+ QString s = name();
+ if ( quoting != QuoteNever ) {
+ addQuotes( s, quoting == QuoteAlways /*bool force*/ );
+ }
+
+ if ( hasAddress() ) {
+ s += QLatin1String(" <") + QLatin1String( address() ) + QLatin1Char('>');
+ }
+ return s;
+}
+
+void Mailbox::fromUnicodeString( const QString &s )
+{
+ from7BitString( encodeRFC2047Sentence( s, "utf-8" ) );
+}
+
+void Mailbox::from7BitString( const QByteArray &s )
+{
+ const char *cursor = s.constData();
+ HeaderParsing::parseMailbox( cursor, cursor + s.length(), *this );
+}
+
+QByteArray KMime::Types::Mailbox::as7BitString( const QByteArray &encCharset ) const
+{
+ if ( !hasName() ) {
+ return address();
+ }
+ QByteArray rv;
+ if ( isUsAscii( name() ) ) {
+ QByteArray tmp = name().toLatin1();
+ addQuotes( tmp, false );
+ rv += tmp;
+ } else {
+ rv += encodeRFC2047String( name(), encCharset, true );
+ }
+ if ( hasAddress() ) {
+ rv += " <" + address() + '>';
+ }
+ return rv;
+}
+
+} // namespace Types
+
+namespace HeaderParsing {
+
+// parse the encoded-word (scursor points to after the initial '=')
+bool parseEncodedWord( const char* &scursor, const char * const send,
+ QString &result, QByteArray &language,
+ QByteArray &usedCS, const QByteArray &defaultCS,
+ bool forceCS )
+{
+ // 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 != '?' ) {
+ // kDebug() << "first";
+ //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 == 0 ) {
+ languageStart = scursor + 1;
+ }
+ }
+
+ // not found? can't be an encoded-word!
+ if ( scursor == send || *scursor != '?' ) {
+ // kDebug() << "second";
+ KMIME_WARN_PREMATURE_END_OF( EncodedWord );
+ return false;
+ }
+
+ // extract the language information, if any (if languageStart is 0,
+ // language will be null, too):
+ QByteArray maybeLanguage( languageStart, scursor - languageStart );
+ // extract charset information (keep in mind: the size given to the
+ // ctor is one off due to the \0 terminator):
+ QByteArray maybeCharset( charsetStart,
+ ( languageStart ? languageStart - 1 : scursor ) - 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 != '?' ) {
+ // kDebug() << "third";
+ KMIME_WARN_PREMATURE_END_OF( EncodedWord );
+ return false;
+ }
+
+ // extract the encoding information:
+ QByteArray maybeEncoding( encodingStart, scursor - encodingStart );
+
+ // kDebug() << "parseEncodedWord: found charset == \"" << maybeCharset
+ // << "\"; language == \"" << maybeLanguage
+ // << "\"; encoding == \"" << maybeEncoding << "\"";
+
+ //
+ // 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 the '?=' sequence (ending the encoded-text):
+ for ( ; scursor != send ; scursor++ ) {
+ if ( *scursor == '?' ) {
+ if ( scursor + 1 != send ) {
+ if ( *( scursor + 1 ) != '=' ) { // We expect a '=' after the '?', but we got something else; ignore
+ KMIME_WARN << "Stray '?' in q-encoded word, ignoring this.";
+ continue;
+ }
+ else { // yep, found a '?=' sequence
+ scursor += 2;
+ break;
+ }
+ }
+ else { // The '?' is the last char, but we need a '=' after it!
+ KMIME_WARN_PREMATURE_END_OF( EncodedWord );
+ return false;
+ }
+ }
+ }
+
+ if ( *( scursor - 2 ) != '?' || *( scursor - 1 ) != '=' ||
+ scursor < encodedTextStart + 2 ) {
+ KMIME_WARN_PREMATURE_END_OF( EncodedWord );
+ return false;
+ }
+
+ // 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 = 0;
+ if ( forceCS || maybeCharset.isEmpty() ) {
+ textCodec = KCharsets::charsets()->codecForName( QLatin1String( defaultCS ), matchOK );
+ usedCS = cachedCharset( defaultCS );
+ } else {
+ textCodec = KCharsets::charsets()->codecForName( QLatin1String( maybeCharset ), matchOK );
+ if ( !matchOK ) { //no suitable codec found => use default charset
+ textCodec = KCharsets::charsets()->codecForName( QLatin1String( defaultCS ), matchOK );
+ usedCS = cachedCharset( defaultCS );
+ } else {
+ usedCS = cachedCharset( maybeCharset );
+ }
+ }
+
+ if ( !matchOK || !textCodec ) {
+ KMIME_WARN_UNKNOWN( Charset, maybeCharset );
+ delete dec;
+ return false;
+ };
+
+ // kDebug() << "mimeName(): \"" << textCodec->name() << "\"";
+
+ // allocate a temporary buffer to store the 8bit text:
+ int encodedTextLength = encodedTextEnd - encodedTextStart;
+ QByteArray buffer;
+ buffer.resize( codec->maxDecodedSizeFor( encodedTextLength ) );
+ char *bbegin = buffer.data();
+ char *bend = bbegin + buffer.length();
+
+ //
+ // STEP 5:
+ // do the actual decoding
+ //
+
+ if ( !dec->decode( encodedTextStart, encodedTextEnd, bbegin, bend ) ) {
+ KMIME_WARN << codec->name() << "codec lies about its maxDecodedSizeFor("
+ << encodedTextLength << ")\nresult may be truncated";
+ }
+
+ result = textCodec->toUnicode( buffer.data(), bbegin - buffer.data() );
+
+ // kDebug() << "result now: \"" << result << "\"";
+ // 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;
+}
+
+// FIXME: Remove this and the other parseToken() method. add a new one where "result" is a
+// QByteArray.
+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
+
+// FIXME: Why is result a QString? This should be a QByteArray, since at this level, we don't
+// know about encodings yet!
+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 += QLatin1Char( 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 += QLatin1Char('\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 += QLatin1Char( 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 += QLatin1String( "\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 += QLatin1Char( ch );
+ } else {
+ // non-folding
+ KMIME_WARN_LONE( LF );
+ result += QLatin1Char( '\n' );
+ // pos is decremented, so's we need not duplicate the whole
+ // switch here. ch could've been everything (incl. <">, "\").
+ scursor--;
+ }
+ break;
+ case '=':
+ {
+ // ### Work around broken clients that send encoded words in quoted-strings
+ // For example, older KMail versions.
+ if( scursor == send )
+ break;
+
+ const char *oldscursor = scursor;
+ QString tmp;
+ QByteArray lang, charset;
+ if( *scursor++ == '?' ) {
+ --scursor;
+ if( parseEncodedWord( scursor, send, tmp, lang, charset ) ) {
+ result += tmp;
+ break;
+ } else {
+ scursor = oldscursor;
+ }
+ } else {
+ scursor = oldscursor;
+ }
+ // fall through
+ }
+ default:
+ KMIME_WARN_IF_8BIT( ch );
+ result += QLatin1Char( 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 += QLatin1Char( ')' );
+ }
+ maybeCmnt.clear();
+ }
+ 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 += QLatin1Char( '(' );
+ }
+ ++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;
+ QByteArray lang, charset;
+ 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 += QLatin1String( ". " );
+ } else {
+ result += QLatin1Char( '.' );
+ }
+ successfullyParsed = scursor;
+ }
+ break;
+ case '"': // quoted-string
+ tmp.clear();
+ 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 += QLatin1Char(' '); // 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 += QLatin1Char(' '); // rfc822, 3.4.4
+ result += tmp;
+ return true;
+ }
+ }
+ break;
+ case '(': // comment
+ // parse it, but ignore content:
+ tmp.clear();
+ 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.clear();
+ oldscursor = scursor;
+ lang.clear();
+ charset.clear();
+ if ( parseEncodedWord( scursor, send, tmp, lang, charset ) ) {
+ successfullyParsed = scursor;
+ switch ( found ) {
+ case None:
+ found = EncodedWord;
+ break;
+ case Phrase:
+ case EncodedWord:
+ case Atom:
+ case QuotedString:
+ if ( !lastWasEncodedWord ) {
+ result += QLatin1Char(' '); // 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.clear();
+ 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 += QLatin1Char(' '); // 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;
+}
+
+// FIXME: This should probably by QByteArray &result instead?
+bool parseDotAtom( const char* &scursor, const char * const send,
+ QString &result, bool isCRLF )
+{
+ eatCFWS( scursor, send, 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 ) {
+
+ // end of header or no '.' -> return
+ if ( scursor == send || *scursor != '.' ) {
+ return true;
+ }
+ scursor++; // eat '.'
+
+ 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 += QLatin1Char('.');
+ 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 += QLatin1Char('[');
+ continue;
+ }
+ // OK, real end of domain-literal:
+ result = maybeDomainLiteral;
+ return true;
+ }
+ } else {
+ // dot-atom:
+ QString maybeDotAtom;
+ if ( parseDotAtom( scursor, send, maybeDotAtom, isCRLF ) ) {
+ result = maybeDotAtom;
+ // Domain may end with '.', if so preserve it'
+ if ( scursor != send && *scursor == '.' ) {
+ result += QLatin1Char('.');
+ scursor++;
+ }
+ 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() );
+ }
+ continue;
+ }
+
+ // empty entry ending the list:
+ if ( *scursor == ':' ) {
+ scursor++;
+ if ( save ) {
+ result.append( QString() );
+ }
+ 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 += QLatin1Char('.');
+ break;
+
+ case '@':
+ goto SAW_AT_SIGN;
+ break;
+
+ case '"': // quoted-string
+ tmp.clear();
+ if ( parseGenericQuotedString( scursor, send, tmp, isCRLF, '"', '"' ) ) {
+ maybeLocalPart += tmp;
+ } else {
+ return false;
+ }
+ break;
+
+ default: // atom
+ scursor--; // re-set scursor to point to ch again
+ tmp.clear();
+ 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.";
+ 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;
+
+}
+
+static QString stripQuotes( const QString &input )
+{
+ const QLatin1Char quotes( '"' );
+ if ( input.startsWith( quotes ) && input.endsWith( quotes ) ) {
+ QString stripped( input.mid( 1, input.size() - 2 ) );
+ return stripped;
+ }
+ else return input;
+}
+
+bool parseMailbox( const char* &scursor, const char * const send,
+ Mailbox &result, bool isCRLF )
+{
+ eatCFWS( scursor, send, isCRLF );
+ if ( scursor == send ) {
+ return false;
+ }
+
+ AddrSpec maybeAddrSpec;
+ QString maybeDisplayName;
+
+ // first, try if it's a vanilla addr-spec:
+ const char * oldscursor = scursor;
+ if ( parseAddrSpec( scursor, send, maybeAddrSpec, isCRLF ) ) {
+ result.setAddress( maybeAddrSpec );
+ // 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.setName( stripQuotes( maybeDisplayName ) );
+ return true;
+ }
+ scursor = oldscursor;
+
+ // second, see if there's a display-name:
+ if ( !parsePhrase( scursor, send, maybeDisplayName, isCRLF ) ) {
+ // failed: reset cursor, note absent display-name
+ maybeDisplayName.clear();
+ 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.setName( stripQuotes( maybeDisplayName ) );
+ result.setAddress( 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;
+ }
+
+ // KDE5 TODO: Don't expose displayName as public, but rather add setter for it that
+ // automatically calls removeBidiControlChars
+ result.displayName = removeBidiControlChars( 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.clear();
+ 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;
+ }
+ // broken clients might use ';' as list delimiter, accept that as well
+ 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 );
+
+// FIXME: Get rid of the very ugly "QStringOrQPair" thing. At this level, we are supposed to work
+// on byte arrays, not strings! The result parameter should be a simple
+// QPair<QByteArray,QByteArray>, which is the attribute name and the value.
+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:
+ //
+ // FIXME: maybeAttribute should be a QByteArray
+ 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 \"*\".";
+ maybeAttribute.truncate( maybeAttribute.length() - 1 );
+ }
+ result = qMakePair( maybeAttribute.toLower(), 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 \"*\".";
+ maybeAttribute.truncate( maybeAttribute.length() - 1 );
+ }
+
+ if ( !parseGenericQuotedString( scursor, send, maybeValue.qstring, isCRLF ) ) {
+ scursor = oldscursor;
+ result = qMakePair( maybeAttribute.toLower(), 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.toLower(), QStringOrQPair() );
+ return false; // this case needs further processing by upper layers!!
+ }
+ }
+
+ result = qMakePair( maybeAttribute.toLower(), maybeValue );
+ return true;
+}
+
+// FIXME: Get rid of QStringOrQPair: Use a simply QMap<QByteArray, QByteArray> for "result"
+// instead!
+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, QByteArray& charset )
+{
+ //
+ // 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\".";
+ value += QString::fromLatin1( decBegin, source.second );
+ return;
+ }
+
+ charset = QByteArray( decBegin, decCursor - decBegin );
+
+ 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.";
+ decCursor = oldDecCursor;
+ } else {
+ decCursor++;
+ }
+
+ // decCursor now points to the start of the
+ // "extended-other-values":
+
+ //
+ // get the decoders:
+ //
+
+ bool matchOK = false;
+ textcodec = KCharsets::charsets()->codecForName( QLatin1String( 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;
+ buffer.resize( 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 its maxDecodedSizeFor()" << endl
+ << "result may be truncated";
+ }
+
+ value += textcodec->toUnicode( buffer.begin(), bit - buffer.begin() );
+
+ // kDebug() << "value now: \"" << value << "\"";
+ // 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 parseParameterListWithCharset( const char* &scursor,
+ const char * const send,
+ QMap<QString,QString> &result,
+ QByteArray& charset, 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 Mode {
+ NoMode = 0x0, Continued = 0x1, Encoded = 0x2
+ };
+
+ enum EncodingMode {
+ NoEncoding,
+ RFC2047,
+ RFC2231
+ };
+
+ QMap<QString,QStringOrQPair>::Iterator 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.clear();
+ attribute = it.key();
+ int mode = NoMode;
+ EncodingMode encodingMode = NoEncoding;
+
+ // is the value rfc2331-encoded?
+ if ( attribute.endsWith( asterisk ) ) {
+ attribute.truncate( attribute.length() - 1 );
+ mode |= Encoded;
+ encodingMode = RFC2231;
+ }
+ // is the value rfc2047-encoded?
+ if( !(*it).qstring.isNull() && (*it).qstring.contains( QLatin1String( "=?" ) ) ) {
+ mode |= Encoded;
+ encodingMode = RFC2047;
+ }
+ // is the value continued?
+ if ( attribute.endsWith( asteriskZero ) ) {
+ attribute.truncate( attribute.length() - 2 );
+ mode |= Continued;
+ }
+ //
+ // decode if necessary:
+ //
+ if ( mode & Encoded ) {
+ if ( encodingMode == RFC2231 ) {
+ decodeRFC2231Value( rfc2231Codec, textcodec,
+ false, /* isn't continuation */
+ value, (*it).qpair, charset );
+ }
+ else if ( encodingMode == RFC2047 ) {
+ value += decodeRFC2047String( (*it).qstring.toLatin1(), charset );
+ }
+ } 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.clear();
+ }
+ } 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, charset );
+ } 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;
+}
+
+
+bool parseParameterList( const char* &scursor, const char * const send,
+ QMap<QString,QString> &result, bool isCRLF )
+{
+ QByteArray charset;
+ return parseParameterListWithCharset( scursor, send, result, charset, isCRLF );
+}
+
+static const char * const 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;
+ // kDebug() << "found" << stdDayNames[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static const char * const stdMonthNames[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+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,
+ QByteArray( maybeTimeZone.first, maybeTimeZone.second ) );
+ secsEastOfGMT = 0;
+ timeZoneKnown = false;
+ return true;
+}
+
+// parse a number and return the number of digits parsed:
+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,
+ KDateTime &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" / "Dec"
+
+ result = KDateTime();
+ QDateTime maybeDateTime;
+
+ 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;
+ }
+
+ //
+ // month-name:
+ //
+ int maybeMonth = 0;
+ if ( !parseMonthName( scursor, send, maybeMonth ) ) {
+ return false;
+ }
+ if ( scursor == send ) {
+ return false;
+ }
+ assert( maybeMonth >= 0 ); assert( maybeMonth <= 11 );
+ ++maybeMonth; // 0-11 -> 1-12
+
+ eatCFWS( scursor, send, isCRLF );
+ if ( scursor == send ) {
+ return false;
+ }
+
+ //
+ // 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;
+ }
+
+ maybeDateTime.setDate( QDate( maybeYear, maybeMonth, maybeDay ) );
+
+ //
+ // time
+ //
+ int maybeHour, maybeMinute, maybeSecond;
+ long int secsEastOfGMT;
+ bool timeZoneKnown = true;
+
+ if ( !parseTime( scursor, send,
+ maybeHour, maybeMinute, maybeSecond,
+ secsEastOfGMT, timeZoneKnown, isCRLF ) ) {
+ return false;
+ }
+
+ maybeDateTime.setTime( QTime( maybeHour, maybeMinute, maybeSecond ) );
+ if ( !maybeDateTime.isValid() )
+ return false;
+
+ result = KDateTime( maybeDateTime, KDateTime::Spec( KDateTime::OffsetFromUTC, secsEastOfGMT ) );
+ if ( !result.isValid() )
+ return false;
+ return true;
+}
+
+Headers::Base *extractFirstHeader( QByteArray &head )
+{
+ int endOfFieldBody = 0;
+ bool folded = false;
+ Headers::Base *header = 0;
+
+ int startOfFieldBody = head.indexOf( ':' );
+ const int endOfFieldHeader = startOfFieldBody;
+
+ if ( startOfFieldBody > -1 ) { //there is another header
+ startOfFieldBody++; //skip the ':'
+ if ( head[startOfFieldBody] == ' ' ) { // skip the space after the ':', if there
+ startOfFieldBody++;
+ }
+ endOfFieldBody = findHeaderLineEnd( head, startOfFieldBody, &folded );
+
+ QByteArray rawType = head.left( endOfFieldHeader );
+ QByteArray rawFieldBody = head.mid( startOfFieldBody, endOfFieldBody - startOfFieldBody );
+ if ( folded ) {
+ rawFieldBody = unfoldHeader( rawFieldBody );
+ }
+ // We might get an invalid mail without a field name, don't crash on that.
+ if ( !rawType.isEmpty() ) {
+ header = HeaderFactory::self()->createHeader( rawType );
+ }
+ if( !header ) {
+ //kWarning() << "Returning Generic header of type" << rawType;
+ header = new Headers::Generic( rawType.constData() );
+ }
+ header->from7BitString( rawFieldBody );
+
+ head.remove( 0, endOfFieldBody + 1 );
+ } else {
+ head.clear();
+ }
+
+ return header;
+}
+
+void extractHeaderAndBody( const QByteArray &content, QByteArray &header, QByteArray &body )
+{
+ header.clear();
+ body.clear();
+
+ // empty header
+ if ( content.startsWith( '\n' ) ) {
+ body = content.right( content.length() - 1 );
+ return;
+ }
+
+ int pos = content.indexOf( "\n\n", 0 );
+ if ( pos > -1 ) {
+ header = content.left( ++pos ); //header *must* end with "\n" !!
+ body = content.mid( pos + 1, content.length() - pos - 1 );
+ } else {
+ header = content;
+ }
+}
+
+Headers::Base::List parseHeaders( const QByteArray &head )
+{
+ Headers::Base::List ret;
+ Headers::Base *h;
+
+ QByteArray copy = head;
+ while( ( h = extractFirstHeader( copy ) ) ) {
+ ret << h;
+ }
+
+ return ret;
+}
+
+} // namespace HeaderParsing
+
+} // namespace KMime
diff --git a/kmime/kmime_header_parsing.h b/kmime/kmime_header_parsing.h
new file mode 100644
index 0000000..09c5b9f
--- /dev/null
+++ b/kmime/kmime_header_parsing.h
@@ -0,0 +1,398 @@
+/* -*- c++ -*-
+ kmime_header_parsing.h
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef __KMIME_HEADER_PARSING_H__
+#define __KMIME_HEADER_PARSING_H__
+
+#include <QtCore/QString>
+#include <QtCore/QPair>
+
+#include <kdatetime.h>
+
+#include "kmime_export.h"
+
+template <typename K, typename V> class QMap;
+class QStringList;
+
+namespace KMime {
+
+namespace Headers {
+ class Base;
+}
+
+namespace Types {
+
+// for when we can't make up our mind what to use...
+// FIXME: Remove this thing, we should _always_ know whether we are handling a
+// byte array or a string.
+// In most places where this is used, it should simply be replaced by QByteArray
+struct KMIME_EXPORT QStringOrQPair {
+ QStringOrQPair() : qstring(), qpair( 0, 0 ) {}
+ QString qstring;
+ QPair<const char*,int> qpair;
+};
+
+struct KMIME_EXPORT AddrSpec {
+ QString asString() const;
+ /*! This is the same as asString(), except it decodes IDNs for display */
+ QString asPrettyString() const;
+ bool isEmpty() const;
+ QString localPart;
+ QString domain;
+};
+typedef QList<AddrSpec> AddrSpecList;
+
+/**
+ Represents an (email address, display name) pair according RFC 2822,
+ section 3.4.
+*/
+class KMIME_EXPORT Mailbox
+{
+ public:
+ typedef QList<Mailbox> List;
+
+ /**
+ Returns a string representation of the email address, without
+ the angle brackets.
+ */
+ QByteArray address() const;
+
+ AddrSpec addrSpec() const;
+
+ /**
+ Returns the display name.
+ */
+ QString name() const;
+
+ /**
+ Sets the email address.
+ */
+ void setAddress( const AddrSpec &addr );
+
+ /**
+ Sets the email address.
+ */
+ void setAddress( const QByteArray &addr );
+
+ /**
+ Sets the name.
+ */
+ void setName( const QString &name );
+
+ /**
+ Sets the name based on a 7bit encoded string.
+ */
+ void setNameFrom7Bit( const QByteArray &name,
+ const QByteArray &defaultCharset = QByteArray() );
+
+ /**
+ Returns true if this mailbox has an address.
+ */
+ bool hasAddress() const;
+
+ /**
+ Returns true if this mailbox has a display name.
+ */
+ bool hasName() const;
+
+ /**
+ Returns a assembled display name / address string of the following form:
+ "Display Name &lt;address&gt;". These are unicode strings without any
+ transport encoding, ie. they are only suitable for displaying.
+ */
+ QString prettyAddress() const;
+
+ /**
+ * Describes how display names should be quoted
+ * @since 4.5
+ */
+ //AK_REVIEW: remove this enum
+ enum Quoting {
+ QuoteNever, ///< Don't quote display names at all. Such an unquoted display name can not
+ /// be machine-processed anymore in some cases, for example when it contains
+ /// commas, like in "Lastname, Firstname".
+ QuoteWhenNecessary, ///< Only quote display names when they contain characters that need to be
+ /// quoted, like commas or quote signs.
+ QuoteAlways ///< Always quote the display name
+ };
+
+ /**
+ * Overloaded method that gives more control over the quoting of the display name
+ * @param quoting describes how the display name should be quoted
+ * @since 4.5
+ */
+ // TODO: KDE5: BIC: remove other prettyAddress() overload, and make it None the default
+ // parameter here
+ //AK_REVIEW: replace by 'QString quotedAddress() const'
+ QString prettyAddress( Quoting quoting ) const;
+
+ /**
+ Parses the given unicode string.
+ */
+ void fromUnicodeString( const QString &s );
+
+ /**
+ Parses the given 7bit encoded string.
+ */
+ void from7BitString( const QByteArray &s );
+
+ /**
+ Returns a 7bit transport encoded representation of this mailbox.
+
+ @param encCharset The charset used for encoding.
+ */
+ QByteArray as7BitString( const QByteArray &encCharset ) const;
+
+ private:
+ QString mDisplayName;
+ AddrSpec mAddrSpec;
+};
+
+typedef QList<Mailbox> MailboxList;
+
+struct KMIME_EXPORT Address {
+ QString displayName;
+ MailboxList mailboxList;
+};
+typedef QList<Address> AddressList;
+
+} // namespace KMime::Types
+
+namespace HeaderParsing {
+
+/**
+ Parses the encoded word.
+
+ @param scursor pointer to the first character beyond the initial '=' of
+ the input string.
+ @param send pointer to end of input buffer.
+ @param result the decoded string the encoded work represented.
+ @param language The language parameter according to RFC 2231, section 5.
+ @param usedCS the used 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 true if the input string was successfully decode; false otherwise.
+*/
+KMIME_EXPORT bool parseEncodedWord( const char* &scursor,
+ const char * const send,
+ QString &result, QByteArray &language,
+ QByteArray &usedCS, const QByteArray &defaultCS = QByteArray(),
+ bool forceCS = false );
+
+//
+// The parsing squad:
+//
+
+/** You may or may not have already started parsing into the
+ atom. This function will go on where you left off. */
+KMIME_EXPORT bool parseAtom( const char* &scursor, const char * const send,
+ QString &result, bool allow8Bit=false );
+
+KMIME_EXPORT bool parseAtom( const char* &scursor, const char * const send,
+ QPair<const char*,int> &result,
+ bool allow8Bit=false );
+
+/** You may or may not have already started parsing into the
+ token. This function will go on where you left off. */
+KMIME_EXPORT bool parseToken( const char* &scursor, const char * const send,
+ QString &result, bool allow8Bit=false );
+
+KMIME_EXPORT bool parseToken( const char* &scursor, const char * const send,
+ QPair<const char*,int> &result,
+ bool allow8Bit=false );
+
+/** @p scursor must be positioned after the opening openChar. */
+KMIME_EXPORT bool parseGenericQuotedString( const char* &scursor,
+ const char* const send,
+ QString &result, bool isCRLF,
+ const char openChar='"',
+ const char closeChar='"' );
+
+/** @p scursor must be positioned right after the opening '(' */
+KMIME_EXPORT bool parseComment( const char* &scursor, const char * const send,
+ QString &result, bool isCRLF=false,
+ bool reallySave=true );
+
+/**
+ Parses a phrase.
+
+ 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.
+
+ @param scursor pointer to the first character beyond the initial '=' of
+ the input string.
+ @param send pointer to end of input buffer.
+ @param result the parsed string.
+
+ @return true if the input phrase was successfully parsed; false otherwise.
+*/
+KMIME_EXPORT bool parsePhrase( const char* &scursor, const char * const send,
+ QString &result, bool isCRLF=false );
+
+/**
+ Parses into the initial atom.
+ You may or may not have already started parsing into the initial
+ atom, but not up to it's end.
+
+ @param scursor pointer to the first character beyond the initial '=' of
+ the input string.
+ @param send pointer to end of input buffer.
+ @param result the parsed string.
+
+ @return true if the input phrase was successfully parsed; false otherwise.
+*/
+KMIME_EXPORT bool parseDotAtom( const char* &scursor, const char * const send,
+ QString &result, bool isCRLF=false );
+
+/**
+ 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.
+
+ @param scursor pointer to the first character beyond the initial '=' of
+ the input string.
+ @param send pointer to end of input buffer.
+ @param isCRLF true if input string is terminated with a CRLF.
+*/
+KMIME_EXPORT void eatCFWS( const char* &scursor, const char * const send,
+ bool isCRLF );
+
+KMIME_EXPORT bool parseDomain( const char* &scursor, const char * const send,
+ QString &result, bool isCRLF=false );
+
+KMIME_EXPORT bool parseObsRoute( const char* &scursor, const char * const send,
+ QStringList &result, bool isCRLF=false,
+ bool save=false );
+
+KMIME_EXPORT bool parseAddrSpec( const char* &scursor, const char * const send,
+ Types::AddrSpec &result, bool isCRLF=false );
+
+KMIME_EXPORT bool parseAngleAddr( const char* &scursor, const char * const send,
+ Types::AddrSpec &result, bool isCRLF=false );
+
+/**
+ Parses a single mailbox.
+
+ RFC 2822, section 3.4 defines a mailbox as follows:
+ <pre>mailbox := addr-spec / ([ display-name ] angle-addr)</pre>
+
+ KMime also accepts the legacy format of specifying display names:
+ <pre>mailbox := (addr-spec [ "(" display-name ")" ])
+ / ([ display-name ] angle-addr)
+ / (angle-addr "(" display-name ")")</pre>
+
+ @param scursor pointer to the first character of the input string
+ @param send pointer to end of input buffer
+ @param result the parsing result
+ @param isCRLF true if input string is terminated with a CRLF.
+*/
+KMIME_EXPORT bool parseMailbox( const char* &scursor, const char * const send,
+ Types::Mailbox &result, bool isCRLF=false );
+
+KMIME_EXPORT bool parseGroup( const char* &scursor, const char * const send,
+ Types::Address &result, bool isCRLF=false );
+
+KMIME_EXPORT bool parseAddress( const char* &scursor, const char * const send,
+ Types::Address &result, bool isCRLF=false );
+
+KMIME_EXPORT bool parseAddressList( const char* &scursor,
+ const char * const send,
+ Types::AddressList &result,
+ bool isCRLF=false );
+
+KMIME_EXPORT bool parseParameter( const char* &scursor, const char * const send,
+ QPair<QString,Types::QStringOrQPair> &result,
+ bool isCRLF=false );
+
+KMIME_EXPORT bool parseParameterList( const char* &scursor,
+ const char * const send,
+ QMap<QString,QString> &result,
+ bool isCRLF=false );
+
+KMIME_EXPORT bool parseRawParameterList( const char* &scursor,
+ const char * const send,
+ QMap<QString,Types::QStringOrQPair> &result,
+ bool isCRLF=false );
+
+/**
+ * Extract the charset embedded in the parameter list if there is one.
+ *
+ * @since 4.5
+ */
+KMIME_EXPORT bool parseParameterListWithCharset( const char* &scursor,
+ const char * const send,
+ QMap<QString,QString> &result,
+ QByteArray& charset, bool isCRLF=false );
+
+/**
+ Parses an integer number.
+ @param scursor pointer to the first character of the input string
+ @param send pointer to end of input buffer
+ @param result the parsing result
+ @returns The number of parsed digits (don't confuse with @p result!)
+*/
+KMIME_EXPORT int parseDigits( const char* &scursor, const char* const send, int &result );
+
+KMIME_EXPORT bool parseTime( const char* &scursor, const char * const send,
+ int &hour, int &min, int &sec,
+ long int &secsEastOfGMT,
+ bool &timeZoneKnown, bool isCRLF=false );
+
+KMIME_EXPORT bool parseDateTime( const char* &scursor, const char * const send,
+ KDateTime &result, bool isCRLF=false );
+
+/**
+ * Extracts and returns the first header that is contained in the given byte array.
+ * The header will also be removed from the passed-in byte array head.
+ *
+ * @since 4.4
+ */
+KMIME_EXPORT KMime::Headers::Base *extractFirstHeader( QByteArray &head );
+
+/**
+ * Extract the header header and the body from a complete content.
+ * Internally, it will simply look for the first newline and use that as a
+ * separator between the header and the body.
+ *
+ * @param content the complete mail
+ * @param header return value for the extracted header
+ * @param body return value for the extracted body
+ * @since 4.6
+ */
+KMIME_EXPORT void extractHeaderAndBody( const QByteArray &content,
+ QByteArray &header, QByteArray &body );
+
+
+} // namespace HeaderParsing
+
+} // namespace KMime
+
+#endif // __KMIME_HEADER_PARSING_H__
+
diff --git a/kmime/kmime_header_parsing_p.h b/kmime/kmime_header_parsing_p.h
new file mode 100644
index 0000000..5238823
--- /dev/null
+++ b/kmime/kmime_header_parsing_p.h
@@ -0,0 +1,39 @@
+/*
+ Copyright (c) 2009 Thomas McGuire <mcguire@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library 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 Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+#ifndef KMIME_HEADER_PARSING_P_H
+#define KMIME_HEADER_PARSING_P_H
+
+#include <QList>
+
+class QByteArray;
+
+namespace KMime {
+
+namespace Headers {
+ class Base;
+}
+namespace HeaderParsing {
+
+QList<KMime::Headers::Base*> parseHeaders( const QByteArray &head );
+
+}
+
+}
+
+#endif
diff --git a/kmime/kmime_headerfactory.cpp b/kmime/kmime_headerfactory.cpp
new file mode 100644
index 0000000..7863c32
--- /dev/null
+++ b/kmime/kmime_headerfactory.cpp
@@ -0,0 +1,117 @@
+/*
+ kmime_header_factory.cpp
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+/**
+ @file
+ This file is part of the API for handling MIME data and
+ defines the HeaderFactory class.
+
+ @brief
+ Defines the HeaderFactory class.
+
+ @authors Constantin Berzan \<exit3219@gmail.com\>
+*/
+
+#include "kmime_headerfactory_p.h"
+#include "kmime_headers.h"
+
+#include <QHash>
+
+#include <KDebug>
+#include <KGlobal>
+
+using namespace KMime;
+
+/**
+ * @internal
+ * Private class that helps to provide binary compatibility between releases.
+ */
+class KMime::HeaderFactoryPrivate
+{
+ public:
+ HeaderFactoryPrivate();
+ ~HeaderFactoryPrivate();
+
+ HeaderFactory *const instance;
+ QHash<QByteArray, HeaderMakerBase*> headerMakers; // Type->obj mapping; with lower-case type.
+};
+
+Q_GLOBAL_STATIC( HeaderFactoryPrivate, sInstance )
+
+HeaderFactoryPrivate::HeaderFactoryPrivate()
+ : instance( new HeaderFactory( this ) )
+{
+}
+
+HeaderFactoryPrivate::~HeaderFactoryPrivate()
+{
+ qDeleteAll( headerMakers );
+ delete instance;
+}
+
+
+
+HeaderFactory* HeaderFactory::self()
+{
+ return sInstance()->instance;
+}
+
+Headers::Base *HeaderFactory::createHeader( const QByteArray &type )
+{
+ Q_ASSERT( !type.isEmpty() );
+ const HeaderMakerBase *maker = d->headerMakers.value( type.toLower() );
+ if( maker ) {
+ return maker->create();
+ } else {
+ //kError() << "Unknown header type" << type;
+ //return new Headers::Generic;
+ return 0;
+ }
+}
+
+HeaderFactory::HeaderFactory( HeaderFactoryPrivate *dd )
+ : d( dd )
+{
+}
+
+HeaderFactory::~HeaderFactory()
+{
+}
+
+bool HeaderFactory::registerHeaderMaker( const QByteArray &type, HeaderMakerBase *maker )
+{
+ if( type.isEmpty() ) {
+ // This is probably a generic (but not abstract) header,
+ // like Address or MailboxList. We cannot register those.
+ kWarning() << "Tried to register header with empty type.";
+ return false;
+ }
+ const QByteArray ltype = type.toLower();
+ if( d->headerMakers.contains( ltype ) ) {
+ kWarning() << "Header of type" << type << "already registered.";
+ // TODO should we make this an error?
+ return false;
+ }
+ d->headerMakers.insert( ltype, maker );
+ return true;
+}
+
diff --git a/kmime/kmime_headerfactory_p.h b/kmime/kmime_headerfactory_p.h
new file mode 100644
index 0000000..49c3ea1
--- /dev/null
+++ b/kmime/kmime_headerfactory_p.h
@@ -0,0 +1,95 @@
+/*
+ kmime_header_factory.h
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the HeaderFactory class.
+
+ @brief
+ Defines the HeaderFactory class.
+
+ @authors Constantin Berzan \<exit3219@gmail.com\>
+*/
+
+#ifndef __KMIME_HEADERFACTORY_H__
+#define __KMIME_HEADERFACTORY_H__
+
+#include "kmime_export.h"
+
+#include <QtCore/QByteArray>
+
+namespace KMime {
+
+namespace Headers {
+ class Base;
+}
+
+class HeaderMakerBase
+{
+ public:
+ virtual ~HeaderMakerBase() {}
+ virtual Headers::Base *create() const = 0;
+};
+
+template <typename T>
+class HeaderMaker : public HeaderMakerBase
+{
+ public:
+ virtual Headers::Base *create() const
+ {
+ return new T;
+ }
+};
+
+class HeaderFactoryPrivate;
+
+/**
+ docu TODO
+*/
+class HeaderFactory
+{
+ public:
+ static HeaderFactory* self();
+
+ template<typename T> inline bool registerHeader()
+ {
+ T dummy;
+ return registerHeaderMaker( QByteArray( dummy.type() ), new HeaderMaker<T>() );
+ }
+
+ Headers::Base *createHeader( const QByteArray &type );
+
+ private:
+ HeaderFactory( HeaderFactoryPrivate *dd );
+ HeaderFactory( const HeaderFactory &other ); // undefined
+ HeaderFactory& operator=( const HeaderFactory &other ); // undefined
+ ~HeaderFactory();
+
+ bool registerHeaderMaker( const QByteArray &type, HeaderMakerBase *maker );
+
+ friend class HeaderFactoryPrivate;
+ HeaderFactoryPrivate *const d;
+};
+
+} // namespace KMime
+
+#endif /* __KMIME_HEADERFACTORY_H__ */
diff --git a/kmime/kmime_headers.cpp b/kmime/kmime_headers.cpp
new file mode 100644
index 0000000..228ff1e
--- /dev/null
+++ b/kmime/kmime_headers.cpp
@@ -0,0 +1,2295 @@
+/* -*- c++ -*-
+ kmime_headers.cpp
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001-2002 the KMime authors.
+ See file AUTHORS for details
+ Copyright (c) 2006 Volker Krause <vkrause@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the various header classes:
+ - header's base class defining the common interface
+ - generic base classes for different types of fields
+ - incompatible, Structured-based field classes
+ - compatible, Unstructured-based field classes
+
+ @brief
+ Defines the various headers classes.
+
+ @authors the KMime authors (see AUTHORS file),
+ Volker Krause \<vkrause@kde.org\>
+*/
+
+#include "kmime_headers.h"
+#include "kmime_headers_p.h"
+
+#include "kmime_util.h"
+#include "kmime_util_p.h"
+#include "kmime_content.h"
+#include "kmime_codecs.h"
+#include "kmime_header_parsing.h"
+#include "kmime_headerfactory_p.h"
+#include "kmime_warning.h"
+
+#include <QtCore/QTextCodec>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+
+#include <kcharsets.h>
+
+#include <assert.h>
+#include <ctype.h>
+
+template <typename T>
+bool registerHeaderHelper()
+{
+ const T dummy;
+ if( QByteArray( dummy.type() ).isEmpty() ) {
+ // This is a generic header.
+ return false;
+ }
+ return KMime::HeaderFactory::self()->registerHeader<T>();
+}
+
+// macro to register a header with HeaderFactory
+#define kmime_register_header( subclass ) \
+namespace { const bool dummyForRegistering##subclass = registerHeaderHelper<subclass>(); }
+
+// macro to generate a default constructor implementation
+#define kmime_mk_trivial_ctor( subclass, baseclass ) \
+subclass::subclass( Content *parent ) : baseclass( parent ) \
+{ \
+ clear(); \
+} \
+ \
+subclass::subclass( Content *parent, const QByteArray &s ) : baseclass( parent ) \
+{ \
+ from7BitString( s ); \
+} \
+ \
+subclass::subclass( Content *parent, const QString &s, const QByteArray &charset ) : \
+ baseclass( parent ) \
+{ \
+ fromUnicodeString( s, charset ); \
+} \
+ \
+subclass::~subclass() {} \
+ \
+kmime_register_header( subclass )
+// end kmime_mk_trivial_ctor
+
+
+#define kmime_mk_trivial_ctor_with_dptr( subclass, baseclass ) \
+subclass::subclass( Content *parent ) : baseclass( new subclass##Private, parent ) \
+{ \
+ clear(); \
+} \
+ \
+subclass::subclass( Content *parent, const QByteArray &s ) : baseclass( new subclass##Private, parent ) \
+{ \
+ from7BitString( s ); \
+} \
+ \
+subclass::subclass( Content *parent, const QString &s, const QByteArray &charset ) : \
+ baseclass( new subclass##Private, parent ) \
+{ \
+ fromUnicodeString( s, charset ); \
+} \
+ \
+subclass::~subclass() {} \
+ \
+kmime_register_header( subclass )
+// end kmime_mk_trivial_ctor_with_dptr
+
+
+#define kmime_mk_trivial_ctor_with_name( subclass, baseclass, name ) \
+kmime_mk_trivial_ctor( subclass, baseclass ) \
+ \
+const char *subclass::type() const \
+{ \
+ return staticType(); \
+} \
+const char *subclass::staticType() { return #name; }
+
+#define kmime_mk_trivial_ctor_with_name_and_dptr( subclass, baseclass, name ) \
+kmime_mk_trivial_ctor_with_dptr( subclass, baseclass ) \
+const char *subclass::type() const { return staticType(); } \
+const char *subclass::staticType() { return #name; }
+
+#define kmime_mk_dptr_ctor( subclass, baseclass ) \
+subclass::subclass( subclass##Private *d, KMime::Content *parent ) : baseclass( d, parent ) {}
+
+using namespace KMime;
+using namespace KMime::Headers;
+using namespace KMime::Types;
+using namespace KMime::HeaderParsing;
+
+namespace KMime {
+namespace Headers {
+//-----<Base>----------------------------------
+Base::Base( KMime::Content *parent ) :
+ d_ptr( new BasePrivate )
+{
+ Q_D(Base);
+ d->parent = parent;
+}
+
+Base::Base( BasePrivate *dd, KMime::Content *parent ) :
+ d_ptr( dd )
+{
+ Q_D(Base);
+ d->parent = parent;
+}
+
+Base::~Base()
+{
+ delete d_ptr;
+ d_ptr = 0;
+}
+
+KMime::Content *Base::parent() const
+{
+ return d_ptr->parent;
+}
+
+void Base::setParent( KMime::Content *parent )
+{
+ d_ptr->parent = parent;
+}
+
+QByteArray Base::rfc2047Charset() const
+{
+ if ( d_ptr->encCS.isEmpty() || forceDefaultCharset() ) {
+ return defaultCharset();
+ } else {
+ return d_ptr->encCS;
+ }
+}
+
+void Base::setRFC2047Charset( const QByteArray &cs )
+{
+ d_ptr->encCS = cachedCharset( cs );
+}
+
+bool Base::forceDefaultCharset() const
+{
+ return ( parent() != 0 ? parent()->forceDefaultCharset() : false );
+}
+
+QByteArray Base::defaultCharset() const
+{
+ return ( parent() != 0 ? parent()->defaultCharset() : Latin1 );
+}
+
+const char *Base::type() const
+{
+ return "";
+}
+
+bool Base::is( const char *t ) const
+{
+ return qstricmp( t, type() ) == 0;
+}
+
+bool Base::isMimeHeader() const
+{
+ return qstrnicmp( type(), "Content-", 8 ) == 0;
+}
+
+bool Base::isXHeader() const
+{
+ return qstrncmp( type(), "X-", 2 ) == 0;
+}
+
+QByteArray Base::typeIntro() const
+{
+ return QByteArray( type() ) + ": ";
+}
+
+//-----</Base>---------------------------------
+
+namespace Generics {
+
+//-----<Unstructured>-------------------------
+
+//@cond PRIVATE
+kmime_mk_dptr_ctor( Unstructured, Base )
+//@endcond
+
+Unstructured::Unstructured( Content *p ) : Base( new UnstructuredPrivate, p )
+{
+}
+
+Unstructured::Unstructured( Content *p, const QByteArray &s ) : Base( new UnstructuredPrivate, p )
+{
+ from7BitString( s );
+}
+
+Unstructured::Unstructured( Content *p, const QString &s, const QByteArray &cs ) : Base( new UnstructuredPrivate, p )
+{
+ fromUnicodeString( s, cs );
+}
+
+Unstructured::~Unstructured()
+{
+}
+
+void Unstructured::from7BitString( const QByteArray &s )
+{
+ Q_D(Unstructured);
+ d->decoded = decodeRFC2047String( s, d->encCS, defaultCharset(), forceDefaultCharset() );
+}
+
+QByteArray Unstructured::as7BitString( bool withHeaderType ) const
+{
+ const Q_D(Unstructured);
+ QByteArray result;
+ if ( withHeaderType ) {
+ result = typeIntro();
+ }
+ result += encodeRFC2047String( d->decoded, d->encCS ) ;
+
+ return result;
+}
+
+void Unstructured::fromUnicodeString( const QString &s, const QByteArray &b )
+{
+ Q_D(Unstructured);
+ d->decoded = s;
+ d->encCS = cachedCharset( b );
+}
+
+QString Unstructured::asUnicodeString() const
+{
+ return d_func()->decoded;
+}
+
+void Unstructured::clear()
+{
+ Q_D(Unstructured);
+ d->decoded.truncate( 0 );
+}
+
+bool Unstructured::isEmpty() const
+{
+ return d_func()->decoded.isEmpty();
+}
+
+//-----</Unstructured>-------------------------
+
+//-----<Structured>-------------------------
+
+Structured::Structured( Content *p ) : Base( new StructuredPrivate, p )
+{
+}
+
+Structured::Structured( Content *p, const QByteArray &s ) : Base( new StructuredPrivate, p )
+{
+ from7BitString( s );
+}
+
+Structured::Structured( Content *p, const QString &s, const QByteArray &cs ) : Base( new StructuredPrivate, p )
+{
+ fromUnicodeString( s, cs );
+}
+
+kmime_mk_dptr_ctor( Structured, Base )
+
+Structured::~Structured()
+{
+}
+
+void Structured::from7BitString( const QByteArray &s )
+{
+ Q_D(Structured);
+ if ( d->encCS.isEmpty() ) {
+ d->encCS = defaultCharset();
+ }
+ const char *cursor = s.constData();
+ parse( cursor, cursor + s.length() );
+}
+
+QString Structured::asUnicodeString() const
+{
+ return QString::fromLatin1( as7BitString( false ) );
+}
+
+void Structured::fromUnicodeString( const QString &s, const QByteArray &b )
+{
+ Q_D(Structured);
+ d->encCS = cachedCharset( b );
+ from7BitString( s.toLatin1() );
+}
+
+//-----</Structured>-------------------------
+
+//-----<Address>-------------------------
+
+Address::Address( Content *p ) : Structured( new AddressPrivate, p )
+{
+}
+
+Address::Address( Content *p, const QByteArray &s ) : Structured( new AddressPrivate, p )
+{
+ from7BitString( s );
+}
+
+Address::Address( Content *p, const QString &s, const QByteArray &cs ) : Structured( new AddressPrivate, p )
+{
+ fromUnicodeString( s, cs );
+}
+
+kmime_mk_dptr_ctor( Address, Structured )
+
+Address:: ~Address()
+{
+}
+
+// helper method used in AddressList and MailboxList
+static bool stringToMailbox( const QByteArray &address,
+ const QString &displayName, Types::Mailbox &mbox )
+{
+ Types::AddrSpec addrSpec;
+ mbox.setName( displayName );
+ const char *cursor = address.constData();
+ if ( !parseAngleAddr( cursor, cursor + address.length(), addrSpec ) ) {
+ if ( !parseAddrSpec( cursor, cursor + address.length(), addrSpec ) ) {
+ kWarning() << "Invalid address";
+ return false;
+ }
+ }
+ mbox.setAddress( addrSpec );
+ return true;
+}
+
+//-----</Address>-------------------------
+
+//-----<MailboxList>-------------------------
+
+kmime_mk_trivial_ctor_with_dptr( MailboxList, Address )
+kmime_mk_dptr_ctor( MailboxList, Address )
+
+QByteArray MailboxList::as7BitString( bool withHeaderType ) const
+{
+ const Q_D(MailboxList);
+ if ( isEmpty() ) {
+ return QByteArray();
+ }
+
+ QByteArray rv;
+ if ( withHeaderType ) {
+ rv = typeIntro();
+ }
+ foreach ( const Types::Mailbox &mbox, d->mailboxList ) {
+ rv += mbox.as7BitString( d->encCS );
+ rv += ", ";
+ }
+ rv.resize( rv.length() - 2 );
+ return rv;
+}
+
+void MailboxList::fromUnicodeString( const QString &s, const QByteArray &b )
+{
+ Q_D(MailboxList);
+ d->encCS = cachedCharset( b );
+ from7BitString( encodeRFC2047Sentence( s, b ) );
+}
+
+QString MailboxList::asUnicodeString() const
+{
+ return prettyAddresses().join( QLatin1String( ", " ) );
+}
+
+void MailboxList::clear()
+{
+ Q_D(MailboxList);
+ d->mailboxList.clear();
+}
+
+bool MailboxList::isEmpty() const
+{
+ return d_func()->mailboxList.isEmpty();
+}
+
+void MailboxList::addAddress( const Types::Mailbox &mbox )
+{
+ Q_D(MailboxList);
+ d->mailboxList.append( mbox );
+}
+
+void MailboxList::addAddress( const QByteArray &address,
+ const QString &displayName )
+{
+ Q_D(MailboxList);
+ Types::Mailbox mbox;
+ if ( stringToMailbox( address, displayName, mbox ) ) {
+ d->mailboxList.append( mbox );
+ }
+}
+
+QList< QByteArray > MailboxList::addresses() const
+{
+ QList<QByteArray> rv;
+ foreach ( const Types::Mailbox &mbox, d_func()->mailboxList ) {
+ rv.append( mbox.address() );
+ }
+ return rv;
+}
+
+QStringList MailboxList::displayNames() const
+{
+ QStringList rv;
+ foreach ( const Types::Mailbox &mbox, d_func()->mailboxList ) {
+ rv.append( mbox.name() );
+ }
+ return rv;
+}
+
+QStringList MailboxList::prettyAddresses() const
+{
+ QStringList rv;
+ foreach ( const Types::Mailbox &mbox, d_func()->mailboxList ) {
+ rv.append( mbox.prettyAddress() );
+ }
+ return rv;
+}
+
+Types::Mailbox::List MailboxList::mailboxes() const
+{
+ return d_func()->mailboxList;
+}
+
+bool MailboxList::parse( const char* &scursor, const char *const send,
+ bool isCRLF )
+{
+ Q_D(MailboxList);
+ // examples:
+ // from := "From:" mailbox-list CRLF
+ // sender := "Sender:" mailbox CRLF
+
+ // parse an address-list:
+ QList<Types::Address> maybeAddressList;
+ if ( !parseAddressList( scursor, send, maybeAddressList, isCRLF ) ) {
+ return false;
+ }
+
+ d->mailboxList.clear();
+
+ // extract the mailboxes and complain if there are groups:
+ QList<Types::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;
+ }
+ d->mailboxList += (*it).mailboxList;
+ }
+ return true;
+}
+
+//-----</MailboxList>-------------------------
+
+//-----<SingleMailbox>-------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_dptr( SingleMailbox, MailboxList )
+//@endcond
+
+bool SingleMailbox::parse( const char* &scursor, const char *const send,
+ bool isCRLF )
+{
+ Q_D(MailboxList);
+ if ( !MailboxList::parse( scursor, send, isCRLF ) ) {
+ return false;
+ }
+
+ if ( d->mailboxList.count() > 1 ) {
+ KMIME_WARN << "multiple mailboxes in header allowing only a single one!"
+ << endl;
+ }
+ return true;
+}
+
+//-----</SingleMailbox>-------------------------
+
+//-----<AddressList>-------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_dptr( AddressList, Address )
+kmime_mk_dptr_ctor( AddressList, Address )
+//@endcond
+
+QByteArray AddressList::as7BitString( bool withHeaderType ) const
+{
+ const Q_D(AddressList);
+ if ( d->addressList.isEmpty() ) {
+ return QByteArray();
+ }
+
+ QByteArray rv;
+ if ( withHeaderType ) {
+ rv = typeIntro();
+ }
+ foreach ( const Types::Address &addr, d->addressList ) {
+ foreach ( const Types::Mailbox &mbox, addr.mailboxList ) {
+ rv += mbox.as7BitString( d->encCS );
+ rv += ", ";
+ }
+ }
+ rv.resize( rv.length() - 2 );
+ return rv;
+}
+
+void AddressList::fromUnicodeString( const QString &s, const QByteArray &b )
+{
+ Q_D(AddressList);
+ d->encCS = cachedCharset( b );
+ from7BitString( encodeRFC2047Sentence( s, b ) );
+}
+
+QString AddressList::asUnicodeString() const
+{
+ return prettyAddresses().join( QLatin1String( ", " ) );
+}
+
+void AddressList::clear()
+{
+ Q_D(AddressList);
+ d->addressList.clear();
+}
+
+bool AddressList::isEmpty() const
+{
+ return d_func()->addressList.isEmpty();
+}
+
+void AddressList::addAddress( const Types::Mailbox &mbox )
+{
+ Q_D(AddressList);
+ Types::Address addr;
+ addr.mailboxList.append( mbox );
+ d->addressList.append( addr );
+}
+
+void AddressList::addAddress( const QByteArray &address,
+ const QString &displayName )
+{
+ Q_D(AddressList);
+ Types::Address addr;
+ Types::Mailbox mbox;
+ if ( stringToMailbox( address, displayName, mbox ) ) {
+ addr.mailboxList.append( mbox );
+ d->addressList.append( addr );
+ }
+}
+
+QList< QByteArray > AddressList::addresses() const
+{
+ QList<QByteArray> rv;
+ foreach ( const Types::Address &addr, d_func()->addressList ) {
+ foreach ( const Types::Mailbox &mbox, addr.mailboxList ) {
+ rv.append( mbox.address() );
+ }
+ }
+ return rv;
+}
+
+QStringList AddressList::displayNames() const
+{
+ QStringList rv;
+ foreach ( const Types::Address &addr, d_func()->addressList ) {
+ foreach ( const Types::Mailbox &mbox, addr.mailboxList ) {
+ rv.append( mbox.name() );
+ }
+ }
+ return rv;
+}
+
+QStringList AddressList::prettyAddresses() const
+{
+ QStringList rv;
+ foreach ( const Types::Address &addr, d_func()->addressList ) {
+ foreach ( const Types::Mailbox &mbox, addr.mailboxList ) {
+ rv.append( mbox.prettyAddress() );
+ }
+ }
+ return rv;
+}
+
+Types::Mailbox::List AddressList::mailboxes() const
+{
+ Types::Mailbox::List rv;
+ foreach ( const Types::Address &addr, d_func()->addressList ) {
+ foreach ( const Types::Mailbox &mbox, addr.mailboxList ) {
+ rv.append( mbox );
+ }
+ }
+ return rv;
+}
+
+bool AddressList::parse( const char* &scursor, const char *const send,
+ bool isCRLF )
+{
+ Q_D(AddressList);
+ QList<Types::Address> maybeAddressList;
+ if ( !parseAddressList( scursor, send, maybeAddressList, isCRLF ) ) {
+ return false;
+ }
+
+ d->addressList = maybeAddressList;
+ return true;
+}
+
+//-----</AddressList>-------------------------
+
+//-----<Token>-------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_dptr( Token, Structured )
+kmime_mk_dptr_ctor( Token, Structured )
+//@endcond
+
+QByteArray Token::as7BitString( bool withHeaderType ) const
+{
+ if ( isEmpty() ) {
+ return QByteArray();
+ }
+ if ( withHeaderType ) {
+ return typeIntro() + d_func()->token;
+ }
+ return d_func()->token;
+}
+
+void Token::clear()
+{
+ Q_D(Token);
+ d->token.clear();
+}
+
+bool Token::isEmpty() const
+{
+ return d_func()->token.isEmpty();
+}
+
+QByteArray Token::token() const
+{
+ return d_func()->token;
+}
+
+void Token::setToken( const QByteArray &t )
+{
+ Q_D(Token);
+ d->token = t;
+}
+
+bool Token::parse( const char* &scursor, const char *const send, bool isCRLF )
+{
+ Q_D(Token);
+ clear();
+ 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;
+ }
+ d->token = QByteArray( 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;
+}
+
+//-----</Token>-------------------------
+
+//-----<PhraseList>-------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_dptr( PhraseList, Structured )
+//@endcond
+
+QByteArray PhraseList::as7BitString( bool withHeaderType ) const
+{
+ const Q_D(PhraseList);
+ if ( isEmpty() ) {
+ return QByteArray();
+ }
+
+ QByteArray rv;
+ if ( withHeaderType ) {
+ rv = typeIntro();
+ }
+
+ for ( int i = 0; i < d->phraseList.count(); ++i ) {
+ // FIXME: only encode when needed, quote when needed, etc.
+ rv += encodeRFC2047String( d->phraseList[i], d->encCS, false, false );
+ if ( i != d->phraseList.count() - 1 ) {
+ rv += ", ";
+ }
+ }
+
+ return rv;
+}
+
+QString PhraseList::asUnicodeString() const
+{
+ return d_func()->phraseList.join( QLatin1String( ", " ) );
+}
+
+void PhraseList::clear()
+{
+ Q_D(PhraseList);
+ d->phraseList.clear();
+}
+
+bool PhraseList::isEmpty() const
+{
+ return d_func()->phraseList.isEmpty();
+}
+
+QStringList PhraseList::phrases() const
+{
+ return d_func()->phraseList;
+}
+
+bool PhraseList::parse( const char* &scursor, const char *const send,
+ bool isCRLF )
+{
+ Q_D(PhraseList);
+ d->phraseList.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;
+ }
+ d->phraseList.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;
+}
+
+//-----</PhraseList>-------------------------
+
+//-----<DotAtom>-------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_dptr( DotAtom, Structured )
+//@endcond
+
+QByteArray DotAtom::as7BitString( bool withHeaderType ) const
+{
+ if ( isEmpty() ) {
+ return QByteArray();
+ }
+
+ QByteArray rv;
+ if ( withHeaderType ) {
+ rv += typeIntro();
+ }
+
+ rv += d_func()->dotAtom.toLatin1(); // FIXME: encoding?
+ return rv;
+}
+
+QString DotAtom::asUnicodeString() const
+{
+ return d_func()->dotAtom;
+}
+
+void DotAtom::clear()
+{
+ Q_D(DotAtom);
+ d->dotAtom.clear();
+}
+
+bool DotAtom::isEmpty() const
+{
+ return d_func()->dotAtom.isEmpty();
+}
+
+bool DotAtom::parse( const char* &scursor, const char *const send,
+ bool isCRLF )
+{
+ Q_D(DotAtom);
+ QString maybeDotAtom;
+ if ( !parseDotAtom( scursor, send, maybeDotAtom, isCRLF ) ) {
+ return false;
+ }
+
+ d->dotAtom = 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;
+}
+
+//-----</DotAtom>-------------------------
+
+//-----<Parametrized>-------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_dptr( Parametrized, Structured )
+kmime_mk_dptr_ctor( Parametrized, Structured )
+//@endcond
+
+QByteArray Parametrized::as7BitString( bool withHeaderType ) const
+{
+ const Q_D(Parametrized);
+ if ( isEmpty() ) {
+ return QByteArray();
+ }
+
+ QByteArray rv;
+ if ( withHeaderType ) {
+ rv += typeIntro();
+ }
+
+ bool first = true;
+ for ( QMap<QString,QString>::ConstIterator it = d->parameterHash.constBegin();
+ it != d->parameterHash.constEnd(); ++it )
+ {
+ if ( !first ) {
+ rv += "; ";
+ } else {
+ first = false;
+ }
+ if ( isUsAscii( it.value() ) ) {
+ rv += it.key().toLatin1() + '=';
+ QByteArray tmp = it.value().toLatin1();
+ addQuotes( tmp, true ); // force quoting, eg. for whitespaces in parameter value
+ rv += tmp;
+ } else {
+ if( useOutlookAttachmentEncoding() ) {
+ rv += it.key().toLatin1() + '=';
+ kDebug() << "doing:" << it.value() << QLatin1String( d->encCS );
+ rv += "\"" + encodeRFC2047String( it.value(), d->encCS ) + "\"";
+ } else {
+ rv += it.key().toLatin1() + "*=";
+ rv += encodeRFC2231String( it.value(), d->encCS );
+ }
+ }
+ }
+
+ return rv;
+}
+
+QString Parametrized::parameter( const QString &key ) const
+{
+ return d_func()->parameterHash.value( key.toLower() );
+}
+
+bool Parametrized::hasParameter( const QString &key ) const
+{
+ return d_func()->parameterHash.contains( key.toLower() );
+}
+
+void Parametrized::setParameter( const QString &key, const QString &value )
+{
+ Q_D(Parametrized);
+ d->parameterHash.insert( key.toLower(), value );
+}
+
+bool Parametrized::isEmpty() const
+{
+ return d_func()->parameterHash.isEmpty();
+}
+
+void Parametrized::clear()
+{
+ Q_D(Parametrized);
+ d->parameterHash.clear();
+}
+
+bool Parametrized::parse( const char *& scursor, const char * const send,
+ bool isCRLF )
+{
+ Q_D(Parametrized);
+ d->parameterHash.clear();
+ QByteArray charset;
+ if ( !parseParameterListWithCharset( scursor, send, d->parameterHash, charset, isCRLF ) ) {
+ return false;
+ }
+ d->encCS = charset;
+ return true;
+}
+
+//-----</Parametrized>-------------------------
+
+//-----<Ident>-------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_dptr( Ident, Address )
+kmime_mk_dptr_ctor( Ident, Address )
+//@endcond
+
+QByteArray Ident::as7BitString( bool withHeaderType ) const
+{
+ const Q_D(Ident);
+ if ( d->msgIdList.isEmpty() ) {
+ return QByteArray();
+ }
+
+ QByteArray rv;
+ if ( withHeaderType ) {
+ rv = typeIntro();
+ }
+ foreach ( const Types::AddrSpec &addr, d->msgIdList ) {
+ if ( !addr.isEmpty() ) {
+ const QString asString = addr.asString();
+ rv += '<';
+ if ( !asString.isEmpty() ) {
+ rv += asString.toLatin1(); // FIXME: change parsing to use QByteArrays
+ }
+ rv += "> ";
+ }
+ }
+ if ( !rv.isEmpty() ) {
+ rv.resize( rv.length() - 1 );
+ }
+ return rv;
+}
+
+void Ident::clear()
+{
+ Q_D(Ident);
+ d->msgIdList.clear();
+ d->cachedIdentifier.clear();
+}
+
+bool Ident::isEmpty() const
+{
+ return d_func()->msgIdList.isEmpty();
+}
+
+bool Ident::parse( const char* &scursor, const char * const send, bool isCRLF )
+{
+ Q_D(Ident);
+ // 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
+
+ d->msgIdList.clear();
+ d->cachedIdentifier.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;
+ }
+ d->msgIdList.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;
+}
+
+QList<QByteArray> Ident::identifiers() const
+{
+ QList<QByteArray> rv;
+ foreach ( const Types::AddrSpec &addr, d_func()->msgIdList ) {
+ if ( !addr.isEmpty() ) {
+ const QString asString = addr.asString();
+ if ( !asString.isEmpty() ) {
+ rv.append( asString.toLatin1() ); // FIXME: change parsing to use QByteArrays
+ }
+ }
+ }
+ return rv;
+}
+
+void Ident::appendIdentifier( const QByteArray &id )
+{
+ Q_D(Ident);
+ QByteArray tmp = id;
+ if ( !tmp.startsWith( '<' ) ) {
+ tmp.prepend( '<' );
+ }
+ if ( !tmp.endsWith( '>' ) ) {
+ tmp.append( '>' );
+ }
+ AddrSpec msgId;
+ const char *cursor = tmp.constData();
+ if ( parseAngleAddr( cursor, cursor + tmp.length(), msgId ) ) {
+ d->msgIdList.append( msgId );
+ } else {
+ kWarning() << "Unable to parse address spec!";
+ }
+}
+
+//-----</Ident>-------------------------
+
+//-----<SingleIdent>-------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_dptr( SingleIdent, Ident )
+kmime_mk_dptr_ctor( SingleIdent, Ident )
+//@endcond
+
+QByteArray SingleIdent::identifier() const
+{
+ if ( d_func()->msgIdList.isEmpty() ) {
+ return QByteArray();
+ }
+
+ if ( d_func()->cachedIdentifier.isEmpty() ) {
+ const Types::AddrSpec &addr = d_func()->msgIdList.first();
+ if ( !addr.isEmpty() ) {
+ const QString asString = addr.asString();
+ if ( !asString.isEmpty() ) {
+ d_func()->cachedIdentifier = asString.toLatin1();// FIXME: change parsing to use QByteArrays
+ }
+ }
+ }
+
+ return d_func()->cachedIdentifier;
+}
+
+void SingleIdent::setIdentifier( const QByteArray &id )
+{
+ Q_D(SingleIdent);
+ d->msgIdList.clear();
+ d->cachedIdentifier.clear();
+ appendIdentifier( id );
+}
+
+bool SingleIdent::parse( const char* &scursor, const char * const send,
+ bool isCRLF )
+{
+ Q_D(SingleIdent);
+ if ( !Ident::parse( scursor, send, isCRLF ) ) {
+ return false;
+ }
+
+ if ( d->msgIdList.count() > 1 ) {
+ KMIME_WARN << "more than one msg-id in header "
+ << "allowing only a single one!" << endl;
+ }
+ return true;
+}
+
+//-----</SingleIdent>-------------------------
+
+} // namespace Generics
+
+//-----<ReturnPath>-------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_name_and_dptr( ReturnPath, Generics::Address, Return-Path )
+//@endcond
+
+QByteArray ReturnPath::as7BitString( bool withHeaderType ) const
+{
+ if ( isEmpty() ) {
+ return QByteArray();
+ }
+
+ QByteArray rv;
+ if ( withHeaderType ) {
+ rv += typeIntro();
+ }
+ rv += '<' + d_func()->mailbox.as7BitString( d_func()->encCS ) + '>';
+ return rv;
+}
+
+void ReturnPath::clear()
+{
+ Q_D(ReturnPath);
+ d->mailbox.setAddress( Types::AddrSpec() );
+ d->mailbox.setName( QString() );
+}
+
+bool ReturnPath::isEmpty() const
+{
+ const Q_D(ReturnPath);
+ return !d->mailbox.hasAddress() && !d->mailbox.hasName();
+}
+
+bool ReturnPath::parse( const char* &scursor, const char * const send,
+ bool isCRLF )
+{
+ Q_D(ReturnPath);
+ 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.setName( QString() );
+ maybeMailbox.setAddress( emptyAddrSpec );
+ } else {
+ // check that there was no display-name:
+ if ( maybeMailbox.hasName() ) {
+ KMIME_WARN << "display-name \"" << maybeMailbox.name()
+ << "\" in Return-Path!" << endl;
+ }
+ }
+ d->mailbox = maybeMailbox;
+
+ // 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>-------------------------------
+
+// NOTE: Do *not* register Generic with HeaderFactory, since its type() is changeable.
+
+Generic::Generic() : Generics::Unstructured( new GenericPrivate )
+{
+}
+
+Generic::Generic( const char *t ) : Generics::Unstructured( new GenericPrivate )
+{
+ setType( t );
+}
+
+Generic::Generic( const char *t, Content *p )
+ : Generics::Unstructured( new GenericPrivate, p )
+{
+ setType( t );
+}
+
+Generic::Generic( const char *t, Content *p, const QByteArray &s )
+ : Generics::Unstructured( new GenericPrivate, p )
+{
+ from7BitString( s );
+ setType( t );
+}
+
+Generic::Generic( const char *t, Content *p, const QString &s, const QByteArray &cs )
+ : Generics::Unstructured( new GenericPrivate, p )
+{
+ fromUnicodeString( s, cs );
+ setType( t );
+}
+
+Generic::~Generic()
+{
+}
+
+void Generic::clear()
+{
+ Q_D(Generic);
+ delete[] d->type;
+ d->type = 0;
+ Unstructured::clear();
+}
+
+bool Generic::isEmpty() const
+{
+ return d_func()->type == 0 || Unstructured::isEmpty();
+}
+
+const char *Generic::type() const
+{
+ return d_func()->type;
+}
+
+void Generic::setType( const char *type )
+{
+ Q_D(Generic);
+ if ( d->type ) {
+ delete[] d->type;
+ }
+ if ( type ) {
+ d->type = new char[strlen( type )+1];
+ strcpy( d->type, type );
+ } else {
+ d->type = 0;
+ }
+}
+
+//-----<Generic>-------------------------------
+
+//-----<MessageID>-----------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_name( MessageID, Generics::SingleIdent, Message-ID )
+//@endcond
+
+void MessageID::generate( const QByteArray &fqdn )
+{
+ setIdentifier( '<' + uniqueString() + '@' + fqdn + '>' );
+}
+
+//-----</MessageID>----------------------------
+
+//-----<Control>-------------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_name_and_dptr( Control, Generics::Structured, Control )
+//@endcond
+
+QByteArray Control::as7BitString( bool withHeaderType ) const
+{
+ const Q_D(Control);
+ if ( isEmpty() ) {
+ return QByteArray();
+ }
+
+ QByteArray rv;
+ if ( withHeaderType ) {
+ rv += typeIntro();
+ }
+
+ rv += d->name;
+ if ( !d->parameter.isEmpty() ) {
+ rv += ' ' + d->parameter;
+ }
+ return rv;
+}
+
+void Control::clear()
+{
+ Q_D(Control);
+ d->name.clear();
+ d->parameter.clear();
+}
+
+bool Control::isEmpty() const
+{
+ return d_func()->name.isEmpty();
+}
+
+QByteArray Control::controlType() const
+{
+ return d_func()->name;
+}
+
+QByteArray Control::parameter() const
+{
+ return d_func()->parameter;
+}
+
+bool Control::isCancel() const
+{
+ return d_func()->name.toLower() == "cancel";
+}
+
+void Control::setCancel( const QByteArray &msgid )
+{
+ Q_D(Control);
+ d->name = "cancel";
+ d->parameter = msgid;
+}
+
+bool Control::parse( const char* &scursor, const char *const send, bool isCRLF )
+{
+ Q_D(Control);
+ clear();
+ eatCFWS( scursor, send, isCRLF );
+ if ( scursor == send ) {
+ return false;
+ }
+ const char *start = scursor;
+ while ( scursor != send && !isspace( *scursor ) ) {
+ ++scursor;
+ }
+ d->name = QByteArray( start, scursor - start );
+ eatCFWS( scursor, send, isCRLF );
+ d->parameter = QByteArray( scursor, send - scursor );
+ return true;
+}
+
+//-----</Control>------------------------------
+
+//-----<MailCopiesTo>--------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_name_and_dptr( MailCopiesTo,
+ Generics::AddressList, Mail-Copies-To )
+//@endcond
+
+QByteArray MailCopiesTo::as7BitString( bool withHeaderType ) const
+{
+ QByteArray rv;
+ if ( withHeaderType ) {
+ rv += typeIntro();
+ }
+ if ( !AddressList::isEmpty() ) {
+ rv += AddressList::as7BitString( false );
+ } else {
+ if ( d_func()->alwaysCopy ) {
+ rv += "poster";
+ } else if ( d_func()->neverCopy ) {
+ rv += "nobody";
+ }
+ }
+ return rv;
+}
+
+QString MailCopiesTo::asUnicodeString() const
+{
+ if ( !AddressList::isEmpty() ) {
+ return AddressList::asUnicodeString();
+ }
+ if ( d_func()->alwaysCopy ) {
+ return QLatin1String( "poster" );
+ }
+ if ( d_func()->neverCopy ) {
+ return QLatin1String( "nobody" );
+ }
+ return QString();
+}
+
+void MailCopiesTo::clear()
+{
+ Q_D(MailCopiesTo);
+ AddressList::clear();
+ d->alwaysCopy = false;
+ d->neverCopy = false;
+}
+
+bool MailCopiesTo::isEmpty() const
+{
+ return AddressList::isEmpty() && !(d_func()->alwaysCopy || d_func()->neverCopy);
+}
+
+bool MailCopiesTo::alwaysCopy() const
+{
+ return !AddressList::isEmpty() || d_func()->alwaysCopy;
+}
+
+void MailCopiesTo::setAlwaysCopy()
+{
+ Q_D(MailCopiesTo);
+ clear();
+ d->alwaysCopy = true;
+}
+
+bool MailCopiesTo::neverCopy() const
+{
+ return d_func()->neverCopy;
+}
+
+void MailCopiesTo::setNeverCopy()
+{
+ Q_D(MailCopiesTo);
+ clear();
+ d->neverCopy = true;
+}
+
+bool MailCopiesTo::parse( const char *& scursor, const char * const send,
+ bool isCRLF )
+{
+ Q_D(MailCopiesTo);
+ clear();
+ if ( send - scursor == 5 ) {
+ if ( qstrnicmp( "never", scursor, 5 ) == 0 ) {
+ d->neverCopy = true;
+ return true;
+ }
+ }
+ if ( send - scursor == 6 ) {
+ if ( qstrnicmp( "always", scursor, 6 ) == 0 || qstrnicmp( "poster", scursor, 6 ) == 0 ) {
+ d->alwaysCopy = true;
+ return true;
+ }
+ if ( qstrnicmp( "nobody", scursor, 6 ) == 0 ) {
+ d->neverCopy = true;
+ return true;
+ }
+ }
+ return AddressList::parse( scursor, send, isCRLF );
+}
+
+//-----</MailCopiesTo>-------------------------
+
+//-----<Date>----------------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_name_and_dptr( Date, Generics::Structured, Date )
+//@endcond
+
+QByteArray Date::as7BitString( bool withHeaderType ) const
+{
+ if ( isEmpty() ) {
+ return QByteArray();
+ }
+
+ QByteArray rv;
+ if ( withHeaderType ) {
+ rv += typeIntro();
+ }
+ rv += d_func()->dateTime.toString( KDateTime::RFCDateDay ).toLatin1();
+ return rv;
+}
+
+void Date::clear()
+{
+ Q_D(Date);
+ d->dateTime = KDateTime();
+}
+
+bool Date::isEmpty() const
+{
+ return d_func()->dateTime.isNull() || !d_func()->dateTime.isValid();
+}
+
+KDateTime Date::dateTime() const
+{
+ return d_func()->dateTime;
+}
+
+void Date::setDateTime( const KDateTime &dt )
+{
+ Q_D(Date);
+ d->dateTime = dt;
+}
+
+int Date::ageInDays() const
+{
+ QDate today = QDate::currentDate();
+ return dateTime().date().daysTo(today);
+}
+
+bool Date::parse( const char* &scursor, const char *const send, bool isCRLF )
+{
+ Q_D(Date);
+ return parseDateTime( scursor, send, d->dateTime, isCRLF );
+}
+
+//-----</Date>---------------------------------
+
+//-----<Newsgroups>----------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_name_and_dptr( Newsgroups, Generics::Structured, Newsgroups )
+kmime_mk_trivial_ctor_with_name( FollowUpTo, Newsgroups, Followup-To )
+//@endcond
+
+QByteArray Newsgroups::as7BitString( bool withHeaderType ) const
+{
+ const Q_D(Newsgroups);
+ if ( isEmpty() ) {
+ return QByteArray();
+ }
+
+ QByteArray rv;
+ if ( withHeaderType ) {
+ rv += typeIntro();
+ }
+
+ for ( int i = 0; i < d->groups.count(); ++i ) {
+ rv += d->groups[ i ];
+ if ( i != d->groups.count() - 1 ) {
+ rv += ',';
+ }
+ }
+ return rv;
+}
+
+void Newsgroups::fromUnicodeString( const QString &s, const QByteArray &b )
+{
+ Q_UNUSED( b );
+ Q_D(Newsgroups);
+ from7BitString( s.toUtf8() );
+ d->encCS = cachedCharset( "UTF-8" );
+}
+
+QString Newsgroups::asUnicodeString() const
+{
+ return QString::fromUtf8( as7BitString( false ) );
+}
+
+void Newsgroups::clear()
+{
+ Q_D(Newsgroups);
+ d->groups.clear();
+}
+
+bool Newsgroups::isEmpty() const
+{
+ return d_func()->groups.isEmpty();
+}
+
+QList<QByteArray> Newsgroups::groups() const
+{
+ return d_func()->groups;
+}
+
+void Newsgroups::setGroups( const QList<QByteArray> &groups )
+{
+ Q_D(Newsgroups);
+ d->groups = groups;
+}
+
+bool Newsgroups::isCrossposted() const
+{
+ return d_func()->groups.count() >= 2;
+}
+
+bool Newsgroups::parse( const char* &scursor, const char *const send, bool isCRLF )
+{
+ Q_D(Newsgroups);
+ clear();
+ forever {
+ eatCFWS( scursor, send, isCRLF );
+ if ( scursor != send && *scursor == ',' ) {
+ ++scursor;
+ }
+ eatCFWS( scursor, send, isCRLF );
+ if ( scursor == send ) {
+ return true;
+ }
+ const char *start = scursor;
+ while ( scursor != send && !isspace( *scursor ) && *scursor != ',' ) {
+ ++scursor;
+ }
+ QByteArray group( start, scursor - start );
+ d->groups.append( group );
+ }
+ return true;
+}
+
+//-----</Newsgroups>---------------------------
+
+//-----<Lines>---------------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_name_and_dptr( Lines, Generics::Structured, Lines )
+//@endcond
+
+QByteArray Lines::as7BitString( bool withHeaderType ) const
+{
+ if ( isEmpty() ) {
+ return QByteArray();
+ }
+
+ QByteArray num;
+ num.setNum( d_func()->lines );
+
+ if ( withHeaderType ) {
+ return typeIntro() + num;
+ }
+ return num;
+}
+
+QString Lines::asUnicodeString() const
+{
+ if ( isEmpty() ) {
+ return QString();
+ }
+ return QString::number( d_func()->lines );
+}
+
+void Lines::clear()
+{
+ Q_D(Lines);
+ d->lines = -1;
+}
+
+bool Lines::isEmpty() const
+{
+ return d_func()->lines == -1;
+}
+
+int Lines::numberOfLines() const
+{
+ return d_func()->lines;
+}
+
+void Lines::setNumberOfLines( int lines )
+{
+ Q_D(Lines);
+ d->lines = lines;
+}
+
+bool Lines::parse( const char* &scursor, const char* const send, bool isCRLF )
+{
+ Q_D(Lines);
+ eatCFWS( scursor, send, isCRLF );
+ if ( parseDigits( scursor, send, d->lines ) == 0 ) {
+ clear();
+ return false;
+ }
+ return true;
+}
+
+//-----</Lines>--------------------------------
+
+//-----<Content-Type>--------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_name_and_dptr( ContentType, Generics::Parametrized,
+ Content-Type )
+//@endcond
+
+bool ContentType::isEmpty() const
+{
+ return d_func()->mimeType.isEmpty();
+}
+
+void ContentType::clear()
+{
+ Q_D(ContentType);
+ d->category = CCsingle;
+ d->mimeType.clear();
+ Parametrized::clear();
+}
+
+QByteArray ContentType::as7BitString( bool withHeaderType ) const
+{
+ if ( isEmpty() ) {
+ return QByteArray();
+ }
+
+ QByteArray rv;
+ if ( withHeaderType ) {
+ rv += typeIntro();
+ }
+
+ rv += mimeType();
+ if ( !Parametrized::isEmpty() ) {
+ rv += "; " + Parametrized::as7BitString( false );
+ }
+
+ return rv;
+}
+
+QByteArray ContentType::mimeType() const
+{
+ Q_D(const ContentType);
+ return d->mimeType;
+}
+
+QByteArray ContentType::mediaType() const
+{
+ Q_D(const ContentType);
+ const int pos = d->mimeType.indexOf( '/' );
+ if ( pos < 0 )
+ return d->mimeType;
+ else
+ return d->mimeType.left( pos );
+}
+
+QByteArray ContentType::subType() const
+{
+ Q_D(const ContentType);
+ const int pos = d->mimeType.indexOf( '/' );
+ if ( pos < 0 )
+ return QByteArray();
+ else
+ return d->mimeType.mid( pos + 1);
+}
+
+void ContentType::setMimeType( const QByteArray &mimeType )
+{
+ Q_D(ContentType);
+ d->mimeType = mimeType;
+ Parametrized::clear();
+
+ if ( isMultipart() ) {
+ d->category = CCcontainer;
+ } else {
+ d->category = CCsingle;
+ }
+}
+
+bool ContentType::isMediatype( const char *mediatype ) const
+{
+ Q_D(const ContentType);
+ const int len = strlen( mediatype );
+ return qstrnicmp( d->mimeType.constData(), mediatype, len ) == 0 && (d->mimeType.at(len) == '/' || d->mimeType.size() == len);
+}
+
+bool ContentType::isSubtype( const char *subtype ) const
+{
+ Q_D(const ContentType);
+ const int pos = d->mimeType.indexOf( '/' );
+ if ( pos < 0 )
+ return false;
+ const int len = strlen( subtype );
+ return qstrnicmp( d->mimeType.constData() + pos + 1, subtype, len ) == 0 && d->mimeType.size() == pos + len + 1;
+}
+
+bool ContentType::isText() const
+{
+ return ( isMediatype( "text" ) || isEmpty() );
+}
+
+bool ContentType::isPlainText() const
+{
+ return ( qstricmp( d_func()->mimeType.constData(), "text/plain" ) == 0 || isEmpty() );
+}
+
+bool ContentType::isHTMLText() const
+{
+ return qstricmp( d_func()->mimeType.constData(), "text/html" ) == 0;
+}
+
+bool ContentType::isImage() const
+{
+ return isMediatype( "image" );
+}
+
+bool ContentType::isMultipart() const
+{
+ return isMediatype( "multipart" );
+}
+
+bool ContentType::isPartial() const
+{
+ return qstricmp( d_func()->mimeType.constData(), "message/partial" ) == 0;
+}
+
+QByteArray ContentType::charset() const
+{
+ QByteArray ret = parameter( QLatin1String( "charset" ) ).toLatin1();
+ if ( ret.isEmpty() || forceDefaultCharset() ) {
+ //return the default-charset if necessary
+ ret = defaultCharset();
+ }
+ return ret;
+}
+
+void ContentType::setCharset( const QByteArray &s )
+{
+ setParameter( QLatin1String( "charset" ), QString::fromLatin1( s ) );
+}
+
+QByteArray ContentType::boundary() const
+{
+ return parameter( QLatin1String( "boundary" ) ).toLatin1();
+}
+
+void ContentType::setBoundary( const QByteArray &s )
+{
+ setParameter( QLatin1String( "boundary" ), QString::fromLatin1( s ) );
+}
+
+QString ContentType::name() const
+{
+ return parameter( QLatin1String( "name" ) );
+}
+
+void ContentType::setName( const QString &s, const QByteArray &cs )
+{
+ Q_D(ContentType);
+ d->encCS = cs;
+ setParameter( QLatin1String( "name" ), s );
+}
+
+QByteArray ContentType::id() const
+{
+ return parameter( QLatin1String( "id" ) ).toLatin1();
+}
+
+void ContentType::setId( const QByteArray &s )
+{
+ setParameter( QLatin1String( "id" ), QString::fromLatin1( s ) );
+}
+
+int ContentType::partialNumber() const
+{
+ QByteArray p = parameter( QLatin1String( "number" ) ).toLatin1();
+ if ( !p.isEmpty() ) {
+ return p.toInt();
+ } else {
+ return -1;
+ }
+}
+
+int ContentType::partialCount() const
+{
+ QByteArray p = parameter( QLatin1String( "total" ) ).toLatin1();
+ if ( !p.isEmpty() ) {
+ return p.toInt();
+ } else {
+ return -1;
+ }
+}
+
+contentCategory ContentType::category() const
+{
+ return d_func()->category;
+}
+
+void ContentType::setCategory( contentCategory c )
+{
+ Q_D(ContentType);
+ d->category = c;
+}
+
+void ContentType::setPartialParams( int total, int number )
+{
+ setParameter( QLatin1String( "number" ), QString::number( number ) );
+ setParameter( QLatin1String( "total" ), QString::number( total ) );
+}
+
+bool ContentType::parse( const char* &scursor, const char * const send,
+ bool isCRLF )
+{
+ Q_D(ContentType);
+ // content-type: type "/" subtype *(";" parameter)
+
+ clear();
+ eatCFWS( scursor, send, isCRLF );
+ if ( scursor == send ) {
+ return false; // empty header
+ }
+
+ // type
+ QPair<const char*,int> maybeMimeType;
+ if ( !parseToken( scursor, send, maybeMimeType, false /* no 8Bit */ ) ) {
+ return false;
+ }
+
+ // 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;
+ }
+
+ d->mimeType.reserve( maybeMimeType.second + maybeSubType.second + 1 );
+ d->mimeType = QByteArray( maybeMimeType.first, maybeMimeType.second ).toLower()
+ + '/' + QByteArray( maybeSubType.first, maybeSubType.second ).toLower();
+
+ // parameter list
+ eatCFWS( scursor, send, isCRLF );
+ if ( scursor == send ) {
+ goto success; // no parameters
+ }
+
+ if ( *scursor != ';' ) {
+ return false;
+ }
+ scursor++;
+
+ if ( !Parametrized::parse( scursor, send, isCRLF ) ) {
+ return false;
+ }
+
+ // adjust category
+success:
+ if ( isMultipart() ) {
+ d->category = CCcontainer;
+ } else {
+ d->category = CCsingle;
+ }
+ return true;
+}
+
+//-----</Content-Type>-------------------------
+
+//-----<ContentID>----------------------
+
+kmime_mk_trivial_ctor_with_name_and_dptr( ContentID, SingleIdent, Content-ID )
+kmime_mk_dptr_ctor( ContentID, SingleIdent )
+
+bool ContentID::parse( const char* &scursor, const char *const send, bool isCRLF )
+{
+ Q_D ( ContentID );
+ // Content-id := "<" contentid ">"
+ // contentid := now whitespaces
+
+ const char* origscursor = scursor;
+ if ( !SingleIdent::parse ( scursor, send, isCRLF ) )
+ {
+ scursor = origscursor;
+ d->msgIdList.clear();
+ d->cachedIdentifier.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 maybeContentId;
+ // Almost parseAngleAddr
+ if ( scursor == send || *scursor != '<' )
+ {
+ return false;
+ }
+ scursor++; // eat '<'
+
+ eatCFWS ( scursor, send, isCRLF );
+ if ( scursor == send )
+ {
+ return false;
+ }
+
+ // Save chars untill '>''
+ QString result;
+ if( !parseAtom(scursor, send, result, false) ) {
+ return false;
+ }
+
+ eatCFWS ( scursor, send, isCRLF );
+ if ( scursor == send || *scursor != '>' )
+ {
+ return false;
+ }
+ scursor++;
+ // /Almost parseAngleAddr
+
+ maybeContentId.localPart = result;
+ d->msgIdList.append ( maybeContentId );
+
+ 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;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+//-----</ContentID>----------------------
+
+//-----<ContentTransferEncoding>----------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_name_and_dptr( ContentTransferEncoding,
+ Generics::Token, Content-Transfer-Encoding )
+//@endcond
+
+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 ContentTransferEncoding::clear()
+{
+ Q_D(ContentTransferEncoding);
+ d->decoded = true;
+ d->cte = CE7Bit;
+ Token::clear();
+}
+
+contentEncoding ContentTransferEncoding::encoding() const
+{
+ return d_func()->cte;
+}
+
+void ContentTransferEncoding::setEncoding( contentEncoding e )
+{
+ Q_D(ContentTransferEncoding);
+ d->cte = e;
+
+ for ( int i = 0; encTable[i].s != 0; ++i ) {
+ if ( d->cte == encTable[i].e ) {
+ setToken( encTable[i].s );
+ break;
+ }
+ }
+}
+
+bool ContentTransferEncoding::decoded() const
+{
+ return d_func()->decoded;
+}
+
+void ContentTransferEncoding::setDecoded( bool decoded )
+{
+ Q_D(ContentTransferEncoding);
+ d->decoded = decoded;
+}
+
+bool ContentTransferEncoding::needToEncode() const
+{
+ const Q_D(ContentTransferEncoding);
+ return d->decoded && (d->cte == CEquPr || d->cte == CEbase64);
+}
+
+bool ContentTransferEncoding::parse( const char *& scursor,
+ const char * const send, bool isCRLF )
+{
+ Q_D(ContentTransferEncoding);
+ clear();
+ if ( !Token::parse( scursor, send, isCRLF ) ) {
+ return false;
+ }
+
+ // TODO: error handling in case of an unknown encoding?
+ for ( int i = 0; encTable[i].s != 0; ++i ) {
+ if ( qstricmp( token().constData(), encTable[i].s ) == 0 ) {
+ d->cte = ( contentEncoding )encTable[i].e;
+ break;
+ }
+ }
+ d->decoded = ( d->cte == CE7Bit || d->cte == CE8Bit );
+ return true;
+}
+
+//-----</ContentTransferEncoding>---------------------------
+
+//-----<ContentDisposition>--------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_name_and_dptr( ContentDisposition,
+ Generics::Parametrized, Content-Disposition )
+//@endcond
+
+QByteArray ContentDisposition::as7BitString( bool withHeaderType ) const
+{
+ if ( isEmpty() ) {
+ return QByteArray();
+ }
+
+ QByteArray rv;
+ if ( withHeaderType ) {
+ rv += typeIntro();
+ }
+
+ if ( d_func()->disposition == CDattachment ) {
+ rv += "attachment";
+ } else if ( d_func()->disposition == CDinline ) {
+ rv += "inline";
+ } else {
+ return QByteArray();
+ }
+
+ if ( !Parametrized::isEmpty() ) {
+ rv += "; " + Parametrized::as7BitString( false );
+ }
+
+ return rv;
+}
+
+bool ContentDisposition::isEmpty() const
+{
+ return d_func()->disposition == CDInvalid;
+}
+
+void ContentDisposition::clear()
+{
+ Q_D(ContentDisposition);
+ d->disposition = CDInvalid;
+ Parametrized::clear();
+}
+
+contentDisposition ContentDisposition::disposition() const
+{
+ return d_func()->disposition;
+}
+
+void ContentDisposition::setDisposition( contentDisposition disp )
+{
+ Q_D(ContentDisposition);
+ d->disposition = disp;
+}
+
+QString KMime::Headers::ContentDisposition::filename() const
+{
+ return parameter( QLatin1String( "filename" ) );
+}
+
+void ContentDisposition::setFilename( const QString &filename )
+{
+ setParameter( QLatin1String( "filename" ), filename );
+}
+
+bool ContentDisposition::parse( const char *& scursor, const char * const send,
+ bool isCRLF )
+{
+ Q_D(ContentDisposition);
+ clear();
+
+ // token
+ QByteArray 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;
+ }
+
+ token = QByteArray( maybeToken.first, maybeToken.second ).toLower();
+ if ( token == "inline" ) {
+ d->disposition = CDinline;
+ } else if ( token == "attachment" ) {
+ d->disposition = CDattachment;
+ } else {
+ return false;
+ }
+
+ // parameter list
+ eatCFWS( scursor, send, isCRLF );
+ if ( scursor == send ) {
+ return true; // no parameters
+ }
+
+ if ( *scursor != ';' ) {
+ return false;
+ }
+ scursor++;
+
+ return Parametrized::parse( scursor, send, isCRLF );
+}
+
+//-----</ContentDisposition>-------------------------
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_name( Subject, Generics::Unstructured, Subject )
+//@endcond
+
+bool Subject::isReply() const
+{
+ return asUnicodeString().indexOf( QLatin1String( "Re:" ), 0, Qt::CaseInsensitive ) == 0;
+}
+
+Base* createHeader( const QByteArray& type )
+{
+ return HeaderFactory::self()->createHeader( type );
+}
+
+
+//@cond PRIVATE
+kmime_mk_trivial_ctor_with_name( ContentDescription,
+ Generics::Unstructured, Content-Description )
+kmime_mk_trivial_ctor_with_name( ContentLocation,
+ Generics::Unstructured, Content-Location )
+kmime_mk_trivial_ctor_with_name( From, Generics::MailboxList, From )
+kmime_mk_trivial_ctor_with_name( Sender, Generics::SingleMailbox, Sender )
+kmime_mk_trivial_ctor_with_name( To, Generics::AddressList, To )
+kmime_mk_trivial_ctor_with_name( Cc, Generics::AddressList, Cc )
+kmime_mk_trivial_ctor_with_name( Bcc, Generics::AddressList, Bcc )
+kmime_mk_trivial_ctor_with_name( ReplyTo, Generics::AddressList, Reply-To )
+kmime_mk_trivial_ctor_with_name( Keywords, Generics::PhraseList, Keywords )
+kmime_mk_trivial_ctor_with_name( MIMEVersion, Generics::DotAtom, MIME-Version )
+kmime_mk_trivial_ctor_with_name( Supersedes, Generics::SingleIdent, Supersedes )
+kmime_mk_trivial_ctor_with_name( InReplyTo, Generics::Ident, In-Reply-To )
+kmime_mk_trivial_ctor_with_name( References, Generics::Ident, References )
+kmime_mk_trivial_ctor_with_name( Organization, Generics::Unstructured, Organization )
+kmime_mk_trivial_ctor_with_name( UserAgent, Generics::Unstructured, User-Agent )
+//@endcond
+
+} // namespace Headers
+
+} // namespace KMime
diff --git a/kmime/kmime_headers.h b/kmime/kmime_headers.h
new file mode 100644
index 0000000..85e1932
--- /dev/null
+++ b/kmime/kmime_headers.h
@@ -0,0 +1,1508 @@
+/* -*- 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
+ Copyright (c) 2006 Volker Krause <vkrause@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ defines the various header classes:
+ - header's base class defining the common interface
+ - generic base classes for different types of fields
+ - incompatible, Structured-based field classes
+ - compatible, Unstructured-based field classes
+
+ @brief
+ Defines the various headers classes.
+
+ @authors the KMime authors (see AUTHORS file),
+ Volker Krause \<vkrause@kde.org\>
+*/
+
+#ifndef __KMIME_HEADERS_H__
+#define __KMIME_HEADERS_H__
+
+#include "kmime_export.h"
+#include "kmime_header_parsing.h"
+
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtCore/QRegExp>
+#include <QtCore/QDateTime>
+#include <QtCore/QMap>
+#include <QtCore/QList>
+#include <QtCore/QByteArray>
+
+#include <kdatetime.h>
+
+namespace KMime {
+
+class Content;
+
+namespace Headers {
+
+class BasePrivate;
+
+enum contentCategory {
+ CCsingle,
+ CCcontainer,
+ CCmixedPart,
+ CCalternativePart
+};
+
+/**
+ Various possible values for the "Content-Transfer-Encoding" header.
+*/
+enum contentEncoding {
+ CE7Bit, ///< 7bit
+ CE8Bit, ///< 8bit
+ CEquPr, ///< quoted-printable
+ CEbase64, ///< base64
+ CEuuenc, ///< uuencode
+ CEbinary ///< binary
+};
+
+/**
+ Various possible values for the "Content-Disposition" header.
+*/
+enum contentDisposition {
+ CDInvalid, ///< Default, invalid value
+ CDinline, ///< inline
+ CDattachment, ///< attachment
+ CDparallel ///< parallel (invalid, do not use)
+};
+
+//often used charset
+// TODO: get rid of this!
+static const QByteArray Latin1( "ISO-8859-1" );
+
+//@cond PRIVATE
+// internal macro to generate default constructors
+#define kmime_mk_trivial_ctor( subclass ) \
+ public: \
+ explicit subclass( Content *parent = 0 ); \
+ subclass( Content *parent, const QByteArray &s ); \
+ subclass( Content *parent, const QString &s, const QByteArray &charset ); \
+ ~subclass();
+
+#define kmime_mk_dptr_ctor( subclass ) \
+ protected: \
+ explicit subclass( subclass##Private *d, KMime::Content *parent = 0 );
+
+#define kmime_mk_trivial_ctor_with_name( subclass ) \
+ kmime_mk_trivial_ctor( subclass ) \
+ const char *type() const; \
+ static const char *staticType();
+//@endcond
+
+//
+//
+// HEADER'S BASE CLASS. DEFINES THE COMMON INTERFACE
+//
+//
+
+/** Baseclass of all header-classes. It represents a
+ header-field as described in RFC-822. */
+class KMIME_EXPORT Base
+{
+ public:
+ /**
+ A list of headers.
+ */
+ typedef QList<KMime::Headers::Base*> List;
+
+ /**
+ Creates an empty header with a parent-content.
+ */
+ explicit Base( KMime::Content *parent = 0 );
+
+ /**
+ Destructor.
+ */
+ virtual ~Base();
+
+ /**
+ Returns the parent of this header.
+ */
+ KMime::Content *parent() const;
+
+ /**
+ Sets the parent for this header to @p parent.
+ */
+ void setParent( KMime::Content *parent );
+
+ /**
+ Parses the given string. Take care of RFC2047-encoded strings.
+ @param s The encoded header data.
+ */
+ virtual void from7BitString( const QByteArray &s ) = 0;
+
+ /**
+ Returns the encoded header.
+ @param withHeaderType Specifies whether the header-type should be included.
+ */
+ virtual QByteArray as7BitString( bool withHeaderType = true ) const = 0;
+
+ /**
+ Returns the charset that is used for RFC2047-encoding.
+ */
+ QByteArray rfc2047Charset() const;
+
+ /**
+ Sets the charset for RFC2047-encoding.
+ @param cs The new charset used for RFC2047 encoding.
+ */
+ void setRFC2047Charset( const QByteArray &cs );
+
+ /**
+ Returns the default charset.
+ */
+ QByteArray defaultCharset() const;
+
+ /**
+ Returns if the default charset is mandatory.
+ */
+ bool forceDefaultCharset() const;
+
+ /**
+ Parses the given string and set the charset.
+ @param s The header data as unicode string.
+ @param b The charset preferred for encoding.
+ */
+ virtual void fromUnicodeString( const QString &s, const QByteArray &b ) = 0;
+
+ /**
+ Returns the decoded content of the header without the header-type.
+
+ @note The return value of this method should only be used when showing an address
+ to the user. It is not guaranteed that fromUnicodeString( asUnicodeString(), ... )
+ will return the original string.
+ */
+ virtual QString asUnicodeString() const = 0;
+
+ /**
+ Deletes.
+ */
+ virtual void clear() = 0;
+
+ /**
+ Checks if this header contains any data.
+ */
+ virtual bool isEmpty() const = 0;
+
+ /**
+ Returns the type of this header (e.g. "From").
+ */
+ virtual const char *type() const;
+
+ /**
+ Checks if this header is of type @p t.
+ */
+ bool is( const char *t ) const;
+
+ /**
+ Checks if this header is a MIME header.
+ */
+ bool isMimeHeader() const;
+
+ /**
+ Checks if this header is a X-Header.
+ */
+ bool isXHeader() const;
+
+ protected:
+ /**
+ Helper method, returns the header prefix including ":".
+ */
+ QByteArray typeIntro() const;
+
+ //@cond PRIVATE
+ BasePrivate *d_ptr;
+ kmime_mk_dptr_ctor( Base )
+ //@endcond
+
+ private:
+ Q_DECLARE_PRIVATE(Base)
+ Q_DISABLE_COPY(Base)
+};
+
+//
+//
+// GENERIC BASE CLASSES FOR DIFFERENT TYPES OF FIELDS
+//
+//
+
+namespace Generics {
+
+class UnstructuredPrivate;
+
+/**
+ 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().
+*/
+
+// known issues:
+// - uses old decodeRFC2047String function, instead of our own...
+
+class KMIME_EXPORT Unstructured : public Base
+{
+ //@cond PRIVATE
+ kmime_mk_dptr_ctor( Unstructured )
+ //@endcond
+ public:
+ explicit Unstructured( Content *p = 0 );
+ Unstructured( Content *p, const QByteArray &s );
+ Unstructured( Content *p, const QString &s, const QByteArray &cs );
+ ~Unstructured();
+
+ virtual void from7BitString( const QByteArray &s );
+ virtual QByteArray as7BitString( bool withHeaderType=true ) const;
+
+ virtual void fromUnicodeString( const QString &s,
+ const QByteArray &b );
+ virtual QString asUnicodeString() const;
+
+ virtual void clear();
+
+ virtual bool isEmpty() const;
+
+ private:
+ Q_DECLARE_PRIVATE(Unstructured)
+};
+
+
+class StructuredPrivate;
+
+/**
+ @brief
+ Base class for structured header fields.
+
+ 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.
+
+ @author Marc Mutz <mutz@kde.org>
+*/
+
+class KMIME_EXPORT Structured : public Base
+{
+ public:
+ explicit Structured( Content *p = 0 );
+ Structured( Content *p, const QByteArray &s );
+ Structured( Content *p, const QString &s, const QByteArray &cs );
+ ~Structured();
+
+ virtual void from7BitString( const QByteArray &s );
+ virtual QString asUnicodeString() const;
+ virtual void fromUnicodeString( const QString &s, const QByteArray &b );
+
+ protected:
+ /**
+ This method parses the raw header and needs to be implemented in
+ every sub-class.
+
+ @param scursor Pointer to the start of the data still to parse.
+ @param send Pointer to the end of the data.
+ @param isCRLF true if input string is terminated with a CRLF.
+ */
+ virtual bool parse( const char* &scursor, const char *const send,
+ bool isCRLF = false ) = 0;
+
+ //@cond PRIVATE
+ kmime_mk_dptr_ctor( Structured )
+ //@endcond
+
+ private:
+ Q_DECLARE_PRIVATE(Structured)
+};
+
+
+class AddressPrivate;
+
+/**
+ Base class for all address related headers.
+*/
+class KMIME_EXPORT Address : public Structured
+{
+ public:
+ explicit Address( Content *p = 0 );
+ Address( Content *p, const QByteArray &s );
+ Address( Content *p, const QString &s, const QByteArray &cs );
+ ~Address();
+ protected:
+ //@cond PRIVATE
+ kmime_mk_dptr_ctor( Address )
+ //@endcond
+ private:
+ Q_DECLARE_PRIVATE(Address)
+};
+
+
+class MailboxListPrivate;
+
+/**
+ Base class for headers that deal with (possibly multiple)
+ addresses, but don't allow groups.
+
+ @see RFC 2822, section 3.4
+*/
+class KMIME_EXPORT MailboxList : public Address
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor( MailboxList )
+ kmime_mk_dptr_ctor( MailboxList )
+ //@endcond
+ public:
+ virtual QByteArray as7BitString( bool withHeaderType = true ) const;
+ virtual void fromUnicodeString( const QString &s, const QByteArray &b );
+ virtual QString asUnicodeString() const;
+
+ virtual void clear();
+ virtual bool isEmpty() const;
+
+ /**
+ Adds an address to this header.
+
+ @param mbox A Mailbox object specifying the address.
+ */
+ void addAddress( const Types::Mailbox &mbox );
+
+ /**
+ Adds an address to this header.
+ @param address The actual email address, with or without angle brackets.
+ @param displayName An optional name associated with the address.
+ */
+ void addAddress( const QByteArray &address,
+ const QString &displayName = QString() );
+
+ /**
+ Returns a list of all addresses in this header, regardless of groups.
+ */
+ QList<QByteArray> addresses() const;
+
+ /**
+ Returns a list of all display names associated with the addresses in
+ this header. An empty entry is added for addresses that do not have
+ a display name.
+ */
+ QStringList displayNames() const;
+
+ /**
+ Returns a list of assembled display name / address strings of the
+ following form: "Display Name &lt;address&gt;". These are unicode
+ strings without any transport encoding, ie. they are only suitable
+ for displaying.
+ */
+ QStringList prettyAddresses() const;
+
+ /**
+ Returns a list of mailboxes listed in this header.
+ */
+ Types::Mailbox::List mailboxes() const;
+
+ protected:
+ bool parse( const char* &scursor, const char *const send, bool isCRLF=false );
+
+ private:
+ Q_DECLARE_PRIVATE(MailboxList)
+};
+
+
+class SingleMailboxPrivate;
+
+/**
+ Base class for headers that deal with exactly one mailbox
+ (e.g. Sender).
+*/
+class KMIME_EXPORT SingleMailbox : public MailboxList
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor( SingleMailbox )
+ //@endcond
+ protected:
+ bool parse( const char* &scursor, const char *const send, bool isCRLF=false );
+ private:
+ Q_DECLARE_PRIVATE(SingleMailbox)
+};
+
+
+class AddressListPrivate;
+
+/**
+ Base class for headers that deal with (possibly multiple)
+ addresses, allowing groups.
+
+ Note: Groups are parsed but not represented in the API yet. All addresses in
+ groups are listed as if they would not be part of a group.
+
+ @todo Add API for groups?
+
+ @see RFC 2822, section 3.4
+*/
+class KMIME_EXPORT AddressList : public Address
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor( AddressList )
+ kmime_mk_dptr_ctor( AddressList )
+ //@endcond
+ public:
+ virtual QByteArray as7BitString( bool withHeaderType = true ) const;
+ virtual void fromUnicodeString( const QString &s, const QByteArray &b );
+ virtual QString asUnicodeString() const;
+
+ virtual void clear();
+ virtual bool isEmpty() const;
+
+ /**
+ Adds an address to this header.
+
+ @param mbox A Mailbox object specifying the address.
+ */
+ void addAddress( const Types::Mailbox &mbox );
+
+ /**
+ Adds an address to this header.
+ @param address The actual email address, with or without angle brackets.
+ @param displayName An optional name associated with the address.
+ */
+ void addAddress( const QByteArray &address, const QString &displayName = QString() );
+
+ /**
+ Returns a list of all addresses in this header, regardless of groups.
+ */
+ QList<QByteArray> addresses() const;
+
+ /**
+ Returns a list of all display names associated with the addresses in this header.
+ An empty entry is added for addresses that don't have a display name.
+ */
+ QStringList displayNames() const;
+
+ /**
+ Returns a list of assembled display name / address strings of the following form:
+ "Display Name &lt;address&gt;". These are unicode strings without any transport
+ encoding, ie. they are only suitable for displaying.
+ */
+ QStringList prettyAddresses() const;
+
+ /**
+ Returns a list of mailboxes listed in this header.
+ */
+ Types::Mailbox::List mailboxes() const;
+
+ protected:
+ bool parse( const char* &scursor, const char *const send, bool isCRLF=false );
+
+ private:
+ Q_DECLARE_PRIVATE(AddressList)
+};
+
+
+class IdentPrivate;
+
+/**
+ Base class for headers which deal with a list of msg-id's.
+
+ @see RFC 2822, section 3.6.4
+*/
+class KMIME_EXPORT Ident : public Address
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor( Ident )
+ kmime_mk_dptr_ctor( Ident )
+ //@endcond
+ public:
+ virtual QByteArray as7BitString( bool withHeaderType = true ) const;
+ virtual void clear();
+ virtual bool isEmpty() const;
+
+ /**
+ Returns the list of identifiers contained in this header.
+ Note:
+ - Identifiers are not enclosed in angle-brackets.
+ - Identifiers are listed in the same order as in the header.
+ */
+ QList<QByteArray> identifiers() const;
+
+ /**
+ Appends a new identifier to this header.
+ @param id The identifier to append, with or without angle-brackets.
+ */
+ void appendIdentifier( const QByteArray &id );
+
+ protected:
+ bool parse( const char* &scursor, const char *const send, bool isCRLF=false );
+
+ private:
+ Q_DECLARE_PRIVATE(Ident)
+};
+
+
+class SingleIdentPrivate;
+
+/**
+ Base class for headers which deal with a single msg-id.
+
+ @see RFC 2822, section 3.6.4
+*/
+class KMIME_EXPORT SingleIdent : public Ident
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor( SingleIdent )
+ kmime_mk_dptr_ctor( SingleIdent )
+ //@endcond
+ public:
+ /**
+ Returns the identifier contained in this header.
+ Note: The identifiers is not enclosed in angle-brackets.
+ */
+ QByteArray identifier() const;
+
+ /**
+ Sets the identifier.
+ @param id The new identifier with or without angle-brackets.
+ */
+ void setIdentifier( const QByteArray &id );
+
+ protected:
+ bool parse( const char* &scursor, const char *const send, bool isCRLF=false );
+
+ private:
+ Q_DECLARE_PRIVATE(SingleIdent)
+};
+
+
+class TokenPrivate;
+
+/**
+ Base class for headers which deal with a single atom.
+*/
+class KMIME_EXPORT Token : public Structured
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor( Token )
+ kmime_mk_dptr_ctor( Token )
+ //@endcond
+ public:
+ virtual QByteArray as7BitString( bool withHeaderType = true ) const;
+ virtual void clear();
+ virtual bool isEmpty() const;
+
+ /**
+ Returns the token.
+ */
+ QByteArray token() const;
+
+ /**
+ Sets the token to @p t,
+ */
+ void setToken( const QByteArray &t );
+
+ protected:
+ bool parse( const char* &scursor, const char *const send, bool isCRLF=false );
+
+ private:
+ Q_DECLARE_PRIVATE(Token)
+};
+
+
+class PhraseListPrivate;
+
+/**
+ Base class for headers containing a list of phrases.
+*/
+class KMIME_EXPORT PhraseList : public Structured
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor( PhraseList )
+ //@endcond
+ public:
+ virtual QByteArray as7BitString( bool withHeaderType = true ) const;
+ virtual QString asUnicodeString() const;
+ virtual void clear();
+ virtual bool isEmpty() const;
+
+ /**
+ Returns the list of phrases contained in this header.
+ */
+ QStringList phrases() const;
+
+ protected:
+ bool parse( const char* &scursor, const char *const send, bool isCRLF=false );
+
+ private:
+ Q_DECLARE_PRIVATE(PhraseList)
+};
+
+
+class DotAtomPrivate;
+
+/**
+ Base class for headers containing a dot atom.
+*/
+class KMIME_EXPORT DotAtom : public Structured
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor( DotAtom )
+ //@endcond
+ public:
+ virtual QByteArray as7BitString( bool withHeaderType = true ) const;
+ virtual QString asUnicodeString() const;
+ virtual void clear();
+ virtual bool isEmpty() const;
+
+ protected:
+ bool parse( const char* &scursor, const char *const send, bool isCRLF=false );
+
+ private:
+ Q_DECLARE_PRIVATE(DotAtom)
+};
+
+
+class ParametrizedPrivate;
+
+/**
+ Base class for headers containing a parameter list such as "Content-Type".
+*/
+class KMIME_EXPORT Parametrized : public Structured
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor( Parametrized )
+ kmime_mk_dptr_ctor( Parametrized )
+ //@endcond
+ public:
+ virtual QByteArray as7BitString( bool withHeaderType = true ) const;
+
+ virtual bool isEmpty() const;
+ virtual void clear();
+
+ //FIXME: Shouldn't the parameter keys be QByteArray and not QStrings? Only the values can be
+ // non-ascii!
+
+ /**
+ Returns the value of the specified parameter.
+ @param key The parameter name.
+ */
+ QString parameter( const QString &key ) const;
+
+ /**
+ @return true if a parameter with the given @p key exists.
+ @since 4.5
+ */
+ bool hasParameter( const QString &key ) const;
+
+ /**
+ Sets the parameter @p key to @p value.
+ @param key The parameter name.
+ @param value The new value for @p key.
+ */
+ void setParameter( const QString &key, const QString &value );
+
+ protected:
+ virtual bool parse( const char* &scursor, const char *const send, bool isCRLF=false );
+
+ private:
+ Q_DECLARE_PRIVATE(Parametrized)
+};
+
+} // namespace Generics
+
+//
+//
+// INCOMPATIBLE, GSTRUCTURED-BASED FIELDS:
+//
+//
+
+class ReturnPathPrivate;
+
+/**
+ Represents the Return-Path header field.
+
+ @see RFC 2822, section 3.6.7
+*/
+class KMIME_EXPORT ReturnPath : public Generics::Address
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor_with_name( ReturnPath )
+ //@endcond
+ public:
+ virtual QByteArray as7BitString( bool withHeaderType = true ) const;
+ virtual void clear();
+ virtual bool isEmpty() const;
+
+ protected:
+ bool parse( const char* &scursor, const char *const send, bool isCRLF=false );
+
+ private:
+ Q_DECLARE_PRIVATE(ReturnPath)
+};
+
+// Address et al.:
+
+// rfc(2)822 headers:
+/**
+ Represent a "From" header.
+
+ @see RFC 2822, section 3.6.2.
+*/
+class KMIME_EXPORT From : public Generics::MailboxList
+{
+ kmime_mk_trivial_ctor_with_name( From )
+};
+
+/**
+ Represents a "Sender" header.
+
+ @see RFC 2822, section 3.6.2.
+*/
+class KMIME_EXPORT Sender : public Generics::SingleMailbox
+{
+ kmime_mk_trivial_ctor_with_name( Sender )
+};
+
+/**
+ Represents a "To" header.
+
+ @see RFC 2822, section 3.6.3.
+*/
+class KMIME_EXPORT To : public Generics::AddressList
+{
+ kmime_mk_trivial_ctor_with_name( To )
+};
+
+/**
+ Represents a "Cc" header.
+
+ @see RFC 2822, section 3.6.3.
+*/
+class KMIME_EXPORT Cc : public Generics::AddressList
+{
+ kmime_mk_trivial_ctor_with_name( Cc )
+};
+
+/**
+ Represents a "Bcc" header.
+
+ @see RFC 2822, section 3.6.3.
+*/
+class KMIME_EXPORT Bcc : public Generics::AddressList
+{
+ kmime_mk_trivial_ctor_with_name( Bcc )
+};
+
+/**
+ Represents a "ReplyTo" header.
+
+ @see RFC 2822, section 3.6.2.
+*/
+class KMIME_EXPORT ReplyTo : public Generics::AddressList
+{
+ kmime_mk_trivial_ctor_with_name( ReplyTo )
+};
+
+
+class MailCopiesToPrivate;
+
+/**
+ Represents a "Mail-Copies-To" header.
+
+ @see http://www.newsreaders.com/misc/mail-copies-to.html
+*/
+class KMIME_EXPORT MailCopiesTo : public Generics::AddressList
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor_with_name( MailCopiesTo )
+ //@endcond
+ public:
+ virtual QByteArray as7BitString( bool withHeaderType = true ) const;
+ virtual QString asUnicodeString() const;
+
+ virtual void clear();
+ virtual bool isEmpty() const;
+
+ /**
+ Returns true if a mail copy was explicitly requested.
+ */
+ bool alwaysCopy() const;
+
+ /**
+ Sets the header to "poster".
+ */
+ void setAlwaysCopy();
+
+ /**
+ Returns true if a mail copy was explicitly denied.
+ */
+ bool neverCopy() const;
+
+ /**
+ Sets the header to "never".
+ */
+ void setNeverCopy();
+
+ protected:
+ virtual bool parse( const char* &scursor, const char *const send, bool isCRLF=false );
+
+ private:
+ Q_DECLARE_PRIVATE(MailCopiesTo)
+};
+
+
+class ContentTransferEncodingPrivate;
+
+/**
+ Represents a "Content-Transfer-Encoding" header.
+
+ @see RFC 2045, section 6.
+*/
+class KMIME_EXPORT ContentTransferEncoding : public Generics::Token
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor_with_name( ContentTransferEncoding )
+ //@endcond
+ public:
+ virtual void clear();
+
+ /**
+ Returns the encoding specified in this header.
+ */
+ contentEncoding encoding() const;
+
+ /**
+ Sets the encoding to @p e.
+ */
+ void setEncoding( contentEncoding e );
+
+ /**
+ Returns whether the Content containing this header is already decoded.
+ */
+ // KDE5: rename to isDecoded().
+ bool decoded() const;
+
+ /**
+ Set whether the Content containing this header is already decoded.
+ For instance, if you fill your Content with already-encoded base64 data,
+ you will want to setDecoded( false ).
+ */
+ void setDecoded( bool decoded = true );
+
+ /**
+ Returns whether the Content containing this header needs to be encoded
+ (i.e., if decoded() is true and encoding() is base64 or quoted-printable).
+ */
+ bool needToEncode() const;
+
+ protected:
+ virtual bool parse( const char* &scursor, const char *const send, bool isCRLF=false );
+
+ private:
+ Q_DECLARE_PRIVATE(ContentTransferEncoding)
+};
+
+/**
+ Represents a "Keywords" header.
+
+ @see RFC 2822, section 3.6.5.
+*/
+class KMIME_EXPORT Keywords : public Generics::PhraseList
+{
+ kmime_mk_trivial_ctor_with_name( Keywords )
+};
+
+// DotAtom:
+
+/**
+ Represents a "MIME-Version" header.
+
+ @see RFC 2045, section 4.
+*/
+class KMIME_EXPORT MIMEVersion : public Generics::DotAtom
+{
+ kmime_mk_trivial_ctor_with_name( MIMEVersion )
+};
+
+// Ident:
+
+/**
+ Represents a "Message-ID" header.
+
+ @see RFC 2822, section 3.6.4.
+*/
+class KMIME_EXPORT MessageID : public Generics::SingleIdent
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor_with_name( MessageID )
+ //@endcond
+ public:
+ /**
+ Generate a message identifer.
+ @param fqdn A fully qualified domain name.
+ */
+ void generate( const QByteArray &fqdn );
+};
+
+class ContentIDPrivate;
+
+/**
+ Represents a "Content-ID" header.
+*/
+class KMIME_EXPORT ContentID : public Generics::SingleIdent
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor_with_name( ContentID )
+ kmime_mk_dptr_ctor( ContentID )
+ //@endcond
+
+ protected:
+ bool parse( const char* &scursor, const char *const send, bool isCRLF=false );
+ private:
+ Q_DECLARE_PRIVATE(ContentID)
+};
+
+/**
+ Represents a "Supersedes" header.
+*/
+class KMIME_EXPORT Supersedes : public Generics::SingleIdent
+{
+ kmime_mk_trivial_ctor_with_name( Supersedes )
+};
+
+/**
+ Represents a "In-Reply-To" header.
+
+ @see RFC 2822, section 3.6.4.
+*/
+class KMIME_EXPORT InReplyTo : public Generics::Ident
+{
+ kmime_mk_trivial_ctor_with_name( InReplyTo )
+};
+
+/**
+ Represents a "References" header.
+
+ @see RFC 2822, section 3.6.4.
+*/
+class KMIME_EXPORT References : public Generics::Ident
+{
+ kmime_mk_trivial_ctor_with_name( References )
+};
+
+
+class ContentTypePrivate;
+
+/**
+ Represents a "Content-Type" header.
+
+ @see RFC 2045, section 5.
+*/
+class KMIME_EXPORT ContentType : public Generics::Parametrized
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor_with_name( ContentType )
+ //@endcond
+ public:
+ virtual QByteArray as7BitString( bool withHeaderType = true ) const;
+ virtual void clear();
+ virtual bool isEmpty() const;
+
+ /**
+ Returns the mimetype.
+ */
+ QByteArray mimeType() const;
+
+ /**
+ Returns the media type (first part of the mimetype).
+ */
+
+ QByteArray mediaType() const;
+
+ /**
+ Returns the mime sub-type (second part of the mimetype).
+ */
+ QByteArray subType() const;
+
+ /**
+ Sets the mimetype and clears already existing parameters.
+ @param mimeType The new mimetype.
+ */
+ void setMimeType( const QByteArray &mimeType );
+
+ /**
+ Tests if the media type equals @p mediatype.
+ */
+ bool isMediatype( const char *mediatype ) const;
+
+ /**
+ Tests if the mime sub-type equals @p subtype.
+ */
+ bool isSubtype( const char *subtype ) const;
+
+ /**
+ Returns true if the associated MIME entity is a text.
+ */
+ bool isText() const;
+
+ /**
+ Returns true if the associated MIME entity is a plain text.
+ */
+ bool isPlainText() const;
+
+ /**
+ Returns true if the associated MIME entity is a HTML file.
+ */
+ bool isHTMLText() const;
+
+ /**
+ Returns true if the associated MIME entity is an image.
+ */
+ bool isImage() const;
+
+ /**
+ Returns true if the associated MIME entity is a mulitpart container.
+ */
+ bool isMultipart() const;
+
+ /**
+ Returns true if the associated MIME entity contains partial data.
+ @see partialNumber(), partialCount()
+ */
+ bool isPartial() const;
+
+ /**
+ Returns the charset for the associated MIME entity.
+ */
+ QByteArray charset() const;
+
+ /**
+ Sets the charset.
+ */
+ void setCharset( const QByteArray &s );
+
+ /**
+ Returns the boundary (for mulitpart containers).
+ */
+ QByteArray boundary() const;
+
+ /**
+ Sets the mulitpart container boundary.
+ */
+ void setBoundary( const QByteArray &s );
+
+ /**
+ Returns the name of the associated MIME entity.
+ */
+ QString name() const;
+
+ /**
+ Sets the name to @p s using charset @p cs.
+ */
+ void setName( const QString &s, const QByteArray &cs );
+
+ /**
+ Returns the identifier of the associated MIME entity.
+ */
+ QByteArray id() const;
+
+ /**
+ Sets the identifier.
+ */
+ void setId( const QByteArray &s );
+
+ /**
+ Returns the position of this part in a multi-part set.
+ @see isPartial(), partialCount()
+ */
+ int partialNumber() const;
+
+ /**
+ Returns the total number of parts in a multi-part set.
+ @see isPartial(), partialNumber()
+ */
+ int partialCount() const;
+
+ /**
+ Sets parameters of a partial MIME entity.
+ @param total The total number of entities in the multi-part set.
+ @param number The number of this entity in a multi-part set.
+ */
+ void setPartialParams( int total, int number );
+
+ // TODO: document
+ contentCategory category() const;
+
+ void setCategory( contentCategory c );
+
+ protected:
+ bool parse( const char* &scursor, const char *const send, bool isCRLF=false );
+
+ private:
+ Q_DECLARE_PRIVATE(ContentType)
+};
+
+
+class ContentDispositionPrivate;
+
+/**
+ Represents a "Content-Disposition" header.
+
+ @see RFC 2183
+*/
+class KMIME_EXPORT ContentDisposition : public Generics::Parametrized
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor_with_name( ContentDisposition )
+ //@endcond
+ public:
+ virtual QByteArray as7BitString( bool withHeaderType = true ) const;
+ virtual bool isEmpty() const;
+ virtual void clear();
+
+ /**
+ Returns the content disposition.
+ */
+ contentDisposition disposition() const;
+
+ /**
+ Sets the content disposition.
+ @param disp The new content disposition.
+ */
+ void setDisposition( contentDisposition disp );
+
+ /**
+ Returns the suggested filename for the associated MIME part.
+ This is just a convenience function, it is equivalent to calling
+ parameter( "filename" );
+ */
+ QString filename() const;
+
+ /**
+ Sets the suggested filename for the associated MIME part.
+ This is just a convenience function, it is equivalent to calling
+ setParameter( "filename", filename );
+ @param filename The filename.
+ */
+ void setFilename( const QString &filename );
+
+ protected:
+ bool parse( const char* &scursor, const char *const send, bool isCRLF=false );
+
+ private:
+ Q_DECLARE_PRIVATE( ContentDisposition )
+};
+
+//
+//
+// COMPATIBLE GUNSTRUCTURED-BASED FIELDS:
+//
+//
+
+
+class GenericPrivate;
+
+/**
+ Represents an arbitrary header, that can contain any header-field.
+ Adds a type over Unstructured.
+ @see Unstructured
+*/
+class KMIME_EXPORT Generic : public Generics::Unstructured
+{
+ public:
+ Generic();
+ Generic( const char *t );
+ Generic( const char *t, Content *p );
+ Generic( const char *t, Content *p, const QByteArray &s );
+ Generic( const char *t, Content *p, const QString &s, const QByteArray &cs );
+ ~Generic();
+
+ virtual void clear();
+
+ virtual bool isEmpty() const;
+
+ virtual const char *type() const;
+
+ void setType( const char *type );
+
+ private:
+ Q_DECLARE_PRIVATE( Generic )
+};
+
+/**
+ Represents a "Subject" header.
+
+ @see RFC 2822, section 3.6.5.
+*/
+class KMIME_EXPORT Subject : public Generics::Unstructured
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor_with_name( Subject )
+ //@endcond
+ public:
+ bool isReply() const;
+};
+
+/**
+ Represents a "Organization" header.
+*/
+class KMIME_EXPORT Organization : public Generics::Unstructured
+{
+ kmime_mk_trivial_ctor_with_name( Organization )
+};
+
+/**
+ Represents a "Content-Description" header.
+*/
+class KMIME_EXPORT ContentDescription : public Generics::Unstructured
+{
+ kmime_mk_trivial_ctor_with_name( ContentDescription )
+};
+
+/**
+ Represents a "Content-Location" header.
+ @since 4.2
+*/
+class KMIME_EXPORT ContentLocation : public Generics::Unstructured
+{
+ kmime_mk_trivial_ctor_with_name( ContentLocation )
+};
+
+class ControlPrivate;
+
+/**
+ Represents a "Control" header.
+
+ @see RFC 1036, section 3.
+*/
+class KMIME_EXPORT Control : public Generics::Structured
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor_with_name( Control )
+ //@endcond
+ public:
+ virtual QByteArray as7BitString( bool withHeaderType = true ) const;
+ virtual void clear();
+ virtual bool isEmpty() const;
+
+ /**
+ Returns the control message type.
+ */
+ QByteArray controlType() const;
+
+ /**
+ Returns the control message parameter.
+ */
+ QByteArray parameter() const;
+
+ /**
+ Returns true if this is a cancel control message.
+ @see RFC 1036, section 3.1.
+ */
+ bool isCancel() const;
+
+ /**
+ Changes this header into a cancel control message for the given message-id.
+ @param msgid The message-id of the article that should be canceled.
+ */
+ void setCancel( const QByteArray &msgid );
+
+ protected:
+ bool parse( const char* &scursor, const char *const send, bool isCRLF = false );
+
+ private:
+ Q_DECLARE_PRIVATE(Control)
+};
+
+
+class DatePrivate;
+
+/**
+ Represents a "Date" header.
+
+ @see RFC 2822, section 3.3.
+*/
+class KMIME_EXPORT Date : public Generics::Structured
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor_with_name( Date )
+ //@endcond
+ public:
+ virtual QByteArray as7BitString( bool withHeaderType = true ) const;
+ virtual void clear();
+ virtual bool isEmpty() const;
+
+ /**
+ Returns the date contained in this header.
+ */
+ KDateTime dateTime() const;
+
+ /**
+ Sets the date.
+ */
+ void setDateTime( const KDateTime &dt );
+
+ /**
+ Returns the age of the message.
+ */
+ int ageInDays() const;
+
+ protected:
+ bool parse( const char* &scursor, const char *const send, bool isCRLF = false );
+
+ private:
+ Q_DECLARE_PRIVATE( Date )
+};
+
+
+class NewsgroupsPrivate;
+
+/**
+ Represents a "Newsgroups" header.
+
+ @see RFC 1036, section 2.1.3.
+*/
+class KMIME_EXPORT Newsgroups : public Generics::Structured
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor_with_name( Newsgroups )
+ //@endcond
+ public:
+ virtual QByteArray as7BitString( bool withHeaderType = true ) const;
+ virtual void fromUnicodeString( const QString &s, const QByteArray &b );
+ virtual QString asUnicodeString() const;
+ virtual void clear();
+ virtual bool isEmpty() const;
+
+ /**
+ Returns the list of newsgroups.
+ */
+ QList<QByteArray> groups() const;
+
+ /**
+ Sets the newsgroup list.
+ */
+ void setGroups( const QList<QByteArray> &groups );
+
+ /**
+ Returns true if this message has been cross-posted, i.e. if it has been
+ posted to multiple groups.
+ */
+ bool isCrossposted() const;
+
+ protected:
+ bool parse( const char* &scursor, const char *const send, bool isCRLF = false );
+
+ private:
+ Q_DECLARE_PRIVATE( Newsgroups )
+};
+
+/**
+ Represents a "Followup-To" header.
+
+ @see RFC 1036, section 2.2.3.
+*/
+class KMIME_EXPORT FollowUpTo : public Newsgroups
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor_with_name( FollowUpTo )
+ //@endcond
+};
+
+
+class LinesPrivate;
+
+/**
+ Represents a "Lines" header.
+
+ @see RFC 1036, section 2.2.12.
+*/
+class KMIME_EXPORT Lines : public Generics::Structured
+{
+ //@cond PRIVATE
+ kmime_mk_trivial_ctor_with_name( Lines )
+ //@endcond
+ public:
+ virtual QByteArray as7BitString( bool withHeaderType = true ) const;
+ virtual QString asUnicodeString() const;
+ virtual void clear();
+ virtual bool isEmpty() const;
+
+ /**
+ Returns the number of lines, undefined if isEmpty() returns true.
+ */
+ int numberOfLines() const;
+
+ /**
+ Sets the number of lines to @p lines.
+ */
+ void setNumberOfLines( int lines );
+
+ protected:
+ bool parse( const char* &scursor, const char *const send, bool isCRLF = false );
+
+ private:
+ Q_DECLARE_PRIVATE( Lines )
+};
+
+/**
+ Represents a "User-Agent" header.
+*/
+class KMIME_EXPORT UserAgent : public Generics::Unstructured
+{
+ kmime_mk_trivial_ctor_with_name( UserAgent )
+};
+
+/** Creates a header based on @param type. If @param type is a known header type,
+ * the right object type will be created, otherwise a null pointer is returned. */
+KMIME_EXPORT Base *createHeader( const QByteArray& type );
+
+} //namespace Headers
+
+} //namespace KMime
+
+// undefine code generation macros again
+#undef kmime_mk_trivial_ctor
+#undef kmime_mk_dptr_ctor
+#undef kmime_mk_trivial_ctor_with_name
+
+#endif // __KMIME_HEADERS_H__
diff --git a/kmime/kmime_headers_p.h b/kmime/kmime_headers_p.h
new file mode 100644
index 0000000..6b6749c
--- /dev/null
+++ b/kmime/kmime_headers_p.h
@@ -0,0 +1,182 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library 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 Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KMIME_HEADERS_P_H
+#define KMIME_HEADERS_P_H
+
+//@cond PRIVATE
+
+#define kmime_mk_empty_private( subclass, base ) \
+class subclass##Private : public base##Private {};
+
+namespace KMime {
+
+namespace Headers {
+
+class BasePrivate
+{
+ public:
+ BasePrivate() : parent( 0 ) {}
+
+ virtual ~BasePrivate() {}
+
+ KMime::Content *parent;
+ QByteArray encCS;
+};
+
+namespace Generics {
+
+class UnstructuredPrivate : public BasePrivate
+{
+ public:
+ QString decoded;
+};
+
+kmime_mk_empty_private( Structured, Base )
+kmime_mk_empty_private( Address, Structured )
+
+class MailboxListPrivate : public AddressPrivate
+{
+ public:
+ QList<Types::Mailbox> mailboxList;
+};
+
+kmime_mk_empty_private( SingleMailbox, MailboxList )
+
+class AddressListPrivate : public AddressPrivate
+{
+ public:
+ QList<Types::Address> addressList;
+};
+
+class IdentPrivate : public AddressPrivate
+{
+ public:
+ QList<Types::AddrSpec> msgIdList;
+ mutable QByteArray cachedIdentifier;
+};
+
+kmime_mk_empty_private( SingleIdent, Ident )
+
+class TokenPrivate : public StructuredPrivate
+{
+ public:
+ QByteArray token;
+};
+
+class PhraseListPrivate : public StructuredPrivate
+{
+ public:
+ QStringList phraseList;
+};
+
+class DotAtomPrivate : public StructuredPrivate
+{
+ public:
+ QString dotAtom;
+};
+
+class ParametrizedPrivate : public StructuredPrivate
+{
+ public:
+ QMap<QString,QString> parameterHash;
+};
+
+} // namespace Generics
+
+class ReturnPathPrivate : public Generics::AddressPrivate
+{
+ public:
+ Types::Mailbox mailbox;
+};
+
+class MailCopiesToPrivate : public Generics::AddressListPrivate
+{
+ public:
+ bool alwaysCopy;
+ bool neverCopy;
+};
+
+class ContentTransferEncodingPrivate : public Generics::TokenPrivate
+{
+ public:
+ contentEncoding cte;
+ bool decoded;
+};
+
+class ContentTypePrivate : public Generics::ParametrizedPrivate
+{
+ public:
+ QByteArray mimeType;
+ contentCategory category;
+};
+
+class ContentDispositionPrivate : public Generics::ParametrizedPrivate
+{
+ public:
+ contentDisposition disposition;
+};
+
+class GenericPrivate : public Generics::UnstructuredPrivate
+{
+ public:
+ GenericPrivate() : type( 0 ) {}
+ ~GenericPrivate()
+ {
+ delete[] type;
+ }
+
+ char *type;
+};
+
+class ControlPrivate : public Generics::StructuredPrivate
+{
+ public:
+ QByteArray name;
+ QByteArray parameter;
+};
+
+class DatePrivate : public Generics::StructuredPrivate
+{
+ public:
+ KDateTime dateTime;
+};
+
+class NewsgroupsPrivate : public Generics::StructuredPrivate
+{
+ public:
+ QList<QByteArray> groups;
+};
+
+class LinesPrivate : public Generics::StructuredPrivate
+{
+ public:
+ int lines;
+};
+
+kmime_mk_empty_private( ContentID, Generics::SingleIdent )
+}
+
+}
+
+#undef kmime_mk_empty_private
+
+//@endcond
+
+#endif
diff --git a/kmime/kmime_mdn.cpp b/kmime/kmime_mdn.cpp
new file mode 100644
index 0000000..3fc28f2
--- /dev/null
+++ b/kmime/kmime_mdn.cpp
@@ -0,0 +1,283 @@
+/* -*- c++ -*-
+ kmime_mdn.cpp
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ provides functions for supporting Message Disposition Notifications (MDNs),
+ also known as email return receipts.
+
+ @brief
+ Provides support for Message Disposition Notifications.
+
+ @authors Marc Mutz \<mutz@kde.org\>
+*/
+
+#include "kmime_mdn.h"
+#include "kmime_version.h"
+#include "kmime_util.h"
+
+#include <klocale.h>
+#include <kdebug.h>
+
+#include <QtCore/QByteArray>
+#include <QtCore/QList>
+
+#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 QByteArray dispositionField( DispositionType d, ActionMode a, SendingMode s,
+ const QList<DispositionModifier> & m ) {
+
+ // mandatory parts: Disposition: foo/baz; bar
+ QByteArray 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 ( QList<DispositionModifier>::const_iterator mt = m.begin() ;
+ mt != m.end() ; ++mt ) {
+ if ( first ) {
+ result += '/';
+ first = false;
+ } else {
+ result += ',';
+ }
+ result += stringFor( *mt );
+ }
+ return result + '\n';
+}
+
+static QByteArray finalRecipient( const QString &recipient )
+{
+ if ( recipient.isEmpty() ) {
+ return QByteArray();
+ } else {
+ return "Final-Recipient: rfc822; "
+ + encodeRFC2047String( recipient, "utf-8" ) + '\n';
+ }
+}
+
+static QByteArray orginalRecipient( const QByteArray & recipient )
+{
+ if ( recipient.isEmpty() ) {
+ return QByteArray();
+ } else {
+ return "Original-Recipient: " + recipient + '\n';
+ }
+}
+
+static QByteArray originalMessageID( const QByteArray &msgid )
+{
+ if ( msgid.isEmpty() ) {
+ return QByteArray();
+ } else {
+ return "Original-Message-ID: " + msgid + '\n';
+ }
+}
+
+static QByteArray 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 QByteArray("Reporting-UA: ") + QByteArray( hostName ) +
+ QByteArray( "; KMime " KMIME_VERSION_STRING "\n" );
+}
+
+QByteArray dispositionNotificationBodyContent( const QString &r,
+ const QByteArray &o,
+ const QByteArray &omid,
+ DispositionType d,
+ ActionMode a,
+ SendingMode s,
+ const QList<DispositionModifier> &m,
+ const QString &special )
+{
+ // in Perl: chomp(special)
+ QString spec;
+ if ( special.endsWith( QLatin1Char( '\n' ) ) ) {
+ spec = special.left( special.length() - 1 );
+ } else {
+ spec = special;
+ }
+
+ // std headers:
+ QByteArray 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 QList<DispositionModifier> & )
+{
+ for ( int i = 0 ; i < numDispositionTypes ; ++i ) {
+ if ( dispositionTypes[i].dispositionType == d ) {
+ return i18n( dispositionTypes[i].description );
+ }
+ }
+ kWarning() << "KMime::MDN::descriptionFor(): No such disposition type:"
+ << ( int )d;
+ return QString();
+}
+
+} // namespace MDN
+} // namespace KMime
diff --git a/kmime/kmime_mdn.h b/kmime/kmime_mdn.h
new file mode 100644
index 0000000..3f3026f
--- /dev/null
+++ b/kmime/kmime_mdn.h
@@ -0,0 +1,196 @@
+/* -*- c++ -*-
+ kmime_mdn.h
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling @ref MIME data and
+ provides functions for supporting Message Disposition Notifications (MDNs),
+ also known as email return receipts.
+
+ @brief
+ Provides support for Message Disposition Notifications.
+
+ @authors Marc Mutz \<mutz@kde.org\>
+
+ @glossary @anchor MDN @b MDN:
+ see @ref Message_Disposition_Notification
+
+ @glossary @anchor Message_Disposition_Notification
+ @b Message @b Disposition @b Notification:
+ Return receipts for email are called message disposition notifications.
+ Their format and usage is outlined in @ref RFC2298.
+
+ @glossary @anchor RFC2298 @anchor rfc2298 @b RFC @b 2298:
+ RFC that defines the <a href="http://tools.ietf.org/html/rfc2298">
+ An Extensible Message Format for Message Disposition Notifications</a>.
+*/
+
+#ifndef __KMIME_MDN_H__
+#define __KMIME_MDN_H__
+
+#include "kmime_export.h"
+#include <QtCore/QString>
+#include <QtCore/QList>
+
+class QByteArray;
+
+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
+};
+
+/**
+ Generates the content of the message/disposition-notification body part.
+*/
+KMIME_EXPORT extern QByteArray dispositionNotificationBodyContent(
+ const QString &finalRecipient,
+ const QByteArray &originalRecipient,
+ const QByteArray &originalMsgID,
+ DispositionType disposition,
+ ActionMode actionMode,
+ SendingMode sendingMode,
+ const QList<DispositionModifier> &dispositionModifers=QList<DispositionModifier>(),
+ const QString &special=QString() );
+
+KMIME_EXPORT extern QString descriptionFor(
+ DispositionType d,
+ const QList<DispositionModifier> &m=QList<DispositionModifier>() );
+
+} // namespace MDN
+
+} // namespace KMime
+
+#endif // __KMIME_MDN_H__
diff --git a/kmime/kmime_message.cpp b/kmime/kmime_message.cpp
new file mode 100644
index 0000000..ff8fac1
--- /dev/null
+++ b/kmime/kmime_message.cpp
@@ -0,0 +1,171 @@
+/*
+ kmime_message.cpp
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001 the KMime authors.
+ See file AUTHORS for details
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kmime_message.h"
+#include "kmime_message_p.h"
+#include "kmime_util_p.h"
+
+#include <KGlobal>
+
+using namespace KMime;
+
+namespace KMime {
+
+Message::Message()
+ : Content( new MessagePrivate( this ) )
+{
+}
+
+Message::Message(MessagePrivate * d)
+ : Content( d )
+{
+}
+
+Message::~Message()
+{
+}
+
+void Message::parse()
+{
+ // KDE5: remove this virtual reimplementation.
+ Content::parse();
+}
+
+QByteArray Message::assembleHeaders()
+{
+ // Create the mandatory fields (RFC5322) if they do not exist already.
+ date( true );
+ from( true );
+
+ // Make sure the mandatory MIME-Version field (RFC2045) is present and valid.
+ Headers::MIMEVersion *mimeVersion = header<Headers::MIMEVersion>( true );
+ mimeVersion->from7BitString( "1.0" );
+
+ // Assemble all header fields.
+ return Content::assembleHeaders();
+}
+
+void Message::clear()
+{
+ // KDE5: remove this virtual reimplementation.
+ Content::clear();
+}
+
+Headers::Base *Message::getHeaderByType( const char *type )
+{
+ // KDE5: remove this virtual reimplementation.
+ return headerByType( type );
+}
+
+Headers::Base *Message::headerByType( const char *type )
+{
+ // KDE5: remove this virtual reimplementation.
+ return Content::headerByType( type );
+}
+
+void Message::setHeader( Headers::Base *h )
+{
+ // KDE5: remove this virtual reimplementation.
+ Content::setHeader( h );
+}
+
+bool Message::removeHeader( const char *type )
+{
+ // KDE5: remove this virtual reimplementation.
+ return Content::removeHeader( type );
+}
+
+bool Message::isTopLevel() const
+{
+ return Content::isTopLevel();
+}
+
+Content *Message::mainBodyPart( const QByteArray &type )
+{
+ KMime::Content *c = this;
+ while ( c ) {
+ // not a multipart message
+ const KMime::Headers::ContentType * const contentType = c->contentType();
+ if ( !contentType->isMultipart() ) {
+ if ( contentType->mimeType() == type || type.isEmpty() ) {
+ return c;
+ }
+ return 0;
+ }
+
+ // empty multipart
+ if ( c->contents().count() == 0 ) {
+ return 0;
+ }
+
+ // multipart/alternative
+ if ( contentType->subType() == "alternative" ) {
+ if ( type.isEmpty() ) {
+ return c->contents().first();
+ }
+ foreach ( Content *c1, c->contents() ) {
+ if ( c1->contentType()->mimeType() == type ) {
+ return c1;
+ }
+ }
+ return 0;
+ }
+
+ c = c->contents().first();
+ }
+
+ return 0;
+}
+
+QString Message::mimeType()
+{
+ static const QString &message_rfc822 = KGlobal::staticQString( QLatin1String( "message/rfc822" ) );
+ return message_rfc822;
+}
+
+
+// @cond PRIVATE
+#define kmime_mk_header_accessor( type, method ) \
+Headers::type *Message::method( bool create ) { \
+ return header<Headers::type>( create ); \
+}
+
+kmime_mk_header_accessor( MessageID, messageID )
+kmime_mk_header_accessor( Subject, subject )
+kmime_mk_header_accessor( Date, date )
+kmime_mk_header_accessor( Organization, organization )
+kmime_mk_header_accessor( From, from )
+kmime_mk_header_accessor( ReplyTo, replyTo )
+kmime_mk_header_accessor( To, to )
+kmime_mk_header_accessor( Cc, cc )
+kmime_mk_header_accessor( Bcc, bcc )
+kmime_mk_header_accessor( References, references )
+kmime_mk_header_accessor( UserAgent, userAgent )
+kmime_mk_header_accessor( InReplyTo, inReplyTo )
+kmime_mk_header_accessor( Sender, sender )
+
+#undef kmime_mk_header_accessor
+// @endcond
+
+}
+
diff --git a/kmime/kmime_message.h b/kmime/kmime_message.h
new file mode 100644
index 0000000..3bbd137
--- /dev/null
+++ b/kmime/kmime_message.h
@@ -0,0 +1,238 @@
+/*
+ kmime_message.h
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001 the KMime authors.
+ See file AUTHORS for details
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef __KMIME_MESSAGE_H__
+#define __KMIME_MESSAGE_H__
+
+#include "kmime_export.h"
+#include "kmime_content.h"
+#include "kmime_headers.h"
+#include "boolflags.h"
+
+#include <QtCore/QMetaType>
+
+namespace boost {
+ template <typename T> class shared_ptr;
+}
+
+namespace KMime {
+
+class MessagePrivate;
+
+/**
+ * Represents a (email) message.
+ *
+ * Sample how to create a multipart message:
+ * \code
+ * // Set the multipart message.
+ * Message *m = new Message;
+ * Headers::ContentType *ct = m->contentType();
+ * ct->setMimeType( "multipart/mixed" );
+ * ct->setBoundary( multiPartBoundary() );
+ * ct->setCategory( Headers::CCcontainer );
+ * m->contentTransferEncoding()->clear();
+ *
+ * // Set the headers.
+ * m->from()->fromUnicodeString( "some@mailaddy.com", "utf-8" );
+ * m->to()->fromUnicodeString( "someother@mailaddy.com", "utf-8" );
+ * m->cc()->fromUnicodeString( "some@mailaddy.com", "utf-8" );
+ * m->date()->setDateTime( KDateTime::currentLocalDateTime() );
+ * m->subject()->fromUnicodeString( "My Subject", "utf-8" );
+ *
+ * // Set the first multipart, the body message.
+ * KMime::Content *b = new KMime::Content;
+ * b->contentType()->setMimeType( "text/plain" );
+ * b->setBody( "Some text..." );
+ *
+ * // Set the second multipart, the attachment.
+ * KMime::Content *a = new KMime::Content;
+ * KMime::Headers::ContentDisposition *d = new KMime::Headers::ContentDisposition( attachMessage );
+ * d->setFilename( "cal.ics" );
+ * d->setDisposition( KMime::Headers::CDattachment );
+ * a->contentType()->setMimeType( "text/plain" );
+ * a->setHeader( d );
+ * a->setBody( "Some text in the attachment..." );
+ *
+ * // Attach the both multiparts and assemble the message.
+ * m->addContent( b );
+ * m->addContent( a );
+ * m->assemble();
+ * \endcode
+ */
+class KMIME_EXPORT Message : public Content
+{
+ public:
+ /**
+ A list of messages.
+ */
+ typedef QList<KMime::Message*> List;
+
+ /**
+ A shared pointer to a message object.
+ */
+ typedef boost::shared_ptr<Message> Ptr;
+
+ /**
+ Creates an empty Message.
+ */
+ Message();
+
+ /**
+ Destroys this Message.
+ */
+ ~Message();
+
+ /* reimpl */
+ virtual void parse();
+
+ /* reimpl */
+ virtual void clear();
+
+ /* reimpl */
+ virtual KDE_DEPRECATED KMime::Headers::Base *getHeaderByType( const char *type );
+
+ /* reimpl */
+ virtual KMime::Headers::Base *headerByType( const char *type );
+
+ /* reimpl */
+ virtual void setHeader( KMime::Headers::Base *h );
+
+ /* reimpl */
+ virtual bool removeHeader( const char *type );
+
+ // KDE5: Why are these virtual?
+ /**
+ Returns the Message-ID header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::MessageID *messageID( bool create = true );
+
+ /**
+ Returns the Subject header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::Subject *subject( bool create = true );
+
+ /**
+ Returns the Date header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::Date *date( bool create = true );
+
+ /**
+ Returns the From header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::From *from( bool create = true );
+
+ /**
+ Returns the Organization header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::Organization *organization( bool create = true );
+
+ /**
+ Returns the Reply-To header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::ReplyTo *replyTo( bool create = true );
+
+ /**
+ Returns the To header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::To *to( bool create = true );
+
+ /**
+ Returns the Cc header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::Cc *cc( bool create = true );
+
+ /**
+ Returns the Bcc header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::Bcc *bcc( bool create = true );
+
+ /**
+ Returns the References header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::References *references( bool create = true );
+
+ /**
+ Returns the User-Agent header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::UserAgent *userAgent( bool create = true );
+
+ /**
+ Returns the In-Reply-To header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::InReplyTo *inReplyTo( bool create = true );
+
+ /**
+ Returns the Sender header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::Sender *sender( bool create = true );
+
+ /* reimpl */
+ virtual bool isTopLevel() const;
+
+ /**
+ Returns the first main body part of a given type, taking multipart/mixed
+ and multipart/alternative nodes into consideration.
+ Eg. \c bodyPart("text/html") will return a html content object if that is
+ provided in a multipart/alternative node, but not if it's the non-first
+ child node of a multipart/mixed node (ie. an attachment).
+ @param type The mimetype of the body part, if not given, the first
+ body part will be returned, regardless of it's type.
+ */
+ Content* mainBodyPart( const QByteArray &type = QByteArray() );
+
+ /**
+ Returns the MIME type used for Messages
+ */
+ static QString mimeType();
+
+ protected:
+ /* reimpl */
+ virtual QByteArray assembleHeaders();
+
+ // @cond PRIVATE
+ explicit Message( MessagePrivate *d );
+ // @endcond
+
+ private:
+ Q_DECLARE_PRIVATE( Message )
+
+}; // class Message
+
+} // namespace KMime
+
+#define KMIME_MESSAGE_METATYPE_DEFINED 1
+Q_DECLARE_METATYPE(KMime::Message*)
+
+#endif // __KMIME_MESSAGE_H__
diff --git a/kmime/kmime_message_p.h b/kmime/kmime_message_p.h
new file mode 100644
index 0000000..ad33d97
--- /dev/null
+++ b/kmime/kmime_message_p.h
@@ -0,0 +1,43 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library 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 Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KMIME_MESSAGE_P_H
+#define KMIME_MESSAGE_P_H
+
+#include "kmime_content_p.h"
+
+// @cond PRIVATE
+
+namespace KMime {
+
+class MessagePrivate : public ContentPrivate
+{
+ public:
+ MessagePrivate( Message *q ) : ContentPrivate( q )
+ {
+ }
+
+ Q_DECLARE_PUBLIC(Message)
+};
+
+}
+
+// @endcond
+
+#endif
diff --git a/kmime/kmime_newsarticle.cpp b/kmime/kmime_newsarticle.cpp
new file mode 100644
index 0000000..b2e72ec
--- /dev/null
+++ b/kmime/kmime_newsarticle.cpp
@@ -0,0 +1,112 @@
+/*
+ kmime_newsarticle.cpp
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001 the KMime authors.
+ See file AUTHORS for details
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kmime_newsarticle.h"
+#include "kmime_message_p.h"
+#include "kmime_util_p.h"
+
+using namespace KMime;
+
+namespace KMime {
+
+class NewsArticlePrivate : public MessagePrivate
+{
+ public:
+ NewsArticlePrivate( NewsArticle *q ) : MessagePrivate( q )
+ {
+ }
+
+ Q_DECLARE_PUBLIC(NewsArticle)
+};
+
+NewsArticle::NewsArticle()
+ : Message( new NewsArticlePrivate( this ) )
+{
+}
+
+NewsArticle::~NewsArticle()
+{
+}
+
+void NewsArticle::parse()
+{
+ // KDE5: remove this virtual reimplementation.
+ Message::parse();
+}
+
+QByteArray NewsArticle::assembleHeaders()
+{
+ // Create the mandatory Lines: field.
+ lines( true );
+
+ // Assemble all header fields.
+ return Message::assembleHeaders();
+}
+
+void NewsArticle::clear()
+{
+ // KDE5: remove this virtual reimplementation.
+ Message::clear();
+}
+
+Headers::Base * NewsArticle::getHeaderByType( const char *type )
+{
+ // KDE5: remove this virtual reimplementation.
+ return headerByType( type );
+}
+
+Headers::Base * NewsArticle::headerByType( const char *type )
+{
+ // KDE5: remove this virtual reimplementation.
+ return Message::headerByType( type );
+}
+
+void NewsArticle::setHeader( Headers::Base *h )
+{
+ // KDE5: remove this virtual reimplementation.
+ Message::setHeader( h );
+}
+
+bool NewsArticle::removeHeader( const char *type )
+{
+ // KDE5: remove this virtual reimplementation.
+ return Message::removeHeader( type );
+}
+
+// @cond PRIVATE
+#define kmime_mk_header_accessor( type, method ) \
+Headers::type* NewsArticle::method( bool create ) { \
+ return header<Headers::type>( create ); \
+}
+
+kmime_mk_header_accessor( Control, control )
+kmime_mk_header_accessor( Lines, lines )
+kmime_mk_header_accessor( Supersedes, supersedes )
+kmime_mk_header_accessor( MailCopiesTo, mailCopiesTo )
+kmime_mk_header_accessor( Newsgroups, newsgroups )
+kmime_mk_header_accessor( FollowUpTo, followUpTo )
+
+#undef kmime_mk_header_accessor
+// @endcond
+
+} // namespace KMime
diff --git a/kmime/kmime_newsarticle.h b/kmime/kmime_newsarticle.h
new file mode 100644
index 0000000..d951440
--- /dev/null
+++ b/kmime/kmime_newsarticle.h
@@ -0,0 +1,125 @@
+/*
+ kmime_newsarticle.h
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001 the KMime authors.
+ See file AUTHORS for details
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef __KMIME_NEWSARTICLE_H__
+#define __KMIME_NEWSARTICLE_H__
+
+#include "kmime_export.h"
+#include "kmime_message.h"
+#include <kpimutils/supertrait.h>
+
+namespace KMime {
+
+class NewsArticlePrivate;
+
+class KMIME_EXPORT NewsArticle : public Message
+{
+ public:
+ /**
+ A shared pointer to a news article.
+ */
+ typedef boost::shared_ptr<NewsArticle> Ptr;
+
+ /**
+ Creates a NewsArticle object.
+ */
+ NewsArticle();
+
+ /**
+ Destroys this NewsArticle.
+ */
+ ~NewsArticle();
+
+ /* reimpl */
+ virtual void parse();
+
+ /* reimpl */
+ virtual void clear();
+
+ /* reimpl */
+ virtual KDE_DEPRECATED KMime::Headers::Base * getHeaderByType( const char *type );
+
+ /* reimpl */
+ virtual KMime::Headers::Base * headerByType( const char *type );
+
+ /* reimpl */
+ virtual void setHeader( KMime::Headers::Base *h );
+
+ /* reimpl */
+ virtual bool removeHeader( const char *type );
+
+ /**
+ Returns the Control header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::Control *control( bool create = true );
+
+ /**
+ Returns the Supersedes header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::Supersedes *supersedes( bool create = true );
+
+ /**
+ Returns the Mail-Copies-To header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::MailCopiesTo *mailCopiesTo( bool create = true );
+
+ /**
+ Returns the Newsgroups header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::Newsgroups *newsgroups( bool create = true );
+
+ /**
+ Returns the Follow-Up-To header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::FollowUpTo *followUpTo( bool create = true );
+
+ /**
+ Returns the Lines header.
+ @param create If true, create the header if it doesn't exist yet.
+ */
+ virtual KMime::Headers::Lines *lines( bool create = true );
+
+ protected:
+ /* reimpl */
+ virtual QByteArray assembleHeaders();
+
+ private:
+ Q_DECLARE_PRIVATE( NewsArticle )
+
+}; // class NewsArticle
+
+} // namespace KMime
+
+
+//@cond PRIVATE
+// super class trait specialization
+namespace KPIMUtils {
+ template <> struct SuperClass<KMime::NewsArticle> : public SuperClassTrait<KMime::Message>{};
+}
+//@endcond
+
+#endif // __KMIME_NEWSARTICLE_H__
diff --git a/kmime/kmime_parsers.cpp b/kmime/kmime_parsers.cpp
new file mode 100644
index 0000000..85768ce
--- /dev/null
+++ b/kmime/kmime_parsers.cpp
@@ -0,0 +1,506 @@
+/*
+ kmime_parsers.cpp
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001 the KMime authors.
+ See file AUTHORS for details
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#include "kmime_parsers.h"
+
+#include <QtCore/QRegExp>
+#include <QtCore/QByteArray>
+
+using namespace KMime::Parser;
+
+namespace KMime {
+namespace Parser {
+
+MultiPart::MultiPart( const QByteArray &src, const QByteArray &boundary )
+{
+ s_rc=src;
+ b_oundary=boundary;
+}
+
+bool MultiPart::parse()
+{
+ QByteArray 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.indexOf( 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 - 1 );
+ }
+ }
+
+ while ( pos1 > -1 && pos2 > -1 ) {
+
+ //skip the rest of the line for the first boundary - the message-part starts here
+ if ( ( pos1 = s_rc.indexOf( '\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.indexOf( 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 character 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.indexOf( '\n', pos1 ) ) > -1 ) { //skip the rest of this line
+ //everything after the end-boundary is considered as the epilouge
+ e_pilouge = s_rc.mid( pos1 + 1, s_rc.length() - pos1 - 1 );
+ }
+ pos1 = -1;
+ pos2 = -1; //break
+ } else {
+ pos1 = pos2; //the search continues ...
+ }
+ }
+ }
+ }
+
+ return !p_arts.isEmpty();
+}
+
+//=============================================================================
+
+NonMimeParser::NonMimeParser( const QByteArray &src ) :
+ s_rc( src ), p_artNr( -1 ), t_otalNr( -1 )
+{
+}
+
+/**
+ * try to guess the mimetype from the file-extension
+ */
+QByteArray NonMimeParser::guessMimeType( const QByteArray &fileName )
+{
+ QByteArray tmp, mimeType;
+ int pos;
+
+ if ( !fileName.isEmpty() ) {
+ pos = fileName.lastIndexOf( '.' );
+ if ( pos++ != -1 ) {
+ tmp = fileName.mid( pos, fileName.length() - pos).toUpper();
+ 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-xpixmap";
+ } else if ( tmp == "XBM") {
+ mimeType = "image/x-xbitmap";
+ } else if ( tmp == "BMP") {
+ mimeType = "image/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 QByteArray &src, const QByteArray &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;
+ QByteArray tmp, fileName;
+
+ if ( ( beginPos = QString::fromLatin1( s_rc ).indexOf( QRegExp( QLatin1String( "begin [0-9][0-9][0-9]" ) ),
+ currentPos ) ) > -1 &&
+ ( beginPos == 0 || s_rc.at( beginPos - 1 ) == '\n') ) {
+ containsBegin = true;
+ uuStart = s_rc.indexOf( '\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.
+ indexOf( "\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 split-articles
+ success = false;
+ break; //too many "non-M-Lines" found, we give up
+ }
+
+ if ( ( !containsBegin || !containsEnd ) && !s_ubject.isNull() ) {
+ // message may be split up => parse subject
+ QRegExp rx( QLatin1String( "[0-9]+/[0-9]+") );
+ pos = rx.indexIn( QLatin1String( s_ubject ), 0 );
+ len = rx.matchedLength();
+ if ( pos != -1 ) {
+ tmp = s_ubject.mid( pos, len );
+ pos = tmp.indexOf( '/' );
+ 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 ) {
+ //everything between "begin ### " and the next LF is considered as the filename
+ fileName = s_rc.mid( beginPos + 10, uuStart - beginPos - 11 );
+ } else {
+ fileName = "";
+ }
+ f_ilenames.append( fileName );
+ //everything beetween "begin" and "end" is uuencoded
+ b_ins.append( s_rc.mid( uuStart, endPos - uuStart + 1 ) );
+ m_imeTypes.append( guessMimeType( fileName ) );
+ firstIteration = false;
+
+ int next = s_rc.indexOf( '\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 QByteArray &src ) :
+ NonMimeParser( src )
+{
+}
+
+bool YENCEncoded::yencMeta( QByteArray &src, const QByteArray &name, int *value )
+{
+ bool found = false;
+ QByteArray sought=name + '=';
+
+ int iPos = src.indexOf( sought );
+ if ( iPos > -1 ) {
+ int pos1 = src.indexOf( ' ', iPos );
+ int pos2 = src.indexOf( '\r', iPos );
+ int pos3 = src.indexOf( '\t', iPos );
+ int pos4 = src.indexOf( '\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.lastIndexOf( '=', 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;
+ QByteArray fileName, mimeType;
+
+ if ( ( beginPos = s_rc.
+ indexOf( "=ybegin ", currentPos ) ) > -1 &&
+ ( beginPos == 0 || s_rc.at( beginPos - 1 ) == '\n' ) ) {
+ yencStart = s_rc.indexOf( '\n', beginPos );
+ if ( yencStart == -1 ) { // no more line breaks found, give up
+ success = false;
+ break;
+ } else {
+ yencStart++;
+ if ( s_rc.indexOf( "=ypart", yencStart ) == yencStart ) {
+ containsPart = true;
+ yencStart = s_rc.indexOf( '\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
+ QByteArray meta = s_rc.mid( beginPos, yencStart - beginPos );
+ int namePos = meta.indexOf( "name=" );
+ if ( namePos == -1 ) {
+ success = false;
+ break;
+ }
+ int eolPos = meta.indexOf( '\r', namePos );
+ if ( eolPos == -1 ) {
+ eolPos = meta.indexOf( '\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;
+ binary.resize( 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[totalSize++] = ch;
+ lineLength++;
+ } else {
+ break;
+ }
+ } else {
+ ch -= 42;
+ if ( ch < 0 ) {
+ ch += 256;
+ }
+ if ( totalSize >= yencSize ) {
+ break;
+ }
+ binary[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.indexOf( '\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/kmime/kmime_parsers.h b/kmime/kmime_parsers.h
new file mode 100644
index 0000000..4112410
--- /dev/null
+++ b/kmime/kmime_parsers.h
@@ -0,0 +1,130 @@
+/*
+ kmime_parsers.h
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001 the KMime authors.
+ See file AUTHORS for details
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef __KMIME_PARSERS__
+#define __KMIME_PARSERS__
+
+#include<QByteArray>
+#include<QList>
+
+namespace KMime {
+
+namespace Parser {
+
+/** Helper-class: splits a multipart-message into single
+ parts as described in RFC 2046
+ @internal
+*/
+class MultiPart
+{
+ public:
+ MultiPart( const QByteArray &src, const QByteArray &boundary );
+ ~MultiPart() {}
+
+ bool parse();
+ QList<QByteArray> parts()
+ { return p_arts; }
+ QByteArray preamble()
+ { return p_reamble; }
+ QByteArray epilouge()
+ { return e_pilouge; }
+
+ protected:
+ QByteArray s_rc, b_oundary, p_reamble, e_pilouge;
+ QList<QByteArray> p_arts;
+};
+
+/** Helper-class: abstract base class of all parsers for
+ non-mime binary data (uuencoded, yenc)
+ @internal
+*/
+class NonMimeParser
+{
+ public:
+ NonMimeParser( const QByteArray &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 ); }
+ QByteArray textPart()
+ { return t_ext; }
+ QList<QByteArray> binaryParts()
+ { return b_ins; }
+ QList<QByteArray> filenames()
+ { return f_ilenames; }
+ QList<QByteArray> mimeTypes()
+ { return m_imeTypes; }
+
+ protected:
+ static QByteArray guessMimeType( const QByteArray &fileName );
+
+ QByteArray s_rc, t_ext;
+ QList<QByteArray> 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 QByteArray &src, const QByteArray &subject );
+
+ virtual bool parse();
+
+ protected:
+ QByteArray s_ubject;
+};
+
+/** Helper-class: tries to extract the data from a possibly
+ yenc encoded message
+ @internal
+*/
+class YENCEncoded : public NonMimeParser
+{
+ public:
+ YENCEncoded( const QByteArray &src );
+
+ virtual bool parse();
+ QList<QByteArray> binaryParts()
+ { return b_ins; }
+
+ protected:
+ QList<QByteArray> b_ins;
+ static bool yencMeta( QByteArray &src, const QByteArray &name, int *value );
+};
+
+} // namespace Parser
+
+} // namespace KMime
+
+#endif // __KMIME_PARSERS__
diff --git a/kmime/kmime_util.cpp b/kmime/kmime_util.cpp
new file mode 100644
index 0000000..1a68c17
--- /dev/null
+++ b/kmime/kmime_util.cpp
@@ -0,0 +1,1009 @@
+/*
+ kmime_util.cpp
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001 the KMime authors.
+ See file AUTHORS for details
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kmime_util.h"
+#include "kmime_util_p.h"
+
+#include "kmime_charfreq.h"
+#include "kmime_codecs.h"
+#include "kmime_header_parsing.h"
+#include "kmime_message.h"
+#include "kmime_warning.h"
+
+#include <config-kmime.h>
+#include <kdefakes.h> // for strcasestr
+#include <kglobal.h>
+#include <klocale.h>
+#include <kcharsets.h>
+#include <kcodecs.h>
+#include <kdebug.h>
+
+#include <QtCore/QList>
+#include <QtCore/QString>
+#include <QtCore/QTextCodec>
+
+#include <ctype.h>
+#include <time.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <boost/concept_check.hpp>
+
+using namespace KMime;
+
+namespace KMime {
+
+QList<QByteArray> c_harsetCache;
+QList<QByteArray> l_anguageCache;
+QString f_allbackCharEnc;
+bool u_seOutlookEncoding = false;
+
+QByteArray cachedCharset( const QByteArray &name )
+{
+ foreach ( const QByteArray& charset, c_harsetCache ) {
+ if ( qstricmp( name.data(), charset.data() ) == 0 ) {
+ return charset;
+ }
+ }
+
+ c_harsetCache.append( name.toUpper() );
+ //kDebug() << "KNMimeBase::cachedCharset() number of cs" << c_harsetCache.count();
+ return c_harsetCache.last();
+}
+
+QByteArray cachedLanguage( const QByteArray &name )
+{
+ foreach ( const QByteArray& language, l_anguageCache ) {
+ if ( qstricmp( name.data(), language.data() ) == 0 ) {
+ return language;
+ }
+ }
+
+ l_anguageCache.append( name.toUpper() );
+ //kDebug() << "KNMimeBase::cachedCharset() number of cs" << c_harsetCache.count();
+ return l_anguageCache.last();
+}
+
+bool isUsAscii( const QString &s )
+{
+ uint sLength = s.length();
+ for ( uint i=0; i<sLength; i++ ) {
+ if ( s.at( i ).toLatin1() <= 0 ) { // c==0: non-latin1, c<0: non-us-ascii
+ return false;
+ }
+ }
+ return true;
+}
+
+QString nameForEncoding( Headers::contentEncoding enc )
+{
+ switch( enc ) {
+ case Headers::CE7Bit: return QString::fromLatin1( "7bit" );
+ case Headers::CE8Bit: return QString::fromLatin1( "8bit" );
+ case Headers::CEquPr: return QString::fromLatin1( "quoted-printable" );
+ case Headers::CEbase64: return QString::fromLatin1( "base64" );
+ case Headers::CEuuenc: return QString::fromLatin1( "uuencode" );
+ case Headers::CEbinary: return QString::fromLatin1( "binary" );
+ default: return QString::fromLatin1( "unknown" );
+ }
+}
+
+QList<Headers::contentEncoding> encodingsForData( const QByteArray &data )
+{
+ QList<Headers::contentEncoding> allowed;
+ CharFreq cf( data );
+
+ switch ( cf.type() ) {
+ case CharFreq::SevenBitText:
+ allowed << Headers::CE7Bit;
+ case CharFreq::EightBitText:
+ allowed << Headers::CE8Bit;
+ case CharFreq::SevenBitData:
+ if ( cf.printableRatio() > 5.0/6.0 ) {
+ // let n the length of data and p the number of printable chars.
+ // Then base64 \approx 4n/3; qp \approx p + 3(n-p)
+ // => qp < base64 iff p > 5n/6.
+ allowed << Headers::CEquPr;
+ allowed << Headers::CEbase64;
+ } else {
+ allowed << Headers::CEbase64;
+ allowed << Headers::CEquPr;
+ }
+ break;
+ case CharFreq::EightBitData:
+ allowed << Headers::CEbase64;
+ break;
+ case CharFreq::None:
+ default:
+ Q_ASSERT( false );
+ }
+
+ return allowed;
+}
+
+// "(),.:;<>@[\]
+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
+};
+
+void setFallbackCharEncoding(const QString& fallbackCharEnc)
+{
+ f_allbackCharEnc = fallbackCharEnc;
+}
+
+QString fallbackCharEncoding()
+{
+ return f_allbackCharEnc;
+}
+
+void setUseOutlookAttachmentEncoding( bool violateStandard )
+{
+ u_seOutlookEncoding = violateStandard;
+}
+
+bool useOutlookAttachmentEncoding()
+{
+ return u_seOutlookEncoding;
+}
+
+
+QString decodeRFC2047String( const QByteArray &src, QByteArray &usedCS,
+ const QByteArray &defaultCS, bool forceCS )
+{
+ QByteArray result;
+ QByteArray spaceBuffer;
+ const char *scursor = src.constData();
+ const char *send = scursor + src.length();
+ bool onlySpacesSinceLastWord = false;
+
+ while ( scursor != send ) {
+ // space
+ if ( isspace( *scursor ) && onlySpacesSinceLastWord ) {
+ spaceBuffer += *scursor++;
+ continue;
+ }
+
+ // possible start of an encoded word
+ if ( *scursor == '=' ) {
+ QByteArray language;
+ QString decoded;
+ ++scursor;
+ const char *start = scursor;
+ if ( HeaderParsing::parseEncodedWord( scursor, send, decoded, language, usedCS, defaultCS, forceCS ) ) {
+ result += decoded.toUtf8();
+ onlySpacesSinceLastWord = true;
+ spaceBuffer.clear();
+ } else {
+ if ( onlySpacesSinceLastWord ) {
+ result += spaceBuffer;
+ onlySpacesSinceLastWord = false;
+ }
+ result += '=';
+ scursor = start; // reset cursor after parsing failure
+ }
+ continue;
+ } else {
+ // unencoded data
+ if ( onlySpacesSinceLastWord ) {
+ result += spaceBuffer;
+ onlySpacesSinceLastWord = false;
+ }
+ result += *scursor;
+ ++scursor;
+ }
+ }
+ // If there are any chars that couldn't be decoded in UTF-8,
+ // use the fallback charset if it exists
+ const QString tryUtf8 = QString::fromUtf8( result );
+ if ( tryUtf8.contains( 0xFFFD ) && !f_allbackCharEnc.isEmpty() ) {
+ QTextCodec* codec = KCharsets::charsets()->codecForName( f_allbackCharEnc );
+ return codec->toUnicode( result );
+ } else {
+ return tryUtf8;
+ }
+}
+
+QString decodeRFC2047String( const QByteArray &src )
+{
+ QByteArray usedCS;
+ return decodeRFC2047String( src, usedCS, "utf-8", false );
+}
+
+static const char *reservedCharacters = "\"()<>@,.;:\\[]=";
+
+QByteArray encodeRFC2047String( const QString &src, const QByteArray &charset,
+ bool addressHeader, bool allow8BitHeaders )
+{
+ QByteArray result;
+ int start=0, end=0;
+ bool nonAscii=false, ok=true, useQEncoding=false;
+
+ // fromLatin1() is safe here, codecForName() uses toLatin1() internally
+ const QTextCodec *codec = KCharsets::charsets()->codecForName( QString::fromLatin1( charset ), ok );
+
+ QByteArray usedCS;
+ if ( !ok ) {
+ //no codec available => try local8Bit and hope the best ;-)
+ usedCS = KGlobal::locale()->encoding();
+ codec = KCharsets::charsets()->codecForName( QString::fromLatin1( usedCS ), ok );
+ }
+ else {
+ Q_ASSERT( codec );
+ if ( charset.isEmpty() )
+ usedCS = codec->name();
+ else
+ usedCS = charset;
+ }
+
+ QTextCodec::ConverterState converterState( QTextCodec::IgnoreHeader );
+ QByteArray encoded8Bit = codec->fromUnicode( src.constData(), src.length(), &converterState );
+ if ( converterState.invalidChars > 0 ) {
+ usedCS = "utf-8";
+ codec = QTextCodec::codecForName( usedCS );
+ encoded8Bit = codec->fromUnicode( src );
+ }
+
+ if ( usedCS.contains( "8859-" ) ) { // use "B"-Encoding for non iso-8859-x charsets
+ useQEncoding = true;
+ }
+
+ 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 ( int x=end; x<encoded8Bit.length(); x++ ) {
+ if ( ( (signed char)encoded8Bit[x]<0) || ( encoded8Bit[x] == '\033' ) ||
+ ( addressHeader && ( strchr(reservedCharacters, encoded8Bit[x]) != 0 ) ) ) {
+ end = x; // 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;// "Q"-encoding implementation described in RFC 2047
+ for ( 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, encode *all* special chars 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?" + encoded8Bit.mid( start, end - start ).toBase64();
+ }
+
+ result +="?=";
+ result += encoded8Bit.right( encoded8Bit.length() - end );
+ } else {
+ result = encoded8Bit;
+ }
+
+ return result;
+}
+
+QByteArray encodeRFC2047Sentence(const QString& src, const QByteArray& charset )
+{
+ QByteArray result;
+ QList<QChar> splitChars;
+ splitChars << QLatin1Char(',') << QLatin1Char('\"') << QLatin1Char(';') << QLatin1Char('\\');
+ const QChar *ch = src.constData();
+ const int length = src.length();
+ int pos = 0;
+ int wordStart = 0;
+
+ //qDebug() << "Input:" << src;
+ // Loop over all characters of the string.
+ // When encountering a split character, RFC-2047-encode the word before it, and add it to the result.
+ while (pos < length) {
+ //qDebug() << "Pos:" << pos << "Result:" << result << "Char:" << ch->toAscii();
+ const bool isAscii = ch->unicode() < 127;
+ const bool isReserved = (strchr( reservedCharacters, ch->toAscii() ) != 0);
+ if ( isAscii && isReserved ) {
+ const int wordSize = pos - wordStart;
+ if (wordSize > 0) {
+ const QString word = src.mid( wordStart, wordSize );
+ result += encodeRFC2047String( word, charset );
+ }
+
+ result += ch->toAscii();
+ wordStart = pos + 1;
+ }
+ ch++;
+ pos++;
+ }
+
+ // Encode the last word
+ const int wordSize = pos - wordStart;
+ if (wordSize > 0) {
+ const QString word = src.mid( wordStart, pos - wordStart );
+ result += encodeRFC2047String( word, charset );
+ }
+
+ return result;
+}
+
+
+
+//-----------------------------------------------------------------------------
+QByteArray encodeRFC2231String( const QString& str, const QByteArray& charset )
+{
+ if ( str.isEmpty() )
+ return QByteArray();
+
+
+ const QTextCodec *codec = KCharsets::charsets()->codecForName( QString::fromLatin1( charset ) );
+ QByteArray latin;
+ if ( charset == "us-ascii" )
+ latin = str.toAscii();
+ else if ( codec )
+ latin = codec->fromUnicode( str );
+ else
+ latin = str.toLocal8Bit();
+
+ char *l;
+ for ( l = latin.data(); *l; ++l ) {
+ if ( ( ( *l & 0xE0 ) == 0 ) || ( *l & 0x80 ) )
+ // *l is control character or 8-bit char
+ break;
+ }
+ if ( !*l )
+ return latin;
+
+ QByteArray result = charset + "''";
+ for ( l = latin.data(); *l; ++l ) {
+ bool needsQuoting = ( *l & 0x80 ) || ( *l == '%' );
+ if( !needsQuoting ) {
+ const QByteArray especials = "()<>@,;:\"/[]?.= \033";
+ int len = especials.length();
+ for ( int i = 0; i < len; i++ )
+ if ( *l == especials[i] ) {
+ needsQuoting = true;
+ break;
+ }
+ }
+ if ( needsQuoting ) {
+ result += '%';
+ unsigned char hexcode;
+ hexcode = ( ( *l & 0xF0 ) >> 4 ) + 48;
+ if ( hexcode >= 58 )
+ hexcode += 7;
+ result += hexcode;
+ hexcode = ( *l & 0x0F ) + 48;
+ if ( hexcode >= 58 )
+ hexcode += 7;
+ result += hexcode;
+ } else {
+ result += *l;
+ }
+ }
+ return result;
+}
+
+
+//-----------------------------------------------------------------------------
+QString decodeRFC2231String( const QByteArray &str, QByteArray &usedCS, const QByteArray &defaultCS,
+ bool forceCS )
+{
+ int p = str.indexOf('\'');
+ if (p < 0) return KCharsets::charsets()->codecForName( QString::fromLatin1( defaultCS ))->toUnicode( str );
+
+
+ QByteArray charset = str.left(p);
+
+ QByteArray st = str.mid( str.lastIndexOf('\'') + 1 );
+
+ char ch, ch2;
+ p = 0;
+ while (p < (int)st.length())
+ {
+ if (st.at(p) == 37)
+ {
+ // Only try to decode the percent-encoded character if the percent sign
+ // is really followed by two other characters, see testcase at bug 163024
+ if ( p + 2 < st.length() ) {
+ ch = st.at(p+1) - 48;
+ if (ch > 16)
+ ch -= 7;
+ ch2 = st.at(p+2) - 48;
+ if (ch2 > 16)
+ ch2 -= 7;
+ st[p] = ch * 16 + ch2;
+ st.remove( p+1, 2 );
+ }
+ }
+ p++;
+ }
+ kDebug() << "Got pre-decoded:" << st;
+ QString result;
+ const QTextCodec * charsetcodec = KCharsets::charsets()->codecForName( QString::fromLatin1( charset ) );
+ if ( !charsetcodec || forceCS )
+ charsetcodec = KCharsets::charsets()->codecForName( QString::fromLatin1( defaultCS ) );
+
+ usedCS = charsetcodec->name();
+ return charsetcodec->toUnicode( st );
+}
+
+QString decodeRFC2231String( const QByteArray &src )
+{
+ QByteArray usedCS;
+ return decodeRFC2231String( src, usedCS, "utf-8", false );
+}
+
+QByteArray uniqueString()
+{
+ static char chars[] = "0123456789abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ time_t now;
+ 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));
+ //kDebug() << pos;
+ p[i] = chars[pos];
+ }
+
+ QByteArray ret;
+ ret.setNum( timeval );
+ ret += '.';
+ ret += p;
+
+ return ret;
+}
+
+QByteArray multiPartBoundary()
+{
+ return "nextPart" + uniqueString();
+}
+
+QByteArray unfoldHeader( const QByteArray &header )
+{
+ QByteArray result;
+ if ( header.isEmpty() ) {
+ return result;
+ }
+
+ int pos = 0, foldBegin = 0, foldMid = 0, foldEnd = 0;
+ while ( ( foldMid = header.indexOf( '\n', pos ) ) >= 0 ) {
+ foldBegin = foldEnd = foldMid;
+ // find the first space before the line-break
+ while ( foldBegin > 0 ) {
+ if ( !QChar::fromLatin1( header[foldBegin - 1] ).isSpace() ) {
+ break;
+ }
+ --foldBegin;
+ }
+ // find the first non-space after the line-break
+ while ( foldEnd <= header.length() - 1 ) {
+ if ( QChar::fromLatin1( header[foldEnd] ).isSpace() ) {
+ ++foldEnd;
+ }
+ else if ( foldEnd > 0 && header[foldEnd - 1] == '\n' &&
+ header[foldEnd] == '=' && foldEnd + 2 < header.length() &&
+ ( ( header[foldEnd + 1] == '0' &&
+ header[foldEnd + 2] == '9' ) ||
+ ( header[foldEnd + 1] == '2' &&
+ header[foldEnd + 2] == '0' ) ) ) {
+ // bug #86302: malformed header continuation starting with =09/=20
+ foldEnd += 3;
+ }
+ else {
+ break;
+ }
+ }
+
+ result += header.mid( pos, foldBegin - pos );
+ if ( foldEnd < header.length() -1 )
+ result += ' ';
+ pos = foldEnd;
+ }
+ const int len = header.length();
+ if ( len > pos ) {
+ result += header.mid( pos, len - pos );
+ }
+ return result;
+}
+
+int findHeaderLineEnd( const QByteArray &src, int &dataBegin, bool *folded )
+{
+ int end = dataBegin;
+ int len = src.length() - 1;
+
+ if ( folded )
+ *folded = false;
+
+ if ( dataBegin < 0 ) {
+ // Not found
+ return -1;
+ }
+
+ if ( dataBegin > len ) {
+ // No data available
+ return len + 1;
+ }
+
+ // If the first line contains nothing, but the next line starts with a space
+ // or a tab, that means a stupid mail client has made the first header field line
+ // entirely empty, and has folded the rest to the next line(s).
+ if ( src.at(end) == '\n' && end + 1 < len &&
+ ( src[end+1] == ' ' || src[end+1] == '\t' ) ) {
+
+ // Skip \n and first whitespace
+ dataBegin += 2;
+ end += 2;
+ }
+
+ if ( src.at(end) != '\n' ) { // check if the header is not empty
+ while ( true ) {
+ end = src.indexOf( '\n', end + 1 );
+ if ( end == -1 || end == len ) {
+ // end of string
+ break;
+ }
+ else if ( src[end+1] == ' ' || src[end+1] == '\t' ||
+ ( src[end+1] == '=' && end+3 <= len &&
+ ( ( src[end+2] == '0' && src[end+3] == '9' ) ||
+ ( src[end+2] == '2' && src[end+3] == '0' ) ) ) ) {
+ // next line is header continuation or starts with =09/=20 (bug #86302)
+ if ( folded )
+ *folded = true;
+ } else {
+ // end of header (no header continuation)
+ break;
+ }
+ }
+ }
+
+ if ( end < 0 ) {
+ end = len + 1; //take the rest of the string
+ }
+ return end;
+}
+
+int indexOfHeader( const QByteArray &src, const QByteArray &name, int &end, int &dataBegin, bool *folded )
+{
+ QByteArray n = name;
+ n.append( ':' );
+ int begin = -1;
+
+ if ( qstrnicmp( n.constData(), src.constData(), n.length() ) == 0 ) {
+ begin = 0;
+ } else {
+ n.prepend('\n');
+ const char *p = strcasestr( src.constData(), n.constData() );
+ if ( !p ) {
+ begin = -1;
+ } else {
+ begin = p - src.constData();
+ ++begin;
+ }
+ }
+
+ if ( begin > -1) { //there is a header with the given name
+ dataBegin = begin + name.length() + 1; //skip the name
+ // skip the usual space after the colon
+ if ( src.at( dataBegin ) == ' ' ) {
+ ++dataBegin;
+ }
+ end = findHeaderLineEnd( src, dataBegin, folded );
+ return begin;
+
+ } else {
+ end = -1;
+ dataBegin = -1;
+ return -1; //header not found
+ }
+}
+
+QByteArray extractHeader( const QByteArray &src, const QByteArray &name )
+{
+ int begin, end;
+ bool folded;
+ QByteArray result;
+
+ if ( src.isEmpty() || indexOfHeader( src, name, end, begin, &folded ) < 0 ) {
+ return result;
+ }
+
+ if ( begin >= 0 ) {
+ if ( !folded ) {
+ result = src.mid( begin, end - begin );
+ } else {
+ if ( end > begin ) {
+ QByteArray hdrValue = src.mid( begin, end - begin );
+ result = unfoldHeader( hdrValue );
+ }
+ }
+ }
+ return result;
+}
+
+QList<QByteArray> extractHeaders( const QByteArray &src, const QByteArray &name )
+{
+ int begin, end;
+ bool folded;
+ QList<QByteArray> result;
+ QByteArray copySrc( src );
+
+ if ( indexOfHeader( copySrc, name, end, begin, &folded ) < 0 ) {
+ return result;
+ }
+
+ while ( begin >= 0 ) {
+ if ( !folded ) {
+ result.append( copySrc.mid( begin, end - begin ) );
+ } else {
+ QByteArray hdrValue = copySrc.mid( begin, end - begin );
+ result.append( unfoldHeader( hdrValue ) );
+ }
+
+ // get the next one, a tiny bit ugly, but we don't want the previous to be found again...
+ copySrc = copySrc.mid( end );
+ if ( indexOfHeader( copySrc, name, end, begin, &folded ) < 0 ) {
+ break;
+ }
+ }
+
+ return result;
+}
+
+void removeHeader( QByteArray &header, const QByteArray &name )
+{
+ int begin, end, dummy;
+ begin = indexOfHeader( header, name, end, dummy );
+ if ( begin >= 0 ) {
+ header.remove( begin, end - begin + 1 );
+ }
+}
+
+QByteArray CRLFtoLF( const QByteArray &s )
+{
+ QByteArray ret = s;
+ ret.replace( "\r\n", "\n" );
+ return ret;
+}
+
+QByteArray CRLFtoLF( const char *s )
+{
+ QByteArray ret = s;
+ return CRLFtoLF( ret );
+}
+
+QByteArray LFtoCRLF( const QByteArray &s )
+{
+ QByteArray ret = s;
+ ret.replace( '\n', "\r\n" );
+ return ret;
+}
+
+QByteArray LFtoCRLF( const char *s )
+{
+ QByteArray ret = s;
+ return LFtoCRLF( ret );
+}
+
+namespace {
+template < typename StringType, typename CharType > void removeQuotesGeneric( StringType & str )
+{
+ bool inQuote = false;
+ for ( int i = 0; i < str.length(); ++i ) {
+ if ( str[i] == CharType( '"' ) ) {
+ str.remove( i, 1 );
+ i--;
+ inQuote = !inQuote;
+ } else {
+ if ( inQuote && ( str[i] == CharType( '\\' ) ) ) {
+ str.remove( i, 1 );
+ }
+ }
+ }
+}
+}
+
+void removeQuots( QByteArray &str )
+{
+ removeQuotesGeneric<QByteArray,char>( str );
+}
+
+void removeQuots( QString &str )
+{
+ removeQuotesGeneric<QString,QLatin1Char>( str );
+}
+
+template<class StringType,class CharType,class CharConverterType,class StringConverterType,class ToString>
+void addQuotes_impl( StringType &str, bool forceQuotes )
+{
+ bool needsQuotes=false;
+ for ( int i=0; i < str.length(); i++ ) {
+ const CharType cur = str.at( i );
+ if ( QString( ToString( str ) ).contains( QRegExp( QLatin1String( "\"|\\\\|=|\\]|\\[|:|;|,|\\.|,|@|<|>|\\)|\\(" ) ) ) ) {
+ needsQuotes = true;
+ }
+ if ( cur == CharConverterType( '\\' ) || cur == CharConverterType( '\"' ) ) {
+ str.insert( i, CharConverterType( '\\' ) );
+ i++;
+ }
+ }
+
+ if ( needsQuotes || forceQuotes ) {
+ str.insert( 0, CharConverterType( '\"' ) );
+ str.append( StringConverterType( "\"" ) );
+ }
+}
+
+void addQuotes( QByteArray &str, bool forceQuotes )
+{
+ addQuotes_impl<QByteArray,char,char,char*,QLatin1String>( str, forceQuotes );
+}
+
+void addQuotes( QString &str, bool forceQuotes )
+{
+ addQuotes_impl<QString,QChar,QLatin1Char,QLatin1String,QString>( str, forceQuotes );
+}
+
+KMIME_EXPORT QString balanceBidiState( const QString &input )
+{
+ const int LRO = 0x202D;
+ const int RLO = 0x202E;
+ const int LRE = 0x202A;
+ const int RLE = 0x202B;
+ const int PDF = 0x202C;
+
+ QString result = input;
+
+ int openDirChangers = 0;
+ int numPDFsRemoved = 0;
+ for ( int i = 0; i < input.length(); i++ ) {
+ const ushort &code = input.at( i ).unicode();
+ if ( code == LRO || code == RLO || code == LRE || code == RLE ) {
+ openDirChangers++;
+ }
+ else if ( code == PDF ) {
+ if ( openDirChangers > 0 ) {
+ openDirChangers--;
+ }
+ else {
+ // One PDF too much, remove it
+ kWarning() << "Possible Unicode spoofing (unexpected PDF) detected in" << input;
+ result.remove( i - numPDFsRemoved, 1 );
+ numPDFsRemoved++;
+ }
+ }
+ }
+
+ if ( openDirChangers > 0 ) {
+ kWarning() << "Possible Unicode spoofing detected in" << input;
+
+ // At PDF chars to the end until the correct state is restored.
+ // As a special exception, when encountering quoted strings, place the PDF before
+ // the last quote.
+ for ( int i = openDirChangers; i > 0; i-- ) {
+ if ( result.endsWith( QLatin1Char( '"' ) ) )
+ result.insert( result.length() - 1, QChar( PDF ) );
+ else
+ result += QChar( PDF );
+ }
+ }
+
+ return result;
+}
+
+QString removeBidiControlChars( const QString &input )
+{
+ const int LRO = 0x202D;
+ const int RLO = 0x202E;
+ const int LRE = 0x202A;
+ const int RLE = 0x202B;
+ QString result = input;
+ result.remove( LRO );
+ result.remove( RLO );
+ result.remove( LRE );
+ result.remove( RLE );
+ return result;
+}
+
+static bool isCryptoPart( Content* content )
+{
+ if( !content->contentType( false ) )
+ return false;
+
+ if( content->contentType()->subType().toLower() == "octet-stream" &&
+ !content->contentDisposition( false ) )
+ return false;
+
+ const Headers::ContentType *contentType = content->contentType();
+ const QByteArray lowerSubType = contentType->subType().toLower();
+ return ( contentType->mediaType().toLower() == "application" &&
+ ( lowerSubType == "pgp-encrypted" ||
+ lowerSubType == "pgp-signature" ||
+ lowerSubType == "pkcs7-mime" ||
+ lowerSubType == "pkcs7-signature" ||
+ lowerSubType == "x-pkcs7-signature" ||
+ ( lowerSubType == "octet-stream" &&
+ content->contentDisposition()->filename().toLower() == QLatin1String( "msg.asc" ) ) ) );
+}
+
+bool hasAttachment( Content* content )
+{
+ if( !content )
+ return false;
+
+ bool emptyFilename = true;
+ if( content->contentDisposition( false ) && !content->contentDisposition()->filename().isEmpty() )
+ emptyFilename = false;
+
+ if( emptyFilename && content->contentType( false ) && !content->contentType()->name().isEmpty() )
+ emptyFilename = false;
+
+ // ignore crypto parts
+ if( !emptyFilename && !isCryptoPart( content ) )
+ return true;
+
+ // Ok, content itself is not an attachment. now we deal with multiparts
+ if( content->contentType()->isMultipart() ) {
+ Q_FOREACH( Content* child, content->contents() ) {
+ if( hasAttachment( child ) )
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool isSigned( Message *message )
+{
+ if ( !message )
+ return false;
+
+ const KMime::Headers::ContentType* const contentType = message->contentType();
+ if ( contentType->isSubtype( "signed" ) ||
+ contentType->isSubtype( "pgp-signature" ) ||
+ contentType->isSubtype( "pkcs7-signature" ) ||
+ contentType->isSubtype( "x-pkcs7-signature" ) ||
+ message->mainBodyPart( "multipart/signed" ) ||
+ message->mainBodyPart( "application/pgp-signature" ) ||
+ message->mainBodyPart( "application/pkcs7-signature" ) ||
+ message->mainBodyPart( "application/x-pkcs7-signature" ) ) {
+ return true;
+ }
+
+ return false;
+}
+
+bool isEncrypted( Message *message )
+{
+ if ( !message )
+ return false;
+
+ const KMime::Headers::ContentType* const contentType = message->contentType();
+ if ( contentType->isSubtype( "encrypted" ) ||
+ contentType->isSubtype( "pgp-encrypted" ) ||
+ contentType->isSubtype( "pkcs7-mime" ) ||
+ message->mainBodyPart( "multipart/encrypted" ) ||
+ message->mainBodyPart( "application/pgp-encrypted" ) ||
+ message->mainBodyPart( "application/pkcs7-mime" ) ) {
+ return true;
+ }
+
+ return false;
+}
+
+bool isInvitation( Content *content )
+{
+ if ( !content )
+ return false;
+
+ const KMime::Headers::ContentType* const contentType = content->contentType( false );
+
+ if ( contentType && contentType->isMediatype( "text" ) && contentType->isSubtype( "calendar" ) )
+ return true;
+
+ return false;
+}
+
+} // namespace KMime
diff --git a/kmime/kmime_util.h b/kmime/kmime_util.h
new file mode 100644
index 0000000..12b8ead
--- /dev/null
+++ b/kmime/kmime_util.h
@@ -0,0 +1,433 @@
+/* -*- 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 library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef __KMIME_UTIL_H__
+#define __KMIME_UTIL_H__
+
+#include <QtCore/QString>
+#include "kmime_export.h"
+#include "kmime_headers.h"
+#include "kmime_content.h"
+
+namespace KMime {
+
+class Message;
+
+/**
+ Consult the charset cache. Only used for reducing mem usage by
+ keeping strings in a common repository.
+ @param name
+*/
+KMIME_EXPORT extern QByteArray cachedCharset( const QByteArray &name );
+
+/**
+ Consult the language cache. Only used for reducing mem usage by
+ keeping strings in a common repository.
+ @param name
+*/
+KMIME_EXPORT extern QByteArray cachedLanguage( const QByteArray &name );
+
+/**
+ Checks whether @p s contains any non-us-ascii characters.
+ @param s
+*/
+KMIME_EXPORT extern bool isUsAscii( const QString &s );
+
+/**
+ Returns a user-visible string for a contentEncoding, for example
+ "quoted-printable" for CEquPr.
+ @ since 4.4
+ TODO should they be i18n'ed?
+*/
+KMIME_EXPORT extern QString nameForEncoding( KMime::Headers::contentEncoding enc );
+
+/**
+ Returns a list of encodings that can correctly encode the @p data.
+ @ since 4.4
+*/
+KMIME_EXPORT QList<KMime::Headers::contentEncoding> encodingsForData(
+ const QByteArray &data );
+//@cond PRIVATE
+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 isOfSet( const uchar map[16], unsigned char ch )
+{
+ return ( ch < 128 ) && ( map[ ch/8 ] & 0x80 >> ch%8 );
+}
+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 );
+}
+//@endcond
+
+/**
+ * Set the fallback charset to use when decoding RFC2047-encoded headers.
+ * If decoding according to the RFC fails, then the fallback encoding is
+ * used instead.
+ *
+ * @param fallbackCharEnc Name of fallback character encoding to use.
+ *
+ * @since 4.5
+ */
+KMIME_EXPORT extern void setFallbackCharEncoding( const QString& fallbackCharEnc );
+
+/**
+ * Retrieve the set fallback charset if there is one set.
+ *
+ * @return The name of the fallback encoding, if one was set, otherwise
+ * an empty QString.
+ *
+ * @since 4.5
+ */
+KMIME_EXPORT extern QString fallbackCharEncoding();
+
+/**
+ * Set whether or not to use outlook compatible attachment filename encoding. Outlook
+ * fails to properly adhere to the RFC2322 standard for parametrized header fields, and
+ * instead is only able to read and write attachment filenames encoded in RFC2047-style.
+ * This will create mails that are not standards-compliant!
+ *
+ * @param violateStandard Whether or not to use outlook-compatible attachment
+ * filename encodings.
+ *
+ * @since 4.5
+ */
+KMIME_EXPORT extern void setUseOutlookAttachmentEncoding( bool violateStandard );
+
+/**
+ * Retrieve whether or not to use outlook compatible encodings for attachments.
+ */
+KMIME_EXPORT extern bool useOutlookAttachmentEncoding();
+/**
+ Decodes string @p src according to RFC2047,i.e., the construct
+ =?charset?[qb]?encoded?=
+
+ @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.
+*/
+KMIME_EXPORT extern QString decodeRFC2047String(
+ const QByteArray &src, QByteArray &usedCS, const QByteArray &defaultCS = QByteArray(),
+ bool forceCS = false );
+
+/** Decode string @p src according to RFC2047 (ie. the
+ =?charset?[qb]?encoded?= construct).
+
+ @param src source string.
+ @return the decoded string.
+*/
+KMIME_EXPORT extern QString decodeRFC2047String( const QByteArray &src );
+
+/**
+ Encodes string @p src according to RFC2047 using charset @p charset.
+
+ This function also makes commas, quotes and other characters part of the encoded name, for example
+ the string "Jöhn Döe" <john@example.com"> would be encoded as <encoded word for "Jöhn Döe"> <john@example.com>,
+ i.e. the opening and closing quote mark would be part of the encoded word.
+ Therefore don't use this function for input strings that contain semantically meaningful characters,
+ like the quoting marks in this example.
+
+ @param src source string.
+ @param charset charset to use. If it can't encode the string, UTF-8 will be used instead.
+ @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.
+*/
+KMIME_EXPORT extern QByteArray encodeRFC2047String(
+ const QString &src, const QByteArray &charset, bool addressHeader=false,
+ bool allow8bitHeaders=false );
+
+
+/**
+ Decodes string @p src according to RFC2231
+
+ @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.
+*/
+KMIME_EXPORT extern QString decodeRFC2231String(
+ const QByteArray &src, QByteArray &usedCS, const QByteArray &defaultCS = QByteArray(),
+ bool forceCS = false );
+
+/** Decode string @p src according to RFC2231 (ie. the
+ charset'lang'encoded construct).
+
+ @param src source string.
+ @return the decoded string.
+*/
+KMIME_EXPORT extern QString decodeRFC2231String( const QByteArray &src );
+
+
+/**
+ Encodes string @p src according to RFC2231 using charset @p charset.
+
+ @param src source string.
+ @param charset charset to use.
+ @return the encoded string.
+*/
+KMIME_EXPORT extern QByteArray encodeRFC2231String( const QString &src, const QByteArray &charset );
+
+/**
+ 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
+*/
+KMIME_EXPORT extern QByteArray uniqueString();
+
+/**
+ 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
+*/
+KMIME_EXPORT extern QByteArray multiPartBoundary();
+
+/**
+ Unfolds the given header if necessary.
+ @param header The header to unfold.
+*/
+KMIME_EXPORT extern QByteArray unfoldHeader( const QByteArray &header );
+
+/**
+ 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.
+*/
+KMIME_EXPORT extern QByteArray extractHeader( const QByteArray &src,
+ const QByteArray &name );
+
+/**
+ Tries to extract the headers 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 all instances of the header @p name in @p src
+
+ @since 4.2
+*/
+KMIME_EXPORT extern QList<QByteArray> extractHeaders( const QByteArray &src,
+ const QByteArray &name );
+
+/**
+ 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
+*/
+KMIME_EXPORT extern QByteArray CRLFtoLF( const QByteArray &s );
+
+/**
+ 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
+*/
+KMIME_EXPORT extern QByteArray CRLFtoLF( const char *s );
+
+/**
+ 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
+*/
+KMIME_EXPORT extern QByteArray LFtoCRLF( const QByteArray &s );
+
+/**
+ Removes quote (DQUOTE) characters and decodes "quoted-pairs"
+ (ie. backslash-escaped characters)
+
+ @param str the string to work on.
+ @see addQuotes
+*/
+//AK_REVIEW: add correctly spelled methods and deprecated the wrongly spelled
+// TODO: KDE5: BIC: rename to "removeQuotes"
+KMIME_EXPORT extern void removeQuots( QByteArray &str );
+
+/**
+ Removes quote (DQUOTE) characters and decodes "quoted-pairs"
+ (ie. backslash-escaped characters)
+
+ @param str the string to work on.
+ @see addQuotes
+*/
+//AK_REVIEW: add correctly spelled methods and deprecated the wrongly spelled
+// TODO: KDE5: BIC: rename to "removeQuotes"
+KMIME_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.
+*/
+KMIME_EXPORT extern void addQuotes( QByteArray &str, bool forceQuotes );
+
+/**
+ * Overloaded method, behaves same as the above.
+ * @since 4.5
+ */
+KMIME_EXPORT extern void addQuotes( QString &str, bool forceQuotes );
+
+/**
+ * Makes sure that the bidirectional state at the end of the string is the
+ * same as at the beginning of the string.
+ *
+ * This is useful so that Unicode control characters that can change the text
+ * direction can not spill over to following strings.
+ *
+ * As an example, consider a mailbox in the form "display name" <local@domain.com>.
+ * If the display name here contains unbalanced control characters that change the
+ * text direction, it would also have an effect on the addrspec, which could lead to
+ * spoofing.
+ *
+ * By passing the display name to this function, one can make sure that no change of
+ * the bidi state can spill over to the next strings, in this case the addrspec.
+ *
+ * Example: The string "Hello <RLO>World" is unbalanced, as it contains a right-to-left
+ * override character, which is never followed by a <PDF>, the "pop directional
+ * formatting" character. This function adds the missing <PDF> at the end, and
+ * the output of this function would be "Hello <RLO>World<PDF>".
+ *
+ * Example of spoofing:
+ * Consider "Firstname Lastname<RLO>" <moc.mitciv@attacker.com>. Because of the RLO,
+ * it is displayed as "Firstname Lastname <moc.rekcatta@victim.com>", which spoofs the
+ * domain name.
+ * By passing "Firstname Lastname<RLO>" to this function, one can balance the <RLO>,
+ * leading to "Firstname Lastname<RLO><PDF>", so the whole mailbox is displayed
+ * correctly as "Firstname Lastname" <moc.mitciv@attacker.com> again.
+ *
+ * See http://unicode.org/reports/tr9 for more information on bidi control chars.
+ *
+ * @param input the display name of a mailbox, which is checked for unbalanced Unicode
+ * direction control characters
+ * @return the display name which now contains a balanced state of direction control
+ * characters
+ *
+ * Note that this function does not do any parsing related to mailboxes, it only works
+ * on plain strings. Therefore, passing the complete mailbox will not lead to any results,
+ * only the display name should be passed.
+ *
+ * @since 4.5
+ */
+KMIME_EXPORT QString balanceBidiState( const QString &input );
+
+/**
+ * Similar to the above function. Instead of trying to balance the Bidi chars, it outright
+ * removes them from the string.
+ *
+ * Reason: KHTML seems to ignore the PDF character, so adding them doesn't fix things :(
+ */
+KMIME_EXPORT QString removeBidiControlChars( const QString &input );
+
+/**
+ * Returns whether or not the given MIME node contains an attachment part. This function will
+ * recursively parse the MIME tree looking for a suitable attachment and return true if one is found.
+ */
+KMIME_EXPORT bool hasAttachment( Content* content );
+
+/**
+ * Returns whether or not the given @p message is partly or fully signed.
+ *
+ * @since 4.6
+ */
+KMIME_EXPORT bool isSigned( Message* message );
+
+/**
+ * Returns whether or not the given @p message is partly or fully encrypted.
+ *
+ * @since 4.6
+ */
+KMIME_EXPORT bool isEncrypted( Message* message );
+
+/**
+ * Returns whether or not the given MIME @p content is an invitation
+ * message of the iTIP protocol.
+ *
+ * @since 4.6
+ */
+KMIME_EXPORT bool isInvitation( Content* content );
+
+} // namespace KMime
+
+#endif /* __KMIME_UTIL_H__ */
diff --git a/kmime/kmime_util_p.h b/kmime/kmime_util_p.h
new file mode 100644
index 0000000..9b381a6
--- /dev/null
+++ b/kmime/kmime_util_p.h
@@ -0,0 +1,63 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library 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 Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KMIME_UTIL_P_H
+#define KMIME_UTIL_P_H
+
+// @cond PRIVATE
+
+/* Internal helper functions. Not part of the public API. */
+
+namespace KMime {
+
+/**
+ Finds the header end in @p src. Aligns the @p dataBegin if needed.
+ @param dataBegin beginning of the data part of the header
+ @param folded true if the headder is folded into multiple lines
+ @returns the end index of the header, -1 if the @p dataBegin was -1.
+*/
+extern int findHeaderLineEnd( const QByteArray &src, int &dataBegin, bool *folded = 0 );
+
+/**
+ Finds the first header of type @p name in @p src.
+ @param end The end index of the header.
+ @param dataBegin begin of the data part of the header, -1 if not found.
+ @param folded true if the headder is folded into multiple lines
+ @returns the begin index of the header, -1 if not found.
+*/
+extern int indexOfHeader( const QByteArray &src, const QByteArray &name, int &end, int &dataBegin, bool *folded = 0 );
+
+/**
+ Removes the first occurrence of the @p name from @p head.
+*/
+// This is used in zero places at the moment.
+extern void removeHeader( QByteArray &head, const QByteArray &name );
+
+/**
+ * Same as encodeRFC2047String(), but with a crucial difference: Instead of encoding the complete
+ * string as a single encoded word, the string will be split up at control characters, and only parts of
+ * the sentence that really need to be encoded will be encoded.
+ */
+extern QByteArray encodeRFC2047Sentence( const QString &src, const QByteArray &charset );
+
+}
+
+// @endcond
+
+#endif
diff --git a/kmime/kmime_version.h b/kmime/kmime_version.h
new file mode 100644
index 0000000..e61621b
--- /dev/null
+++ b/kmime/kmime_version.h
@@ -0,0 +1,32 @@
+/*
+ kmime_version.h
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org>
+ See file AUTHORS for details
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef __KMIME_VERSION_H__
+#define __KMIME_VERSION_H__
+
+#define KMIME_MAJOR 0;
+#define KMIME_MINOR 2;
+#define KMIME_PATCHLEVEL 0;
+#define KMIME_VERSION (KMIME_MAJOR * 100 + KMIME_MINOR * 10 + KMIME_PATCHLEVEL)
+#define KMIME_VERSION_STRING "0.2.0"
+
+#endif // __KMIME_VERSION_H__
diff --git a/kmime/kmime_warning.h b/kmime/kmime_warning.h
new file mode 100644
index 0000000..12b30c7
--- /dev/null
+++ b/kmime/kmime_warning.h
@@ -0,0 +1,60 @@
+/*
+ kmime_warning.h
+
+ KMime, the KDE Internet mail/usenet news message library.
+ Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org>
+ See file AUTHORS for details
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KMIME_WARNING_H
+#define KMIME_WARNING_H
+
+#ifndef KMIME_NO_WARNING
+# include <kdebug.h>
+# define KMIME_WARN kDebug() << "Tokenizer Warning:"
+# define KMIME_WARN_UNKNOWN(x,y) KMIME_WARN << "unknown " #x ": \"" \
+ << y << "\"";
+# define KMIME_WARN_UNKNOWN_ENCODING KMIME_WARN << "unknown encoding in " \
+ "RFC 2047 encoded-word (only know 'q' and 'b')";
+# define KMIME_WARN_UNKNOWN_CHARSET(c) KMIME_WARN << "unknown charset \"" \
+ << c << "\" in RFC 2047 encoded-word";
+# define KMIME_WARN_8BIT(ch) KMIME_WARN \
+ << "8Bit character '" << ch << "'"
+# 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
+# define KMIME_WARN_LONE(x) KMIME_WARN << "Lonely " #x " character"
+# define KMIME_WARN_NON_FOLDING(x) KMIME_WARN << "Non-folding " #x
+# define KMIME_WARN_CTL_OUTSIDE_QS(x) KMIME_WARN << "Control character " \
+ #x " outside quoted-string"
+# define KMIME_WARN_INVALID_X_IN_Y(X,Y) KMIME_WARN << "Invalid character '" \
+ QString(QChar(X)) << "' in " #Y;
+# define KMIME_WARN_TOO_LONG(x) KMIME_WARN << #x \
+ " too long or missing delimiter";
+#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
+
+#endif
diff --git a/kmime/tests/CMakeLists.txt b/kmime/tests/CMakeLists.txt
new file mode 100644
index 0000000..5cc97bc
--- /dev/null
+++ b/kmime/tests/CMakeLists.txt
@@ -0,0 +1,4 @@
+include_directories(${CMAKE_SOURCE_DIR}/kmime)
+
+add_subdirectory( manual )
+add_subdirectory( auto )
diff --git a/kmime/tests/auto/CMakeLists.txt b/kmime/tests/auto/CMakeLists.txt
new file mode 100644
index 0000000..af9522b
--- /dev/null
+++ b/kmime/tests/auto/CMakeLists.txt
@@ -0,0 +1,23 @@
+add_definitions( -DMAIL_DATA_DIR="\\"${CMAKE_CURRENT_SOURCE_DIR}/../data/mails\\"" )
+add_definitions( -DCODEC_DIR="\\"${CMAKE_CURRENT_SOURCE_DIR}/../data\\"" )
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}")
+
+macro(add_kmime_test _source)
+ set(_test ${_source})
+ get_filename_component(_name ${_source} NAME_WE)
+ kde4_add_unit_test(${_name} TESTNAME kmime-${_name} ${_test})
+ target_link_libraries(${_name} kmime ${QT_QTTEST_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY} ${KDE4_KDEUI_LIBS} )
+endmacro(add_kmime_test)
+
+add_kmime_test(rfc2047test.cpp)
+add_kmime_test(utiltest.cpp)
+add_kmime_test(contentindextest.cpp)
+add_kmime_test(charfreqtest.cpp)
+add_kmime_test(headertest.cpp)
+add_kmime_test(contenttest.cpp)
+add_kmime_test(messagetest.cpp)
+add_kmime_test(codectest.cpp)
+#add_kmime_test(headerfactorytest.cpp)
+add_kmime_test(rfc2231test.cpp)
+add_kmime_test(base64benchmark.cpp)
+add_kmime_test(sizetest.cpp)
diff --git a/kmime/tests/auto/base64benchmark.cpp b/kmime/tests/auto/base64benchmark.cpp
new file mode 100644
index 0000000..1334b09
--- /dev/null
+++ b/kmime/tests/auto/base64benchmark.cpp
@@ -0,0 +1,108 @@
+/*
+ Copyright (c) 2010 Volker Krause <vkrause@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ un