aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog9
-rw-r--r--SConstruct2
-rw-r--r--src/contentDispositionField.cpp8
-rw-r--r--src/contentTypeField.cpp8
-rw-r--r--src/defaultParameter.cpp416
-rw-r--r--src/parameter.cpp22
-rw-r--r--src/parameterFactory.cpp23
-rw-r--r--src/parameterizedHeaderField.cpp107
-rw-r--r--tests/parser/parameterTest.cpp249
-rw-r--r--vmime/contentDispositionField.hpp5
-rw-r--r--vmime/defaultParameter.hpp70
-rw-r--r--vmime/parameter.hpp16
-rw-r--r--vmime/parameterFactory.hpp16
-rw-r--r--vmime/parameterizedHeaderField.hpp4
-rw-r--r--vmime/standardParams.hpp2
-rw-r--r--vmime/utility/stream.hpp13
16 files changed, 951 insertions, 19 deletions
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 <[email protected]>
+
+ * 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 <[email protected]>
* 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 <const defaultParameter&>(*findParameter("filename")).getValue());
}
-void contentDispositionField::setFilename(const string& filename)
+void contentDispositionField::setFilename(const word& filename)
{
dynamic_cast <defaultParameter&>(*getParameter("filename")).setValue(filename);
}
@@ -88,13 +88,13 @@ void contentDispositionField::setFilename(const string& filename)
const string contentDispositionField::getSize() const
{
- return (dynamic_cast <const defaultParameter&>(*findParameter("size")).getValue());
+ return (dynamic_cast <const defaultParameter&>(*findParameter("size")).getValue().getBuffer());
}
void contentDispositionField::setSize(const string& size)
{
- dynamic_cast <defaultParameter&>(*getParameter("size")).setValue(size);
+ dynamic_cast <defaultParameter&>(*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 <const defaultParameter&>(*findParameter("boundary")).getValue());
+ return (dynamic_cast <const defaultParameter&>(*findParameter("boundary")).getValue().getBuffer());
}
void contentTypeField::setBoundary(const string& boundary)
{
- dynamic_cast <defaultParameter&>(*getParameter("boundary")).setValue(boundary);
+ dynamic_cast <defaultParameter&>(*getParameter("boundary")).setValue(word(boundary));
}
@@ -64,13 +64,13 @@ void contentTypeField::setCharset(const charset& ch)
const string contentTypeField::getReportType() const
{
- return (dynamic_cast <const defaultParameter&>(*findParameter("report-type")).getValue());
+ return (dynamic_cast <const defaultParameter&>(*findParameter("report-type")).getValue().getBuffer());
}
void contentTypeField::setReportType(const string& reportType)
{
- dynamic_cast <defaultParameter&>(*getParameter("report-type")).setValue(reportType);
+ dynamic_cast <defaultParameter&>(*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 <[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 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 <const word&>(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 <valueChunk>& chunks)
+{
+ bool foundCharsetChunk = false;
+
+ charset ch(charsets::US_ASCII);
+ std::ostringstream value;
+
+ for (std::vector <valueChunk>::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 <string> 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 <unsigned char>(c) / 16;
+ const int h2 = static_cast <unsigned char>(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 <valueChunk>& chunks)
+{
+ string value;
+
+ for (std::vector <valueChunk>::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 <defaultParameter>::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 <parameter::valueChunk> 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 <string, paramInfo> 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 <string, paramInfo>::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 <string, paramInfo>::value_type(name, info));
+ }
}
// Skip white-spaces after this parameter
while (p < pend && parserHelpers::isSpace(*p)) ++p;
}
}
+
+ for (std::map <string, paramInfo>::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 <[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 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 <iostream>
+#include <ostream>
+
+#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 <vmime::string> 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 <vmime::defaultParameter*> \
+ (p.getParameterAt(n))->getValue().getCharset().generate())
+#define PARAM_BUFFER(p, n) (static_cast <vmime::defaultParameter*> \
+ (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<vmime::platforms::posix::posixHandler>();
+
+ add("Parse", testcase(this, "Parse", &parameterTest::testParse));
+ add("Generate", testcase(this, "Generate", &parameterTest::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 <[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 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 <defaultParameter>;
+
+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 <valueChunk>& 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 <valueChunk>& 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), &registerer<T>::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 <parameterizedHeaderField>;
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 <istream>
#include <ostream>
+#include <sstream>
#include "vmime/types.hpp"
@@ -122,6 +123,18 @@ outputStream& operator<<(outputStream& os, const char (&str)[N])
}
+template <typename T>
+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)