1 // Copyright 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.
5 #import "components/webp_transcode/webp_network_client.h"
8 #include "base/compiler_specific.h"
9 #include "base/location.h"
10 #include "base/logging.h"
11 #include "base/mac/bind_objc_block.h"
12 #include "base/mac/scoped_nsobject.h"
13 #include "base/sequenced_task_runner.h"
14 #include "base/single_thread_task_runner.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/sys_string_conversions.h"
17 #include "base/thread_task_runner_handle.h"
18 #include "components/webp_transcode/webp_decoder.h"
19 #include "net/base/net_errors.h"
20 #include "net/http/http_request_headers.h"
21 #include "net/url_request/url_request.h"
27 using namespace webp_transcode;
31 // MIME type for WebP images.
32 const char kWebPMimeType[] = "image/webp";
33 NSString* const kNSWebPMimeType = @"image/webp";
35 NSURLResponse* NewImageResponse(NSURLResponse* webp_response,
36 size_t content_length,
37 WebpDecoder::DecodedImageFormat format) {
38 DCHECK(webp_response);
40 NSString* mime_type = nil;
42 case WebpDecoder::JPEG:
43 mime_type = @"image/jpeg";
45 case WebpDecoder::PNG:
46 mime_type = @"image/png";
48 case WebpDecoder::TIFF:
49 mime_type = @"image/tiff";
51 case WebpDecoder::DECODED_FORMAT_COUNT:
57 if ([webp_response isKindOfClass:[NSHTTPURLResponse class]]) {
58 NSHTTPURLResponse* http_response =
59 static_cast<NSHTTPURLResponse*>(webp_response);
60 NSMutableDictionary* header_fields = [NSMutableDictionary
61 dictionaryWithDictionary:[http_response allHeaderFields]];
62 [header_fields setObject:[NSString stringWithFormat:@"%zu", content_length]
63 forKey:@"Content-Length"];
64 [header_fields setObject:mime_type forKey:@"Content-Type"];
65 return [[NSHTTPURLResponse alloc] initWithURL:[http_response URL]
66 statusCode:[http_response statusCode]
67 HTTPVersion:@"HTTP/1.1"
68 headerFields:header_fields];
70 return [[NSURLResponse alloc] initWithURL:[webp_response URL]
72 expectedContentLength:content_length
73 textEncodingName:[webp_response textEncodingName]];
77 class WebpDecoderDelegate : public WebpDecoder::Delegate {
79 WebpDecoderDelegate(id<CRNNetworkClientProtocol> client,
80 const base::Time& request_creation_time,
81 const scoped_refptr<base::TaskRunner>& callback_runner)
82 : underlying_client_([client retain]),
83 callback_task_runner_(callback_runner),
84 request_creation_time_(request_creation_time) {
85 DCHECK(underlying_client_.get());
88 void SetOriginalResponse(
89 const base::scoped_nsobject<NSURLResponse>& response) {
90 original_response_.reset([response retain]);
93 // WebpDecoder::Delegate methods.
94 void OnFinishedDecoding(bool success) override {
95 base::scoped_nsprotocol<id<CRNNetworkClientProtocol>> block_client(
96 [underlying_client_ retain]);
98 callback_task_runner_->PostTask(FROM_HERE, base::BindBlock(^{
99 [block_client didFinishLoading];
102 DLOG(WARNING) << "WebP decoding failed "
103 << base::SysNSStringToUTF8(
104 [[original_response_ URL] absoluteString]);
105 void (^errorBlock)(void) = ^{
106 [block_client didFailWithNSErrorCode:NSURLErrorCannotDecodeContentData
107 netErrorCode:net::ERR_CONTENT_DECODING_FAILED];
109 callback_task_runner_->PostTask(FROM_HERE, base::BindBlock(errorBlock));
113 void SetImageFeatures(size_t total_size,
114 WebpDecoder::DecodedImageFormat format) override {
115 base::scoped_nsobject<NSURLResponse> imageResponse(
116 NewImageResponse(original_response_, total_size, format));
117 DCHECK(imageResponse);
118 base::scoped_nsprotocol<id<CRNNetworkClientProtocol>> block_client(
119 [underlying_client_ retain]);
120 callback_task_runner_->PostTask(FROM_HERE, base::BindBlock(^{
121 [block_client didReceiveResponse:imageResponse];
125 void OnDataDecoded(NSData* data) override {
126 base::scoped_nsprotocol<id<CRNNetworkClientProtocol>> block_client(
127 [underlying_client_ retain]);
128 callback_task_runner_->PostTask(FROM_HERE, base::BindBlock(^{
129 [block_client didLoadData:data];
134 ~WebpDecoderDelegate() override {}
136 base::scoped_nsprotocol<id<CRNNetworkClientProtocol>> underlying_client_;
137 base::scoped_nsobject<NSURLResponse> original_response_;
138 scoped_refptr<base::TaskRunner> callback_task_runner_;
139 base::Time request_creation_time_;
144 @interface WebPNetworkClient () {
145 scoped_refptr<webp_transcode::WebpDecoder> _webpDecoder;
146 scoped_refptr<WebpDecoderDelegate> _webpDecoderDelegate;
147 scoped_refptr<base::SequencedTaskRunner> _taskRunner;
148 base::Time _requestCreationTime;
152 @implementation WebPNetworkClient
154 - (instancetype)init {
155 NOTREACHED() << "Use |-initWithTaskRunner:| instead";
159 - (instancetype)initWithTaskRunner:
160 (const scoped_refptr<base::SequencedTaskRunner>&)runner {
161 if (self = [super init]) {
163 _taskRunner = runner;
168 - (void)didCreateNativeRequest:(net::URLRequest*)nativeRequest {
169 // Append 'image/webp' to the outgoing 'Accept' header.
170 const net::HttpRequestHeaders& headers =
171 nativeRequest->extra_request_headers();
172 std::string acceptHeader;
173 if (headers.GetHeader("Accept", &acceptHeader)) {
174 // Add 'image/webp' if it isn't in the Accept header yet.
175 if (acceptHeader.find(kWebPMimeType) == std::string::npos) {
176 acceptHeader += std::string(",") + kWebPMimeType;
177 nativeRequest->SetExtraRequestHeaderByName("Accept", acceptHeader, true);
180 // All requests should already have an Accept: header, so this case
181 // should never happen outside of unit tests.
182 nativeRequest->SetExtraRequestHeaderByName("Accept", kWebPMimeType, false);
184 [super didCreateNativeRequest:nativeRequest];
187 - (void)didLoadData:(NSData*)data {
188 if (_webpDecoder.get()) {
189 // |data| is assumed to be immutable.
190 base::scoped_nsobject<NSData> scopedData([data retain]);
191 _taskRunner->PostTask(FROM_HERE, base::Bind(&WebpDecoder::OnDataReceived,
192 _webpDecoder, scopedData));
194 [super didLoadData:data];
198 - (void)didReceiveResponse:(NSURLResponse*)response {
199 DCHECK(self.underlyingClient);
200 NSString* responseMimeType = [response MIMEType];
201 if (responseMimeType &&
202 [responseMimeType caseInsensitiveCompare:kNSWebPMimeType] ==
204 _webpDecoderDelegate =
205 new WebpDecoderDelegate(self.underlyingClient, _requestCreationTime,
206 base::ThreadTaskRunnerHandle::Get());
207 _webpDecoder = new webp_transcode::WebpDecoder(_webpDecoderDelegate.get());
208 base::scoped_nsobject<NSURLResponse> scoped_response([response copy]);
209 _taskRunner->PostTask(FROM_HERE,
210 base::Bind(&WebpDecoderDelegate::SetOriginalResponse,
211 _webpDecoderDelegate, scoped_response));
212 // Do not call super here, the WebpDecoderDelegate will update the mime type
213 // and call |-didReceiveResponse:|.
215 // If this isn't a WebP, pass the call up the chain.
216 // TODO(marq): It would be nice if at this point the client could remove
217 // itself from the client stack.
218 [super didReceiveResponse:response];
222 - (void)didFinishLoading {
223 if (_webpDecoder.get()) {
224 _taskRunner->PostTask(FROM_HERE,
225 base::Bind(&WebpDecoder::Stop, _webpDecoder));
227 [super didFinishLoading];