Skip to content

Commit bc24cff

Browse files
committed
Reject unknown source formats.
1 parent ca84f55 commit bc24cff

1 file changed

Lines changed: 82 additions & 0 deletions

File tree

  • cmd/soroban-cli/src/commands/contract

cmd/soroban-cli/src/commands/contract/verify.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use regex::Regex;
77
use sha2::{Digest, Sha256};
88
use soroban_spec_tools::contract::Spec;
99
use stellar_xdr::{Hash, ScMetaEntry, ScMetaV0};
10+
use url::Url;
1011
use walkdir::WalkDir;
1112

1213
use crate::{
@@ -121,6 +122,9 @@ pub enum Error {
121122
#[error("reading stdin: {0}")]
122123
Stdin(std::io::Error),
123124

125+
#[error("source {uri:?} has an unsupported format; accepted formats are {formats}")]
126+
UnsupportedSourceFormat { uri: String, formats: String },
127+
124128
#[error("the WASM records only `source_sha256` (no `source_uri`). Pass `--source-uri URL_OR_PATH` to provide retrieval.")]
125129
SourceUriRequired,
126130

@@ -588,6 +592,8 @@ async fn materialize_source(
588592
return Err(Error::SourceUriRequired);
589593
};
590594

595+
validate_source_format(&source)?;
596+
591597
print.infoln(format!("Fetching source code from {source}"));
592598
let bytes = fetch_tarball_bytes(&source).await?;
593599
if let Some(expected) = &meta.source_sha256 {
@@ -600,6 +606,45 @@ async fn materialize_source(
600606
)?)
601607
}
602608

609+
/// Extensions we accept for a source archive: the archive is always a gzipped
610+
/// tarball (see `source_archive`), so only these name it. Checked
611+
/// case-insensitively against the source's basename.
612+
const RECOGNIZED_SOURCE_EXTENSIONS: &[&str] = &[".tar.gz", ".tgz"];
613+
614+
/// The last path segment of `source`, whether it's a URL or a local path. Try
615+
/// parsing as a URL first (so query strings and fragments are dropped); if that
616+
/// fails, `source` is a local path, so fall back to `Path::file_name`.
617+
fn source_basename(source: &str) -> String {
618+
if let Ok(url) = Url::parse(source) {
619+
return url
620+
.path_segments()
621+
.and_then(|mut segments| segments.next_back())
622+
.unwrap_or_default()
623+
.to_string();
624+
}
625+
Path::new(source)
626+
.file_name()
627+
.map(|name| name.to_string_lossy().into_owned())
628+
.unwrap_or_default()
629+
}
630+
631+
/// Reject a `--source-uri` (or recorded `source_uri`) whose basename doesn't end
632+
/// in a recognized archive extension, before we bother fetching it, naming the
633+
/// formats we accept.
634+
fn validate_source_format(source: &str) -> Result<(), Error> {
635+
let basename = source_basename(source).to_ascii_lowercase();
636+
if RECOGNIZED_SOURCE_EXTENSIONS
637+
.iter()
638+
.any(|ext| basename.ends_with(ext))
639+
{
640+
return Ok(());
641+
}
642+
Err(Error::UnsupportedSourceFormat {
643+
uri: source.to_string(),
644+
formats: RECOGNIZED_SOURCE_EXTENSIONS.join(", "),
645+
})
646+
}
647+
603648
/// Retrieve the tarball bytes. `source` is either an `http(s)://` URL or a
604649
/// local file path. The split is by prefix, not by attempting both — keeps
605650
/// behavior predictable.
@@ -1307,4 +1352,41 @@ mod tests {
13071352
let p = find_rebuilt_wasm(dir.path(), &meta, &preexisting).unwrap();
13081353
assert!(p.ends_with("hello.wasm"));
13091354
}
1355+
1356+
#[test]
1357+
fn source_basename_strips_url_query_and_fragment() {
1358+
assert_eq!(
1359+
source_basename("https://example.com/path/src.tar.gz?token=abc#frag"),
1360+
"src.tar.gz"
1361+
);
1362+
assert_eq!(source_basename("https://example.com/a/b/x.tgz"), "x.tgz");
1363+
}
1364+
1365+
#[test]
1366+
fn source_basename_handles_local_paths() {
1367+
assert_eq!(source_basename("/tmp/foo/src.tar.gz"), "src.tar.gz");
1368+
assert_eq!(source_basename("./relative/src.tgz"), "src.tgz");
1369+
assert_eq!(source_basename("src.tar.gz"), "src.tar.gz");
1370+
}
1371+
1372+
#[test]
1373+
fn validate_source_format_accepts_recognized_extensions() {
1374+
validate_source_format("https://example.com/src.tar.gz").unwrap();
1375+
validate_source_format("/tmp/src.tgz").unwrap();
1376+
// Case-insensitive.
1377+
validate_source_format("SRC.TAR.GZ").unwrap();
1378+
}
1379+
1380+
#[test]
1381+
fn validate_source_format_rejects_unknown_formats() {
1382+
for source in [
1383+
"https://example.com/src.zip",
1384+
"/tmp/src.rar",
1385+
"src",
1386+
"src.gz",
1387+
] {
1388+
let err = validate_source_format(source).unwrap_err();
1389+
assert!(matches!(err, Error::UnsupportedSourceFormat { .. }));
1390+
}
1391+
}
13101392
}

0 commit comments

Comments
 (0)