clawhdf5-remote, h5rs: URLs' credentials are never shown
Every RemoteError message and HttpStorage's Debug output held the URL as given, with any user:password@ and the query string — for a presigned S3/GCS/Azure URL, its signature or token. An application logging the error leaked the credential. - New clawhdf5_remote::redact_url: no userinfo, no fragment, query values replaced by REDACTED (plain key names kept). - HttpStorage formats every message with the redacted URL, and scrubs the URL's secret parts from errors of the HTTP client (whose texts can echo the URI); Debug shows the redacted URL. storage_for_url's and the object store URL errors are redacted too. HttpStorage::url() still returns the URL as given, documented as not for logging. - h5rs prints FILE arguments that are URLs redacted: in errors and in dump/stat/check/diff output. - The test server can force a status and send a wrong Content-Range. Tests: 404, 403 (at open and on a read), wrong Content-Range (at open and on a read), no range support, encoded body, ETag change, timeout, connection closed and bad scheme errors, Display and Debug, contain none of the secrets; h5rs likewise for every subcommand. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -204,7 +204,7 @@ pub fn run(args: &mut Args, out: &mut Out) -> std::io::Result<i32> {
|
||||
}
|
||||
}
|
||||
if !quiet {
|
||||
c.summary(&file, out)?;
|
||||
c.summary(&crate::h5::shown(&file), out)?;
|
||||
}
|
||||
Ok(if c.panicked {
|
||||
3
|
||||
|
||||
@@ -196,7 +196,8 @@ pub fn run(args: &mut Args, out: &mut Out) -> std::io::Result<i32> {
|
||||
Err(_) => {
|
||||
writeln!(
|
||||
out.e,
|
||||
"h5rs diff: object <{obj}> could not be found in <{f}>"
|
||||
"h5rs diff: object <{obj}> could not be found in <{}>",
|
||||
crate::h5::shown(f)
|
||||
)?;
|
||||
return Ok(2);
|
||||
}
|
||||
@@ -214,7 +215,7 @@ pub fn run(args: &mut Args, out: &mut Out) -> std::io::Result<i32> {
|
||||
let entries = match collected {
|
||||
Ok(e) => e,
|
||||
Err(e) => {
|
||||
writeln!(out.e, "h5rs diff: {f}: {e}")?;
|
||||
writeln!(out.e, "h5rs diff: {}: {e}", crate::h5::shown(f))?;
|
||||
return Ok(2);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -98,6 +98,7 @@ pub fn run(args: &mut Args, out: &mut Out) -> std::io::Result<i32> {
|
||||
problems: 0,
|
||||
paths: OnceCell::new(),
|
||||
};
|
||||
let file = crate::h5::shown(&file);
|
||||
let fname = std::path::Path::new(&file)
|
||||
.file_name()
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
|
||||
@@ -229,20 +229,21 @@ impl H5 {
|
||||
if !is_url(arg) {
|
||||
return H5::open(Path::new(arg));
|
||||
}
|
||||
let name = shown(arg);
|
||||
#[cfg(feature = "remote")]
|
||||
{
|
||||
let storage =
|
||||
clawhdf5_remote::storage_for_url(arg, &clawhdf5_remote::Options::default())
|
||||
.map_err(|e| Error::new(format!("{arg}: {e}")))?;
|
||||
.map_err(|e| Error::new(format!("{name}: {e}")))?;
|
||||
let size = storage.len();
|
||||
let file = File::open_storage(storage).map_err(|e| {
|
||||
Error::new(format!("{arg}: not an HDF5 file this tool can open: {e}"))
|
||||
Error::new(format!("{name}: not an HDF5 file this tool can open: {e}"))
|
||||
})?;
|
||||
Ok(H5::new(PathBuf::from(arg), file, size))
|
||||
Ok(H5::new(PathBuf::from(&name), file, size))
|
||||
}
|
||||
#[cfg(not(feature = "remote"))]
|
||||
Err(Error::new(format!(
|
||||
"{arg}: URLs need h5rs built with the `remote` feature"
|
||||
"{name}: URLs need h5rs built with the `remote` feature"
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -256,18 +257,19 @@ impl H5 {
|
||||
if h5.file.contiguous_bytes().is_some() {
|
||||
return Ok(h5);
|
||||
}
|
||||
let name = shown(arg);
|
||||
#[cfg(feature = "remote")]
|
||||
{
|
||||
let storage =
|
||||
clawhdf5_remote::storage_for_url(arg, &clawhdf5_remote::Options::default())
|
||||
.map_err(|e| Error::new(format!("{arg}: {e}")))?;
|
||||
.map_err(|e| Error::new(format!("{name}: {e}")))?;
|
||||
let bytes = clawhdf5_remote::download(&*storage, max_download)
|
||||
.map_err(|e| Error::new(format!("{arg}: {e}")))?;
|
||||
.map_err(|e| Error::new(format!("{name}: {e}")))?;
|
||||
let size = bytes.len() as u64;
|
||||
let file = File::from_bytes(bytes).map_err(|e| {
|
||||
Error::new(format!("{arg}: not an HDF5 file this tool can open: {e}"))
|
||||
Error::new(format!("{name}: not an HDF5 file this tool can open: {e}"))
|
||||
})?;
|
||||
Ok(H5::new(PathBuf::from(arg), file, size))
|
||||
Ok(H5::new(PathBuf::from(&name), file, size))
|
||||
}
|
||||
#[cfg(not(feature = "remote"))]
|
||||
{
|
||||
@@ -777,6 +779,28 @@ pub fn split_file_arg(arg: &str) -> (String, Option<String>) {
|
||||
(arg.to_string(), None)
|
||||
}
|
||||
|
||||
/// A FILE argument as it may be printed: a URL without its credentials
|
||||
/// (userinfo, query string values — a presigned URL's signature), a path
|
||||
/// as given.
|
||||
pub fn shown(arg: &str) -> String {
|
||||
if !is_url(arg) {
|
||||
return arg.to_string();
|
||||
}
|
||||
#[cfg(feature = "remote")]
|
||||
{
|
||||
clawhdf5_remote::redact_url(arg)
|
||||
}
|
||||
#[cfg(not(feature = "remote"))]
|
||||
{
|
||||
let (scheme, rest) = arg.split_once("://").unwrap_or(("", arg));
|
||||
let rest = rest.split(['?', '#']).next().unwrap_or("");
|
||||
let host_end = rest.find('/').unwrap_or(rest.len());
|
||||
let (authority, path) = rest.split_at(host_end);
|
||||
let host = authority.rsplit_once('@').map_or(authority, |(_, h)| h);
|
||||
format!("{scheme}://{host}{path}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a FILE argument is a URL (`scheme://...`) rather than a path.
|
||||
pub fn is_url(arg: &str) -> bool {
|
||||
arg.split_once("://").is_some_and(|(scheme, _)| {
|
||||
|
||||
@@ -206,7 +206,7 @@ pub fn run(args: &mut Args, out: &mut Out) -> std::io::Result<i32> {
|
||||
if let Err(e) = walk {
|
||||
errors.push(e.to_string());
|
||||
}
|
||||
report(&h5, &file, &s, out)?;
|
||||
report(&h5, &crate::h5::shown(&file), &s, out)?;
|
||||
for e in &errors {
|
||||
writeln!(out.e, "h5rs stat: {e}")?;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user