Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ios / web / net / clients / crw_js_injection_network_client_unittest.mm
blobb5353d5dc850920a0bff2287dfabf9143cf81972
1 // Copyright 2014 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 "ios/web/net/clients/crw_js_injection_network_client.h"
7 #import <Foundation/Foundation.h>
9 #include "base/files/file_path.h"
10 #include "base/files/file_util.h"
11 #include "base/mac/scoped_nsobject.h"
12 #include "base/path_service.h"
13 #include "base/strings/sys_string_conversions.h"
14 #import "ios/net/clients/crn_network_client_protocol.h"
15 #import "ios/net/crn_http_url_response.h"
16 #include "testing/gmock/include/gmock/gmock.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #import "third_party/ocmock/OCMock/OCMock.h"
19 #import "third_party/ocmock/gtest_support.h"
21 namespace {
23 // Returns an NSString filled with the char 'a' of length |length|.
24 NSString* GetLongString(NSUInteger length) {
25    base::scoped_nsobject<NSMutableData> data(
26        [[NSMutableData alloc] initWithLength:length]);
27    memset([data mutableBytes], 'a', length);
28    NSString* long_string =
29        [[NSString alloc] initWithData:data
30                              encoding:NSASCIIStringEncoding];
31    return [long_string autorelease];
36 // Class to serve as underlying client for JS injection client to expose
37 // data and responses that are passed on from the JS injection client.
38 @interface UnderlyingClient : CRNForwardingNetworkClient {
39   base::scoped_nsobject<NSMutableData> _loadedData;
40   base::scoped_nsobject<NSURLResponse> _receivedResponse;
42 // Returns all data loaded by the client.
43 - (NSData*)loadedData;
44 // Returns response received by the client.
45 - (NSURLResponse*)receivedResponse;
46 @end
48 @implementation UnderlyingClient
50 - (instancetype)init {
51   if ((self = [super init])) {
52     _loadedData.reset([[NSMutableData alloc] init]);
53   }
54   return self;
57 - (NSData*)loadedData {
58   return _loadedData.get();
61 - (NSURLResponse*)receivedResponse {
62   return _receivedResponse.get();
65 - (void)didLoadData:(NSData*)data {
66   [_loadedData appendData:data];
67   [super didLoadData:data];
70 - (void)didReceiveResponse:(NSURLResponse*)response {
71   _receivedResponse.reset([response copy]);
72   [super didReceiveResponse:response];
75 @end
77 namespace {
79 const char kTestFile[] = "ios/web/test/data/chrome.html";
81 class CRWJSInjectionNetworkClientTest : public testing::Test {
82  public:
83   CRWJSInjectionNetworkClientTest() {}
85   void SetUp() override {
86     // Set up mock original network client proxy.
87     mock_web_proxy_.reset([[OCMockObject
88         niceMockForProtocol:@protocol(CRNNetworkClientProtocol)] retain]);
90     // Set up underlying client to inspect data and responses passed on by
91     // the JS injection client.
92     underlying_client_.reset([[UnderlyingClient alloc] init]);
93     [underlying_client_ setUnderlyingClient:
94         static_cast<id<CRNNetworkClientProtocol>>(mock_web_proxy_)];
96     // Link mock proxy into the JSInjectionNetworkClient.
97     js_injection_client_.reset([[CRWJSInjectionNetworkClient alloc] init]);
98     [js_injection_client_ setUnderlyingClient:underlying_client_];
100     // Load data for testing
101     base::FilePath file_path;
102     ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &file_path));
103     file_path = file_path.AppendASCII(kTestFile);
104     test_data_.reset([[NSData dataWithContentsOfFile:
105         base::SysUTF8ToNSString(file_path.value())] retain]);
106     ASSERT_TRUE(test_data_);
107   }
109   void TearDown() override { EXPECT_OCMOCK_VERIFY(mock_web_proxy_); }
111  protected:
112   // Returns a CRNHTTPURLResponse. If |include_content_length|, header includes
113   // Content-Length set to the length of test_data_.
114   CRNHTTPURLResponse* CreateTestResponse(BOOL include_content_length);
116   // Returns number of times an injected cr_web script tag is found in the
117   // underlying client's loaded data. Script tag should immediately follow
118   // the html start tag, if it exists, or should be injected before any header,
119   // if the first tag is something other than an html start tag.
120   NSUInteger GetScriptTagCount() const;
122   // Checks that if response forwarded to the underlying client has header field
123   // Content-Length, the value matches the length of the data.
124   void ExpectConsistentContentLength();
126   base::scoped_nsobject<CRWJSInjectionNetworkClient> js_injection_client_;
127   base::scoped_nsobject<UnderlyingClient> underlying_client_;
128   base::scoped_nsobject<OCMockObject> mock_web_proxy_;
129   base::scoped_nsobject<NSData> test_data_;
132 CRNHTTPURLResponse* CRWJSInjectionNetworkClientTest::CreateTestResponse(
133     BOOL include_content_length) {
134   NSMutableDictionary *headers = [NSMutableDictionary
135       dictionaryWithDictionary:@{ @"Content-Type" : @"text/html" }];
136   if (include_content_length) {
137     headers[@"Content-Length"] = @([test_data_ length]).stringValue;
138   }
139   return [[CRNHTTPURLResponse alloc]
140       initWithURL:[NSURL URLWithString:@"http://testjsinjection.html"]
141        statusCode:200
142       HTTPVersion:@"HTTP/1.1"
143      headerFields:headers];
146 NSUInteger CRWJSInjectionNetworkClientTest::GetScriptTagCount() const {
147   base::scoped_nsobject<NSString> data_string(
148       [[NSString alloc] initWithData:[underlying_client_ loadedData]
149                             encoding:NSUTF8StringEncoding]);
150   NSRegularExpression* script_tag_reg_exp = [NSRegularExpression
151       regularExpressionWithPattern:@"(^|<html>)<script src="
152                                     "\"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-"
153                                     "[0-9a-f]{4}-[0-9a-f]{12}+_crweb\\.js\" "
154                                     "charset=\"utf-8\"></script>"
155                            options:NSRegularExpressionCaseInsensitive
156                              error:nil];
157   return [script_tag_reg_exp
158       numberOfMatchesInString:data_string
159                       options:0
160                         range:NSMakeRange(0, [data_string length])];
163 void CRWJSInjectionNetworkClientTest::ExpectConsistentContentLength() {
164   NSData* forwarded_data = [underlying_client_ loadedData];
165   NSDictionary* output_headers =
166       [static_cast<NSHTTPURLResponse*>([underlying_client_ receivedResponse])
167           allHeaderFields];
168   ASSERT_TRUE(output_headers);
169   NSInteger content_length = [output_headers[@"Content-Length"] integerValue];
170   if (content_length) {
171     EXPECT_EQ(static_cast<NSInteger>([forwarded_data length]),
172         (content_length));
173   }
176 }  // namespace
178 #pragma mark - Tests
180 // Tests injection where response header has Content-Length. Checks that
181 // Content-Length is updated to match new size of data.
182 TEST_F(CRWJSInjectionNetworkClientTest, InjectionWithContentLength) {
183   base::scoped_nsobject<CRNHTTPURLResponse> test_response(
184       CreateTestResponse(YES));
185   [js_injection_client_ didReceiveResponse:test_response];
186   [js_injection_client_ didLoadData:test_data_];
187   [js_injection_client_ didFinishLoading];
189   EXPECT_EQ(1u, GetScriptTagCount());
190   ExpectConsistentContentLength();
193 // Tests injection where response header does not have Content-Length.
194 TEST_F(CRWJSInjectionNetworkClientTest, InjectionWithoutContentLength) {
195   base::scoped_nsobject<CRNHTTPURLResponse> test_response(
196       CreateTestResponse(NO));
197   [js_injection_client_ didReceiveResponse:test_response];
198   [js_injection_client_ didLoadData:test_data_];
199   [js_injection_client_ didFinishLoading];
201   EXPECT_EQ(1u, GetScriptTagCount());
202   ExpectConsistentContentLength();
205 // Tests that injection occurs at the beginning of the file if data has no html
206 // start tag but does have other tags within the first 1KB.
207 TEST_F(CRWJSInjectionNetworkClientTest, MissingHTMLTag) {
208   base::scoped_nsobject<NSString> test_string(
209       [[NSString alloc] initWithData:test_data_
210                             encoding:NSUTF8StringEncoding]);
211   NSData* truncated_data =
212       [[test_string stringByReplacingOccurrencesOfString:@"<html>"
213                                               withString:@""]
214           dataUsingEncoding:NSUTF8StringEncoding];
215   test_data_.reset([truncated_data retain]);
216   ASSERT_TRUE(test_data_);
218   base::scoped_nsobject<CRNHTTPURLResponse> test_response(
219       CreateTestResponse(YES));
220   [js_injection_client_ didReceiveResponse:test_response];
221   [js_injection_client_ didLoadData:test_data_];
222   [js_injection_client_ didFinishLoading];
224   EXPECT_EQ(1u, GetScriptTagCount());
225   ExpectConsistentContentLength();
228 // Tests that injection occurs just following the html tag when there is < 1KB
229 // of padding preceding the html start tag.
230 TEST_F(CRWJSInjectionNetworkClientTest, LessThan1KBBeforeHTMLTag) {
231   base::scoped_nsobject<NSString> test_string(
232       [[NSString alloc] initWithData:test_data_
233                             encoding:NSUTF8StringEncoding]);
234   NSData* padded_data = [[GetLongString(900u)
235       stringByAppendingString:test_string]
236           dataUsingEncoding:NSUTF8StringEncoding];
237   test_data_.reset([padded_data retain]);
238   ASSERT_TRUE(test_data_);
240   base::scoped_nsobject<CRNHTTPURLResponse> test_response(
241       CreateTestResponse(YES));
242   [js_injection_client_ didReceiveResponse:test_response];
243   [js_injection_client_ didLoadData:test_data_];
244   [js_injection_client_ didFinishLoading];
246   EXPECT_EQ(1u, GetScriptTagCount());
247   ExpectConsistentContentLength();
250 TEST_F(CRWJSInjectionNetworkClientTest, PaddedMissingHTMLTag) {
251   base::scoped_nsobject<NSString> test_string(
252       [[NSString alloc] initWithData:test_data_
253                             encoding:NSUTF8StringEncoding]);
254   test_string.reset(
255       [[test_string stringByReplacingOccurrencesOfString:@"<html>"
256                                               withString:@""] retain]);
257   NSData* padded_data = [[GetLongString(900u)
258       stringByAppendingString:test_string]
259             dataUsingEncoding:NSUTF8StringEncoding];
260   test_data_.reset([padded_data retain]);
261   ASSERT_TRUE(test_data_);
263   base::scoped_nsobject<CRNHTTPURLResponse> test_response(
264       CreateTestResponse(YES));
265   [js_injection_client_ didReceiveResponse:test_response];
266   [js_injection_client_ didLoadData:test_data_];
267   [js_injection_client_ didFinishLoading];
269   EXPECT_EQ(1u, GetScriptTagCount());
270   ExpectConsistentContentLength();
273 // Tests scenario in which data is loaded one byte at a time, as might occur
274 // under a slow connection.
275 TEST_F(CRWJSInjectionNetworkClientTest, FragmentedDataLoad) {
276   base::scoped_nsobject<CRNHTTPURLResponse> test_response(
277       CreateTestResponse(YES));
278   [js_injection_client_ didReceiveResponse:test_response];
279   // Load data one byte at a time.
280   for (NSUInteger i = 0; i < [test_data_ length]; i++) {
281     [js_injection_client_ didLoadData:
282         [test_data_ subdataWithRange:NSMakeRange(i, 1u)]];
283   }
284   [js_injection_client_ didFinishLoading];
286   EXPECT_EQ(1u, GetScriptTagCount());
287   ExpectConsistentContentLength();