Fix: errors related to strings not json-escaped

This commit is contained in:
Mike Dilger 2025-02-21 14:09:26 +13:00
parent c03da62981
commit 97c040b01c
No known key found for this signature in database
GPG Key ID: 47581A78D4329BA4
3 changed files with 68 additions and 48 deletions

View File

@ -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,

View File

@ -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?;
}
}

View File

@ -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<String, Error> {
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<u8> = 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<String, Error> {
let v: Vec<u8> = Vec::with_capacity(256);
let e = pocket_types::json::json_escape(s.as_bytes(), v)?;
Ok(unsafe { String::from_utf8_unchecked(e) })
}