1use gix_error::{OptionExt, Result, bail, message, validation};
2use std::borrow::Cow;
3
4use bstr::{BStr, BString, ByteSlice};
5
6use crate::{Defaults, MagicSignature, Pattern, SearchMode};
7
8impl Pattern {
9 pub fn from_bytes(
13 input: &[u8],
14 Defaults {
15 signature,
16 search_mode,
17 literal,
18 }: Defaults,
19 ) -> Result<Self> {
20 if input.is_empty() {
21 bail!(validation("An empty string is not a valid pathspec").with_input(input));
22 }
23 if literal {
24 return Ok(Self::from_literal(input, signature));
25 }
26 if input.as_bstr() == ":" {
27 return Ok(Pattern {
28 nil: true,
29 ..Default::default()
30 });
31 }
32
33 let mut p = Pattern {
34 signature,
35 search_mode: SearchMode::default(),
36 ..Default::default()
37 };
38
39 let mut cursor = 0;
40 if input.first() == Some(&b':') {
41 cursor += 1;
42 p.signature |= parse_short_keywords(input, &mut cursor)?;
43 if let Some(b'(') = input.get(cursor) {
44 cursor += 1;
45 parse_long_keywords(input, &mut p, &mut cursor)?;
46 }
47 }
48
49 if search_mode != Default::default() && p.search_mode == Default::default() {
50 p.search_mode = search_mode;
51 }
52 let mut path = &input[cursor..];
53 if path.last() == Some(&b'/') {
54 p.signature |= MagicSignature::MUST_BE_DIR;
55 path = &path[..path.len() - 1];
56 }
57 p.path = path.into();
58 Ok(p)
59 }
60
61 pub fn from_literal(input: &[u8], default_signature: MagicSignature) -> Self {
64 Pattern {
65 path: input.into(),
66 signature: default_signature,
67 search_mode: SearchMode::Literal,
68 ..Default::default()
69 }
70 }
71}
72
73fn parse_short_keywords(input: &[u8], cursor: &mut usize) -> Result<MagicSignature> {
74 let unimplemented_chars = b"\"#%&'-',;<=>@_`~";
75
76 let mut signature = MagicSignature::empty();
77 while let Some(&b) = input.get(*cursor) {
78 *cursor += 1;
79 signature |= match b {
80 b'/' => MagicSignature::TOP,
81 b'^' | b'!' => MagicSignature::EXCLUDE,
82 b':' => break,
83 _ if unimplemented_chars.contains(&b) => {
84 bail!(validation("Unimplemented short keyword").with_input(vec![b]));
85 }
86 _ => {
87 *cursor -= 1;
88 break;
89 }
90 }
91 }
92
93 Ok(signature)
94}
95
96fn parse_long_keywords(input: &[u8], p: &mut Pattern, cursor: &mut usize) -> Result {
97 let end = input
98 .find(")")
99 .ok_or_raise(|| validation("Missing ')' at the end of pathspec signature").with_input(input))?;
100
101 let input = &input[*cursor..end];
102 *cursor = end + 1;
103
104 if input.is_empty() {
105 return Ok(());
106 }
107
108 split_on_non_escaped_char(input, b',', |keyword| {
109 if keyword.is_empty() {
112 return Ok(());
113 }
114 let attr_prefix = b"attr:";
115 match keyword {
116 b"attr" => {}
117 b"top" => p.signature |= MagicSignature::TOP,
118 b"icase" => p.signature |= MagicSignature::ICASE,
119 b"exclude" => p.signature |= MagicSignature::EXCLUDE,
120 b"literal" => match p.search_mode {
121 SearchMode::PathAwareGlob => {
122 bail!(
123 validation("'literal' and 'glob' keywords cannot be used together in the same pathspec",)
124 .with_input(keyword)
125 );
126 }
127 _ => p.search_mode = SearchMode::Literal,
128 },
129 b"glob" => match p.search_mode {
130 SearchMode::Literal => {
131 bail!(
132 validation("'literal' and 'glob' keywords cannot be used together in the same pathspec",)
133 .with_input(keyword)
134 );
135 }
136 _ => p.search_mode = SearchMode::PathAwareGlob,
137 },
138 _ if keyword.starts_with(attr_prefix) => {
139 if p.attributes.is_empty() {
140 p.attributes = parse_attributes(&keyword[attr_prefix.len()..])?;
141 } else {
142 bail!(
143 validation("Only one attribute specification is allowed in the same pathspec",)
144 .with_input(keyword)
145 );
146 }
147 }
148 _ => {
149 bail!(validation("Found invalid keyword in pathspec signature").with_input(keyword));
150 }
151 }
152 Ok(())
153 })
154}
155
156fn split_on_non_escaped_char(input: &[u8], split_char: u8, mut f: impl FnMut(&[u8]) -> Result) -> Result {
157 let mut i = 0;
162 let mut last = 0;
163 while i < input.len() {
164 if input[i] == b'\\' {
165 i += 2;
166 continue;
167 }
168 if input[i] == split_char {
169 f(&input[last..i])?;
170 last = i + 1;
171 }
172 i += 1;
173 }
174 f(&input[last..])
175}
176
177fn parse_attributes(input: &[u8]) -> Result<Vec<gix_attributes::Assignment>> {
178 if input.is_empty() {
179 bail!(validation("Attribute specification cannot be empty"));
180 }
181
182 input
183 .split(|&b| b == b' ')
184 .filter(|attr| !attr.is_empty())
185 .map(|attr| {
186 let (name, state) = match attr.first() {
187 Some(b'!') => (&attr[1..], gix_attributes::State::Unspecified),
188 Some(b'-') => (&attr[1..], gix_attributes::State::Unset),
189 _ => match attr.find_byte(b'=') {
190 Some(pos) => {
191 let (name, value) = attr.split_at(pos);
192 let value = &value[1..];
193 let value = if value.contains(&b'\\') {
194 Cow::Owned(unescape_and_check_attr_value(value.into())?)
195 } else {
196 check_attribute_value(value.into())?;
197 Cow::Borrowed(value.as_bstr())
198 };
199 (name, gix_attributes::StateRef::from_bytes(value.as_ref()).to_owned())
200 }
201 None => (attr, gix_attributes::State::Set),
202 },
203 };
204 let name = gix_attributes::NameRef::try_from(name.as_bstr())?;
205 Ok(gix_attributes::Assignment {
206 name: name.to_owned(),
207 state,
208 })
209 })
210 .collect()
211}
212
213fn unescape_and_check_attr_value(value: &BStr) -> Result<BString> {
214 let mut out = BString::from(Vec::with_capacity(value.len()));
215 let mut bytes = value.iter();
216 while let Some(mut b) = bytes.next().copied() {
217 if b == b'\\' {
218 b = *bytes.next().ok_or_raise(|| {
219 message!(r"Escape character '\' is not allowed as the last character in an attribute value")
220 .validation()
221 .with_input(value)
222 })?;
223 }
224
225 out.push(validated_attr_value_byte(b)?);
226 }
227 Ok(out)
228}
229
230fn check_attribute_value(input: &BStr) -> Result {
231 match input.iter().copied().find(|b| !is_valid_attr_value(*b)) {
232 Some(b) => Err(message("Invalid character in attribute value")
233 .with_input(vec![b])
234 .validation_error()),
235 None => Ok(()),
236 }
237}
238
239fn is_valid_attr_value(byte: u8) -> bool {
240 byte.is_ascii_alphanumeric() || b",-_".contains(&byte)
241}
242
243fn validated_attr_value_byte(byte: u8) -> Result<u8> {
244 if is_valid_attr_value(byte) {
245 Ok(byte)
246 } else {
247 Err(message("Invalid character in attribute value")
248 .with_input(vec![byte])
249 .validation_error())
250 }
251}