[Curl] Extract multipart handling from ResourceHandle to CurlRequest.
[WebKit-https.git] / Source / WebCore / platform / network / curl / CurlMultipartHandle.cpp
1 /*
2  * Copyright (C) 2013 University of Szeged
3  * Copyright (C) 2018 Sony Interactive Entertainment Inc.
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF SZEGED ``AS IS'' AND ANY
16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL UNIVERSITY OF SZEGED OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "config.h"
29 #include "CurlMultipartHandle.h"
30
31 #if USE(CURL)
32
33 #include "CurlMultipartHandleClient.h"
34 #include "CurlResponse.h"
35 #include "HTTPHeaderNames.h"
36 #include "HTTPParsers.h"
37 #include "ResourceResponse.h"
38 #include "SharedBuffer.h"
39 #include <wtf/StringExtras.h>
40
41 namespace WebCore {
42
43 std::unique_ptr<CurlMultipartHandle> CurlMultipartHandle::createIfNeeded(CurlMultipartHandleClient& client, const CurlResponse& response)
44 {
45     auto boundary = extractBoundary(response);
46     if (!boundary)
47         return nullptr;
48
49     return std::make_unique<CurlMultipartHandle>(client, *boundary);
50 }
51
52 std::optional<String> CurlMultipartHandle::extractBoundary(const CurlResponse& response)
53 {
54     for (auto header : response.headers) {
55         auto splitPosistion = header.find(":");
56         if (splitPosistion == notFound)
57             continue;
58
59         auto key = header.left(splitPosistion).stripWhiteSpace();
60         if (!equalIgnoringASCIICase(key, "Content-Type"))
61             continue;
62
63         auto contentType = header.substring(splitPosistion + 1).stripWhiteSpace();
64         auto mimeType = extractMIMETypeFromMediaType(contentType);
65         if (!equalIgnoringASCIICase(mimeType, "multipart/x-mixed-replace"))
66             continue;
67
68         auto boundary = extractBoundaryFromContentType(contentType);
69         if (!boundary)
70             continue;
71
72         return String("--" + *boundary);
73     }
74
75     return std::nullopt;
76 }
77
78 std::optional<String> CurlMultipartHandle::extractBoundaryFromContentType(const String& contentType)
79 {
80     static const size_t length = strlen("boundary=");
81
82     auto boundaryStart = contentType.findIgnoringASCIICase("boundary=");
83     if (boundaryStart == notFound)
84         return std::nullopt;
85
86     boundaryStart += length;
87     size_t boundaryEnd = 0;
88
89     if (contentType[boundaryStart] == '"') {
90         // Boundary value starts with a " quote. Search for the closing one.
91         ++boundaryStart;
92         boundaryEnd = contentType.find('"', boundaryStart);
93         if (boundaryEnd == notFound)
94             return std::nullopt;
95     } else if (contentType[boundaryStart] == '\'') {
96         // Boundary value starts with a ' quote. Search for the closing one.
97         ++boundaryStart;
98         boundaryEnd = contentType.find('\'', boundaryStart);
99         if (boundaryEnd == notFound)
100             return std::nullopt;
101     } else {
102         // Check for the end of the boundary. That can be a semicolon or a newline.
103         boundaryEnd = contentType.find(';', boundaryStart);
104         if (boundaryEnd == notFound)
105             boundaryEnd = contentType.length();
106     }
107
108     // The boundary end should not be before the start
109     if (boundaryEnd <= boundaryStart)
110         return std::nullopt;
111
112     return contentType.substring(boundaryStart, boundaryEnd - boundaryStart);
113 }
114
115 CurlMultipartHandle::CurlMultipartHandle(CurlMultipartHandleClient& client, const String& boundary)
116     : m_client(client)
117     , m_boundary(boundary)
118 {
119
120 }
121
122 void CurlMultipartHandle::didReceiveData(const SharedBuffer& buffer)
123 {
124     if (m_state == State::End)
125         return; // The handler is closed down so ignore everything.
126
127     m_buffer.append(buffer.data(), buffer.size());
128
129     while (processContent()) { }
130 }
131
132 void CurlMultipartHandle::didComplete()
133 {
134     // Process the leftover data.
135     while (processContent()) { }
136
137     if (m_state != State::End) {
138         // It seems we are still not at the end of the processing.
139         // Push out the remaining data.
140         m_client.didReceiveDataFromMultipart(SharedBuffer::create(m_buffer.data(), m_buffer.size()));
141         m_state = State::End;
142     }
143
144     m_buffer.clear();
145 }
146
147 bool CurlMultipartHandle::processContent()
148 {
149 /*
150     The allowed transitions between the states:
151          Check Boundary
152                |
153       /-- In Boundary <----\
154      |         |            |
155      |     In Header        |
156      |         |            |
157      |     In Content       |
158      |         |            |
159      |    End Boundary ----/
160      |         |
161       \-----> End
162
163 */
164     switch (m_state) {
165     case State::CheckBoundary: {
166         if (m_buffer.size() < m_boundary.length()) {
167             // We don't have enough data, so just skip.
168             return false;
169         }
170
171         // Check for the boundary string.
172         size_t boundaryStart;
173         size_t lastPartialMatch;
174
175         if (!checkForBoundary(boundaryStart, lastPartialMatch) && boundaryStart == notFound) {
176             // Did not find the boundary start in this chunk.
177             // Skip ahead to the last valid looking boundary character and start again.
178             m_buffer.remove(0, lastPartialMatch);
179             return false;
180         }
181
182         // Found the boundary start.
183         // Consume everything before that and also the boundary
184         m_buffer.remove(0, boundaryStart + m_boundary.length());
185         m_state = State::InBoundary;
186     }
187     // Fallthrough.
188     case State::InBoundary: {
189         // Now the first two characters should be: \r\n
190         if (m_buffer.size() < 2)
191             return false;
192
193         const char* content = m_buffer.data();
194         // By default we'll remove 2 characters at the end.
195         // The \r and \n as stated in the multipart RFC.
196         size_t removeCount = 2;
197
198         if (content[0] != '\r' || content[1] != '\n') {
199             // There should be a \r and a \n but it seems that's not the case.
200             // So we'll check for a simple \n. Not really RFC compatible but servers do tricky things.
201             if (content[0] != '\n') {
202                 // Also no \n so just go to the end.
203                 m_state = State::End;
204                 return false;
205             }
206
207             // Found a simple \n so remove just that.
208             removeCount = 1;
209         }
210
211         // Consume the characters.
212         m_buffer.remove(0, removeCount);
213         m_headers.clear();
214         m_state = State::InHeader;
215     }
216     // Fallthrough.
217     case State::InHeader: {
218         // Process the headers.
219         if (!parseHeadersIfPossible()) {
220             // Parsing of headers failed, try again later.
221             return false;
222         }
223
224         m_client.didReceiveHeaderFromMultipart(m_headers);
225         m_state = State::InContent;
226     }
227     // Fallthrough.
228     case State::InContent: {
229         if (m_buffer.isEmpty())
230             return false;
231
232         size_t boundaryStart;
233         size_t lastPartialMatch;
234
235         if (!checkForBoundary(boundaryStart, lastPartialMatch) && boundaryStart == notFound) {
236             // Did not find the boundary start, all data up to the lastPartialMatch is ok.
237             m_client.didReceiveDataFromMultipart(SharedBuffer::create(m_buffer.data(), lastPartialMatch));
238             m_buffer.remove(0, lastPartialMatch);
239             return false;
240         }
241
242         // There was a boundary start (or end we'll check that later), push out part of the data.
243         m_client.didReceiveDataFromMultipart(SharedBuffer::create(m_buffer.data(), boundaryStart));
244         m_buffer.remove(0, boundaryStart + m_boundary.length());
245         m_state = State::EndBoundary;
246     }
247     // Fallthrough.
248     case State::EndBoundary: {
249         if (m_buffer.size() < 2)
250             return false; // Not enough data to check. Return later when there is more data.
251
252         // We'll decide if this is a closing boundary or an opening one.
253         const char* content = m_buffer.data();
254
255         if (content[0] == '-' && content[1] == '-') {
256             // This is a closing boundary. Close down the handler.
257             m_state = State::End;
258             return false;
259         }
260
261         // This was a simple content separator not a closing one.
262         // Go to before the content processing.
263         m_state = State::InBoundary;
264         break;
265     }
266     case State::End:
267         // We are done. Nothing to do anymore.
268         return false;
269     default:
270         ASSERT_NOT_REACHED();
271         return false;
272     }
273
274     return true; // There are still things to process, so go for it.
275 }
276
277 bool CurlMultipartHandle::checkForBoundary(size_t& boundaryStartPosition, size_t& lastPartialMatchPosition)
278 {
279     auto contentLength = m_buffer.size();
280
281     boundaryStartPosition = notFound;
282     lastPartialMatchPosition = contentLength;
283
284     if (contentLength < m_boundary.length())
285         return false;
286
287     const auto content = m_buffer.data();
288
289     for (size_t i = 0; i < contentLength - m_boundary.length(); ++i) {
290         auto length = matchedLength(content + i);
291         if (length == m_boundary.length()) {
292             boundaryStartPosition = i;
293             return true;
294         }
295
296         if (length)
297             lastPartialMatchPosition = i;
298     }
299
300     return false;
301 }
302
303 size_t CurlMultipartHandle::matchedLength(const char* data)
304 {
305     auto length = m_boundary.length();
306     for (size_t i = 0; i < length; ++i) {
307         if (data[i] != m_boundary[i])
308             return i;
309     }
310
311     return length;
312 }
313
314 bool CurlMultipartHandle::parseHeadersIfPossible()
315 {
316     auto contentLength = m_buffer.size();
317
318     if (!contentLength)
319         return false;
320
321     const auto content = m_buffer.data();
322
323     // Check if we have the header closing strings.
324     if (!strnstr(content, "\r\n\r\n", contentLength)) {
325         // Some servers closes the headers with only \n-s.
326         if (!strnstr(content, "\n\n", contentLength)) {
327             // Don't have the header closing string. Wait for more data.
328             return false;
329         }
330     }
331
332     // Parse the HTTP headers.
333     String value;
334     StringView name;
335     auto p = content;
336     const auto end = content + contentLength;
337     size_t totalConsumedLength = 0;
338     for (; p < end; ++p) {
339         String failureReason;
340         size_t consumedLength = parseHTTPHeader(p, end - p, failureReason, name, value, false);
341         if (!consumedLength)
342             break; // No more header to parse.
343
344         p += consumedLength;
345         totalConsumedLength += consumedLength;
346
347         // The name should not be empty, but the value could be empty.
348         if (name.isEmpty())
349             break;
350
351         m_headers.append(name.toString() + ": " + value + "\r\n");
352     }
353
354     m_buffer.remove(0, totalConsumedLength + 1);
355     return true;
356 }
357
358 } // namespace WebCore
359
360 #endif