1 // Copyright 2015 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 <Foundation/Foundation.h>
9 #include "base/logging.h"
10 #include "base/mac/scoped_nsobject.h"
11 #include "base/strings/sys_string_conversions.h"
12 #import "ios/third_party/gcdwebserver/src/GCDWebServer/Core/GCDWebServer.h"
13 #include "net/base/mac/url_conversions.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 #include "testing/gtest_mac.h"
18 @interface TestDelegate : NSObject<NSURLSessionDataDelegate,
20 NSURLSessionTaskDelegate>
22 // Completion semaphore for this TestDelegate. When the request this delegate is
23 // attached to finishes (either successfully or with an error), this delegate
24 // signals this semaphore.
25 @property(assign, nonatomic) dispatch_semaphore_t semaphore;
27 // Total count of bytes received by the request this delegate is attached to.
28 @property(nonatomic) unsigned long receivedBytes;
30 // Error the request this delegate is attached to failed with, if any.
31 @property(retain, nonatomic) NSError* error;
35 @implementation TestDelegate
36 @synthesize semaphore = _semaphore;
37 @synthesize receivedBytes = _receivedBytes;
38 @synthesize error = _error;
41 if (self = [super init]) {
42 _semaphore = dispatch_semaphore_create(0);
48 dispatch_release(_semaphore);
54 - (void)URLSession:(NSURLSession*)session
55 didBecomeInvalidWithError:(NSError*)error {
58 - (void)URLSession:(NSURLSession*)session
59 task:(NSURLSessionTask*)task
60 didCompleteWithError:(NSError*)error {
61 [self setError:error];
62 dispatch_semaphore_signal(_semaphore);
65 - (void)URLSession:(NSURLSession*)session
66 task:(NSURLSessionTask*)task
67 didReceiveChallenge:(NSURLAuthenticationChallenge*)challenge
69 (void (^)(NSURLSessionAuthChallengeDisposition disp,
70 NSURLCredential* credential))completionHandler {
71 completionHandler(NSURLSessionAuthChallengeUseCredential, nil);
74 - (void)URLSession:(NSURLSession*)session
75 dataTask:(NSURLSessionDataTask*)dataTask
76 didReceiveResponse:(NSURLResponse*)response
77 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))
79 completionHandler(NSURLSessionResponseAllow);
82 - (void)URLSession:(NSURLSession*)session
83 dataTask:(NSURLSessionDataTask*)dataTask
84 didReceiveData:(NSData*)data {
85 _receivedBytes += (unsigned long)data.length;
88 - (void)URLSession:(NSURLSession*)session
89 dataTask:(NSURLSessionDataTask*)dataTask
90 willCacheResponse:(NSCachedURLResponse*)proposedResponse
92 (void (^)(NSCachedURLResponse* cachedResponse))completionHandler {
93 completionHandler(proposedResponse);
98 // base::TimeDelta would normally be ideal for this but it does not support
99 // nanosecond resolution.
100 static const int64_t ns_in_second = 1000000000LL;
102 class HttpTest : public ::testing::Test {
105 ~HttpTest() override {}
107 void SetUp() override {
108 [CrNet setPartialUserAgent:@"CrNetTest/1.0.0.0"];
110 NSURLSessionConfiguration* config =
111 [NSURLSessionConfiguration ephemeralSessionConfiguration];
112 [CrNet installIntoSessionConfiguration:config];
113 delegate_.reset([[TestDelegate alloc] init]);
114 NSURLSession* session = [NSURLSession sessionWithConfiguration:config
117 // Take a reference to the session and store it so it doesn't get
118 // deallocated until this object does.
119 session_.reset([session retain]);
120 web_server_.reset([[GCDWebServer alloc] init]);
123 void TearDown() override {
128 // Starts a GCDWebServer instance on localhost port 8080, and remembers the
129 // root URL for later; tests can use GetURL() to produce a URL referring to a
130 // specific resource under the root URL.
131 void StartWebServer() {
132 [web_server_ startWithPort:8080 bonjourName:nil];
133 server_root_ = net::GURLWithNSURL([web_server_ serverURL]);
136 // Registers a fixed response |text| to be returned to requests for |path|,
137 // which is relative to |server_root_|.
138 void RegisterPathText(const std::string& path, const std::string& text) {
139 NSString* nspath = base::SysUTF8ToNSString(path);
140 NSData* data = [NSData dataWithBytes:text.c_str() length:text.length()];
141 [web_server_ addGETHandlerForPath:nspath
143 contentType:@"text/plain"
147 void RegisterPathHandler(const std::string& path,
148 GCDWebServerProcessBlock handler) {
149 NSString* nspath = base::SysUTF8ToNSString(path);
150 [web_server_ addHandlerForMethod:@"GET"
152 requestClass:NSClassFromString(@"GCDWebServerRequest")
153 processBlock:handler];
156 // Launches the supplied |task| and blocks until it completes, with a timeout
158 void StartDataTaskAndWaitForCompletion(NSURLSessionDataTask* task) {
160 int64_t deadline_ns = 1 * ns_in_second;
161 dispatch_semaphore_wait([delegate_ semaphore],
162 dispatch_time(DISPATCH_TIME_NOW, deadline_ns));
165 // Returns a URL to refer to the resource named |path| served by the test
166 // server. If |path| starts with a /, the leading / will be stripped.
167 GURL GetURL(const std::string& path) {
168 std::string real_path = path[0] == '/' ? path.substr(1) : path;
169 return server_root_.Resolve(real_path);
172 // Some convenience functions for working with GCDWebServerRequest and
173 // GCDWebServerResponse.
175 // Returns true if the value for the request header |header| is not nil and
176 // contains the string |target|.
177 bool HeaderValueContains(GCDWebServerRequest* request,
178 const std::string& header,
179 const std::string& target) {
180 NSString* key = base::SysUTF8ToNSString(header);
181 NSString* needle = base::SysUTF8ToNSString(target);
182 NSString* haystack = request.headers[key];
185 return [haystack rangeOfString:needle].location != NSNotFound;
188 base::scoped_nsobject<NSURLSession> session_;
189 base::scoped_nsobject<TestDelegate> delegate_;
192 base::scoped_nsobject<GCDWebServer> web_server_;
196 TEST_F(HttpTest, NSURLConnectionReceivesData) {
197 const char kData[] = "foobar";
198 const char kPath[] = "/foo";
199 RegisterPathText(kPath, kData);
202 NSURL* url = net::NSURLWithGURL(GetURL(kPath));
203 NSURLRequest* req = [NSURLRequest requestWithURL:url];
204 NSURLResponse* resp = nil;
205 NSData* received = [NSURLConnection sendSynchronousRequest:req
206 returningResponse:&resp
208 EXPECT_EQ(0, memcmp([received bytes], kData, sizeof(kData)));
211 TEST_F(HttpTest, NSURLSessionReceivesData) {
212 const char kPath[] = "/foo";
213 const char kData[] = "foobar";
214 RegisterPathText(kPath, kData);
217 NSURL* url = net::NSURLWithGURL(GetURL(kPath));
218 NSURLSessionDataTask* task = [session_ dataTaskWithURL:url];
219 StartDataTaskAndWaitForCompletion(task);
220 EXPECT_EQ(nil, [delegate_ error]);
221 EXPECT_EQ(strlen(kData), [delegate_ receivedBytes]);
224 TEST_F(HttpTest, SdchDisabledByDefault) {
225 const char kPath[] = "/foo";
226 RegisterPathHandler(kPath,
227 ^GCDWebServerResponse* (GCDWebServerRequest* req) {
228 EXPECT_FALSE(HeaderValueContains(req, "Accept-Encoding", "sdch"));
232 NSURL* url = net::NSURLWithGURL(GetURL(kPath));
233 NSURLRequest* req = [NSURLRequest requestWithURL:url];
234 NSURLResponse* resp = nil;
235 NSError* error = nil;
236 NSData* received = [NSURLConnection sendSynchronousRequest:req
237 returningResponse:&resp
242 // TODO(ellyjones): There needs to be a test that enabling SDCH works, but
243 // because CrNet is static and 'uninstall' only disables it, there is no way to
244 // have an individual test enable or disable SDCH.
245 // Probably there is a way to get gtest tests to run in a separate process, but
246 // I'm not sure what it is.