diff --git a/src/vmime/generationContext.cpp b/src/vmime/generationContext.cpp index e9662883..0a76f4d0 100644 --- a/src/vmime/generationContext.cpp +++ b/src/vmime/generationContext.cpp @@ -32,7 +32,8 @@ 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("") + m_epilogText(""), + m_paramValueMode(PARAMETER_VALUE_RFC2231_ONLY) { } @@ -89,6 +90,19 @@ void generationContext::setEpilogText(const string& epilogText) } +void generationContext::setEncodedParameterValueMode(const EncodedParameterValueModes mode) +{ + m_paramValueMode = mode; +} + + +generationContext::EncodedParameterValueModes + generationContext::getEncodedParameterValueMode() const +{ + return m_paramValueMode; +} + + generationContext& generationContext::operator=(const generationContext& ctx) { copyFrom(ctx); @@ -103,6 +117,7 @@ void generationContext::copyFrom(const generationContext& ctx) m_maxLineLength = ctx.m_maxLineLength; m_prologText = ctx.m_prologText; m_epilogText = ctx.m_epilogText; + m_paramValueMode = ctx.m_paramValueMode; } diff --git a/src/vmime/generationContext.hpp b/src/vmime/generationContext.hpp index 949f06ac..22700c90 100644 --- a/src/vmime/generationContext.hpp +++ b/src/vmime/generationContext.hpp @@ -82,6 +82,51 @@ public: */ void setEpilogText(const string& epilogText); + /** Modes available for generating values in parameterized header fields. + */ + enum EncodedParameterValueModes + { + PARAMETER_VALUE_NO_ENCODING, /**< Only generate 7-bit (ASCII-only) values, + even if the value contains non-ASCII chars or + if folding is needed. */ + PARAMETER_VALUE_RFC2047_ONLY, /**< Only generate RFC-2047 values (do not use + RFC-2231). This is non-standard but most + mail clients support it. */ + PARAMETER_VALUE_RFC2231_ONLY, /**< Only generate RFC-2231 values (do not use + RFC-2047). Some mail clients may not support + it. This is the default. */ + PARAMETER_VALUE_RFC2231_AND_RFC2047 /**< Generate both RFC-2047- and RFC-2231-encoded + values. */ + }; + + /** Sets the mode used for generating parameter values in a parameterized + * header field (see parameterizedHeaderField class). + * + * PARAMETER_VALUE_NO_ENCODING or PARAMETER_VALUE_RFC2047_ONLY + * can be used for compatibility with implementations that do not + * understand RFC-2231, to generate a normal parameter value. + * PARAMETER_VALUE_RFC2047_ONLY is non-standard (and expressly + * prohibited by the RFC) but most mail clients support it. + * + * Notice: if both the normal value and the extended value are present, + * the latter can be ignored by mail processing systems. This may lead + * to annoying problems, for example, with strange names of attachments + * with all but 7-bit ascii characters removed, etc. Either + * PARAMETER_VALUE_RFC2231_ONLY or PARAMETER_VALUE_RFC2047_ONLY should + * be preferred over PARAMETER_VALUE_RFC2231_AND_RFC2047, not to create + * a normal value if the extended value is to be generated. + * + * @param mode parameter value generation mode + */ + void setEncodedParameterValueMode(const EncodedParameterValueModes mode); + + /** Returns the mode used for generating parameter values in a parameterized + * header field (see parameterizedHeaderField class). + * + * @return parameter value generation mode + */ + EncodedParameterValueModes getEncodedParameterValueMode() const; + /** Returns the default context used for generating messages. * * @return a reference to the default generation context @@ -97,6 +142,8 @@ protected: string m_prologText; string m_epilogText; + + EncodedParameterValueModes m_paramValueMode; }; diff --git a/src/vmime/parameter.cpp b/src/vmime/parameter.cpp index b8d5b36e..45e68ad6 100644 --- a/src/vmime/parameter.cpp +++ b/src/vmime/parameter.cpp @@ -279,7 +279,13 @@ void parameter::generateImpl const string& value = m_value->getBuffer(); // For compatibility with implementations that do not understand RFC-2231, - // also generate a normal "7bit/us-ascii" parameter + // we may also generate a normal "7bit/us-ascii" parameter + generationContext::EncodedParameterValueModes + genMode = ctx.getEncodedParameterValueMode(); + +#if VMIME_ALWAYS_GENERATE_7BIT_PARAMETER + genMode = generationContext::PARAMETER_VALUE_RFC2231_AND_RFC2047; +#endif // [By Eugene A. Shatokhin] // Note that if both the normal "7bit/us-ascii" value and the extended @@ -366,7 +372,8 @@ void parameter::generateImpl const bool alwaysEncode = m_value->getCharset().getRecommendedEncoding(recommendedEnc); bool extended = alwaysEncode; - if (needQuotedPrintable) + if ((needQuotedPrintable || cutValue) && + genMode != generationContext::PARAMETER_VALUE_NO_ENCODING) { // Send the name in quoted-printable, so outlook express et.al. // will understand the real filename @@ -378,7 +385,8 @@ void parameter::generateImpl else { // Do not chop off this value, but just add the complete name as one header line. - for (size_t i = 0 ; i < value.length() ; ++i) + for (size_t i = 0, n = value.length(), curValueLength = 0 ; + i < n && curValueLength < valueLength ; ++i) { const char_t c = value[i]; @@ -391,6 +399,7 @@ void parameter::generateImpl { sevenBitStream << value[i]; ++pos; + ++curValueLength; } else { @@ -406,26 +415,29 @@ void parameter::generateImpl ++pos; } -#if VMIME_ALWAYS_GENERATE_7BIT_PARAMETER - os << sevenBitBuffer; -#endif // !VMIME_ALWAYS_GENERATE_7BIT_PARAMETER + if (genMode == generationContext::PARAMETER_VALUE_RFC2047_ONLY || + genMode == generationContext::PARAMETER_VALUE_RFC2231_AND_RFC2047) + { + os << sevenBitBuffer; + } // Also generate an extended parameter if the value contains 8-bit characters // or is too long for a single line - if (extended || cutValue) + if ((extended || cutValue) && + genMode != generationContext::PARAMETER_VALUE_NO_ENCODING && + genMode != generationContext::PARAMETER_VALUE_RFC2047_ONLY) { -#if VMIME_ALWAYS_GENERATE_7BIT_PARAMETER - - os << ';'; - ++pos; - -#else // !VMIME_ALWAYS_GENERATE_7BIT_PARAMETER - - // The data output to 'sevenBitBuffer' will be discarded in this case - pos = curLinePos; - -#endif // VMIME_ALWAYS_GENERATE_7BIT_PARAMETER + if (genMode == generationContext::PARAMETER_VALUE_RFC2231_AND_RFC2047) + { + os << ';'; + ++pos; + } + else + { + // The data output to 'sevenBitBuffer' will be discarded in this case + pos = curLinePos; + } /* RFC-2231 * ======== @@ -572,8 +584,8 @@ void parameter::generateImpl } } } -#if !VMIME_ALWAYS_GENERATE_7BIT_PARAMETER - else + else if (!(genMode == generationContext::PARAMETER_VALUE_RFC2047_ONLY || + genMode == generationContext::PARAMETER_VALUE_RFC2231_AND_RFC2047)) { // The value does not contain 8-bit characters and // is short enough for a single line. @@ -582,7 +594,6 @@ void parameter::generateImpl // Output what has been stored in temporary buffer so far os << sevenBitBuffer; } -#endif // !VMIME_ALWAYS_GENERATE_7BIT_PARAMETER if (newLinePos) *newLinePos = pos; diff --git a/tests/parser/parameterTest.cpp b/tests/parser/parameterTest.cpp index 2edfabbe..12c17444 100644 --- a/tests/parser/parameterTest.cpp +++ b/tests/parser/parameterTest.cpp @@ -53,6 +53,26 @@ VMIME_TEST_SUITE_BEGIN(parameterTest) setValue(vmime::headerFieldFactory::getInstance()->createValue(getName())); setValue(vmime::word("X")); } + + using vmime::parameterizedHeaderField::generate; + + const vmime::string generate + (const vmime::generationContext::EncodedParameterValueModes genMode, + const vmime::size_t maxLineLength = 0) const + { + vmime::generationContext ctx(vmime::generationContext::getDefaultContext()); + ctx.setEncodedParameterValueMode(genMode); + + if (maxLineLength != 0) + ctx.setMaxLineLength(maxLineLength); + + std::ostringstream oss; + vmime::utility::outputStreamAdapter adapter(oss); + + vmime::parameterizedHeaderField::generate(ctx, adapter); + + return oss.str(); + } }; @@ -245,11 +265,17 @@ VMIME_TEST_SUITE_BEGIN(parameterTest) p1.appendParameter(vmime::make_shared ("param1", vmime::word("value 1\xe9", vmime::charset("charset")))); -#if VMIME_ALWAYS_GENERATE_7BIT_PARAMETER - VASSERT_EQ("1", "F: X; param1=\"value 1\";param1*=charset''value%201%E9", p1.generate()); -#else - VASSERT_EQ("1", "F: X; param1*=charset''value%201%E9", p1.generate()); -#endif + VASSERT_EQ("1.no-encoding", "F: X; param1=\"value 1\"", + p1.generate(vmime::generationContext::PARAMETER_VALUE_NO_ENCODING)); + + VASSERT_EQ("1.rfc2047", "F: X; param1=\"=?charset?Q?value_1=E9?=\"", + p1.generate(vmime::generationContext::PARAMETER_VALUE_RFC2047_ONLY)); + + VASSERT_EQ("1.rfc2231", "F: X; param1*=charset''value%201%E9", + p1.generate(vmime::generationContext::PARAMETER_VALUE_RFC2231_ONLY)); + + VASSERT_EQ("1.both", "F: X; param1=\"=?charset?Q?value_1=E9?=\";param1*=charset''value%201%E9", + p1.generate(vmime::generationContext::PARAMETER_VALUE_RFC2231_AND_RFC2047)); // Value that spans on multiple lines parameterizedHeaderField p2; @@ -257,26 +283,34 @@ VMIME_TEST_SUITE_BEGIN(parameterTest) vmime::word("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", vmime::charset("charset")))); -#if VMIME_ALWAYS_GENERATE_7BIT_PARAMETER - VASSERT_EQ("2", "F: X; \r\n " - "param1=abcdefghijklm;\r\n " + VASSERT_EQ("2.no-encoding", "F: X; \r\n " + "param1=abcdefghijkl", + p2.generate(vmime::generationContext::PARAMETER_VALUE_NO_ENCODING, 25)); // max line length = 25 + + VASSERT_EQ("2.rfc2047", "F: X; \r\n " + "param1=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + p2.generate(vmime::generationContext::PARAMETER_VALUE_RFC2047_ONLY, 25)); // max line length = 25 + + VASSERT_EQ("2.rfc2231", "F: X; \r\n " "param1*0*=charset''abc;\r\n " "param1*1*=defghijkl;\r\n " "param1*2*=mnopqrstu;\r\n " "param1*3*=vwxyzABCD;\r\n " "param1*4*=EFGHIJKLM;\r\n " "param1*5*=NOPQRSTUV;\r\n " - "param1*6*=WXYZ", p2.generate(25)); // max line length = 25 -#else - VASSERT_EQ("2", "F: X; \r\n " + "param1*6*=WXYZ", + p2.generate(vmime::generationContext::PARAMETER_VALUE_RFC2231_ONLY, 25)); // max line length = 25 + + VASSERT_EQ("2.both", "F: X; \r\n " + "param1=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ;\r\n " "param1*0*=charset''abc;\r\n " "param1*1*=defghijkl;\r\n " "param1*2*=mnopqrstu;\r\n " "param1*3*=vwxyzABCD;\r\n " "param1*4*=EFGHIJKLM;\r\n " "param1*5*=NOPQRSTUV;\r\n " - "param1*6*=WXYZ", p2.generate(25)); // max line length = 25 -#endif + "param1*6*=WXYZ", + p2.generate(vmime::generationContext::PARAMETER_VALUE_RFC2231_AND_RFC2047, 25)); // max line length = 25 // Non-ASCII parameter value parameterizedHeaderField p3; @@ -284,26 +318,58 @@ VMIME_TEST_SUITE_BEGIN(parameterTest) vmime::word("δσσσσσσσσσσσσσσσσσσσσδσδα δσαδσδσαδσαδασδασ δσαδασδσα δσαδασδσα δασδασδασ δασαχφδδσα 2008.doc", vmime::charset("utf-8")))); -#if VMIME_ALWAYS_GENERATE_7BIT_PARAMETER - VASSERT_EQ("3", "F: X; \r\n " - "param1=\" 2008.doc\";param1*0*=utf-8''%CE%B4%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83;\r\n " + VASSERT_EQ("3.no-encoding", "F: X; \r\n " + "param1=\" 2008.doc\"", + p3.generate(vmime::generationContext::PARAMETER_VALUE_NO_ENCODING, 80)); // max line length = 80 + + VASSERT_EQ("3.7bit-only", "F: X; \r\n " + "param1=\"=?utf-8?B?zrTPg8+Dz4PPg8+Dz4PPg8+Dz4PPg8+Dz4PPg8+Dz4PPg8+Dz4PPg8+DzrTPg860?=\r\n " + "=?utf-8?B?zrEgzrTPg86xzrTPg860z4POsc60z4POsc60zrHPg860zrHPgyDOtM+DzrHOtM6x?=\r\n " + "=?utf-8?B?z4POtM+DzrEgzrTPg86xzrTOsc+DzrTPg86xIM60zrHPg860zrHPg860zrHPgyDOtA==?=\r\n " + "=?utf-8?B?zrHPg86xz4fPhs60zrTPg86xIDIwMDguZG9j?=\"", + p3.generate(vmime::generationContext::PARAMETER_VALUE_RFC2047_ONLY, 80)); // max line length = 80 + + VASSERT_EQ("3.both", "F: X; \r\n " + "param1=\"=?utf-8?B?zrTPg8+Dz4PPg8+Dz4PPg8+Dz4PPg8+Dz4PPg8+Dz4PPg8+Dz4PPg8+DzrTPg860?=\r\n " + "=?utf-8?B?zrEgzrTPg86xzrTPg860z4POsc60z4POsc60zrHPg860zrHPgyDOtM+DzrHOtM6x?=\r\n " + "=?utf-8?B?z4POtM+DzrEgzrTPg86xzrTOsc+DzrTPg86xIM60zrHPg860zrHPg860zrHPgyDOtA==?=\r\n " + "=?utf-8?B?zrHPg86xz4fPhs60zrTPg86xIDIwMDguZG9j?=\";\r\n " + "param1*0*=utf-8''%CE%B4%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83;\r\n " "param1*1*=%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83;\r\n " "param1*2*=%CE%B4%CF%83%CE%B4%CE%B1%20%CE%B4%CF%83%CE%B1%CE%B4%CF%83%CE%B4%CF;\r\n " "param1*3*=%83%CE%B1%CE%B4%CF%83%CE%B1%CE%B4%CE%B1%CF%83%CE%B4%CE%B1%CF%83%20;\r\n " "param1*4*=%CE%B4%CF%83%CE%B1%CE%B4%CE%B1%CF%83%CE%B4%CF%83%CE%B1%20%CE%B4%CF;\r\n " "param1*5*=%83%CE%B1%CE%B4%CE%B1%CF%83%CE%B4%CF%83%CE%B1%20%CE%B4%CE%B1%CF%83;\r\n " "param1*6*=%CE%B4%CE%B1%CF%83%CE%B4%CE%B1%CF%83%20%CE%B4%CE%B1%CF%83%CE%B1%CF;\r\n " - "param1*7*=%87%CF%86%CE%B4%CE%B4%CF%83%CE%B1%202008.doc", p3.generate(80)); -#else - VASSERT_EQ("3", "F: X; param1*0*=utf-8''%CE%B4%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83;\r\n " + "param1*7*=%87%CF%86%CE%B4%CE%B4%CF%83%CE%B1%202008.doc", + p3.generate(vmime::generationContext::PARAMETER_VALUE_RFC2231_AND_RFC2047, 80)); // max line length = 80 + + VASSERT_EQ("3.either", "F: X; param1*0*=utf-8''%CE%B4%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83;\r\n " "param1*1*=%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83%CF%83;\r\n " "param1*2*=%CE%B4%CF%83%CE%B4%CE%B1%20%CE%B4%CF%83%CE%B1%CE%B4%CF%83%CE%B4%CF;\r\n " "param1*3*=%83%CE%B1%CE%B4%CF%83%CE%B1%CE%B4%CE%B1%CF%83%CE%B4%CE%B1%CF%83%20;\r\n " "param1*4*=%CE%B4%CF%83%CE%B1%CE%B4%CE%B1%CF%83%CE%B4%CF%83%CE%B1%20%CE%B4%CF;\r\n " "param1*5*=%83%CE%B1%CE%B4%CE%B1%CF%83%CE%B4%CF%83%CE%B1%20%CE%B4%CE%B1%CF%83;\r\n " "param1*6*=%CE%B4%CE%B1%CF%83%CE%B4%CE%B1%CF%83%20%CE%B4%CE%B1%CF%83%CE%B1%CF;\r\n " - "param1*7*=%87%CF%86%CE%B4%CE%B4%CF%83%CE%B1%202008.doc", p3.generate(80)); -#endif + "param1*7*=%87%CF%86%CE%B4%CE%B4%CF%83%CE%B1%202008.doc", + p3.generate(vmime::generationContext::PARAMETER_VALUE_RFC2231_ONLY, 80)); // max line length = 80 + + // No encoding needed + parameterizedHeaderField p4; + p4.appendParameter(vmime::make_shared ("param1", + vmime::word("va lue", vmime::charset("charset")))); + + VASSERT_EQ("4.no-encoding", "F: X; param1=\"va lue\"", + p4.generate(vmime::generationContext::PARAMETER_VALUE_NO_ENCODING)); + + VASSERT_EQ("4.rfc2047", "F: X; param1=\"va lue\"", + p4.generate(vmime::generationContext::PARAMETER_VALUE_RFC2047_ONLY)); + + VASSERT_EQ("4.rfc2231", "F: X; param1=\"va lue\"", + p4.generate(vmime::generationContext::PARAMETER_VALUE_RFC2231_ONLY)); + + VASSERT_EQ("4.both", "F: X; param1=\"va lue\"", + p4.generate(vmime::generationContext::PARAMETER_VALUE_RFC2231_AND_RFC2047)); } void testNonStandardEncodedParam()