Add ICU message format support
[chromium-blink-merge.git] / ios / net / protocol_handler_util.mm
blobd1878c54bbc3dc66ba324ed4335198c784b44b5d
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"
7 #include <string>
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"
24 #include "url/gurl.h"
26 namespace {
28 // "Content-Type" HTTP header.
29 NSString* const kContentType = @"Content-Type";
31 }  // namespace
33 namespace net {
35 NSError* GetIOSError(NSInteger ns_error_code,
36                      int net_error_code,
37                      NSString* url,
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)
52                           code:net_error_code
53                       userInfo:nil];
54   DCHECK(url);
55   NSDictionary* dictionary = @{
56       NSURLErrorFailingURLStringErrorKey : url,
57       @"CreationDate" : creation_date,
58       NSUnderlyingErrorKey : underlying_error,
59   };
60   return [NSError errorWithDomain:NSURLErrorDomain
61                              code:ns_error_code
62                          userInfo:dictionary];
65 NSURLResponse* GetNSURLResponseForRequest(URLRequest* request) {
66   NSURL* url = NSURLWithGURL(request->url());
67   DCHECK(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")) {
72     std::string mt;
73     request->GetMimeType(&mt);
74     NSString* mime_type = base::SysUTF8ToNSString(mt);
75     DCHECK(mime_type);
76     std::string cs;
77     request->GetCharset(&cs);
78     NSString* charset = base::SysUTF8ToNSString(cs);
79     DCHECK(charset);
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
84                                       MIMEType:mime_type
85                          expectedContentLength:-1
86                               textEncodingName:charset] autorelease];
87   } else {
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) {
93       void* iter = nullptr;
94       std::string name, value;
95       while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
96         NSString* key = base::SysUTF8ToNSString(name);
97         if (!key) {
98           DLOG(ERROR) << "Header name is not in UTF8: " << name;
99           // Skip the invalid header.
100           continue;
101         }
102         // Do not copy "Cache-Control" headers as we provide our own controls.
103         if ([key caseInsensitiveCompare:@"cache-control"] == NSOrderedSame)
104           continue;
105         if ([key caseInsensitiveCompare:kContentType] == NSOrderedSame) {
106           key = kContentType;
107           has_content_type_header = true;
108         }
110         // Handle bad encoding.
111         NSString* v = base::SysUTF8ToNSString(value);
112         if (!v) {
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))
117             continue;
118           std::string value_utf8;
119           if (!base::ConvertToUtf8AndNormalize(value, encoding, &value_utf8))
120             continue;
121           v = base::SysUTF8ToNSString(value_utf8);
122           DCHECK(v);
123         }
125         // Duplicate keys are appended using a comma separator (RFC 2616).
126         NSMutableString* existing = [header_fields objectForKey:key];
127         if (existing) {
128           [existing appendFormat:@",%@", v];
129         } else {
130           [header_fields setObject:[NSMutableString stringWithString:v]
131                             forKey:key];
132         }
133       }
134     }
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);
141       if ([type length])
142         [header_fields setObject:type forKey:kContentType];
143     }
144     NSString* content_type = [header_fields objectForKey:kContentType];
145     if (content_type) {
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];
151       }
152     }
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";
160     if (headers) {
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()];
165     }
167     return [[[CRNHTTPURLResponse alloc] initWithURL:url
168                                          statusCode:request->GetResponseCode()
169                                         HTTPVersion:version_string
170                                        headerFields:header_fields] autorelease];
171   }
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;
178   NSString* key;
179   for (key in headers) {
180     if ([key isEqualToString:@"Referer"]) {
181       // The referrer must be set through the set_referrer method rather than as
182       // a header.
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
186       // strip it.
187       out_request->set_referrer_policy(URLRequest::NEVER_CLEAR_REFERRER);
188       continue;
189     }
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));
196     }
197   }
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");
209   }
210   out_request->SetExtraRequestHeaders(net_headers);
213 }  // namespace net