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