From 2a8bb9c3613a189a33fb129bcf55a14d8edf1ac7 Mon Sep 17 00:00:00 2001 From: Mick Grove Date: Mon, 9 Feb 2026 12:27:03 -0800 Subject: [PATCH] v1.80.0 --- src/direct_revoke.rs | 48 +++++++++++++++++++++++++++++++++++++------- src/reporter.rs | 41 ++++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 8 deletions(-) diff --git a/src/direct_revoke.rs b/src/direct_revoke.rs index 4af8b78..db257b1 100644 --- a/src/direct_revoke.rs +++ b/src/direct_revoke.rs @@ -219,6 +219,9 @@ fn render_extractor( globals: &Object, ) -> Result { let render = |template_str: &str| -> Result { + if !template_str.contains("{{") && !template_str.contains("{%") { + return Ok(template_str.to_string()); + } let template = parser .parse(template_str) .map_err(|e| anyhow!("Failed to parse extractor template: {}", e))?; @@ -238,6 +241,15 @@ fn render_extractor( } } +fn truncate_with_ellipsis(input: &str, max_chars: usize) -> String { + let truncated: String = input.chars().take(max_chars).collect(); + if input.chars().count() > max_chars { + format!("{}...", truncated) + } else { + input.to_string() + } +} + /// Extract a value from an HTTP response using the specified extractor. fn extract_value_from_response( extractor: &ResponseExtractor, @@ -338,10 +350,11 @@ async fn execute_http_revocation( let headers = response.headers().clone(); let body = response.text().await.context("Failed to read response body")?; - debug!("Revocation response status: {}", status); - debug!("Revocation response body: {}", body); + let display_body = truncate_with_ellipsis(&body, 500); + let body_len = body.chars().count(); - let display_body = if body.len() > 500 { format!("{}...", &body[..500]) } else { body.clone() }; + debug!("Revocation response status: {}", status); + debug!("Revocation response body (len={}): {}", body_len, display_body); let matchers = http_revocation .request @@ -404,8 +417,11 @@ async fn execute_revocation_step( .await .with_context(|| format!("Failed to read response body for {}", step_name))?; + let display_body = truncate_with_ellipsis(&body, 500); + let body_len = body.chars().count(); + debug!("Step {} response status: {}", step_number, status); - debug!("Step {} response body: {}", step_number, body); + debug!("Step {} response body (len={}): {}", step_number, body_len, display_body); // Extract variables from the response if configured if let Some(extractors) = &step.extract { @@ -437,7 +453,7 @@ async fn execute_revocation_step( step_number, e, status, - body + display_body )); } } @@ -481,8 +497,7 @@ async fn execute_multi_step_revocation( if is_final_step { // Final step: validate response to determine success - let display_body = - if body.len() > 500 { format!("{}...", &body[..500]) } else { body.clone() }; + let display_body = truncate_with_ellipsis(&body, 500); let matchers = step .request @@ -1310,4 +1325,23 @@ mod tests { _ => panic!("Expected JsonPath variant"), } } + + // ---- truncate_with_ellipsis ---- + + #[test] + fn truncate_with_ellipsis_no_truncation() { + let input = "ok"; + let output = truncate_with_ellipsis(input, 500); + assert_eq!(output, input); + } + + #[test] + fn truncate_with_ellipsis_handles_unicode() { + let input = "é".repeat(501); + let output = truncate_with_ellipsis(&input, 500); + + assert!(output.ends_with("...")); + assert_eq!(output.chars().count(), 503); + assert!(output.chars().take(500).all(|ch| ch == 'é')); + } } diff --git a/src/reporter.rs b/src/reporter.rs index bf1cd75..0c0b56b 100644 --- a/src/reporter.rs +++ b/src/reporter.rs @@ -669,7 +669,8 @@ impl DetailsReporter { const MAX_RESPONSE_LENGTH: usize = 512; let truncated_body: String = validation_body_str.chars().take(MAX_RESPONSE_LENGTH).collect(); - let ellipsis = if validation_body_str.len() > MAX_RESPONSE_LENGTH { "..." } else { "" }; + let ellipsis = + if validation_body_str.chars().count() > MAX_RESPONSE_LENGTH { "..." } else { "" }; format!("{}{}", truncated_body, ellipsis) }; @@ -1323,6 +1324,20 @@ mod tests { (report_match, blob_path) } + fn build_validation_response(validation_body: &str, full_response: bool) -> String { + let temp = tempdir().unwrap(); + let datastore = + Arc::new(Mutex::new(findings_store::FindingsStore::new(temp.path().to_path_buf()))); + let reporter = DetailsReporter { datastore, styles: Styles::new(false), only_valid: false }; + + let (report_match, _) = sample_report_match(validation_body, StatusCode::OK.as_u16(), true); + let mut scan_args = sample_scan_args(); + scan_args.full_validation_response = full_response; + + let record = reporter.build_finding_record(&report_match, &scan_args); + record.finding.validation.response + } + #[test] fn build_finding_record_uses_git_blob_path() { let temp = tempdir().unwrap(); @@ -1370,6 +1385,30 @@ mod tests { ); } + #[test] + fn validation_response_truncates_when_flag_off() { + let body = "a".repeat(513); + let response = build_validation_response(&body, false); + assert_eq!(response, format!("{}...", "a".repeat(512))); + } + + #[test] + fn validation_response_full_when_flag_on() { + let body = "a".repeat(513); + let response = build_validation_response(&body, true); + assert_eq!(response, body); + } + + #[test] + fn validation_response_truncation_counts_chars() { + let body = "é".repeat(513); + let response = build_validation_response(&body, false); + + assert!(response.ends_with("...")); + assert_eq!(response.chars().count(), 515); + assert!(response.chars().take(512).all(|ch| ch == 'é')); + } + use super::build_git_urls; #[test]