diff options
Diffstat (limited to 'src')
41 files changed, 1817 insertions, 697 deletions
diff --git a/src/address.cpp b/src/address.cpp index eccf4e21..ab207cf6 100644 --- a/src/address.cpp +++ b/src/address.cpp @@ -66,8 +66,9 @@ address-list = (address *("," address)) / obs-addr-list */ -ref <address> address::parseNext(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +ref <address> address::parseNext + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { bool escaped = false; bool quoted = false; @@ -179,7 +180,7 @@ ref <address> address::parseNext(const string& buffer, const string::size_type p ? create <mailboxGroup>().dynamicCast <address>() : create <mailbox>().dynamicCast <address>(); - parsedAddress->parse(buffer, start, pos, NULL); + parsedAddress->parse(ctx, buffer, start, pos, NULL); parsedAddress->setParsedBounds(start, pos); return (parsedAddress); diff --git a/src/addressList.cpp b/src/addressList.cpp index 467a283a..5e033f38 100644 --- a/src/addressList.cpp +++ b/src/addressList.cpp @@ -50,8 +50,9 @@ addressList::~addressList() } -void addressList::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void addressList::parseImpl + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { removeAllAddresses(); @@ -59,7 +60,7 @@ void addressList::parseImpl(const string& buffer, const string::size_type positi while (pos < end) { - ref <address> parsedAddress = address::parseNext(buffer, pos, end, &pos); + ref <address> parsedAddress = address::parseNext(ctx, buffer, pos, end, &pos); if (parsedAddress != NULL) m_list.push_back(parsedAddress); @@ -72,16 +73,20 @@ void addressList::parseImpl(const string& buffer, const string::size_type positi } -void addressList::generateImpl(utility::outputStream& os, const string::size_type maxLineLength, - const string::size_type curLinePos, string::size_type* newLinePos) const +void addressList::generateImpl + (const generationContext& ctx, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const { string::size_type pos = curLinePos; + generationContext tmpCtx(ctx); + tmpCtx.setMaxLineLength(tmpCtx.getMaxLineLength() - 2); + if (!m_list.empty()) { for (std::vector <ref <address> >::const_iterator i = m_list.begin() ; ; ) { - (*i)->generate(os, maxLineLength - 2, pos, &pos); + (*i)->generate(ctx, os, pos, &pos); if (++i == m_list.end()) break; diff --git a/src/base.cpp b/src/base.cpp index 47262faf..d5f3e787 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -40,7 +40,8 @@ #include "vmime/utility/encoder/encoderFactory.hpp" #include "vmime/headerFieldFactory.hpp" #include "vmime/textPartFactory.hpp" -#include "vmime/options.hpp" +#include "vmime/generationContext.hpp" +#include "vmime/parsingContext.hpp" #if VMIME_HAVE_MESSAGING_FEATURES #include "vmime/net/serviceFactory.hpp" @@ -132,7 +133,8 @@ public: initializer() { - options::getInstance(); + parsingContext::getDefaultContext(); + generationContext::getDefaultContext(); utility::encoder::encoderFactory::getInstance(); headerFieldFactory::getInstance(); diff --git a/src/body.cpp b/src/body.cpp index 20781012..8c599b99 100644 --- a/src/body.cpp +++ b/src/body.cpp @@ -24,8 +24,6 @@ #include "vmime/bodyPart.hpp" #include "vmime/body.hpp" -#include "vmime/options.hpp" - #include "vmime/contentTypeField.hpp" #include "vmime/text.hpp" @@ -56,7 +54,8 @@ body::~body() void body::parseImpl - (ref <utility::parserInputStreamAdapter> parser, + (const parsingContext& /* ctx */, + ref <utility::parserInputStreamAdapter> parser, const utility::stream::size_type position, const utility::stream::size_type end, utility::stream::size_type* newPosition) @@ -381,8 +380,9 @@ void body::parseImpl } -void body::generateImpl(utility::outputStream& os, const string::size_type maxLineLength, - const string::size_type /* curLinePos */, string::size_type* newLinePos) const +void body::generateImpl + (const generationContext& ctx, utility::outputStream& os, + const string::size_type /* curLinePos */, string::size_type* newLinePos) const { // MIME-Multipart if (getPartCount() != 0) @@ -418,7 +418,7 @@ void body::generateImpl(utility::outputStream& os, const string::size_type maxLi const string& prologText = m_prologText.empty() ? (isRootPart() - ? options::getInstance()->multipart.getPrologText() + ? ctx.getPrologText() : NULL_STRING ) : m_prologText; @@ -426,7 +426,7 @@ void body::generateImpl(utility::outputStream& os, const string::size_type maxLi const string& epilogText = m_epilogText.empty() ? (isRootPart() - ? options::getInstance()->multipart.getEpilogText() + ? ctx.getEpilogText() : NULL_STRING ) : m_epilogText; @@ -435,7 +435,7 @@ void body::generateImpl(utility::outputStream& os, const string::size_type maxLi { text prolog(prologText, vmime::charset("us-ascii")); - prolog.encodeAndFold(os, maxLineLength, 0, + prolog.encodeAndFold(ctx, os, 0, NULL, text::FORCE_NO_ENCODING | text::NO_NEW_LINE_SEQUENCE); os << CRLF; @@ -447,7 +447,7 @@ void body::generateImpl(utility::outputStream& os, const string::size_type maxLi { os << CRLF; - getPartAt(p)->generate(os, maxLineLength, 0); + getPartAt(p)->generate(ctx, os, 0); os << CRLF << "--" << boundary; } @@ -458,7 +458,7 @@ void body::generateImpl(utility::outputStream& os, const string::size_type maxLi { text epilog(epilogText, vmime::charset("us-ascii")); - epilog.encodeAndFold(os, maxLineLength, 0, + epilog.encodeAndFold(ctx, os, 0, NULL, text::FORCE_NO_ENCODING | text::NO_NEW_LINE_SEQUENCE); os << CRLF; @@ -471,7 +471,7 @@ void body::generateImpl(utility::outputStream& os, const string::size_type maxLi else { // Generate the contents - m_contents->generate(os, getEncoding(), maxLineLength); + m_contents->generate(os, getEncoding(), ctx.getMaxLineLength()); } } diff --git a/src/bodyPart.cpp b/src/bodyPart.cpp index fbe9f1ed..32544ba8 100644 --- a/src/bodyPart.cpp +++ b/src/bodyPart.cpp @@ -47,17 +47,18 @@ bodyPart::bodyPart(weak_ref <vmime::bodyPart> parentPart) void bodyPart::parseImpl - (ref <utility::parserInputStreamAdapter> parser, + (const parsingContext& ctx, + ref <utility::parserInputStreamAdapter> parser, const utility::stream::size_type position, const utility::stream::size_type end, utility::stream::size_type* newPosition) { // Parse the headers string::size_type pos = position; - m_header->parse(parser, pos, end, &pos); + m_header->parse(ctx, parser, pos, end, &pos); // Parse the body contents - m_body->parse(parser, pos, end, NULL); + m_body->parse(ctx, parser, pos, end, NULL); setParsedBounds(position, end); @@ -66,14 +67,15 @@ void bodyPart::parseImpl } -void bodyPart::generateImpl(utility::outputStream& os, const string::size_type maxLineLength, - const string::size_type /* curLinePos */, string::size_type* newLinePos) const +void bodyPart::generateImpl + (const generationContext& ctx, utility::outputStream& os, + const string::size_type /* curLinePos */, string::size_type* newLinePos) const { - m_header->generate(os, maxLineLength); + m_header->generate(ctx, os); os << CRLF; - m_body->generate(os, maxLineLength); + m_body->generate(ctx, os); if (newLinePos) *newLinePos = 0; diff --git a/src/charset.cpp b/src/charset.cpp index 84368e85..092676b2 100644 --- a/src/charset.cpp +++ b/src/charset.cpp @@ -57,8 +57,9 @@ charset::charset(const char* name) } -void charset::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void charset::parseImpl + (const parsingContext& /* ctx */, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { m_name = utility::stringUtils::trim (string(buffer.begin() + position, buffer.begin() + end)); @@ -74,8 +75,9 @@ void charset::parseImpl(const string& buffer, const string::size_type position, } -void charset::generateImpl(utility::outputStream& os, const string::size_type /* maxLineLength */, - const string::size_type curLinePos, string::size_type* newLinePos) const +void charset::generateImpl + (const generationContext& /* ctx */, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const { os << m_name; @@ -85,17 +87,25 @@ void charset::generateImpl(utility::outputStream& os, const string::size_type /* void charset::convert(utility::inputStream& in, utility::outputStream& out, - const charset& source, const charset& dest) + const charset& source, const charset& dest, + const charsetConverterOptions& opts) { - charsetConverter conv(source, dest); - conv.convert(in, out); + ref <charsetConverter> conv = charsetConverter::create(source, dest, opts); + conv->convert(in, out); } -void charset::convert(const string& in, string& out, const charset& source, const charset& dest) +void charset::convert(const string& in, string& out, const charset& source, const charset& dest, + const charsetConverterOptions& opts) { - charsetConverter conv(source, dest); - conv.convert(in, out); + if (source == dest) + { + out = in; + return; + } + + ref <charsetConverter> conv = charsetConverter::create(source, dest, opts); + conv->convert(in, out); } diff --git a/src/charsetConverter.cpp b/src/charsetConverter.cpp index a33f4f84..c2041476 100644 --- a/src/charsetConverter.cpp +++ b/src/charsetConverter.cpp @@ -22,398 +22,25 @@ // #include "vmime/charsetConverter.hpp" -#include "vmime/exception.hpp" -#include "vmime/utility/inputStreamStringAdapter.hpp" -#include "vmime/utility/outputStreamStringAdapter.hpp" - - -extern "C" -{ -#ifndef VMIME_BUILDING_DOC - - #include <iconv.h> - #include <errno.h> - - // HACK: prototypes may differ depending on the compiler and/or system (the - // second parameter may or may not be 'const'). This relies on the compiler - // for choosing the right type. - class ICONV_HACK - { - public: - - ICONV_HACK(const char** ptr) : m_ptr(ptr) { } - - operator const char**() { return m_ptr; } - operator char**() { return const_cast <char**>(m_ptr); } - - private: - - const char** m_ptr; - }; - -#endif // VMIME_BUILDING_DOC -} - - - -// Output replacement char when an invalid sequence is encountered -template <typename OUTPUT_CLASS, typename ICONV_DESC> -void outputInvalidChar(OUTPUT_CLASS& out, ICONV_DESC cd) -{ - const char* invalidCharIn = "?"; - size_t invalidCharInLen = 1; - - char invalidCharOutBuffer[16]; - char* invalidCharOutPtr = invalidCharOutBuffer; - size_t invalidCharOutLen = 16; - - if (iconv(cd, ICONV_HACK(&invalidCharIn), &invalidCharInLen, - &invalidCharOutPtr, &invalidCharOutLen) != static_cast <size_t>(-1)) - { - out.write(invalidCharOutBuffer, 16 - invalidCharOutLen); - } -} +#include "vmime/charsetConverter_iconv.hpp" +#include "vmime/charsetConverter_idna.hpp" namespace vmime { -charsetConverter::charsetConverter(const charset& source, const charset& dest) - : m_desc(NULL), m_source(source), m_dest(dest) -{ - // Get an iconv descriptor - const iconv_t cd = iconv_open(dest.getName().c_str(), source.getName().c_str()); - - if (cd != reinterpret_cast <iconv_t>(-1)) - { - iconv_t* p = new iconv_t; - *p= cd; - - m_desc = p; - } -} - - -charsetConverter::~charsetConverter() -{ - if (m_desc != NULL) - { - // Close iconv handle - iconv_close(*static_cast <iconv_t*>(m_desc)); - - delete static_cast <iconv_t*>(m_desc); - m_desc = NULL; - } -} - - -void charsetConverter::convert(utility::inputStream& in, utility::outputStream& out) -{ - if (m_desc == NULL) - throw exceptions::charset_conv_error("Cannot initialize converter."); - - const iconv_t cd = *static_cast <iconv_t*>(m_desc); - - char inBuffer[32768]; - char outBuffer[32768]; - size_t inPos = 0; - - bool prevIsInvalid = false; - bool breakAfterNext = false; - - while (true) - { - // Fullfill the buffer - size_t inLength = static_cast <size_t>(in.read(inBuffer + inPos, sizeof(inBuffer) - inPos) + inPos); - size_t outLength = sizeof(outBuffer); - - const char* inPtr = breakAfterNext ? NULL : inBuffer; - size_t *ptrLength = breakAfterNext ? NULL : &inLength; - char* outPtr = outBuffer; - - // Convert input bytes - if (iconv(cd, ICONV_HACK(&inPtr), ptrLength, - &outPtr, &outLength) == static_cast <size_t>(-1)) - { - // Illegal input sequence or input sequence has no equivalent - // sequence in the destination charset. - if (prevIsInvalid) - { - // Write successfully converted bytes - out.write(outBuffer, sizeof(outBuffer) - outLength); - - // Output a special character to indicate we don't known how to - // convert the sequence at this position - outputInvalidChar(out, cd); - - // Skip a byte and leave unconverted bytes in the input buffer - std::copy(const_cast <char*>(inPtr + 1), inBuffer + sizeof(inBuffer), inBuffer); - inPos = inLength - 1; - } - else - { - // Write successfully converted bytes - out.write(outBuffer, sizeof(outBuffer) - outLength); - - // Leave unconverted bytes in the input buffer - std::copy(const_cast <char*>(inPtr), inBuffer + sizeof(inBuffer), inBuffer); - inPos = inLength; - - if (errno != E2BIG) - prevIsInvalid = true; - } - } - else - { - // Write successfully converted bytes - out.write(outBuffer, sizeof(outBuffer) - outLength); - - inPos = 0; - prevIsInvalid = false; - } - - if (breakAfterNext) - break; - - // Check for end of data, loop again to flush stateful data from iconv - if (in.eof() && inPos == 0) - breakAfterNext = true; - } -} - - -void charsetConverter::convert(const string& in, string& out) +// static +ref <charsetConverter> charsetConverter::create + (const charset& source, const charset& dest, + const charsetConverterOptions& opts) { - out.clear(); - - utility::inputStreamStringAdapter is(in); - utility::outputStreamStringAdapter os(out); - - convert(is, os); - - os.flush(); + if (source == "idna" || dest == "idna") + return vmime::create <charsetConverter_idna>(source, dest, opts); + else + return vmime::create <charsetConverter_iconv>(source, dest, opts); } - -// charsetFilteredOutputStream - -namespace utility { - - -charsetFilteredOutputStream::charsetFilteredOutputStream - (const charset& source, const charset& dest, outputStream& os) - : m_desc(NULL), m_sourceCharset(source), m_destCharset(dest), - m_stream(os), m_unconvCount(0) -{ - // Get an iconv descriptor - const iconv_t cd = iconv_open(dest.getName().c_str(), source.getName().c_str()); - - if (cd != reinterpret_cast <iconv_t>(-1)) - { - iconv_t* p = new iconv_t; - *p= cd; - - m_desc = p; - } -} - - -charsetFilteredOutputStream::~charsetFilteredOutputStream() -{ - if (m_desc != NULL) - { - // Close iconv handle - iconv_close(*static_cast <iconv_t*>(m_desc)); - - delete static_cast <iconv_t*>(m_desc); - m_desc = NULL; - } -} - - -outputStream& charsetFilteredOutputStream::getNextOutputStream() -{ - return m_stream; -} - - -void charsetFilteredOutputStream::write - (const value_type* const data, const size_type count) -{ - if (m_desc == NULL) - throw exceptions::charset_conv_error("Cannot initialize converter."); - - const iconv_t cd = *static_cast <iconv_t*>(m_desc); - - const value_type* curData = data; - size_type curDataLen = count; - - // If there is some unconverted bytes left, add more data from this - // chunk to see if it can now be converted. - while (m_unconvCount != 0 || curDataLen != 0) - { - if (m_unconvCount != 0) - { - // Check if an incomplete input sequence is larger than the - // input buffer size: should not happen except if something - // in the input sequence is invalid. If so, output a special - // character and skip one byte in the invalid sequence. - if (m_unconvCount >= sizeof(m_unconvBuffer)) - { - outputInvalidChar(m_stream, cd); - - std::copy(m_unconvBuffer + 1, - m_unconvBuffer + m_unconvCount, m_unconvBuffer); - - m_unconvCount--; - } - - // Get more data - const size_type remaining = - std::min(curDataLen, sizeof(m_unconvBuffer) - m_unconvCount); - - std::copy(curData, curData + remaining, m_unconvBuffer + m_unconvCount); - - m_unconvCount += remaining; - curDataLen -= remaining; - curData += remaining; - - if (remaining == 0) - return; // no more data - - // Try a conversion - const char* inPtr = m_unconvBuffer; - size_t inLength = m_unconvCount; - char* outPtr = m_outputBuffer; - size_t outLength = sizeof(m_outputBuffer); - - const size_t inLength0 = inLength; - - if (iconv(cd, ICONV_HACK(&inPtr), &inLength, &outPtr, &outLength) == static_cast <size_t>(-1)) - { - const size_t inputConverted = inLength0 - inLength; - - // Write successfully converted bytes - m_stream.write(m_outputBuffer, sizeof(m_outputBuffer) - outLength); - - // Shift unconverted bytes - std::copy(m_unconvBuffer + inputConverted, - m_unconvBuffer + m_unconvCount, m_unconvBuffer); - - m_unconvCount -= inputConverted; - - continue; - } - - // Write successfully converted bytes - m_stream.write(m_outputBuffer, sizeof(m_outputBuffer) - outLength); - - // Empty the unconverted buffer - m_unconvCount = 0; - } - - if (curDataLen == 0) - return; // no more data - - // Now, convert the current data buffer - const char* inPtr = curData; - size_t inLength = std::min(curDataLen, sizeof(m_outputBuffer) / MAX_CHARACTER_WIDTH); - char* outPtr = m_outputBuffer; - size_t outLength = sizeof(m_outputBuffer); - - const size_t inLength0 = inLength; - - if (iconv(cd, ICONV_HACK(&inPtr), &inLength, &outPtr, &outLength) == static_cast <size_t>(-1)) - { - // Write successfully converted bytes - m_stream.write(m_outputBuffer, sizeof(m_outputBuffer) - outLength); - - const size_t inputConverted = inLength0 - inLength; - - curData += inputConverted; - curDataLen -= inputConverted; - - // Put one byte byte into the unconverted buffer so - // that the next iteration fill it - if (curDataLen != 0) - { - m_unconvCount = 1; - m_unconvBuffer[0] = *curData; - - curData++; - curDataLen--; - } - } - else - { - // Write successfully converted bytes - m_stream.write(m_outputBuffer, sizeof(m_outputBuffer) - outLength); - - curData += inLength0; - curDataLen -= inLength0; - } - } -} - - -void charsetFilteredOutputStream::flush() -{ - if (m_desc == NULL) - throw exceptions::charset_conv_error("Cannot initialize converter."); - - const iconv_t cd = *static_cast <iconv_t*>(m_desc); - - size_t offset = 0; - - // Process unconverted bytes - while (m_unconvCount != 0) - { - // Try a conversion - const char* inPtr = m_unconvBuffer + offset; - size_t inLength = m_unconvCount; - char* outPtr = m_outputBuffer; - size_t outLength = sizeof(m_outputBuffer); - - const size_t inLength0 = inLength; - - if (iconv(cd, ICONV_HACK(&inPtr), &inLength, &outPtr, &outLength) == static_cast <size_t>(-1)) - { - const size_t inputConverted = inLength0 - inLength; - - // Skip a "blocking" character - if (inputConverted == 0) - { - outputInvalidChar(m_stream, cd); - - offset++; - m_unconvCount--; - } - else - { - // Write successfully converted bytes - m_stream.write(m_outputBuffer, sizeof(m_outputBuffer) - outLength); - - offset += inputConverted; - m_unconvCount -= inputConverted; - } - } - else - { - // Write successfully converted bytes - m_stream.write(m_outputBuffer, sizeof(m_outputBuffer) - outLength); - - m_unconvCount = 0; - } - } - - m_stream.flush(); -} - - -} // utility - - } // vmime diff --git a/src/charsetConverterOptions.cpp b/src/charsetConverterOptions.cpp new file mode 100644 index 00000000..caeacd01 --- /dev/null +++ b/src/charsetConverterOptions.cpp @@ -0,0 +1,37 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/charsetConverterOptions.hpp" + + +namespace vmime +{ + + +charsetConverterOptions::charsetConverterOptions() + : invalidSequence("?") +{ +} + + +} // vmime diff --git a/src/charsetConverter_iconv.cpp b/src/charsetConverter_iconv.cpp new file mode 100644 index 00000000..c5d3557e --- /dev/null +++ b/src/charsetConverter_iconv.cpp @@ -0,0 +1,435 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/charsetConverter_iconv.hpp" + +#include "vmime/exception.hpp" +#include "vmime/utility/inputStreamStringAdapter.hpp" +#include "vmime/utility/outputStreamStringAdapter.hpp" + + +extern "C" +{ +#ifndef VMIME_BUILDING_DOC + + #include <iconv.h> + #include <errno.h> + + // HACK: prototypes may differ depending on the compiler and/or system (the + // second parameter may or may not be 'const'). This relies on the compiler + // for choosing the right type. + class ICONV_HACK + { + public: + + ICONV_HACK(const char** ptr) : m_ptr(ptr) { } + + operator const char**() { return m_ptr; } + operator char**() { return const_cast <char**>(m_ptr); } + + private: + + const char** m_ptr; + }; + +#endif // VMIME_BUILDING_DOC +} + + + +// Output replacement char when an invalid sequence is encountered +template <typename OUTPUT_CLASS, typename ICONV_DESC> +void outputInvalidChar(OUTPUT_CLASS& out, ICONV_DESC cd, + const vmime::charsetConverterOptions& opts = vmime::charsetConverterOptions()) +{ + const char* invalidCharIn = opts.invalidSequence.c_str(); + size_t invalidCharInLen = opts.invalidSequence.length(); + + char invalidCharOutBuffer[16]; + char* invalidCharOutPtr = invalidCharOutBuffer; + size_t invalidCharOutLen = 16; + + if (iconv(cd, ICONV_HACK(&invalidCharIn), &invalidCharInLen, + &invalidCharOutPtr, &invalidCharOutLen) != static_cast <size_t>(-1)) + { + out.write(invalidCharOutBuffer, 16 - invalidCharOutLen); + } +} + + + +namespace vmime +{ + + +charsetConverter_iconv::charsetConverter_iconv + (const charset& source, const charset& dest, const charsetConverterOptions& opts) + : m_desc(NULL), m_source(source), m_dest(dest), m_options(opts) +{ + // Get an iconv descriptor + const iconv_t cd = iconv_open(dest.getName().c_str(), source.getName().c_str()); + + if (cd != reinterpret_cast <iconv_t>(-1)) + { + iconv_t* p = new iconv_t; + *p= cd; + + m_desc = p; + } +} + + +charsetConverter_iconv::~charsetConverter_iconv() +{ + if (m_desc != NULL) + { + // Close iconv handle + iconv_close(*static_cast <iconv_t*>(m_desc)); + + delete static_cast <iconv_t*>(m_desc); + m_desc = NULL; + } +} + + +void charsetConverter_iconv::convert(utility::inputStream& in, utility::outputStream& out) +{ + if (m_desc == NULL) + throw exceptions::charset_conv_error("Cannot initialize converter."); + + const iconv_t cd = *static_cast <iconv_t*>(m_desc); + + char inBuffer[32768]; + char outBuffer[32768]; + size_t inPos = 0; + + bool prevIsInvalid = false; + bool breakAfterNext = false; + + while (true) + { + // Fullfill the buffer + size_t inLength = static_cast <size_t>(in.read(inBuffer + inPos, sizeof(inBuffer) - inPos) + inPos); + size_t outLength = sizeof(outBuffer); + + const char* inPtr = breakAfterNext ? NULL : inBuffer; + size_t *ptrLength = breakAfterNext ? NULL : &inLength; + char* outPtr = outBuffer; + + // Convert input bytes + if (iconv(cd, ICONV_HACK(&inPtr), ptrLength, + &outPtr, &outLength) == static_cast <size_t>(-1)) + { + // Illegal input sequence or input sequence has no equivalent + // sequence in the destination charset. + if (prevIsInvalid) + { + // Write successfully converted bytes + out.write(outBuffer, sizeof(outBuffer) - outLength); + + // Output a special character to indicate we don't known how to + // convert the sequence at this position + outputInvalidChar(out, cd, m_options); + + // Skip a byte and leave unconverted bytes in the input buffer + std::copy(const_cast <char*>(inPtr + 1), inBuffer + sizeof(inBuffer), inBuffer); + inPos = inLength - 1; + } + else + { + // Write successfully converted bytes + out.write(outBuffer, sizeof(outBuffer) - outLength); + + // Leave unconverted bytes in the input buffer + std::copy(const_cast <char*>(inPtr), inBuffer + sizeof(inBuffer), inBuffer); + inPos = inLength; + + if (errno != E2BIG) + prevIsInvalid = true; + } + } + else + { + // Write successfully converted bytes + out.write(outBuffer, sizeof(outBuffer) - outLength); + + inPos = 0; + prevIsInvalid = false; + } + + if (breakAfterNext) + break; + + // Check for end of data, loop again to flush stateful data from iconv + if (in.eof() && inPos == 0) + breakAfterNext = true; + } +} + + +void charsetConverter_iconv::convert(const string& in, string& out) +{ + if (m_source == m_dest) + { + // No conversion needed + out = in; + return; + } + + out.clear(); + + utility::inputStreamStringAdapter is(in); + utility::outputStreamStringAdapter os(out); + + convert(is, os); + + os.flush(); +} + + +ref <utility::charsetFilteredOutputStream> charsetConverter_iconv::getFilteredOutputStream(utility::outputStream& os) +{ + return vmime::create <utility::charsetFilteredOutputStream_iconv>(m_source, m_dest, &os); +} + + + +// charsetFilteredOutputStream_iconv + +namespace utility { + + +charsetFilteredOutputStream_iconv::charsetFilteredOutputStream_iconv + (const charset& source, const charset& dest, outputStream* os) + : m_desc(NULL), m_sourceCharset(source), m_destCharset(dest), + m_stream(*os), m_unconvCount(0) +{ + // Get an iconv descriptor + const iconv_t cd = iconv_open(dest.getName().c_str(), source.getName().c_str()); + + if (cd != reinterpret_cast <iconv_t>(-1)) + { + iconv_t* p = new iconv_t; + *p= cd; + + m_desc = p; + } +} + + +charsetFilteredOutputStream_iconv::~charsetFilteredOutputStream_iconv() +{ + if (m_desc != NULL) + { + // Close iconv handle + iconv_close(*static_cast <iconv_t*>(m_desc)); + + delete static_cast <iconv_t*>(m_desc); + m_desc = NULL; + } +} + + +outputStream& charsetFilteredOutputStream_iconv::getNextOutputStream() +{ + return m_stream; +} + + +void charsetFilteredOutputStream_iconv::write + (const value_type* const data, const size_type count) +{ + if (m_desc == NULL) + throw exceptions::charset_conv_error("Cannot initialize converter."); + + const iconv_t cd = *static_cast <iconv_t*>(m_desc); + + const value_type* curData = data; + size_type curDataLen = count; + + // If there is some unconverted bytes left, add more data from this + // chunk to see if it can now be converted. + while (m_unconvCount != 0 || curDataLen != 0) + { + if (m_unconvCount != 0) + { + // Check if an incomplete input sequence is larger than the + // input buffer size: should not happen except if something + // in the input sequence is invalid. If so, output a special + // character and skip one byte in the invalid sequence. + if (m_unconvCount >= sizeof(m_unconvBuffer)) + { + outputInvalidChar(m_stream, cd); + + std::copy(m_unconvBuffer + 1, + m_unconvBuffer + m_unconvCount, m_unconvBuffer); + + m_unconvCount--; + } + + // Get more data + const size_type remaining = + std::min(curDataLen, sizeof(m_unconvBuffer) - m_unconvCount); + + std::copy(curData, curData + remaining, m_unconvBuffer + m_unconvCount); + + m_unconvCount += remaining; + curDataLen -= remaining; + curData += remaining; + + if (remaining == 0) + return; // no more data + + // Try a conversion + const char* inPtr = m_unconvBuffer; + size_t inLength = m_unconvCount; + char* outPtr = m_outputBuffer; + size_t outLength = sizeof(m_outputBuffer); + + const size_t inLength0 = inLength; + + if (iconv(cd, ICONV_HACK(&inPtr), &inLength, &outPtr, &outLength) == static_cast <size_t>(-1)) + { + const size_t inputConverted = inLength0 - inLength; + + // Write successfully converted bytes + m_stream.write(m_outputBuffer, sizeof(m_outputBuffer) - outLength); + + // Shift unconverted bytes + std::copy(m_unconvBuffer + inputConverted, + m_unconvBuffer + m_unconvCount, m_unconvBuffer); + + m_unconvCount -= inputConverted; + + continue; + } + + // Write successfully converted bytes + m_stream.write(m_outputBuffer, sizeof(m_outputBuffer) - outLength); + + // Empty the unconverted buffer + m_unconvCount = 0; + } + + if (curDataLen == 0) + return; // no more data + + // Now, convert the current data buffer + const char* inPtr = curData; + size_t inLength = std::min(curDataLen, sizeof(m_outputBuffer) / MAX_CHARACTER_WIDTH); + char* outPtr = m_outputBuffer; + size_t outLength = sizeof(m_outputBuffer); + + const size_t inLength0 = inLength; + + if (iconv(cd, ICONV_HACK(&inPtr), &inLength, &outPtr, &outLength) == static_cast <size_t>(-1)) + { + // Write successfully converted bytes + m_stream.write(m_outputBuffer, sizeof(m_outputBuffer) - outLength); + + const size_t inputConverted = inLength0 - inLength; + + curData += inputConverted; + curDataLen -= inputConverted; + + // Put one byte byte into the unconverted buffer so + // that the next iteration fill it + if (curDataLen != 0) + { + m_unconvCount = 1; + m_unconvBuffer[0] = *curData; + + curData++; + curDataLen--; + } + } + else + { + // Write successfully converted bytes + m_stream.write(m_outputBuffer, sizeof(m_outputBuffer) - outLength); + + curData += inLength0; + curDataLen -= inLength0; + } + } +} + + +void charsetFilteredOutputStream_iconv::flush() +{ + if (m_desc == NULL) + throw exceptions::charset_conv_error("Cannot initialize converter."); + + const iconv_t cd = *static_cast <iconv_t*>(m_desc); + + size_t offset = 0; + + // Process unconverted bytes + while (m_unconvCount != 0) + { + // Try a conversion + const char* inPtr = m_unconvBuffer + offset; + size_t inLength = m_unconvCount; + char* outPtr = m_outputBuffer; + size_t outLength = sizeof(m_outputBuffer); + + const size_t inLength0 = inLength; + + if (iconv(cd, ICONV_HACK(&inPtr), &inLength, &outPtr, &outLength) == static_cast <size_t>(-1)) + { + const size_t inputConverted = inLength0 - inLength; + + // Skip a "blocking" character + if (inputConverted == 0) + { + outputInvalidChar(m_stream, cd); + + offset++; + m_unconvCount--; + } + else + { + // Write successfully converted bytes + m_stream.write(m_outputBuffer, sizeof(m_outputBuffer) - outLength); + + offset += inputConverted; + m_unconvCount -= inputConverted; + } + } + else + { + // Write successfully converted bytes + m_stream.write(m_outputBuffer, sizeof(m_outputBuffer) - outLength); + + m_unconvCount = 0; + } + } + + m_stream.flush(); +} + + +} // utility + + +} // vmime diff --git a/src/charsetConverter_idna.cpp b/src/charsetConverter_idna.cpp new file mode 100644 index 00000000..cde2209a --- /dev/null +++ b/src/charsetConverter_idna.cpp @@ -0,0 +1,168 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/charsetConverter_idna.hpp" + +#include "vmime/exception.hpp" + +#include "vmime/utility/stringUtils.hpp" +#include "vmime/utility/streamUtils.hpp" +#include "vmime/utility/outputStreamStringAdapter.hpp" + + +extern "C" +{ + +#include "contrib/punycode/punycode.h" +#include "contrib/punycode/punycode.c" + +} + +#include "contrib/utf8/utf8.h" + + +namespace vmime +{ + + +charsetConverter_idna::charsetConverter_idna + (const charset& source, const charset& dest, const charsetConverterOptions& opts) + : m_source(source), m_dest(dest), m_options(opts) +{ +} + + +charsetConverter_idna::~charsetConverter_idna() +{ +} + + +void charsetConverter_idna::convert(utility::inputStream& in, utility::outputStream& out) +{ + // IDNA should be used for short strings, so it does not matter if we + // do not work directly on the stream + string inStr; + vmime::utility::outputStreamStringAdapter os(inStr); + vmime::utility::bufferedStreamCopy(in, os); + + string outStr; + convert(inStr, outStr); + + out << outStr; +} + + +void charsetConverter_idna::convert(const string& in, string& out) +{ + if (m_source == m_dest) + { + // No conversion needed + out = in; + return; + } + + out.clear(); + + if (m_dest == "idna") + { + if (utility::stringUtils::is7bit(in)) + { + // No need to encode as Punycode + out = in; + return; + } + + string inUTF8; + charset::convert(in, inUTF8, m_source, vmime::charsets::UTF_8); + + const string::value_type* ch = inUTF8.c_str(); + const string::value_type* end = inUTF8.c_str() + inUTF8.length(); + + std::vector <punycode_uint> unichars; + unichars.reserve(inUTF8.length()); + + while (ch < end) + { + const utf8::uint32_t uc = utf8::unchecked::next(ch); + unichars.push_back(uc); + } + + std::vector <char> output(inUTF8.length() * 2); + punycode_uint outputLen = output.size(); + + const punycode_status status = punycode_encode + (unichars.size(), &unichars[0], /* case_flags */ NULL, &outputLen, &output[0]); + + if (status == punycode_success) + { + out = string("xn--") + string(output.begin(), output.begin() + outputLen); + } + else + { + // TODO + } + } + else if (m_source == "idna") + { + if (in.length() < 5 || in.substr(0, 4) != "xn--") + { + // Not an IDNA string + out = in; + return; + } + + std::vector <punycode_uint> output(in.length() - 4); + punycode_uint outputLen = output.size(); + + const punycode_status status = punycode_decode + (in.length() - 4, &in[4], &outputLen, &output[0], /* case_flags */ NULL); + + if (status == punycode_success) + { + std::vector <string::value_type> outUTF8Bytes(outputLen * 4); + string::value_type* p = &outUTF8Bytes[0]; + + for (std::vector <punycode_uint>::const_iterator it = output.begin() ; + it != output.begin() + outputLen ; ++it) + { + p = utf8::unchecked::append(*it, p); + } + + string outUTF8(&outUTF8Bytes[0], p); + charset::convert(outUTF8, out, vmime::charsets::UTF_8, m_dest); + } + else + { + // TODO + } + } +} + + +ref <utility::charsetFilteredOutputStream> charsetConverter_idna::getFilteredOutputStream(utility::outputStream& /* os */) +{ + return NULL; +} + + +} // vmime diff --git a/src/component.cpp b/src/component.cpp index f2d34093..b102b45d 100644 --- a/src/component.cpp +++ b/src/component.cpp @@ -57,6 +57,15 @@ void component::parse (ref <utility::inputStream> inputStream, const utility::stream::size_type position, const utility::stream::size_type end, utility::stream::size_type* newPosition) { + parse(parsingContext::getDefaultContext(), inputStream, position, end, newPosition); +} + + +void component::parse + (const parsingContext& ctx, + ref <utility::inputStream> inputStream, const utility::stream::size_type position, + const utility::stream::size_type end, utility::stream::size_type* newPosition) +{ m_parsedOffset = m_parsedLength = 0; ref <utility::seekableInputStream> seekableStream = @@ -71,14 +80,14 @@ void component::parse utility::bufferedStreamCopyRange(*inputStream, ossAdapter, position, end - position); const string buffer = oss.str(); - parseImpl(buffer, 0, buffer.length(), NULL); + parseImpl(ctx, buffer, 0, buffer.length(), NULL); } else { ref <utility::parserInputStreamAdapter> parser = vmime::create <utility::parserInputStreamAdapter>(seekableStream); - parseImpl(parser, position, end, newPosition); + parseImpl(ctx, parser, position, end, newPosition); } } @@ -87,7 +96,15 @@ void component::parse(const string& buffer) { m_parsedOffset = m_parsedLength = 0; - parseImpl(buffer, 0, buffer.length(), NULL); + parseImpl(parsingContext::getDefaultContext(), buffer, 0, buffer.length(), NULL); +} + + +void component::parse(const parsingContext& ctx, const string& buffer) +{ + m_parsedOffset = m_parsedLength = 0; + + parseImpl(ctx, buffer, 0, buffer.length(), NULL); } @@ -97,7 +114,18 @@ void component::parse { m_parsedOffset = m_parsedLength = 0; - parseImpl(buffer, position, end, newPosition); + parseImpl(parsingContext::getDefaultContext(), buffer, position, end, newPosition); +} + + +void component::parse + (const parsingContext& ctx, + const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) +{ + m_parsedOffset = m_parsedLength = 0; + + parseImpl(ctx, buffer, position, end, newPosition); } @@ -116,11 +144,14 @@ void component::offsetParsedBounds(const utility::stream::size_type offset) void component::parseImpl - (ref <utility::parserInputStreamAdapter> parser, const utility::stream::size_type position, + (const parsingContext& ctx, ref <utility::parserInputStreamAdapter> parser, + const utility::stream::size_type position, const utility::stream::size_type end, utility::stream::size_type* newPosition) { + // This is the default implementation for parsing from an input stream: + // actually, we extract the substring and use the "parse from string" implementation const std::string buffer = parser->extract(position, end); - parseImpl(buffer, 0, buffer.length(), newPosition); + parseImpl(ctx, buffer, 0, buffer.length(), newPosition); // Recursivey offset parsed bounds on children if (position != 0) @@ -132,16 +163,19 @@ void component::parseImpl void component::parseImpl - (const string& buffer, const string::size_type position, + (const parsingContext& ctx, const string& buffer, const string::size_type position, const string::size_type end, string::size_type* newPosition) { + // This is the default implementation for parsing from a string: + // actually, we encapsulate the string buffer in an input stream, then use + // the "parse from input stream" implementation ref <utility::seekableInputStream> stream = vmime::create <utility::inputStreamStringAdapter>(buffer); ref <utility::parserInputStreamAdapter> parser = vmime::create <utility::parserInputStreamAdapter>(stream); - parseImpl(parser, position, end, newPosition); + parseImpl(ctx, parser, position, end, newPosition); } @@ -151,7 +185,10 @@ const string component::generate(const string::size_type maxLineLength, std::ostringstream oss; utility::outputStreamAdapter adapter(oss); - generate(adapter, maxLineLength, curLinePos, NULL); + generationContext ctx(generationContext::getDefaultContext()); + ctx.setMaxLineLength(maxLineLength); + + generateImpl(ctx, adapter, curLinePos, NULL); return (oss.str()); } @@ -159,21 +196,21 @@ const string component::generate(const string::size_type maxLineLength, void component::generate (utility::outputStream& os, - const string::size_type maxLineLength, const string::size_type curLinePos, string::size_type* newLinePos) const { - generateImpl(os, maxLineLength, curLinePos, newLinePos); + generateImpl(generationContext::getDefaultContext(), + os, curLinePos, newLinePos); } void component::generate - (ref <utility::outputStream> os, - const string::size_type maxLineLength, + (const generationContext& ctx, + utility::outputStream& outputStream, const string::size_type curLinePos, string::size_type* newLinePos) const { - generateImpl(*os, maxLineLength, curLinePos, newLinePos); + generateImpl(ctx, outputStream, curLinePos, newLinePos); } diff --git a/src/constants.cpp b/src/constants.cpp index 9ce7189b..551d0a18 100644 --- a/src/constants.cpp +++ b/src/constants.cpp @@ -153,6 +153,8 @@ namespace charsets const string::value_type* const WINDOWS_1256 = "windows-1256"; const string::value_type* const WINDOWS_1257 = "windows-1257"; const string::value_type* const WINDOWS_1258 = "windows-1258"; + + const string::value_type* const IDNA = "idna"; } diff --git a/src/contentDisposition.cpp b/src/contentDisposition.cpp index 401e9958..300d4ee3 100644 --- a/src/contentDisposition.cpp +++ b/src/contentDisposition.cpp @@ -47,8 +47,9 @@ contentDisposition::contentDisposition(const contentDisposition& type) } -void contentDisposition::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void contentDisposition::parseImpl + (const parsingContext& /* ctx */, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { m_name = utility::stringUtils::trim(utility::stringUtils::toLower (string(buffer.begin() + position, buffer.begin() + end))); @@ -60,8 +61,9 @@ void contentDisposition::parseImpl(const string& buffer, const string::size_type } -void contentDisposition::generateImpl(utility::outputStream& os, const string::size_type /* maxLineLength */, - const string::size_type curLinePos, string::size_type* newLinePos) const +void contentDisposition::generateImpl + (const generationContext& /* ctx */, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const { os << m_name; diff --git a/src/context.cpp b/src/context.cpp new file mode 100644 index 00000000..07fe4875 --- /dev/null +++ b/src/context.cpp @@ -0,0 +1,87 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/context.hpp" + + +namespace vmime +{ + + +context::context() + : m_internationalizedEmail(false) +{ +} + + +context::context(const context& ctx) + : object(), + m_internationalizedEmail(ctx.m_internationalizedEmail) +{ +} + + +context::~context() +{ +} + + +bool context::getInternationalizedEmailSupport() const +{ + return m_internationalizedEmail; +} + + +void context::setInternationalizedEmailSupport(const bool support) +{ + m_internationalizedEmail = support; +} + + +const charsetConverterOptions& context::getCharsetConversionOptions() const +{ + return m_charsetConvOptions; +} + + +void context::setCharsetConversionOptions(const charsetConverterOptions& opts) +{ + m_charsetConvOptions = opts; +} + + +context& context::operator=(const context& ctx) +{ + copyFrom(ctx); + return *this; +} + + +void context::copyFrom(const context& ctx) +{ + m_internationalizedEmail = ctx.m_internationalizedEmail; + m_charsetConvOptions = ctx.m_charsetConvOptions; +} + + +} // vmime diff --git a/src/dateTime.cpp b/src/dateTime.cpp index f98d7c64..eaf955c3 100644 --- a/src/dateTime.cpp +++ b/src/dateTime.cpp @@ -68,8 +68,9 @@ zone = "UT" / "GMT" ; Universal Time */ -void datetime::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void datetime::parseImpl + (const parsingContext& /* ctx */, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { const string::value_type* const pend = buffer.data() + end; const string::value_type* p = buffer.data() + position; @@ -589,8 +590,9 @@ void datetime::parseImpl(const string& buffer, const string::size_type position, } -void datetime::generateImpl(utility::outputStream& os, const string::size_type /* maxLineLength */, - const string::size_type curLinePos, string::size_type* newLinePos) const +void datetime::generateImpl + (const generationContext& /* ctx */, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const { static const string::value_type* dayNames[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; diff --git a/src/disposition.cpp b/src/disposition.cpp index 7a31ed8c..c5da6e30 100644 --- a/src/disposition.cpp +++ b/src/disposition.cpp @@ -171,8 +171,9 @@ const std::vector <string> disposition::getModifierList() const } -void disposition::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void disposition::parseImpl + (const parsingContext& /* ctx */, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { // disposition-mode ";" disposition-type // [ "/" disposition-modifier *( "," disposition-modifier ) ] @@ -276,8 +277,9 @@ void disposition::parseImpl(const string& buffer, const string::size_type positi } -void disposition::generateImpl(utility::outputStream& os, const string::size_type maxLineLength, - const string::size_type curLinePos, string::size_type* newLinePos) const +void disposition::generateImpl + (const generationContext& ctx, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const { string::size_type pos = curLinePos; @@ -287,7 +289,7 @@ void disposition::generateImpl(utility::outputStream& os, const string::size_typ os << actionMode << "/" << sendingMode << ";"; pos += actionMode.length() + 1 + sendingMode.length() + 1; - if (pos > maxLineLength) + if (pos > ctx.getMaxLineLength()) { os << NEW_LINE_SEQUENCE; pos = NEW_LINE_SEQUENCE_LENGTH; diff --git a/src/emailAddress.cpp b/src/emailAddress.cpp new file mode 100644 index 00000000..09d08780 --- /dev/null +++ b/src/emailAddress.cpp @@ -0,0 +1,513 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/emailAddress.hpp" + +#include "vmime/platform.hpp" + +#include "vmime/parserHelpers.hpp" +#include "vmime/utility/outputStreamStringAdapter.hpp" +#include "vmime/utility/stringUtils.hpp" + + +namespace vmime +{ + + +emailAddress::emailAddress() +{ +} + + +emailAddress::emailAddress(const emailAddress& eml) + : component(), m_localName(eml.m_localName), m_domainName(eml.m_domainName) +{ +} + + +emailAddress::emailAddress(const string& email) +{ + parse(email); +} + + +emailAddress::emailAddress(const char* email) +{ + parse(email); +} + + +emailAddress::emailAddress(const string& localName, const string& domainName) + : component(), m_localName(word(localName, vmime::charsets::UTF_8)), + m_domainName(word(domainName, vmime::charsets::UTF_8)) +{ +} + + +emailAddress::emailAddress(const word& localName, const word& domainName) + : component(), m_localName(localName), m_domainName(domainName) +{ +} + + +void emailAddress::parseImpl + (const parsingContext& /* ctx */, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) +{ + const string::value_type* const pend = buffer.data() + end; + const string::value_type* const pstart = buffer.data() + position; + const string::value_type* p = pstart; + + enum ParserStates + { + State_Before, + State_LocalPartStart, + State_LocalPartMiddle, + State_LocalPartComment, + State_LocalPartQuoted, + State_DomainPartStart, + State_DomainPartMiddle, + State_DomainPartComment, + State_End, + State_Error + } state = State_Before; + + std::ostringstream localPart; + std::ostringstream domainPart; + + bool escapeNext = false; // for quoting + bool prevIsDot = false; + bool atFound = false; + bool stop = false; + int commentLevel = 0; + + while (p < pend && !stop) + { + const string::value_type c = *p; + + if ((localPart.str().length() + domainPart.str().length()) >= 256) + { + state = State_Error; + break; + } + + switch (state) + { + case State_Before: + + if (parserHelpers::isSpace(c)) + ++p; + else + state = State_LocalPartStart; + + case State_LocalPartStart: + + if (c == '"') + { + state = State_LocalPartQuoted; + ++p; + } + else if (c == '(') + { + state = State_LocalPartComment; + ++commentLevel; + ++p; + } + else + { + state = State_LocalPartMiddle; + localPart << c; + ++p; + } + + break; + + case State_LocalPartComment: + + if (escapeNext) + { + escapeNext = false; + ++p; + } + else if (c == '\\') + { + escapeNext = true; + ++p; + } + else if (c == '(') + { + ++commentLevel; + ++p; + } + else if (c == ')') + { + if (--commentLevel == 0) + { + // End of comment + state = State_LocalPartMiddle; + } + + ++p; + } + else + { + // Comment continues + ++p; + } + + break; + + case State_LocalPartQuoted: + + if (escapeNext) + { + escapeNext = false; + + if (c == '"' || c == '\\') + { + localPart << c; + ++p; + } + else + { + // This char cannot be escaped + state = State_Error; + } + } + else if (c == '"') + { + // End of quoted string + state = State_LocalPartMiddle; + ++p; + } + else if (c == '\\') + { + escapeNext = true; + ++p; + } + else + { + localPart << c; + ++p; + } + + break; + + case State_LocalPartMiddle: + + if (c == '.') + { + prevIsDot = true; + localPart << c; + ++p; + } + else if (c == '"' && prevIsDot) + { + prevIsDot = false; + state = State_LocalPartQuoted; + ++p; + } + else if (c == '(') + { + // By allowing comments anywhere in the local part, + // we are more permissive than RFC-2822 + state = State_LocalPartComment; + ++commentLevel; + ++p; + } + else if (c == '@') + { + atFound = true; + state = State_DomainPartStart; + ++p; + } + else if (parserHelpers::isSpace(c)) + { + // Allow not specifying domain part + state = State_End; + } + else + { + prevIsDot = false; + localPart << c; + ++p; + } + + break; + + case State_DomainPartStart: + + if (c == '(') + { + state = State_DomainPartComment; + ++commentLevel; + ++p; + } + else + { + state = State_DomainPartMiddle; + domainPart << c; + ++p; + } + + break; + + case State_DomainPartMiddle: + + if (parserHelpers::isSpace(c)) + { + state = State_End; + } + else if (c == '(') + { + // By allowing comments anywhere in the domain part, + // we are more permissive than RFC-2822 + state = State_DomainPartComment; + ++commentLevel; + ++p; + } + else + { + domainPart << c; + ++p; + } + + break; + + case State_DomainPartComment: + + if (escapeNext) + { + escapeNext = false; + ++p; + } + else if (c == '\\') + { + escapeNext = true; + ++p; + } + else if (c == '(') + { + ++commentLevel; + ++p; + } + else if (c == ')') + { + if (--commentLevel == 0) + { + // End of comment + state = State_DomainPartMiddle; + } + + ++p; + } + else + { + // Comment continues + ++p; + } + + break; + + case State_End: + case State_Error: + + stop = true; + break; + } + } + + if (p == pend && state != State_Error) + { + if (state == State_DomainPartMiddle) + state = State_End; + else if (state == State_LocalPartMiddle) + state = State_End; // allow not specifying domain part + } + + if (state != State_End) + { + m_localName = word("invalid", vmime::charsets::UTF_8); + m_domainName = word("invalid", vmime::charsets::UTF_8); + } + else + { + // If the domain part is missing, use local host name + if (domainPart.str().empty() && !atFound) + domainPart << platform::getHandler()->getHostName(); + + m_localName = word(localPart.str(), vmime::charsets::UTF_8); + m_domainName = word(domainPart.str(), vmime::charsets::UTF_8); + } + + setParsedBounds(position, p - pend); + + if (newPosition) + *newPosition = p - pend; +} + + +static const string domainNameToIDNA(const string& domainName) +{ + std::ostringstream idnaDomain; + string::size_type p = 0; + + for (string::size_type n = domainName.find('.', p) ; + (n = domainName.find('.', p)) != string::npos ; p = n + 1) + { + string idnaPart; + charset::convert(string(domainName.begin() + p, domainName.begin() + n), + idnaPart, vmime::charsets::UTF_8, vmime::charsets::IDNA); + + idnaDomain << idnaPart << '.'; + } + + if (p < domainName.length()) + { + string idnaPart; + charset::convert(string(domainName.begin() + p, domainName.end()), + idnaPart, vmime::charsets::UTF_8, vmime::charsets::IDNA); + + idnaDomain << idnaPart; + } + + return idnaDomain.str(); +} + + +void emailAddress::generateImpl + (const generationContext& ctx, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const +{ + string localPart, domainPart; + + if (ctx.getInternationalizedEmailSupport() && + (!utility::stringUtils::is7bit(m_localName.getBuffer()) || + !utility::stringUtils::is7bit(m_domainName.getBuffer()))) + { + // Local part + string localPartUTF8(m_localName.getConvertedText(vmime::charsets::UTF_8)); + word localPartWord(localPartUTF8, vmime::charsets::UTF_8); + + vmime::utility::outputStreamStringAdapter os(localPart); + localPartWord.generate(ctx, os, 0, NULL, text::FORCE_NO_ENCODING | text::QUOTE_IF_NEEDED, NULL); + + // Domain part + domainPart = m_domainName.getConvertedText(vmime::charsets::UTF_8); + } + else + { + // Local part + vmime::utility::outputStreamStringAdapter os(localPart); + m_localName.generate(ctx, os, 0, NULL, text::QUOTE_IF_NEEDED, NULL); + + // Domain part as IDNA + domainPart = domainNameToIDNA(m_domainName.getConvertedText(vmime::charsets::UTF_8)); + } + + os << localPart + << "@" + << domainPart; + + if (newLinePos) + { + *newLinePos = curLinePos + + localPart.length() + + 1 // @ + + domainPart.length(); + } +} + + +bool emailAddress::operator==(const class emailAddress& eml) const +{ + return (m_localName == eml.m_localName && + m_domainName == eml.m_domainName); +} + + +bool emailAddress::operator!=(const class emailAddress& eml) const +{ + return !(*this == eml); +} + + +void emailAddress::copyFrom(const component& other) +{ + const emailAddress& source = dynamic_cast <const emailAddress&>(other); + + m_localName = source.m_localName; + m_domainName = source.m_domainName; +} + + +emailAddress& emailAddress::operator=(const emailAddress& other) +{ + copyFrom(other); + return (*this); +} + + +ref <component>emailAddress::clone() const +{ + return vmime::create <emailAddress>(*this); +} + + +const word& emailAddress::getLocalName() const +{ + return m_localName; +} + + +void emailAddress::setLocalName(const word& localName) +{ + m_localName = localName; +} + + +const word& emailAddress::getDomainName() const +{ + return m_domainName; +} + + +void emailAddress::setDomainName(const word& domainName) +{ + m_domainName = domainName; +} + + +const std::vector <ref <component> > emailAddress::getChildComponents() +{ + return std::vector <ref <component> >(); +} + + +bool emailAddress::isEmpty() const +{ + return m_localName.isEmpty(); +} + + +} // vmime diff --git a/src/encoding.cpp b/src/encoding.cpp index 49d78b75..53f88531 100644 --- a/src/encoding.cpp +++ b/src/encoding.cpp @@ -61,8 +61,9 @@ encoding::encoding(const encoding& enc) } -void encoding::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void encoding::parseImpl + (const parsingContext& /* ctx */, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { m_usage = USAGE_UNKNOWN; @@ -80,8 +81,9 @@ void encoding::parseImpl(const string& buffer, const string::size_type position, } -void encoding::generateImpl(utility::outputStream& os, const string::size_type /* maxLineLength */, - const string::size_type curLinePos, string::size_type* newLinePos) const +void encoding::generateImpl + (const generationContext& /* ctx */, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const { os << m_name; diff --git a/src/generationContext.cpp b/src/generationContext.cpp new file mode 100644 index 00000000..0f19e623 --- /dev/null +++ b/src/generationContext.cpp @@ -0,0 +1,109 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 3 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/generationContext.hpp" + + +namespace vmime +{ + + +generationContext::generationContext() + : m_maxLineLength(lineLengthLimits::convenient), + m_prologText("This is a multi-part message in MIME format. Your mail reader " \ + "does not understand MIME message format."), + m_epilogText("") +{ +} + + +generationContext::generationContext(const generationContext& ctx) + : context(ctx), + m_maxLineLength(ctx.m_maxLineLength), + m_prologText(ctx.m_prologText), + m_epilogText(ctx.m_epilogText) +{ +} + + +generationContext& generationContext::getDefaultContext() +{ + static generationContext ctx; + return ctx; +} + + +string::size_type generationContext::getMaxLineLength() const +{ + return m_maxLineLength; +} + + +void generationContext::setMaxLineLength(const string::size_type maxLineLength) +{ + m_maxLineLength = maxLineLength; +} + + +const string generationContext::getPrologText() const +{ + return m_prologText; +} + + +void generationContext::setPrologText(const string& prologText) +{ + m_prologText = prologText; +} + + +const string generationContext::getEpilogText() const +{ + return m_epilogText; +} + + +void generationContext::setEpilogText(const string& epilogText) +{ + m_epilogText = epilogText; +} + + +generationContext& generationContext::operator=(const generationContext& ctx) +{ + copyFrom(ctx); + return *this; +} + + +void generationContext::copyFrom(const generationContext& ctx) +{ + context::copyFrom(ctx); + + m_maxLineLength = ctx.m_maxLineLength; + m_prologText = ctx.m_prologText; + m_epilogText = ctx.m_epilogText; +} + + +} // vmime diff --git a/src/header.cpp b/src/header.cpp index 6543a302..d1896d96 100644 --- a/src/header.cpp +++ b/src/header.cpp @@ -61,8 +61,9 @@ field-body-contents = specials tokens, or else consisting of texts> */ -void header::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void header::parseImpl + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { string::size_type pos = position; @@ -70,7 +71,7 @@ void header::parseImpl(const string& buffer, const string::size_type position, while (pos < end) { - ref <headerField> field = headerField::parseNext(buffer, pos, end, &pos); + ref <headerField> field = headerField::parseNext(ctx, buffer, pos, end, &pos); if (field == NULL) break; m_fields.push_back(field); @@ -83,14 +84,15 @@ void header::parseImpl(const string& buffer, const string::size_type position, } -void header::generateImpl(utility::outputStream& os, const string::size_type maxLineLength, - const string::size_type /* curLinePos */, string::size_type* newLinePos) const +void header::generateImpl + (const generationContext& ctx, utility::outputStream& os, + const string::size_type /* curLinePos */, string::size_type* newLinePos) const { // Generate the fields for (std::vector <ref <headerField> >::const_iterator it = m_fields.begin() ; it != m_fields.end() ; ++it) { - (*it)->generate(os, maxLineLength); + (*it)->generate(ctx, os); os << CRLF; } diff --git a/src/headerField.cpp b/src/headerField.cpp index 3d0f8834..0a17abac 100644 --- a/src/headerField.cpp +++ b/src/headerField.cpp @@ -73,8 +73,9 @@ headerField& headerField::operator=(const headerField& other) } -ref <headerField> headerField::parseNext(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +ref <headerField> headerField::parseNext + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { string::size_type pos = position; @@ -215,7 +216,7 @@ ref <headerField> headerField::parseNext(const string& buffer, const string::siz // Return a new field ref <headerField> field = headerFieldFactory::getInstance()->create(name); - field->parse(buffer, contentsStart, contentsEnd, NULL); + field->parse(ctx, buffer, contentsStart, contentsEnd, NULL); field->setParsedBounds(nameStart, pos); if (newPosition) @@ -262,19 +263,21 @@ ref <headerField> headerField::parseNext(const string& buffer, const string::siz } -void headerField::parseImpl(const string& buffer, const string::size_type position, const string::size_type end, - string::size_type* newPosition) +void headerField::parseImpl + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { - m_value->parse(buffer, position, end, newPosition); + m_value->parse(ctx, buffer, position, end, newPosition); } -void headerField::generateImpl(utility::outputStream& os, const string::size_type maxLineLength, - const string::size_type curLinePos, string::size_type* newLinePos) const +void headerField::generateImpl + (const generationContext& ctx, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const { os << m_name + ": "; - m_value->generate(os, maxLineLength, curLinePos + m_name.length() + 2, newLinePos); + m_value->generate(ctx, os, curLinePos + m_name.length() + 2, newLinePos); } diff --git a/src/mailbox.cpp b/src/mailbox.cpp index a9d18958..1c199a76 100644 --- a/src/mailbox.cpp +++ b/src/mailbox.cpp @@ -23,6 +23,7 @@ #include "vmime/mailbox.hpp" #include "vmime/parserHelpers.hpp" +#include "vmime/utility/outputStreamStringAdapter.hpp" namespace vmime @@ -40,13 +41,13 @@ mailbox::mailbox(const mailbox& mbox) } -mailbox::mailbox(const string& email) +mailbox::mailbox(const emailAddress& email) : m_email(email) { } -mailbox::mailbox(const text& name, const string& email) +mailbox::mailbox(const text& name, const emailAddress& email) : m_name(name), m_email(email) { } @@ -65,8 +66,9 @@ angle-addr = [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr */ -void mailbox::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void mailbox::parseImpl + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { const string::value_type* const pend = buffer.data() + end; const string::value_type* const pstart = buffer.data() + position; @@ -313,27 +315,13 @@ void mailbox::parseImpl(const string& buffer, const string::size_type position, // (email address is mandatory, whereas name is optional). if (address.empty() && !name.empty() && !hadBrackets) { - m_email.clear(); - m_email.reserve(name.size()); m_name.removeAllWords(); - - for (string::size_type i = 0 ; i < name.size() ; ++i) - { - if (!parserHelpers::isSpace(name[i])) - m_email += name[i]; - } + m_email.parse(ctx, name); } else { - text::decodeAndUnfold(name, &m_name); - m_email.clear(); - m_email.reserve(address.size()); - - for (string::size_type i = 0 ; i < address.size() ; ++i) - { - if (!parserHelpers::isSpace(address[i])) - m_email += address[i]; - } + text::decodeAndUnfold(ctx, name, &m_name); + m_email.parse(ctx, address); } setParsedBounds(position, position + (p - pstart)); @@ -343,28 +331,30 @@ void mailbox::parseImpl(const string& buffer, const string::size_type position, } -void mailbox::generateImpl(utility::outputStream& os, const string::size_type maxLineLength, - const string::size_type curLinePos, string::size_type* newLinePos) const +void mailbox::generateImpl + (const generationContext& ctx, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const { + string generatedEmail; + utility::outputStreamStringAdapter generatedEmailStream(generatedEmail); + m_email.generate(ctx, generatedEmailStream, 0, NULL); + if (m_name.isEmpty()) { - bool newLine = false; + string::size_type pos = curLinePos; // No display name is specified, only email address. - if (curLinePos /* + 2 */ + m_email.length() > maxLineLength) + if (curLinePos + generatedEmail.length() > ctx.getMaxLineLength()) { os << NEW_LINE_SEQUENCE; - newLine = true; + pos = NEW_LINE_SEQUENCE.length(); } - //os << "<" << m_email << ">"; - os << m_email; + os << generatedEmail; + pos += generatedEmail.length(); if (newLinePos) - { - *newLinePos = curLinePos + m_email.length() /* + 2 */; - if (newLine) *newLinePos += 1; - } + *newLinePos = pos; } else { @@ -415,24 +405,21 @@ void mailbox::generateImpl(utility::outputStream& os, const string::size_type ma } string::size_type pos = curLinePos; - bool newLine = true; - m_name.encodeAndFold(os, maxLineLength, pos, &pos, + m_name.encodeAndFold(ctx, os, pos, &pos, text::QUOTE_IF_POSSIBLE | (forceEncode ? text::FORCE_ENCODING : 0)); - if (pos + m_email.length() + 3 > maxLineLength) + if (pos + generatedEmail.length() + 3 > ctx.getMaxLineLength()) { os << NEW_LINE_SEQUENCE; - newLine = true; + pos = NEW_LINE_SEQUENCE.length(); } - os << " <" << m_email << ">"; + os << " <" << generatedEmail << ">"; + pos += 2 + generatedEmail.length() + 1; if (newLinePos) - { - *newLinePos = pos + m_email.length() + 3; - if (newLine) *newLinePos += NEW_LINE_SEQUENCE.length(); - } + *newLinePos = pos; } } @@ -473,14 +460,14 @@ ref <component>mailbox::clone() const bool mailbox::isEmpty() const { - return (m_email.empty()); + return m_email.isEmpty(); } void mailbox::clear() { m_name.removeAllWords(); - m_email.clear(); + m_email = emailAddress(); } @@ -502,13 +489,13 @@ void mailbox::setName(const text& name) } -const string& mailbox::getEmail() const +const emailAddress& mailbox::getEmail() const { return (m_email); } -void mailbox::setEmail(const string& email) +void mailbox::setEmail(const emailAddress& email) { m_email = email; } diff --git a/src/mailboxField.cpp b/src/mailboxField.cpp index c3c5214e..1f11f49c 100644 --- a/src/mailboxField.cpp +++ b/src/mailboxField.cpp @@ -43,15 +43,16 @@ mailboxField::mailboxField(const mailboxField&) } -void mailboxField::parse(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void mailboxField::parse + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { ref <mailbox> mbox = vmime::create <mailbox>(); // Here, we cannot simply call "m_mailbox.parse()" because it // may have more than one address specified (even if this field // should contain only one). We are never too much careful... - ref <address> parsedAddress = address::parseNext(buffer, position, end, newPosition); + ref <address> parsedAddress = address::parseNext(ctx, buffer, position, end, newPosition); if (parsedAddress) { diff --git a/src/mailboxGroup.cpp b/src/mailboxGroup.cpp index 251f920b..65611b33 100644 --- a/src/mailboxGroup.cpp +++ b/src/mailboxGroup.cpp @@ -54,8 +54,9 @@ mailboxGroup::~mailboxGroup() } -void mailboxGroup::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void mailboxGroup::parseImpl + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { const string::value_type* const pend = buffer.data() + end; const string::value_type* const pstart = buffer.data() + position; @@ -80,7 +81,7 @@ void mailboxGroup::parseImpl(const string& buffer, const string::size_type posit while (pos < end) { - ref <address> parsedAddress = address::parseNext(buffer, pos, end, &pos); + ref <address> parsedAddress = address::parseNext(ctx, buffer, pos, end, &pos); if (parsedAddress) { @@ -102,7 +103,7 @@ void mailboxGroup::parseImpl(const string& buffer, const string::size_type posit } } - text::decodeAndUnfold(name, &m_name); + text::decodeAndUnfold(ctx, name, &m_name); setParsedBounds(position, end); @@ -111,8 +112,9 @@ void mailboxGroup::parseImpl(const string& buffer, const string::size_type posit } -void mailboxGroup::generateImpl(utility::outputStream& os, const string::size_type maxLineLength, - const string::size_type curLinePos, string::size_type* newLinePos) const +void mailboxGroup::generateImpl + (const generationContext& ctx, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const { // We have to encode the name: // - if it contains characters in a charset different from "US-ASCII", @@ -156,7 +158,10 @@ void mailboxGroup::generateImpl(utility::outputStream& os, const string::size_ty string::size_type pos = curLinePos; - m_name.encodeAndFold(os, maxLineLength - 2, pos, &pos, + generationContext tmpCtx(ctx); + tmpCtx.setMaxLineLength(ctx.getMaxLineLength() - 2); + + m_name.encodeAndFold(ctx, os, pos, &pos, forceEncode ? text::FORCE_ENCODING : 0); os << ":"; @@ -176,7 +181,7 @@ void mailboxGroup::generateImpl(utility::outputStream& os, const string::size_ty ++pos; } - (*it)->generate(os, maxLineLength - 2, pos, &pos); + (*it)->generate(tmpCtx, os, pos, &pos); } os << ";"; diff --git a/src/mailboxList.cpp b/src/mailboxList.cpp index b3106fb8..03a225f8 100644 --- a/src/mailboxList.cpp +++ b/src/mailboxList.cpp @@ -196,17 +196,18 @@ const std::vector <ref <component> > mailboxList::getChildComponents() } -void mailboxList::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void mailboxList::parseImpl + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { - m_list.parse(buffer, position, end, newPosition); + m_list.parse(ctx, buffer, position, end, newPosition); } -void mailboxList::generateImpl(utility::outputStream& os, const string::size_type maxLineLength, +void mailboxList::generateImpl(const generationContext& ctx, utility::outputStream& os, const string::size_type curLinePos, string::size_type* newLinePos) const { - m_list.generate(os, maxLineLength, curLinePos, newLinePos); + m_list.generate(ctx, os, curLinePos, newLinePos); } diff --git a/src/mdn/MDNHelper.cpp b/src/mdn/MDNHelper.cpp index 533813b1..a0d48599 100644 --- a/src/mdn/MDNHelper.cpp +++ b/src/mdn/MDNHelper.cpp @@ -269,7 +269,7 @@ ref <bodyPart> MDNHelper::createSecondMDNPart(const sendableMDNInfos& mdnInfos, ref <headerField> fr = headerFieldFactory::getInstance()-> create(vmime::fields::FINAL_RECIPIENT); - fr->setValue("rfc822; " + mdnInfos.getRecipient().getEmail()); + fr->setValue("rfc822; " + mdnInfos.getRecipient().getEmail().generate()); fields.appendField(fr); diff --git a/src/mediaType.cpp b/src/mediaType.cpp index 92e8d058..62d65c23 100644 --- a/src/mediaType.cpp +++ b/src/mediaType.cpp @@ -48,8 +48,9 @@ mediaType::mediaType(const string& type, const string& subType) } -void mediaType::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void mediaType::parseImpl + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { const string::value_type* const pend = buffer.data() + end; const string::value_type* const pstart = buffer.data() + position; @@ -82,12 +83,13 @@ void mediaType::parseImpl(const string& buffer, const string::size_type position } -void mediaType::generateImpl(utility::outputStream& os, const string::size_type maxLineLength, - const string::size_type curLinePos, string::size_type* newLinePos) const +void mediaType::generateImpl + (const generationContext& ctx, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const { const string value = m_type + "/" + m_subType; - if (curLinePos + value.length() > maxLineLength) + if (curLinePos + value.length() > ctx.getMaxLineLength()) { os << NEW_LINE_SEQUENCE; os << value; diff --git a/src/message.cpp b/src/message.cpp index c52d54c2..75016c36 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -22,7 +22,6 @@ // #include "vmime/message.hpp" -#include "vmime/options.hpp" #include "vmime/utility/outputStreamAdapter.hpp" @@ -38,37 +37,10 @@ message::message() } -void message::generate(utility::outputStream& os, const string::size_type maxLineLength, - const string::size_type curLinePos, string::size_type* newLinePos) const -{ - // We override this function to change the default value for the - // "maxLineLength" parameter. So, the user will just have to call - // message::generate() without any argument to use the maximum line - // length specified in vmime::options... - bodyPart::generate(os, maxLineLength, curLinePos, newLinePos); -} - - const string message::generate(const string::size_type maxLineLength, const string::size_type curLinePos) const { - std::ostringstream oss; - utility::outputStreamAdapter adapter(oss); - - generate(adapter, maxLineLength, curLinePos, NULL); - - return (oss.str()); -} - - - -void message::generate - (ref <utility::outputStream> os, - const string::size_type maxLineLength, - const string::size_type curLinePos, - string::size_type* newLinePos) const -{ - bodyPart::generate(os, maxLineLength, curLinePos, newLinePos); + return bodyPart::generate(maxLineLength, curLinePos); } diff --git a/src/messageId.cpp b/src/messageId.cpp index 3102294e..6b558e1b 100644 --- a/src/messageId.cpp +++ b/src/messageId.cpp @@ -61,8 +61,9 @@ messageId::messageId(const string& left, const string& right) msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS] */ -void messageId::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void messageId::parseImpl + (const parsingContext& /* ctx */, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { const string::value_type* const pend = buffer.data() + end; const string::value_type* const pstart = buffer.data() + position; @@ -145,8 +146,9 @@ void messageId::parseImpl(const string& buffer, const string::size_type position } -ref <messageId> messageId::parseNext(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +ref <messageId> messageId::parseNext + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { string::size_type pos = position; @@ -161,7 +163,7 @@ ref <messageId> messageId::parseNext(const string& buffer, const string::size_ty ++pos; ref <messageId> mid = vmime::create <messageId>(); - mid->parse(buffer, begin, pos, NULL); + mid->parse(ctx, buffer, begin, pos, NULL); if (newPosition != NULL) *newPosition = pos; @@ -185,12 +187,13 @@ const string messageId::getId() const } -void messageId::generateImpl(utility::outputStream& os, const string::size_type maxLineLength, - const string::size_type curLinePos, string::size_type* newLinePos) const +void messageId::generateImpl + (const generationContext& ctx, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const { string::size_type pos = curLinePos; - if (curLinePos + m_left.length() + m_right.length() + 3 > maxLineLength) + if (curLinePos + m_left.length() + m_right.length() + 3 > ctx.getMaxLineLength()) { os << NEW_LINE_SEQUENCE; pos = NEW_LINE_SEQUENCE_LENGTH; diff --git a/src/messageIdSequence.cpp b/src/messageIdSequence.cpp index a255235a..99c96319 100644 --- a/src/messageIdSequence.cpp +++ b/src/messageIdSequence.cpp @@ -84,8 +84,9 @@ const std::vector <ref <component> > messageIdSequence::getChildComponents() } -void messageIdSequence::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void messageIdSequence::parseImpl + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { removeAllMessageIds(); @@ -93,7 +94,7 @@ void messageIdSequence::parseImpl(const string& buffer, const string::size_type while (pos < end) { - ref <messageId> parsedMid = messageId::parseNext(buffer, pos, end, &pos); + ref <messageId> parsedMid = messageId::parseNext(ctx, buffer, pos, end, &pos); if (parsedMid != NULL) m_list.push_back(parsedMid); @@ -106,16 +107,20 @@ void messageIdSequence::parseImpl(const string& buffer, const string::size_type } -void messageIdSequence::generateImpl(utility::outputStream& os, const string::size_type maxLineLength, - const string::size_type curLinePos, string::size_type* newLinePos) const +void messageIdSequence::generateImpl + (const generationContext& ctx, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const { string::size_type pos = curLinePos; if (!m_list.empty()) { + generationContext tmpCtx(ctx); + tmpCtx.setMaxLineLength(ctx.getMaxLineLength() - 2); + for (std::vector <ref <messageId> >::const_iterator it = m_list.begin() ; ; ) { - (*it)->generate(os, maxLineLength - 2, pos, &pos); + (*it)->generate(ctx, os, pos, &pos); if (++it == m_list.end()) break; diff --git a/src/net/sendmail/sendmailTransport.cpp b/src/net/sendmail/sendmailTransport.cpp index 181d0d7f..dbbb55a8 100644 --- a/src/net/sendmail/sendmailTransport.cpp +++ b/src/net/sendmail/sendmailTransport.cpp @@ -152,11 +152,11 @@ void sendmailTransport::send args.push_back("-i"); args.push_back("-f"); - args.push_back(expeditor.getEmail()); + args.push_back(expeditor.getEmail().generate()); args.push_back("--"); for (int i = 0 ; i < recipients.getMailboxCount() ; ++i) - args.push_back(recipients.getMailboxAt(i)->getEmail()); + args.push_back(recipients.getMailboxAt(i)->getEmail().generate()); // Call sendmail try diff --git a/src/net/smtp/SMTPCommand.cpp b/src/net/smtp/SMTPCommand.cpp index f338e248..99a3ac17 100644 --- a/src/net/smtp/SMTPCommand.cpp +++ b/src/net/smtp/SMTPCommand.cpp @@ -32,6 +32,7 @@ #include "vmime/net/socket.hpp" #include "vmime/mailbox.hpp" +#include "vmime/utility/outputStreamAdapter.hpp" namespace vmime { @@ -90,7 +91,12 @@ ref <SMTPCommand> SMTPCommand::MAIL(const mailbox& mbox) { std::ostringstream cmd; cmd.imbue(std::locale::classic()); - cmd << "MAIL FROM:<" << mbox.getEmail() << ">"; + cmd << "MAIL FROM:<"; + + vmime::utility::outputStreamAdapter cmd2(cmd); + mbox.getEmail().generate(cmd2); + + cmd << ">"; return createCommand(cmd.str()); } @@ -101,7 +107,12 @@ ref <SMTPCommand> SMTPCommand::RCPT(const mailbox& mbox) { std::ostringstream cmd; cmd.imbue(std::locale::classic()); - cmd << "RCPT TO:<" << mbox.getEmail() << ">"; + cmd << "RCPT TO:<"; + + vmime::utility::outputStreamAdapter cmd2(cmd); + mbox.getEmail().generate(cmd2); + + cmd << ">"; return createCommand(cmd.str()); } diff --git a/src/parameter.cpp b/src/parameter.cpp index fd39c641..37a59890 100644 --- a/src/parameter.cpp +++ b/src/parameter.cpp @@ -113,18 +113,23 @@ void parameter::setValue(const word& value) } -void parameter::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void parameter::parseImpl + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { m_value->setBuffer(string(buffer.begin() + position, buffer.begin() + end)); - m_value->setCharset(charset(charsets::US_ASCII)); + + if (ctx.getInternationalizedEmailSupport()) + m_value->setCharset(charset(charsets::UTF_8)); + else + m_value->setCharset(charset(charsets::US_ASCII)); if (newPosition) *newPosition = end; } -void parameter::parse(const std::vector <valueChunk>& chunks) +void parameter::parse(const parsingContext& ctx, const std::vector <valueChunk>& chunks) { bool foundCharsetChunk = false; @@ -236,7 +241,7 @@ void parameter::parse(const std::vector <valueChunk>& chunks) // if the data is not encoded, because it can recover // from parsing errors. vmime::text t; - t.parse(chunk.data); + t.parse(ctx, chunk.data); if (t.getWordCount() != 0) { @@ -253,8 +258,9 @@ void parameter::parse(const std::vector <valueChunk>& chunks) } -void parameter::generateImpl(utility::outputStream& os, const string::size_type maxLineLength, - const string::size_type curLinePos, string::size_type* newLinePos) const +void parameter::generateImpl + (const generationContext& ctx, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const { const string& name = m_name; const string& value = m_value->getBuffer(); @@ -276,7 +282,7 @@ void parameter::generateImpl(utility::outputStream& os, const string::size_type string::size_type pos = curLinePos; - if (pos + name.length() + 10 + value.length() > maxLineLength) + if (pos + name.length() + 10 + value.length() > ctx.getMaxLineLength()) { sevenBitStream << NEW_LINE_SEQUENCE; pos = NEW_LINE_SEQUENCE_LENGTH; @@ -287,7 +293,7 @@ void parameter::generateImpl(utility::outputStream& os, const string::size_type string::size_type valueLength = 0; // Use worst-case length name.length()+2 for 'name=' part of line - for (string::size_type i = 0 ; (i < value.length()) && (pos + name.length() + 2 + valueLength < maxLineLength - 4) ; ++i, ++valueLength) + for (string::size_type i = 0 ; (i < value.length()) && (pos + name.length() + 2 + valueLength < ctx.getMaxLineLength() - 4) ; ++i, ++valueLength) { switch (value[i]) { @@ -431,7 +437,7 @@ void parameter::generateImpl(utility::outputStream& os, const string::size_type name.length() + 4 /* *0*= */ + 2 /* '' */ + m_value->getCharset().getName().length(); - if (pos + firstSectionLength + 5 >= maxLineLength) + if (pos + firstSectionLength + 5 >= ctx.getMaxLineLength()) { os << NEW_LINE_SEQUENCE; pos = NEW_LINE_SEQUENCE_LENGTH; @@ -448,7 +454,7 @@ void parameter::generateImpl(utility::outputStream& os, const string::size_type { // Check whether we should start a new line (taking into // account the next character will be encoded = worst case) - if (currentSectionLength + 3 >= maxLineLength) + if (currentSectionLength + 3 >= ctx.getMaxLineLength()) { sectionText.push_back(currentSection); sectionCount++; diff --git a/src/parameterizedHeaderField.cpp b/src/parameterizedHeaderField.cpp index 77d732df..619fe7cf 100644 --- a/src/parameterizedHeaderField.cpp +++ b/src/parameterizedHeaderField.cpp @@ -78,8 +78,9 @@ struct paramInfo #endif // VMIME_BUILDING_DOC -void parameterizedHeaderField::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void parameterizedHeaderField::parseImpl + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { const string::value_type* const pend = buffer.data() + end; const string::value_type* const pstart = buffer.data() + position; @@ -108,7 +109,7 @@ void parameterizedHeaderField::parseImpl(const string& buffer, const string::siz --valueLength; // Parse value - getValue()->parse(buffer, valueStart, valueStart + valueLength); + getValue()->parse(ctx, buffer, valueStart, valueStart + valueLength); // Reset parameters removeAllParameters(); @@ -316,7 +317,7 @@ void parameterizedHeaderField::parseImpl(const string& buffer, const string::siz // Append this parameter to the list ref <parameter> param = vmime::create <parameter>((*it).first); - param->parse(info.value); + param->parse(ctx, info.value); param->setParsedBounds(info.start, info.end); appendParameter(param); @@ -328,13 +329,14 @@ void parameterizedHeaderField::parseImpl(const string& buffer, const string::siz } -void parameterizedHeaderField::generateImpl(utility::outputStream& os, const string::size_type maxLineLength, - const string::size_type curLinePos, string::size_type* newLinePos) const +void parameterizedHeaderField::generateImpl + (const generationContext& ctx, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const { string::size_type pos = curLinePos; // Parent header field - headerField::generateImpl(os, maxLineLength, pos, &pos); + headerField::generateImpl(ctx, os, pos, &pos); // Parameters for (std::vector <ref <parameter> >::const_iterator @@ -343,7 +345,7 @@ void parameterizedHeaderField::generateImpl(utility::outputStream& os, const str os << "; "; pos += 2; - (*it)->generate(os, maxLineLength, pos, &pos); + (*it)->generate(ctx, os, pos, &pos); } if (newLinePos) diff --git a/src/options.cpp b/src/parsingContext.cpp index 9ec6056b..b440ef1e 100644 --- a/src/options.cpp +++ b/src/parsingContext.cpp @@ -21,49 +21,28 @@ // the GNU General Public License cover the whole combination. // -#include "vmime/options.hpp" +#include "vmime/parsingContext.hpp" namespace vmime { -options* options::getInstance() +parsingContext::parsingContext() { - static options instance; - return (&instance); } -options::multipartOptions::multipartOptions() - : m_prologText("This is a multi-part message in MIME format. Your mail reader " \ - "does not understand MIME message format."), - m_epilogText("") +parsingContext::parsingContext(const parsingContext& ctx) + : context() { } -const string& options::multipartOptions::getPrologText() const +parsingContext& parsingContext::getDefaultContext() { - return (m_prologText); -} - - -void options::multipartOptions::setPrologText(const string& prologText) -{ - m_prologText = prologText; -} - - -const string& options::multipartOptions::getEpilogText() const -{ - return (m_epilogText); -} - - -void options::multipartOptions::setEpilogText(const string& epilogText) -{ - m_epilogText = epilogText; + static parsingContext ctx; + return ctx; } diff --git a/src/path.cpp b/src/path.cpp index 6fe3e7aa..3e6e7a84 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -112,8 +112,9 @@ const std::vector <ref <component> > path::getChildComponents() } -void path::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void path::parseImpl + (const parsingContext& /* ctx */, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { string::size_type pos = position; @@ -165,8 +166,9 @@ void path::parseImpl(const string& buffer, const string::size_type position, } -void path::generateImpl(utility::outputStream& os, const string::size_type /* maxLineLength */, - const string::size_type curLinePos, string::size_type* newLinePos) const +void path::generateImpl + (const generationContext& /* ctx */, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const { if (m_localPart.empty() && m_domain.empty()) { diff --git a/src/relay.cpp b/src/relay.cpp index 2262fa7e..90957dbe 100644 --- a/src/relay.cpp +++ b/src/relay.cpp @@ -24,6 +24,7 @@ #include "vmime/relay.hpp" #include "vmime/text.hpp" #include "vmime/parserHelpers.hpp" +#include "vmime/utility/outputStreamAdapter.hpp" #include <sstream> @@ -57,8 +58,9 @@ relay::relay(const relay& r) ["for" addr-spec] ; initial form */ -void relay::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void relay::parseImpl + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { const string::value_type* const pend = buffer.data() + end; const string::value_type* const pstart = buffer.data() + position; @@ -71,7 +73,7 @@ void relay::parseImpl(const string& buffer, const string::size_type position, if (p >= pstart) { // Parse the date/time part - m_date.parse(buffer, position + (p - pstart) + 1, end); + m_date.parse(ctx, buffer, position + (p - pstart) + 1, end); // Parse the components std::istringstream iss(string @@ -198,8 +200,9 @@ void relay::parseImpl(const string& buffer, const string::size_type position, } -void relay::generateImpl(utility::outputStream& os, const string::size_type maxLineLength, - const string::size_type curLinePos, string::size_type* newLinePos) const +void relay::generateImpl + (const generationContext& ctx, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const { std::ostringstream oss; int count = 0; @@ -217,9 +220,12 @@ void relay::generateImpl(utility::outputStream& os, const string::size_type maxL if (m_id.length()) oss << (count++ > 0 ? " " : "") << "id " << m_id; if (m_for.length()) oss << (count++ > 0 ? " " : "") << "for " << m_for; - oss << "; " << m_date.generate(); + oss << "; "; - text(oss.str()).encodeAndFold(os, maxLineLength, + vmime::utility::outputStreamAdapter dos(oss); + m_date.generate(ctx, dos, 0, NULL); + + text(oss.str()).encodeAndFold(ctx, os, curLinePos, newLinePos, text::FORCE_NO_ENCODING); } diff --git a/src/text.cpp b/src/text.cpp index 1ba83101..d1ae6075 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -67,14 +67,15 @@ text::~text() } -void text::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void text::parseImpl + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { removeAllWords(); string::size_type newPos; - const std::vector <ref <word> > words = word::parseMultiple(buffer, position, end, &newPos); + const std::vector <ref <word> > words = word::parseMultiple(ctx, buffer, position, end, &newPos); copy_vector(words, m_words); @@ -85,10 +86,11 @@ void text::parseImpl(const string& buffer, const string::size_type position, } -void text::generateImpl(utility::outputStream& os, const string::size_type maxLineLength, - const string::size_type curLinePos, string::size_type* newLinePos) const +void text::generateImpl + (const generationContext& ctx, utility::outputStream& os, + const string::size_type curLinePos, string::size_type* newLinePos) const { - encodeAndFold(os, maxLineLength, curLinePos, newLinePos, 0); + encodeAndFold(ctx, os, curLinePos, newLinePos, 0); } @@ -142,12 +144,12 @@ bool text::operator!=(const text& t) const } -const string text::getConvertedText(const charset& dest) const +const string text::getConvertedText(const charset& dest, const charsetConverterOptions& opts) const { string out; for (std::vector <ref <word> >::const_iterator i = m_words.begin() ; i != m_words.end() ; ++i) - out += (*i)->getConvertedText(dest); + out += (*i)->getConvertedText(dest, opts); return (out); } @@ -348,15 +350,16 @@ void text::createFromString(const string& in, const charset& ch) } -void text::encodeAndFold(utility::outputStream& os, const string::size_type maxLineLength, - const string::size_type firstLineOffset, string::size_type* lastLineLength, const int flags) const +void text::encodeAndFold + (const generationContext& ctx, utility::outputStream& os, + const string::size_type firstLineOffset, string::size_type* lastLineLength, const int flags) const { string::size_type curLineLength = firstLineOffset; word::generatorState state; for (size_t wi = 0 ; wi < getWordCount() ; ++wi) { - getWordAt(wi)->generate(os, maxLineLength, curLineLength, + getWordAt(wi)->generate(ctx, os, curLineLength, &curLineLength, flags, &state); } @@ -369,7 +372,17 @@ ref <text> text::decodeAndUnfold(const string& in) { ref <text> t = vmime::create <text>(); - decodeAndUnfold(in, t.get()); + decodeAndUnfold(parsingContext::getDefaultContext(), in, t.get()); + + return t; +} + + +ref <text> text::decodeAndUnfold(const parsingContext& ctx, const string& in) +{ + ref <text> t = vmime::create <text>(); + + decodeAndUnfold(ctx, in, t.get()); return t; } @@ -377,11 +390,17 @@ ref <text> text::decodeAndUnfold(const string& in) text* text::decodeAndUnfold(const string& in, text* generateInExisting) { + return decodeAndUnfold(parsingContext::getDefaultContext(), in, generateInExisting); +} + + +text* text::decodeAndUnfold(const parsingContext& ctx, const string& in, text* generateInExisting) +{ text* out = (generateInExisting != NULL) ? generateInExisting : new text(); out->removeAllWords(); - const std::vector <ref <word> > words = word::parseMultiple(in, 0, in.length(), NULL); + const std::vector <ref <word> > words = word::parseMultiple(ctx, in, 0, in.length(), NULL); copy_vector(words, out->m_words); diff --git a/src/utility/stringUtils.cpp b/src/utility/stringUtils.cpp index ad498342..8e5f7205 100644 --- a/src/utility/stringUtils.cpp +++ b/src/utility/stringUtils.cpp @@ -151,6 +151,12 @@ string::size_type stringUtils::countASCIIchars } +bool stringUtils::is7bit(const string& str) +{ + return countASCIIchars(str.begin(), str.end()) == str.length(); +} + + string::size_type stringUtils::findFirstNonASCIIchar (const string::const_iterator begin, const string::const_iterator end) { @@ -205,5 +211,32 @@ const string stringUtils::unquote(const string& str) } +bool stringUtils::needQuoting(const string& str, const string& specialChars) +{ + return str.find_first_of(specialChars.c_str()) != string::npos; +} + + +string stringUtils::quote + (const string& str, const string& escapeSpecialChars, const string& escapeChar) +{ + std::ostringstream oss; + string::size_type lastPos = 0, pos = 0; + + while ((pos = str.find_first_of(escapeSpecialChars, lastPos)) != string::npos) + { + oss << str.substr(lastPos, pos - lastPos) + << escapeChar + << str[pos]; + + lastPos = pos + 1; + } + + oss << str.substr(lastPos); + + return oss.str(); +} + + } // utility } // vmime diff --git a/src/word.cpp b/src/word.cpp index 9ab31087..3be9998e 100644 --- a/src/word.cpp +++ b/src/word.cpp @@ -66,9 +66,10 @@ word::word(const string& buffer, const charset& charset) } -ref <word> word::parseNext(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition, - bool prevIsEncoded, bool* isEncoded, bool isFirst) +ref <word> word::parseNext + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition, + bool prevIsEncoded, bool* isEncoded, bool isFirst) { string::size_type pos = position; @@ -87,6 +88,9 @@ ref <word> word::parseNext(const string& buffer, const string::size_type positio string::size_type startPos = pos; string unencoded; + const charset defaultCharset = ctx.getInternationalizedEmailSupport() + ? charset(charsets::UTF_8) : charset(charsets::US_ASCII); + while (pos < end) { // End of line: does not occur in the middle of an encoded word. This is @@ -124,7 +128,7 @@ ref <word> word::parseNext(const string& buffer, const string::size_type positio if (prevIsEncoded) unencoded = whiteSpaces + unencoded; - ref <word> w = vmime::create <word>(unencoded, charset(charsets::US_ASCII)); + ref <word> w = vmime::create <word>(unencoded, defaultCharset); w->setParsedBounds(position, pos); if (newPosition) @@ -205,7 +209,7 @@ ref <word> word::parseNext(const string& buffer, const string::size_type positio // Treat unencoded text at the end of the buffer if (!unencoded.empty()) { - ref <word> w = vmime::create <word>(unencoded, charset(charsets::US_ASCII)); + ref <word> w = vmime::create <word>(unencoded, defaultCharset); w->setParsedBounds(position, end); if (newPosition) @@ -221,8 +225,9 @@ ref <word> word::parseNext(const string& buffer, const string::size_type positio } -const std::vector <ref <word> > word::parseMultiple(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +const std::vector <ref <word> > word::parseMultiple + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { std::vector <ref <word> > res; ref <word> w; @@ -231,7 +236,7 @@ const std::vector <ref <word> > word::parseMultiple(const string& buffer, const bool prevIsEncoded = false; - while ((w = word::parseNext(buffer, pos, end, &pos, prevIsEncoded, &prevIsEncoded, (w == NULL))) != NULL) + while ((w = word::parseNext(ctx, buffer, pos, end, &pos, prevIsEncoded, &prevIsEncoded, (w == NULL))) != NULL) res.push_back(w); if (newPosition) @@ -241,8 +246,9 @@ const std::vector <ref <word> > word::parseMultiple(const string& buffer, const } -void word::parseImpl(const string& buffer, const string::size_type position, - const string::size_type end, string::size_type* newPosition) +void word::parseImpl + (const parsingContext& ctx, const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) { if (position + 6 < end && // 6 = "=?(.+)?(.*)?=" buffer[position] == '=' && buffer[position + 1] == '?') @@ -315,7 +321,8 @@ void word::parseImpl(const string& buffer, const string::size_type position, // Unknown encoding or malformed encoded word: treat the buffer as ordinary text (RFC-2047, Page 9). m_buffer = string(buffer.begin() + position, buffer.begin() + end); - m_charset = charsets::US_ASCII; + m_charset = ctx.getInternationalizedEmailSupport() + ? charset(charsets::UTF_8) : charset(charsets::US_ASCII); setParsedBounds(position, end); @@ -324,14 +331,14 @@ void word::parseImpl(const string& buffer, const string::size_type position, } -void word::generateImpl(utility::outputStream& os, const string::size_type maxLineLength, +void word::generateImpl(const generationContext& ctx, utility::outputStream& os, const string::size_type curLinePos, string::size_type* newLinePos) const { - generate(os, maxLineLength, curLinePos, newLinePos, 0, NULL); + generate(ctx, os, curLinePos, newLinePos, 0, NULL); } -void word::generate(utility::outputStream& os, const string::size_type maxLineLength, +void word::generate(const generationContext& ctx, utility::outputStream& os, const string::size_type curLinePos, string::size_type* newLinePos, const int flags, generatorState* state) const { @@ -350,17 +357,27 @@ void word::generate(utility::outputStream& os, const string::size_type maxLineLe else if ((flags & text::FORCE_ENCODING) != 0) encodingNeeded = true; else // auto-detect - encodingNeeded = wordEncoder::isEncodingNeeded(m_buffer, m_charset); + encodingNeeded = wordEncoder::isEncodingNeeded(ctx, m_buffer, m_charset); + + // If text does not need to be encoded, quote the buffer (no folding is performed). + if (!encodingNeeded && + (flags & text::QUOTE_IF_NEEDED) && + utility::stringUtils::needQuoting(m_buffer)) + { + const string quoted = utility::stringUtils::quote(m_buffer, "\\\"", "\\"); + os << '"' << quoted << '"'; + curLineLength += 1 + quoted.length() + 1; + } // If possible and requested (with flag), quote the buffer (no folding is performed). // Quoting is possible if and only if: // - the buffer does not need to be encoded // - the buffer does not contain quoting character (") // - there is enough remaining space on the current line to hold the whole buffer - if (!encodingNeeded && - (flags & text::QUOTE_IF_POSSIBLE) && - m_buffer.find('"') == string::npos && - (curLineLength + 2 /* 2 x " */ + m_buffer.length()) < maxLineLength) + else if (!encodingNeeded && + (flags & text::QUOTE_IF_POSSIBLE) && + m_buffer.find('"') == string::npos && + (curLineLength + 2 /* 2 x " */ + m_buffer.length()) < ctx.getMaxLineLength()) { os << '"' << m_buffer << '"'; curLineLength += 2 + m_buffer.length(); @@ -368,6 +385,19 @@ void word::generate(utility::outputStream& os, const string::size_type maxLineLe // We will fold lines without encoding them. else if (!encodingNeeded) { + string buffer; + + if (ctx.getInternationalizedEmailSupport()) + { + // Convert the buffer to UTF-8 + charset::convert(m_buffer, buffer, m_charset, charsets::UTF_8); + } + else + { + // Leave the buffer as-is + buffer = m_buffer; + } + // Here, we could have the following conditions: // // * a maximum line length of N bytes @@ -379,7 +409,7 @@ void word::generate(utility::outputStream& os, const string::size_type maxLineLe string::size_type maxRunLength = 0; string::size_type curRunLength = 0; - for (string::const_iterator p = m_buffer.begin(), end = m_buffer.end() ; p != end ; ++p) + for (string::const_iterator p = buffer.begin(), end = buffer.end() ; p != end ; ++p) { if (parserHelpers::isSpace(*p)) { @@ -394,19 +424,19 @@ void word::generate(utility::outputStream& os, const string::size_type maxLineLe maxRunLength = std::max(maxRunLength, curRunLength); - if (((flags & text::FORCE_NO_ENCODING) == 0) && maxRunLength >= maxLineLength - 3) + if (((flags & text::FORCE_NO_ENCODING) == 0) && maxRunLength >= ctx.getMaxLineLength() - 3) { // Generate with encoding forced - generate(os, maxLineLength, curLinePos, newLinePos, flags | text::FORCE_ENCODING, state); + generate(ctx, os, curLinePos, newLinePos, flags | text::FORCE_ENCODING, state); return; } // Output runs, and fold line when a whitespace is encountered - string::const_iterator lastWSpos = m_buffer.end(); // last white-space position - string::const_iterator curLineStart = m_buffer.begin(); // current line start + string::const_iterator lastWSpos = buffer.end(); // last white-space position + string::const_iterator curLineStart = buffer.begin(); // current line start - string::const_iterator p = m_buffer.begin(); - const string::const_iterator end = m_buffer.end(); + string::const_iterator p = buffer.begin(); + const string::const_iterator end = buffer.end(); bool finished = false; bool newLine = false; @@ -417,7 +447,7 @@ void word::generate(utility::outputStream& os, const string::size_type maxLineLe { // Exceeded maximum line length, but we have found a white-space // where we can cut the line... - if (curLineLength >= maxLineLength && lastWSpos != end) + if (curLineLength >= ctx.getMaxLineLength() && lastWSpos != end) break; if (*p == ' ' || *p == '\t') @@ -437,7 +467,7 @@ void word::generate(utility::outputStream& os, const string::size_type maxLineLe // we write the full line no matter of the max line length... if (!newLine && p != end && lastWSpos == end && - !state->isFirstWord && curLineStart == m_buffer.begin()) + !state->isFirstWord && curLineStart == buffer.begin()) { // Here, we are continuing on the line of previous encoded // word, but there is not even enough space to put the @@ -468,7 +498,7 @@ void word::generate(utility::outputStream& os, const string::size_type maxLineLe os << string(curLineStart, p); - if (p != m_buffer.begin() && parserHelpers::isSpace(*(p - 1))) + if (p != buffer.begin() && parserHelpers::isSpace(*(p - 1))) state->lastCharIsSpace = true; else state->lastCharIsSpace = false; @@ -563,9 +593,9 @@ void word::generate(utility::outputStream& os, const string::size_type maxLineLe */ const string::size_type maxLineLength3 = - (maxLineLength == lineLengthLimits::infinite) - ? maxLineLength - : std::min(maxLineLength, static_cast <string::size_type>(76)); + (ctx.getMaxLineLength() == lineLengthLimits::infinite) + ? ctx.getMaxLineLength() + : std::min(ctx.getMaxLineLength(), static_cast <string::size_type>(76)); wordEncoder wordEnc(m_buffer, m_charset); @@ -691,13 +721,13 @@ bool word::operator!=(const word& w) const } -const string word::getConvertedText(const charset& dest) const +const string word::getConvertedText(const charset& dest, const charsetConverterOptions& opts) const { string out; try { - charset::convert(m_buffer, out, m_charset, dest); + charset::convert(m_buffer, out, m_charset, dest, opts); } catch (vmime::exceptions::charset_conv_error& e) { diff --git a/src/wordEncoder.cpp b/src/wordEncoder.cpp index 32e46df1..82a74cff 100644 --- a/src/wordEncoder.cpp +++ b/src/wordEncoder.cpp @@ -168,7 +168,7 @@ const string wordEncoder::getNextChunk(const string::size_type maxLength) // Fully RFC-compliant encoding else { - charsetConverter conv(charsets::UTF_8, m_charset); + ref <charsetConverter> conv = charsetConverter::create(charsets::UTF_8, m_charset); string::size_type inputCount = 0; string::size_type outputCount = 0; @@ -185,7 +185,7 @@ const string wordEncoder::getNextChunk(const string::size_type maxLength) // Convert back to original encoding string encodeBytes; - conv.convert(inputChar, encodeBytes); + conv->convert(inputChar, encodeBytes); encodeBuffer += encodeBytes; @@ -225,23 +225,31 @@ wordEncoder::Encoding wordEncoder::getEncoding() const // static -bool wordEncoder::isEncodingNeeded(const string& buffer, const charset& charset) +bool wordEncoder::isEncodingNeeded + (const generationContext& ctx, const string& buffer, const charset& charset) { - // Charset-specific encoding - encoding recEncoding; + if (!ctx.getInternationalizedEmailSupport()) + { + // Charset-specific encoding + encoding recEncoding; - if (charset.getRecommendedEncoding(recEncoding)) - return true; + if (charset.getRecommendedEncoding(recEncoding)) + return true; - // No encoding is needed if the buffer only contains ASCII chars - if (utility::stringUtils::findFirstNonASCIIchar(buffer.begin(), buffer.end()) != string::npos) - return true; + // No encoding is needed if the buffer only contains ASCII chars + if (utility::stringUtils::findFirstNonASCIIchar(buffer.begin(), buffer.end()) != string::npos) + return true; + } // Force encoding when there are only ASCII chars, but there is // also at least one of '\n' or '\r' (header fields) if (buffer.find_first_of("\n\r") != string::npos) return true; + // If any RFC-2047 sequence is found in the buffer, encode it + if (buffer.find("=?") != string::npos) + return true; + return false; } |