diff options
Diffstat (limited to 'vmime-master/src/vmime/parameter.cpp')
-rw-r--r-- | vmime-master/src/vmime/parameter.cpp | 665 |
1 files changed, 665 insertions, 0 deletions
diff --git a/vmime-master/src/vmime/parameter.cpp b/vmime-master/src/vmime/parameter.cpp new file mode 100644 index 0000000..41b37a4 --- /dev/null +++ b/vmime-master/src/vmime/parameter.cpp @@ -0,0 +1,665 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002 Vincent Richard <vincent@vmime.org> +// +// 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 3 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// Linking this library statically or dynamically with other modules is making +// a combined work based on this library. Thus, the terms and conditions of +// the GNU General Public License cover the whole combination. +// + +#include "vmime/parameter.hpp" +#include "vmime/parserHelpers.hpp" + +#include "vmime/text.hpp" +#include "vmime/encoding.hpp" + +#include "vmime/utility/outputStreamAdapter.hpp" +#include "vmime/utility/outputStreamStringAdapter.hpp" + + +namespace vmime { + + +parameter::parameter(const string& name) + : m_name(name), + m_value(make_shared <word>()) { + +} + + +parameter::parameter(const string& name, const word& value) + : m_name(name), + m_value(make_shared <word>(value)) { + +} + + +parameter::parameter(const string& name, const string& value) + : m_name(name), + m_value(make_shared <word>(value)) { + +} + + +parameter::parameter(const parameter&) + : component() { + +} + + +shared_ptr <component> parameter::clone() const { + + shared_ptr <parameter> p = make_shared <parameter>(m_name); + p->copyFrom(*this); + + return (p); +} + + +void parameter::copyFrom(const component& other) { + + const parameter& param = dynamic_cast <const parameter&>(other); + + m_name = param.m_name; + m_value->copyFrom(*param.m_value); +} + + +parameter& parameter::operator=(const parameter& other) { + + copyFrom(other); + return (*this); +} + + +const string& parameter::getName() const { + + return m_name; +} + + +const word& parameter::getValue() const { + + return *m_value; +} + + +void parameter::setValue(const component& value) { + + std::ostringstream oss; + utility::outputStreamAdapter vos(oss); + + value.generate(vos); + + setValue(word(oss.str(), vmime::charsets::US_ASCII)); +} + + +void parameter::setValue(const word& value) { + + *m_value = value; +} + + +void parameter::parseImpl( + const parsingContext& ctx, + const string& buffer, + const size_t position, + const size_t end, + size_t* newPosition +) { + + m_value->setBuffer(string(buffer.begin() + position, buffer.begin() + end)); + + if (ctx.getInternationalizedEmailSupport()) { + m_value->setCharset(charset(charsets::UTF_8)); + } else { + m_value->setCharset(charset(charsets::US_ASCII)); + } + + if (newPosition) { + *newPosition = end; + } +} + + +void parameter::parse( + const parsingContext& ctx, + const std::vector <valueChunk>& chunks +) { + + bool foundCharsetChunk = false; + + charset ch(charsets::US_ASCII); + string lang; + + std::ostringstream value; + value.imbue(std::locale::classic()); + + for (std::vector <valueChunk>::size_type i = 0 ; i < chunks.size() ; ++i) { + + const valueChunk& chunk = chunks[i]; + + // Decode following data + if (chunk.encoded) { + + const size_t len = chunk.data.length(); + size_t 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" + size_t 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) { + + // Extract language + lang = chunk.data.substr(pos, q - pos); + + ++q; + pos = q; + } + + foundCharsetChunk = true; + } + + for (size_t i = pos ; i < len ; ++i) { + + const char c = chunk.data[i]; + + if (c == '%' && i + 2 < len) { + + unsigned int 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 << static_cast <char>(v); + + i += 2; // skip next 2 chars + + } else { + + value << c; + } + } + + // Simply copy data, as it is not encoded + } else { + + // This syntax is non-standard (expressly prohibited + // by RFC-2047), but is used by Mozilla: + // + // Content-Type: image/png; + // name="=?us-ascii?Q?Logo_VMime=2Epng?=" + + // Using 'vmime::text' to parse the data is safe even + // if the data is not encoded, because it can recover + // from parsing errors. + vmime::text t; + t.parse(ctx, chunk.data); + + if (t.getWordCount() != 0) { + + value << t.getWholeBuffer(); + + if (!foundCharsetChunk) { + + // This is still wrong. Each word can have it's own charset, and can + // be mixed (eg. iso-8859-1 and iso-2022-jp), but very unlikely. Real + // fix is to have parameters store a vmime::text instead of a + // vmime::word in m_value. But that changes the interface. + for (size_t i = 0 ; i < t.getWordCount() ; ++i) { + + if (t.getWordAt(i)->getCharset() != ch && ch == charsets::US_ASCII) { + + ch = t.getWordAt(i)->getCharset(); + break; + } + } + } + } + } + } + + m_value->setBuffer(value.str()); + m_value->setCharset(ch); + m_value->setLanguage(lang); +} + + +void parameter::generateImpl( + const generationContext& ctx, + utility::outputStream& os, + const size_t curLinePos, + size_t* newLinePos +) const { + + const string& name = m_name; + const string& value = m_value->getBuffer(); + + // For compatibility with implementations that do not understand RFC-2231, + // we may also generate a normal "7bit/us-ascii" parameter + generationContext::EncodedParameterValueModes + genMode = ctx.getEncodedParameterValueMode(); + +#if VMIME_ALWAYS_GENERATE_7BIT_PARAMETER + genMode = generationContext::PARAMETER_VALUE_RFC2231_AND_RFC2047; +#endif + + // [By Eugene A. Shatokhin] + // Note that if both the normal "7bit/us-ascii" value and the extended + // value are present, the latter can be ignored by mail processing systems. + // This may lead to annoying problems, for example, with strange names of + // attachments with all but 7-bit ascii characters removed, etc. To avoid + // this, I would suggest not to create "7bit/us-ascii" value if the extended + // value is to be generated. + + // A stream for a temporary storage + string sevenBitBuffer; + utility::outputStreamStringAdapter sevenBitStream(sevenBitBuffer); + + size_t pos = curLinePos; + + if (pos + name.length() + 10 + value.length() > ctx.getMaxLineLength()) { + + sevenBitStream << NEW_LINE_SEQUENCE; + pos = NEW_LINE_SEQUENCE_LENGTH; + } + + bool needQuoting = false; + bool needQuotedPrintable = false; + size_t valueLength = 0; + + // Use worst-case length name.length()+2 for 'name=' part of line + for (size_t i = 0 ; (i < value.length()) && (pos + name.length() + 2 + valueLength < ctx.getMaxLineLength() - 4) ; ++i, ++valueLength) { + + 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; + + default: + + if (!parserHelpers::isAscii(value[i])) { + needQuotedPrintable = true; + needQuoting = true; + } + + break; + } + } + + const bool cutValue = (valueLength != value.length()); // has the value been cut? + + if (needQuoting) { + + sevenBitStream << name << "=\""; + pos += name.length() + 2; + + } else { + + sevenBitStream << name << "="; + pos += name.length() + 1; + } + + // Check whether there is a recommended encoding for this charset. + // If so, the whole buffer will be encoded. Else, the number of + // 7-bit (ASCII) bytes in the input will be used to determine if + // we need to encode the whole buffer. + encoding recommendedEnc; + const bool alwaysEncode = m_value->getCharset().getRecommendedEncoding(recommendedEnc); + bool extended = alwaysEncode; + + if ((needQuotedPrintable || cutValue || !m_value->getLanguage().empty()) && + genMode != generationContext::PARAMETER_VALUE_NO_ENCODING) { + + // Send the name in quoted-printable, so outlook express et.al. + // will understand the real filename + size_t oldLen = sevenBitBuffer.length(); + m_value->generate(sevenBitStream); + pos += sevenBitBuffer.length() - oldLen; + extended = true; // also send with RFC-2231 encoding + + } else { + + // Do not chop off this value, but just add the complete name as one header line. + for (size_t i = 0, n = value.length(), curValueLength = 0 ; + i < n && curValueLength < valueLength ; ++i) { + + const char_t c = value[i]; + + if (/* needQuoting && */ (c == '"' || c == '\\')) { // 'needQuoting' is implicit + + sevenBitStream << '\\' << value[i]; // escape 'x' with '\x' + pos += 2; + + } else if (parserHelpers::isAscii(c)) { + + sevenBitStream << value[i]; + ++pos; + ++curValueLength; + + } else { + + extended = true; + } + } + + } // !needQuotedPrintable + + if (needQuoting) { + + sevenBitStream << '"'; + ++pos; + } + + if (genMode == generationContext::PARAMETER_VALUE_RFC2047_ONLY || + genMode == generationContext::PARAMETER_VALUE_RFC2231_AND_RFC2047) { + + os << sevenBitBuffer; + } + + // Also generate an extended parameter if the value contains 8-bit characters + // or is too long for a single line + if ((extended || cutValue) && + genMode != generationContext::PARAMETER_VALUE_NO_ENCODING && + genMode != generationContext::PARAMETER_VALUE_RFC2047_ONLY) { + + + if (genMode == generationContext::PARAMETER_VALUE_RFC2231_AND_RFC2047) { + + os << ';'; + ++pos; + + } else { + + // The data output to 'sevenBitBuffer' will be discarded in this case + pos = curLinePos; + } + + /* 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 size_t firstSectionLength = + name.length() + 4 /* *0*= */ + 2 /* '' */ + + m_value->getCharset().getName().length(); + + if (pos + firstSectionLength + 5 >= ctx.getMaxLineLength()) { + + 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; + size_t currentSectionLength = firstSectionLength; + + for (size_t 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 >= ctx.getMaxLineLength()) { + + 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 ';': + case ',': + case '(': + case ')': + case '<': + case '>': + case '@': + case ':': + case '/': + case '[': + case ']': + case '?': + case '=': + + encode = true; + break; + + default: + + encode = (!parserHelpers::isPrint(c) || + !parserHelpers::isAscii(c) || + alwaysEncode); + + 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; + } + } + + } else if (!(genMode == generationContext::PARAMETER_VALUE_RFC2047_ONLY || + genMode == generationContext::PARAMETER_VALUE_RFC2231_AND_RFC2047)) { + + // The value does not contain 8-bit characters and + // is short enough for a single line. + // "7bit/us-ascii" will suffice in this case. + + // Output what has been stored in temporary buffer so far + os << sevenBitBuffer; + } + + if (newLinePos) { + *newLinePos = pos; + } +} + + +size_t parameter::getGeneratedSize(const generationContext& ctx) { + + const string& name = m_name; + const string& value = m_value->getBuffer(); + + const size_t bytesNeedingEncoding = + value.length() - utility::stringUtils::countASCIIchars(value.begin(), value.end()); + + const size_t valueLength = value.length(); + + // Compute generated length in the very worst case + + // Non-encoded parameter + value (worst case: quoting + QP) + size_t len = name.length() + 1 /* = */ + 2 /* "" */ + 7 /* =?...?Q?...?= */ + + m_value->getCharset().getName().length() + valueLength + bytesNeedingEncoding * 2 + 1 /* ; */; + + // Encoded parameter + value + const size_t maxEncodedValueLengthOnLine = + ctx.getMaxLineLength() - 2 /* CRLF */ - NEW_LINE_SEQUENCE_LENGTH + - name.length() - 5 /* *00*= */ - 1 /* ; */; + + const size_t encodedValueLength = (valueLength + bytesNeedingEncoding * 2) + + m_value->getCharset().getName().length() + m_value->getLanguage().length() + 2 /* 2 x ' */; + + const size_t numberOfSections = 1 /* worst case: generation starts at the end of a line */ + + std::max(size_t(1), encodedValueLength / maxEncodedValueLengthOnLine); + + len += numberOfSections * (name.length() + 5 /* *00*= */ + 1 /* ; */ + 2 /* CRLF */ + NEW_LINE_SEQUENCE_LENGTH) + encodedValueLength; + + return len; + +} + + +const std::vector <shared_ptr <component> > parameter::getChildComponents() { + + std::vector <shared_ptr <component> > list; + + list.push_back(m_value); + + return list; +} + + +} // vmime |