Skip to content

Commit 23031bf

Browse files
fix: prevent panic when parsing a URL with no path (#55)
* chore: sate clippy * fix: prevent panic when parsing a URL with no path Prior to this change `parse` would panic at `src/lib.rs:203:29` with `index out of bounds: the len is 0 but the index is 0` if given an input like "git:". * feat: improve error handling This removes a seemingly redundant and not very illuminating error variant. It also ensures that the error message from the `Url` crate is displayed when displaying `UrlParseError`.
1 parent 623e780 commit 23031bf

File tree

3 files changed

+23
-25
lines changed

3 files changed

+23
-25
lines changed

src/lib.rs

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ impl fmt::Display for GitUrl {
107107
format!(":{}", &self.path)
108108
}
109109
}
110-
_ => (&self.path).to_string(),
110+
_ => self.path.to_string(),
111111
};
112112

113113
let git_url_str = format!("{}{}{}{}{}", scheme_prefix, auth_info, host, port, path);
@@ -156,11 +156,7 @@ impl GitUrl {
156156
/// Returns a `Result<GitUrl>` after normalizing and parsing `url` for metadata
157157
pub fn parse(url: &str) -> Result<GitUrl, GitUrlParseError> {
158158
// Normalize the url so we can use Url crate to process ssh urls
159-
let normalized = if let Ok(url) = normalize_url(url) {
160-
url
161-
} else {
162-
return Err(GitUrlParseError::UrlNormalizeFailed);
163-
};
159+
let normalized = normalize_url(url)?;
164160

165161
// Some pre-processing for paths
166162
let scheme = if let Ok(scheme) = Scheme::from_str(normalized.scheme()) {
@@ -170,6 +166,9 @@ impl GitUrl {
170166
normalized.scheme().to_string(),
171167
));
172168
};
169+
if normalized.path().is_empty() {
170+
return Err(GitUrlParseError::EmptyPath);
171+
}
173172

174173
// Normalized ssh urls can always have their first '/' removed
175174
let urlpath = match &scheme {
@@ -211,7 +210,7 @@ impl GitUrl {
211210
let mut fullname: Vec<&str> = Vec::new();
212211

213212
// TODO: Add support for parsing out orgs from these urls
214-
let hosts_w_organization_in_path = vec!["dev.azure.com", "ssh.dev.azure.com"];
213+
let hosts_w_organization_in_path = ["dev.azure.com", "ssh.dev.azure.com"];
215214
//vec!["dev.azure.com", "ssh.dev.azure.com", "visualstudio.com"];
216215

217216
let host_str = if let Some(host) = normalized.host_str() {
@@ -362,7 +361,7 @@ fn normalize_file_path(filepath: &str) -> Result<Url, GitUrlParseError> {
362361
if let Ok(file_url) = normalize_url(&format!("file://{}", filepath)) {
363362
Ok(file_url)
364363
} else {
365-
return Err(GitUrlParseError::FileUrlNormalizeFailedSchemeAdded);
364+
Err(GitUrlParseError::FileUrlNormalizeFailedSchemeAdded)
366365
}
367366
}
368367
}
@@ -463,11 +462,7 @@ fn is_ssh_url(url: &str) -> bool {
463462

464463
// Make sure we provided a username, and not just `@`
465464
let parts: Vec<&str> = url.split('@').collect();
466-
if parts.len() != 2 && !parts[0].is_empty() {
467-
return false;
468-
} else {
469-
return true;
470-
}
465+
return parts.len() == 2 || parts[0].is_empty();
471466
}
472467

473468
// it's an ssh url if we have a domain:path pattern
@@ -481,21 +476,14 @@ fn is_ssh_url(url: &str) -> bool {
481476
// This should also handle if a port is specified
482477
// no port example: ssh://user@domain:path/to/repo.git
483478
// port example: ssh://user@domain:port/path/to/repo.git
484-
if parts.len() != 2 && !parts[0].is_empty() && !parts[1].is_empty() {
485-
return false;
486-
} else {
487-
return true;
488-
}
479+
parts.len() == 2 && parts[0].is_empty() && parts[1].is_empty()
489480
}
490481

491482
#[derive(Error, Debug, PartialEq, Eq)]
492483
pub enum GitUrlParseError {
493-
#[error("Error from Url crate")]
484+
#[error("Error from Url crate: {0}")]
494485
UrlParseError(#[from] url::ParseError),
495486

496-
#[error("Url normalization into url::Url failed")]
497-
UrlNormalizeFailed,
498-
499487
#[error("No url scheme was found, then failed to normalize as ssh url.")]
500488
SshUrlNormalizeFailedNoScheme,
501489

@@ -526,6 +514,8 @@ pub enum GitUrlParseError {
526514
UnsupportedUrlHostFormat,
527515
#[error("Git Url not in expected format for SSH")]
528516
UnsupportedSshUrlFormat,
517+
#[error("Normalized URL has no path")]
518+
EmptyPath,
529519

530520
#[error("Found null bytes within input url before parsing")]
531521
FoundNullBytes,

tests/normalize.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ fn null_in_input2() {
169169
// From https://github.com/tjtelan/git-url-parse-rs/issues/51
170170
#[test]
171171
fn large_bad_input1() {
172-
let test_url = std::iter::repeat("g@1::::").take(10000).collect::<String>();
172+
let test_url = "g@1::::".repeat(10000);
173173
let normalized = normalize_url(&test_url);
174174

175175
assert!(normalized.is_err());
@@ -178,7 +178,7 @@ fn large_bad_input1() {
178178
// From https://github.com/tjtelan/git-url-parse-rs/issues/51
179179
#[test]
180180
fn large_bad_input2() {
181-
let test_url = std::iter::repeat(":").take(10000).collect::<String>();
181+
let test_url = ":".repeat(10000);
182182
let normalized = normalize_url(&test_url);
183183

184184
assert!(normalized.is_err());

tests/parse.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,14 @@ fn ssh_without_organization() {
367367
assert_eq!(parsed, expected);
368368
}
369369

370+
#[test]
371+
fn empty_path() {
372+
assert_eq!(
373+
GitUrlParseError::EmptyPath,
374+
GitUrl::parse("git:").unwrap_err()
375+
)
376+
}
377+
370378
#[test]
371379
fn bad_port_number() {
372380
let test_url = "https://github.com:crypto-browserify/browserify-rsa.git";
@@ -375,7 +383,7 @@ fn bad_port_number() {
375383
assert!(e.is_err());
376384
assert_eq!(
377385
format!("{}", e.err().unwrap()),
378-
"Url normalization into url::Url failed"
386+
"Error from Url crate: invalid port number"
379387
);
380388
}
381389

0 commit comments

Comments
 (0)