| 132 |
132 |
|
);
|
| 133 |
133 |
|
}
|
| 134 |
134 |
|
|
|
135 |
+ |
/// The endpoint answers 204 whether or not anything is recorded, so every
|
|
136 |
+ |
/// other test here passes with the reports going nowhere. That is exactly
|
|
137 |
+ |
/// what happened in production: `RUST_LOG` is unset, the default filter
|
|
138 |
+ |
/// named only the crate path, and `csp_violation` matched no directive, so
|
|
139 |
+ |
/// the deployed control discarded every report it accepted.
|
|
140 |
+ |
#[tokio::test]
|
|
141 |
+ |
async fn default_filter_actually_records_a_violation() {
|
|
142 |
+ |
use std::io::Write;
|
|
143 |
+ |
use std::sync::{Arc, Mutex};
|
|
144 |
+ |
use tracing_subscriber::layer::SubscriberExt;
|
|
145 |
+ |
|
|
146 |
+ |
#[derive(Clone)]
|
|
147 |
+ |
struct Buffer(Arc<Mutex<Vec<u8>>>);
|
|
148 |
+ |
impl Write for Buffer {
|
|
149 |
+ |
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
150 |
+ |
self.0.lock().unwrap().extend_from_slice(buf);
|
|
151 |
+ |
Ok(buf.len())
|
|
152 |
+ |
}
|
|
153 |
+ |
fn flush(&mut self) -> std::io::Result<()> {
|
|
154 |
+ |
Ok(())
|
|
155 |
+ |
}
|
|
156 |
+ |
}
|
|
157 |
+ |
impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for Buffer {
|
|
158 |
+ |
type Writer = Self;
|
|
159 |
+ |
fn make_writer(&'a self) -> Self::Writer {
|
|
160 |
+ |
self.clone()
|
|
161 |
+ |
}
|
|
162 |
+ |
}
|
|
163 |
+ |
|
|
164 |
+ |
let buffer = Buffer(Arc::new(Mutex::new(Vec::new())));
|
|
165 |
+ |
let subscriber = tracing_subscriber::registry()
|
|
166 |
+ |
.with(tracing_subscriber::EnvFilter::new(
|
|
167 |
+ |
crate::DEFAULT_LOG_FILTER,
|
|
168 |
+ |
))
|
|
169 |
+ |
.with(
|
|
170 |
+ |
tracing_subscriber::fmt::layer()
|
|
171 |
+ |
.with_ansi(false)
|
|
172 |
+ |
.with_writer(buffer.clone()),
|
|
173 |
+ |
);
|
|
174 |
+ |
|
|
175 |
+ |
let body = br#"{"csp-report":{"document-uri":"https://makenot.work/","violated-directive":"style-src","blocked-uri":"inline"}}"#;
|
|
176 |
+ |
tracing::subscriber::with_default(subscriber, || {
|
|
177 |
+ |
log_violation(
|
|
178 |
+ |
&serde_json::from_slice::<Value>(body).unwrap()["csp-report"],
|
|
179 |
+ |
"report-uri",
|
|
180 |
+ |
);
|
|
181 |
+ |
});
|
|
182 |
+ |
|
|
183 |
+ |
let logged = String::from_utf8(buffer.0.lock().unwrap().clone()).unwrap();
|
|
184 |
+ |
assert!(
|
|
185 |
+ |
logged.contains("CSP violation reported"),
|
|
186 |
+ |
"the default filter dropped the violation; nothing would reach prod logs. got: {logged:?}"
|
|
187 |
+ |
);
|
|
188 |
+ |
assert!(
|
|
189 |
+ |
logged.contains("style-src"),
|
|
190 |
+ |
"directive missing: {logged:?}"
|
|
191 |
+ |
);
|
|
192 |
+ |
}
|
|
193 |
+ |
|
| 135 |
194 |
|
#[test]
|
| 136 |
195 |
|
fn truncates_long_fields_on_a_char_boundary() {
|
| 137 |
196 |
|
let long = "\u{e9}".repeat(MAX_FIELD_LEN);
|