diff options
Diffstat (limited to 'src/body.cpp')
-rw-r--r-- | src/body.cpp | 543 |
1 files changed, 543 insertions, 0 deletions
diff --git a/src/body.cpp b/src/body.cpp new file mode 100644 index 00000000..1b5c853b --- /dev/null +++ b/src/body.cpp @@ -0,0 +1,543 @@ +// +// VMime library (http://vmime.sourceforge.net) +// Copyright (C) 2002-2004 Vincent Richard <[email protected]> +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include "bodyPart.hpp" +#include "body.hpp" + +#include "options.hpp" + +#include "contentTypeField.hpp" +#include "contentEncodingField.hpp" + +#include "utility/random.hpp" + +#include "parserHelpers.hpp" + + +namespace vmime +{ + + +body::body(bodyPart& part) + : parts(*this), m_part(part), m_header(part.header()) +{ +} + + +void body::parse(const string& buffer, const string::size_type position, + const string::size_type end, string::size_type* newPosition) +{ + parts.clear(); + + // Check whether the body is a MIME-multipart + bool isMultipart = false; + string boundary; + + try + { + const contentTypeField& ctf = dynamic_cast <contentTypeField&> + (m_header.fields.find(headerField::ContentType)); + + if (ctf.value().type() == mediaTypes::MULTIPART) + { + isMultipart = true; + + try + { + boundary = ctf.boundary(); + } + catch (exceptions::no_such_parameter&) + { + // No "boundary" parameter specified: we can try to + // guess it by scanning the body contents... + string::size_type pos = buffer.find("\n--", position); + + if ((pos != string::npos) && (pos < end)) + { + pos += 3; + + const string::size_type start = pos; + + char_t c = buffer[pos]; + string::size_type length = 0; + + // We have to stop after a reasonnably long boundary length (100) + // not to take the whole body contents for a boundary... + while (pos < end && length < 100 && !(c == '\r' || c == '\n')) + { + ++length; + c = buffer[pos++]; + } + + if (pos < end && length < 100) + { + // RFC #1521, Page 31: + // "...the boundary parameter, which consists of 1 to 70 + // characters from a set of characters known to be very + // robust through email gateways, and NOT ending with + // white space..." + while (pos != start && isspace(buffer[pos - 1])) + --pos; + + boundary = string(buffer.begin() + start, + buffer.begin() + pos); + } + } + } + } + } + catch (exceptions::no_such_field&) + { + // No "Content-Type" field... + } + + // This is a multi-part body + if (isMultipart && !boundary.empty()) + { + const string boundarySep("--" + boundary); + + string::size_type partStart = position; + string::size_type pos = buffer.find(boundarySep, position); + + bool lastPart = false; + + if (pos != string::npos && pos < end) + { + m_prologText = string(buffer.begin() + position, buffer.begin() + pos); + } + + for (int index = 0 ; !lastPart && (pos != string::npos) && (pos < end) ; ++index) + { + string::size_type partEnd = pos; + + // Get rid of the [CR]LF just before the boundary string + if (pos - 1 >= position && buffer[pos - 1] == '\n') --partEnd; + if (pos - 2 >= position && buffer[pos - 2] == '\r') --partEnd; + + // Check whether it is the last part (boundary terminated by "--") + pos += boundarySep.length(); + + if (pos + 1 < end && buffer[pos] == '-' && buffer[pos + 1] == '-') + { + lastPart = true; + pos += 2; + } + + // RFC #1521, Page 31: + // "...(If a boundary appears to end with white space, the + // white space must be presumed to have been added by a + // gateway, and must be deleted.)..." + while (pos < end && (buffer[pos] == ' ' || buffer[pos] == '\t')) + ++pos; + + // End of boundary line + if (pos + 1 < end && buffer[pos] == '\r' && buffer[pos + 1] =='\n') + { + pos += 2; + } + else if (pos < end && buffer[pos] == '\n') + { + ++pos; + } + + if (index > 0) + { + bodyPart* part = new bodyPart; + + try + { + part->parse(buffer, partStart, partEnd, NULL); + } + catch (std::exception&) + { + delete (part); + throw; + } + + parts.m_parts.push_back(part); + } + + partStart = pos; + pos = buffer.find(boundarySep, partStart); + } + + if (partStart < end) + m_epilogText = string(buffer.begin() + partStart, buffer.begin() + end); + } + // Treat the contents as 'simple' data + else + { + // Extract the (encoded) contents + m_contents.set(buffer, position, end, encoding()); + } + + if (newPosition) + *newPosition = end; +} + + +void body::generate(utility::outputStream& os, const string::size_type maxLineLength, + const string::size_type /* curLinePos */, string::size_type* newLinePos) const +{ + // MIME-Multipart + if (parts.size() != 0) + { + string boundary; + + try + { + contentTypeField& ctf = dynamic_cast<contentTypeField&> + (m_header.fields.find(headerField::ContentType)); + + boundary = ctf.boundary(); + } + catch (exceptions::no_such_field&) + { + // Warning: no content-type and no boundary string specified! + boundary = generateRandomBoundaryString(); + } + catch (exceptions::no_such_parameter&) + { + // Warning: no boundary string specified! + boundary = generateRandomBoundaryString(); + } + + const string& prologText = + m_prologText.empty() + ? (isRootPart() + ? options::getInstance()->multipart.prologText() + : NULL_STRING + ) + : m_prologText; + + const string& epilogText = + m_epilogText.empty() + ? (isRootPart() + ? options::getInstance()->multipart.epilogText() + : NULL_STRING + ) + : m_epilogText; + + if (!prologText.empty()) + { + encodeAndFoldText(os, text(word(prologText, charset())), maxLineLength, 0, + NULL, encodeAndFoldFlags::forceNoEncoding | encodeAndFoldFlags::noNewLineSequence); + + os << CRLF; + } + + os << "--" << boundary; + + for (std::vector <bodyPart*>::const_iterator + p = parts.m_parts.begin() ; p != parts.m_parts.end() ; ++p) + { + os << CRLF; + + (*p)->generate(os, maxLineLength, 0); + + os << CRLF << "--" << boundary; + } + + os << "--" << CRLF; + + if (!epilogText.empty()) + { + encodeAndFoldText(os, text(word(epilogText, charset())), maxLineLength, 0, + NULL, encodeAndFoldFlags::forceNoEncoding | encodeAndFoldFlags::noNewLineSequence); + + os << CRLF; + } + + if (newLinePos) + *newLinePos = 0; + } + // Simple body + else + { + // Generate the contents + m_contents.generate(os, encoding(), maxLineLength); + } +} + + +/* + RFC #1521, Page 32: + 7.2.1. Multipart: The common syntax + + "...Encapsulation boundaries must not appear within the + encapsulations, and must be no longer than 70 characters..." + + + boundary := 0*69<bchars> bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / "+" /"_" + / "," / "-" / "." / "/" / ":" / "=" / "?" +*/ + +const string body::generateRandomBoundaryString() +{ + // 64 characters that can be _safely_ used in a boundary string + static const char bchars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-+"; + + /* + RFC #1521, Page 19: + + Since the hyphen character ("-") is represented as itself in the + Quoted-Printable encoding, care must be taken, when encapsulating a + quoted-printable encoded body in a multipart entity, to ensure that + the encapsulation boundary does not appear anywhere in the encoded + body. (A good strategy is to choose a boundary that includes a + character sequence such as "=_" which can never appear in a quoted- + printable body. See the definition of multipart messages later in + this document.) + */ + + string::value_type boundary[2 + 48 + 1] = { 0 }; + + boundary[0] = '='; + boundary[1] = '_'; + + // Generate a string of random characters + unsigned int r = utility::random::time(); + unsigned int m = sizeof(unsigned int); + + for (size_t i = 2 ; i < (sizeof(boundary) / sizeof(boundary[0]) - 1) ; ++i) + { + boundary[i] = bchars[r & 63]; + r >>= 6; + + if (--m == 0) + { + r = utility::random::next(); + m = sizeof(unsigned int); + } + } + + return (string(boundary)); +} + + +const bool body::isValidBoundary(const string& boundary) +{ + static const string validChars("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'()+_,-./:=?"); + + const string::const_iterator end = boundary.end(); + bool valid = false; + + if (boundary.length() > 0 && boundary.length() < 70) + { + const string::value_type last = *(end - 1); + + if (!(last == ' ' || last == '\t' || last == '\n')) + { + valid = true; + + for (string::const_iterator i = boundary.begin() ; valid && i != end ; ++i) + valid = (validChars.find_first_of(*i) != string::npos); + } + } + + return (valid); +} + + +// +// Quick-access functions +// + +const mediaType body::contentType() const +{ + try + { + const contentTypeField& ctf = dynamic_cast<contentTypeField&>(m_header.fields.find(headerField::ContentType)); + return (ctf.value()); + } + catch (exceptions::no_such_field&) + { + // Defaults to "text/plain" (RFC-1521) + return (mediaType(mediaTypes::TEXT, mediaTypes::TEXT_PLAIN)); + } +} + + +const class charset body::charset() const +{ + try + { + const contentTypeField& ctf = dynamic_cast<contentTypeField&>(m_header.fields.find(headerField::ContentType)); + const class charset& cs = ctf.charset(); + + return (cs); + } + catch (exceptions::no_such_parameter&) + { + // Defaults to "us-ascii" (RFC-1521) + return (vmime::charset(charsets::US_ASCII)); + } + catch (exceptions::no_such_field&) + { + // Defaults to "us-ascii" (RFC-1521) + return (vmime::charset(charsets::US_ASCII)); + } +} + + +const class encoding body::encoding() const +{ + try + { + const contentEncodingField& cef = m_header.fields.ContentTransferEncoding(); + return (cef.value()); + } + catch (exceptions::no_such_field&) + { + // Defaults to "7bit" (RFC-1521) + return (vmime::encoding(encodingTypes::SEVEN_BIT)); + } +} + + +const bool body::isRootPart() const +{ + return (m_part.parent() == NULL); +} + + +body& body::operator=(const body& b) +{ + m_prologText = b.m_prologText; + m_epilogText = b.m_epilogText; + + m_contents = b.m_contents; + + parts = b.parts; + + return (*this); +} + + +///////////////////// +// Parts container // +///////////////////// + + +body::partsContainer::partsContainer(class body& body) + : m_body(body) +{ +} + + +// Part insertion +void body::partsContainer::append(bodyPart* part) +{ + part->m_parent = &(m_body.m_part); + + m_parts.push_back(part); + + // Check whether we have a boundary string + try + { + contentTypeField& ctf = dynamic_cast<contentTypeField&> + (m_body.m_header.fields.find(headerField::ContentType)); + + try + { + const string boundary = ctf.boundary(); + + if (boundary.empty() || !isValidBoundary(boundary)) + throw exceptions::no_such_parameter("boundary"); // to generate a new one + } + catch (exceptions::no_such_parameter&) + { + // No "boundary" parameter: generate a random one. + ctf.boundary() = generateRandomBoundaryString(); + } + + if (ctf.value().type() != mediaTypes::MULTIPART) + { + // Warning: multi-part body but the Content-Type is + // not specified as "multipart/..." + } + } + catch (exceptions::no_such_field&) + { + // No "Content-Type" field: create a new one and generate + // a random boundary string. + contentTypeField& ctf = dynamic_cast<contentTypeField&> + (m_body.m_header.fields.get(headerField::ContentType)); + + ctf.value() = mediaType(mediaTypes::MULTIPART, mediaTypes::MULTIPART_MIXED); + ctf.boundary() = generateRandomBoundaryString(); + } +} + + +void body::partsContainer::insert(const iterator it, bodyPart* part) +{ + part->m_parent = &(m_body.m_part); + + m_parts.insert(it.m_iterator, part); +} + + +// Part removing +void body::partsContainer::remove(const iterator it) +{ + delete (*it.m_iterator); + m_parts.erase(it.m_iterator); +} + + +void body::partsContainer::clear() +{ + free_container(m_parts); +} + + +body::partsContainer::~partsContainer() +{ + clear(); +} + + +body::partsContainer& body::partsContainer::operator=(const partsContainer& c) +{ + std::vector <bodyPart*> parts; + + for (std::vector <bodyPart*>::const_iterator it = c.m_parts.begin() ; it != c.m_parts.end() ; ++it) + { + bodyPart* p = (*it)->clone(); + p->m_parent = &(m_body.m_part); + + parts.push_back(p); + } + + for (std::vector <bodyPart*>::iterator it = m_parts.begin() ; it != m_parts.end() ; ++it) + delete (*it); + + m_parts.resize(parts.size()); + std::copy(parts.begin(), parts.end(), m_parts.begin()); + + return (*this); +} + + +} // vmime |