1 // Copyright (c) 2013 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/npapi/plugin_url_fetcher.h" 6 7 #include "base/memory/scoped_ptr.h" 8 #include "content/child/child_thread.h" 9 #include "content/child/npapi/webplugin.h" 10 #include "content/child/npapi/plugin_host.h" 11 #include "content/child/npapi/plugin_instance.h" 12 #include "content/child/npapi/plugin_stream_url.h" 13 #include "content/child/npapi/webplugin_resource_client.h" 14 #include "content/child/plugin_messages.h" 15 #include "content/child/resource_dispatcher.h" 16 #include "net/base/load_flags.h" 17 #include "net/base/net_errors.h" 18 #include "net/http/http_response_headers.h" 19 #include "third_party/WebKit/public/platform/WebURLLoaderClient.h" 20 #include "third_party/WebKit/public/platform/WebURLResponse.h" 21 #include "webkit/child/multipart_response_delegate.h" 22 #include "webkit/child/weburlloader_impl.h" 23 #include "webkit/common/resource_request_body.h" 24 25 namespace content { 26 namespace { 27 28 // This class handles individual multipart responses. It is instantiated when 29 // we receive HTTP status code 206 in the HTTP response. This indicates 30 // that the response could have multiple parts each separated by a boundary 31 // specified in the response header. 32 // TODO(jam): this is similar to MultiPartResponseClient in webplugin_impl.cc, 33 // we should remove that other class once we switch to loading from the plugin 34 // process by default. 35 class MultiPartResponseClient : public blink::WebURLLoaderClient { 36 public: 37 explicit MultiPartResponseClient(PluginStreamUrl* plugin_stream) 38 : byte_range_lower_bound_(0), plugin_stream_(plugin_stream) {} 39 40 // blink::WebURLLoaderClient implementation: 41 virtual void didReceiveResponse( 42 blink::WebURLLoader* loader, 43 const blink::WebURLResponse& response) OVERRIDE { 44 int64 byte_range_upper_bound, instance_size; 45 if (!webkit_glue::MultipartResponseDelegate::ReadContentRanges( 46 response, &byte_range_lower_bound_, &byte_range_upper_bound, 47 &instance_size)) { 48 NOTREACHED(); 49 } 50 } 51 virtual void didReceiveData(blink::WebURLLoader* loader, 52 const char* data, 53 int data_length, 54 int encoded_data_length) OVERRIDE { 55 // TODO(ananta) 56 // We should defer further loads on multipart resources on the same lines 57 // as regular resources requested by plugins to prevent reentrancy. 58 int64 data_offset = byte_range_lower_bound_; 59 byte_range_lower_bound_ += data_length; 60 plugin_stream_->DidReceiveData(data, data_length, data_offset); 61 // DANGER: this instance may be deleted at this point. 62 } 63 64 private: 65 // The lower bound of the byte range. 66 int64 byte_range_lower_bound_; 67 // The handler for the data. 68 PluginStreamUrl* plugin_stream_; 69 }; 70 71 } // namespace 72 73 PluginURLFetcher::PluginURLFetcher(PluginStreamUrl* plugin_stream, 74 const GURL& url, 75 const GURL& first_party_for_cookies, 76 const std::string& method, 77 const char* buf, 78 unsigned int len, 79 const GURL& referrer, 80 bool notify_redirects, 81 bool is_plugin_src_load, 82 int origin_pid, 83 int render_view_id, 84 unsigned long resource_id, 85 bool copy_stream_data) 86 : plugin_stream_(plugin_stream), 87 url_(url), 88 first_party_for_cookies_(first_party_for_cookies), 89 method_(method), 90 referrer_(referrer), 91 notify_redirects_(notify_redirects), 92 is_plugin_src_load_(is_plugin_src_load), 93 resource_id_(resource_id), 94 copy_stream_data_(copy_stream_data), 95 data_offset_(0), 96 pending_failure_notification_(false) { 97 webkit_glue::ResourceLoaderBridge::RequestInfo request_info; 98 request_info.method = method; 99 request_info.url = url; 100 request_info.first_party_for_cookies = first_party_for_cookies; 101 request_info.referrer = referrer; 102 request_info.load_flags = net::LOAD_NORMAL; 103 request_info.requestor_pid = origin_pid; 104 request_info.request_type = ResourceType::OBJECT; 105 request_info.routing_id = render_view_id; 106 107 std::vector<char> body; 108 if (method == "POST") { 109 bool content_type_found = false; 110 std::vector<std::string> names; 111 std::vector<std::string> values; 112 PluginHost::SetPostData(buf, len, &names, &values, &body); 113 for (size_t i = 0; i < names.size(); ++i) { 114 if (!request_info.headers.empty()) 115 request_info.headers += "\r\n"; 116 request_info.headers += names[i] + ": " + values[i]; 117 if (LowerCaseEqualsASCII(names[i], "content-type")) 118 content_type_found = true; 119 } 120 121 if (!content_type_found) { 122 if (!request_info.headers.empty()) 123 request_info.headers += "\r\n"; 124 request_info.headers += "Content-Type: application/x-www-form-urlencoded"; 125 } 126 } 127 128 bridge_.reset(ChildThread::current()->resource_dispatcher()->CreateBridge( 129 request_info)); 130 if (!body.empty()) { 131 scoped_refptr<webkit_glue::ResourceRequestBody> request_body = 132 new webkit_glue::ResourceRequestBody; 133 request_body->AppendBytes(&body[0], body.size()); 134 bridge_->SetRequestBody(request_body.get()); 135 } 136 137 bridge_->Start(this); 138 139 // TODO(jam): range requests 140 } 141 142 PluginURLFetcher::~PluginURLFetcher() { 143 } 144 145 void PluginURLFetcher::Cancel() { 146 bridge_->Cancel(); 147 } 148 149 void PluginURLFetcher::URLRedirectResponse(bool allow) { 150 if (allow) { 151 bridge_->SetDefersLoading(false); 152 } else { 153 bridge_->Cancel(); 154 plugin_stream_->DidFail(resource_id_); // That will delete |this|. 155 } 156 } 157 158 void PluginURLFetcher::OnUploadProgress(uint64 position, uint64 size) { 159 } 160 161 bool PluginURLFetcher::OnReceivedRedirect( 162 const GURL& new_url, 163 const webkit_glue::ResourceResponseInfo& info, 164 bool* has_new_first_party_for_cookies, 165 GURL* new_first_party_for_cookies) { 166 // TODO(jam): THIS LOGIC IS COPIED FROM WebPluginImpl::willSendRequest until 167 // kDirectNPAPIRequests is the default and we can remove the old path there. 168 169 // Currently this check is just to catch an https -> http redirect when 170 // loading the main plugin src URL. Longer term, we could investigate 171 // firing mixed diplay or scripting issues for subresource loads 172 // initiated by plug-ins. 173 if (is_plugin_src_load_ && 174 !plugin_stream_->instance()->webplugin()->CheckIfRunInsecureContent( 175 new_url)) { 176 plugin_stream_->DidFail(resource_id_); // That will delete |this|. 177 return false; 178 } 179 180 // It's unfortunate that this logic of when a redirect's method changes is 181 // in url_request.cc, but weburlloader_impl.cc and this file have to duplicate 182 // it instead of passing that information. 183 int response_code = info.headers->response_code(); 184 if (response_code != 307) 185 method_ = "GET"; 186 GURL old_url = url_; 187 url_ = new_url; 188 *has_new_first_party_for_cookies = true; 189 *new_first_party_for_cookies = first_party_for_cookies_; 190 191 // If the plugin does not participate in url redirect notifications then just 192 // block cross origin 307 POST redirects. 193 if (!notify_redirects_) { 194 if (response_code == 307 && method_ == "POST" && 195 old_url.GetOrigin() != new_url.GetOrigin()) { 196 plugin_stream_->DidFail(resource_id_); // That will delete |this|. 197 return false; 198 } 199 } else { 200 // Pause the request while we ask the plugin what to do about the redirect. 201 bridge_->SetDefersLoading(true); 202 plugin_stream_->WillSendRequest(url_, response_code); 203 } 204 205 return true; 206 } 207 208 void PluginURLFetcher::OnReceivedResponse( 209 const webkit_glue::ResourceResponseInfo& info) { 210 // TODO(jam): THIS LOGIC IS COPIED FROM WebPluginImpl::didReceiveResponse 211 // GetAllHeaders, and GetResponseInfo until kDirectNPAPIRequests is the 212 // default and we can remove the old path there. 213 214 bool request_is_seekable = true; 215 DCHECK(!multipart_delegate_.get()); 216 if (plugin_stream_->seekable()) { 217 int response_code = info.headers->response_code(); 218 if (response_code == 206) { 219 blink::WebURLResponse response; 220 response.initialize(); 221 webkit_glue::WebURLLoaderImpl::PopulateURLResponse(url_, info, &response); 222 223 std::string multipart_boundary; 224 if (webkit_glue::MultipartResponseDelegate::ReadMultipartBoundary( 225 response, &multipart_boundary)) { 226 plugin_stream_->instance()->webplugin()->DidStartLoading(); 227 228 MultiPartResponseClient* multi_part_response_client = 229 new MultiPartResponseClient(plugin_stream_); 230 231 multipart_delegate_.reset(new webkit_glue::MultipartResponseDelegate( 232 multi_part_response_client, NULL, response, multipart_boundary)); 233 234 // Multiple ranges requested, data will be delivered by 235 // MultipartResponseDelegate. 236 data_offset_ = 0; 237 return; 238 } 239 240 int64 upper_bound = 0, instance_size = 0; 241 // Single range requested - go through original processing for 242 // non-multipart requests, but update data offset. 243 webkit_glue::MultipartResponseDelegate::ReadContentRanges( 244 response, &data_offset_, &upper_bound, &instance_size); 245 } else if (response_code == 200) { 246 // TODO: should we handle this case? We used to but it's not clear that we 247 // still need to. This was bug 5403, fixed in r7139. 248 } 249 } 250 251 // If the length comes in as -1, then it indicates that it was not 252 // read off the HTTP headers. We replicate Safari webkit behavior here, 253 // which is to set it to 0. 254 int expected_length = std::max(static_cast<int>(info.content_length), 0); 255 256 base::Time temp; 257 uint32 last_modified = 0; 258 std::string headers; 259 if (info.headers) { // NULL for data: urls. 260 if (info.headers->GetLastModifiedValue(&temp)) 261 last_modified = static_cast<uint32>(temp.ToDoubleT()); 262 263 // TODO(darin): Shouldn't we also report HTTP version numbers? 264 int response_code = info.headers->response_code(); 265 headers = base::StringPrintf("HTTP %d ", response_code); 266 headers += info.headers->GetStatusText(); 267 headers += "\n"; 268 269 void* iter = NULL; 270 std::string name, value; 271 while (info.headers->EnumerateHeaderLines(&iter, &name, &value)) { 272 // TODO(darin): Should we really exclude headers with an empty value? 273 if (!name.empty() && !value.empty()) 274 headers += name + ": " + value + "\n"; 275 } 276 277 // Bug http://b/issue?id=925559. The flash plugin would not handle the HTTP 278 // error codes in the stream header and as a result, was unaware of the fate 279 // of the HTTP requests issued via NPN_GetURLNotify. Webkit and FF destroy 280 // the stream and invoke the NPP_DestroyStream function on the plugin if the 281 // HTTPrequest fails. 282 if ((url_.SchemeIs("http") || url_.SchemeIs("https")) && 283 (response_code < 100 || response_code >= 400)) { 284 pending_failure_notification_ = true; 285 } 286 } 287 288 plugin_stream_->DidReceiveResponse(info.mime_type, 289 headers, 290 expected_length, 291 last_modified, 292 request_is_seekable); 293 } 294 295 void PluginURLFetcher::OnDownloadedData(int len, 296 int encoded_data_length) { 297 } 298 299 void PluginURLFetcher::OnReceivedData(const char* data, 300 int data_length, 301 int encoded_data_length) { 302 if (multipart_delegate_) { 303 multipart_delegate_->OnReceivedData(data, data_length, encoded_data_length); 304 } else { 305 int64 offset = data_offset_; 306 data_offset_ += data_length; 307 308 if (copy_stream_data_) { 309 // QuickTime writes to this memory, and since we got it from 310 // ResourceDispatcher it's not mapped for write access in this process. 311 // http://crbug.com/308466. 312 scoped_ptr<char[]> data_copy(new char[data_length]); 313 memcpy(data_copy.get(), data, data_length); 314 plugin_stream_->DidReceiveData(data_copy.get(), data_length, offset); 315 } else { 316 plugin_stream_->DidReceiveData(data, data_length, offset); 317 } 318 // DANGER: this instance may be deleted at this point. 319 } 320 } 321 322 void PluginURLFetcher::OnCompletedRequest( 323 int error_code, 324 bool was_ignored_by_handler, 325 const std::string& security_info, 326 const base::TimeTicks& completion_time) { 327 if (multipart_delegate_) { 328 multipart_delegate_->OnCompletedRequest(); 329 multipart_delegate_.reset(); 330 } 331 332 if (error_code == net::OK) { 333 plugin_stream_->DidFinishLoading(resource_id_); 334 } else { 335 plugin_stream_->DidFail(resource_id_); 336 } 337 } 338 339 } // namespace content 340