2 * Copyright (C) 2013 University of Szeged
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF SZEGED ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UNIVERSITY OF SZEGED OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "MultipartHandle.h"
31 #include "HTTPHeaderNames.h"
32 #include "HTTPParsers.h"
33 #include "ResourceHandleClient.h"
34 #include "ResourceHandleInternal.h"
35 #include "ResourceResponse.h"
36 #include "SharedBuffer.h"
37 #include <wtf/StringExtras.h>
41 bool MultipartHandle::extractBoundary(const String& contentType, String& boundary)
43 static const size_t length = strlen("boundary=");
44 size_t boundaryStart = contentType.findIgnoringASCIICase("boundary=");
47 if (boundaryStart == notFound)
50 boundaryStart += length;
51 size_t boundaryEnd = 0;
53 if (contentType[boundaryStart] == '"') {
54 // Boundary value starts with a " quote. Search for the closing one.
56 boundaryEnd = contentType.find('"', boundaryStart);
57 if (boundaryEnd == notFound)
59 } else if (contentType[boundaryStart] == '\'') {
60 // Boundary value starts with a ' quote. Search for the closing one.
62 boundaryEnd = contentType.find('\'', boundaryStart);
63 if (boundaryEnd == notFound)
66 // Check for the end of the boundary. That can be a semicolon or a newline.
67 boundaryEnd = contentType.find(';', boundaryStart);
68 if (boundaryEnd == notFound)
69 boundaryEnd = contentType.length();
72 // The boundary end should not be before the start
73 if (boundaryEnd <= boundaryStart)
76 boundary = contentType.substring(boundaryStart, boundaryEnd - boundaryStart);
81 bool MultipartHandle::matchForBoundary(const char* data, size_t position, size_t& matchedLength)
85 for (size_t i = 0; i < m_boundaryLength; ++i) {
86 if (data[position + i] != m_boundary[i]) {
92 matchedLength = m_boundaryLength;
96 bool MultipartHandle::checkForBoundary(size_t& boundaryStartPosition, size_t& lastPartialMatchPosition)
98 size_t contentLength = m_buffer.size();
100 boundaryStartPosition = notFound;
101 lastPartialMatchPosition = contentLength;
103 if (contentLength < m_boundaryLength)
106 const char* content = m_buffer.data();
109 for (size_t i = 0; i < contentLength - m_boundaryLength; ++i) {
110 if (matchForBoundary(content, i, matched)) {
111 boundaryStartPosition = i;
116 lastPartialMatchPosition = i;
122 bool MultipartHandle::parseHeadersIfPossible()
124 size_t contentLength = m_buffer.size();
129 const char* content = m_buffer.data();
131 // Check if we have the header closing strings.
132 if (!strnstr(content, "\r\n\r\n", contentLength)) {
133 // Some servers closes the headers with only \n-s.
134 if (!strnstr(content, "\n\n", contentLength)) {
135 // Don't have the header closing string. Wait for more data.
140 // Parse the HTTP headers.
143 char* p = const_cast<char*>(content);
144 const char* end = content + contentLength;
145 size_t totalConsumedLength = 0;
146 for (; p < end; ++p) {
147 String failureReason;
148 size_t consumedLength = parseHTTPHeader(p, end - p, failureReason, name, value, false);
150 break; // No more header to parse.
153 totalConsumedLength += consumedLength;
155 // The name should not be empty, but the value could be empty.
159 m_headers.add(name.toString(), value);
162 m_buffer.remove(0, totalConsumedLength + 1);
166 void MultipartHandle::contentReceived(const char* data, size_t length)
169 return; // The handler is closed down so ignore everything.
171 m_buffer.append(data, length);
173 while (processContent()) { }
176 bool MultipartHandle::processContent()
179 The allowed transitions between the states:
182 /-- In Boundary <----\
194 case CheckBoundary: {
195 if (m_buffer.size() < m_boundaryLength) {
196 // We don't have enough data, so just skip.
200 // Check for the boundary string.
201 size_t boundaryStart;
202 size_t lastPartialMatch;
204 if (!checkForBoundary(boundaryStart, lastPartialMatch) && boundaryStart == notFound) {
205 // Did not find the boundary start in this chunk.
206 // Skip ahead to the last valid looking boundary character and start again.
207 m_buffer.remove(0, lastPartialMatch);
211 // Found the boundary start.
212 // Consume everything before that and also the boundary
213 m_buffer.remove(0, boundaryStart + m_boundaryLength);
214 m_state = InBoundary;
218 // Now the first two characters should be: \r\n
219 if (m_buffer.size() < 2)
222 const char* content = m_buffer.data();
223 // By default we'll remove 2 characters at the end.
224 // The \r and \n as stated in the multipart RFC.
225 size_t removeCount = 2;
227 if (content[0] != '\r' || content[1] != '\n') {
228 // There should be a \r and a \n but it seems that's not the case.
229 // So we'll check for a simple \n. Not really RFC compatible but servers do tricky things.
230 if (content[0] != '\n') {
231 // Also no \n so just go to the end.
236 // Found a simple \n so remove just that.
240 // Consume the characters.
241 m_buffer.remove(0, removeCount);
247 // Process the headers.
248 if (!parseHeadersIfPossible()) {
249 // Parsing of headers failed, try again later.
253 didReceiveResponse();
258 if (m_buffer.isEmpty())
261 size_t boundaryStart = notFound;
262 size_t lastPartialMatch;
264 if (!checkForBoundary(boundaryStart, lastPartialMatch) && boundaryStart == notFound) {
265 // Did not find the boundary start, all data up to the lastPartialMatch is ok.
266 didReceiveData(lastPartialMatch);
267 m_buffer.remove(0, lastPartialMatch);
271 // There was a boundary start (or end we'll check that later), push out part of the data.
272 didReceiveData(boundaryStart);
273 m_buffer.remove(0, boundaryStart + m_boundaryLength);
274 m_state = EndBoundary;
278 if (m_buffer.size() < 2)
279 return false; // Not enough data to check. Return later when there is more data.
281 // We'll decide if this is a closing boundary or an opening one.
282 const char* content = m_buffer.data();
284 if (content[0] == '-' && content[1] == '-') {
285 // This is a closing boundary. Close down the handler.
290 // This was a simple content separator not a closing one.
291 // Go to before the content processing.
292 m_state = InBoundary;
296 // We are done. Nothing to do anymore.
299 ASSERT_NOT_REACHED();
303 return true; // There are still things to process, so go for it.
306 void MultipartHandle::contentEnded()
308 // Process the leftover data.
309 while (processContent()) { }
311 if (m_state != End) {
312 // It seems we are still not at the end of the processing.
313 // Push out the remaining data.
314 didReceiveData(m_buffer.size());
321 void MultipartHandle::didReceiveData(size_t length)
323 ResourceHandleInternal* d = m_resourceHandle->getInternal();
325 if (!d->m_delegate->hasHandle()) {
326 // Request has been canceled, so we'll go to the end state.
332 const char* data = m_buffer.data();
333 d->client()->didReceiveBuffer(m_resourceHandle, SharedBuffer::create(data, length), length);
337 void MultipartHandle::didReceiveResponse()
339 ResourceHandleInternal* d = m_resourceHandle->getInternal();
341 auto response = d->m_response;
343 HTTPHeaderMap::const_iterator end = m_headers.end();
344 for (HTTPHeaderMap::const_iterator it = m_headers.begin(); it != end; ++it)
345 response.setHTTPHeaderField(it->key, it->value);
347 String contentType = m_headers.get(HTTPHeaderName::ContentType);
348 String mimeType = extractMIMETypeFromMediaType(contentType);
350 response.setMimeType(mimeType.convertToASCIILowercase());
351 response.setTextEncodingName(extractCharsetFromMediaType(contentType));
353 m_resourceHandle->didReceiveResponse(WTFMove(response));
357 } // namespace WebCore