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"
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
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,
38 NSMutableString* 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
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];
57 if (range.location != NSNotFound) {
58 [header insertString:value atIndex:NSMaxRange(range)];
62 // Else, there is no |directive| and no 'default-src', nothing to do.
67 @implementation CRWCspNetworkClient
69 - (void)didReceiveResponse:(NSURLResponse*)response {
70 if (![response isKindOfClass:[NSHTTPURLResponse class]]) {
71 [super didReceiveResponse:response];
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) {
93 // No CSP header, return early.
94 [super didReceiveResponse:response];
98 if (!g_connect_value) {
99 g_connect_value = [[NSString alloc]
100 initWithFormat:@" %@ %s", kSelf, web::kURLForVerification];
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)
114 base::scoped_nsobject<NSMutableString> cspHeader(
115 [[NSMutableString alloc] initWithString:header]);
117 RelaxCspValue(kConnectSrc, g_connect_value, cspHeader);
119 RelaxCspValue(kFrameSrc, kFrameValue, cspHeader);
120 header.reset([cspHeader retain]);
123 DCHECK(![outputHeaders objectForKey:key]);
124 [outputHeaders setObject:header forKey:key];
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];