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 #include "base/mac/scoped_nsobject.h"
6 #include "base/memory/scoped_ptr.h"
7 #include "base/message_loop/message_loop.h"
8 #include "base/run_loop.h"
9 #include "base/strings/sys_string_conversions.h"
10 #import "ios/net/protocol_handler_util.h"
11 #include "net/base/elements_upload_data_stream.h"
12 #import "net/base/mac/url_conversions.h"
13 #include "net/base/upload_bytes_element_reader.h"
14 #include "net/http/http_request_headers.h"
15 #include "net/http/http_response_headers.h"
16 #include "net/url_request/data_protocol_handler.h"
17 #include "net/url_request/url_request.h"
18 #include "net/url_request/url_request_job.h"
19 #include "net/url_request/url_request_job_factory.h"
20 #include "net/url_request/url_request_job_factory_impl.h"
21 #include "net/url_request/url_request_test_util.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 #include "testing/gtest_mac.h"
26 // When C++ exceptions are disabled, the C++ library defines |try| and
27 // |catch| so as to allow exception-expecting C++ code to build properly when
28 // language support for exceptions is not present. These macros interfere
29 // with the use of |@try| and |@catch| in Objective-C files such as this one.
30 // Undefine these macros here, after everything has been #included, since
31 // there will be no C++ uses and only Objective-C uses from this point on.
38 const int kResponseCode = 200;
39 const char* kTextHtml = "text/html";
40 const char* kTextPlain = "text/plain";
41 const char* kAscii = "US-ASCII";
43 class HeadersURLRequestJob : public URLRequestJob {
45 HeadersURLRequestJob(URLRequest* request)
46 : URLRequestJob(request, nullptr) {}
48 void Start() override {
49 // Fills response headers and returns immediately.
50 NotifyHeadersComplete();
53 bool GetMimeType(std::string* mime_type) const override {
54 *mime_type = GetContentTypeValue();
58 void GetResponseInfo(HttpResponseInfo* info) override {
59 // This is called by NotifyHeadersComplete().
60 std::string header_string("HTTP/1.0 200 OK");
61 header_string.push_back('\0');
62 header_string += std::string("Cache-Control: max-age=600");
63 header_string.push_back('\0');
64 if (request()->url().DomainIs("multiplecontenttype")) {
65 header_string += std::string(
66 "coNteNt-tYPe: text/plain; charset=iso-8859-4, image/png");
67 header_string.push_back('\0');
69 header_string += std::string("Content-Type: ") + GetContentTypeValue();
70 header_string.push_back('\0');
71 header_string += std::string("Foo: A");
72 header_string.push_back('\0');
73 header_string += std::string("Bar: B");
74 header_string.push_back('\0');
75 header_string += std::string("Baz: C");
76 header_string.push_back('\0');
77 header_string += std::string("Foo: D");
78 header_string.push_back('\0');
79 header_string += std::string("Foo: E");
80 header_string.push_back('\0');
81 header_string += std::string("Bar: F");
82 header_string.push_back('\0');
83 info->headers = new HttpResponseHeaders(header_string);
86 int GetResponseCode() const override {
90 ~HeadersURLRequestJob() override {}
92 std::string GetContentTypeValue() const {
93 if (request()->url().DomainIs("badcontenttype"))
99 class NetProtocolHandler : public URLRequestJobFactory::ProtocolHandler {
101 URLRequestJob* MaybeCreateJob(
103 NetworkDelegate* network_delegate) const override {
104 return new HeadersURLRequestJob(request);
108 class ProtocolHandlerUtilTest : public testing::Test,
109 public URLRequest::Delegate {
111 ProtocolHandlerUtilTest() : request_context_(new TestURLRequestContext) {
112 // Ownership of the protocol handlers is transferred to the factory.
113 job_factory_.SetProtocolHandler("http", new NetProtocolHandler);
114 job_factory_.SetProtocolHandler("data", new DataProtocolHandler);
115 request_context_->set_job_factory(&job_factory_);
118 NSURLResponse* BuildDataURLResponse(const std::string& mime_type,
119 const std::string& encoding,
120 const std::string& content) {
121 // Build an URL in the form "data:<mime_type>;charset=<encoding>,<content>"
122 // The ';' is removed if mime_type or charset is empty.
123 std::string url_string = std::string("data:") + mime_type;
124 if (!encoding.empty())
125 url_string += ";charset=" + encoding;
127 GURL url(url_string);
129 scoped_ptr<URLRequest> request(
130 request_context_->CreateRequest(url, DEFAULT_PRIORITY, this));
134 return GetNSURLResponseForRequest(request.get());
137 void CheckDataResponse(NSURLResponse* response,
138 const std::string& mime_type,
139 const std::string& encoding) {
140 EXPECT_NSEQ(base::SysUTF8ToNSString(mime_type), [response MIMEType]);
141 EXPECT_NSEQ(base::SysUTF8ToNSString(encoding), [response textEncodingName]);
142 // The response class must be NSURLResponse (and not NSHTTPURLResponse) when
143 // the scheme is "data".
144 EXPECT_TRUE([response isMemberOfClass:[NSURLResponse class]]);
147 void OnResponseStarted(URLRequest* request) override {}
148 void OnReadCompleted(URLRequest* request, int bytes_read) override {}
151 base::MessageLoop loop_;
152 URLRequestJobFactoryImpl job_factory_;
153 scoped_ptr<URLRequestContext> request_context_;
158 TEST_F(ProtocolHandlerUtilTest, GetResponseDataSchemeTest) {
159 NSURLResponse* response;
160 // MIME type and charset are correctly carried over.
161 response = BuildDataURLResponse("#mime=type'", "$(charset-*", "content");
162 CheckDataResponse(response, "#mime=type'", "$(charset-*");
163 // Missing values are treated as default values.
164 response = BuildDataURLResponse("", "", "content");
165 CheckDataResponse(response, kTextPlain, kAscii);
168 TEST_F(ProtocolHandlerUtilTest, GetResponseHttpTest) {
170 GURL url(std::string("http://url"));
171 scoped_ptr<URLRequest> request(
172 request_context_->CreateRequest(url, DEFAULT_PRIORITY, this));
174 // Create a response from the request.
175 NSURLResponse* response = GetNSURLResponseForRequest(request.get());
176 EXPECT_NSEQ([NSString stringWithUTF8String:kTextHtml], [response MIMEType]);
177 ASSERT_TRUE([response isKindOfClass:[NSHTTPURLResponse class]]);
178 NSHTTPURLResponse* http_response = (NSHTTPURLResponse*)response;
179 NSDictionary* headers = [http_response allHeaderFields];
180 // Check the headers, duplicates must be appended.
181 EXPECT_EQ(5u, [headers count]);
182 NSString* foo_header = [headers objectForKey:@"Foo"];
183 EXPECT_NSEQ(@"A,D,E", foo_header);
184 NSString* bar_header = [headers objectForKey:@"Bar"];
185 EXPECT_NSEQ(@"B,F", bar_header);
186 NSString* baz_header = [headers objectForKey:@"Baz"];
187 EXPECT_NSEQ(@"C", baz_header);
188 NSString* cache_header = [headers objectForKey:@"Cache-Control"];
189 EXPECT_NSEQ(@"no-store", cache_header); // Cache-Control is overridden.
191 EXPECT_EQ(request->GetResponseCode(), [http_response statusCode]);
194 TEST_F(ProtocolHandlerUtilTest, BadHttpContentType) {
195 // Create a request using the magic domain that triggers a garbage
196 // content-type in the test framework.
197 GURL url(std::string("http://badcontenttype"));
198 scoped_ptr<URLRequest> request(
199 request_context_->CreateRequest(url, DEFAULT_PRIORITY, this));
201 // Create a response from the request.
203 GetNSURLResponseForRequest(request.get());
205 @catch (id exception) {
206 FAIL() << "Exception while creating response";
210 TEST_F(ProtocolHandlerUtilTest, MultipleHttpContentType) {
211 // Create a request using the magic domain that triggers a garbage
212 // content-type in the test framework.
213 GURL url(std::string("http://multiplecontenttype"));
214 scoped_ptr<URLRequest> request(
215 request_context_->CreateRequest(url, DEFAULT_PRIORITY, this));
217 // Create a response from the request.
218 NSURLResponse* response = GetNSURLResponseForRequest(request.get());
219 EXPECT_NSEQ(@"text/plain", [response MIMEType]);
220 EXPECT_NSEQ(@"iso-8859-4", [response textEncodingName]);
221 NSHTTPURLResponse* http_response = (NSHTTPURLResponse*)response;
222 NSDictionary* headers = [http_response allHeaderFields];
223 NSString* content_type_header = [headers objectForKey:@"Content-Type"];
224 EXPECT_NSEQ(@"text/plain; charset=iso-8859-4", content_type_header);
227 TEST_F(ProtocolHandlerUtilTest, CopyHttpHeaders) {
228 GURL url(std::string("http://url"));
229 base::scoped_nsobject<NSMutableURLRequest> in_request(
230 [[NSMutableURLRequest alloc] initWithURL:NSURLWithGURL(url)]);
231 [in_request setAllHTTPHeaderFields:@{
232 @"Referer" : @"referrer",
233 @"User-Agent" : @"secret",
234 @"Accept" : @"money/cash",
237 scoped_ptr<URLRequest> out_request(
238 request_context_->CreateRequest(url, DEFAULT_PRIORITY, nullptr));
239 CopyHttpHeaders(in_request, out_request.get());
241 EXPECT_EQ("referrer", out_request->referrer());
242 const HttpRequestHeaders& headers = out_request->extra_request_headers();
243 EXPECT_FALSE(headers.HasHeader("User-Agent")); // User agent is not copied.
244 EXPECT_FALSE(headers.HasHeader("Content-Type")); // Only in POST requests.
246 EXPECT_TRUE(headers.GetHeader("Accept", &header));
247 EXPECT_EQ("money/cash", header);
248 EXPECT_TRUE(headers.GetHeader("Foo", &header));
249 EXPECT_EQ("bar", header);
252 TEST_F(ProtocolHandlerUtilTest, AddMissingHeaders) {
253 GURL url(std::string("http://url"));
254 base::scoped_nsobject<NSMutableURLRequest> in_request(
255 [[NSMutableURLRequest alloc] initWithURL:NSURLWithGURL(url)]);
256 scoped_ptr<URLRequest> out_request(
257 request_context_->CreateRequest(url, DEFAULT_PRIORITY, nullptr));
258 out_request->set_method("POST");
259 scoped_ptr<UploadElementReader> reader(
260 new UploadBytesElementReader(nullptr, 0));
261 out_request->set_upload(
262 ElementsUploadDataStream::CreateWithReader(reader.Pass(), 0));
263 CopyHttpHeaders(in_request, out_request.get());
265 // Some headers are added by default if missing.
266 const HttpRequestHeaders& headers = out_request->extra_request_headers();
268 EXPECT_TRUE(headers.GetHeader("Accept", &header));
269 EXPECT_EQ("*/*", header);
270 EXPECT_TRUE(headers.GetHeader("Content-Type", &header));
271 EXPECT_EQ("application/x-www-form-urlencoded", header);