Skip to content

Commit 13c7922

Browse files
committed
fix!: validate::tag::name() now rejects names starting with slashes, and with empty components, too
It's a breaking change as it renames the `DoubleDot` variant of `tag::Error` to `RepatedDot`, while moving a couple of variants from `reference::Error` to `tag::Error`
1 parent 0401758 commit 13c7922

File tree

4 files changed

+73
-50
lines changed

4 files changed

+73
-50
lines changed

gix-validate/src/reference.rs

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,6 @@ pub mod name {
1111
Tag(#[from] crate::tag::name::Error),
1212
#[error("Standalone references must be all uppercased, like 'HEAD'")]
1313
SomeLowercase,
14-
#[error("A reference name must not start with a slash '/'")]
15-
StartsWithSlash,
16-
#[error("Multiple slashes in a row are not allowed as they may change the reference's meaning")]
17-
RepeatedSlash,
18-
#[error("Path components must not start with '.'")]
19-
StartsWithDot,
2014
}
2115

2216
impl From<Infallible> for Error {
@@ -75,34 +69,10 @@ fn validate(path: &BStr, mode: Mode) -> Result<Cow<'_, BStr>, name::Error> {
7569
},
7670
)?;
7771
let sanitize = matches!(mode, Mode::PartialSanitize);
78-
if path.get(0) == Some(&b'/') {
79-
if sanitize {
80-
out.to_mut()[0] = b'-';
81-
} else {
82-
return Err(name::Error::StartsWithSlash);
83-
}
84-
}
8572
let mut previous = 0;
8673
let mut saw_slash = false;
87-
let mut out_ofs = 0;
88-
for (mut byte_pos, byte) in path.iter().enumerate() {
89-
byte_pos -= out_ofs;
74+
for (byte_pos, byte) in path.iter().enumerate() {
9075
match *byte {
91-
b'/' if previous == b'/' => {
92-
if sanitize {
93-
out.to_mut().remove(byte_pos);
94-
out_ofs += 1;
95-
} else {
96-
return Err(name::Error::RepeatedSlash);
97-
}
98-
}
99-
b'.' if previous == b'/' => {
100-
if sanitize {
101-
out.to_mut()[byte_pos] = b'-';
102-
} else {
103-
return Err(name::Error::StartsWithDot);
104-
}
105-
}
10676
_ => {}
10777
}
10878

gix-validate/src/tag.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ pub mod name {
1212
pub enum Error {
1313
#[error("A ref must not contain invalid bytes or ascii control characters: {byte:?}")]
1414
InvalidByte { byte: BString },
15+
#[error("A reference name must not start with a slash '/'")]
16+
StartsWithSlash,
17+
#[error("Multiple slashes in a row are not allowed as they may change the reference's meaning")]
18+
RepeatedSlash,
1519
#[error("A ref must not contain '..' as it may be mistaken for a range")]
16-
DoubleDot,
20+
RepeatedDot,
1721
#[error("A ref must not end with '.lock'")]
1822
LockFileSuffix,
1923
#[error("A ref must not contain '@{{' which is a part of a ref-log")]
@@ -72,6 +76,13 @@ pub(crate) fn name_inner(input: &BStr, mode: Mode) -> Result<Cow<'_, BStr>, name
7276
return Err(name::Error::EndsWithSlash);
7377
}
7478
}
79+
if input.get(0) == Some(&b'/') {
80+
if sanitize {
81+
out.to_mut()[0] = b'-';
82+
} else {
83+
return Err(name::Error::StartsWithSlash);
84+
}
85+
}
7586

7687
let mut previous = 0;
7788
let mut out_ofs = 0;
@@ -100,7 +111,14 @@ pub(crate) fn name_inner(input: &BStr, mode: Mode) -> Result<Cow<'_, BStr>, name
100111
out.to_mut().remove(byte_pos);
101112
out_ofs += 1;
102113
} else {
103-
return Err(name::Error::DoubleDot);
114+
return Err(name::Error::RepeatedDot);
115+
}
116+
}
117+
b'.' if previous == b'/' => {
118+
if sanitize {
119+
out.to_mut()[byte_pos] = b'-';
120+
} else {
121+
return Err(name::Error::StartsWithDot);
104122
}
105123
}
106124
b'{' if previous == b'@' => {
@@ -110,6 +128,21 @@ pub(crate) fn name_inner(input: &BStr, mode: Mode) -> Result<Cow<'_, BStr>, name
110128
return Err(name::Error::ReflogPortion);
111129
}
112130
}
131+
b'/' if previous == b'/' => {
132+
if sanitize {
133+
out.to_mut().remove(byte_pos);
134+
out_ofs += 1;
135+
} else {
136+
return Err(name::Error::RepeatedSlash);
137+
}
138+
}
139+
b'.' if previous == b'/' => {
140+
if sanitize {
141+
out.to_mut()[byte_pos] = b'-';
142+
} else {
143+
return Err(name::Error::StartsWithDot);
144+
}
145+
}
113146
_ => {}
114147
}
115148
previous = *byte;

