droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 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. |
| 4 | |
dcheng | 5e05b43 | 2016-01-14 04:41:45 | [diff] [blame] | 5 | #import "ios/net/protocol_handler_util.h" |
| 6 | |
dcheng | 942f39d7 | 2016-04-07 21:11:23 | [diff] [blame] | 7 | #include <memory> |
dcheng | 5e05b43 | 2016-01-14 04:41:45 | [diff] [blame] | 8 | #include <utility> |
| 9 | |
dcheng | 942f39d7 | 2016-04-07 21:11:23 | [diff] [blame] | 10 | #include "base/memory/ptr_util.h" |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 11 | #include "base/run_loop.h" |
| 12 | #include "base/strings/sys_string_conversions.h" |
Gabriel Charette | c710874 | 2019-08-23 03:31:40 | [diff] [blame^] | 13 | #include "base/test/task_environment.h" |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 14 | #include "net/base/elements_upload_data_stream.h" |
| 15 | #import "net/base/mac/url_conversions.h" |
| 16 | #include "net/base/upload_bytes_element_reader.h" |
| 17 | #include "net/http/http_request_headers.h" |
| 18 | #include "net/http/http_response_headers.h" |
| 19 | #include "net/url_request/data_protocol_handler.h" |
| 20 | #include "net/url_request/url_request.h" |
| 21 | #include "net/url_request/url_request_job.h" |
| 22 | #include "net/url_request/url_request_job_factory.h" |
| 23 | #include "net/url_request/url_request_job_factory_impl.h" |
| 24 | #include "net/url_request/url_request_test_util.h" |
| 25 | #include "testing/gtest/include/gtest/gtest.h" |
| 26 | #include "testing/gtest_mac.h" |
Sylvain Defresne | 64778080 | 2017-10-09 14:59:27 | [diff] [blame] | 27 | #include "testing/platform_test.h" |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 28 | #include "url/gurl.h" |
| 29 | |
marq | 8e82dba | 2017-06-19 16:09:20 | [diff] [blame] | 30 | #if !defined(__has_feature) || !__has_feature(objc_arc) |
| 31 | #error "This file requires ARC support." |
| 32 | #endif |
| 33 | |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 34 | // When C++ exceptions are disabled, the C++ library defines |try| and |
| 35 | // |catch| so as to allow exception-expecting C++ code to build properly when |
| 36 | // language support for exceptions is not present. These macros interfere |
| 37 | // with the use of |@try| and |@catch| in Objective-C files such as this one. |
| 38 | // Undefine these macros here, after everything has been #included, since |
| 39 | // there will be no C++ uses and only Objective-C uses from this point on. |
| 40 | #undef try |
| 41 | #undef catch |
| 42 | |
| 43 | namespace net { |
| 44 | namespace { |
| 45 | |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 46 | const char* kTextHtml = "text/html"; |
| 47 | const char* kTextPlain = "text/plain"; |
| 48 | const char* kAscii = "US-ASCII"; |
| 49 | |
| 50 | class HeadersURLRequestJob : public URLRequestJob { |
| 51 | public: |
| 52 | HeadersURLRequestJob(URLRequest* request) |
| 53 | : URLRequestJob(request, nullptr) {} |
| 54 | |
| 55 | void Start() override { |
| 56 | // Fills response headers and returns immediately. |
| 57 | NotifyHeadersComplete(); |
| 58 | } |
| 59 | |
| 60 | bool GetMimeType(std::string* mime_type) const override { |
| 61 | *mime_type = GetContentTypeValue(); |
| 62 | return true; |
| 63 | } |
| 64 | |
| 65 | void GetResponseInfo(HttpResponseInfo* info) override { |
| 66 | // This is called by NotifyHeadersComplete(). |
| 67 | std::string header_string("HTTP/1.0 200 OK"); |
| 68 | header_string.push_back('\0'); |
| 69 | header_string += std::string("Cache-Control: max-age=600"); |
| 70 | header_string.push_back('\0'); |
| 71 | if (request()->url().DomainIs("multiplecontenttype")) { |
| 72 | header_string += std::string( |
| 73 | "coNteNt-tYPe: text/plain; charset=iso-8859-4, image/png"); |
| 74 | header_string.push_back('\0'); |
| 75 | } |
| 76 | header_string += std::string("Content-Type: ") + GetContentTypeValue(); |
| 77 | header_string.push_back('\0'); |
| 78 | header_string += std::string("Foo: A"); |
| 79 | header_string.push_back('\0'); |
| 80 | header_string += std::string("Bar: B"); |
| 81 | header_string.push_back('\0'); |
| 82 | header_string += std::string("Baz: C"); |
| 83 | header_string.push_back('\0'); |
| 84 | header_string += std::string("Foo: D"); |
| 85 | header_string.push_back('\0'); |
| 86 | header_string += std::string("Foo: E"); |
| 87 | header_string.push_back('\0'); |
| 88 | header_string += std::string("Bar: F"); |
| 89 | header_string.push_back('\0'); |
| 90 | info->headers = new HttpResponseHeaders(header_string); |
| 91 | } |
| 92 | |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 93 | protected: |
| 94 | ~HeadersURLRequestJob() override {} |
| 95 | |
| 96 | std::string GetContentTypeValue() const { |
| 97 | if (request()->url().DomainIs("badcontenttype")) |
| 98 | return "\xff"; |
| 99 | return kTextHtml; |
| 100 | } |
| 101 | }; |
| 102 | |
| 103 | class NetProtocolHandler : public URLRequestJobFactory::ProtocolHandler { |
| 104 | public: |
| 105 | URLRequestJob* MaybeCreateJob( |
| 106 | URLRequest* request, |
| 107 | NetworkDelegate* network_delegate) const override { |
| 108 | return new HeadersURLRequestJob(request); |
| 109 | } |
| 110 | }; |
| 111 | |
Sylvain Defresne | 64778080 | 2017-10-09 14:59:27 | [diff] [blame] | 112 | class ProtocolHandlerUtilTest : public PlatformTest, |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 113 | public URLRequest::Delegate { |
| 114 | public: |
| 115 | ProtocolHandlerUtilTest() : request_context_(new TestURLRequestContext) { |
| 116 | // Ownership of the protocol handlers is transferred to the factory. |
svaldez | 5d58c9e | 2015-08-24 21:36:20 | [diff] [blame] | 117 | job_factory_.SetProtocolHandler("http", |
dcheng | 942f39d7 | 2016-04-07 21:11:23 | [diff] [blame] | 118 | base::WrapUnique(new NetProtocolHandler)); |
svaldez | 5d58c9e | 2015-08-24 21:36:20 | [diff] [blame] | 119 | job_factory_.SetProtocolHandler("data", |
dcheng | 942f39d7 | 2016-04-07 21:11:23 | [diff] [blame] | 120 | base::WrapUnique(new DataProtocolHandler)); |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 121 | request_context_->set_job_factory(&job_factory_); |
| 122 | } |
| 123 | |
| 124 | NSURLResponse* BuildDataURLResponse(const std::string& mime_type, |
| 125 | const std::string& encoding, |
| 126 | const std::string& content) { |
| 127 | // Build an URL in the form "data:<mime_type>;charset=<encoding>,<content>" |
| 128 | // The ';' is removed if mime_type or charset is empty. |
| 129 | std::string url_string = std::string("data:") + mime_type; |
| 130 | if (!encoding.empty()) |
| 131 | url_string += ";charset=" + encoding; |
| 132 | url_string += ","; |
| 133 | GURL url(url_string); |
| 134 | |
dcheng | 942f39d7 | 2016-04-07 21:11:23 | [diff] [blame] | 135 | std::unique_ptr<URLRequest> request( |
davidben | 151423e | 2015-03-23 18:48:36 | [diff] [blame] | 136 | request_context_->CreateRequest(url, DEFAULT_PRIORITY, this)); |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 137 | request->Start(); |
| 138 | base::RunLoop loop; |
| 139 | loop.RunUntilIdle(); |
| 140 | return GetNSURLResponseForRequest(request.get()); |
| 141 | } |
| 142 | |
| 143 | void CheckDataResponse(NSURLResponse* response, |
| 144 | const std::string& mime_type, |
| 145 | const std::string& encoding) { |
| 146 | EXPECT_NSEQ(base::SysUTF8ToNSString(mime_type), [response MIMEType]); |
| 147 | EXPECT_NSEQ(base::SysUTF8ToNSString(encoding), [response textEncodingName]); |
| 148 | // The response class must be NSURLResponse (and not NSHTTPURLResponse) when |
| 149 | // the scheme is "data". |
| 150 | EXPECT_TRUE([response isMemberOfClass:[NSURLResponse class]]); |
| 151 | } |
| 152 | |
maksim.sisov | 5af9098 | 2016-09-05 11:26:28 | [diff] [blame] | 153 | void OnResponseStarted(URLRequest* request, int net_error) override {} |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 154 | void OnReadCompleted(URLRequest* request, int bytes_read) override {} |
| 155 | |
| 156 | protected: |
Gabriel Charette | dfa3604 | 2019-08-19 17:30:11 | [diff] [blame] | 157 | base::test::TaskEnvironment task_environment_; |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 158 | URLRequestJobFactoryImpl job_factory_; |
dcheng | 942f39d7 | 2016-04-07 21:11:23 | [diff] [blame] | 159 | std::unique_ptr<URLRequestContext> request_context_; |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 160 | }; |
| 161 | |
| 162 | } // namespace |
| 163 | |
| 164 | TEST_F(ProtocolHandlerUtilTest, GetResponseDataSchemeTest) { |
| 165 | NSURLResponse* response; |
| 166 | // MIME type and charset are correctly carried over. |
Stephen McGruer | 1eb762a | 2018-05-18 17:04:44 | [diff] [blame] | 167 | response = BuildDataURLResponse("?mime=type'", "$(charset-*", "content"); |
| 168 | CheckDataResponse(response, "?mime=type'", "$(charset-*"); |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 169 | // Missing values are treated as default values. |
| 170 | response = BuildDataURLResponse("", "", "content"); |
| 171 | CheckDataResponse(response, kTextPlain, kAscii); |
| 172 | } |
| 173 | |
| 174 | TEST_F(ProtocolHandlerUtilTest, GetResponseHttpTest) { |
| 175 | // Create a request. |
| 176 | GURL url(std::string("http://url")); |
dcheng | 942f39d7 | 2016-04-07 21:11:23 | [diff] [blame] | 177 | std::unique_ptr<URLRequest> request( |
davidben | 151423e | 2015-03-23 18:48:36 | [diff] [blame] | 178 | request_context_->CreateRequest(url, DEFAULT_PRIORITY, this)); |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 179 | request->Start(); |
| 180 | // Create a response from the request. |
| 181 | NSURLResponse* response = GetNSURLResponseForRequest(request.get()); |
| 182 | EXPECT_NSEQ([NSString stringWithUTF8String:kTextHtml], [response MIMEType]); |
| 183 | ASSERT_TRUE([response isKindOfClass:[NSHTTPURLResponse class]]); |
| 184 | NSHTTPURLResponse* http_response = (NSHTTPURLResponse*)response; |
| 185 | NSDictionary* headers = [http_response allHeaderFields]; |
| 186 | // Check the headers, duplicates must be appended. |
| 187 | EXPECT_EQ(5u, [headers count]); |
| 188 | NSString* foo_header = [headers objectForKey:@"Foo"]; |
| 189 | EXPECT_NSEQ(@"A,D,E", foo_header); |
| 190 | NSString* bar_header = [headers objectForKey:@"Bar"]; |
| 191 | EXPECT_NSEQ(@"B,F", bar_header); |
| 192 | NSString* baz_header = [headers objectForKey:@"Baz"]; |
| 193 | EXPECT_NSEQ(@"C", baz_header); |
| 194 | NSString* cache_header = [headers objectForKey:@"Cache-Control"]; |
| 195 | EXPECT_NSEQ(@"no-store", cache_header); // Cache-Control is overridden. |
| 196 | // Check the status. |
| 197 | EXPECT_EQ(request->GetResponseCode(), [http_response statusCode]); |
| 198 | } |
| 199 | |
| 200 | TEST_F(ProtocolHandlerUtilTest, BadHttpContentType) { |
| 201 | // Create a request using the magic domain that triggers a garbage |
| 202 | // content-type in the test framework. |
| 203 | GURL url(std::string("http://badcontenttype")); |
dcheng | 942f39d7 | 2016-04-07 21:11:23 | [diff] [blame] | 204 | std::unique_ptr<URLRequest> request( |
davidben | 151423e | 2015-03-23 18:48:36 | [diff] [blame] | 205 | request_context_->CreateRequest(url, DEFAULT_PRIORITY, this)); |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 206 | request->Start(); |
| 207 | // Create a response from the request. |
| 208 | @try { |
| 209 | GetNSURLResponseForRequest(request.get()); |
| 210 | } |
| 211 | @catch (id exception) { |
| 212 | FAIL() << "Exception while creating response"; |
| 213 | } |
| 214 | } |
| 215 | |
| 216 | TEST_F(ProtocolHandlerUtilTest, MultipleHttpContentType) { |
| 217 | // Create a request using the magic domain that triggers a garbage |
| 218 | // content-type in the test framework. |
| 219 | GURL url(std::string("http://multiplecontenttype")); |
dcheng | 942f39d7 | 2016-04-07 21:11:23 | [diff] [blame] | 220 | std::unique_ptr<URLRequest> request( |
davidben | 151423e | 2015-03-23 18:48:36 | [diff] [blame] | 221 | request_context_->CreateRequest(url, DEFAULT_PRIORITY, this)); |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 222 | request->Start(); |
| 223 | // Create a response from the request. |
| 224 | NSURLResponse* response = GetNSURLResponseForRequest(request.get()); |
| 225 | EXPECT_NSEQ(@"text/plain", [response MIMEType]); |
| 226 | EXPECT_NSEQ(@"iso-8859-4", [response textEncodingName]); |
| 227 | NSHTTPURLResponse* http_response = (NSHTTPURLResponse*)response; |
| 228 | NSDictionary* headers = [http_response allHeaderFields]; |
| 229 | NSString* content_type_header = [headers objectForKey:@"Content-Type"]; |
| 230 | EXPECT_NSEQ(@"text/plain; charset=iso-8859-4", content_type_header); |
| 231 | } |
| 232 | |
| 233 | TEST_F(ProtocolHandlerUtilTest, CopyHttpHeaders) { |
| 234 | GURL url(std::string("http://url")); |
marq | 8e82dba | 2017-06-19 16:09:20 | [diff] [blame] | 235 | NSMutableURLRequest* in_request = |
| 236 | [[NSMutableURLRequest alloc] initWithURL:NSURLWithGURL(url)]; |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 237 | [in_request setAllHTTPHeaderFields:@{ |
| 238 | @"Referer" : @"referrer", |
| 239 | @"User-Agent" : @"secret", |
| 240 | @"Accept" : @"money/cash", |
| 241 | @"Foo" : @"bar", |
| 242 | }]; |
dcheng | 942f39d7 | 2016-04-07 21:11:23 | [diff] [blame] | 243 | std::unique_ptr<URLRequest> out_request( |
davidben | 151423e | 2015-03-23 18:48:36 | [diff] [blame] | 244 | request_context_->CreateRequest(url, DEFAULT_PRIORITY, nullptr)); |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 245 | CopyHttpHeaders(in_request, out_request.get()); |
| 246 | |
| 247 | EXPECT_EQ("referrer", out_request->referrer()); |
| 248 | const HttpRequestHeaders& headers = out_request->extra_request_headers(); |
| 249 | EXPECT_FALSE(headers.HasHeader("User-Agent")); // User agent is not copied. |
| 250 | EXPECT_FALSE(headers.HasHeader("Content-Type")); // Only in POST requests. |
| 251 | std::string header; |
| 252 | EXPECT_TRUE(headers.GetHeader("Accept", &header)); |
| 253 | EXPECT_EQ("money/cash", header); |
| 254 | EXPECT_TRUE(headers.GetHeader("Foo", &header)); |
| 255 | EXPECT_EQ("bar", header); |
| 256 | } |
| 257 | |
| 258 | TEST_F(ProtocolHandlerUtilTest, AddMissingHeaders) { |
| 259 | GURL url(std::string("http://url")); |
marq | 8e82dba | 2017-06-19 16:09:20 | [diff] [blame] | 260 | NSMutableURLRequest* in_request = |
| 261 | [[NSMutableURLRequest alloc] initWithURL:NSURLWithGURL(url)]; |
dcheng | 942f39d7 | 2016-04-07 21:11:23 | [diff] [blame] | 262 | std::unique_ptr<URLRequest> out_request( |
davidben | 151423e | 2015-03-23 18:48:36 | [diff] [blame] | 263 | request_context_->CreateRequest(url, DEFAULT_PRIORITY, nullptr)); |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 264 | out_request->set_method("POST"); |
dcheng | 942f39d7 | 2016-04-07 21:11:23 | [diff] [blame] | 265 | std::unique_ptr<UploadElementReader> reader( |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 266 | new UploadBytesElementReader(nullptr, 0)); |
| 267 | out_request->set_upload( |
dcheng | 5e05b43 | 2016-01-14 04:41:45 | [diff] [blame] | 268 | ElementsUploadDataStream::CreateWithReader(std::move(reader), 0)); |
droger | 476922e0 | 2015-03-10 17:17:52 | [diff] [blame] | 269 | CopyHttpHeaders(in_request, out_request.get()); |
| 270 | |
| 271 | // Some headers are added by default if missing. |
| 272 | const HttpRequestHeaders& headers = out_request->extra_request_headers(); |
| 273 | std::string header; |
| 274 | EXPECT_TRUE(headers.GetHeader("Accept", &header)); |
| 275 | EXPECT_EQ("*/*", header); |
| 276 | EXPECT_TRUE(headers.GetHeader("Content-Type", &header)); |
| 277 | EXPECT_EQ("application/x-www-form-urlencoded", header); |
| 278 | } |
| 279 | |
| 280 | } // namespace net |