Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(680)

Side by Side Diff: content/browser/frame_host/ancestor_throttle.cc

Issue 1617043002: Introduce AncestorThrottle, which will process 'X-Frame-Options' headers. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@block-response
Patch Set: DCHECK. Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 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.
4
5 #include "content/browser/frame_host/ancestor_throttle.h"
6
7 #include "base/strings/string_split.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "content/browser/frame_host/frame_tree.h"
11 #include "content/browser/frame_host/frame_tree_node.h"
12 #include "content/browser/frame_host/navigation_handle_impl.h"
13 #include "content/public/browser/browser_thread.h"
14 #include "content/public/browser/navigation_handle.h"
15 #include "content/public/browser/navigation_throttle.h"
16 #include "content/public/common/console_message_level.h"
17 #include "net/http/http_response_headers.h"
18 #include "url/origin.h"
19
20 namespace content {
21
22 // static
23 std::unique_ptr<NavigationThrottle> AncestorThrottle::MaybeCreateThrottleFor(
24 NavigationHandle* handle) {
25 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
26
27 if (handle->IsInMainFrame())
28 return nullptr;
29
30 return std::unique_ptr<NavigationThrottle>(new AncestorThrottle(handle));
31 }
32
33 AncestorThrottle::AncestorThrottle(NavigationHandle* handle)
34 : NavigationThrottle(handle) {}
35
36 AncestorThrottle::~AncestorThrottle() {}
37
38 NavigationThrottle::ThrottleCheckResult
39 AncestorThrottle::WillProcessResponse() {
40 DCHECK(!navigation_handle()->IsInMainFrame());
41
42 NavigationHandleImpl* handle =
43 static_cast<NavigationHandleImpl*>(navigation_handle());
44
45 std::string header_value;
46 HeaderDisposition disposition =
47 ParseHeader(handle->GetResponseHeaders(), &header_value);
48 switch (disposition) {
49 case HeaderDisposition::CONFLICT:
50 ParseError(header_value, disposition);
51 return NavigationThrottle::BLOCK_RESPONSE;
52
53 case HeaderDisposition::INVALID:
54 ParseError(header_value, disposition);
55 // TODO(mkwst): Consider failing here.
56 return NavigationThrottle::PROCEED;
57
58 case HeaderDisposition::DENY:
59 ConsoleError(disposition);
60 return NavigationThrottle::BLOCK_RESPONSE;
61
62 case HeaderDisposition::SAMEORIGIN: {
63 url::Origin current_origin(navigation_handle()->GetURL());
64 url::Origin top_origin =
65 handle->frame_tree_node()->frame_tree()->root()->current_origin();
66 if (top_origin.IsSameOriginWith(current_origin))
67 return NavigationThrottle::PROCEED;
68 ConsoleError(disposition);
69 return NavigationThrottle::BLOCK_RESPONSE;
70 }
71
72 case HeaderDisposition::NONE:
73 case HeaderDisposition::BYPASS:
74 case HeaderDisposition::ALLOWALL:
75 return NavigationThrottle::PROCEED;
76 }
77 NOTREACHED();
78 return NavigationThrottle::BLOCK_RESPONSE;
79 }
80
81 void AncestorThrottle::ParseError(const std::string& value,
82 HeaderDisposition disposition) {
83 DCHECK(disposition == HeaderDisposition::CONFLICT ||
84 disposition == HeaderDisposition::INVALID);
85
86 std::string message;
87 if (disposition == HeaderDisposition::CONFLICT) {
88 message = base::StringPrintf(
89 "Refused to display '%s' in a frame because it set multiple "
90 "'X-Frame-Options' headers with conflicting values "
91 "('%s'). Falling back to 'deny'.",
92 navigation_handle()->GetURL().spec().c_str(), value.c_str());
93 } else {
94 message = base::StringPrintf(
95 "Invalid 'X-Frame-Options' header encountered when loading '%s': "
96 "'%s' is not a recognized directive. The header will be ignored.",
97 navigation_handle()->GetURL().spec().c_str(), value.c_str());
98 }
99
100 // Log a console error in the parent of the current RenderFrameHost (as
101 // the current RenderFrameHost itself doesn't yet have a document).
102 navigation_handle()->GetRenderFrameHost()->GetParent()->AddMessageToConsole(
103 CONSOLE_MESSAGE_LEVEL_ERROR, message);
104 }
105
106 void AncestorThrottle::ConsoleError(HeaderDisposition disposition) {
107 DCHECK(disposition == HeaderDisposition::DENY ||
108 disposition == HeaderDisposition::SAMEORIGIN);
109 std::string message = base::StringPrintf(
110 "Refused to display '%s' in a frame because it set 'X-Frame-Options' "
111 "to '%s'.",
112 navigation_handle()->GetURL().spec().c_str(),
113 disposition == HeaderDisposition::DENY ? "deny" : "sameorigin");
114
115 // Log a console error in the parent of the current RenderFrameHost (as
116 // the current RenderFrameHost itself doesn't yet have a document).
117 navigation_handle()->GetRenderFrameHost()->GetParent()->AddMessageToConsole(
118 CONSOLE_MESSAGE_LEVEL_ERROR, message);
119 }
120
121 AncestorThrottle::HeaderDisposition AncestorThrottle::ParseHeader(
122 const net::HttpResponseHeaders* headers,
123 std::string* header_value) {
124 DCHECK(header_value);
125 if (!headers)
126 return HeaderDisposition::NONE;
127
128 // Process the 'X-Frame-Options header as per Section 2 of RFC7034:
129 // https://tools.ietf.org/html/rfc7034#section-2
130 //
131 // Note that we do not support the 'ALLOW-FROM' value, and we special-case
132 // the invalid "ALLOWALL" value due to its prevalance in the wild.
133 HeaderDisposition result = HeaderDisposition::NONE;
134 size_t iter = 0;
135 std::string value;
136 while (headers->EnumerateHeader(&iter, "x-frame-options", &value)) {
137 HeaderDisposition current = HeaderDisposition::INVALID;
138
139 base::StringPiece trimmed =
140 base::TrimWhitespaceASCII(value, base::TRIM_ALL);
141 if (!header_value->empty())
142 header_value->append(", ");
143 header_value->append(trimmed.as_string());
144
145 if (base::LowerCaseEqualsASCII(trimmed, "deny"))
146 current = HeaderDisposition::DENY;
147 else if (base::LowerCaseEqualsASCII(trimmed, "allowall"))
148 current = HeaderDisposition::ALLOWALL;
149 else if (base::LowerCaseEqualsASCII(trimmed, "sameorigin"))
150 current = HeaderDisposition::SAMEORIGIN;
151 else
152 current = HeaderDisposition::INVALID;
153
154 if (result == HeaderDisposition::NONE)
155 result = current;
156 else if (result != current)
157 result = HeaderDisposition::CONFLICT;
158 }
159
160 // If 'X-Frame-Options' would potentially block the response, check whether
161 // the 'frame-ancestors' CSP directive should take effect instead. See
162 // https://www.w3.org/TR/CSP/#frame-ancestors-and-frame-options
163 if (result != HeaderDisposition::NONE &&
164 result != HeaderDisposition::ALLOWALL) {
165 iter = 0;
166 value = std::string();
167 while (headers->EnumerateHeader(&iter, "content-security-policy", &value)) {
168 // TODO(mkwst): 'frame-ancestors' is currently handled in Blink. We should
169 // handle it here instead. Until then, don't block the request, and let
170 // Blink handle it. https://crbug.com/555418
171 std::vector<std::string> tokens = base::SplitString(
172 value, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
173 if (std::count_if(tokens.begin(), tokens.end(), [](std::string token) {
174 // The trailing " " is intentional; we'd otherwise match
175 // "frame-ancestors-is-not-this-directive".
176 return base::StartsWith(token, "frame-ancestors ",
177 base::CompareCase::INSENSITIVE_ASCII);
178 })) {
179 return HeaderDisposition::BYPASS;
180 }
181 }
182 }
183 return result;
184 }
185
186 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698