Home | History | Annotate | Download | only in npapi
      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