Skip to content

Commit

Permalink
sanitize json string
Browse files Browse the repository at this point in the history
  • Loading branch information
Larkooo committed Feb 3, 2025
1 parent 42aa4c3 commit 8a52e55
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 5 deletions.
13 changes: 8 additions & 5 deletions crates/torii/sqlite/src/executor/erc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::executor::LOG_TARGET;
use crate::simple_broker::SimpleBroker;
use crate::types::{ContractType, TokenBalance};
use crate::utils::{
felt_to_sql_string, fetch_content_from_ipfs, sql_string_to_u256, u256_to_sql_string, I256,
felt_to_sql_string, fetch_content_from_ipfs, sanitize_json_string, sql_string_to_u256, u256_to_sql_string, I256
};

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -313,14 +313,17 @@ impl<'c, P: Provider + Sync + Send + 'static> Executor<'c, P> {
}

let decoded = data_url.decode_to_vec().context("Failed to decode data URI")?;
// Filter out control characters and escape unescaped quotes
// HACK: Loot Survior NFT metadata contains control characters which makes the json
// DATA invalid so filter them out
let decoded_str = String::from_utf8_lossy(&decoded.0)
.chars()
.filter(|c| !c.is_ascii_control())
.collect::<String>()
.replace(r#"""#, r#"\""#); // Escape unescaped quotes
.collect::<String>();
let sanitized_json = sanitize_json_string(&decoded_str);

let json: serde_json::Value = serde_json::from_str(&decoded_str)
println!("sanitized_json: {}", sanitized_json);

let json: serde_json::Value = serde_json::from_str(&sanitized_json)
.with_context(|| format!("Failed to parse metadata JSON from data URI: {}", &uri))?;

Ok(json)
Expand Down
66 changes: 66 additions & 0 deletions crates/torii/sqlite/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,58 @@ pub fn sql_string_to_felts(sql_string: &str) -> Vec<Felt> {
sql_string.split(SQL_FELT_DELIMITER).map(|felt| Felt::from_str(felt).unwrap()).collect()
}

/// Sanitizes a JSON string by escaping unescaped double quotes within string values.
pub fn sanitize_json_string(s: &str) -> String {
let mut result = String::new();
let mut chars = s.chars().peekable();
let mut in_string = false;

while let Some(c) = chars.next() {
match c {
'"' => {
if !in_string {
// Starting a string
result.push('"');
in_string = true;
} else {
// Check next char to see if this is the end of the string
match chars.peek() {
Some(&':') | Some(&',') | Some(&'}') => {
// This is end of a JSON string
result.push('"');
in_string = false;
}
_ => {
// This is an internal quote that needs escaping
result.push_str("\\\"");
}
}
}
}
'\\' => {
if let Some(&next) = chars.peek() {
if next == '"' {
// Already escaped quote, preserve it without adding extra escapes
result.push('\\');
result.push('"');
chars.next(); // Consume the quote
} else {
// Regular backslash
result.push('\\');
}
} else {
result.push('\\');
}
}
_ => {
result.push(c);
}
}
}

result
}

pub async fn fetch_content_from_ipfs(cid: &str) -> Result<Bytes> {
let mut retries = IPFS_CLIENT_MAX_RETRY;
let client = IpfsClient::from_str(IPFS_CLIENT_URL)?
Expand Down Expand Up @@ -166,6 +218,20 @@ mod tests {

use super::*;

#[test]
fn test_sanitize_json_string() {
let input = r#"{"name":""Rage Shout" DireWolf"}"#;
let expected = r#"{"name":"\"Rage Shout\" DireWolf"}"#;
let sanitized = sanitize_json_string(input);
assert_eq!(sanitized, expected);

let input_escaped = r#"{"name":"\"Properly Escaped\" Wolf"}"#;
let expected_escaped = r#"{"name":"\"Properly Escaped\" Wolf"}"#;
let sanitized_escaped = sanitize_json_string(input_escaped);
assert_eq!(sanitized_escaped, expected_escaped);
}


#[test]
fn test_must_utc_datetime_from_timestamp() {
let timestamp = 1633027200;
Expand Down

0 comments on commit 8a52e55

Please # to comment.