|
1 |
| -use bstr::BStr; |
| 1 | +use bstr::{BStr, ByteSlice}; |
| 2 | +use std::borrow::Cow; |
2 | 3 |
|
3 | 4 | ///
|
4 | 5 | #[allow(clippy::empty_docs)]
|
@@ -33,36 +34,110 @@ pub mod name {
|
33 | 34 | /// Assure the given `input` resemble a valid git tag name, which is returned unchanged on success.
|
34 | 35 | /// Tag names are provided as names, lik` v1.0` or `alpha-1`, without paths.
|
35 | 36 | pub fn name(input: &BStr) -> Result<&BStr, name::Error> {
|
| 37 | + match name_inner(input, Mode::Validate)? { |
| 38 | + Cow::Borrowed(inner) => Ok(inner), |
| 39 | + Cow::Owned(_) => { |
| 40 | + unreachable!("When validating, the input isn't changed") |
| 41 | + } |
| 42 | + } |
| 43 | +} |
| 44 | + |
| 45 | +#[derive(Eq, PartialEq)] |
| 46 | +pub(crate) enum Mode { |
| 47 | + Sanitize, |
| 48 | + Validate, |
| 49 | +} |
| 50 | + |
| 51 | +pub(crate) fn name_inner(input: &BStr, mode: Mode) -> Result<Cow<'_, BStr>, name::Error> { |
| 52 | + let mut out = Cow::Borrowed(input); |
| 53 | + let sanitize = matches!(mode, Mode::Sanitize); |
36 | 54 | if input.is_empty() {
|
37 |
| - return Err(name::Error::Empty); |
| 55 | + return if sanitize { |
| 56 | + out.to_mut().push(b'-'); |
| 57 | + Ok(out) |
| 58 | + } else { |
| 59 | + Err(name::Error::Empty) |
| 60 | + }; |
38 | 61 | }
|
39 | 62 | if *input.last().expect("non-empty") == b'/' {
|
40 |
| - return Err(name::Error::EndsWithSlash); |
| 63 | + if sanitize { |
| 64 | + while out.last() == Some(&b'/') { |
| 65 | + out.to_mut().pop(); |
| 66 | + } |
| 67 | + let bytes_from_end = out.to_mut().as_bytes_mut().iter_mut().rev(); |
| 68 | + for b in bytes_from_end.take_while(|b| **b == b'/') { |
| 69 | + *b = b'-'; |
| 70 | + } |
| 71 | + } else { |
| 72 | + return Err(name::Error::EndsWithSlash); |
| 73 | + } |
41 | 74 | }
|
42 | 75 |
|
43 | 76 | let mut previous = 0;
|
44 |
| - for byte in input.iter() { |
| 77 | + let mut out_ofs = 0; |
| 78 | + for (mut byte_pos, byte) in input.iter().enumerate() { |
| 79 | + byte_pos -= out_ofs; |
45 | 80 | match byte {
|
46 | 81 | b'\\' | b'^' | b':' | b'[' | b'?' | b' ' | b'~' | b'\0'..=b'\x1F' | b'\x7F' => {
|
47 |
| - return Err(name::Error::InvalidByte { |
48 |
| - byte: (&[*byte][..]).into(), |
49 |
| - }) |
| 82 | + if sanitize { |
| 83 | + out.to_mut()[byte_pos] = b'-'; |
| 84 | + } else { |
| 85 | + return Err(name::Error::InvalidByte { |
| 86 | + byte: (&[*byte][..]).into(), |
| 87 | + }); |
| 88 | + } |
| 89 | + } |
| 90 | + b'*' => { |
| 91 | + if sanitize { |
| 92 | + out.to_mut()[byte_pos] = b'-'; |
| 93 | + } else { |
| 94 | + return Err(name::Error::Asterisk); |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + b'.' if previous == b'.' => { |
| 99 | + if sanitize { |
| 100 | + out.to_mut().remove(byte_pos); |
| 101 | + out_ofs += 1; |
| 102 | + } else { |
| 103 | + return Err(name::Error::DoubleDot); |
| 104 | + } |
| 105 | + } |
| 106 | + b'{' if previous == b'@' => { |
| 107 | + if sanitize { |
| 108 | + out.to_mut()[byte_pos] = b'-'; |
| 109 | + } else { |
| 110 | + return Err(name::Error::ReflogPortion); |
| 111 | + } |
50 | 112 | }
|
51 |
| - b'*' => return Err(name::Error::Asterisk), |
52 |
| - b'.' if previous == b'.' => return Err(name::Error::DoubleDot), |
53 |
| - b'{' if previous == b'@' => return Err(name::Error::ReflogPortion), |
54 | 113 | _ => {}
|
55 | 114 | }
|
56 | 115 | previous = *byte;
|
57 | 116 | }
|
58 | 117 | if input[0] == b'.' {
|
59 |
| - return Err(name::Error::StartsWithDot); |
| 118 | + if sanitize { |
| 119 | + out.to_mut()[0] = b'-'; |
| 120 | + } else { |
| 121 | + return Err(name::Error::StartsWithDot); |
| 122 | + } |
60 | 123 | }
|
61 | 124 | if input[input.len() - 1] == b'.' {
|
62 |
| - return Err(name::Error::EndsWithDot); |
| 125 | + if sanitize { |
| 126 | + let last = out.len() - 1; |
| 127 | + out.to_mut()[last] = b'-'; |
| 128 | + } else { |
| 129 | + return Err(name::Error::EndsWithDot); |
| 130 | + } |
63 | 131 | }
|
64 | 132 | if input.ends_with(b".lock") {
|
65 |
| - return Err(name::Error::LockFileSuffix); |
| 133 | + if sanitize { |
| 134 | + while out.ends_with(b".lock") { |
| 135 | + let len_without_suffix = out.len() - b".lock".len(); |
| 136 | + out.to_mut().truncate(len_without_suffix); |
| 137 | + } |
| 138 | + } else { |
| 139 | + return Err(name::Error::LockFileSuffix); |
| 140 | + } |
66 | 141 | }
|
67 |
| - Ok(input) |
| 142 | + Ok(out) |
68 | 143 | }
|
0 commit comments