use std::collections::HashMap;
use std::sync::Arc;
use kingfisher_core::{BlobId, Location};
use kingfisher_rules::{Confidence, Rule};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct Finding {
#[serde(skip_serializing)]
#[schemars(skip)]
pub rule: Arc<Rule>,
pub rule_id: String,
pub rule_name: String,
pub secret: String,
pub location: FindingLocation,
pub confidence: Confidence,
pub entropy: f32,
pub fingerprint: u64,
#[serde(default)]
pub captures: HashMap<String, String>,
pub is_base64_encoded: bool,
pub blob_id: BlobId,
}
impl Finding {
pub fn rule(&self) -> &Rule {
&self.rule
}
pub fn is_high_confidence(&self) -> bool {
self.confidence == Confidence::High
}
pub fn line(&self) -> usize {
self.location.line
}
pub fn column(&self) -> usize {
self.location.column
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct FindingLocation {
pub start_offset: usize,
pub end_offset: usize,
pub line: usize,
pub column: usize,
pub end_line: usize,
pub end_column: usize,
}
impl FindingLocation {
pub fn from_location(location: &Location) -> Self {
let source_span = location.resolved_source_span();
Self {
start_offset: location.offset_span.start,
end_offset: location.offset_span.end,
line: source_span.start.line,
column: source_span.start.column,
end_line: source_span.end.line,
end_column: source_span.end.column,
}
}
pub fn from_offsets(start: usize, end: usize) -> Self {
Self {
start_offset: start,
end_offset: end,
line: 0,
column: 0,
end_line: 0,
end_column: 0,
}
}
pub fn new(
start_offset: usize,
end_offset: usize,
line: usize,
column: usize,
end_line: usize,
end_column: usize,
) -> Self {
Self { start_offset, end_offset, line, column, end_line, end_column }
}
}
impl From<&Location> for FindingLocation {
fn from(location: &Location) -> Self {
Self::from_location(location)
}
}
#[derive(Debug, Clone, JsonSchema)]
pub struct SerializableCapture {
pub name: Option<String>,
pub match_number: i32,
pub start: usize,
pub end: usize,
pub value: String,
}
impl SerializableCapture {
pub fn raw_value(&self) -> &str {
&self.value
}
pub fn display_value(&self) -> std::borrow::Cow<'_, str> {
std::borrow::Cow::Borrowed(&self.value)
}
}
impl serde::Serialize for SerializableCapture {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("SerializableCapture", 5)?;
state.serialize_field("name", &self.name)?;
state.serialize_field("match_number", &self.match_number)?;
state.serialize_field("start", &self.start)?;
state.serialize_field("end", &self.end)?;
let value = self.display_value();
state.serialize_field("value", &value)?;
state.end()
}
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct SerializableCaptures {
#[schemars(with = "Vec<SerializableCapture>")]
pub captures: SmallVec<[SerializableCapture; 2]>,
}
impl SerializableCaptures {
pub fn from_captures(
captures: ®ex::bytes::Captures,
_input: &[u8],
re: ®ex::bytes::Regex,
) -> Self {
let mut serialized_captures: SmallVec<[SerializableCapture; 2]> = SmallVec::new();
let capture_names: SmallVec<[Option<String>; 4]> =
re.capture_names().map(|name| name.map(str::to_owned)).collect();
if captures.len() > 1 {
for i in 1..captures.len() {
if let Some(cap) = captures.get(i) {
let raw_value = String::from_utf8_lossy(cap.as_bytes());
let value = raw_value.into_owned();
let name = capture_names.get(i).and_then(Clone::clone);
serialized_captures.push(SerializableCapture {
name,
match_number: i32::try_from(i).unwrap_or(0),
start: cap.start(),
end: cap.end(),
value,
});
}
}
} else if captures.len() == 1 {
if let Some(cap) = captures.get(0) {
let raw_value = String::from_utf8_lossy(cap.as_bytes());
let value = raw_value.into_owned();
let name = capture_names.first().and_then(Clone::clone);
serialized_captures.push(SerializableCapture {
name,
match_number: 0,
start: cap.start(),
end: cap.end(),
value,
});
}
}
SerializableCaptures { captures: serialized_captures }
}
}