use crate::constants::*;
use serde_json::Value;
use std::borrow::Cow;
pub fn merge_schema(a: &mut Value, b: &Value) {
match (a, b) {
(Value::Object(a_map), Value::Object(b_map)) => {
for (k, v) in b_map {
if let Some(va) = a_map.get_mut(k) {
merge_schema(va, v);
} else {
a_map.insert(k.clone(), v.clone());
}
}
}
(a, b) => *a = b.clone(),
}
}
pub fn merge_schema_owned(a: &mut Value, b: Value) {
match (a, b) {
(Value::Object(a_map), Value::Object(b_map)) => {
for (k, v) in b_map {
match a_map.entry(k) {
serde_json::map::Entry::Occupied(mut entry) => {
merge_schema_owned(entry.get_mut(), v);
}
serde_json::map::Entry::Vacant(entry) => {
entry.insert(v);
}
}
}
}
(a, b) => *a = b,
}
}
pub fn update_schema(a: &mut Value, b: &Value) {
merge_schema(a, b);
if let Some(headers) = a
.get_mut("data")
.and_then(|d| d.get_mut("CONTEXT"))
.and_then(|c| c.get_mut("HEADERS"))
.and_then(|h| h.as_object_mut())
{
if let Some(val) = headers.get("requested-with-ajax").cloned() {
headers.insert("Requested-With-Ajax".to_string(), val.clone());
headers.insert("REQUESTED-WITH-AJAX".to_string(), val);
} else if let Some(val) = headers.get("Requested-With-Ajax").cloned() {
headers.insert("requested-with-ajax".to_string(), val.clone());
headers.insert("REQUESTED-WITH-AJAX".to_string(), val);
} else if let Some(val) = headers.get("REQUESTED-WITH-AJAX").cloned() {
headers.insert("requested-with-ajax".to_string(), val.clone());
headers.insert("Requested-With-Ajax".to_string(), val);
}
}
if let Some(obj) = a.as_object_mut() {
obj.insert("version".to_string(), VERSION.into());
} else {
a["version"] = VERSION.into();
}
}
pub fn update_schema_owned(a: &mut Value, b: Value) {
merge_schema_owned(a, b);
if let Some(headers) = a
.get_mut("data")
.and_then(|d| d.get_mut("CONTEXT"))
.and_then(|c| c.get_mut("HEADERS"))
.and_then(|h| h.as_object_mut())
{
if let Some(val) = headers.get("requested-with-ajax").cloned() {
headers.insert("Requested-With-Ajax".to_string(), val.clone());
headers.insert("REQUESTED-WITH-AJAX".to_string(), val);
} else if let Some(val) = headers.get("Requested-With-Ajax").cloned() {
headers.insert("requested-with-ajax".to_string(), val.clone());
headers.insert("REQUESTED-WITH-AJAX".to_string(), val);
} else if let Some(val) = headers.get("REQUESTED-WITH-AJAX").cloned() {
headers.insert("requested-with-ajax".to_string(), val.clone());
headers.insert("Requested-With-Ajax".to_string(), val);
}
}
if let Some(obj) = a.as_object_mut() {
obj.insert("version".to_string(), VERSION.into());
} else {
a["version"] = VERSION.into();
}
}
pub fn extract_blocks(raw_source: &str) -> Result<Vec<(usize, usize)>, usize> {
let mut blocks = Vec::new();
let mut curr_pos: usize = 0;
let len_src = raw_source.len();
let bytes = raw_source.as_bytes();
while let Some(pos) = raw_source[curr_pos..].find(BIF_OPEN) {
let open_pos = curr_pos + pos;
let start_body = open_pos + BIF_OPEN.len();
curr_pos = start_body;
if curr_pos < len_src && bytes[curr_pos] == BIF_COMMENT_B {
let mut nested_comment = 0;
let mut search_pos = curr_pos;
while let Some(delim_pos_rel) = raw_source[search_pos..].find(':') {
let delim_pos = search_pos + delim_pos_rel;
if delim_pos > 0 && delim_pos + 1 < len_src {
let prev = bytes[delim_pos - 1];
let next = bytes[delim_pos + 1];
if prev == BIF_OPEN0 && next == BIF_COMMENT_B {
nested_comment += 1;
search_pos = delim_pos + 1;
continue;
}
if nested_comment > 0 && prev == BIF_COMMENT_B && next == BIF_CLOSE1 {
nested_comment -= 1;
search_pos = delim_pos + 1;
continue;
}
if prev == BIF_COMMENT_B && next == BIF_CLOSE1 {
curr_pos = delim_pos + BIF_CLOSE.len();
blocks.push((open_pos, curr_pos));
break;
}
}
search_pos = delim_pos + 1;
}
} else {
let mut nested = 0;
let mut search_pos = curr_pos;
while let Some(delim_pos_rel) = raw_source[search_pos..].find(':') {
let delim_pos = search_pos + delim_pos_rel;
if delim_pos > 0 && delim_pos + 1 < len_src {
let prev = bytes[delim_pos - 1];
let next = bytes[delim_pos + 1];
if prev == BIF_OPEN0 {
nested += 1;
search_pos = delim_pos + 1;
continue;
}
if nested > 0 && next == BIF_CLOSE1 {
nested -= 1;
search_pos = delim_pos + 1;
continue;
}
if next == BIF_CLOSE1 {
curr_pos = delim_pos + BIF_CLOSE.len();
blocks.push((open_pos, curr_pos));
break;
}
}
search_pos = delim_pos + 1;
}
}
}
let mut prev_end = 0;
for (start, end) in &blocks {
if let Some(pos) = raw_source[prev_end..*start].find(BIF_CLOSE) {
return Err(prev_end + pos);
}
prev_end = *end;
}
if let Some(pos) = raw_source[prev_end..].find(BIF_CLOSE) {
return Err(prev_end + pos);
}
Ok(blocks)
}
pub fn strip_prefix_suffix<'a>(str: &'a str, prefix: &'a str, suffix: &'a str) -> &'a str {
let start = match str.strip_prefix(prefix) {
Some(striped) => striped,
None => return str,
};
let end = match start.strip_suffix(suffix) {
Some(striped) => striped,
None => return str,
};
end
}
pub fn get_from_key(schema: &Value, key: &str) -> String {
if let Some(v) = resolve_pointer(schema, key) {
match v {
Value::Null => String::new(),
Value::Bool(b) => b.to_string(),
Value::Number(n) => n.to_string(),
Value::String(s) => s.clone(),
_ => String::new(),
}
} else {
String::new()
}
}
pub fn is_empty_key(schema: &Value, key: &str) -> bool {
if let Some(value) = resolve_pointer(schema, key) {
match value {
Value::Object(map) => map.is_empty(),
Value::Array(arr) => arr.is_empty(),
Value::String(s) => s.is_empty(),
Value::Null => true,
Value::Number(_) => false,
Value::Bool(_) => false,
}
} else {
true
}
}
pub fn is_bool_key(schema: &Value, key: &str) -> bool {
if let Some(value) = resolve_pointer(schema, key) {
match value {
Value::Object(obj) => !obj.is_empty(),
Value::Array(arr) => !arr.is_empty(),
Value::String(s) if s.is_empty() || s == "false" => false,
Value::String(s) => s.parse::<f64>().ok().map_or(true, |n| n > 0.0),
Value::Null => false,
Value::Number(n) => n.as_f64().map_or(false, |f| f > 0.0),
Value::Bool(b) => *b,
}
} else {
false
}
}
pub fn is_array_key(schema: &Value, key: &str) -> bool {
if let Some(value) = resolve_pointer(schema, key) {
match value {
Value::Object(_) => true,
Value::Array(_) => true,
_ => false,
}
} else {
false
}
}
pub fn is_defined_key(schema: &Value, key: &str) -> bool {
match resolve_pointer(schema, key) {
Some(value) => !value.is_null(),
None => false,
}
}
pub(crate) fn resolve_pointer<'a>(schema: &'a Value, key: &str) -> Option<&'a Value> {
if !key.contains(BIF_ARRAY) && !key.contains('/') {
return schema.get(key);
}
let mut current = schema;
let mut start = 0;
let bytes = key.as_bytes();
let len = bytes.len();
let bif_array_bytes = BIF_ARRAY.as_bytes();
let delim_len = bif_array_bytes.len();
let mut i = 0;
while i < len {
let is_slash = bytes[i] == b'/';
let is_arrow =
!is_slash && i + delim_len <= len && &bytes[i..i + delim_len] == bif_array_bytes;
if is_slash || is_arrow {
let part = &key[start..i];
if !part.is_empty() {
current = match current {
Value::Object(map) => map.get(part)?,
Value::Array(arr) => {
let idx = part.parse::<usize>().ok()?;
arr.get(idx)?
}
_ => return None,
};
}
if is_slash {
i += 1;
start = i;
} else {
i += delim_len;
start = i;
}
} else {
i += 1;
}
}
if start < len {
let part = &key[start..];
current = match current {
Value::Object(map) => map.get(part)?,
Value::Array(arr) => {
let idx = part.parse::<usize>().ok()?;
arr.get(idx)?
}
_ => return None,
};
}
Some(current)
}
pub fn get_code_position(src: &str) -> Option<usize> {
if !src.contains(BIF_CODE) {
return None;
}
let mut level = 0;
let bytes = src.as_bytes();
let len = bytes.len();
let mut i = 0;
while i + 1 < len {
let b0 = bytes[i];
let b1 = bytes[i + 1];
if b0 == BIF_OPEN_B[0] && b1 == BIF_OPEN_B[1] {
level += 1;
i += 2;
} else if b0 == BIF_CLOSE_B[0] && b1 == BIF_CLOSE_B[1] {
level -= 1;
i += 2;
} else if b0 == BIF_CODE_B[0] && b1 == BIF_CODE_B[1] {
if level == 0 {
return Some(i);
}
i += 2;
} else {
i += 1;
}
}
None
}
pub fn remove_comments(raw_source: &str) -> String {
let mut result = String::new();
let mut blocks = Vec::new();
let bytes = raw_source.as_bytes();
let mut curr_pos: usize = 0;
let mut open_pos: usize;
let mut nested_comment = 0;
let len_open = BIF_COMMENT_OPEN_B.len();
let len_close = BIF_CLOSE_B.len();
let len_src = bytes.len();
while let Some(rel_pos) = raw_source[curr_pos..].find(BIF_COMMENT_OPEN) {
let absolute_pos = curr_pos + rel_pos;
curr_pos = absolute_pos + len_open;
open_pos = absolute_pos;
while let Some(delim_pos_rel) = raw_source[curr_pos..].find(BIF_DELIM) {
curr_pos += delim_pos_rel;
if curr_pos >= len_src {
break;
}
if bytes[curr_pos - 1] == BIF_OPEN0 && bytes[curr_pos + 1] == BIF_COMMENT_B {
nested_comment += 1;
curr_pos += 1;
continue;
}
if nested_comment > 0
&& bytes[curr_pos + 1] == BIF_CLOSE1
&& bytes[curr_pos - 1] == BIF_COMMENT_B
{
nested_comment -= 1;
curr_pos += 1;
continue;
}
if bytes[curr_pos + 1] == BIF_CLOSE1 && bytes[curr_pos - 1] == BIF_COMMENT_B {
curr_pos += len_close;
blocks.push((open_pos, curr_pos));
break;
} else {
curr_pos += 1;
}
}
}
let mut prev_end = 0;
for (start, end) in &blocks {
result.push_str(&raw_source[prev_end..*start]);
prev_end = *end;
}
result.push_str(&raw_source[curr_pos..]);
result
}
pub fn wildcard_match(text: &str, pattern: &str) -> bool {
let text_chars: Vec<char> = text.chars().collect();
let pattern_chars: Vec<char> = pattern.chars().collect();
fn match_recursive(text: &[char], pattern: &[char]) -> bool {
if pattern.is_empty() {
return text.is_empty();
}
let first_char = *pattern.first().unwrap();
let rest_pattern = &pattern[1..];
match first_char {
'\\' => {
if rest_pattern.is_empty() || text.is_empty() {
return false;
}
let escaped_char = rest_pattern.first().unwrap();
match_recursive(&text[1..], &rest_pattern[1..])
&& *text.first().unwrap() == *escaped_char
}
'.' => {
match_recursive(text, rest_pattern)
|| (!text.is_empty() && match_recursive(&text[1..], rest_pattern))
}
'?' => !text.is_empty() && match_recursive(&text[1..], rest_pattern),
'*' => {
match_recursive(text, rest_pattern)
|| (!text.is_empty() && match_recursive(&text[1..], pattern))
}
'~' => text.is_empty(),
_ => {
if text.is_empty() || first_char != *text.first().unwrap() {
false
} else {
match_recursive(&text[1..], rest_pattern)
}
}
}
}
match_recursive(&text_chars, &pattern_chars)
}
pub fn find_tag_position(text: &str, tag: &str) -> Option<usize> {
if let Some(start_pos) = text.find(tag) {
if !tag.starts_with("</") {
if let Some(end_tag_pos) = text[start_pos..].find('>') {
return Some(start_pos + end_tag_pos + 1);
}
} else {
return Some(start_pos);
}
}
None
}
pub fn escape_chars<'a>(input: &'a str, escape_braces: bool) -> Cow<'a, str> {
let needs_escape = input.chars().any(|c| match c {
'&' | '<' | '>' | '"' | '\'' | '/' => true,
'{' | '}' if escape_braces => true,
_ => false,
});
if !needs_escape {
return Cow::Borrowed(input);
}
let mut result = String::with_capacity(input.len() * 2);
for c in input.chars() {
if c.is_ascii() {
match c {
'&' => result.push_str("&"),
'<' => result.push_str("<"),
'>' => result.push_str(">"),
'"' => result.push_str("""),
'\'' => result.push_str("'"),
'/' => result.push_str("/"),
'{' if escape_braces => result.push_str("{"),
'}' if escape_braces => result.push_str("}"),
_ => result.push(c),
}
} else {
result.push(c);
}
}
Cow::Owned(result)
}
pub fn unescape_chars<'a>(input: &'a str, escape_braces: bool) -> Cow<'a, str> {
if !input.contains('&') {
return Cow::Borrowed(input);
}
let mut result = String::with_capacity(input.len());
let mut chars = input.chars().peekable();
while let Some(c) = chars.next() {
if c == '&' {
let mut entity = String::new();
let mut has_semicolon = false;
while let Some(&next_char) = chars.peek() {
if next_char == ';' {
chars.next();
has_semicolon = true;
break;
}
entity.push(chars.next().unwrap());
}
match (entity.as_str(), has_semicolon) {
("amp", true) => result.push('&'),
("lt", true) => result.push('<'),
("gt", true) => result.push('>'),
("quot", true) => result.push('"'),
("#x27", true) => result.push('\''),
("#x2F", true) => result.push('/'),
("#123", true) if escape_braces => result.push('{'),
("#125", true) if escape_braces => result.push('}'),
_ => {
result.push('&');
result.push_str(&entity);
if has_semicolon {
result.push(';');
}
}
}
} else {
result.push(c);
}
}
Cow::Owned(result)
}
pub fn filter_value(value: &mut Value) {
match value {
Value::String(s) => {
let unescaped = unescape_chars(s, true);
let processed = match unescaped {
Cow::Borrowed(_) => escape_chars(s, true),
Cow::Owned(ref u) => escape_chars(u, true),
};
if let Cow::Owned(new_s) = processed {
*s = new_s;
}
}
Value::Object(obj) => {
for v in obj.values_mut() {
filter_value(v);
}
}
Value::Array(arr) => {
for item in arr.iter_mut() {
filter_value(item);
}
}
_ => {}
}
}
pub fn filter_value_keys(value: &mut Value) {
match value {
Value::Object(obj) => {
let needs_change = obj.keys().any(|k| {
k.contains('&')
|| k.chars()
.any(|c| matches!(c, '&' | '<' | '>' | '"' | '\'' | '/' | '{' | '}'))
});
if !needs_change {
for val in obj.values_mut() {
filter_value_keys(val);
}
return;
}
let mut new_obj = serde_json::Map::with_capacity(obj.len());
for (key, val) in obj.iter_mut() {
let unescaped = unescape_chars(key, true);
let processed = match unescaped {
Cow::Borrowed(_) => escape_chars(key, true),
Cow::Owned(ref u) => escape_chars(u, true),
};
let new_key = match processed {
Cow::Borrowed(b) => b.to_string(),
Cow::Owned(o) => o,
};
filter_value_keys(val);
new_obj.insert(new_key, std::mem::take(val));
}
*obj = new_obj;
}
Value::Array(arr) => {
for item in arr.iter_mut() {
filter_value_keys(item);
}
}
_ => {}
}
}