1 // Copyright 2013-2014 The Rust Project Developers.
2 // Copyright 2018 The Uuid Project Developers.
3 //
4 // See the COPYRIGHT file at the top-level directory of this distribution.
5 //
6 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
7 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
8 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
9 // option. This file may not be copied, modified, or distributed
10 // except according to those terms.
11 
12 //! [`Uuid`] parsing constructs and utilities.
13 //!
14 //! [`Uuid`]: ../struct.Uuid.html
15 
16 pub(crate) mod error;
17 pub(crate) use self::error::Error;
18 
19 use crate::{adapter, Uuid};
20 
21 /// Check if the length matches any of the given criteria lengths.
len_matches_any(len: usize, crits: &[usize]) -> bool22 fn len_matches_any(len: usize, crits: &[usize]) -> bool {
23     for crit in crits {
24         if len == *crit {
25             return true;
26         }
27     }
28 
29     false
30 }
31 
32 /// Check if the length matches any criteria lengths in the given range
33 /// (inclusive).
34 #[allow(dead_code)]
len_matches_range(len: usize, min: usize, max: usize) -> bool35 fn len_matches_range(len: usize, min: usize, max: usize) -> bool {
36     for crit in min..(max + 1) {
37         if len == crit {
38             return true;
39         }
40     }
41 
42     false
43 }
44 
45 // Accumulated length of each hyphenated group in hex digits.
46 const ACC_GROUP_LENS: [usize; 5] = [8, 12, 16, 20, 32];
47 
48 // Length of each hyphenated group in hex digits.
49 const GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12];
50 
51 impl Uuid {
52     /// Parses a `Uuid` from a string of hexadecimal digits with optional
53     /// hyphens.
54     ///
55     /// Any of the formats generated by this module (simple, hyphenated, urn)
56     /// are supported by this parsing function.
parse_str(mut input: &str) -> Result<Uuid, crate::Error>57     pub fn parse_str(mut input: &str) -> Result<Uuid, crate::Error> {
58         // Ensure length is valid for any of the supported formats
59         let len = input.len();
60 
61         if len == adapter::Urn::LENGTH && input.starts_with("urn:uuid:") {
62             input = &input[9..];
63         } else if !len_matches_any(
64             len,
65             &[adapter::Hyphenated::LENGTH, adapter::Simple::LENGTH],
66         ) {
67             Err(Error::InvalidLength {
68                 expected: error::ExpectedLength::Any(&[
69                     adapter::Hyphenated::LENGTH,
70                     adapter::Simple::LENGTH,
71                 ]),
72                 found: len,
73             })?;
74         }
75 
76         // `digit` counts only hexadecimal digits, `i_char` counts all chars.
77         let mut digit = 0;
78         let mut group = 0;
79         let mut acc = 0;
80         let mut buffer = [0u8; 16];
81 
82         for (i_char, chr) in input.bytes().enumerate() {
83             if digit as usize >= adapter::Simple::LENGTH && group != 4 {
84                 if group == 0 {
85                     Err(Error::InvalidLength {
86                         expected: error::ExpectedLength::Any(&[
87                             adapter::Hyphenated::LENGTH,
88                             adapter::Simple::LENGTH,
89                         ]),
90                         found: len,
91                     })?;
92                 }
93 
94                 Err(Error::InvalidGroupCount {
95                     expected: error::ExpectedLength::Any(&[1, 5]),
96                     found: group + 1,
97                 })?;
98             }
99 
100             if digit % 2 == 0 {
101                 // First digit of the byte.
102                 match chr {
103                     // Calulate upper half.
104                     b'0'..=b'9' => acc = chr - b'0',
105                     b'a'..=b'f' => acc = chr - b'a' + 10,
106                     b'A'..=b'F' => acc = chr - b'A' + 10,
107                     // Found a group delimiter
108                     b'-' => {
109                         // TODO: remove the u8 cast
110                         // BODY: this only needed until we switch to
111                         //       ParseError
112                         if ACC_GROUP_LENS[group] as u8 != digit {
113                             // Calculate how many digits this group consists of
114                             // in the input.
115                             let found = if group > 0 {
116                                 // TODO: remove the u8 cast
117                                 // BODY: this only needed until we switch to
118                                 //       ParseError
119                                 digit - ACC_GROUP_LENS[group - 1] as u8
120                             } else {
121                                 digit
122                             };
123 
124                             Err(Error::InvalidGroupLength {
125                                 expected: error::ExpectedLength::Exact(
126                                     GROUP_LENS[group],
127                                 ),
128                                 found: found as usize,
129                                 group,
130                             })?;
131                         }
132                         // Next group, decrement digit, it is incremented again
133                         // at the bottom.
134                         group += 1;
135                         digit -= 1;
136                     }
137                     _ => {
138                         Err(Error::InvalidCharacter {
139                             expected: "0123456789abcdefABCDEF-",
140                             found: input[i_char..].chars().next().unwrap(),
141                             index: i_char,
142                             urn: error::UrnPrefix::Optional,
143                         })?;
144                     }
145                 }
146             } else {
147                 // Second digit of the byte, shift the upper half.
148                 acc *= 16;
149                 match chr {
150                     b'0'..=b'9' => acc += chr - b'0',
151                     b'a'..=b'f' => acc += chr - b'a' + 10,
152                     b'A'..=b'F' => acc += chr - b'A' + 10,
153                     b'-' => {
154                         // The byte isn't complete yet.
155                         let found = if group > 0 {
156                             // TODO: remove the u8 cast
157                             // BODY: this only needed until we switch to
158                             //       ParseError
159                             digit - ACC_GROUP_LENS[group - 1] as u8
160                         } else {
161                             digit
162                         };
163 
164                         Err(Error::InvalidGroupLength {
165                             expected: error::ExpectedLength::Exact(
166                                 GROUP_LENS[group],
167                             ),
168                             found: found as usize,
169                             group,
170                         })?;
171                     }
172                     _ => {
173                         Err(Error::InvalidCharacter {
174                             expected: "0123456789abcdefABCDEF-",
175                             found: input[i_char..].chars().next().unwrap(),
176                             index: i_char,
177                             urn: error::UrnPrefix::Optional,
178                         })?;
179                     }
180                 }
181                 buffer[(digit / 2) as usize] = acc;
182             }
183             digit += 1;
184         }
185 
186         // Now check the last group.
187         // TODO: remove the u8 cast
188         // BODY: this only needed until we switch to
189         //       ParseError
190         if ACC_GROUP_LENS[4] as u8 != digit {
191             Err(Error::InvalidGroupLength {
192                 expected: error::ExpectedLength::Exact(GROUP_LENS[4]),
193                 found: (digit as usize - ACC_GROUP_LENS[3]),
194                 group,
195             })?;
196         }
197 
198         Ok(Uuid::from_bytes(buffer))
199     }
200 }
201 
202 #[cfg(test)]
203 mod tests {
204     use super::*;
205     use crate::{adapter, std::string::ToString, test_util};
206 
207     #[test]
test_parse_uuid_v4()208     fn test_parse_uuid_v4() {
209         const EXPECTED_UUID_LENGTHS: error::ExpectedLength =
210             error::ExpectedLength::Any(&[
211                 adapter::Hyphenated::LENGTH,
212                 adapter::Simple::LENGTH,
213             ]);
214 
215         const EXPECTED_GROUP_COUNTS: error::ExpectedLength =
216             error::ExpectedLength::Any(&[1, 5]);
217 
218         const EXPECTED_CHARS: &'static str = "0123456789abcdefABCDEF-";
219 
220         // Invalid
221         assert_eq!(
222             Uuid::parse_str("").map_err(crate::Error::expect_parser),
223             Err(Error::InvalidLength {
224                 expected: EXPECTED_UUID_LENGTHS,
225                 found: 0,
226             })
227         );
228 
229         assert_eq!(
230             Uuid::parse_str("!").map_err(crate::Error::expect_parser),
231             Err(Error::InvalidLength {
232                 expected: EXPECTED_UUID_LENGTHS,
233                 found: 1
234             })
235         );
236 
237         assert_eq!(
238             Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E45")
239                 .map_err(crate::Error::expect_parser),
240             Err(Error::InvalidLength {
241                 expected: EXPECTED_UUID_LENGTHS,
242                 found: 37,
243             })
244         );
245 
246         assert_eq!(
247             Uuid::parse_str("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4")
248                 .map_err(crate::Error::expect_parser),
249             Err(Error::InvalidLength {
250                 expected: EXPECTED_UUID_LENGTHS,
251                 found: 35
252             })
253         );
254 
255         assert_eq!(
256             Uuid::parse_str("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4")
257                 .map_err(crate::Error::expect_parser),
258             Err(Error::InvalidCharacter {
259                 expected: EXPECTED_CHARS,
260                 found: 'G',
261                 index: 20,
262                 urn: error::UrnPrefix::Optional,
263             })
264         );
265 
266         assert_eq!(
267             Uuid::parse_str("F9168C5E-CEB2F4faaFB6BFF329BF39FA1E4")
268                 .map_err(crate::Error::expect_parser),
269             Err(Error::InvalidGroupCount {
270                 expected: EXPECTED_GROUP_COUNTS,
271                 found: 2
272             })
273         );
274 
275         assert_eq!(
276             Uuid::parse_str("F9168C5E-CEB2-4faaFB6BFF329BF39FA1E4")
277                 .map_err(crate::Error::expect_parser),
278             Err(Error::InvalidGroupCount {
279                 expected: EXPECTED_GROUP_COUNTS,
280                 found: 3,
281             })
282         );
283 
284         assert_eq!(
285             Uuid::parse_str("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4")
286                 .map_err(crate::Error::expect_parser),
287             Err(Error::InvalidGroupCount {
288                 expected: EXPECTED_GROUP_COUNTS,
289                 found: 4,
290             })
291         );
292 
293         assert_eq!(
294             Uuid::parse_str("F9168C5E-CEB2-4faa")
295                 .map_err(crate::Error::expect_parser),
296             Err(Error::InvalidLength {
297                 expected: EXPECTED_UUID_LENGTHS,
298                 found: 18,
299             })
300         );
301 
302         assert_eq!(
303             Uuid::parse_str("F9168C5E-CEB2-4faaXB6BFF329BF39FA1E4")
304                 .map_err(crate::Error::expect_parser),
305             Err(Error::InvalidCharacter {
306                 expected: EXPECTED_CHARS,
307                 found: 'X',
308                 index: 18,
309                 urn: error::UrnPrefix::Optional,
310             })
311         );
312 
313         assert_eq!(
314             Uuid::parse_str("F9168C5E-CEB-24fa-eB6BFF32-BF39FA1E4")
315                 .map_err(crate::Error::expect_parser),
316             Err(Error::InvalidGroupLength {
317                 expected: error::ExpectedLength::Exact(4),
318                 found: 3,
319                 group: 1,
320             })
321         );
322         // (group, found, expecting)
323         //
324         assert_eq!(
325             Uuid::parse_str("01020304-1112-2122-3132-41424344")
326                 .map_err(crate::Error::expect_parser),
327             Err(Error::InvalidGroupLength {
328                 expected: error::ExpectedLength::Exact(12),
329                 found: 8,
330                 group: 4,
331             })
332         );
333 
334         assert_eq!(
335             Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c")
336                 .map_err(crate::Error::expect_parser),
337             Err(Error::InvalidLength {
338                 expected: EXPECTED_UUID_LENGTHS,
339                 found: 31,
340             })
341         );
342 
343         assert_eq!(
344             Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c88")
345                 .map_err(crate::Error::expect_parser),
346             Err(Error::InvalidLength {
347                 expected: EXPECTED_UUID_LENGTHS,
348                 found: 33,
349             })
350         );
351 
352         assert_eq!(
353             Uuid::parse_str("67e5504410b1426f9247bb680e5fe0cg8")
354                 .map_err(crate::Error::expect_parser),
355             Err(Error::InvalidLength {
356                 expected: EXPECTED_UUID_LENGTHS,
357                 found: 33,
358             })
359         );
360 
361         assert_eq!(
362             Uuid::parse_str("67e5504410b1426%9247bb680e5fe0c8")
363                 .map_err(crate::Error::expect_parser),
364             Err(Error::InvalidCharacter {
365                 expected: EXPECTED_CHARS,
366                 found: '%',
367                 index: 15,
368                 urn: error::UrnPrefix::Optional,
369             })
370         );
371 
372         assert_eq!(
373             Uuid::parse_str("231231212212423424324323477343246663")
374                 .map_err(crate::Error::expect_parser),
375             Err(Error::InvalidLength {
376                 expected: EXPECTED_UUID_LENGTHS,
377                 found: 36,
378             })
379         );
380 
381         // Valid
382         assert!(Uuid::parse_str("00000000000000000000000000000000").is_ok());
383         assert!(Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8").is_ok());
384         assert!(Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4").is_ok());
385         assert!(Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c8").is_ok());
386         assert!(Uuid::parse_str("01020304-1112-2122-3132-414243444546").is_ok());
387         assert!(Uuid::parse_str(
388             "urn:uuid:67e55044-10b1-426f-9247-bb680e5fe0c8"
389         )
390         .is_ok());
391 
392         // Nil
393         let nil = Uuid::nil();
394         assert_eq!(
395             Uuid::parse_str("00000000000000000000000000000000").unwrap(),
396             nil
397         );
398         assert_eq!(
399             Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(),
400             nil
401         );
402 
403         // Round-trip
404         let uuid_orig = test_util::new();
405         let orig_str = uuid_orig.to_string();
406         let uuid_out = Uuid::parse_str(&orig_str).unwrap();
407         assert_eq!(uuid_orig, uuid_out);
408 
409         // Test error reporting
410         assert_eq!(
411             Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c")
412                 .map_err(crate::Error::expect_parser),
413             Err(Error::InvalidLength {
414                 expected: EXPECTED_UUID_LENGTHS,
415                 found: 31,
416             })
417         );
418         assert_eq!(
419             Uuid::parse_str("67e550X410b1426f9247bb680e5fe0cd")
420                 .map_err(crate::Error::expect_parser),
421             Err(Error::InvalidCharacter {
422                 expected: EXPECTED_CHARS,
423                 found: 'X',
424                 index: 6,
425                 urn: error::UrnPrefix::Optional,
426             })
427         );
428         assert_eq!(
429             Uuid::parse_str("67e550-4105b1426f9247bb680e5fe0c")
430                 .map_err(crate::Error::expect_parser),
431             Err(Error::InvalidGroupLength {
432                 expected: error::ExpectedLength::Exact(8),
433                 found: 6,
434                 group: 0,
435             })
436         );
437         assert_eq!(
438             Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF1-02BF39FA1E4")
439                 .map_err(crate::Error::expect_parser),
440             Err(Error::InvalidGroupLength {
441                 expected: error::ExpectedLength::Exact(4),
442                 found: 5,
443                 group: 3,
444             })
445         );
446     }
447 }
448