gix-validate/tests/reference/mod.rs

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ mod name_partial {
9494
mktest!(
9595
refs_path_double_dot,
9696
b"refs/../somewhere",
97-
RefError::Tag(TagError::DoubleDot)
97+
RefError::Tag(TagError::StartsWithDot)
9898
);
9999
mktests!(refs_path_double_dot_san, b"refs/../somewhere", "refs/-/somewhere");
100100
mktest!(
@@ -105,7 +105,7 @@ mod name_partial {
105105
mktest!(
106106
refs_path_name_starts_with_multi_dot,
107107
b"..refs/somewhere",
108-
RefError::Tag(TagError::DoubleDot)
108+
RefError::Tag(TagError::RepeatedDot)
109109
);
110110
mktests!(
111111
refs_path_name_starts_with_multi_dot_san,
@@ -120,18 +120,26 @@ mod name_partial {
120120
mktest!(
121121
refs_path_component_is_singular_dot,
122122
b"refs/./still-inside-but-not-cool",
123-
RefError::StartsWithDot
123+
RefError::Tag(TagError::StartsWithDot)
124124
);
125125
mktests!(
126126
refs_path_component_is_singular_dot_san,
127127
b"refs/./still-inside-but-not-cool",
128128
"refs/-/still-inside-but-not-cool"
129129
);
130-
mktest!(any_path_starts_with_slash, b"/etc/foo", RefError::StartsWithSlash);
130+
mktest!(
131+
any_path_starts_with_slash,
132+
b"/etc/foo",
133+
RefError::Tag(TagError::StartsWithSlash)
134+
);
131135
mktests!(any_path_starts_with_slash_san, b"/etc/foo", "-etc/foo");
132136
mktest!(empty_path, b"", RefError::Tag(TagError::Empty));
133137
mktests!(empty_path_san, b"", "-");
134-
mktest!(refs_starts_with_slash, b"/refs/heads/main", RefError::StartsWithSlash);
138+
mktest!(
139+
refs_starts_with_slash,
140+
b"/refs/heads/main",
141+
RefError::Tag(TagError::StartsWithSlash)
142+
);
135143
mktests!(refs_starts_with_slash_san, b"/refs/heads/main", "-refs/heads/main");
136144
mktest!(
137145
ends_with_slash,
@@ -142,12 +150,12 @@ mod name_partial {
142150
mktest!(
143151
path_with_duplicate_slashes,
144152
b"refs//heads/main",
145-
RefError::RepeatedSlash
153+
RefError::Tag(TagError::RepeatedSlash)
146154
);
147155
mktests!(path_with_duplicate_slashes_san, b"refs//heads/main", "refs/heads/main");
148156
mktest!(
149157
path_with_spaces,
150-
b"refs//heads/name with spaces",
158+
b"refs/heads/name with spaces",
151159
RefError::Tag(TagError::InvalidByte { .. })
152160
);
153161
mktests!(
@@ -255,7 +263,7 @@ mod name {
255263
mktest!(
256264
refs_path_double_dot,
257265
b"refs/../somewhere",
258-
RefError::Tag(TagError::DoubleDot)
266+
RefError::Tag(TagError::StartsWithDot)
259267
);
260268
mktests!(refs_path_double_dot_san, b"refs/../somewhere", "refs/-/somewhere");
261269
mktest!(refs_name_special_case_upload_pack, b"(null)", RefError::SomeLowercase);
@@ -274,7 +282,7 @@ mod name {
274282
mktest!(
275283
refs_path_name_starts_with_dot_in_name,
276284
b"refs/.somewhere",
277-
RefError::StartsWithDot
285+
RefError::Tag(TagError::StartsWithDot)
278286
);
279287
mktests!(
280288
refs_path_name_starts_with_dot_in_name_san,
@@ -294,7 +302,7 @@ mod name {
294302
mktest!(
295303
refs_path_component_is_singular_dot,
296304
b"refs/./still-inside-but-not-cool",
297-
RefError::StartsWithDot
305+
RefError::Tag(TagError::StartsWithDot)
298306
);
299307
mktests!(
300308
refs_path_component_is_singular_dot_an,
@@ -305,11 +313,19 @@ mod name {
305313
mktests!(capitalized_name_without_path_san, b"Main", "Main");
306314
mktest!(lowercase_name_without_path, b"main", RefError::SomeLowercase);
307315
mktests!(lowercase_name_without_path_san, b"main", "main");
308-
mktest!(any_path_starts_with_slash, b"/etc/foo", RefError::StartsWithSlash);
316+
mktest!(
317+
any_path_starts_with_slash,
318+
b"/etc/foo",
319+
RefError::Tag(TagError::StartsWithSlash)
320+
);
309321
mktests!(any_path_starts_with_slash_san, b"/etc/foo", "-etc/foo");
310322
mktest!(empty_path, b"", RefError::Tag(TagError::Empty));
311323
mktests!(empty_path_san, b"", "-");
312-
mktest!(refs_starts_with_slash, b"/refs/heads/main", RefError::StartsWithSlash);
324+
mktest!(
325+
refs_starts_with_slash,
326+
b"/refs/heads/main",
327+
RefError::Tag(TagError::StartsWithSlash)
328+
);
313329
mktests!(refs_starts_with_slash_san, b"/refs/heads/main", "-refs/heads/main");
314330
mktest!(
315331
ends_with_slash,
@@ -330,7 +346,7 @@ mod name {
330346
mktest!(
331347
a_path_with_duplicate_slashes,
332348
b"refs//heads/main",
333-
RefError::RepeatedSlash
349+
RefError::Tag(TagError::RepeatedSlash)
334350
);
335351
mktests!(
336352
a_path_with_duplicate_slashes_san,

gix-validate/tests/tag/mod.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,24 +77,28 @@ mod name {
7777
"this_looks_like_a_@-reflog}"
7878
);
7979
mktest!(suffix_is_dot_lock, b"prefix.lock", LockFileSuffix);
80-
mktest!(too_many_dots, b"......", DoubleDot);
80+
mktest!(too_many_dots, b"......", RepeatedDot);
8181
mktests!(too_many_dots_san, b"......", "-");
8282
mktests!(too_many_dots_and_slashes_san, b"//....///....///", "-");
8383
mktests!(suffix_is_dot_lock_san, b"prefix.lock", "prefix");
8484
mktest!(suffix_is_dot_lock_multiple, b"prefix.lock.lock", LockFileSuffix);
8585
mktests!(suffix_is_dot_lock_multiple_san, b"prefix.lock.lock", "prefix");
8686
mktest!(ends_with_slash, b"prefix/", EndsWithSlash);
87+
mktest!(empty_component, b"prefix//suffix", RepeatedSlash);
88+
mktests!(empty_component_san, b"prefix//suffix", "prefix/suffix");
8789
mktests!(ends_with_slash_san, b"prefix/", "prefix");
8890
mktest!(is_dot_lock, b".lock", StartsWithDot);
8991
mktests!(is_dot_lock_san, b".lock", "-lock");
90-
mktest!(contains_double_dot, b"with..double-dot", DoubleDot);
92+
mktest!(contains_double_dot, b"with..double-dot", RepeatedDot);
9193
mktests!(contains_double_dot_san, b"with..double-dot", "with.double-dot");
92-
mktest!(starts_with_double_dot, b"..with-double-dot", DoubleDot);
94+
mktest!(starts_with_double_dot, b"..with-double-dot", RepeatedDot);
9395
mktests!(starts_with_double_dot_san, b"..with-double-dot", "-with-double-dot");
94-
mktest!(ends_with_double_dot, b"with-double-dot..", DoubleDot);
96+
mktest!(ends_with_double_dot, b"with-double-dot..", RepeatedDot);
9597
mktests!(ends_with_double_dot_san, b"with-double-dot..", "with-double-dot-");
9698
mktest!(starts_with_asterisk, b"*suffix", Asterisk);
9799
mktests!(starts_with_asterisk_san, b"*suffix", "-suffix");
100+
mktest!(starts_with_slash, b"/suffix", StartsWithSlash);
101+
mktests!(starts_with_slash_san, b"/suffix", "-suffix");
98102
mktest!(ends_with_asterisk, b"prefix*", Asterisk);
99103
mktests!(ends_with_asterisk_san, b"prefix*", "prefix-");
100104
mktest!(contains_asterisk, b"prefix*suffix", Asterisk);

0 commit comments

Comments
 (0)