Module: Otto::Security::CSP::Parser
- Defined in:
- lib/otto/security/csp/parser.rb
Overview
Parses inbound Content-Security-Policy violation report bodies into a list of normalized Report objects.
Handles BOTH standardized wire formats:
- Legacy
application/csp-report— a single JSON object{"csp-report": { ... }}. - Reporting API
application/reports+json— a JSON ARRAY of{"type": "csp-violation", "body": { ... }}entries (a single un-wrapped object is tolerated too).
The parser keys off the JSON SHAPE rather than trusting the declared
Content-Type, because browsers and intermediaries are inconsistent
about the header. The content_type argument is accepted for future use
and symmetry with the middleware but is not currently required to
disambiguate.
It is intentionally TOTAL: malformed JSON, an unexpected top-level type, or entries that are not CSP violations yield an empty array rather than raising. A violation-report receiver must never fail on hostile input.
Class Method Summary collapse
-
.extract_from_object(data) ⇒ Array<Hash>
A single top-level object in either the legacy
{"csp-report": {...}}envelope or a lone Reporting API{"type":..., "body": {...}}object. -
.extract_from_reporting_api(entries) ⇒ Array<Hash>
Reporting API batch: an array of report envelopes.
-
.extract_raw_reports(data, _content_type = nil) ⇒ Array<Hash>
Pull the per-violation field hashes out of either wire format.
-
.parse(body, content_type = nil) ⇒ Array<Otto::Security::CSP::Report>
Parse a raw report body into normalized reports.
-
.safe_json_parse(body) ⇒ Object?
Parse JSON, swallowing the errors a hostile/garbled body can throw.
Class Method Details
.extract_from_object(data) ⇒ Array<Hash>
A single top-level object in either the legacy {"csp-report": {...}}
envelope or a lone Reporting API {"type":..., "body": {...}} object.
105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/otto/security/csp/parser.rb', line 105 def extract_from_object(data) if data['csp-report'].is_a?(Hash) [data['csp-report']] elsif data['body'].is_a?(Hash) && (data['type'].nil? || data['type'] == 'csp-violation') # Mirror extract_from_reporting_api: accept a lone csp-violation (or # untyped) envelope, but skip other single-object report types # (deprecation, intervention, ...). [data['body']] else [] end end |
.extract_from_reporting_api(entries) ⇒ Array<Hash>
Reporting API batch: an array of report envelopes. Keep entries that are (or are untyped but shaped like) CSP violations and carry a body.
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/otto/security/csp/parser.rb', line 84 def extract_from_reporting_api(entries) entries.filter_map do |entry| next unless entry.is_a?(Hash) body = entry['body'] next unless body.is_a?(Hash) type = entry['type'] # Accept entries explicitly typed csp-violation, or untyped bodies. # Skip other report types (deprecation, intervention, ...). next unless type.nil? || type == 'csp-violation' body end end |
.extract_raw_reports(data, _content_type = nil) ⇒ Array<Hash>
Pull the per-violation field hashes out of either wire format.
68 69 70 71 72 73 74 75 76 77 |
# File 'lib/otto/security/csp/parser.rb', line 68 def extract_raw_reports(data, _content_type = nil) case data when Array extract_from_reporting_api(data) when Hash extract_from_object(data) else [] end end |
.parse(body, content_type = nil) ⇒ Array<Otto::Security::CSP::Report>
Parse a raw report body into normalized reports.
42 43 44 45 46 47 48 49 50 51 |
# File 'lib/otto/security/csp/parser.rb', line 42 def parse(body, content_type = nil) return [] if body.nil? || body.empty? data = safe_json_parse(body) return [] if data.nil? extract_raw_reports(data, content_type).filter_map do |raw| Report.from_raw(raw) end end |
.safe_json_parse(body) ⇒ Object?
Parse JSON, swallowing the errors a hostile/garbled body can throw.
57 58 59 60 61 |
# File 'lib/otto/security/csp/parser.rb', line 57 def safe_json_parse(body) JSON.parse(body) rescue JSON::ParserError, EncodingError nil end |