Added support for RFC-2231.

This commit is contained in:
Vincent Richard 2005-04-12 18:42:54 +00:00
parent 18dacf00af
commit f879a9794c
16 changed files with 951 additions and 19 deletions

View File

@ -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

View File

@ -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' ] ],

View File

@ -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));
}

View File

@ -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));
}

416
src/defaultParameter.cpp Normal file
View File

@ -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

View File

@ -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());

View File

@ -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

View File

@ -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)

View File

@ -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();
}

View File

@ -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);

View File

@ -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

View File

@ -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);
};

View File

@ -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);
};

View File

@ -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>;

View File

@ -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);

View File

@ -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)