blob: de591c0ee7d75fb338f1cbfdf36482a572bbcc79 [file] [log] [blame]
// 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 <Foundation/Foundation.h>
#include <stddef.h>
#include "base/bind.h"
#include "base/location.h"
#include "base/mac/scoped_nsobject.h"
#include "base/task_runner.h"
#include "ios/chrome/browser/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"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
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<NSMutableData> 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<NSData> DecodeWebpImage(
const base::scoped_nsobject<NSData>& webp_image) {
scoped_refptr<WebpDecoderDelegate> delegate(new WebpDecoderDelegate);
scoped_refptr<webp_transcode::WebpDecoder> decoder(
new webp_transcode::WebpDecoder(delegate.get()));
decoder->OnDataReceived(webp_image);
DLOG_IF(ERROR, !delegate->data()) << "WebP image decoding failed.";
return base::scoped_nsobject<NSData>(delegate->data());
}
} // namespace
ImageFetcher::ImageFetcher(const scoped_refptr<base::TaskRunner>& 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_) {
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.
std::unique_ptr<const net::URLFetcher> fetcher_deleter(fetcher);
// Retrieves the callback and ensures that it will be deleted in the event
// of early return.
base::mac::ScopedBlock<ImageFetchedCallback> 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<NSData> data([[NSData alloc]
initWithBytes:reinterpret_cast<const unsigned char*>(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<ImageFetchedCallback>& callback,
const GURL& url,
int http_response_code,
NSData* data) {
(callback.get())(url, http_response_code, data);
}
void ImageFetcher::SetRequestContextGetter(
const scoped_refptr<net::URLRequestContextGetter>& request_context_getter) {
request_context_getter_ = request_context_getter;
}