Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/cont_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ jobs:
cargo update -p openssl --precise "0.10.78"
cargo update -p openssl-sys --precise "0.9.114"
cargo update -p zeroize --precise "1.8.2"
cargo update -p jobserver --precise "0.1.34"
- name: Test
run: cargo test --verbose --all-features

Expand Down Expand Up @@ -74,6 +75,7 @@ jobs:
cargo update -p openssl --precise "0.10.78"
cargo update -p openssl-sys --precise "0.9.114"
cargo update -p zeroize --precise "1.8.2"
cargo update -p jobserver --precise "0.1.34"
- name: Check features
run: cargo check --verbose ${{ matrix.features }}

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ To build with the MSRV you will need to pin dependencies by running:
cargo update -p openssl --precise "0.10.78"
cargo update -p openssl-sys --precise "0.9.114"
cargo update -p zeroize --precise "1.8.2"
cargo update -p jobserver --precise "0.1.34"
```

## License
Expand Down
22 changes: 17 additions & 5 deletions src/raw_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,7 @@ impl<S: Read + Write> RawClient<S> {
let req = req.with_auth(authorization);

let mut raw = serde_json::to_vec(&req)?;
trace!("==> {}", String::from_utf8_lossy(&raw));
trace!("==> {}", serde_json::to_string(&req.redacted())?);

raw.extend_from_slice(b"\n");
let mut stream = self.stream.lock()?;
Expand Down Expand Up @@ -912,7 +912,7 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
}

fn batch_call(&self, batch: &Batch) -> Result<Vec<serde_json::Value>, Error> {
let mut raw = Vec::new();
let mut requests = Vec::new();

let mut missing_responses = Vec::new();
let mut answers = BTreeMap::new();
Expand Down Expand Up @@ -951,15 +951,27 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {

self.waiting_map.lock()?.insert(req.id, sender);

raw.append(&mut serde_json::to_vec(&req)?);
raw.extend_from_slice(b"\n");
requests.push(req);
}

if missing_responses.is_empty() {
return Ok(vec![]);
}

trace!("==> {}", String::from_utf8_lossy(&raw));
let mut raw = Vec::new();
for req in &requests {
raw.append(&mut serde_json::to_vec(req)?);
raw.extend_from_slice(b"\n");
}

trace!(
"==> {}",
requests
.iter()
.map(|req| serde_json::to_string(&req.redacted()))
.collect::<Result<Vec<String>, _>>()?
.join("\n")
);

let mut stream = self.stream.lock()?;
stream.write_all(&raw)?;
Expand Down
47 changes: 46 additions & 1 deletion src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ impl Display for EstimationMode {
}
}

/// Placeholder that replaces the authorization token in log output.
pub(crate) const REDACTED_AUTHORIZATION: &str = "<REDACTED>";

#[derive(Serialize, Clone)]
/// A request that can be sent to the server
pub struct Request<'a> {
Expand Down Expand Up @@ -104,6 +107,18 @@ impl<'a> Request<'a> {
self.authorization = authorization;
self
}

/// Returns a copy of this [`Request`] with the authorization token replaced by
/// [`REDACTED_AUTHORIZATION`], safe to log.
pub(crate) fn redacted(&self) -> Self {
Self {
authorization: self
.authorization
.as_ref()
.map(|_| REDACTED_AUTHORIZATION.to_string()),
..self.clone()
}
}
}

#[doc(hidden)]
Expand Down Expand Up @@ -531,7 +546,7 @@ impl From<std::sync::mpsc::RecvError> for Error {
mod tests {
use crate::ScriptStatus;

use super::{Param, Request};
use super::{Param, Request, REDACTED_AUTHORIZATION};

#[test]
fn script_status_roundtrip() {
Expand Down Expand Up @@ -595,4 +610,34 @@ mod tests {
assert!(parsed["params"].is_array());
assert_eq!(parsed["params"][0], "test-scripthash");
}

#[test]
fn test_request_redacted_with_authorization() {
let req = Request::new_id(
42,
"blockchain.scripthash.get_balance",
vec![Param::String("test-scripthash".to_string())],
)
.with_auth(Some("Bearer secret-token".to_string()));

let json = serde_json::to_string(&req.redacted()).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();

assert_eq!(parsed["authorization"], REDACTED_AUTHORIZATION);
assert!(!json.contains("secret-token"));
assert_eq!(parsed["jsonrpc"], "2.0");
assert_eq!(parsed["id"], 42);
assert_eq!(parsed["method"], "blockchain.scripthash.get_balance");
assert_eq!(parsed["params"][0], "test-scripthash");
}

#[test]
fn test_request_redacted_without_authorization() {
let req = Request::new_id(1, "server.version", vec![]);

assert_eq!(
serde_json::to_string(&req.redacted()).unwrap(),
serde_json::to_string(&req).unwrap()
);
}
}
Loading