OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "content/child/multipart_response_delegate.h" | |
6 | |
7 #include "base/logging.h" | |
8 #include "base/macros.h" | |
9 #include "base/memory/ref_counted.h" | |
10 #include "base/strings/string_number_conversions.h" | |
11 #include "base/strings/string_util.h" | |
12 #include "net/http/http_response_headers.h" | |
13 #include "net/http/http_util.h" | |
14 #include "third_party/WebKit/public/platform/WebHTTPHeaderVisitor.h" | |
15 #include "third_party/WebKit/public/platform/WebString.h" | |
16 #include "third_party/WebKit/public/platform/WebURL.h" | |
17 #include "third_party/WebKit/public/platform/WebURLLoaderClient.h" | |
18 | |
19 using blink::WebHTTPHeaderVisitor; | |
20 using blink::WebString; | |
21 using blink::WebURLLoader; | |
22 using blink::WebURLLoaderClient; | |
23 using blink::WebURLResponse; | |
24 | |
25 namespace content { | |
26 | |
27 namespace { | |
28 | |
29 // The list of response headers that we do not copy from the original | |
30 // response when generating a WebURLResponse for a MIME payload. | |
31 const char* kReplaceHeaders[] = { | |
32 "content-type", | |
33 "content-length", | |
34 "content-disposition", | |
35 "content-range", | |
36 "range", | |
37 "set-cookie" | |
38 }; | |
39 | |
40 class HeaderCopier : public WebHTTPHeaderVisitor { | |
41 public: | |
42 HeaderCopier(WebURLResponse* response) | |
43 : response_(response) { | |
44 } | |
45 void visitHeader(const WebString& name, const WebString& value) override { | |
46 const std::string& name_utf8 = name.utf8(); | |
47 for (size_t i = 0; i < arraysize(kReplaceHeaders); ++i) { | |
48 if (base::LowerCaseEqualsASCII(name_utf8, kReplaceHeaders[i])) | |
49 return; | |
50 } | |
51 response_->setHTTPHeaderField(name, value); | |
52 } | |
53 private: | |
54 WebURLResponse* response_; | |
55 }; | |
56 | |
57 } // namespace | |
58 | |
59 MultipartResponseDelegate::MultipartResponseDelegate( | |
60 WebURLLoaderClient* client, | |
61 WebURLLoader* loader, | |
62 const WebURLResponse& response, | |
63 const std::string& boundary) | |
64 : client_(client), | |
65 loader_(loader), | |
66 original_response_(response), | |
67 encoded_data_length_(0), | |
68 boundary_("--"), | |
69 first_received_data_(true), | |
70 processing_headers_(false), | |
71 stop_sending_(false), | |
72 has_sent_first_response_(false) { | |
73 // Some servers report a boundary prefixed with "--". See bug 5786. | |
74 if (base::StartsWith(boundary, "--", base::CompareCase::SENSITIVE)) { | |
75 boundary_.assign(boundary); | |
76 } else { | |
77 boundary_.append(boundary); | |
78 } | |
79 } | |
80 | |
81 void MultipartResponseDelegate::OnReceivedData(const char* data, | |
82 int data_len, | |
83 int encoded_data_length) { | |
84 // stop_sending_ means that we've already received the final boundary token. | |
85 // The server should stop sending us data at this point, but if it does, we | |
86 // just throw it away. | |
87 if (stop_sending_) | |
88 return; | |
89 | |
90 data_.append(data, data_len); | |
91 encoded_data_length_ += encoded_data_length; | |
92 if (first_received_data_) { | |
93 // Some servers don't send a boundary token before the first chunk of | |
94 // data. We handle this case anyway (Gecko does too). | |
95 first_received_data_ = false; | |
96 | |
97 // Eat leading \r\n | |
98 int pos = PushOverLine(data_, 0); | |
99 if (pos) | |
100 data_ = data_.substr(pos); | |
101 | |
102 if (data_.length() < boundary_.length() + 2) { | |
103 // We don't have enough data yet to make a boundary token. Just wait | |
104 // until the next chunk of data arrives. | |
105 first_received_data_ = true; | |
106 return; | |
107 } | |
108 | |
109 if (0 != data_.compare(0, boundary_.length(), boundary_)) { | |
110 data_ = boundary_ + "\n" + data_; | |
111 } | |
112 } | |
113 DCHECK(!first_received_data_); | |
114 | |
115 // Headers | |
116 if (processing_headers_) { | |
117 // Eat leading \r\n | |
118 int pos = PushOverLine(data_, 0); | |
119 if (pos) | |
120 data_ = data_.substr(pos); | |
121 | |
122 if (ParseHeaders()) { | |
123 // Successfully parsed headers. | |
124 processing_headers_ = false; | |
125 } else { | |
126 // Get more data before trying again. | |
127 return; | |
128 } | |
129 } | |
130 DCHECK(!processing_headers_); | |
131 | |
132 size_t boundary_pos; | |
133 while ((boundary_pos = FindBoundary()) != std::string::npos) { | |
134 if (client_) { | |
135 // Strip out trailing \n\r characters in the buffer preceding the | |
136 // boundary on the same lines as Firefox. | |
137 size_t data_length = boundary_pos; | |
138 if (boundary_pos > 0 && data_[boundary_pos - 1] == '\n') { | |
139 data_length--; | |
140 if (boundary_pos > 1 && data_[boundary_pos - 2] == '\r') { | |
141 data_length--; | |
142 } | |
143 } | |
144 if (data_length > 0) { | |
145 // Send the last data chunk. | |
146 client_->didReceiveData(loader_, | |
147 data_.data(), | |
148 static_cast<int>(data_length), | |
149 encoded_data_length_); | |
150 encoded_data_length_ = 0; | |
151 } | |
152 } | |
153 size_t boundary_end_pos = boundary_pos + boundary_.length(); | |
154 if (boundary_end_pos < data_.length() && '-' == data_[boundary_end_pos]) { | |
155 // This was the last boundary so we can stop processing. | |
156 stop_sending_ = true; | |
157 data_.clear(); | |
158 return; | |
159 } | |
160 | |
161 // We can now throw out data up through the boundary | |
162 int offset = PushOverLine(data_, boundary_end_pos); | |
163 data_ = data_.substr(boundary_end_pos + offset); | |
164 | |
165 // Ok, back to parsing headers | |
166 if (!ParseHeaders()) { | |
167 processing_headers_ = true; | |
168 break; | |
169 } | |
170 } | |
171 | |
172 // At this point, we should send over any data we have, but keep enough data | |
173 // buffered to handle a boundary that may have been truncated. | |
174 if (!processing_headers_ && data_.length() > boundary_.length()) { | |
175 // If the last character is a new line character, go ahead and just send | |
176 // everything we have buffered. This matches an optimization in Gecko. | |
177 int send_length = data_.length() - boundary_.length(); | |
178 if (data_.back() == '\n') | |
179 send_length = data_.length(); | |
180 if (client_) | |
181 client_->didReceiveData(loader_, | |
182 data_.data(), | |
183 send_length, | |
184 encoded_data_length_); | |
185 data_ = data_.substr(send_length); | |
186 encoded_data_length_ = 0; | |
187 } | |
188 } | |
189 | |
190 void MultipartResponseDelegate::OnCompletedRequest() { | |
191 // If we have any pending data and we're not in a header, go ahead and send | |
192 // it to WebCore. | |
193 if (!processing_headers_ && !data_.empty() && !stop_sending_ && client_) { | |
194 client_->didReceiveData(loader_, | |
195 data_.data(), | |
196 static_cast<int>(data_.length()), | |
197 encoded_data_length_); | |
198 encoded_data_length_ = 0; | |
199 } | |
200 } | |
201 | |
202 int MultipartResponseDelegate::PushOverLine(const std::string& data, | |
203 size_t pos) { | |
204 int offset = 0; | |
205 if (pos < data.length() && (data[pos] == '\r' || data[pos] == '\n')) { | |
206 ++offset; | |
207 if (pos + 1 < data.length() && data[pos + 1] == '\n') | |
208 ++offset; | |
209 } | |
210 return offset; | |
211 } | |
212 | |
213 bool MultipartResponseDelegate::ParseHeaders() { | |
214 int headers_end_pos = net::HttpUtil::LocateEndOfAdditionalHeaders( | |
215 data_.c_str(), data_.size(), 0); | |
216 | |
217 if (headers_end_pos < 0) | |
218 return false; | |
219 | |
220 // Eat headers and prepend a status line as is required by | |
221 // HttpResponseHeaders. | |
222 std::string headers("HTTP/1.1 200 OK\r\n"); | |
223 headers.append(data_, 0, headers_end_pos); | |
224 data_ = data_.substr(headers_end_pos); | |
225 | |
226 scoped_refptr<net::HttpResponseHeaders> response_headers = | |
227 new net::HttpResponseHeaders( | |
228 net::HttpUtil::AssembleRawHeaders(headers.c_str(), headers.size())); | |
229 | |
230 // Create a WebURLResponse based on the original set of headers + the | |
231 // replacement headers. We only replace the same few headers that gecko | |
232 // does. See netwerk/streamconv/converters/nsMultiMixedConv.cpp. | |
233 WebURLResponse response(original_response_.url()); | |
234 | |
235 std::string mime_type; | |
236 response_headers->GetMimeType(&mime_type); | |
237 response.setMIMEType(WebString::fromUTF8(mime_type)); | |
238 | |
239 std::string charset; | |
240 response_headers->GetCharset(&charset); | |
241 response.setTextEncodingName(WebString::fromUTF8(charset)); | |
242 | |
243 // Copy the response headers from the original response. | |
244 HeaderCopier copier(&response); | |
245 original_response_.visitHTTPHeaderFields(&copier); | |
246 | |
247 // Replace original headers with multipart headers listed in kReplaceHeaders. | |
248 for (size_t i = 0; i < arraysize(kReplaceHeaders); ++i) { | |
249 std::string name(kReplaceHeaders[i]); | |
250 std::string value; | |
251 size_t iterator = 0; | |
252 while (response_headers->EnumerateHeader(&iterator, name, &value)) { | |
253 response.addHTTPHeaderField(WebString::fromLatin1(name), | |
254 WebString::fromLatin1(value)); | |
255 } | |
256 } | |
257 // To avoid recording every multipart load as a separate visit in | |
258 // the history database, we want to keep track of whether the response | |
259 // is part of a multipart payload. We do want to record the first visit, | |
260 // so we only set isMultipartPayload to true after the first visit. | |
261 response.setIsMultipartPayload(has_sent_first_response_); | |
262 has_sent_first_response_ = true; | |
263 // Send the response! | |
264 if (client_) | |
265 client_->didReceiveResponse(loader_, response); | |
266 | |
267 return true; | |
268 } | |
269 | |
270 // Boundaries are supposed to be preceeded with --, but it looks like gecko | |
271 // doesn't require the dashes to exist. See nsMultiMixedConv::FindToken. | |
272 size_t MultipartResponseDelegate::FindBoundary() { | |
273 size_t boundary_pos = data_.find(boundary_); | |
274 if (boundary_pos != std::string::npos) { | |
275 // Back up over -- for backwards compat | |
276 // TODO(tc): Don't we only want to do this once? Gecko code doesn't seem | |
277 // to care. | |
278 if (boundary_pos >= 2) { | |
279 if ('-' == data_[boundary_pos - 1] && '-' == data_[boundary_pos - 2]) { | |
280 boundary_pos -= 2; | |
281 boundary_ = "--" + boundary_; | |
282 } | |
283 } | |
284 } | |
285 return boundary_pos; | |
286 } | |
287 | |
288 } // namespace content | |
OLD | NEW |