Fix breakages in https://codereview.chromium.org/1155713003/
[chromium-blink-merge.git] / ios / web / net / clients / crw_csp_network_client.mm
blob8d8d5db59c4b322ee15af4c61e7dd6c550557a4e
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 #import "ios/web/net/clients/crw_csp_network_client.h"
7 #import <Foundation/Foundation.h>
9 #include "base/logging.h"
10 #include "base/mac/scoped_nsobject.h"
11 #import "ios/web/net/crw_url_verifying_protocol_handler.h"
13 namespace {
15 // HTTP headers for the content security policy.
16 NSString* const kCSPHeaders[] {
17   @"Content-Security-Policy", @"Content-Security-Policy-Report-Only",
18       @"X-WebKit-CSP", @"X-WebKit-CSP-Report-Only"
21 NSString* const kDefaultSrc = @"default-src";
22 NSString* const kConnectSrc = @"connect-src";
23 NSString* const kSelf = @"'self'";
24 NSString* const kFrameSrc = @"frame-src";
25 NSString* const kFrameValue = @" crwebinvoke: crwebinvokeimmediate: crwebnull:";
27 // Value of the 'connect-src' directive for the Content Security Policy.
28 // Lazily initialized.
29 NSString* g_connect_value = nil;
31 // Adds |value| (i.e. 'self') to the CSP |directive| (i.e. 'frame-src').
32 // |header| is the value of the 'Content-Security-Policy' header and is modified
33 // by the function.
34 // If |directive| is not in the CSP, the function checks for 'default-src' and
35 // adds |value| there if needed.
36 void RelaxCspValue(NSString* directive,
37                             NSString* value,
38                             NSMutableString* header) {
39   DCHECK(directive);
40   DCHECK(value);
41   DCHECK(header);
42   // The function is sub-optimal if the directive is 'default-src' as we could
43   // skip one of the calls to |-rangeOfString:options:| in that case.
44   // Please consider improving the implementation if you need to support this.
45   DCHECK(![directive isEqualToString:kDefaultSrc]);
47   // If |directive| is already present in |header|, |value| is prepended to the
48   // existing value.
49   NSRange range =
50       [header rangeOfString:directive options:NSCaseInsensitiveSearch];
51   if (range.location == NSNotFound) {
52     // Else, if the 'default-src' directive is present, |value| is prepended to
53     // the existing value of "default-src".
54     range = [header rangeOfString:kDefaultSrc options:NSCaseInsensitiveSearch];
55   }
57   if (range.location != NSNotFound) {
58     [header insertString:value atIndex:NSMaxRange(range)];
59     return;
60   }
62   // Else, there is no |directive| and no 'default-src', nothing to do.
65 }  // namespace
67 @implementation CRWCspNetworkClient
69 - (void)didReceiveResponse:(NSURLResponse*)response {
70   if (![response isKindOfClass:[NSHTTPURLResponse class]]) {
71     [super didReceiveResponse:response];
72     return;
73   }
75   NSHTTPURLResponse* httpResponse = static_cast<NSHTTPURLResponse*>(response);
76   base::scoped_nsobject<NSDictionary> inputHeaders(
77       [[httpResponse allHeaderFields] retain]);
79   // Enumerate the headers and return early if there is nothing to do.
80   bool hasCspHeader = false;
81   for (NSString* key in inputHeaders.get()) {
82     for (size_t i = 0; i < arraysize(kCSPHeaders); ++i) {
83       if ([key caseInsensitiveCompare:kCSPHeaders[i]] == NSOrderedSame) {
84         hasCspHeader = true;
85         break;
86       }
87     }
88     if (hasCspHeader)
89       break;
90   }
92   if (!hasCspHeader) {
93     // No CSP header, return early.
94     [super didReceiveResponse:response];
95     return;
96   }
98   if (!g_connect_value) {
99     g_connect_value = [[NSString alloc]
100         initWithFormat:@" %@ %s", kSelf, web::kURLForVerification];
101   }
103   base::scoped_nsobject<NSMutableDictionary> outputHeaders(
104       [[NSMutableDictionary alloc] init]);
106   // Add some values to the content security policy headers in order to keep the
107   // URL verification and the javascript injection working.
108   for (NSString* key in inputHeaders.get()) {
109     base::scoped_nsobject<NSString> header(
110         [[inputHeaders objectForKey:key] retain]);
111     for (size_t i = 0; i < arraysize(kCSPHeaders); ++i) {
112       if ([key caseInsensitiveCompare:kCSPHeaders[i]] != NSOrderedSame)
113         continue;
114       base::scoped_nsobject<NSMutableString> cspHeader(
115           [[NSMutableString alloc] initWithString:header]);
116       // Fix connect-src.
117       RelaxCspValue(kConnectSrc, g_connect_value, cspHeader);
118       // Fix frame-src.
119       RelaxCspValue(kFrameSrc, kFrameValue, cspHeader);
120       header.reset([cspHeader retain]);
121       break;
122     }
123     DCHECK(![outputHeaders objectForKey:key]);
124     [outputHeaders setObject:header forKey:key];
125   }
127   // Build a new response with |outputHeaders|.
128   base::scoped_nsobject<NSHTTPURLResponse> outResponse(
129       [[NSHTTPURLResponse alloc] initWithURL:[httpResponse URL]
130                                   statusCode:[httpResponse statusCode]
131                                  HTTPVersion:@"HTTP/1.1"
132                                 headerFields:outputHeaders]);
133   [super didReceiveResponse:outResponse];
136 @end