PageRenderTime 104ms CodeModel.GetById 41ms app.highlight 30ms RepoModel.GetById 30ms app.codeStats 0ms

/mordor/http/multipart.cpp

http://github.com/mozy/mordor
C++ | 231 lines | 207 code | 22 blank | 2 comment | 41 complexity | 698c423218a288d7b2f19a5c5f66b44a MD5 | raw file
  1// Copyright (c) 2009 - Mozy, Inc.
  2
  3#include "multipart.h"
  4
  5#include <boost/bind.hpp>
  6
  7#include "mordor/assert.h"
  8#include "mordor/streams/buffer.h"
  9#include "mordor/streams/buffered.h"
 10#include "mordor/streams/notify.h"
 11#include "mordor/streams/null.h"
 12#include "mordor/streams/transfer.h"
 13#include "parser.h"
 14
 15namespace Mordor {
 16
 17static const char allowedBoundaryChars[] =
 18    "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'()+_,-./:=?";
 19
 20std::string
 21Multipart::randomBoundary()
 22{
 23    std::string result;
 24    result.resize(40);
 25    for (size_t i = 0; i < 40; ++i)
 26        result[i] = allowedBoundaryChars[rand() % 74];
 27    return result;
 28}
 29
 30Multipart::Multipart(Stream::ptr stream, std::string boundary)
 31: m_stream(stream),
 32  m_boundary(boundary),
 33  m_finished(false),
 34  m_firstPart(true)
 35{
 36    MORDOR_ASSERT(m_stream);
 37    MORDOR_ASSERT(m_stream->supportsRead() || m_stream->supportsWrite());
 38    MORDOR_ASSERT(!(m_stream->supportsRead() && m_stream->supportsWrite()));
 39    while (!m_boundary.empty() && m_boundary[m_boundary.size() - 1] == ' ')
 40        m_boundary.resize(m_boundary.size() - 1);
 41    MORDOR_ASSERT(!m_boundary.empty());
 42    MORDOR_ASSERT(m_boundary.size() <= 70);
 43    if (m_boundary.find_first_not_of(allowedBoundaryChars) != std::string::npos) {
 44        if (stream->supportsWrite()) {
 45            MORDOR_NOTREACHED();
 46        } else {
 47            MORDOR_THROW_EXCEPTION(InvalidMultipartBoundaryException());
 48        }
 49    }
 50    m_boundary = "--" + m_boundary;
 51    if (m_stream->supportsRead()) {
 52        if (!m_stream->supportsFind())
 53            m_stream.reset(new BufferedStream(m_stream));
 54        MORDOR_ASSERT(m_stream->supportsFind());
 55        MORDOR_ASSERT(m_stream->supportsUnread());
 56    }
 57}
 58
 59BodyPart::ptr
 60Multipart::nextPart()
 61{
 62    if (m_stream->supportsWrite()) {
 63        MORDOR_ASSERT(!m_finished);
 64        MORDOR_ASSERT(!m_currentPart);
 65        m_currentPart.reset(new BodyPart(shared_from_this()));
 66        std::string boundary = m_boundary + "\r\n";
 67        m_stream->write(boundary.c_str(), boundary.size());
 68        if (m_firstPart) {
 69            m_firstPart = false;
 70            m_boundary = "\r\n" + m_boundary;
 71        }
 72        return m_currentPart;
 73    } else {
 74        if (m_finished) {
 75            MORDOR_ASSERT(!m_currentPart);
 76            return m_currentPart;
 77        }
 78        if (m_currentPart) {
 79            transferStream(m_currentPart->stream(), NullStream::get());
 80            // Changed by the notification callback
 81            MORDOR_ASSERT(!m_currentPart);
 82        }
 83        size_t offsetToBoundary = m_stream->find(m_boundary);
 84
 85        Buffer b;
 86        MORDOR_VERIFY(m_stream->read(b, offsetToBoundary + m_boundary.size()) ==
 87                      offsetToBoundary + m_boundary.size());
 88
 89        if (m_firstPart) {
 90            m_firstPart = false;
 91            m_boundary = "\r\n" + m_boundary;
 92        }
 93
 94        b.clear();
 95        m_stream->read(b, 2);
 96        if (b == "--") {
 97            m_finished = true;
 98        }
 99        if (b == "\n") {
100            m_stream->unread(b, 1);
101        }
102        if (b != "\r\n") {
103            std::string restOfLine = m_stream->getDelimited();
104            MORDOR_ASSERT(!restOfLine.empty());
105            restOfLine.resize(restOfLine.size() - 1);
106            if (restOfLine.find_first_not_of(" \r\t") != std::string::npos) {
107                MORDOR_THROW_EXCEPTION(InvalidMultipartBoundaryException());
108            }
109        }
110
111        if (m_finished) {
112            if (multipartFinished) {
113                multipartFinished();
114                multipartFinished = NULL;
115            }
116            return m_currentPart;
117        }
118        m_currentPart.reset(new BodyPart(shared_from_this()));
119        return m_currentPart;
120    }
121}
122
123void
124Multipart::finish()
125{
126    MORDOR_ASSERT(m_stream->supportsWrite());
127    MORDOR_ASSERT(!m_finished);
128    std::string finalBoundary = m_boundary + "--\r\n";
129    m_stream->write(finalBoundary.c_str(), finalBoundary.size());
130    m_finished = true;
131    if (multipartFinished) {
132        multipartFinished();
133        multipartFinished = NULL;
134    }
135}
136
137void
138Multipart::partDone()
139{
140    m_currentPart.reset();
141}
142
143class BodyPartStream : public MutatingFilterStream
144{
145public:
146    BodyPartStream(Stream::ptr parent, std::string boundary)
147        : MutatingFilterStream(parent),
148          m_boundary(boundary)
149    {}
150
151    using MutatingFilterStream::read;
152    size_t read(Buffer &b, size_t len)
153    {
154        ptrdiff_t boundary = parent()->find(m_boundary, len, false);
155        if (boundary >= 0)
156            len = (std::min)((size_t)boundary, len);
157        return parent()->read(b, len);
158    }
159
160private:
161    std::string m_boundary;
162};
163
164BodyPart::BodyPart(Multipart::ptr multipart)
165: m_multipart(multipart)
166{
167    if (m_multipart->m_stream->supportsRead()) {
168        HTTP::TrailerParser parser(m_headers);
169        parser.run(m_multipart->m_stream);
170        if (parser.error())
171            MORDOR_THROW_EXCEPTION(HTTP::BadMessageHeaderException());
172        if (!parser.complete())
173            MORDOR_THROW_EXCEPTION(HTTP::IncompleteMessageHeaderException());
174        m_stream.reset(new BodyPartStream(m_multipart->m_stream, m_multipart->m_boundary));
175        NotifyStream *notify = new NotifyStream(m_stream);
176        notify->notifyOnEof = boost::bind(&Multipart::partDone, m_multipart);
177        m_stream.reset(notify);
178    }
179}
180
181HTTP::EntityHeaders &
182BodyPart::headers()
183{
184    return m_headers;
185}
186
187Stream::ptr
188BodyPart::stream()
189{
190    if (m_multipart->m_stream->supportsWrite()) {
191        MORDOR_ASSERT(m_headers.contentType.type != "multipart");
192    }
193    if (!m_stream) {
194        MORDOR_ASSERT(m_multipart->m_stream->supportsWrite());
195        std::ostringstream os;
196        os << m_headers << "\r\n";
197        std::string headers = os.str();
198        m_multipart->m_stream->write(headers.c_str(), headers.size());
199        NotifyStream *notify = new NotifyStream(m_multipart->m_stream, false);
200        notify->notifyOnClose(boost::bind(&Multipart::partDone, m_multipart));
201        m_stream.reset(notify);
202    }
203    return m_stream;
204}
205
206Multipart::ptr
207BodyPart::multipart()
208{
209    if (m_childMultipart)
210        return m_childMultipart;
211    MORDOR_ASSERT(m_headers.contentType.type == "multipart");
212    if (!m_stream) {
213        MORDOR_ASSERT(m_multipart->m_stream->supportsWrite());
214        std::ostringstream os;
215        os << m_headers;
216        std::string headers = os.str();
217        m_multipart->m_stream->write(headers.c_str(), headers.size());
218        NotifyStream *notify = new NotifyStream(m_multipart->m_stream, false);
219        notify->notifyOnClose(boost::bind(&Multipart::partDone, m_multipart));
220        m_stream.reset(notify);
221    }
222    HTTP::StringMap::const_iterator it = m_headers.contentType.parameters.find("boundary");
223    if (it == m_headers.contentType.parameters.end()) {
224        MORDOR_ASSERT(!m_multipart->m_stream->supportsWrite());
225        MORDOR_THROW_EXCEPTION(InvalidMultipartBoundaryException());
226    }
227    m_childMultipart.reset(new Multipart(m_stream, it->second));
228    return m_childMultipart;
229}
230
231}