| // Copyright 2016 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cobalt/dom/csp_violation_reporter.h" |
| |
| #include "base/hash.h" |
| #include "base/json/json_writer.h" |
| #include "base/values.h" |
| #include "cobalt/dom/document.h" |
| #include "cobalt/dom/html_element_context.h" |
| #include "cobalt/dom/security_policy_violation_event.h" |
| #include "cobalt/network/net_poster.h" |
| #include "cobalt/script/global_environment.h" |
| #include "googleurl/src/gurl.h" |
| |
| namespace cobalt { |
| namespace dom { |
| |
| namespace { |
| |
| // Fields in the JSON violation report. |
| const char kDocumentUri[] = "document-uri"; |
| const char kReferrer[] = "referrer"; |
| const char kViolatedDirective[] = "violated-directive"; |
| const char kEffectiveDirective[] = "effective-directive"; |
| const char kOriginalPolicy[] = "original-policy"; |
| const char kBlockedUri[] = "blocked-uri"; |
| const char kSourceFile[] = "source-file"; |
| const char kLineNumber[] = "line-number"; |
| const char kColumnNumber[] = "column-number"; |
| const char kStatusCode[] = "status-code"; |
| const char kCspReport[] = "csp-report"; |
| const char kCspReportContentType[] = "application/csp-report"; |
| |
| struct ViolationEvent { |
| ViolationEvent() : line_number(0), column_number(0), status_code(0) {} |
| std::string document_uri; |
| std::string blocked_uri; |
| std::string referrer; |
| std::string violated_directive; |
| std::string effective_directive; |
| std::string original_policy; |
| std::string source_file; |
| int line_number; |
| int column_number; |
| int status_code; |
| }; |
| |
| std::string StripUrlForUseInReport(const GURL& origin_url, const GURL& url) { |
| // https://www.w3.org/TR/CSP/#strip-uri-for-reporting |
| if (!url.is_valid()) { |
| return std::string(); |
| } |
| if (!url.IsStandard() || url.SchemeIsFile()) { |
| // 1. Non-standard scheme. |
| return url.scheme(); |
| } |
| if (origin_url.GetOrigin() != url.GetOrigin()) { |
| // 2. Mismatched origin. |
| return url.GetOrigin().spec(); |
| } else { |
| // 3. Same origin- strip username, password and ref. |
| GURL::Replacements replacements; |
| replacements.ClearUsername(); |
| replacements.ClearPassword(); |
| replacements.ClearRef(); |
| return url.ReplaceComponents(replacements).spec(); |
| } |
| } |
| |
| void GatherSecurityPolicyViolationEventData( |
| const Document* document, const csp::ViolationInfo& violation_info, |
| ViolationEvent* event_data) { |
| event_data->document_uri = |
| StripUrlForUseInReport(document->url_as_gurl(), document->url_as_gurl()); |
| event_data->blocked_uri = StripUrlForUseInReport(document->url_as_gurl(), |
| violation_info.blocked_url); |
| // TODO: Implement Document referrer, if needed. |
| event_data->referrer = ""; |
| event_data->violated_directive = violation_info.directive_text; |
| event_data->effective_directive = violation_info.effective_directive; |
| event_data->original_policy = violation_info.header; |
| |
| script::GlobalEnvironment* global_environment = |
| document->html_element_context()->script_runner()->GetGlobalEnvironment(); |
| const std::vector<script::StackFrame>& stack_trace = |
| global_environment->GetStackTrace(1); |
| if (stack_trace.size() > 0) { |
| event_data->line_number = stack_trace[0].line_number; |
| event_data->column_number = stack_trace[0].column_number; |
| event_data->source_file = StripUrlForUseInReport( |
| document->url_as_gurl(), GURL(stack_trace[0].source_url)); |
| } |
| |
| // TODO: Set the status code if the document origin is non-secure. |
| event_data->status_code = 0; |
| } |
| |
| } // namespace |
| |
| CspViolationReporter::CspViolationReporter( |
| Document* document, const network_bridge::PostSender& post_sender) |
| : post_sender_(post_sender), |
| message_loop_proxy_(base::MessageLoopProxy::current()), |
| document_(document) {} |
| |
| CspViolationReporter::~CspViolationReporter() {} |
| |
| // https://www.w3.org/TR/CSP2/#violation-reports |
| void CspViolationReporter::Report(const csp::ViolationInfo& violation_info) { |
| if (base::MessageLoopProxy::current() != message_loop_proxy_) { |
| message_loop_proxy_->PostTask( |
| FROM_HERE, base::Bind(&CspViolationReporter::Report, |
| base::Unretained(this), violation_info)); |
| return; |
| } |
| |
| DLOG(INFO) << violation_info.console_message; |
| ViolationEvent violation_data; |
| GatherSecurityPolicyViolationEventData(document_, violation_info, |
| &violation_data); |
| document_->DispatchEvent(new SecurityPolicyViolationEvent( |
| violation_data.document_uri, violation_data.referrer, |
| violation_data.blocked_uri, violation_data.violated_directive, |
| violation_data.effective_directive, violation_data.original_policy, |
| violation_data.source_file, violation_data.status_code, |
| violation_data.line_number, violation_data.column_number)); |
| |
| if (violation_info.endpoints.empty() || post_sender_.is_null()) { |
| return; |
| } |
| |
| // We need to be careful here when deciding what information to send to the |
| // report-uri. Currently, we send only the current document's URL and the |
| // directive that was violated. The document's URL is safe to send because |
| // it's the document itself that's requesting that it be sent. You could |
| // make an argument that we shouldn't send HTTPS document URLs to HTTP |
| // report-uris (for the same reasons that we supress the Referer in that |
| // case), but the Referer is sent implicitly whereas this request is only |
| // sent explicitly. As for which directive was violated, that's pretty |
| // harmless information. |
| |
| scoped_ptr<base::DictionaryValue> csp_report(new base::DictionaryValue()); |
| csp_report->SetString(kDocumentUri, violation_data.document_uri); |
| csp_report->SetString(kReferrer, violation_data.referrer); |
| csp_report->SetString(kViolatedDirective, violation_data.violated_directive); |
| csp_report->SetString(kEffectiveDirective, |
| violation_data.effective_directive); |
| csp_report->SetString(kOriginalPolicy, violation_data.original_policy); |
| csp_report->SetString(kBlockedUri, violation_data.blocked_uri); |
| if (!violation_data.source_file.empty() && violation_data.line_number != 0) { |
| csp_report->SetString(kSourceFile, violation_data.source_file); |
| csp_report->SetInteger(kLineNumber, violation_data.line_number); |
| csp_report->SetInteger(kColumnNumber, violation_data.column_number); |
| } |
| csp_report->SetInteger(kStatusCode, violation_data.status_code); |
| |
| scoped_ptr<base::DictionaryValue> report_object(new base::DictionaryValue()); |
| report_object->Set(kCspReport, csp_report.release()); |
| |
| std::string json_string; |
| base::JSONWriter::Write(report_object.get(), &json_string); |
| |
| SendViolationReports(violation_info.endpoints, json_string); |
| } |
| |
| void CspViolationReporter::SendViolationReports( |
| const std::vector<std::string>& endpoints, const std::string& report) { |
| uint32 report_hash = base::Hash(report); |
| if (violation_reports_sent_.find(report_hash) != |
| violation_reports_sent_.end()) { |
| return; |
| } |
| violation_reports_sent_.insert(report_hash); |
| const GURL& origin_url = document_->url_as_gurl(); |
| for (std::vector<std::string>::const_iterator it = endpoints.begin(); |
| it != endpoints.end(); ++it) { |
| GURL resolved_endpoint = origin_url.Resolve(*it); |
| post_sender_.Run(resolved_endpoint, kCspReportContentType, report); |
| } |
| } |
| |
| } // namespace dom |
| } // namespace cobalt |