1 use crate::backport::*;
2 use crate::error::{ErrorKind, Position};
3 use crate::identifier::Identifier;
4 use crate::{BuildMetadata, Comparator, Op, Prerelease, Version, VersionReq};
5 use core::str::FromStr;
6 
7 /// Error parsing a SemVer version or version requirement.
8 ///
9 /// # Example
10 ///
11 /// ```
12 /// use semver::Version;
13 ///
14 /// fn main() {
15 ///     let err = Version::parse("1.q.r").unwrap_err();
16 ///
17 ///     // "unexpected character 'q' while parsing minor version number"
18 ///     eprintln!("{}", err);
19 /// }
20 /// ```
21 pub struct Error {
22     pub(crate) kind: ErrorKind,
23 }
24 
25 impl FromStr for Version {
26     type Err = Error;
27 
from_str(text: &str) -> Result<Self, Self::Err>28     fn from_str(text: &str) -> Result<Self, Self::Err> {
29         if text.is_empty() {
30             return Err(Error::new(ErrorKind::Empty));
31         }
32 
33         let mut pos = Position::Major;
34         let (major, text) = numeric_identifier(text, pos)?;
35         let text = dot(text, pos)?;
36 
37         pos = Position::Minor;
38         let (minor, text) = numeric_identifier(text, pos)?;
39         let text = dot(text, pos)?;
40 
41         pos = Position::Patch;
42         let (patch, text) = numeric_identifier(text, pos)?;
43 
44         if text.is_empty() {
45             return Ok(Version::new(major, minor, patch));
46         }
47 
48         let (pre, text) = if let Some(text) = text.strip_prefix('-') {
49             pos = Position::Pre;
50             let (pre, text) = prerelease_identifier(text)?;
51             if pre.is_empty() {
52                 return Err(Error::new(ErrorKind::EmptySegment(pos)));
53             }
54             (pre, text)
55         } else {
56             (Prerelease::EMPTY, text)
57         };
58 
59         let (build, text) = if let Some(text) = text.strip_prefix('+') {
60             pos = Position::Build;
61             let (build, text) = build_identifier(text)?;
62             if build.is_empty() {
63                 return Err(Error::new(ErrorKind::EmptySegment(pos)));
64             }
65             (build, text)
66         } else {
67             (BuildMetadata::EMPTY, text)
68         };
69 
70         if let Some(unexpected) = text.chars().next() {
71             return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)));
72         }
73 
74         Ok(Version {
75             major,
76             minor,
77             patch,
78             pre,
79             build,
80         })
81     }
82 }
83 
84 impl FromStr for VersionReq {
85     type Err = Error;
86 
from_str(text: &str) -> Result<Self, Self::Err>87     fn from_str(text: &str) -> Result<Self, Self::Err> {
88         let text = text.trim_start_matches(' ');
89         if let Some((ch, text)) = wildcard(text) {
90             let rest = text.trim_start_matches(' ');
91             if rest.is_empty() {
92                 #[cfg(not(no_const_vec_new))]
93                 return Ok(VersionReq::STAR);
94                 #[cfg(no_const_vec_new)] // rustc <1.39
95                 return Ok(VersionReq {
96                     comparators: Vec::new(),
97                 });
98             } else if rest.starts_with(',') {
99                 return Err(Error::new(ErrorKind::WildcardNotTheOnlyComparator(ch)));
100             } else {
101                 return Err(Error::new(ErrorKind::UnexpectedAfterWildcard));
102             }
103         }
104 
105         let depth = 0;
106         let mut comparators = Vec::new();
107         let len = version_req(text, &mut comparators, depth)?;
108         unsafe { comparators.set_len(len) }
109         Ok(VersionReq { comparators })
110     }
111 }
112 
113 impl FromStr for Comparator {
114     type Err = Error;
115 
from_str(text: &str) -> Result<Self, Self::Err>116     fn from_str(text: &str) -> Result<Self, Self::Err> {
117         let text = text.trim_start_matches(' ');
118         let (comparator, pos, rest) = comparator(text)?;
119         if !rest.is_empty() {
120             let unexpected = rest.chars().next().unwrap();
121             return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)));
122         }
123         Ok(comparator)
124     }
125 }
126 
127 impl FromStr for Prerelease {
128     type Err = Error;
129 
from_str(text: &str) -> Result<Self, Self::Err>130     fn from_str(text: &str) -> Result<Self, Self::Err> {
131         let (pre, rest) = prerelease_identifier(text)?;
132         if !rest.is_empty() {
133             return Err(Error::new(ErrorKind::IllegalCharacter(Position::Pre)));
134         }
135         Ok(pre)
136     }
137 }
138 
139 impl FromStr for BuildMetadata {
140     type Err = Error;
141 
from_str(text: &str) -> Result<Self, Self::Err>142     fn from_str(text: &str) -> Result<Self, Self::Err> {
143         let (build, rest) = build_identifier(text)?;
144         if !rest.is_empty() {
145             return Err(Error::new(ErrorKind::IllegalCharacter(Position::Build)));
146         }
147         Ok(build)
148     }
149 }
150 
151 impl Error {
new(kind: ErrorKind) -> Self152     fn new(kind: ErrorKind) -> Self {
153         Error { kind }
154     }
155 }
156 
157 impl Op {
158     const DEFAULT: Self = Op::Caret;
159 }
160 
numeric_identifier(input: &str, pos: Position) -> Result<(u64, &str), Error>161 fn numeric_identifier(input: &str, pos: Position) -> Result<(u64, &str), Error> {
162     let mut len = 0;
163     let mut value = 0u64;
164 
165     while let Some(&digit) = input.as_bytes().get(len) {
166         if digit < b'0' || digit > b'9' {
167             break;
168         }
169         if value == 0 && len > 0 {
170             return Err(Error::new(ErrorKind::LeadingZero(pos)));
171         }
172         match value
173             .checked_mul(10)
174             .and_then(|value| value.checked_add((digit - b'0') as u64))
175         {
176             Some(sum) => value = sum,
177             None => return Err(Error::new(ErrorKind::Overflow(pos))),
178         }
179         len += 1;
180     }
181 
182     if len > 0 {
183         Ok((value, &input[len..]))
184     } else if let Some(unexpected) = input[len..].chars().next() {
185         Err(Error::new(ErrorKind::UnexpectedChar(pos, unexpected)))
186     } else {
187         Err(Error::new(ErrorKind::UnexpectedEnd(pos)))
188     }
189 }
190 
wildcard(input: &str) -> Option<(char, &str)>191 fn wildcard(input: &str) -> Option<(char, &str)> {
192     if let Some(rest) = input.strip_prefix('*') {
193         Some(('*', rest))
194     } else if let Some(rest) = input.strip_prefix('x') {
195         Some(('x', rest))
196     } else if let Some(rest) = input.strip_prefix('X') {
197         Some(('X', rest))
198     } else {
199         None
200     }
201 }
202 
dot(input: &str, pos: Position) -> Result<&str, Error>203 fn dot(input: &str, pos: Position) -> Result<&str, Error> {
204     if let Some(rest) = input.strip_prefix('.') {
205         Ok(rest)
206     } else if let Some(unexpected) = input.chars().next() {
207         Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)))
208     } else {
209         Err(Error::new(ErrorKind::UnexpectedEnd(pos)))
210     }
211 }
212 
prerelease_identifier(input: &str) -> Result<(Prerelease, &str), Error>213 fn prerelease_identifier(input: &str) -> Result<(Prerelease, &str), Error> {
214     let (string, rest) = identifier(input, Position::Pre)?;
215     let identifier = unsafe { Identifier::new_unchecked(string) };
216     Ok((Prerelease { identifier }, rest))
217 }
218 
build_identifier(input: &str) -> Result<(BuildMetadata, &str), Error>219 fn build_identifier(input: &str) -> Result<(BuildMetadata, &str), Error> {
220     let (string, rest) = identifier(input, Position::Build)?;
221     let identifier = unsafe { Identifier::new_unchecked(string) };
222     Ok((BuildMetadata { identifier }, rest))
223 }
224 
identifier(input: &str, pos: Position) -> Result<(&str, &str), Error>225 fn identifier(input: &str, pos: Position) -> Result<(&str, &str), Error> {
226     let mut accumulated_len = 0;
227     let mut segment_len = 0;
228     let mut segment_has_nondigit = false;
229 
230     loop {
231         match input.as_bytes().get(accumulated_len + segment_len) {
232             Some(b'A'..=b'Z') | Some(b'a'..=b'z') | Some(b'-') => {
233                 segment_len += 1;
234                 segment_has_nondigit = true;
235             }
236             Some(b'0'..=b'9') => {
237                 segment_len += 1;
238             }
239             boundary => {
240                 if segment_len == 0 {
241                     if accumulated_len == 0 && boundary != Some(&b'.') {
242                         return Ok(("", input));
243                     } else {
244                         return Err(Error::new(ErrorKind::EmptySegment(pos)));
245                     }
246                 }
247                 if pos == Position::Pre
248                     && segment_len > 1
249                     && !segment_has_nondigit
250                     && input[accumulated_len..].starts_with('0')
251                 {
252                     return Err(Error::new(ErrorKind::LeadingZero(pos)));
253                 }
254                 accumulated_len += segment_len;
255                 if boundary == Some(&b'.') {
256                     accumulated_len += 1;
257                     segment_len = 0;
258                     segment_has_nondigit = false;
259                 } else {
260                     return Ok(input.split_at(accumulated_len));
261                 }
262             }
263         }
264     }
265 }
266 
op(input: &str) -> (Op, &str)267 fn op(input: &str) -> (Op, &str) {
268     let bytes = input.as_bytes();
269     if bytes.first() == Some(&b'=') {
270         (Op::Exact, &input[1..])
271     } else if bytes.first() == Some(&b'>') {
272         if bytes.get(1) == Some(&b'=') {
273             (Op::GreaterEq, &input[2..])
274         } else {
275             (Op::Greater, &input[1..])
276         }
277     } else if bytes.first() == Some(&b'<') {
278         if bytes.get(1) == Some(&b'=') {
279             (Op::LessEq, &input[2..])
280         } else {
281             (Op::Less, &input[1..])
282         }
283     } else if bytes.first() == Some(&b'~') {
284         (Op::Tilde, &input[1..])
285     } else if bytes.first() == Some(&b'^') {
286         (Op::Caret, &input[1..])
287     } else {
288         (Op::DEFAULT, input)
289     }
290 }
291 
comparator(input: &str) -> Result<(Comparator, Position, &str), Error>292 fn comparator(input: &str) -> Result<(Comparator, Position, &str), Error> {
293     let (mut op, text) = op(input);
294     let default_op = input.len() == text.len();
295     let text = text.trim_start_matches(' ');
296 
297     let mut pos = Position::Major;
298     let (major, text) = numeric_identifier(text, pos)?;
299     let mut has_wildcard = false;
300 
301     let (minor, text) = if let Some(text) = text.strip_prefix('.') {
302         pos = Position::Minor;
303         if let Some((_, text)) = wildcard(text) {
304             has_wildcard = true;
305             if default_op {
306                 op = Op::Wildcard;
307             }
308             (None, text)
309         } else {
310             let (minor, text) = numeric_identifier(text, pos)?;
311             (Some(minor), text)
312         }
313     } else {
314         (None, text)
315     };
316 
317     let (patch, text) = if let Some(text) = text.strip_prefix('.') {
318         pos = Position::Patch;
319         if let Some((_, text)) = wildcard(text) {
320             if default_op {
321                 op = Op::Wildcard;
322             }
323             (None, text)
324         } else if has_wildcard {
325             return Err(Error::new(ErrorKind::UnexpectedAfterWildcard));
326         } else {
327             let (patch, text) = numeric_identifier(text, pos)?;
328             (Some(patch), text)
329         }
330     } else {
331         (None, text)
332     };
333 
334     let (pre, text) = if patch.is_some() && text.starts_with('-') {
335         pos = Position::Pre;
336         let text = &text[1..];
337         let (pre, text) = prerelease_identifier(text)?;
338         if pre.is_empty() {
339             return Err(Error::new(ErrorKind::EmptySegment(pos)));
340         }
341         (pre, text)
342     } else {
343         (Prerelease::EMPTY, text)
344     };
345 
346     let text = if patch.is_some() && text.starts_with('+') {
347         pos = Position::Build;
348         let text = &text[1..];
349         let (build, text) = build_identifier(text)?;
350         if build.is_empty() {
351             return Err(Error::new(ErrorKind::EmptySegment(pos)));
352         }
353         text
354     } else {
355         text
356     };
357 
358     let text = text.trim_start_matches(' ');
359 
360     let comparator = Comparator {
361         op,
362         major,
363         minor,
364         patch,
365         pre,
366     };
367 
368     Ok((comparator, pos, text))
369 }
370 
version_req(input: &str, out: &mut Vec<Comparator>, depth: usize) -> Result<usize, Error>371 fn version_req(input: &str, out: &mut Vec<Comparator>, depth: usize) -> Result<usize, Error> {
372     let (comparator, pos, text) = match comparator(input) {
373         Ok(success) => success,
374         Err(mut error) => {
375             if let Some((ch, mut rest)) = wildcard(input) {
376                 rest = rest.trim_start_matches(' ');
377                 if rest.is_empty() || rest.starts_with(',') {
378                     error.kind = ErrorKind::WildcardNotTheOnlyComparator(ch);
379                 }
380             }
381             return Err(error);
382         }
383     };
384 
385     if text.is_empty() {
386         out.reserve_exact(depth + 1);
387         unsafe { out.as_mut_ptr().add(depth).write(comparator) }
388         return Ok(depth + 1);
389     }
390 
391     let text = if let Some(text) = text.strip_prefix(',') {
392         text.trim_start_matches(' ')
393     } else {
394         let unexpected = text.chars().next().unwrap();
395         return Err(Error::new(ErrorKind::ExpectedCommaFound(pos, unexpected)));
396     };
397 
398     const MAX_COMPARATORS: usize = 32;
399     if depth + 1 == MAX_COMPARATORS {
400         return Err(Error::new(ErrorKind::ExcessiveComparators));
401     }
402 
403     // Recurse to collect parsed Comparator objects on the stack. We perform a
404     // single allocation to allocate exactly the right sized Vec only once the
405     // total number of comparators is known.
406     let len = version_req(text, out, depth + 1)?;
407     unsafe { out.as_mut_ptr().add(depth).write(comparator) }
408     Ok(len)
409 }
410