diff --git a/ChangeLog b/ChangeLog index 3573bd23..c08c211e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,13 @@ VERSION 0.6.4cvs ================ +2005-04-12 Vincent Richard + + * parameter.{cpp|hpp}, contentDispositionField.{cpp|hpp}: added support + for RFC-2231 (encoded word extensions). Changed 'filename' parameter + type from 'vmime::string' to 'vmime::word'. Default parameter type is + now vmime::word, and not vmime::string. + 2005-04-09 Vincent Richard * encoderB64.cpp: fixed a bug in Base64 decoding. Bytes to be decoded @@ -57,7 +64,7 @@ VERSION 0.6.4cvs deleted (thanks to Stefan Uhrig). * SConstruct: fixed compilation/linking problem with g++ and X86-64 on - static library: added -fPIC/-fpic in compiler flags. + static library: added -fPIC/-fpic in compiler flags. * messaging/POP3*: added notifications. diff --git a/SConstruct b/SConstruct index fb75e4f9..23a5d891 100644 --- a/SConstruct +++ b/SConstruct @@ -94,6 +94,7 @@ libvmime_sources = [ 'contentTypeField.cpp', 'contentTypeField.hpp', 'dateTime.cpp', 'dateTime.hpp', 'defaultAttachment.cpp', 'defaultAttachment.hpp', + 'defaultParameter.cpp', 'defaultParameter.hpp', 'disposition.cpp', 'disposition.hpp', 'emptyContentHandler.cpp', 'emptyContentHandler.hpp', 'encoder.cpp', 'encoder.hpp', @@ -328,6 +329,7 @@ libvmimetest_sources = [ [ 'tests/parser/messageIdTest', [ 'tests/parser/messageIdTest.cpp' ] ], [ 'tests/parser/messageIdSequenceTest', [ 'tests/parser/messageIdSequenceTest.cpp' ] ], [ 'tests/parser/pathTest', [ 'tests/parser/pathTest.cpp' ] ], + [ 'tests/parser/parameterTest', [ 'tests/parser/parameterTest.cpp' ] ], [ 'tests/parser/textTest', [ 'tests/parser/textTest.cpp' ] ], [ 'tests/utility/md5Test', [ 'tests/utility/md5Test.cpp' ] ], [ 'tests/utility/stringProxyTest', [ 'tests/utility/stringProxyTest.cpp' ] ], diff --git a/src/contentDispositionField.cpp b/src/contentDispositionField.cpp index bfbacc21..816c484f 100644 --- a/src/contentDispositionField.cpp +++ b/src/contentDispositionField.cpp @@ -74,13 +74,13 @@ void contentDispositionField::setReadDate(const datetime& readDate) } -const string contentDispositionField::getFilename() const +const word contentDispositionField::getFilename() const { return (dynamic_cast (*findParameter("filename")).getValue()); } -void contentDispositionField::setFilename(const string& filename) +void contentDispositionField::setFilename(const word& filename) { dynamic_cast (*getParameter("filename")).setValue(filename); } @@ -88,13 +88,13 @@ void contentDispositionField::setFilename(const string& filename) const string contentDispositionField::getSize() const { - return (dynamic_cast (*findParameter("size")).getValue()); + return (dynamic_cast (*findParameter("size")).getValue().getBuffer()); } void contentDispositionField::setSize(const string& size) { - dynamic_cast (*getParameter("size")).setValue(size); + dynamic_cast (*getParameter("size")).setValue(word(size)); } diff --git a/src/contentTypeField.cpp b/src/contentTypeField.cpp index 61b4e5e4..28e31901 100644 --- a/src/contentTypeField.cpp +++ b/src/contentTypeField.cpp @@ -40,13 +40,13 @@ contentTypeField::contentTypeField(contentTypeField&) const string contentTypeField::getBoundary() const { - return (dynamic_cast (*findParameter("boundary")).getValue()); + return (dynamic_cast (*findParameter("boundary")).getValue().getBuffer()); } void contentTypeField::setBoundary(const string& boundary) { - dynamic_cast (*getParameter("boundary")).setValue(boundary); + dynamic_cast (*getParameter("boundary")).setValue(word(boundary)); } @@ -64,13 +64,13 @@ void contentTypeField::setCharset(const charset& ch) const string contentTypeField::getReportType() const { - return (dynamic_cast (*findParameter("report-type")).getValue()); + return (dynamic_cast (*findParameter("report-type")).getValue().getBuffer()); } void contentTypeField::setReportType(const string& reportType) { - dynamic_cast (*getParameter("report-type")).setValue(reportType); + dynamic_cast (*getParameter("report-type")).setValue(word(reportType)); } diff --git a/src/defaultParameter.cpp b/src/defaultParameter.cpp new file mode 100644 index 00000000..796b4d49 --- /dev/null +++ b/src/defaultParameter.cpp @@ -0,0 +1,416 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "vmime/defaultParameter.hpp" +#include "vmime/parserHelpers.hpp" + + +namespace vmime +{ + + +defaultParameter::defaultParameter() +{ +} + + +defaultParameter& defaultParameter::operator=(const defaultParameter& other) +{ + copyFrom(other); + return (*this); +} + + +const word& defaultParameter::getValue() const +{ + return (m_value); +} + + +word& defaultParameter::getValue() +{ + return (m_value); +} + + +void defaultParameter::setValue(const word& value) +{ + m_value = value; +} + + +void defaultParameter::setValue(const component& value) +{ + const word& v = dynamic_cast (value); + m_value = v; +} + + +void defaultParameter::parse(const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) +{ + m_value = word(string(buffer.begin() + position, buffer.begin() + end), + charset(charsets::US_ASCII)); + + if (newPosition) + *newPosition = end; +} + + +void defaultParameter::parse(const std::vector & chunks) +{ + bool foundCharsetChunk = false; + + charset ch(charsets::US_ASCII); + std::ostringstream value; + + for (std::vector ::size_type i = 0 ; i < chunks.size() ; ++i) + { + const valueChunk& chunk = chunks[i]; + + // Decode following data + if (chunk.encoded) + { + const string::size_type len = chunk.data.length(); + string::size_type pos = 0; + + // If this is the first encoded chunk, extract charset + // and language information + if (!foundCharsetChunk) + { + // Eg. "us-ascii'en'This%20is%20even%20more%20" + string::size_type q = chunk.data.find_first_of('\''); + + if (q != string::npos) + { + const string chs = chunk.data.substr(0, q); + + if (!chs.empty()) + ch = charset(chs); + + ++q; + pos = q; + } + + q = chunk.data.find_first_of('\'', pos); + + if (q != string::npos) + { + // Ignore language + ++q; + pos = q; + } + + foundCharsetChunk = true; + } + + for (string::size_type i = pos ; i < len ; ++i) + { + const string::value_type c = chunk.data[i]; + + if (c == '%' && i + 2 < len) + { + string::value_type v = 0; + + // First char + switch (chunk.data[i + 1]) + { + case 'a': case 'A': v += 10; break; + case 'b': case 'B': v += 11; break; + case 'c': case 'C': v += 12; break; + case 'd': case 'D': v += 13; break; + case 'e': case 'E': v += 14; break; + case 'f': case 'F': v += 15; break; + default: // assume 0-9 + + v += (chunk.data[i + 1] - '0'); + break; + } + + v *= 16; + + // Second char + switch (chunk.data[i + 2]) + { + case 'a': case 'A': v += 10; break; + case 'b': case 'B': v += 11; break; + case 'c': case 'C': v += 12; break; + case 'd': case 'D': v += 13; break; + case 'e': case 'E': v += 14; break; + case 'f': case 'F': v += 15; break; + default: // assume 0-9 + + v += (chunk.data[i + 2] - '0'); + break; + } + + value << v; + + i += 2; // skip next 2 chars + } + else + { + value << c; + } + } + } + // Simply copy data, as it is not encoded + else + { + value << chunk.data; + } + } + + m_value = word(value.str(), ch); +} + + +void defaultParameter::generate(utility::outputStream& os, const string::size_type maxLineLength, + const string::size_type curLinePos, string::size_type* newLinePos) const +{ + const string& name = getName(); + const string& value = m_value.getBuffer(); + + // For compatibility with implementations that do not understand RFC-2231, + // also generate a normal "7bit/us-ascii" parameter + string::size_type pos = curLinePos; + + if (pos + name.length() + 10 > maxLineLength) + { + os << NEW_LINE_SEQUENCE; + pos = NEW_LINE_SEQUENCE_LENGTH; + } + + bool needQuoting = false; + + for (string::size_type i = 0 ; (i < value.length()) && (pos < maxLineLength - 4) ; ++i) + { + switch (value[i]) + { + // Characters that need to be quoted _and_ escaped + case '"': + case '\\': + // Other characters that need quoting + case ' ': + case '\t': + case '(': + case ')': + case '<': + case '>': + case '@': + case ',': + case ';': + case ':': + case '/': + case '[': + case ']': + case '?': + case '=': + + needQuoting = true; + break; + } + } + + if (needQuoting) + { + os << name << "=\""; + pos += name.length() + 2; + } + else + { + os << name << "="; + pos += name.length() + 1; + } + + bool extended = false; + + for (string::size_type i = 0 ; (i < value.length()) && (pos < maxLineLength - 4) ; ++i) + { + const char_t c = value[i]; + + if (/* needQuoting && */ (c == '"' || c == '\\')) // 'needQuoting' is implicit + { + os << '\\' << value[i]; // escape 'x' with '\x' + pos += 2; + } + else if (parserHelpers::isAscii(c)) + { + os << value[i]; + ++pos; + } + else + { + extended = true; + } + } + + if (needQuoting) + { + os << '"'; + ++pos; + } + + // Also generate an extended parameter if the value contains 8-bit characters + // or is too long for a single line + if (extended || (value.length() >= (maxLineLength - name.length() + 3))) + { + os << ';'; + ++pos; + + /* RFC-2231 + * ======== + * + * Content-Type: message/external-body; access-type=URL; + * URL*0="ftp://"; + * URL*1="cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar" + * + * Content-Type: application/x-stuff; + * title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A + * + * Content-Type: application/x-stuff; + * title*0*=us-ascii'en'This%20is%20even%20more%20 + * title*1*=%2A%2A%2Afun%2A%2A%2A%20 + * title*2="isn't it!" + */ + + // Check whether there is enough space for the first section: + // parameter name, section identifier, charset and separators + // + at least 5 characters for the value + const string::size_type firstSectionLength = + name.length() + 4 /* *0*= */ + 2 /* '' */ + + m_value.getCharset().getName().length(); + + if (pos + firstSectionLength + 5 >= maxLineLength) + { + os << NEW_LINE_SEQUENCE; + pos = NEW_LINE_SEQUENCE_LENGTH; + } + + // Split text into multiple sections that fit on one line + int sectionCount = 0; + std::vector sectionText; + + string currentSection; + string::size_type currentSectionLength = firstSectionLength; + + for (string::size_type i = 0 ; i < value.length() ; ++i) + { + // Check whether we should start a new line (taking into + // account the next character will be encoded = worst case) + if (currentSectionLength + 3 >= maxLineLength) + { + sectionText.push_back(currentSection); + sectionCount++; + + currentSection.clear(); + currentSectionLength = NEW_LINE_SEQUENCE_LENGTH + + name.length() + 6; + } + + // Output next character + const char_t c = value[i]; + bool encode = false; + + switch (c) + { + // special characters + case ' ': + case '\t': + case '\r': + case '\n': + case '"': + case ';': + case ',': + + encode = true; + break; + + default: + + encode = (!parserHelpers::isPrint(c) || + !parserHelpers::isAscii(c)); + + break; + } + + if (encode) // need encoding + { + const int h1 = static_cast (c) / 16; + const int h2 = static_cast (c) % 16; + + currentSection += '%'; + currentSection += "0123456789ABCDEF"[h1]; + currentSection += "0123456789ABCDEF"[h2]; + + pos += 3; + currentSectionLength += 3; + } + else + { + currentSection += value[i]; + + ++pos; + ++currentSectionLength; + } + } + + if (!currentSection.empty()) + { + sectionText.push_back(currentSection); + sectionCount++; + } + + // Output sections + for (int sectionNumber = 0 ; sectionNumber < sectionCount ; ++sectionNumber) + { + os << name; + + if (sectionCount != 1) // no section specifier when only a single one + { + os << '*'; + os << sectionNumber; + } + + os << "*="; + + if (sectionNumber == 0) + { + os << m_value.getCharset().getName(); + os << '\'' << /* No language */ '\''; + } + + os << sectionText[sectionNumber]; + + if (sectionNumber + 1 < sectionCount) + { + os << ';'; + os << NEW_LINE_SEQUENCE; + pos = NEW_LINE_SEQUENCE_LENGTH; + } + } + } + + if (newLinePos) + *newLinePos = pos; +} + + + +} // vmime diff --git a/src/parameter.cpp b/src/parameter.cpp index 1cc2e697..988755be 100644 --- a/src/parameter.cpp +++ b/src/parameter.cpp @@ -63,6 +63,23 @@ void parameter::parse(const string& buffer, const string::size_type position, getValue().parse(buffer, position, end, newPosition); setParsedBounds(position, end); + + if (newPosition) + *newPosition = end; +} + + +void parameter::parse(const std::vector & chunks) +{ + string value; + + for (std::vector ::const_iterator it = chunks.begin() ; + it != chunks.end() ; ++it) + { + value += (*it).data; + } + + getValue().parse(value, 0, value.length(), NULL); } @@ -87,10 +104,13 @@ void parameter::generate(utility::outputStream& os, const string::size_type maxL void parameter::generateValue(utility::outputStream& os, const string::size_type /* maxLineLength */, const string::size_type curLinePos, string::size_type* newLinePos) const { + // NOTE: This default implementation does not support parameter + // values that span on several lines ('defaultParameter' can do + // that, following rules specified in RFC-2231). + std::ostringstream valueStream; utility::outputStreamAdapter valueStreamV(valueStream); - // TODO: can we imagine having values that span on multiple lines? getValue().generate(valueStreamV, lineLengthLimits::infinite, 0, NULL); const string value(valueStream.str()); diff --git a/src/parameterFactory.cpp b/src/parameterFactory.cpp index 77450409..a5a7a81c 100644 --- a/src/parameterFactory.cpp +++ b/src/parameterFactory.cpp @@ -73,4 +73,27 @@ parameter* parameterFactory::create } +parameter* parameterFactory::create(const string& name, const component& value) +{ + const string lcName = utility::stringUtils::toLower(name); + + NameMap::const_iterator pos = m_nameMap.find(lcName); + parameter* param = NULL; + + if (pos != m_nameMap.end()) + { + param = ((*pos).second)(); + } + else + { + param = registerer ::creator(); + } + + param->m_name = name; + param->setValue(value); + + return (param); +} + + } // vmime diff --git a/src/parameterizedHeaderField.cpp b/src/parameterizedHeaderField.cpp index 92631d9d..d3c8f67c 100644 --- a/src/parameterizedHeaderField.cpp +++ b/src/parameterizedHeaderField.cpp @@ -55,6 +55,20 @@ parameterizedHeaderField::parameterizedHeaderField() ; to use within parameter values */ + +#ifndef VMIME_BUILDING_DOC + +struct paramInfo +{ + bool extended; + std::vector value; + string::size_type start; + string::size_type end; +}; + +#endif // VMIME_BUILDING_DOC + + void parameterizedHeaderField::parse(const string& buffer, const string::size_type position, const string::size_type end, string::size_type* newPosition) { @@ -73,6 +87,8 @@ void parameterizedHeaderField::parse(const string& buffer, const string::size_ty // If there is one or more parameters following... if (p < pend) { + std::map params; + while (*p == ';') { // Skip ';' @@ -183,20 +199,99 @@ void parameterizedHeaderField::parse(const string& buffer, const string::size_ty // Don't allow ill-formed parameters if (attrStart != attrEnd && value.length()) { - // Append this parameter to the list - parameter* param = parameterFactory::getInstance()-> - create(string(buffer.begin() + attrStart, - buffer.begin() + attrEnd), value); + string name(buffer.begin() + attrStart, buffer.begin() + attrEnd); - param->setParsedBounds(attrStart, position + (p - pstart)); + // Check for RFC-2231 extended parameters + bool extended = false; + bool encoded = false; - appendParameter(param); + if (name[name.length() - 1] == '*') + { + name.erase(name.end() - 1, name.end()); + + extended = true; + encoded = true; + } + + // Check for RFC-2231 multi-section parameters + const string::size_type star = name.find_last_of('*'); + + if (star != string::npos) + { + bool allDigits = true; + + for (string::size_type i = star + 1 ; allDigits && (i < name.length()) ; ++i) + allDigits = parserHelpers::isDigit(name[i]); + + if (allDigits) + { + name.erase(name.begin() + star, name.end()); + extended = true; + } + + // NOTE: we ignore section number, and we suppose that + // the sequence is correct (ie. the sections appear + // in order: param*0, param*1...) + } + + // Add/replace/modify the parameter + const std::map ::iterator it = params.find(name); + + if (it != params.end()) + { + paramInfo& info = (*it).second; + + // An extended parameter replaces a normal one + if (!info.extended) + { + info.extended = extended; + info.value.clear(); + info.start = attrStart; + } + + // Append a new section for a multi-section parameter + parameter::valueChunk chunk; + chunk.encoded = encoded; + chunk.data = value; + + info.value.push_back(chunk); + info.end = position + (p - pstart); + } + else + { + parameter::valueChunk chunk; + chunk.encoded = encoded; + chunk.data = value; + + paramInfo info; + info.extended = extended; + info.value.push_back(chunk); + info.start = attrStart; + info.end = position + (p - pstart); + + // Insert a new parameter + params.insert(std::map ::value_type(name, info)); + } } // Skip white-spaces after this parameter while (p < pend && parserHelpers::isSpace(*p)) ++p; } } + + for (std::map ::const_iterator it = params.begin() ; + it != params.end() ; ++it) + { + const paramInfo& info = (*it).second; + + // Append this parameter to the list + parameter* param = parameterFactory::getInstance()->create((*it).first); + + param->parse(info.value); + param->setParsedBounds(info.start, info.end); + + appendParameter(param); + } } if (newPosition) diff --git a/tests/parser/parameterTest.cpp b/tests/parser/parameterTest.cpp new file mode 100644 index 00000000..4ce58210 --- /dev/null +++ b/tests/parser/parameterTest.cpp @@ -0,0 +1,249 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "../lib/unit++/unit++.h" + +#include +#include + +#include "vmime/vmime.hpp" +#include "vmime/platforms/posix/posixHandler.hpp" + +#include "tests/parser/testUtils.hpp" + +using namespace unitpp; + + +namespace +{ + // HACK: parameterizedHeaderField constructor is private + class parameterizedHeaderField : public vmime::parameterizedHeaderField + { + private: + + vmime::typeAdapter m_value; + + public: + + parameterizedHeaderField() : headerField("F"), m_value("X") { } + + const vmime::component& getValue() const { return m_value; } + vmime::component& getValue() { return m_value; } + + void setValue(const vmime::component&) { /* Do nothing */ } + }; + + +#define PARAM_VALUE(p, n) (p.getParameterAt(n)->getValue().generate()) +#define PARAM_NAME(p, n) (p.getParameterAt(n)->getName()) +#define PARAM_CHARSET(p, n) (static_cast \ + (p.getParameterAt(n))->getValue().getCharset().generate()) +#define PARAM_BUFFER(p, n) (static_cast \ + (p.getParameterAt(n))->getValue().getBuffer()) + + + class parameterTest : public suite + { + void testParse() + { + // Simple parameter + parameterizedHeaderField p1; + p1.parse("X; param1=value1;\r\n"); + + assert_eq("1.1", 1, p1.getParameterCount()); + assert_eq("1.2", "param1", PARAM_NAME(p1, 0)); + assert_eq("1.3", "value1", PARAM_VALUE(p1, 0)); + + // Multi-section parameters (1/2) + parameterizedHeaderField p2a; + p2a.parse("X; param1=value1;\r\n" + " param2*0=\"val\";\r\n" + " param2*1=\"ue2\";"); + + assert_eq("2a.1", 2, p2a.getParameterCount()); + assert_eq("2a.2", "param1", PARAM_NAME(p2a, 0)); + assert_eq("2a.3", "value1", PARAM_VALUE(p2a, 0)); + assert_eq("2a.4", "param2", PARAM_NAME(p2a, 1)); + assert_eq("2a.5", "value2", PARAM_VALUE(p2a, 1)); + + // Multi-section parameters (2/2) + parameterizedHeaderField p2b; + p2b.parse("X; param1=value1;\r\n" + " param2=\"should be ignored\";\r\n" + " param2*0=\"val\";\r\n" + " param2*1=\"ue2\";"); + + assert_eq("2b.1", 2, p2b.getParameterCount()); + assert_eq("2b.2", "param1", PARAM_NAME(p2b, 0)); + assert_eq("2b.3", "value1", PARAM_VALUE(p2b, 0)); + assert_eq("2b.4", "param2", PARAM_NAME(p2b, 1)); + assert_eq("2b.5", "value2", PARAM_VALUE(p2b, 1)); + + // Extended parameter (charset and language information) + parameterizedHeaderField p3; + p3.parse("X; param1*=charset'language'value1;\r\n"); + + assert_eq("3.1", 1, p3.getParameterCount()); + assert_eq("3.2", "param1", PARAM_NAME(p3, 0)); + assert_eq("3.3", "charset", PARAM_CHARSET(p3, 0)); + assert_eq("3.4", "value1", PARAM_BUFFER(p3, 0)); + + // Encoded characters in extended parameter values + parameterizedHeaderField p4; + p4.parse("X; param1*=a%20value%20with%20multiple%20word%73"); // 0x73 = 's' + + assert_eq("4.1", 1, p4.getParameterCount()); + assert_eq("4.2", "param1", PARAM_NAME(p4, 0)); + assert_eq("4.3", "a value with multiple words", PARAM_VALUE(p4, 0)); + + // Invalid encoded character + parameterizedHeaderField p5; + p5.parse("X; param1*=test%20value%"); + + assert_eq("5.1", 1, p5.getParameterCount()); + assert_eq("5.2", "param1", PARAM_NAME(p5, 0)); + assert_eq("5.3", "test value%", PARAM_VALUE(p5, 0)); + + // Spaces before and after '=' + parameterizedHeaderField p6; + p6.parse("X; param1\t= \"value1\""); + + assert_eq("6.1", 1, p6.getParameterCount()); + assert_eq("6.2", "param1", PARAM_NAME(p6, 0)); + assert_eq("6.3", "value1", PARAM_VALUE(p6, 0)); + + // Quoted strings and escaped chars + parameterizedHeaderField p7; + p7.parse("X; param1=\"this is a slash: \\\"\\\\\\\"\""); // \"\\\" + + assert_eq("7.1", 1, p7.getParameterCount()); + assert_eq("7.2", "param1", PARAM_NAME(p7, 0)); + assert_eq("7.3", "this is a slash: \"\\\"", PARAM_VALUE(p7, 0)); + + // Extended parameter with charset specified in more than one + // section (this is forbidden by RFC, but is should not fail) + parameterizedHeaderField p8; + p8.parse("X; param1*0*=charset1'language1'value1;\r\n" + " param1*1*=charset2'language2'value2;"); + + assert_eq("8.1", 1, p8.getParameterCount()); + assert_eq("8.2", "param1", PARAM_NAME(p8, 0)); + assert_eq("8.3", "charset1", PARAM_CHARSET(p8, 0)); + assert_eq("8.4", "value1charset2'language2'value2", PARAM_BUFFER(p8, 0)); + + // Charset not specified in the first section (that is not encoded), + // but specified in the second one (legal) + parameterizedHeaderField p9; + p9.parse("X; param1*0=value1;\r\n" + " param1*1*=charset'language'value2;"); + + assert_eq("9.1", 1, p9.getParameterCount()); + assert_eq("9.2", "param1", PARAM_NAME(p9, 0)); + assert_eq("9.3", "charset", PARAM_CHARSET(p9, 0)); + assert_eq("9.4", "value1value2", PARAM_BUFFER(p9, 0)); + + // Characters prefixed with '%' in a simple (not extended) section + // should not be decoded + parameterizedHeaderField p10; + p10.parse("X; param1=val%20ue1"); + + assert_eq("10.1", 1, p10.getParameterCount()); + assert_eq("10.2", "param1", PARAM_NAME(p10, 0)); + assert_eq("10.3", "val%20ue1", PARAM_VALUE(p10, 0)); + + // Multiple sections + charset specified and encoding + parameterizedHeaderField p11; + p11.parse("X; param1*0*=charset'language'value1a%20;" + " param1*1*=value1b%20;" + " param1*2=value1c"); + + assert_eq("11.1", 1, p11.getParameterCount()); + assert_eq("11.2", "param1", PARAM_NAME(p11, 0)); + assert_eq("11.3", "charset", PARAM_CHARSET(p11, 0)); + assert_eq("11.4", "value1a value1b value1c", PARAM_BUFFER(p11, 0)); + + // No charset specified: defaults to US-ASCII + parameterizedHeaderField p12; + p12.parse("X; param1*='language'value1"); + + assert_eq("12.1", 1, p12.getParameterCount()); + assert_eq("12.2", "param1", PARAM_NAME(p12, 0)); + assert_eq("12.3", "us-ascii", PARAM_CHARSET(p12, 0)); + assert_eq("12.4", "value1", PARAM_BUFFER(p12, 0)); + } + + void testGenerate() + { + // Simple parameter/value + parameterizedHeaderField p1; + p1.appendParameter(vmime::parameterFactory::getInstance()->create("param1", "value1")); + + assert_eq("1", "F: X; param1=value1", p1.generate()); + + // Value that needs quoting (1/2) + parameterizedHeaderField p2a; + p2a.appendParameter(vmime::parameterFactory::getInstance()->create("param1", "value1a;value1b")); + + assert_eq("2a", "F: X; param1=\"value1a;value1b\"", p2a.generate()); + + // Value that needs quoting (2/2) + parameterizedHeaderField p2b; + p2b.appendParameter(vmime::parameterFactory::getInstance()->create("param1", "va\\lue\"1")); + + assert_eq("2b", "F: X; param1=\"va\\\\lue\\\"1\"", p2b.generate()); + + // Extended parameter with charset specifier + parameterizedHeaderField p3; + p3.appendParameter(vmime::parameterFactory::getInstance()->create("param1", + vmime::word("value 1\xe9", vmime::charset("charset")))); + + assert_eq("3", "F: X; param1=\"value 1\";param1*=charset''value%201%E9", p3.generate()); + + // Value that spans on multiple lines + parameterizedHeaderField p4; + p4.appendParameter(vmime::parameterFactory::getInstance()->create("param1", + vmime::word("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + vmime::charset("charset")))); + + assert_eq("4", "F: X; param1=abcdefgh;\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", p4.generate(25)); // max line length = 25 + } + + public: + + parameterTest() : suite("vmime::path") + { + vmime::platformDependant::setHandler(); + + add("Parse", testcase(this, "Parse", ¶meterTest::testParse)); + add("Generate", testcase(this, "Generate", ¶meterTest::testGenerate)); + + suite::main().add("vmime::parameter", this); + } + + }; + + parameterTest* theTest = new parameterTest(); +} diff --git a/vmime/contentDispositionField.hpp b/vmime/contentDispositionField.hpp index dc62baa0..74382c49 100644 --- a/vmime/contentDispositionField.hpp +++ b/vmime/contentDispositionField.hpp @@ -26,6 +26,7 @@ #include "vmime/contentDisposition.hpp" #include "vmime/dateTime.hpp" +#include "vmime/word.hpp" namespace vmime @@ -52,8 +53,8 @@ public: const datetime& getReadDate() const; void setReadDate(const datetime& readDate); - const string getFilename() const; - void setFilename(const string& filename); + const word getFilename() const; + void setFilename(const word& filename); const string getSize() const; void setSize(const string& size); diff --git a/vmime/defaultParameter.hpp b/vmime/defaultParameter.hpp new file mode 100644 index 00000000..7c6f7af7 --- /dev/null +++ b/vmime/defaultParameter.hpp @@ -0,0 +1,70 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#ifndef VMIME_DEFAULTPARAMETER_HPP_INCLUDED +#define VMIME_DEFAULTPARAMETER_HPP_INCLUDED + + +#include "vmime/parameter.hpp" +#include "vmime/parameterFactory.hpp" + +#include "vmime/word.hpp" + + +namespace vmime +{ + + +/** Default parameter implementation (with support for RFC-2231). + */ + +class defaultParameter : public parameter +{ + friend class parameterFactory::registerer ; + +protected: + + defaultParameter(); + +public: + + defaultParameter& operator=(const defaultParameter& other); + + const word& getValue() const; + word& getValue(); + + void setValue(const word& value); + void setValue(const component& value); + + void parse(const string& buffer, const string::size_type position, const string::size_type end, string::size_type* newPosition = NULL); + void generate(utility::outputStream& os, const string::size_type maxLineLength = lineLengthLimits::infinite, const string::size_type curLinePos = 0, string::size_type* newLinePos = NULL) const; + +private: + + void parse(const std::vector & chunks); + + + word m_value; +}; + + +} // vmime + + +#endif // VMIME_DEFAULTPARAMETER_HPP_INCLUDED diff --git a/vmime/parameter.hpp b/vmime/parameter.hpp index 605b7ba8..b62c87ab 100644 --- a/vmime/parameter.hpp +++ b/vmime/parameter.hpp @@ -36,6 +36,20 @@ class parameter : public component public: +#ifndef VMIME_BUILDING_DOC + + /** A single section of a multi-section parameter, + * as defined in RFC-2231/3. This is used when + * calling parse() on the parameter. + */ + struct valueChunk + { + bool encoded; + string data; + }; + +#endif // VMIME_BUILDING_DOC + parameter* clone() const; void copyFrom(const component& other); parameter& operator=(const parameter& other); @@ -79,6 +93,8 @@ private: string m_name; void generateValue(utility::outputStream& os, const string::size_type maxLineLength, const string::size_type curLinePos, string::size_type* newLinePos) const; + + virtual void parse(const std::vector & chunks); }; diff --git a/vmime/parameterFactory.hpp b/vmime/parameterFactory.hpp index 3c05f4f8..28d8cbdb 100644 --- a/vmime/parameterFactory.hpp +++ b/vmime/parameterFactory.hpp @@ -67,7 +67,23 @@ public: (utility::stringUtils::toLower(name), ®isterer::creator)); } + /** Create a new parameter and parse its value. The returned parameter + * can then be added in a vmime::parameterizedHeaderField object. + * + * @param name parameter name (ASCII characters only) + * @param value value to be parsed + * @return created parameter + */ parameter* create(const string& name, const string& value = NULL_STRING); + + /** Create a new parameter and set its value. The returned parameter + * can then be added in a vmime::parameterizedHeaderField object. + * + * @param name parameter name (ASCII characters only) + * @param value value to be set + * @return created parameter + */ + parameter* create(const string& name, const component& value); }; diff --git a/vmime/parameterizedHeaderField.hpp b/vmime/parameterizedHeaderField.hpp index 3e971c1b..ec85b766 100644 --- a/vmime/parameterizedHeaderField.hpp +++ b/vmime/parameterizedHeaderField.hpp @@ -32,6 +32,10 @@ namespace vmime { +/** A header field that can also contain parameters (name=value pairs). + * Parameters can be created using vmime::parameterFactory. + */ + class parameterizedHeaderField : virtual public headerField { friend class headerFieldFactory::registerer ; diff --git a/vmime/standardParams.hpp b/vmime/standardParams.hpp index 62f5774c..6c48835f 100644 --- a/vmime/standardParams.hpp +++ b/vmime/standardParams.hpp @@ -22,6 +22,7 @@ #include "vmime/genericParameter.hpp" +#include "vmime/defaultParameter.hpp" // Inclusion for field value types #include "vmime/dateTime.hpp" @@ -42,7 +43,6 @@ namespace vmime } -DECLARE_STANDARD_PARAM(defaultParameter, string); DECLARE_STANDARD_PARAM(dateParameter, datetime); DECLARE_STANDARD_PARAM(charsetParameter, charset); diff --git a/vmime/utility/stream.hpp b/vmime/utility/stream.hpp index 5875a1f1..fa7186d1 100644 --- a/vmime/utility/stream.hpp +++ b/vmime/utility/stream.hpp @@ -23,6 +23,7 @@ #include #include +#include #include "vmime/types.hpp" @@ -122,6 +123,18 @@ outputStream& operator<<(outputStream& os, const char (&str)[N]) } +template +outputStream& operator<<(outputStream& os, const T& t) +{ + std::ostringstream oss; + oss << t; + + os << oss.str(); + + return (os); +} + + /** Copy data from one stream into another stream using a buffered method. * * @param is input stream (source data)