1 // Copyright 2012 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 "ios/net/protocol_handler_util.h"
9 #include "base/base64.h"
10 #include "base/i18n/icu_encoding_detection.h"
11 #include "base/i18n/icu_string_conversions.h"
12 #include "base/logging.h"
13 #include "base/mac/scoped_nsobject.h"
14 #include "base/macros.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "base/time/time.h"
17 #include "ios/net/crn_http_url_response.h"
18 #import "net/base/mac/url_conversions.h"
19 #include "net/base/net_errors.h"
20 #include "net/http/http_request_headers.h"
21 #include "net/http/http_response_headers.h"
22 #include "net/http/http_version.h"
23 #include "net/url_request/url_request.h"
28 // "Content-Type" HTTP header.
29 NSString* const kContentType = @"Content-Type";
35 NSError* GetIOSError(NSInteger ns_error_code,
38 const base::Time& creation_time) {
39 // The error we pass through has the domain NSURLErrorDomain, an IOS error
40 // code, and a userInfo dictionary in which we smuggle more detailed info
41 // about the error from our network stack. This dictionary contains the
42 // failing URL, and a nested error in which we deposit the original error code
43 // passed in from the Chrome network stack.
44 // The nested error has domain:kErrorDomain, code:|original_error_code|, and
45 // userInfo:nil; this NSError is keyed in the dictionary with
46 // NSUnderlyingErrorKey.
47 NSDate* creation_date = [NSDate
48 dateWithTimeIntervalSinceReferenceDate:creation_time.ToCFAbsoluteTime()];
49 DCHECK(creation_date);
50 NSError* underlying_error =
51 [NSError errorWithDomain:base::SysUTF8ToNSString(kErrorDomain)
55 NSDictionary* dictionary = @{
56 NSURLErrorFailingURLStringErrorKey : url,
57 @"CreationDate" : creation_date,
58 NSUnderlyingErrorKey : underlying_error,
60 return [NSError errorWithDomain:NSURLErrorDomain
65 NSURLResponse* GetNSURLResponseForRequest(URLRequest* request) {
66 NSURL* url = NSURLWithGURL(request->url());
69 // The default iOS stack returns a NSURLResponse when the request has a data
70 // scheme, and a NSHTTPURLResponse otherwise.
71 if (request->url().SchemeIs("data")) {
73 request->GetMimeType(&mt);
74 NSString* mime_type = base::SysUTF8ToNSString(mt);
77 request->GetCharset(&cs);
78 NSString* charset = base::SysUTF8ToNSString(cs);
80 // The default iOS stack computes the length of the decoded string. If we
81 // wanted to do that we would have to decode the string now. However, using
82 // the unknown length (-1) seems to be working.
83 return [[[NSURLResponse alloc] initWithURL:url
85 expectedContentLength:-1
86 textEncodingName:charset] autorelease];
88 // Iterate over all the headers and copy them.
89 bool has_content_type_header = false;
90 NSMutableDictionary* header_fields = [NSMutableDictionary dictionary];
91 HttpResponseHeaders* headers = request->response_headers();
92 if (headers != nullptr) {
94 std::string name, value;
95 while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
96 NSString* key = base::SysUTF8ToNSString(name);
98 DLOG(ERROR) << "Header name is not in UTF8: " << name;
99 // Skip the invalid header.
102 // Do not copy "Cache-Control" headers as we provide our own controls.
103 if ([key caseInsensitiveCompare:@"cache-control"] == NSOrderedSame)
105 if ([key caseInsensitiveCompare:kContentType] == NSOrderedSame) {
107 has_content_type_header = true;
110 // Handle bad encoding.
111 NSString* v = base::SysUTF8ToNSString(value);
113 DLOG(ERROR) << "Header \"" << name << "\" is not in UTF8: " << value;
114 // Infer the encoding, or skip the header if it's not possible.
115 std::string encoding;
116 if (!base::DetectEncoding(value, &encoding))
118 std::string value_utf8;
119 if (!base::ConvertToUtf8AndNormalize(value, encoding, &value_utf8))
121 v = base::SysUTF8ToNSString(value_utf8);
125 // Duplicate keys are appended using a comma separator (RFC 2616).
126 NSMutableString* existing = [header_fields objectForKey:key];
128 [existing appendFormat:@",%@", v];
130 [header_fields setObject:[NSMutableString stringWithString:v]
136 // WebUI does not define a "Content-Type" header. Use the MIME type instead.
137 if (!has_content_type_header) {
138 std::string mime_type = "";
139 request->GetMimeType(&mime_type);
140 NSString* type = base::SysUTF8ToNSString(mime_type);
142 [header_fields setObject:type forKey:kContentType];
144 NSString* content_type = [header_fields objectForKey:kContentType];
146 NSRange range = [content_type rangeOfString:@","];
147 // If there are several "Content-Type" headers, keep only the first one.
148 if (range.location != NSNotFound) {
149 [header_fields setObject:[content_type substringToIndex:range.location]
150 forKey:kContentType];
154 // Use a "no-store" cache control to ensure that the response is not cached
155 // by the system. See b/7045043.
156 [header_fields setObject:@"no-store" forKey:@"Cache-Control"];
158 // Parse the HTTP version.
159 NSString* version_string = @"HTTP/1.1";
161 const HttpVersion& http_version = headers->GetHttpVersion();
162 version_string = [NSString stringWithFormat:@"HTTP/%hu.%hu",
163 http_version.major_value(),
164 http_version.minor_value()];
167 return [[[CRNHTTPURLResponse alloc] initWithURL:url
168 statusCode:request->GetResponseCode()
169 HTTPVersion:version_string
170 headerFields:header_fields] autorelease];
174 void CopyHttpHeaders(NSURLRequest* in_request, URLRequest* out_request) {
175 DCHECK(out_request->extra_request_headers().IsEmpty());
176 NSDictionary* headers = [in_request allHTTPHeaderFields];
177 HttpRequestHeaders net_headers;
179 for (key in headers) {
180 if ([key isEqualToString:@"Referer"]) {
181 // The referrer must be set through the set_referrer method rather than as
183 out_request->SetReferrer(
184 base::SysNSStringToUTF8([headers objectForKey:key]));
185 // If the referrer is explicitly set, we don't want the network stack to
187 out_request->set_referrer_policy(URLRequest::NEVER_CLEAR_REFERRER);
190 if (![key isEqualToString:@"User-Agent"]) {
191 // The user agent string is added by the network stack, and might be
192 // different from the one provided by UIWebView. Do not copy it.
193 NSString* value = [headers objectForKey:key];
194 net_headers.SetHeader(base::SysNSStringToUTF8(key),
195 base::SysNSStringToUTF8(value));
198 // Set default values for some missing headers.
199 // The "Accept" header is defined by Webkit on the desktop version.
200 net_headers.SetHeaderIfMissing("Accept", "*/*");
201 // The custom NSURLProtocol example from Apple adds a default "Content-Type"
202 // header for non-empty POST requests. This suggests that this header can be
203 // missing, and Chrome network stack does not add it by itself.
204 if (out_request->has_upload() && out_request->method() == "POST") {
205 DLOG_IF(WARNING, !net_headers.HasHeader(HttpRequestHeaders::kContentType))
206 << "Missing \"Content-Type\" header in POST request.";
207 net_headers.SetHeaderIfMissing(HttpRequestHeaders::kContentType,
208 "application/x-www-form-urlencoded");
210 out_request->SetExtraRequestHeaders(net_headers);