From f879a9794c4908df8e7484250b56b13342a66038 Mon Sep 17 00:00:00 2001
From: Vincent Richard <vincent@vincent-richard.net>
Date: Tue, 12 Apr 2005 18:42:54 +0000
Subject: [PATCH] Added support for RFC-2231.

---
 ChangeLog                          |   9 +-
 SConstruct                         |   2 +
 src/contentDispositionField.cpp    |   8 +-
 src/contentTypeField.cpp           |   8 +-
 src/defaultParameter.cpp           | 416 +++++++++++++++++++++++++++++
 src/parameter.cpp                  |  22 +-
 src/parameterFactory.cpp           |  23 ++
 src/parameterizedHeaderField.cpp   | 107 +++++++-
 tests/parser/parameterTest.cpp     | 249 +++++++++++++++++
 vmime/contentDispositionField.hpp  |   5 +-
 vmime/defaultParameter.hpp         |  70 +++++
 vmime/parameter.hpp                |  16 ++
 vmime/parameterFactory.hpp         |  16 ++
 vmime/parameterizedHeaderField.hpp |   4 +
 vmime/standardParams.hpp           |   2 +-
 vmime/utility/stream.hpp           |  13 +
 16 files changed, 951 insertions(+), 19 deletions(-)
 create mode 100644 src/defaultParameter.cpp
 create mode 100644 tests/parser/parameterTest.cpp
 create mode 100644 vmime/defaultParameter.hpp

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  <vincent@vincent-richard.net>
+
+ * 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  <vincent@vincent-richard.net>
 
  * 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 <vincent@vincent-richard.net>
+//
+// 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 <vincent@vincent-richard.net>
+//
+// 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 <vincent@vincent-richard.net>
+//
+// 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)