PageRenderTime 67ms CodeModel.GetById 9ms app.highlight 53ms RepoModel.GetById 2ms app.codeStats 0ms

/indra/llmessage/llmime.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 629 lines | 406 code | 54 blank | 169 comment | 57 complexity | 0d8929309d60c5c7d902f694624027bf MD5 | raw file
  1/** 
  2 * @file llmime.cpp
  3 * @author Phoenix
  4 * @date 2006-12-20
  5 * @brief Implementation of mime tools.
  6 *
  7 * $LicenseInfo:firstyear=2006&license=viewerlgpl$
  8 * Second Life Viewer Source Code
  9 * Copyright (C) 2010, Linden Research, Inc.
 10 * 
 11 * This library is free software; you can redistribute it and/or
 12 * modify it under the terms of the GNU Lesser General Public
 13 * License as published by the Free Software Foundation;
 14 * version 2.1 of the License only.
 15 * 
 16 * This library is distributed in the hope that it will be useful,
 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 19 * Lesser General Public License for more details.
 20 * 
 21 * You should have received a copy of the GNU Lesser General Public
 22 * License along with this library; if not, write to the Free Software
 23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 24 * 
 25 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 26 * $/LicenseInfo$
 27 */
 28
 29#include "linden_common.h"
 30#include "llmime.h"
 31
 32#include <vector>
 33
 34#include "llmemorystream.h"
 35
 36/**
 37 * Useful constants.
 38 */
 39// Headers specified in rfc-2045 will be canonicalized below.
 40static const std::string CONTENT_LENGTH("Content-Length");
 41static const std::string CONTENT_TYPE("Content-Type");
 42static const S32 KNOWN_HEADER_COUNT = 6;
 43static const std::string KNOWN_HEADER[KNOWN_HEADER_COUNT] =
 44{
 45	CONTENT_LENGTH,
 46	CONTENT_TYPE,
 47	std::string("MIME-Version"),
 48	std::string("Content-Transfer-Encoding"),
 49	std::string("Content-ID"),
 50	std::string("Content-Description"),
 51};
 52
 53// parser helpers
 54static const std::string MULTIPART("multipart");
 55static const std::string BOUNDARY("boundary");
 56static const std::string END_OF_CONTENT_PARAMETER("\r\n ;\t");
 57static const std::string SEPARATOR_PREFIX("--");
 58//static const std::string SEPARATOR_SUFFIX("\r\n");
 59
 60/*
 61Content-Type: multipart/mixed; boundary="segment"
 62Content-Length: 24832
 63
 64--segment
 65Content-Type: image/j2c
 66Content-Length: 23715
 67
 68<data>
 69
 70--segment
 71Content-Type: text/xml; charset=UTF-8
 72
 73<meta data>
 74EOF
 75
 76*/
 77
 78/**
 79 * LLMimeIndex
 80 */
 81
 82/** 
 83 * @class LLMimeIndex::Impl
 84 * @brief Implementation details of the mime index class.
 85 * @see LLMimeIndex
 86 */
 87class LLMimeIndex::Impl
 88{
 89public:
 90	Impl() : mOffset(-1), mUseCount(1)
 91	{}
 92	Impl(LLSD headers, S32 offset) :
 93		mHeaders(headers), mOffset(offset), mUseCount(1)
 94	{}
 95public:
 96	LLSD mHeaders;
 97	S32 mOffset;
 98	S32 mUseCount;
 99
100	typedef std::vector<LLMimeIndex> sub_part_t;
101	sub_part_t mAttachments;
102};
103
104LLSD LLMimeIndex::headers() const
105{
106	return mImpl->mHeaders;
107}
108
109S32 LLMimeIndex::offset() const
110{
111	return mImpl->mOffset;
112}
113
114S32 LLMimeIndex::contentLength() const
115{
116	// Find the content length in the headers.
117	S32 length = -1;
118	LLSD content_length = mImpl->mHeaders[CONTENT_LENGTH];
119	if(content_length.isDefined())
120	{
121		length = content_length.asInteger();
122	}
123	return length;
124}
125
126std::string LLMimeIndex::contentType() const
127{
128	std::string type;
129	LLSD content_type = mImpl->mHeaders[CONTENT_TYPE];
130	if(content_type.isDefined())
131	{
132		type = content_type.asString();
133	}
134	return type;
135}
136
137bool LLMimeIndex::isMultipart() const
138{
139	bool multipart = false;
140	LLSD content_type = mImpl->mHeaders[CONTENT_TYPE];
141	if(content_type.isDefined())
142	{
143		std::string type = content_type.asString();
144		int comp = type.compare(0, MULTIPART.size(), MULTIPART);
145		if(0 == comp)
146		{
147			multipart = true;
148		}
149	}
150	return multipart;
151}
152
153S32 LLMimeIndex::subPartCount() const
154{
155	return mImpl->mAttachments.size();
156}
157
158LLMimeIndex LLMimeIndex::subPart(S32 index) const
159{
160	LLMimeIndex part;
161	if((index >= 0) && (index < (S32)mImpl->mAttachments.size()))
162	{
163		part = mImpl->mAttachments[index];
164	}
165	return part;
166}
167
168LLMimeIndex::LLMimeIndex() : mImpl(new LLMimeIndex::Impl)
169{
170}
171
172LLMimeIndex::LLMimeIndex(LLSD headers, S32 content_offset) :
173	mImpl(new LLMimeIndex::Impl(headers, content_offset))
174{
175}
176
177LLMimeIndex::LLMimeIndex(const LLMimeIndex& mime) :
178	mImpl(mime.mImpl)
179{
180	++mImpl->mUseCount;
181}
182
183LLMimeIndex::~LLMimeIndex()
184{
185	if(0 == --mImpl->mUseCount)
186	{
187		delete mImpl;
188	}
189}
190
191LLMimeIndex& LLMimeIndex::operator=(const LLMimeIndex& mime)
192{
193	// Increment use count first so that we handle self assignment
194	// automatically.
195	++mime.mImpl->mUseCount;
196	if(0 == --mImpl->mUseCount)
197	{
198		delete mImpl;
199	}
200	mImpl = mime.mImpl;
201	return *this;
202}
203
204bool LLMimeIndex::attachSubPart(LLMimeIndex sub_part)
205{
206	// *FIX: Should we check for multi-part?
207	if(mImpl->mAttachments.size() < S32_MAX)
208	{
209		mImpl->mAttachments.push_back(sub_part);
210		return true;
211	}
212	return false;
213}
214
215/**
216 * LLMimeParser
217 */
218/** 
219 * @class LLMimeParser::Impl
220 * @brief Implementation details of the mime parser class.
221 * @see LLMimeParser
222 */
223class LLMimeParser::Impl
224{
225public:
226	// @brief Constructor.
227	Impl();
228
229	// @brief Reset this for a new parse.
230	void reset();
231
232	/** 
233	 * @brief Parse a mime entity to find the index information.
234	 *
235	 * This method will scan the istr until a single complete mime
236	 * entity is read, an EOF, or limit bytes have been scanned. The
237	 * istr will be modified by this parsing, so pass in a temporary
238	 * stream or rewind/reset the stream after this call.
239	 * @param istr An istream which contains a mime entity.
240	 * @param limit The maximum number of bytes to scan.
241	 * @param separator The multipart separator if it is known.
242	 * @param is_subpart Set true if parsing a multipart sub part.
243	 * @param index[out] The parsed output.
244	 * @return Returns true if an index was parsed and no errors occurred.
245	 */
246	bool parseIndex(
247		std::istream& istr,
248		S32 limit,
249		const std::string& separator,
250		bool is_subpart,
251		LLMimeIndex& index);
252
253protected:
254	/**
255	 * @brief parse the headers.
256	 *
257	 * At the end of a successful parse, mScanCount will be at the
258	 * start of the content.
259	 * @param istr The input stream.
260	 * @param limit maximum number of bytes to process
261	 * @param headers[out] A map of the headers found.
262	 * @return Returns true if the parse was successful.
263	 */
264	bool parseHeaders(std::istream& istr, S32 limit, LLSD& headers);
265
266	/**
267	 * @brief Figure out the separator string from a content type header.
268	 * 
269	 * @param multipart_content_type The content type value from the headers.
270	 * @return Returns the separator string.
271	 */
272	std::string findSeparator(std::string multipart_content_type);
273
274	/**
275	 * @brief Scan through istr past the separator.
276	 *
277	 * @param istr The input stream.
278	 * @param limit Maximum number of bytes to scan.
279	 * @param separator The multipart separator.
280	 */
281	void scanPastSeparator(
282		std::istream& istr,
283		S32 limit,
284		const std::string& separator);
285
286	/**
287	 * @brief Scan through istr past the content of the current mime part.
288	 *
289	 * @param istr The input stream.
290	 * @param limit Maximum number of bytes to scan.
291	 * @param headers The headers for this mime part.
292	 * @param separator The multipart separator if known.
293	 */
294	void scanPastContent(
295		std::istream& istr,
296		S32 limit,
297		LLSD headers,
298		const std::string separator);
299
300	/**
301	 * @brief Eat CRLF.
302	 *
303	 * This method has no concept of the limit, so ensure you have at
304	 * least 2 characters left to eat before hitting the limit. This
305	 * method will increment mScanCount as it goes.
306	 * @param istr The input stream.
307	 * @return Returns true if CRLF was found and consumed off of istr.
308	 */
309	bool eatCRLF(std::istream& istr);
310
311	// @brief Returns true if parsing should continue.
312	bool continueParse() const { return (!mError && mContinue); }
313
314	// @brief anonymous enumeration for parse buffer size.
315	enum
316	{
317		LINE_BUFFER_LENGTH = 1024
318	};
319
320protected:
321	S32 mScanCount;
322	bool mContinue;
323	bool mError;
324	char mBuffer[LINE_BUFFER_LENGTH];
325};
326
327LLMimeParser::Impl::Impl()
328{
329	reset();
330}
331
332void LLMimeParser::Impl::reset()
333{
334	mScanCount = 0;
335	mContinue = true;
336	mError = false;
337	mBuffer[0] = '\0';
338}
339
340bool LLMimeParser::Impl::parseIndex(
341	std::istream& istr,
342	S32 limit,
343	const std::string& separator,
344	bool is_subpart,
345	LLMimeIndex& index)
346{
347	LLSD headers;
348	bool parsed_something = false;
349	if(parseHeaders(istr, limit, headers))
350	{
351		parsed_something = true;
352		LLMimeIndex mime(headers, mScanCount);
353		index = mime;
354		if(index.isMultipart())
355		{
356			// Figure out the separator, scan past it, and recurse.
357			std::string ct = headers[CONTENT_TYPE].asString();
358			std::string sep = findSeparator(ct);
359			scanPastSeparator(istr, limit, sep);
360			while(continueParse() && parseIndex(istr, limit, sep, true, mime))
361			{
362				index.attachSubPart(mime);
363			}
364		}
365		else
366		{
367			// Scan to the end of content.
368			scanPastContent(istr, limit, headers, separator);
369			if(is_subpart)
370			{
371				scanPastSeparator(istr, limit, separator);
372			}
373		}
374	}
375	if(mError) return false;
376	return parsed_something;
377}
378
379bool LLMimeParser::Impl::parseHeaders(
380	std::istream& istr,
381	S32 limit,
382	LLSD& headers)
383{
384	while(continueParse())
385	{
386		// Get the next line.
387		// We subtract 1 from the limit so that we make sure
388		// not to read past limit when we get() the newline.
389		S32 max_get = llmin((S32)LINE_BUFFER_LENGTH, limit - mScanCount - 1);
390		istr.getline(mBuffer, max_get, '\r');
391		mScanCount += istr.gcount();
392		int c = istr.get();
393		if(EOF == c)
394		{
395			mContinue = false;
396			return false;
397		}
398		++mScanCount;
399		if(c != '\n')
400		{
401			mError = true;
402			return false;
403		}
404		if(mScanCount >= limit)
405		{
406			mContinue = false;
407		}
408
409		// Check if that's the end of headers.
410		if('\0' == mBuffer[0])
411		{
412			break;
413		}
414
415		// Split out the name and value.
416		// *NOTE: The use of strchr() here is safe since mBuffer is
417		// guaranteed to be NULL terminated from the call to getline()
418		// above.
419		char* colon = strchr(mBuffer, ':');
420		if(!colon)
421		{
422			mError = true;
423			return false;
424		}
425
426		// Cononicalize the name part, and store the name: value in
427		// the headers structure. We do this by iterating through
428		// 'known' headers and replacing the value found with the
429		// correct one.
430		// *NOTE: Not so efficient, but iterating through a small
431		// subset should not be too much of an issue.
432		std::string name(mBuffer, colon++ - mBuffer);
433		while(isspace(*colon)) ++colon;
434		std::string value(colon);
435		for(S32 ii = 0; ii < KNOWN_HEADER_COUNT; ++ii)
436		{
437			if(0 == LLStringUtil::compareInsensitive(name, KNOWN_HEADER[ii]))
438			{
439				name = KNOWN_HEADER[ii];
440				break;
441			}
442		}
443		headers[name] = value;
444	}
445	if(headers.isUndefined()) return false;
446	return true;
447}
448
449std::string LLMimeParser::Impl::findSeparator(std::string header)
450{
451	//                               01234567890
452	//Content-Type: multipart/mixed; boundary="segment"
453	std::string separator;
454	std::string::size_type pos = header.find(BOUNDARY);
455	if(std::string::npos == pos) return separator;
456	pos += BOUNDARY.size() + 1;
457	std::string::size_type end;
458	if(header[pos] == '"')
459	{
460		// the boundary is quoted, find the end from pos, and take the
461		// substring.
462		end = header.find('"', ++pos);
463		if(std::string::npos == end)
464		{
465			// poorly formed boundary.
466			mError = true;
467		}
468	}
469	else
470	{
471		// otherwise, it's every character until a whitespace, end of
472		// line, or another parameter begins.
473		end = header.find_first_of(END_OF_CONTENT_PARAMETER, pos);
474		if(std::string::npos == end)
475		{
476			// it goes to the end of the string.
477			end = header.size();
478		}
479	}
480	if(!mError) separator = header.substr(pos, end - pos);
481	return separator;
482}
483
484void LLMimeParser::Impl::scanPastSeparator(
485	std::istream& istr,
486	S32 limit,
487	const std::string& sep)
488{
489	std::ostringstream ostr;
490	ostr << SEPARATOR_PREFIX << sep;
491	std::string separator = ostr.str();
492	bool found_separator = false;
493	while(!found_separator && continueParse())
494	{
495		// Subtract 1 from the limit so that we make sure not to read
496		// past limit when we get() the newline.
497		S32 max_get = llmin((S32)LINE_BUFFER_LENGTH, limit - mScanCount - 1);
498		istr.getline(mBuffer, max_get, '\r');
499		mScanCount += istr.gcount();
500		if(istr.gcount() >= LINE_BUFFER_LENGTH - 1)
501		{
502			// that's way too long to be a separator, so ignore it.
503			continue;
504		}
505		int c = istr.get();
506		if(EOF == c)
507		{
508			mContinue = false;
509			return;
510		}
511		++mScanCount;
512		if(c != '\n')
513		{
514			mError = true;
515			return;
516		}
517		if(mScanCount >= limit)
518		{
519			mContinue = false;
520		}
521		if(0 == LLStringUtil::compareStrings(std::string(mBuffer), separator))
522		{
523			found_separator = true;
524		}
525	}
526}
527
528void LLMimeParser::Impl::scanPastContent(
529	std::istream& istr,
530	S32 limit,
531	LLSD headers,
532	const std::string separator)
533{
534	if(headers.has(CONTENT_LENGTH))
535	{
536		S32 content_length = headers[CONTENT_LENGTH].asInteger();
537		// Subtract 2 here for the \r\n after the content.
538		S32 max_skip = llmin(content_length, limit - mScanCount - 2);
539		istr.ignore(max_skip);
540		mScanCount += max_skip;
541
542		// *NOTE: Check for hitting the limit and eof here before
543		// checking for the trailing EOF, because our mime parser has
544		// to gracefully handle incomplete mime entites.
545		if((mScanCount >= limit) || istr.eof())
546		{
547			mContinue = false;
548		}
549		else if(!eatCRLF(istr))
550		{
551			mError = true;
552			return;
553		}
554	}
555}
556
557bool LLMimeParser::Impl::eatCRLF(std::istream& istr)
558{
559	int c = istr.get();
560	++mScanCount;
561	if(c != '\r')
562	{
563		return false;
564	}
565	c = istr.get();
566	++mScanCount;
567	if(c != '\n')
568	{
569		return false;
570	}
571	return true;
572}
573	
574
575LLMimeParser::LLMimeParser() : mImpl(* new LLMimeParser::Impl)
576{
577}
578
579LLMimeParser::~LLMimeParser()
580{
581	delete & mImpl;
582}
583
584void LLMimeParser::reset()
585{
586	mImpl.reset();
587}
588
589bool LLMimeParser::parseIndex(std::istream& istr, LLMimeIndex& index)
590{
591	std::string separator;
592	return mImpl.parseIndex(istr, S32_MAX, separator, false, index);
593}
594
595bool LLMimeParser::parseIndex(
596	const std::vector<U8>& buffer,
597	LLMimeIndex& index)
598{
599	LLMemoryStream mstr(&buffer[0], buffer.size());
600	return parseIndex(mstr, buffer.size() + 1, index);
601}
602
603bool LLMimeParser::parseIndex(
604	std::istream& istr,
605	S32 limit,
606	LLMimeIndex& index)
607{
608	std::string separator;
609	return mImpl.parseIndex(istr, limit, separator, false, index);
610}
611
612bool LLMimeParser::parseIndex(const U8* buffer, S32 length, LLMimeIndex& index)
613{
614	LLMemoryStream mstr(buffer, length);
615	return parseIndex(mstr, length + 1, index);
616}
617
618/*
619bool LLMimeParser::verify(std::istream& isr, LLMimeIndex& index) const
620{
621	return false;
622}
623
624bool LLMimeParser::verify(U8* buffer, S32 length, LLMimeIndex& index) const
625{
626	LLMemoryStream mstr(buffer, length);
627	return verify(mstr, index);
628}
629*/