|  | // Copyright 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #import "ios/net/protocol_handler_util.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/memory/ptr_util.h" | 
|  | #include "base/message_loop/message_loop.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/strings/sys_string_conversions.h" | 
|  | #include "net/base/elements_upload_data_stream.h" | 
|  | #import "net/base/mac/url_conversions.h" | 
|  | #include "net/base/upload_bytes_element_reader.h" | 
|  | #include "net/http/http_request_headers.h" | 
|  | #include "net/http/http_response_headers.h" | 
|  | #include "net/url_request/data_protocol_handler.h" | 
|  | #include "net/url_request/url_request.h" | 
|  | #include "net/url_request/url_request_job.h" | 
|  | #include "net/url_request/url_request_job_factory.h" | 
|  | #include "net/url_request/url_request_job_factory_impl.h" | 
|  | #include "net/url_request/url_request_test_util.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "testing/gtest_mac.h" | 
|  | #include "testing/platform_test.h" | 
|  | #include "url/gurl.h" | 
|  |  | 
|  | #if !defined(__has_feature) || !__has_feature(objc_arc) | 
|  | #error "This file requires ARC support." | 
|  | #endif | 
|  |  | 
|  | // When C++ exceptions are disabled, the C++ library defines |try| and | 
|  | // |catch| so as to allow exception-expecting C++ code to build properly when | 
|  | // language support for exceptions is not present.  These macros interfere | 
|  | // with the use of |@try| and |@catch| in Objective-C files such as this one. | 
|  | // Undefine these macros here, after everything has been #included, since | 
|  | // there will be no C++ uses and only Objective-C uses from this point on. | 
|  | #undef try | 
|  | #undef catch | 
|  |  | 
|  | namespace net { | 
|  | namespace { | 
|  |  | 
|  | const char* kTextHtml = "text/html"; | 
|  | const char* kTextPlain = "text/plain"; | 
|  | const char* kAscii = "US-ASCII"; | 
|  |  | 
|  | class HeadersURLRequestJob : public URLRequestJob { | 
|  | public: | 
|  | HeadersURLRequestJob(URLRequest* request) | 
|  | : URLRequestJob(request, nullptr) {} | 
|  |  | 
|  | void Start() override { | 
|  | // Fills response headers and returns immediately. | 
|  | NotifyHeadersComplete(); | 
|  | } | 
|  |  | 
|  | bool GetMimeType(std::string* mime_type) const override { | 
|  | *mime_type = GetContentTypeValue(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void GetResponseInfo(HttpResponseInfo* info) override { | 
|  | // This is called by NotifyHeadersComplete(). | 
|  | std::string header_string("HTTP/1.0 200 OK"); | 
|  | header_string.push_back('\0'); | 
|  | header_string += std::string("Cache-Control: max-age=600"); | 
|  | header_string.push_back('\0'); | 
|  | if (request()->url().DomainIs("multiplecontenttype")) { | 
|  | header_string += std::string( | 
|  | "coNteNt-tYPe: text/plain; charset=iso-8859-4, image/png"); | 
|  | header_string.push_back('\0'); | 
|  | } | 
|  | header_string += std::string("Content-Type: ") + GetContentTypeValue(); | 
|  | header_string.push_back('\0'); | 
|  | header_string += std::string("Foo: A"); | 
|  | header_string.push_back('\0'); | 
|  | header_string += std::string("Bar: B"); | 
|  | header_string.push_back('\0'); | 
|  | header_string += std::string("Baz: C"); | 
|  | header_string.push_back('\0'); | 
|  | header_string += std::string("Foo: D"); | 
|  | header_string.push_back('\0'); | 
|  | header_string += std::string("Foo: E"); | 
|  | header_string.push_back('\0'); | 
|  | header_string += std::string("Bar: F"); | 
|  | header_string.push_back('\0'); | 
|  | info->headers = new HttpResponseHeaders(header_string); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | ~HeadersURLRequestJob() override {} | 
|  |  | 
|  | std::string GetContentTypeValue() const { | 
|  | if (request()->url().DomainIs("badcontenttype")) | 
|  | return "\xff"; | 
|  | return kTextHtml; | 
|  | } | 
|  | }; | 
|  |  | 
|  | class NetProtocolHandler : public URLRequestJobFactory::ProtocolHandler { | 
|  | public: | 
|  | URLRequestJob* MaybeCreateJob( | 
|  | URLRequest* request, | 
|  | NetworkDelegate* network_delegate) const override { | 
|  | return new HeadersURLRequestJob(request); | 
|  | } | 
|  | }; | 
|  |  | 
|  | class ProtocolHandlerUtilTest : public PlatformTest, | 
|  | public URLRequest::Delegate { | 
|  | public: | 
|  | ProtocolHandlerUtilTest() : request_context_(new TestURLRequestContext) { | 
|  | // Ownership of the protocol handlers is transferred to the factory. | 
|  | job_factory_.SetProtocolHandler("http", | 
|  | base::WrapUnique(new NetProtocolHandler)); | 
|  | job_factory_.SetProtocolHandler("data", | 
|  | base::WrapUnique(new DataProtocolHandler)); | 
|  | request_context_->set_job_factory(&job_factory_); | 
|  | } | 
|  |  | 
|  | NSURLResponse* BuildDataURLResponse(const std::string& mime_type, | 
|  | const std::string& encoding, | 
|  | const std::string& content) { | 
|  | // Build an URL in the form "data:<mime_type>;charset=<encoding>,<content>" | 
|  | // The ';' is removed if mime_type or charset is empty. | 
|  | std::string url_string = std::string("data:") + mime_type; | 
|  | if (!encoding.empty()) | 
|  | url_string += ";charset=" + encoding; | 
|  | url_string += ","; | 
|  | GURL url(url_string); | 
|  |  | 
|  | std::unique_ptr<URLRequest> request( | 
|  | request_context_->CreateRequest(url, DEFAULT_PRIORITY, this)); | 
|  | request->Start(); | 
|  | base::RunLoop loop; | 
|  | loop.RunUntilIdle(); | 
|  | return GetNSURLResponseForRequest(request.get()); | 
|  | } | 
|  |  | 
|  | void CheckDataResponse(NSURLResponse* response, | 
|  | const std::string& mime_type, | 
|  | const std::string& encoding) { | 
|  | EXPECT_NSEQ(base::SysUTF8ToNSString(mime_type), [response MIMEType]); | 
|  | EXPECT_NSEQ(base::SysUTF8ToNSString(encoding), [response textEncodingName]); | 
|  | // The response class must be NSURLResponse (and not NSHTTPURLResponse) when | 
|  | // the scheme is "data". | 
|  | EXPECT_TRUE([response isMemberOfClass:[NSURLResponse class]]); | 
|  | } | 
|  |  | 
|  | void OnResponseStarted(URLRequest* request, int net_error) override {} | 
|  | void OnReadCompleted(URLRequest* request, int bytes_read) override {} | 
|  |  | 
|  | protected: | 
|  | base::MessageLoop loop_; | 
|  | URLRequestJobFactoryImpl job_factory_; | 
|  | std::unique_ptr<URLRequestContext> request_context_; | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | TEST_F(ProtocolHandlerUtilTest, GetResponseDataSchemeTest) { | 
|  | NSURLResponse* response; | 
|  | // MIME type and charset are correctly carried over. | 
|  | response = BuildDataURLResponse("?mime=type'", "$(charset-*", "content"); | 
|  | CheckDataResponse(response, "?mime=type'", "$(charset-*"); | 
|  | // Missing values are treated as default values. | 
|  | response = BuildDataURLResponse("", "", "content"); | 
|  | CheckDataResponse(response, kTextPlain, kAscii); | 
|  | } | 
|  |  | 
|  | TEST_F(ProtocolHandlerUtilTest, GetResponseHttpTest) { | 
|  | // Create a request. | 
|  | GURL url(std::string("http://url")); | 
|  | std::unique_ptr<URLRequest> request( | 
|  | request_context_->CreateRequest(url, DEFAULT_PRIORITY, this)); | 
|  | request->Start(); | 
|  | // Create a response from the request. | 
|  | NSURLResponse* response = GetNSURLResponseForRequest(request.get()); | 
|  | EXPECT_NSEQ([NSString stringWithUTF8String:kTextHtml], [response MIMEType]); | 
|  | ASSERT_TRUE([response isKindOfClass:[NSHTTPURLResponse class]]); | 
|  | NSHTTPURLResponse* http_response = (NSHTTPURLResponse*)response; | 
|  | NSDictionary* headers = [http_response allHeaderFields]; | 
|  | // Check the headers, duplicates must be appended. | 
|  | EXPECT_EQ(5u, [headers count]); | 
|  | NSString* foo_header = [headers objectForKey:@"Foo"]; | 
|  | EXPECT_NSEQ(@"A,D,E", foo_header); | 
|  | NSString* bar_header = [headers objectForKey:@"Bar"]; | 
|  | EXPECT_NSEQ(@"B,F", bar_header); | 
|  | NSString* baz_header = [headers objectForKey:@"Baz"]; | 
|  | EXPECT_NSEQ(@"C", baz_header); | 
|  | NSString* cache_header = [headers objectForKey:@"Cache-Control"]; | 
|  | EXPECT_NSEQ(@"no-store", cache_header);  // Cache-Control is overridden. | 
|  | // Check the status. | 
|  | EXPECT_EQ(request->GetResponseCode(), [http_response statusCode]); | 
|  | } | 
|  |  | 
|  | TEST_F(ProtocolHandlerUtilTest, BadHttpContentType) { | 
|  | // Create a request using the magic domain that triggers a garbage | 
|  | // content-type in the test framework. | 
|  | GURL url(std::string("http://badcontenttype")); | 
|  | std::unique_ptr<URLRequest> request( | 
|  | request_context_->CreateRequest(url, DEFAULT_PRIORITY, this)); | 
|  | request->Start(); | 
|  | // Create a response from the request. | 
|  | @try { | 
|  | GetNSURLResponseForRequest(request.get()); | 
|  | } | 
|  | @catch (id exception) { | 
|  | FAIL() << "Exception while creating response"; | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(ProtocolHandlerUtilTest, MultipleHttpContentType) { | 
|  | // Create a request using the magic domain that triggers a garbage | 
|  | // content-type in the test framework. | 
|  | GURL url(std::string("http://multiplecontenttype")); | 
|  | std::unique_ptr<URLRequest> request( | 
|  | request_context_->CreateRequest(url, DEFAULT_PRIORITY, this)); | 
|  | request->Start(); | 
|  | // Create a response from the request. | 
|  | NSURLResponse* response = GetNSURLResponseForRequest(request.get()); | 
|  | EXPECT_NSEQ(@"text/plain", [response MIMEType]); | 
|  | EXPECT_NSEQ(@"iso-8859-4", [response textEncodingName]); | 
|  | NSHTTPURLResponse* http_response = (NSHTTPURLResponse*)response; | 
|  | NSDictionary* headers = [http_response allHeaderFields]; | 
|  | NSString* content_type_header = [headers objectForKey:@"Content-Type"]; | 
|  | EXPECT_NSEQ(@"text/plain; charset=iso-8859-4", content_type_header); | 
|  | } | 
|  |  | 
|  | TEST_F(ProtocolHandlerUtilTest, CopyHttpHeaders) { | 
|  | GURL url(std::string("http://url")); | 
|  | NSMutableURLRequest* in_request = | 
|  | [[NSMutableURLRequest alloc] initWithURL:NSURLWithGURL(url)]; | 
|  | [in_request setAllHTTPHeaderFields:@{ | 
|  | @"Referer" : @"referrer", | 
|  | @"User-Agent" : @"secret", | 
|  | @"Accept" : @"money/cash", | 
|  | @"Foo" : @"bar", | 
|  | }]; | 
|  | std::unique_ptr<URLRequest> out_request( | 
|  | request_context_->CreateRequest(url, DEFAULT_PRIORITY, nullptr)); | 
|  | CopyHttpHeaders(in_request, out_request.get()); | 
|  |  | 
|  | EXPECT_EQ("referrer", out_request->referrer()); | 
|  | const HttpRequestHeaders& headers = out_request->extra_request_headers(); | 
|  | EXPECT_FALSE(headers.HasHeader("User-Agent"));    // User agent is not copied. | 
|  | EXPECT_FALSE(headers.HasHeader("Content-Type"));  // Only in POST requests. | 
|  | std::string header; | 
|  | EXPECT_TRUE(headers.GetHeader("Accept", &header)); | 
|  | EXPECT_EQ("money/cash", header); | 
|  | EXPECT_TRUE(headers.GetHeader("Foo", &header)); | 
|  | EXPECT_EQ("bar", header); | 
|  | } | 
|  |  | 
|  | TEST_F(ProtocolHandlerUtilTest, AddMissingHeaders) { | 
|  | GURL url(std::string("http://url")); | 
|  | NSMutableURLRequest* in_request = | 
|  | [[NSMutableURLRequest alloc] initWithURL:NSURLWithGURL(url)]; | 
|  | std::unique_ptr<URLRequest> out_request( | 
|  | request_context_->CreateRequest(url, DEFAULT_PRIORITY, nullptr)); | 
|  | out_request->set_method("POST"); | 
|  | std::unique_ptr<UploadElementReader> reader( | 
|  | new UploadBytesElementReader(nullptr, 0)); | 
|  | out_request->set_upload( | 
|  | ElementsUploadDataStream::CreateWithReader(std::move(reader), 0)); | 
|  | CopyHttpHeaders(in_request, out_request.get()); | 
|  |  | 
|  | // Some headers are added by default if missing. | 
|  | const HttpRequestHeaders& headers = out_request->extra_request_headers(); | 
|  | std::string header; | 
|  | EXPECT_TRUE(headers.GetHeader("Accept", &header)); | 
|  | EXPECT_EQ("*/*", header); | 
|  | EXPECT_TRUE(headers.GetHeader("Content-Type", &header)); | 
|  | EXPECT_EQ("application/x-www-form-urlencoded", header); | 
|  | } | 
|  |  | 
|  | }  // namespace net |