Added support for RFC-2231.
This commit is contained in:
parent
18dacf00af
commit
f879a9794c
@ -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.
|
||||
|
||||
|
@ -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' ] ],
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
416
src/defaultParameter.cpp
Normal 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
|
@ -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());
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
249
tests/parser/parameterTest.cpp
Normal file
249
tests/parser/parameterTest.cpp
Normal 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", ¶meterTest::testParse));
|
||||
add("Generate", testcase(this, "Generate", ¶meterTest::testGenerate));
|
||||
|
||||
suite::main().add("vmime::parameter", this);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
parameterTest* theTest = new parameterTest();
|
||||
}
|
@ -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);
|
||||
|
70
vmime/defaultParameter.hpp
Normal file
70
vmime/defaultParameter.hpp
Normal 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
|
@ -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);
|
||||
};
|
||||
|
||||
|
||||
|
@ -67,7 +67,23 @@ public:
|
||||
(utility::stringUtils::toLower(name), ®isterer<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);
|
||||
};
|
||||
|
||||
|
||||
|
@ -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>;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user