summaryrefslogtreecommitdiff
path: root/indra/llmessage/llmime.cpp
diff options
context:
space:
mode:
authorJames Cook <james@lindenlab.com>2007-01-02 08:33:20 +0000
committerJames Cook <james@lindenlab.com>2007-01-02 08:33:20 +0000
commit420b91db29485df39fd6e724e782c449158811cb (patch)
treeb471a94563af914d3ed3edd3e856d21cb1b69945 /indra/llmessage/llmime.cpp
Print done when done.
Diffstat (limited to 'indra/llmessage/llmime.cpp')
-rw-r--r--indra/llmessage/llmime.cpp613
1 files changed, 613 insertions, 0 deletions
diff --git a/indra/llmessage/llmime.cpp b/indra/llmessage/llmime.cpp
new file mode 100644
index 0000000000..9df9cdf3a7
--- /dev/null
+++ b/indra/llmessage/llmime.cpp
@@ -0,0 +1,613 @@
+/**
+ * @file llmime.cpp
+ * @author Phoenix
+ * @date 2006-12-20
+ * @brief Implementation of mime tools.
+ *
+ * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc.
+ * $License$
+ */
+
+#include "linden_common.h"
+#include "llmime.h"
+
+#include <vector>
+
+#include "llmemorystream.h"
+
+/**
+ * Useful constants.
+ */
+// Headers specified in rfc-2045 will be canonicalized below.
+static const std::string CONTENT_LENGTH("Content-Length");
+static const std::string CONTENT_TYPE("Content-Type");
+static const S32 KNOWN_HEADER_COUNT = 6;
+static const std::string KNOWN_HEADER[KNOWN_HEADER_COUNT] =
+{
+ CONTENT_LENGTH,
+ CONTENT_TYPE,
+ std::string("MIME-Version"),
+ std::string("Content-Transfer-Encoding"),
+ std::string("Content-ID"),
+ std::string("Content-Description"),
+};
+
+// parser helpers
+static const std::string MULTIPART("multipart");
+static const std::string BOUNDARY("boundary");
+static const std::string END_OF_CONTENT_PARAMETER("\r\n ;\t");
+static const std::string SEPARATOR_PREFIX("--");
+//static const std::string SEPARATOR_SUFFIX("\r\n");
+
+/*
+Content-Type: multipart/mixed; boundary="segment"
+Content-Length: 24832
+
+--segment
+Content-Type: image/j2c
+Content-Length: 23715
+
+<data>
+
+--segment
+Content-Type: text/xml; charset=UTF-8
+
+<meta data>
+EOF
+
+*/
+
+/**
+ * LLMimeIndex
+ */
+
+/**
+ * @class LLMimeIndex::Impl
+ * @brief Implementation details of the mime index class.
+ * @see LLMimeIndex
+ */
+class LLMimeIndex::Impl
+{
+public:
+ Impl() : mOffset(-1), mUseCount(1)
+ {}
+ Impl(LLSD headers, S32 offset) :
+ mHeaders(headers), mOffset(offset), mUseCount(1)
+ {}
+public:
+ LLSD mHeaders;
+ S32 mOffset;
+ S32 mUseCount;
+
+ typedef std::vector<LLMimeIndex> sub_part_t;
+ sub_part_t mAttachments;
+};
+
+LLSD LLMimeIndex::headers() const
+{
+ return mImpl->mHeaders;
+}
+
+S32 LLMimeIndex::offset() const
+{
+ return mImpl->mOffset;
+}
+
+S32 LLMimeIndex::contentLength() const
+{
+ // Find the content length in the headers.
+ S32 length = -1;
+ LLSD content_length = mImpl->mHeaders[CONTENT_LENGTH];
+ if(content_length.isDefined())
+ {
+ length = content_length.asInteger();
+ }
+ return length;
+}
+
+std::string LLMimeIndex::contentType() const
+{
+ std::string type;
+ LLSD content_type = mImpl->mHeaders[CONTENT_TYPE];
+ if(content_type.isDefined())
+ {
+ type = content_type.asString();
+ }
+ return type;
+}
+
+bool LLMimeIndex::isMultipart() const
+{
+ bool multipart = false;
+ LLSD content_type = mImpl->mHeaders[CONTENT_TYPE];
+ if(content_type.isDefined())
+ {
+ std::string type = content_type.asString();
+ int comp = type.compare(0, MULTIPART.size(), MULTIPART);
+ if(0 == comp)
+ {
+ multipart = true;
+ }
+ }
+ return multipart;
+}
+
+S32 LLMimeIndex::subPartCount() const
+{
+ return mImpl->mAttachments.size();
+}
+
+LLMimeIndex LLMimeIndex::subPart(S32 index) const
+{
+ LLMimeIndex part;
+ if((index >= 0) && (index < (S32)mImpl->mAttachments.size()))
+ {
+ part = mImpl->mAttachments[index];
+ }
+ return part;
+}
+
+LLMimeIndex::LLMimeIndex() : mImpl(new LLMimeIndex::Impl)
+{
+}
+
+LLMimeIndex::LLMimeIndex(LLSD headers, S32 content_offset) :
+ mImpl(new LLMimeIndex::Impl(headers, content_offset))
+{
+}
+
+LLMimeIndex::LLMimeIndex(const LLMimeIndex& mime) :
+ mImpl(mime.mImpl)
+{
+ ++mImpl->mUseCount;
+}
+
+LLMimeIndex::~LLMimeIndex()
+{
+ if(0 == --mImpl->mUseCount)
+ {
+ delete mImpl;
+ }
+}
+
+LLMimeIndex& LLMimeIndex::operator=(const LLMimeIndex& mime)
+{
+ // Increment use count first so that we handle self assignment
+ // automatically.
+ ++mime.mImpl->mUseCount;
+ if(0 == --mImpl->mUseCount)
+ {
+ delete mImpl;
+ }
+ mImpl = mime.mImpl;
+ return *this;
+}
+
+bool LLMimeIndex::attachSubPart(LLMimeIndex sub_part)
+{
+ // *FIX: Should we check for multi-part?
+ if(mImpl->mAttachments.size() < S32_MAX)
+ {
+ mImpl->mAttachments.push_back(sub_part);
+ return true;
+ }
+ return false;
+}
+
+/**
+ * LLMimeParser
+ */
+/**
+ * @class LLMimeParser::Impl
+ * @brief Implementation details of the mime parser class.
+ * @see LLMimeParser
+ */
+class LLMimeParser::Impl
+{
+public:
+ // @brief Constructor.
+ Impl();
+
+ // @brief Reset this for a new parse.
+ void reset();
+
+ /**
+ * @brief Parse a mime entity to find the index information.
+ *
+ * This method will scan the istr until a single complete mime
+ * entity is read, an EOF, or limit bytes have been scanned. The
+ * istr will be modified by this parsing, so pass in a temporary
+ * stream or rewind/reset the stream after this call.
+ * @param istr An istream which contains a mime entity.
+ * @param limit The maximum number of bytes to scan.
+ * @param separator The multipart separator if it is known.
+ * @param is_subpart Set true if parsing a multipart sub part.
+ * @param index[out] The parsed output.
+ * @return Returns true if an index was parsed and no errors occurred.
+ */
+ bool parseIndex(
+ std::istream& istr,
+ S32 limit,
+ const std::string& separator,
+ bool is_subpart,
+ LLMimeIndex& index);
+
+protected:
+ /**
+ * @brief parse the headers.
+ *
+ * At the end of a successful parse, mScanCount will be at the
+ * start of the content.
+ * @param istr The input stream.
+ * @param limit maximum number of bytes to process
+ * @param headers[out] A map of the headers found.
+ * @return Returns true if the parse was successful.
+ */
+ bool parseHeaders(std::istream& istr, S32 limit, LLSD& headers);
+
+ /**
+ * @brief Figure out the separator string from a content type header.
+ *
+ * @param multipart_content_type The content type value from the headers.
+ * @return Returns the separator string.
+ */
+ std::string findSeparator(std::string multipart_content_type);
+
+ /**
+ * @brief Scan through istr past the separator.
+ *
+ * @param istr The input stream.
+ * @param limit Maximum number of bytes to scan.
+ * @param separator The multipart separator.
+ */
+ void scanPastSeparator(
+ std::istream& istr,
+ S32 limit,
+ const std::string& separator);
+
+ /**
+ * @brief Scan through istr past the content of the current mime part.
+ *
+ * @param istr The input stream.
+ * @param limit Maximum number of bytes to scan.
+ * @param headers The headers for this mime part.
+ * @param separator The multipart separator if known.
+ */
+ void scanPastContent(
+ std::istream& istr,
+ S32 limit,
+ LLSD headers,
+ const std::string separator);
+
+ /**
+ * @brief Eat CRLF.
+ *
+ * This method has no concept of the limit, so ensure you have at
+ * least 2 characters left to eat before hitting the limit. This
+ * method will increment mScanCount as it goes.
+ * @param istr The input stream.
+ * @return Returns true if CRLF was found and consumed off of istr.
+ */
+ bool eatCRLF(std::istream& istr);
+
+ // @brief Returns true if parsing should continue.
+ bool continueParse() const { return (!mError && mContinue); }
+
+ // @brief anonymous enumeration for parse buffer size.
+ enum
+ {
+ LINE_BUFFER_LENGTH = 1024
+ };
+
+protected:
+ S32 mScanCount;
+ bool mContinue;
+ bool mError;
+ char mBuffer[LINE_BUFFER_LENGTH];
+};
+
+LLMimeParser::Impl::Impl()
+{
+ reset();
+}
+
+void LLMimeParser::Impl::reset()
+{
+ mScanCount = 0;
+ mContinue = true;
+ mError = false;
+ mBuffer[0] = '\0';
+}
+
+bool LLMimeParser::Impl::parseIndex(
+ std::istream& istr,
+ S32 limit,
+ const std::string& separator,
+ bool is_subpart,
+ LLMimeIndex& index)
+{
+ LLSD headers;
+ bool parsed_something = false;
+ if(parseHeaders(istr, limit, headers))
+ {
+ parsed_something = true;
+ LLMimeIndex mime(headers, mScanCount);
+ index = mime;
+ if(index.isMultipart())
+ {
+ // Figure out the separator, scan past it, and recurse.
+ std::string ct = headers[CONTENT_TYPE].asString();
+ std::string sep = findSeparator(ct);
+ scanPastSeparator(istr, limit, sep);
+ while(continueParse() && parseIndex(istr, limit, sep, true, mime))
+ {
+ index.attachSubPart(mime);
+ }
+ }
+ else
+ {
+ // Scan to the end of content.
+ scanPastContent(istr, limit, headers, separator);
+ if(is_subpart)
+ {
+ scanPastSeparator(istr, limit, separator);
+ }
+ }
+ }
+ if(mError) return false;
+ return parsed_something;
+}
+
+bool LLMimeParser::Impl::parseHeaders(
+ std::istream& istr,
+ S32 limit,
+ LLSD& headers)
+{
+ while(continueParse())
+ {
+ // Get the next line.
+ // We subtract 1 from the limit so that we make sure
+ // not to read past limit when we get() the newline.
+ S32 max_get = llmin((S32)LINE_BUFFER_LENGTH, limit - mScanCount - 1);
+ istr.getline(mBuffer, max_get, '\r');
+ mScanCount += istr.gcount();
+ int c = istr.get();
+ if(EOF == c)
+ {
+ mContinue = false;
+ return false;
+ }
+ ++mScanCount;
+ if(c != '\n')
+ {
+ mError = true;
+ return false;
+ }
+ if(mScanCount >= limit)
+ {
+ mContinue = false;
+ }
+
+ // Check if that's the end of headers.
+ if('\0' == mBuffer[0])
+ {
+ break;
+ }
+
+ // Split out the name and value.
+ // *NOTE: The use of strchr() here is safe since mBuffer is
+ // guaranteed to be NULL terminated from the call to getline()
+ // above.
+ char* colon = strchr(mBuffer, ':');
+ if(!colon)
+ {
+ mError = true;
+ return false;
+ }
+
+ // Cononicalize the name part, and store the name: value in
+ // the headers structure. We do this by iterating through
+ // 'known' headers and replacing the value found with the
+ // correct one.
+ // *NOTE: Not so efficient, but iterating through a small
+ // subset should not be too much of an issue.
+ std::string name(mBuffer, colon++ - mBuffer);
+ while(isspace(*colon)) ++colon;
+ std::string value(colon);
+ for(S32 ii = 0; ii < KNOWN_HEADER_COUNT; ++ii)
+ {
+ if(0 == LLString::compareInsensitive(
+ name.c_str(),
+ KNOWN_HEADER[ii].c_str()))
+ {
+ name = KNOWN_HEADER[ii];
+ break;
+ }
+ }
+ headers[name] = value;
+ }
+ if(headers.isUndefined()) return false;
+ return true;
+}
+
+std::string LLMimeParser::Impl::findSeparator(std::string header)
+{
+ // 01234567890
+ //Content-Type: multipart/mixed; boundary="segment"
+ std::string separator;
+ std::string::size_type pos = header.find(BOUNDARY);
+ if(std::string::npos == pos) return separator;
+ pos += BOUNDARY.size() + 1;
+ std::string::size_type end;
+ if(header[pos] == '"')
+ {
+ // the boundary is quoted, find the end from pos, and take the
+ // substring.
+ end = header.find('"', ++pos);
+ if(std::string::npos == end)
+ {
+ // poorly formed boundary.
+ mError = true;
+ }
+ }
+ else
+ {
+ // otherwise, it's every character until a whitespace, end of
+ // line, or another parameter begins.
+ end = header.find_first_of(END_OF_CONTENT_PARAMETER, pos);
+ if(std::string::npos == end)
+ {
+ // it goes to the end of the string.
+ end = header.size();
+ }
+ }
+ if(!mError) separator = header.substr(pos, end - pos);
+ return separator;
+}
+
+void LLMimeParser::Impl::scanPastSeparator(
+ std::istream& istr,
+ S32 limit,
+ const std::string& sep)
+{
+ std::ostringstream ostr;
+ ostr << SEPARATOR_PREFIX << sep;
+ std::string separator = ostr.str();
+ bool found_separator = false;
+ while(!found_separator && continueParse())
+ {
+ // Subtract 1 from the limit so that we make sure not to read
+ // past limit when we get() the newline.
+ S32 max_get = llmin((S32)LINE_BUFFER_LENGTH, limit - mScanCount - 1);
+ istr.getline(mBuffer, max_get, '\r');
+ mScanCount += istr.gcount();
+ if(istr.gcount() >= LINE_BUFFER_LENGTH - 1)
+ {
+ // that's way too long to be a separator, so ignore it.
+ continue;
+ }
+ int c = istr.get();
+ if(EOF == c)
+ {
+ mContinue = false;
+ return;
+ }
+ ++mScanCount;
+ if(c != '\n')
+ {
+ mError = true;
+ return;
+ }
+ if(mScanCount >= limit)
+ {
+ mContinue = false;
+ }
+ if(0 == LLString::compareStrings(mBuffer, separator.c_str()))
+ {
+ found_separator = true;
+ }
+ }
+}
+
+void LLMimeParser::Impl::scanPastContent(
+ std::istream& istr,
+ S32 limit,
+ LLSD headers,
+ const std::string separator)
+{
+ if(headers.has(CONTENT_LENGTH))
+ {
+ S32 content_length = headers[CONTENT_LENGTH].asInteger();
+ // Subtract 2 here for the \r\n after the content.
+ S32 max_skip = llmin(content_length, limit - mScanCount - 2);
+ istr.ignore(max_skip);
+ mScanCount += max_skip;
+
+ // *NOTE: Check for hitting the limit and eof here before
+ // checking for the trailing EOF, because our mime parser has
+ // to gracefully handle incomplete mime entites.
+ if((mScanCount >= limit) || istr.eof())
+ {
+ mContinue = false;
+ }
+ else if(!eatCRLF(istr))
+ {
+ mError = true;
+ return;
+ }
+ }
+}
+
+bool LLMimeParser::Impl::eatCRLF(std::istream& istr)
+{
+ int c = istr.get();
+ ++mScanCount;
+ if(c != '\r')
+ {
+ return false;
+ }
+ c = istr.get();
+ ++mScanCount;
+ if(c != '\n')
+ {
+ return false;
+ }
+ return true;
+}
+
+
+LLMimeParser::LLMimeParser() : mImpl(* new LLMimeParser::Impl)
+{
+}
+
+LLMimeParser::~LLMimeParser()
+{
+ delete & mImpl;
+}
+
+void LLMimeParser::reset()
+{
+ mImpl.reset();
+}
+
+bool LLMimeParser::parseIndex(std::istream& istr, LLMimeIndex& index)
+{
+ std::string separator;
+ return mImpl.parseIndex(istr, S32_MAX, separator, false, index);
+}
+
+bool LLMimeParser::parseIndex(
+ const std::vector<U8>& buffer,
+ LLMimeIndex& index)
+{
+ LLMemoryStream mstr(&buffer[0], buffer.size());
+ return parseIndex(mstr, buffer.size() + 1, index);
+}
+
+bool LLMimeParser::parseIndex(
+ std::istream& istr,
+ S32 limit,
+ LLMimeIndex& index)
+{
+ std::string separator;
+ return mImpl.parseIndex(istr, limit, separator, false, index);
+}
+
+bool LLMimeParser::parseIndex(const U8* buffer, S32 length, LLMimeIndex& index)
+{
+ LLMemoryStream mstr(buffer, length);
+ return parseIndex(mstr, length + 1, index);
+}
+
+/*
+bool LLMimeParser::verify(std::istream& isr, LLMimeIndex& index) const
+{
+ return false;
+}
+
+bool LLMimeParser::verify(U8* buffer, S32 length, LLMimeIndex& index) const
+{
+ LLMemoryStream mstr(buffer, length);
+ return verify(mstr, index);
+}
+*/