use bstr::BString;
use regex::bytes::Regex;
use schemars::{JsonSchema, Schema, SchemaGenerator};
use serde::{Deserialize, Serialize};
use serde_json::json;
use smallvec::SmallVec;
use std::{borrow::Cow, sync::Arc};
use crate::{snippet::Base64BString, util::intern};
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
pub struct Group(pub Base64BString);
impl Group {
pub fn new(m: regex::bytes::Match<'_>) -> Self {
Self(Base64BString(BString::from(m.as_bytes())))
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct Groups(pub SmallVec<[Group; 1]>);
impl JsonSchema for Groups {
fn schema_name() -> Cow<'static, str> {
"Groups".into()
}
fn json_schema(generator: &mut SchemaGenerator) -> Schema {
let mut schema = Schema::default();
schema.insert("type".to_owned(), json!("array"));
schema.insert("items".to_owned(), generator.subschema_for::<Group>().to_value());
schema
}
}
#[derive(Debug, Clone, JsonSchema)]
pub struct SerializableCapture {
pub name: Option<&'static str>,
pub match_number: i32,
pub start: usize,
pub end: usize,
#[serde(skip_serializing, skip_deserializing)]
pub value: Arc<str>,
}
impl SerializableCapture {
pub fn raw_value(&self) -> &str {
&self.value
}
pub fn display_value(&self) -> std::borrow::Cow<'_, str> {
crate::util::display_value(&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: &Regex) -> Self {
let mut serialized_captures: SmallVec<[SerializableCapture; 2]> = SmallVec::new();
let capture_names: SmallVec<[Option<&'static str>; 4]> =
re.capture_names().map(|name| name.map(intern)).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 raw_shared = Arc::from(raw_value.as_ref());
let name = capture_names.get(i).and_then(|opt| *opt);
serialized_captures.push(SerializableCapture {
name,
match_number: i32::try_from(i).unwrap_or(0),
start: cap.start(),
end: cap.end(),
value: raw_shared,
});
}
}
} else if captures.len() == 1 {
if let Some(cap) = captures.get(0) {
let raw_value = String::from_utf8_lossy(cap.as_bytes());
let raw_shared = Arc::from(raw_value.as_ref());
let name = capture_names.first().and_then(|opt| *opt);
serialized_captures.push(SerializableCapture {
name,
match_number: 0,
start: cap.start(),
end: cap.end(),
value: raw_shared,
});
}
}
SerializableCaptures { captures: serialized_captures }
}
pub fn from_captures_with_secret_group(
captures: ®ex::bytes::Captures,
input: &[u8],
re: &Regex,
betterleaks_secret_group: Option<usize>,
) -> Self {
let mut serialized = Self::from_captures(captures, input, re);
let Some(configured_group) = betterleaks_secret_group else {
return serialized;
};
let selected_group = if configured_group > 0 {
configured_group
} else {
(1..captures.len())
.find(|index| {
captures.get(*index).is_some_and(|value| !value.as_bytes().is_empty())
})
.unwrap_or(0)
};
let Some(secret) = captures.get(selected_group) else {
return serialized;
};
if let Some(position) = serialized.captures.iter().position(|capture| {
capture.match_number == i32::try_from(selected_group).unwrap_or(-1)
&& capture.name.is_some_and(|name| name.eq_ignore_ascii_case("TOKEN"))
}) {
let token = serialized.captures.remove(position);
serialized.captures.insert(0, token);
return serialized;
}
let value = serialized
.captures
.iter()
.find(|capture| capture.match_number == i32::try_from(selected_group).unwrap_or(-1))
.map(|capture| Arc::clone(&capture.value))
.unwrap_or_else(|| Arc::from(String::from_utf8_lossy(secret.as_bytes()).as_ref()));
serialized.captures.insert(
0,
SerializableCapture {
name: Some(intern("TOKEN")),
match_number: i32::try_from(selected_group).unwrap_or(-1),
start: secret.start(),
end: secret.end(),
value,
},
);
serialized
}
}