From 97c040b01c5e38afe9b2eccd17280744a389b1c0 Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Fri, 21 Feb 2025 14:09:26 +1300 Subject: [PATCH] Fix: errors related to strings not json-escaped --- src/lib.rs | 16 ++++++++-------- src/nostr.rs | 48 ++++++++++++++++++++++++------------------------ src/reply.rs | 52 ++++++++++++++++++++++++++++++++++++---------------- 3 files changed, 68 insertions(+), 48 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2e74d9d..78d8b04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -374,7 +374,7 @@ impl WebSocketService { if m.len() > self.burst_tokens { log::info!(target: "Client", "{}: Rate limited exceeded", self.peer); let reply = NostrReply::Notice("Rate limit exceeded.".into()); - self.websocket.send(Message::text(reply.as_json())).await?; + self.websocket.send(Message::text(reply.as_json()?)).await?; let error = ChorusError::RateLimitExceeded; self.error_punishment += error.punishment(); return Err(error.into()); @@ -419,7 +419,7 @@ impl WebSocketService { // Offer AUTH to clients right off the bat let reply = NostrReply::Auth(self.challenge.clone()); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; let mut last_message_at = Instant::now(); @@ -493,7 +493,7 @@ impl WebSocketService { let message = NostrReply::Event(subid, event); // note, this is not currently counted in throttling self.websocket - .send(Message::text(message.as_json())) + .send(Message::text(message.as_json()?)) .await?; continue 'subs; } @@ -529,7 +529,7 @@ impl WebSocketService { if message.len() > self.burst_tokens { log::info!(target: "Client", "{}: Rate limited exceeded", self.peer); let reply = NostrReply::Notice("Rate limit exceeded.".into()); - self.websocket.send(Message::text(reply.as_json())).await?; + self.websocket.send(Message::text(reply.as_json()?)).await?; let error = ChorusError::RateLimitExceeded; self.error_punishment += error.punishment(); return Err(error.into()); @@ -556,15 +556,15 @@ impl WebSocketService { if !self.replied { if let Some(subid) = &self.negentropy_sub { let reply = NostrReply::NegErr(subid, format!("error: {e}")); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; } else { let reply = NostrReply::Notice(format!("error: {}", e.inner)); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; } } if self.error_punishment >= 1.0 { let reply = NostrReply::Notice("Closing due to error(s)".into()); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; return Err(ChorusError::ErrorClose.into()); } } @@ -573,7 +573,7 @@ impl WebSocketService { let reply = NostrReply::Notice( "binary messages are not processed by this relay".to_owned(), ); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; log::info!(target: "Client", "{}: Received unhandled binary message: {:02X?}", self.peer, diff --git a/src/nostr.rs b/src/nostr.rs index a426578..14422d0 100644 --- a/src/nostr.rs +++ b/src/nostr.rs @@ -46,7 +46,7 @@ impl WebSocketService { } else { log::warn!(target: "Client", "{}: Received unhandled text message: {}", self.peer, msg); let reply = NostrReply::Notice("Command unrecognized".to_owned()); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; } Ok(()) @@ -106,7 +106,7 @@ impl WebSocketService { } _ => NostrReply::Closed(&subid, NostrReplyPrefix::Error, format!("{}", e.inner)), }; - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; Err(e) } else { Ok(()) @@ -141,7 +141,7 @@ impl WebSocketService { NostrReplyPrefix::AuthRequired, "DM kinds were included in the filters".to_owned(), ); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; return Ok(()); } } @@ -199,11 +199,11 @@ impl WebSocketService { } } let reply = NostrReply::Count(subid, events.len(), opthll); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; } else { for event in events.drain(..) { let reply = NostrReply::Event(subid, event); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; } // New policy Feb 2025: Redactions trigger a "CLOSED: auth-required" because @@ -216,18 +216,18 @@ impl WebSocketService { NostrReplyPrefix::AuthRequired, "At least one matching event requires AUTH".to_owned(), ); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; return Ok(()); } if completes { // Closed let reply = NostrReply::Closed(subid, NostrReplyPrefix::None, "".to_owned()); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; } else { // EOSE let reply = NostrReply::Eose(subid); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; } } } @@ -307,11 +307,11 @@ impl WebSocketService { }, _ => NostrReply::Ok(id, false, NostrReplyPrefix::Error, format!("{}", e.inner)), }; - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; Err(e) } else { let reply = NostrReply::Ok(id, true, NostrReplyPrefix::None, "".to_string()); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; Ok(()) } } @@ -388,7 +388,7 @@ impl WebSocketService { // message, and clients just presume it was closed. /* let reply = NostrReply::Closed(subid, NostrReplyPrefix::None, "".to_owned()); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; */ Ok(()) @@ -416,11 +416,11 @@ impl WebSocketService { } _ => NostrReply::Ok(id, false, NostrReplyPrefix::Error, format!("{}", e.inner)), }; - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; Err(e) } else { let reply = NostrReply::Ok(id, true, NostrReplyPrefix::None, "".to_string()); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; Ok(()) } } @@ -499,7 +499,7 @@ impl WebSocketService { if !GLOBALS.config.read().enable_negentropy { let reply = NostrReply::NegErr(&subid, "blocked: Negentropy sync is disabled".to_owned()); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; return Ok(()); } @@ -533,14 +533,14 @@ impl WebSocketService { // NEG-ERR if the message was empty if incoming_msg.is_empty() { let reply = NostrReply::NegErr(&subid, "error: Empty negentropy message".to_owned()); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; return Ok(()); } // If the version is too high, respond with our version number if incoming_msg[0] != 0x61 { let reply = NostrReply::NegMsg(&subid, vec![0x61]); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; return Ok(()); } @@ -594,11 +594,11 @@ impl WebSocketService { match neg.reconcile(&Bytes::from(incoming_msg)) { Ok(response) => { let reply = NostrReply::NegMsg(&subid, response.as_bytes().to_owned()); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; } Err(e) => { let reply = NostrReply::NegErr(&subid, format!("{e}")); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; } } @@ -634,7 +634,7 @@ impl WebSocketService { if !GLOBALS.config.read().enable_negentropy { let reply = NostrReply::NegErr(&subid, "blocked: Negentropy sync is disabled".to_owned()); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; return Ok(()); } @@ -656,7 +656,7 @@ impl WebSocketService { // NEG-ERR if the message was empty if incoming_msg.is_empty() { let reply = NostrReply::NegErr(&subid, "error: Empty negentropy message".to_owned()); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; return Ok(()); } @@ -664,14 +664,14 @@ impl WebSocketService { // have already happened in NEG-OPEN) if incoming_msg[0] != 0x61 { let reply = NostrReply::NegErr(&subid, "Version mismatch".to_owned()); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; return Ok(()); } // Look up the events we have let Some(nsv) = self.neg_subscriptions.get(&subid) else { let reply = NostrReply::NegErr(&subid, "Subscription not found".to_owned()); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; return Ok(()); }; @@ -679,11 +679,11 @@ impl WebSocketService { match neg.reconcile(&Bytes::from(incoming_msg)) { Ok(response) => { let reply = NostrReply::NegMsg(&subid, response.as_bytes().to_owned()); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; } Err(e) => { let reply = NostrReply::NegErr(&subid, format!("{e}")); - self.send(Message::text(reply.as_json())).await?; + self.send(Message::text(reply.as_json()?)).await?; } } diff --git a/src/reply.rs b/src/reply.rs index 695d6a0..2762f5d 100644 --- a/src/reply.rs +++ b/src/reply.rs @@ -1,3 +1,4 @@ +use crate::Error; use pocket_types::{write_hex, Event, Hll8, Id}; use std::fmt; @@ -45,40 +46,59 @@ pub enum NostrReply<'a> { } impl NostrReply<'_> { - pub fn as_json(&self) -> String { - match self { - NostrReply::Auth(challenge) => format!(r#"["AUTH","{challenge}"]"#), - NostrReply::Event(subid, event) => format!(r#"["EVENT","{subid}",{}]"#, event), - NostrReply::Ok(id, ok, prefix, msg) => format!(r#"["OK","{id}",{ok},"{prefix}{msg}"]"#), - NostrReply::Eose(subid) => format!(r#"["EOSE","{subid}"]"#), + pub fn as_json(&self) -> Result { + Ok(match self { + NostrReply::Auth(challenge) => { + let esc_challenge = escape(challenge)?; + format!(r#"["AUTH","{esc_challenge}"]"#) + } + NostrReply::Event(subid, event) => { + let esc_subid = escape(subid)?; + format!(r#"["EVENT","{esc_subid}",{event}]"#) + } + NostrReply::Ok(id, ok, prefix, msg) => { + let esc_msg = escape(msg)?; + format!(r#"["OK","{id}",{ok},"{prefix}{esc_msg}"]"#) + } + NostrReply::Eose(subid) => { + let esc_subid = escape(subid)?; + format!(r#"["EOSE","{esc_subid}"]"#) + } NostrReply::Closed(subid, prefix, msg) => { format!(r#"["CLOSED","{subid}","{prefix}{msg}"]"#) } - NostrReply::Notice(msg) => format!(r#"["NOTICE","{msg}"]"#), + NostrReply::Notice(msg) => { + let esc_msg = escape(msg)?; + format!(r#"["NOTICE","{esc_msg}"]"#) + } NostrReply::Count(subid, c, opthll) => { + let esc_subid = escape(subid)?; if let Some(hll) = opthll { let hll = hll.to_hex_string(); - format!(r#"["COUNT","{subid}",{{"count":{c}, "hll":"{hll}"}}]"#) + format!(r#"["COUNT","{esc_subid}",{{"count":{c}, "hll":"{hll}"}}]"#) } else { - format!(r#"["COUNT","{subid}",{{"count":{c}}}]"#) + format!(r#"["COUNT","{esc_subid}",{{"count":{c}}}]"#) } } NostrReply::NegErr(subid, reason) => { - format!(r#"["NEG-ERR","{subid}","{reason}"]"#) + let esc_subid = escape(subid)?; + let esc_reason = escape(reason)?; + format!(r#"["NEG-ERR","{esc_subid}","{esc_reason}"]"#) } NostrReply::NegMsg(subid, msg) => { + let esc_subid = escape(subid)?; // write msg as hex let mut buf: Vec = vec![0; msg.len() * 2]; write_hex!(msg, &mut buf, msg.len()).unwrap(); let msg_hex = unsafe { std::str::from_utf8_unchecked(&buf) }; - format!(r#"["NEG-MSG","{subid}","{}"]"#, msg_hex) + format!(r#"["NEG-MSG","{esc_subid}","{}"]"#, msg_hex) } - } + }) } } -impl fmt::Display for NostrReply<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.as_json()) - } +fn escape(s: &str) -> Result { + let v: Vec = Vec::with_capacity(256); + let e = pocket_types::json::json_escape(s.as_bytes(), v)?; + Ok(unsafe { String::from_utf8_unchecked(e) }) }