| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 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 "chrome_frame/npapi_url_request.h" | |
| 6 | |
| 7 #include "base/string_number_conversions.h" | |
| 8 #include "base/threading/platform_thread.h" | |
| 9 #include "chrome/common/automation_messages.h" | |
| 10 #include "chrome_frame/chrome_frame_npapi.h" | |
| 11 #include "chrome_frame/np_browser_functions.h" | |
| 12 #include "chrome_frame/np_utils.h" | |
| 13 #include "net/base/net_errors.h" | |
| 14 | |
| 15 class NPAPIUrlRequest : public PluginUrlRequest { | |
| 16 public: | |
| 17 explicit NPAPIUrlRequest(NPP instance); | |
| 18 ~NPAPIUrlRequest(); | |
| 19 | |
| 20 virtual bool Start(); | |
| 21 virtual void Stop(); | |
| 22 virtual bool Read(int bytes_to_read); | |
| 23 | |
| 24 // Called from NPAPI | |
| 25 NPError OnStreamCreated(const char* mime_type, NPStream* stream); | |
| 26 NPError OnStreamDestroyed(NPReason reason); | |
| 27 int OnWriteReady(); | |
| 28 int OnWrite(void* buffer, int len); | |
| 29 | |
| 30 // Thread unsafe implementation of ref counting, since | |
| 31 // this will be called on the plugin UI thread only. | |
| 32 virtual unsigned long API_CALL AddRef(); | |
| 33 virtual unsigned long API_CALL Release(); | |
| 34 | |
| 35 const net::URLRequestStatus& status() const { | |
| 36 return status_; | |
| 37 } | |
| 38 | |
| 39 NPP instance() const { | |
| 40 return instance_; | |
| 41 } | |
| 42 | |
| 43 private: | |
| 44 unsigned long ref_count_; | |
| 45 NPP instance_; | |
| 46 NPStream* stream_; | |
| 47 size_t pending_read_size_; | |
| 48 net::URLRequestStatus status_; | |
| 49 | |
| 50 base::PlatformThreadId thread_; | |
| 51 static int instance_count_; | |
| 52 DISALLOW_COPY_AND_ASSIGN(NPAPIUrlRequest); | |
| 53 }; | |
| 54 | |
| 55 int NPAPIUrlRequest::instance_count_ = 0; | |
| 56 | |
| 57 NPAPIUrlRequest::NPAPIUrlRequest(NPP instance) | |
| 58 : ref_count_(0), instance_(instance), stream_(NULL), | |
| 59 pending_read_size_(0), | |
| 60 status_(net::URLRequestStatus::FAILED, net::ERR_FAILED), | |
| 61 thread_(base::PlatformThread::CurrentId()) { | |
| 62 DVLOG(1) << "Created request. Count: " << ++instance_count_; | |
| 63 } | |
| 64 | |
| 65 NPAPIUrlRequest::~NPAPIUrlRequest() { | |
| 66 DVLOG(1) << "Deleted request. Count: " << --instance_count_; | |
| 67 } | |
| 68 | |
| 69 // NPAPIUrlRequest member defines. | |
| 70 bool NPAPIUrlRequest::Start() { | |
| 71 NPError result = NPERR_GENERIC_ERROR; | |
| 72 DVLOG(1) << "Starting URL request: " << url(); | |
| 73 // Initialize the net::HostPortPair structure from the url | |
| 74 socket_address_ = net::HostPortPair::FromURL(GURL(url())); | |
| 75 | |
| 76 if (LowerCaseEqualsASCII(method(), "get")) { | |
| 77 // TODO(joshia): if we have extra headers for HTTP GET, then implement | |
| 78 // it using XHR | |
| 79 result = npapi::GetURLNotify(instance_, url().c_str(), NULL, this); | |
| 80 } else if (LowerCaseEqualsASCII(method(), "post")) { | |
| 81 uint32 data_len = static_cast<uint32>(post_data_len()); | |
| 82 | |
| 83 std::string buffer; | |
| 84 if (extra_headers().length() > 0) { | |
| 85 buffer += extra_headers(); | |
| 86 TrimWhitespace(buffer, TRIM_ALL, &buffer); | |
| 87 | |
| 88 // Firefox looks specifically for "Content-length: \d+\r\n\r\n" | |
| 89 // to detect if extra headers are added to the message buffer. | |
| 90 buffer += "\r\nContent-length: "; | |
| 91 buffer += base::IntToString(data_len); | |
| 92 buffer += "\r\n\r\n"; | |
| 93 } | |
| 94 | |
| 95 std::string data; | |
| 96 data.resize(data_len); | |
| 97 uint32 bytes_read; | |
| 98 upload_data_->Read(&data[0], data_len, | |
| 99 reinterpret_cast<ULONG*>(&bytes_read)); | |
| 100 DCHECK_EQ(data_len, bytes_read); | |
| 101 buffer += data; | |
| 102 | |
| 103 result = npapi::PostURLNotify(instance_, url().c_str(), NULL, | |
| 104 buffer.length(), buffer.c_str(), false, this); | |
| 105 } else { | |
| 106 NOTREACHED() << "PluginUrlRequest only supports 'GET'/'POST'"; | |
| 107 } | |
| 108 | |
| 109 if (NPERR_NO_ERROR != result) { | |
| 110 int os_error = net::ERR_FAILED; | |
| 111 switch (result) { | |
| 112 case NPERR_INVALID_URL: | |
| 113 os_error = net::ERR_INVALID_URL; | |
| 114 break; | |
| 115 default: | |
| 116 break; | |
| 117 } | |
| 118 | |
| 119 delegate_->OnResponseEnd(id(), | |
| 120 net::URLRequestStatus(net::URLRequestStatus::FAILED, os_error)); | |
| 121 return false; | |
| 122 } | |
| 123 | |
| 124 return true; | |
| 125 } | |
| 126 | |
| 127 void NPAPIUrlRequest::Stop() { | |
| 128 DVLOG(1) << "Finished request: Url - " << url() | |
| 129 << " result: " << static_cast<int>(status_.status()); | |
| 130 | |
| 131 status_.set_status(net::URLRequestStatus::CANCELED); | |
| 132 if (stream_) { | |
| 133 npapi::DestroyStream(instance_, stream_, NPRES_USER_BREAK); | |
| 134 stream_ = NULL; | |
| 135 } | |
| 136 } | |
| 137 | |
| 138 bool NPAPIUrlRequest::Read(int bytes_to_read) { | |
| 139 pending_read_size_ = bytes_to_read; | |
| 140 return true; | |
| 141 } | |
| 142 | |
| 143 NPError NPAPIUrlRequest::OnStreamCreated(const char* mime_type, | |
| 144 NPStream* stream) { | |
| 145 stream_ = stream; | |
| 146 status_.set_status(net::URLRequestStatus::IO_PENDING); | |
| 147 // TODO(iyengar) | |
| 148 // Add support for passing persistent cookies and information about any URL | |
| 149 // redirects to Chrome. | |
| 150 delegate_->OnResponseStarted(id(), mime_type, stream->headers, stream->end, | |
| 151 base::Time::FromTimeT(stream->lastmodified), std::string(), 0, | |
| 152 socket_address_); | |
| 153 return NPERR_NO_ERROR; | |
| 154 } | |
| 155 | |
| 156 NPError NPAPIUrlRequest::OnStreamDestroyed(NPReason reason) { | |
| 157 // If the request has been aborted, then ignore the |reason| argument. | |
| 158 // Normally the execution flow is such than NPRES_USER_BREAK will be passed | |
| 159 // when the stream is aborted, but sometimes NPRES_NETWORK_ERROR is passed | |
| 160 // instead. To prevent Chrome from receiving a notification of a failed | |
| 161 // network connection, when Chrome actually canceled the request, we ignore | |
| 162 // the status here. | |
| 163 if (net::URLRequestStatus::CANCELED != status_.status()) { | |
| 164 switch (reason) { | |
| 165 case NPRES_DONE: | |
| 166 status_.set_status(net::URLRequestStatus::SUCCESS); | |
| 167 status_.set_os_error(0); | |
| 168 break; | |
| 169 case NPRES_USER_BREAK: | |
| 170 status_.set_status(net::URLRequestStatus::CANCELED); | |
| 171 status_.set_os_error(net::ERR_ABORTED); | |
| 172 break; | |
| 173 case NPRES_NETWORK_ERR: | |
| 174 default: | |
| 175 status_.set_status(net::URLRequestStatus::FAILED); | |
| 176 status_.set_os_error(net::ERR_CONNECTION_CLOSED); | |
| 177 break; | |
| 178 } | |
| 179 } | |
| 180 | |
| 181 delegate_->OnResponseEnd(id(), status_); | |
| 182 return NPERR_NO_ERROR; | |
| 183 } | |
| 184 | |
| 185 int NPAPIUrlRequest::OnWriteReady() { | |
| 186 return pending_read_size_; | |
| 187 } | |
| 188 | |
| 189 int NPAPIUrlRequest::OnWrite(void* buffer, int len) { | |
| 190 pending_read_size_ = 0; | |
| 191 std::string data(reinterpret_cast<char*>(buffer), len); | |
| 192 delegate_->OnReadComplete(id(), data); | |
| 193 return len; | |
| 194 } | |
| 195 | |
| 196 STDMETHODIMP_(ULONG) NPAPIUrlRequest::AddRef() { | |
| 197 DCHECK_EQ(base::PlatformThread::CurrentId(), thread_); | |
| 198 ++ref_count_; | |
| 199 return ref_count_; | |
| 200 } | |
| 201 | |
| 202 STDMETHODIMP_(ULONG) NPAPIUrlRequest::Release() { | |
| 203 DCHECK_EQ(base::PlatformThread::CurrentId(), thread_); | |
| 204 unsigned long ret = --ref_count_; | |
| 205 if (!ret) | |
| 206 delete this; | |
| 207 | |
| 208 return ret; | |
| 209 } | |
| 210 | |
| 211 NPAPIUrlRequestManager::NPAPIUrlRequestManager() : instance_(NULL) { | |
| 212 } | |
| 213 | |
| 214 NPAPIUrlRequestManager::~NPAPIUrlRequestManager() { | |
| 215 StopAll(); | |
| 216 } | |
| 217 | |
| 218 // PluginUrlRequestManager implementation | |
| 219 PluginUrlRequestManager::ThreadSafeFlags | |
| 220 NPAPIUrlRequestManager::GetThreadSafeFlags() { | |
| 221 return PluginUrlRequestManager::NOT_THREADSAFE; | |
| 222 } | |
| 223 | |
| 224 void NPAPIUrlRequestManager::StartRequest(int request_id, | |
| 225 const AutomationURLRequest& request_info) { | |
| 226 scoped_refptr<NPAPIUrlRequest> new_request(new NPAPIUrlRequest(instance_)); | |
| 227 DCHECK(new_request); | |
| 228 if (new_request->Initialize(this, request_id, request_info.url, | |
| 229 request_info.method, request_info.referrer, | |
| 230 request_info.extra_request_headers, | |
| 231 request_info.upload_data, | |
| 232 static_cast<ResourceType::Type>(request_info.resource_type), | |
| 233 enable_frame_busting_, | |
| 234 request_info.load_flags)) { | |
| 235 DCHECK(request_map_.find(request_id) == request_map_.end()); | |
| 236 if (new_request->Start()) { | |
| 237 request_map_[request_id] = new_request; | |
| 238 // Keep additional reference on request for NPSTREAM | |
| 239 // This will be released in NPP_UrlNotify | |
| 240 new_request->AddRef(); | |
| 241 } | |
| 242 } | |
| 243 } | |
| 244 | |
| 245 void NPAPIUrlRequestManager::ReadRequest(int request_id, int bytes_to_read) { | |
| 246 scoped_refptr<NPAPIUrlRequest> request = LookupRequest(request_id); | |
| 247 if (request) | |
| 248 request->Read(bytes_to_read); | |
| 249 } | |
| 250 | |
| 251 void NPAPIUrlRequestManager::EndRequest(int request_id) { | |
| 252 scoped_refptr<NPAPIUrlRequest> request = LookupRequest(request_id); | |
| 253 if (request) | |
| 254 request->Stop(); | |
| 255 } | |
| 256 | |
| 257 void NPAPIUrlRequestManager::StopAll() { | |
| 258 std::vector<scoped_refptr<NPAPIUrlRequest> > request_list; | |
| 259 // We copy the pending requests into a temporary vector as the Stop | |
| 260 // function in the request could also try to delete the request from | |
| 261 // the request map and the iterator could end up being invalid. | |
| 262 for (RequestMap::iterator it = request_map_.begin(); | |
| 263 it != request_map_.end(); ++it) { | |
| 264 DCHECK(it->second != NULL); | |
| 265 request_list.push_back(it->second); | |
| 266 } | |
| 267 | |
| 268 for (std::vector<scoped_refptr<NPAPIUrlRequest> >::size_type index = 0; | |
| 269 index < request_list.size(); ++index) { | |
| 270 request_list[index]->Stop(); | |
| 271 } | |
| 272 } | |
| 273 | |
| 274 void NPAPIUrlRequestManager::SetCookiesForUrl(const GURL& url, | |
| 275 const std::string& cookie) { | |
| 276 if (npapi::VersionMinor() >= NPVERS_HAS_URL_AND_AUTH_INFO) { | |
| 277 npapi::SetValueForURL(instance_, NPNURLVCookie, url.spec().c_str(), | |
| 278 cookie.c_str(), cookie.length()); | |
| 279 } else { | |
| 280 NOTREACHED() << "Unsupported version"; | |
| 281 } | |
| 282 } | |
| 283 | |
| 284 void NPAPIUrlRequestManager::GetCookiesForUrl(const GURL& url, int cookie_id) { | |
| 285 std::string cookie_string; | |
| 286 bool success = false; | |
| 287 | |
| 288 if (npapi::VersionMinor() >= NPVERS_HAS_URL_AND_AUTH_INFO) { | |
| 289 char* cookies = NULL; | |
| 290 unsigned int cookie_length = 0; | |
| 291 NPError ret = npapi::GetValueForURL(instance_, NPNURLVCookie, | |
| 292 url.spec().c_str(), &cookies, | |
| 293 &cookie_length); | |
| 294 if (ret == NPERR_NO_ERROR) { | |
| 295 DVLOG(1) << "Obtained cookies:" << cookies << " from host"; | |
| 296 cookie_string.append(cookies, cookie_length); | |
| 297 npapi::MemFree(cookies); | |
| 298 success = true; | |
| 299 } | |
| 300 } else { | |
| 301 NOTREACHED() << "Unsupported version"; | |
| 302 } | |
| 303 | |
| 304 if (!success) | |
| 305 DVLOG(1) << "Failed to return cookies for url:" << url.spec(); | |
| 306 | |
| 307 OnCookiesRetrieved(success, url, cookie_string, cookie_id); | |
| 308 } | |
| 309 | |
| 310 // PluginRequestDelegate implementation. | |
| 311 // Callbacks from NPAPIUrlRequest. Simply forward to the delegate. | |
| 312 void NPAPIUrlRequestManager::OnResponseStarted(int request_id, | |
| 313 const char* mime_type, const char* headers, int size, | |
| 314 base::Time last_modified, const std::string& redirect_url, | |
| 315 int redirect_status, const net::HostPortPair& socket_address) { | |
| 316 delegate_->OnResponseStarted(request_id, mime_type, headers, size, | |
| 317 last_modified, redirect_url, redirect_status, socket_address); | |
| 318 } | |
| 319 | |
| 320 void NPAPIUrlRequestManager::OnReadComplete(int request_id, | |
| 321 const std::string& data) { | |
| 322 delegate_->OnReadComplete(request_id, data); | |
| 323 } | |
| 324 | |
| 325 void NPAPIUrlRequestManager::OnResponseEnd( | |
| 326 int request_id, | |
| 327 const net::URLRequestStatus& status) { | |
| 328 // Delete from map. | |
| 329 RequestMap::iterator it = request_map_.find(request_id); | |
| 330 DCHECK(request_map_.end() != it); | |
| 331 scoped_refptr<NPAPIUrlRequest> request = (*it).second; | |
| 332 request_map_.erase(it); | |
| 333 | |
| 334 // Inform delegate unless canceled. | |
| 335 if (status.status() != net::URLRequestStatus::CANCELED) | |
| 336 delegate_->OnResponseEnd(request_id, status); | |
| 337 } | |
| 338 | |
| 339 void NPAPIUrlRequestManager::OnCookiesRetrieved(bool success, const GURL& url, | |
| 340 const std::string& cookie_string, int cookie_id) { | |
| 341 delegate_->OnCookiesRetrieved(success, url, cookie_string, cookie_id); | |
| 342 } | |
| 343 | |
| 344 // Notifications from browser. Find the NPAPIUrlRequest and forward to it. | |
| 345 NPError NPAPIUrlRequestManager::NewStream(NPMIMEType type, | |
| 346 NPStream* stream, | |
| 347 NPBool seekable, | |
| 348 uint16* stream_type) { | |
| 349 NPAPIUrlRequest* request = RequestFromNotifyData(stream->notifyData); | |
| 350 if (!request) | |
| 351 return NPERR_NO_ERROR; | |
| 352 | |
| 353 // This stream is being constructed for a request that has already been | |
| 354 // canceled. Signal its immediate termination. | |
| 355 if (net::URLRequestStatus::CANCELED == request->status().status()) { | |
| 356 return npapi::DestroyStream(request->instance(), | |
| 357 stream, NPRES_USER_BREAK); | |
| 358 } | |
| 359 | |
| 360 DCHECK(request_map_.find(request->id()) != request_map_.end()); | |
| 361 | |
| 362 // If the host browser does not support the NPAPI redirect notification | |
| 363 // spec, and if the request URL is implicitly redirected, we need to | |
| 364 // inform Chrome about the redirect and allow it to follow the redirect. | |
| 365 // We achieve this by comparing the URL requested with the URL received in | |
| 366 // the response and if they don't match we assume a redirect. This would have | |
| 367 // a sideffect that two GET requests would be sent out in this case. | |
| 368 if (!BrowserSupportsRedirectNotification()) { | |
| 369 if (GURL(request->url().c_str()) != GURL(stream->url)) { | |
| 370 DVLOG(1) << "Request URL:" | |
| 371 << request->url() | |
| 372 << " was redirected to:" | |
| 373 << stream->url; | |
| 374 delegate_->OnResponseStarted( | |
| 375 request->id(), "", "", 0, base::Time(), stream->url, 302, | |
| 376 net::HostPortPair(net::HostPortPair::FromURL(GURL(stream->url)))); | |
| 377 return NPERR_GENERIC_ERROR; | |
| 378 } | |
| 379 } | |
| 380 // We need to return the requested stream mode if we are returning a success | |
| 381 // code. If we don't do this it causes Opera to blow up. | |
| 382 *stream_type = NP_NORMAL; | |
| 383 return request->OnStreamCreated(type, stream); | |
| 384 } | |
| 385 | |
| 386 int32 NPAPIUrlRequestManager::WriteReady(NPStream* stream) { | |
| 387 NPAPIUrlRequest* request = RequestFromNotifyData(stream->notifyData); | |
| 388 if (!request) | |
| 389 return 0x7FFFFFFF; | |
| 390 DCHECK(request_map_.find(request->id()) != request_map_.end()); | |
| 391 return request->OnWriteReady(); | |
| 392 } | |
| 393 | |
| 394 int32 NPAPIUrlRequestManager::Write(NPStream* stream, int32 offset, | |
| 395 int32 len, void* buffer) { | |
| 396 NPAPIUrlRequest* request = RequestFromNotifyData(stream->notifyData); | |
| 397 if (!request) | |
| 398 return len; | |
| 399 DCHECK(request_map_.find(request->id()) != request_map_.end()); | |
| 400 return request->OnWrite(buffer, len); | |
| 401 } | |
| 402 | |
| 403 NPError NPAPIUrlRequestManager::DestroyStream(NPStream* stream, | |
| 404 NPReason reason) { | |
| 405 NPAPIUrlRequest* request = RequestFromNotifyData(stream->notifyData); | |
| 406 if (!request) | |
| 407 return NPERR_NO_ERROR; | |
| 408 | |
| 409 // It is possible to receive notification of a destroyed stream for a | |
| 410 // unregistered request: EndRequest will unregister a request in response | |
| 411 // to an AutomationMsg_RequestEnd message. EndRequest will also invoke | |
| 412 // npapi::DestroyStream, which will call back to this function. | |
| 413 if (request_map_.find(request->id()) != request_map_.end()) | |
| 414 return request->OnStreamDestroyed(reason); | |
| 415 | |
| 416 return NPERR_NO_ERROR; | |
| 417 } | |
| 418 | |
| 419 void NPAPIUrlRequestManager::UrlNotify(const char* url, NPReason reason, | |
| 420 void* notify_data) { | |
| 421 NPAPIUrlRequest* request = RequestFromNotifyData(notify_data); | |
| 422 DCHECK(request != NULL); | |
| 423 if (request) { | |
| 424 request->Stop(); | |
| 425 request->Release(); | |
| 426 } | |
| 427 } | |
| 428 | |
| 429 void NPAPIUrlRequestManager::UrlRedirectNotify(const char* url, int status, | |
| 430 void* notify_data) { | |
| 431 NPAPIUrlRequest* request = RequestFromNotifyData(notify_data); | |
| 432 if (request) { | |
| 433 delegate_->OnResponseStarted( | |
| 434 request->id(), "", "", 0, base::Time(), url, status, | |
| 435 net::HostPortPair(net::HostPortPair::FromURL(GURL(url)))); | |
| 436 } else { | |
| 437 NOTREACHED() << "Received unexpected redirect notification for url:" | |
| 438 << url; | |
| 439 } | |
| 440 } | |
| 441 | |
| 442 scoped_refptr<NPAPIUrlRequest> NPAPIUrlRequestManager::LookupRequest( | |
| 443 int request_id) { | |
| 444 RequestMap::iterator index = request_map_.find(request_id); | |
| 445 if (index != request_map_.end()) | |
| 446 return index->second; | |
| 447 return NULL; | |
| 448 } | |
| OLD | NEW |