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 {
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