// Copyright 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "ios/chrome/browser/net/image_fetcher.h" #import #include "base/bind.h" #include "base/location.h" #include "base/mac/scoped_nsobject.h" #include "base/task_runner.h" #include "components/webp_transcode/webp_decoder.h" #include "ios/web/public/web_thread.h" #include "net/base/load_flags.h" #include "net/http/http_response_headers.h" #include "net/url_request/url_fetcher.h" #include "net/url_request/url_request_context_getter.h" namespace { class WebpDecoderDelegate : public webp_transcode::WebpDecoder::Delegate { public: NSData* data() const { return decoded_image_; } // WebpDecoder::Delegate methods void OnFinishedDecoding(bool success) override { if (!success) decoded_image_.reset(); } void SetImageFeatures( size_t total_size, webp_transcode::WebpDecoder::DecodedImageFormat format) override { decoded_image_.reset([[NSMutableData alloc] initWithCapacity:total_size]); } void OnDataDecoded(NSData* data) override { DCHECK(decoded_image_); [decoded_image_ appendData:data]; } private: ~WebpDecoderDelegate() override {} base::scoped_nsobject decoded_image_; }; // Content-type header for WebP images. static const char kWEBPMimeType[] = "image/webp"; // Returns a NSData object containing the decoded image. // Returns nil in case of failure. base::scoped_nsobject DecodeWebpImage( const base::scoped_nsobject& webp_image) { scoped_refptr delegate(new WebpDecoderDelegate); scoped_refptr decoder( new webp_transcode::WebpDecoder(delegate.get())); decoder->OnDataReceived(webp_image); DLOG_IF(ERROR, !delegate->data()) << "WebP image decoding failed."; return base::scoped_nsobject([delegate->data() retain]); } } // namespace namespace image_fetcher { ImageFetcher::ImageFetcher(const scoped_refptr& task_runner) : request_context_getter_(nullptr), task_runner_(task_runner), weak_factory_(this) { DCHECK(task_runner_.get()); } ImageFetcher::~ImageFetcher() { // Delete all the entries in the |downloads_in_progress_| map. This will in // turn cancel all of the requests. for (const auto& pair : downloads_in_progress_) { [pair.second release]; delete pair.first; } } void ImageFetcher::StartDownload( const GURL& url, ImageFetchedCallback callback, const std::string& referrer, net::URLRequest::ReferrerPolicy referrer_policy) { DCHECK(request_context_getter_.get()); net::URLFetcher* fetcher = net::URLFetcher::Create(url, net::URLFetcher::GET, this).release(); downloads_in_progress_[fetcher] = [callback copy]; fetcher->SetLoadFlags( net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_AUTH_DATA); fetcher->SetRequestContext(request_context_getter_.get()); fetcher->SetReferrer(referrer); fetcher->SetReferrerPolicy(referrer_policy); fetcher->Start(); } void ImageFetcher::StartDownload( const GURL& url, ImageFetchedCallback callback) { ImageFetcher::StartDownload( url, callback, std::string(), net::URLRequest::NEVER_CLEAR_REFERRER); } // Delegate callback that is called when URLFetcher completes. If the image // was fetched successfully, creates a new NSData and returns it to the // callback, otherwise returns nil to the callback. void ImageFetcher::OnURLFetchComplete(const net::URLFetcher* fetcher) { if (downloads_in_progress_.find(fetcher) == downloads_in_progress_.end()) { LOG(ERROR) << "Received callback for unknown URLFetcher " << fetcher; return; } // Ensures that |fetcher| will be deleted in the event of early return. scoped_ptr fetcher_deleter(fetcher); // Retrieves the callback and ensures that it will be deleted in the event // of early return. base::mac::ScopedBlock callback( downloads_in_progress_[fetcher]); // Remove |fetcher| from the map. downloads_in_progress_.erase(fetcher); // Make sure the request was successful. For "data" requests, the response // code has no meaning, because there is no actual server (data is encoded // directly in the URL). In that case, set the response code to 200 (OK). const GURL& original_url = fetcher->GetOriginalURL(); const int http_response_code = original_url.SchemeIs("data") ? 200 : fetcher->GetResponseCode(); if (http_response_code != 200) { (callback.get())(original_url, http_response_code, nil); return; } std::string response; if (!fetcher->GetResponseAsString(&response)) { (callback.get())(original_url, http_response_code, nil); return; } // Create a NSData from the returned data and notify the callback. base::scoped_nsobject data([[NSData alloc] initWithBytes:reinterpret_cast(response.data()) length:response.size()]); if (fetcher->GetResponseHeaders()) { std::string mime_type; fetcher->GetResponseHeaders()->GetMimeType(&mime_type); if (mime_type == kWEBPMimeType) { base::PostTaskAndReplyWithResult(task_runner_.get(), FROM_HERE, base::Bind(&DecodeWebpImage, data), base::Bind(&ImageFetcher::RunCallback, weak_factory_.GetWeakPtr(), callback, original_url, http_response_code)); return; } } (callback.get())(original_url, http_response_code, data); } void ImageFetcher::RunCallback( const base::mac::ScopedBlock& callback, const GURL& url, int http_response_code, NSData* data) { (callback.get())(url, http_response_code, data); } void ImageFetcher::SetRequestContextGetter( const scoped_refptr& request_context_getter) { request_context_getter_ = request_context_getter; } } // namespace image_fetcher