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",
114 make_scoped_ptr(new NetProtocolHandler));
115 job_factory_.SetProtocolHandler("data",
116 make_scoped_ptr(new DataProtocolHandler));
117 request_context_->set_job_factory(&job_factory_);
120 NSURLResponse* BuildDataURLResponse(const std::string& mime_type,
121 const std::string& encoding,
122 const std::string& content) {
123 // Build an URL in the form "data:<mime_type>;charset=<encoding>,<content>"
124 // The ';' is removed if mime_type or charset is empty.
125 std::string url_string = std::string("data:") + mime_type;
126 if (!encoding.empty())
127 url_string += ";charset=" + encoding;
129 GURL url(url_string);
131 scoped_ptr<URLRequest> request(
132 request_context_->CreateRequest(url, DEFAULT_PRIORITY, this));
136 return GetNSURLResponseForRequest(request.get());
139 void CheckDataResponse(NSURLResponse* response,
140 const std::string& mime_type,
141 const std::string& encoding) {
142 EXPECT_NSEQ(base::SysUTF8ToNSString(mime_type), [response MIMEType]);
143 EXPECT_NSEQ(base::SysUTF8ToNSString(encoding), [response textEncodingName]);
144 // The response class must be NSURLResponse (and not NSHTTPURLResponse) when
145 // the scheme is "data".
146 EXPECT_TRUE([response isMemberOfClass:[NSURLResponse class]]);
149 void OnResponseStarted(URLRequest* request) override {}
150 void OnReadCompleted(URLRequest* request, int bytes_read) override {}
153 base::MessageLoop loop_;
154 URLRequestJobFactoryImpl job_factory_;
155 scoped_ptr<URLRequestContext> request_context_;
160 TEST_F(ProtocolHandlerUtilTest, GetResponseDataSchemeTest) {
161 NSURLResponse* response;
162 // MIME type and charset are correctly carried over.
163 response = BuildDataURLResponse("#mime=type'", "$(charset-*", "content");
164 CheckDataResponse(response, "#mime=type'", "$(charset-*");
165 // Missing values are treated as default values.
166 response = BuildDataURLResponse("", "", "content");
167 CheckDataResponse(response, kTextPlain, kAscii);
170 TEST_F(ProtocolHandlerUtilTest, GetResponseHttpTest) {
172 GURL url(std::string("http://url"));
173 scoped_ptr<URLRequest> request(
174 request_context_->CreateRequest(url, DEFAULT_PRIORITY, this));
176 // Create a response from the request.
177 NSURLResponse* response = GetNSURLResponseForRequest(request.get());
178 EXPECT_NSEQ([NSString stringWithUTF8String:kTextHtml], [response MIMEType]);
179 ASSERT_TRUE([response isKindOfClass:[NSHTTPURLResponse class]]);
180 NSHTTPURLResponse* http_response = (NSHTTPURLResponse*)response;
181 NSDictionary* headers = [http_response allHeaderFields];
182 // Check the headers, duplicates must be appended.
183 EXPECT_EQ(5u, [headers count]);
184 NSString* foo_header = [headers objectForKey:@"Foo"];
185 EXPECT_NSEQ(@"A,D,E", foo_header);
186 NSString* bar_header = [headers objectForKey:@"Bar"];
187 EXPECT_NSEQ(@"B,F", bar_header);
188 NSString* baz_header = [headers objectForKey:@"Baz"];
189 EXPECT_NSEQ(@"C", baz_header);
190 NSString* cache_header = [headers objectForKey:@"Cache-Control"];
191 EXPECT_NSEQ(@"no-store", cache_header); // Cache-Control is overridden.
193 EXPECT_EQ(request->GetResponseCode(), [http_response statusCode]);
196 TEST_F(ProtocolHandlerUtilTest, BadHttpContentType) {
197 // Create a request using the magic domain that triggers a garbage
198 // content-type in the test framework.
199 GURL url(std::string("http://badcontenttype"));
200 scoped_ptr<URLRequest> request(
201 request_context_->CreateRequest(url, DEFAULT_PRIORITY, this));
203 // Create a response from the request.
205 GetNSURLResponseForRequest(request.get());
207 @catch (id exception) {
208 FAIL() << "Exception while creating response";
212 TEST_F(ProtocolHandlerUtilTest, MultipleHttpContentType) {
213 // Create a request using the magic domain that triggers a garbage
214 // content-type in the test framework.
215 GURL url(std::string("http://multiplecontenttype"));
216 scoped_ptr<URLRequest> request(
217 request_context_->CreateRequest(url, DEFAULT_PRIORITY, this));
219 // Create a response from the request.
220 NSURLResponse* response = GetNSURLResponseForRequest(request.get());
221 EXPECT_NSEQ(@"text/plain", [response MIMEType]);
222 EXPECT_NSEQ(@"iso-8859-4", [response textEncodingName]);
223 NSHTTPURLResponse* http_response = (NSHTTPURLResponse*)response;
224 NSDictionary* headers = [http_response allHeaderFields];
225 NSString* content_type_header = [headers objectForKey:@"Content-Type"];
226 EXPECT_NSEQ(@"text/plain; charset=iso-8859-4", content_type_header);
229 TEST_F(ProtocolHandlerUtilTest, CopyHttpHeaders) {
230 GURL url(std::string("http://url"));
231 base::scoped_nsobject<NSMutableURLRequest> in_request(
232 [[NSMutableURLRequest alloc] initWithURL:NSURLWithGURL(url)]);
233 [in_request setAllHTTPHeaderFields:@{
234 @"Referer" : @"referrer",
235 @"User-Agent" : @"secret",
236 @"Accept" : @"money/cash",
239 scoped_ptr<URLRequest> out_request(
240 request_context_->CreateRequest(url, DEFAULT_PRIORITY, nullptr));
241 CopyHttpHeaders(in_request, out_request.get());
243 EXPECT_EQ("referrer", out_request->referrer());
244 const HttpRequestHeaders& headers = out_request->extra_request_headers();
245 EXPECT_FALSE(headers.HasHeader("User-Agent")); // User agent is not copied.
246 EXPECT_FALSE(headers.HasHeader("Content-Type")); // Only in POST requests.
248 EXPECT_TRUE(headers.GetHeader("Accept", &header));
249 EXPECT_EQ("money/cash", header);
250 EXPECT_TRUE(headers.GetHeader("Foo", &header));
251 EXPECT_EQ("bar", header);
254 TEST_F(ProtocolHandlerUtilTest, AddMissingHeaders) {
255 GURL url(std::string("http://url"));
256 base::scoped_nsobject<NSMutableURLRequest> in_request(
257 [[NSMutableURLRequest alloc] initWithURL:NSURLWithGURL(url)]);
258 scoped_ptr<URLRequest> out_request(
259 request_context_->CreateRequest(url, DEFAULT_PRIORITY, nullptr));
260 out_request->set_method("POST");
261 scoped_ptr<UploadElementReader> reader(
262 new UploadBytesElementReader(nullptr, 0));
263 out_request->set_upload(
264 ElementsUploadDataStream::CreateWithReader(reader.Pass(), 0));
265 CopyHttpHeaders(in_request, out_request.get());
267 // Some headers are added by default if missing.
268 const HttpRequestHeaders& headers = out_request->extra_request_headers();
270 EXPECT_TRUE(headers.GetHeader("Accept", &header));
271 EXPECT_EQ("*/*", header);
272 EXPECT_TRUE(headers.GetHeader("Content-Type", &header));
273 EXPECT_EQ("application/x-www-form-urlencoded", header);