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