1use nom::Parser;
7use nom::bytes::streaming::tag;
8use nom::character::streaming::char as nomchar;
9use nom::combinator::{all_consuming, consumed, map_res};
10use nom::multi::{separated_list0, separated_list1};
11use nom::sequence::{delimited, preceded, separated_pair, terminated};
12use std::collections::{BTreeMap, BTreeSet, btree_map};
13use thiserror;
14
15use crate::derivation::parse_error::{ErrorKind, NomError, NomResult, into_nomerror};
16use crate::derivation::{CAHash, Derivation, Output, write};
17use crate::store_path::{self, StorePath};
18use crate::{aterm, nixhash, nixhash::NixHash};
19
20#[derive(Debug, thiserror::Error)]
21pub enum Error<I> {
22 #[error("parsing error: {0}")]
23 Parser(#[from] NomError<I>),
24 #[error("premature EOF")]
25 Incomplete,
26 #[error("validation error: {0}")]
27 Validation(super::DerivationError),
28}
29
30impl From<Error<&[u8]>> for Error<Vec<u8>> {
32 fn from(value: Error<&[u8]>) -> Self {
33 match value {
34 Error::Parser(nom_error) => Error::Parser(NomError {
35 input: nom_error.input.to_vec(),
36 code: nom_error.code,
37 }),
38 Error::Incomplete => Error::Incomplete,
39 Error::Validation(e) => Error::Validation(e),
40 }
41 }
42}
43
44pub(crate) fn parse(i: &[u8]) -> Result<Derivation, Error<&[u8]>> {
45 match all_consuming(parse_derivation).parse(i) {
46 Ok((rest, derivation)) => {
47 debug_assert!(rest.is_empty());
49
50 derivation.validate(true).map_err(Error::Validation)?;
52
53 Ok(derivation)
54 }
55 Err(nom::Err::Incomplete(_)) => Err(Error::Incomplete),
56 Err(nom::Err::Error(e) | nom::Err::Failure(e)) => Err(e.into()),
57 }
58}
59
60#[allow(dead_code)]
65pub fn parse_streaming(i: &[u8]) -> (Result<Derivation, Error<&[u8]>>, &[u8]) {
66 match consumed(parse_derivation).parse(i) {
67 Ok((_, (rest, derivation))) => {
68 if let Err(e) = derivation.validate(true).map_err(Error::Validation) {
70 return (Err(e), i);
71 }
72
73 (Ok(derivation), rest)
74 }
75 Err(nom::Err::Incomplete(_)) => (Err(Error::Incomplete), i),
76 Err(nom::Err::Error(e) | nom::Err::Failure(e)) => (Err(e.into()), i),
77 }
78}
79
80fn from_algo_and_mode_and_digest<B: AsRef<[u8]>>(
85 algo_and_mode: &str,
86 digest: B,
87) -> Result<CAHash, nixhash::Error> {
88 Ok(match algo_and_mode.strip_prefix("r:") {
89 Some(algo) => nixhash::CAHash::Nar(NixHash::from_algo_and_digest(
90 algo.try_into()?,
91 digest.as_ref(),
92 )?),
93 None => nixhash::CAHash::Flat(NixHash::from_algo_and_digest(
94 algo_and_mode.try_into()?,
95 digest.as_ref(),
96 )?),
97 })
98}
99
100fn parse_output(i: &[u8]) -> NomResult<&[u8], (String, Output)> {
104 delimited(
105 nomchar('('),
106 map_res(
107 |i| {
108 (
109 terminated(aterm::parse_string_field, nomchar(',')),
110 terminated(aterm::parse_string_field, nomchar(',')),
111 terminated(aterm::parse_string_field, nomchar(',')),
112 aterm::parse_bytes_field,
113 )
114 .parse(i)
115 .map_err(into_nomerror)
116 },
117 |(output_name, output_path, algo_and_mode, encoded_digest)| {
118 let ca_hash_res = {
120 if algo_and_mode.is_empty() && encoded_digest.is_empty() {
121 None
122 } else {
123 match data_encoding::HEXLOWER.decode(&encoded_digest) {
124 Ok(digest) => {
125 Some(from_algo_and_mode_and_digest(&algo_and_mode, digest))
126 }
127 Err(e) => Some(Err(nixhash::Error::InvalidBase64Encoding(e))),
128 }
129 }
130 }
131 .transpose();
132
133 match ca_hash_res {
134 Ok(hash_with_mode) => Ok((
135 output_name,
136 Output {
137 path: if output_path.is_empty() {
140 None
141 } else {
142 Some(string_to_store_path(i, &output_path)?)
143 },
144 ca_hash: hash_with_mode,
145 },
146 )),
147 Err(e) => Err(nom::Err::Failure(NomError {
148 input: i,
149 code: ErrorKind::NixHashError(e),
150 })),
151 }
152 },
153 ),
154 nomchar(')'),
155 )
156 .parse(i)
157}
158
159fn parse_outputs(i: &[u8]) -> NomResult<&[u8], BTreeMap<String, Output>> {
165 let res = delimited(
166 nomchar('['),
167 separated_list1(tag(","), parse_output),
168 nomchar(']'),
169 )
170 .parse(i);
171
172 match res {
173 Ok((rst, outputs_lst)) => {
174 let mut outputs = BTreeMap::default();
175 for (output_name, output) in outputs_lst.into_iter() {
176 if outputs.contains_key(&output_name) {
177 return Err(nom::Err::Failure(NomError {
178 input: i,
179 code: ErrorKind::DuplicateMapKey(output_name.to_string()),
180 }));
181 }
182 outputs.insert(output_name, output);
183 }
184 Ok((rst, outputs))
185 }
186 Err(e) => Err(e),
188 }
189}
190
191fn parse_input_derivations(
192 i: &[u8],
193) -> NomResult<&[u8], BTreeMap<StorePath<String>, BTreeSet<String>>> {
194 let (i, input_derivations_list) = parse_kv(aterm::parse_string_list)(i)?;
195
196 let mut input_derivations: BTreeMap<StorePath<String>, BTreeSet<_>> = BTreeMap::new();
198
199 for (input_derivation, output_names) in input_derivations_list {
200 let mut new_output_names = BTreeSet::new();
201 for output_name in output_names.into_iter() {
202 if new_output_names.contains(&output_name) {
203 return Err(nom::Err::Failure(NomError {
204 input: i,
205 code: ErrorKind::DuplicateInputDerivationOutputName(
206 input_derivation.to_string(),
207 output_name.to_string(),
208 ),
209 }));
210 }
211 new_output_names.insert(output_name);
212 }
213
214 let input_derivation = string_to_store_path(i, input_derivation.as_str())?;
215
216 input_derivations.insert(input_derivation, new_output_names);
217 }
218
219 Ok((i, input_derivations))
220}
221
222fn parse_input_sources(i: &[u8]) -> NomResult<&[u8], BTreeSet<StorePath<String>>> {
223 let (i, input_sources_lst) = aterm::parse_string_list(i).map_err(into_nomerror)?;
224
225 let mut input_sources: BTreeSet<_> = BTreeSet::new();
226 for input_source in input_sources_lst.into_iter() {
227 let input_source = string_to_store_path(i, input_source.as_str())?;
228 if input_sources.contains(&input_source) {
229 return Err(nom::Err::Failure(NomError {
230 input: i,
231 code: ErrorKind::DuplicateInputSource(input_source.to_owned()),
232 }));
233 } else {
234 input_sources.insert(input_source);
235 }
236 }
237
238 Ok((i, input_sources))
239}
240
241fn string_to_store_path<'a, 'i, S>(
242 i: &'i [u8],
243 path_str: &'a str,
244) -> Result<StorePath<S>, nom::Err<NomError<&'i [u8]>>>
245where
246 S: std::clone::Clone + AsRef<str> + std::convert::From<&'a str>,
247{
248 let path =
249 StorePath::from_absolute_path(path_str.as_bytes()).map_err(|e: store_path::Error| {
250 nom::Err::Failure(NomError {
251 input: i,
252 code: e.into(),
253 })
254 })?;
255
256 #[cfg(debug_assertions)]
257 assert_eq!(path_str, path.to_absolute_path());
258
259 Ok(path)
260}
261
262pub fn parse_derivation(i: &[u8]) -> NomResult<&[u8], Derivation> {
263 use nom::Parser;
264 preceded(
265 tag(write::DERIVATION_PREFIX),
266 delimited(
267 nomchar('('),
269 (
272 terminated(parse_outputs, nomchar(',')),
274 terminated(parse_input_derivations, nomchar(',')),
276 terminated(parse_input_sources, nomchar(',')),
278 |i| {
280 terminated(aterm::parse_string_field, nomchar(','))
281 .parse(i)
282 .map_err(into_nomerror)
283 },
284 |i| {
286 terminated(aterm::parse_string_field, nomchar(','))
287 .parse(i)
288 .map_err(into_nomerror)
289 },
290 |i| {
292 terminated(aterm::parse_string_list, nomchar(','))
293 .parse(i)
294 .map_err(into_nomerror)
295 },
296 parse_kv(aterm::parse_bytes_field),
298 ),
299 nomchar(')'),
300 )
301 .map(
302 |(
303 outputs,
304 input_derivations,
305 input_sources,
306 system,
307 builder,
308 arguments,
309 environment,
310 )| {
311 Derivation {
312 arguments,
313 builder,
314 environment,
315 input_derivations,
316 input_sources,
317 outputs,
318 system,
319 }
320 },
321 ),
322 )
323 .parse(i)
324}
325
326pub(crate) fn parse_kv<'a, V, VF>(
332 vf: VF,
333) -> impl FnMut(&'a [u8]) -> NomResult<&'a [u8], BTreeMap<String, V>> + 'static
334where
335 VF: FnMut(&'a [u8]) -> nom::IResult<&'a [u8], V, nom::error::Error<&'a [u8]>> + Clone + 'static,
336{
337 move |i|
338 delimited(
340 nomchar('['),
341 |ii| {
342 let res = separated_list0(
343 nomchar(','),
344 delimited(
346 nomchar('('),
347 separated_pair(
348 aterm::parse_string_field,
349 nomchar(','),
350 vf.clone(),
351 ),
352 nomchar(')'),
353 ),
354 ).parse(ii).map_err(into_nomerror);
355
356 match res {
357 Ok((rest, pairs)) => {
358 let mut kvs: BTreeMap<String, V> = BTreeMap::new();
359 for (k, v) in pairs.into_iter() {
360 match kvs.entry(k) {
363 btree_map::Entry::Vacant(e) => { e.insert(v); },
364 btree_map::Entry::Occupied(e) => {
365 return Err(nom::Err::Failure(NomError {
366 input: i,
367 code: ErrorKind::DuplicateMapKey(e.key().clone()),
368 }));
369 }
370 }
371 }
372 Ok((rest, kvs))
373 }
374 Err(e) => Err(e),
375 }
376 },
377 nomchar(']'),
378 ).parse(i)
379}
380
381#[cfg(test)]
382mod tests {
383 use crate::store_path::StorePathRef;
384 use std::collections::{BTreeMap, BTreeSet};
385 use std::sync::LazyLock;
386
387 use crate::{
388 derivation::{
389 CAHash, NixHash, Output, parse_error::ErrorKind, parser::from_algo_and_mode_and_digest,
390 },
391 store_path::StorePath,
392 };
393 use bstr::{BString, ByteSlice};
394 use hex_literal::hex;
395 use rstest::rstest;
396
397 const DIGEST_SHA256: [u8; 32] =
398 hex!("a5ce9c155ed09397614646c9717fc7cd94b1023d7b76b618d409e4fefd6e9d39");
399
400 static NIXHASH_SHA256: NixHash = NixHash::Sha256(DIGEST_SHA256);
401 static EXP_MULTI_OUTPUTS: LazyLock<BTreeMap<String, Output>> = LazyLock::new(|| {
402 let mut b = BTreeMap::new();
403 b.insert(
404 "lib".to_string(),
405 Output {
406 path: Some(
407 StorePath::from_bytes(b"2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib")
408 .unwrap(),
409 ),
410 ca_hash: None,
411 },
412 );
413 b.insert(
414 "out".to_string(),
415 Output {
416 path: Some(
417 StorePath::from_bytes(
418 b"55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out".as_bytes(),
419 )
420 .unwrap(),
421 ),
422 ca_hash: None,
423 },
424 );
425 b
426 });
427
428 static EXP_AB_MAP: LazyLock<BTreeMap<String, BString>> = LazyLock::new(|| {
429 let mut b = BTreeMap::new();
430 b.insert("a".to_string(), b"1".into());
431 b.insert("b".to_string(), b"2".into());
432 b
433 });
434
435 static EXP_INPUT_DERIVATIONS_SIMPLE: LazyLock<BTreeMap<StorePath<String>, BTreeSet<String>>> =
436 LazyLock::new(|| {
437 let mut b = BTreeMap::new();
438 b.insert(
439 StorePath::from_bytes(b"8bjm87p310sb7r2r0sg4xrynlvg86j8k-hello-2.12.1.tar.gz.drv")
440 .unwrap(),
441 {
442 let mut output_names = BTreeSet::new();
443 output_names.insert("out".to_string());
444 output_names
445 },
446 );
447 b.insert(
448 StorePath::from_bytes(b"p3jc8aw45dza6h52v81j7lk69khckmcj-bash-5.2-p15.drv")
449 .unwrap(),
450 {
451 let mut output_names = BTreeSet::new();
452 output_names.insert("out".to_string());
453 output_names.insert("lib".to_string());
454 output_names
455 },
456 );
457 b
458 });
459
460 static EXP_INPUT_DERIVATIONS_SIMPLE_ATERM: LazyLock<String> = LazyLock::new(|| {
461 format!(
462 "[(\"{0}\",[\"out\"]),(\"{1}\",[\"out\",\"lib\"])]",
463 "/nix/store/8bjm87p310sb7r2r0sg4xrynlvg86j8k-hello-2.12.1.tar.gz.drv",
464 "/nix/store/p3jc8aw45dza6h52v81j7lk69khckmcj-bash-5.2-p15.drv"
465 )
466 });
467
468 static EXP_INPUT_SOURCES_SIMPLE: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
469 let mut b = BTreeSet::new();
470 b.insert("/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out".to_string());
471 b.insert("/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib".to_string());
472 b
473 });
474
475 #[rstest]
477 #[case::empty(b"[]", &BTreeMap::new(), b"")]
478 #[case::simple(b"[(\"a\",\"1\"),(\"b\",\"2\")]", &EXP_AB_MAP, b"")]
479 fn parse_kv(
480 #[case] input: &'static [u8],
481 #[case] expected: &BTreeMap<String, BString>,
482 #[case] exp_rest: &[u8],
483 ) {
484 let (rest, parsed) =
485 super::parse_kv(crate::aterm::parse_bytes_field)(input).expect("must parse");
486 assert_eq!(exp_rest, rest, "expected remainder");
487 assert_eq!(*expected, parsed);
488 }
489
490 #[rstest]
491 #[case::incomplete_empty(b"[")]
492 #[case::incomplete_simple(b"[(\"a\",\"1\")")]
493 #[case::incomplete_complicated_escape(b"[(\"a")]
494 #[case::incomplete_complicated_sep(b"[(\"a\",")]
495 #[case::incomplete_complicated_multi_escape(b"[(\"a\",\"")]
496 #[case::incomplete_complicated_multi_outer_sep(b"[(\"a\",\"b\"),")]
497 fn parse_kv_incomplete(#[case] input: &'static [u8]) {
498 assert!(matches!(
499 super::parse_kv(crate::aterm::parse_bytes_field)(input),
500 Err(nom::Err::Incomplete(_))
501 ));
502 }
503
504 #[test]
506 fn parse_kv_fail_dup_keys() {
507 let input: &'static [u8] = b"[(\"a\",\"1\"),(\"a\",\"2\")]";
508 let e = super::parse_kv(crate::aterm::parse_bytes_field)(input).expect_err("must fail");
509
510 match e {
511 nom::Err::Failure(e) => {
512 assert_eq!(ErrorKind::DuplicateMapKey("a".to_string()), e.code);
513 }
514 _ => panic!("unexpected error"),
515 }
516 }
517
518 #[rstest]
520 #[case::empty(b"[]", &BTreeMap::new())]
521 #[case::simple(EXP_INPUT_DERIVATIONS_SIMPLE_ATERM.as_bytes(), &EXP_INPUT_DERIVATIONS_SIMPLE)]
522 fn parse_input_derivations(
523 #[case] input: &'static [u8],
524 #[case] expected: &BTreeMap<StorePath<String>, BTreeSet<String>>,
525 ) {
526 let (rest, parsed) = super::parse_input_derivations(input).expect("must parse");
527
528 assert_eq!(expected, &parsed, "parsed mismatch");
529 assert!(rest.is_empty(), "rest must be empty");
530 }
531
532 #[test]
534 fn parse_input_derivations_fail_dup_output_names() {
535 let input_str = format!(
536 "[(\"{0}\",[\"out\"]),(\"{1}\",[\"out\",\"out\"])]",
537 "/nix/store/8bjm87p310sb7r2r0sg4xrynlvg86j8k-hello-2.12.1.tar.gz.drv",
538 "/nix/store/p3jc8aw45dza6h52v81j7lk69khckmcj-bash-5.2-p15.drv"
539 );
540 let e = super::parse_input_derivations(input_str.as_bytes()).expect_err("must fail");
541
542 match e {
543 nom::Err::Failure(e) => {
544 assert_eq!(
545 ErrorKind::DuplicateInputDerivationOutputName(
546 "/nix/store/p3jc8aw45dza6h52v81j7lk69khckmcj-bash-5.2-p15.drv".to_string(),
547 "out".to_string()
548 ),
549 e.code
550 );
551 }
552 _ => panic!("unexpected error"),
553 }
554 }
555
556 #[rstest]
558 #[case::empty(b"[]", &BTreeSet::new())]
559 #[case::simple(b"[\"/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out\",\"/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib\"]", &EXP_INPUT_SOURCES_SIMPLE)]
560 fn parse_input_sources(#[case] input: &'static [u8], #[case] expected: &BTreeSet<String>) {
561 let (rest, parsed) = super::parse_input_sources(input).expect("must parse");
562
563 assert_eq!(
564 expected,
565 &parsed
566 .iter()
567 .map(StorePath::to_absolute_path)
568 .collect::<BTreeSet<_>>(),
569 "parsed mismatch"
570 );
571 assert!(rest.is_empty(), "rest must be empty");
572 }
573
574 #[test]
576 fn parse_input_sources_fail_dup_keys() {
577 let input: &'static [u8] = b"[\"/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-foo\",\"/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-foo\"]";
578 let e = super::parse_input_sources(input).expect_err("must fail");
579
580 match e {
581 nom::Err::Failure(e) => {
582 assert_eq!(
583 ErrorKind::DuplicateInputSource(
584 StorePathRef::from_absolute_path(
585 "/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-foo".as_bytes()
586 )
587 .unwrap()
588 .to_owned()
589 ),
590 e.code
591 );
592 }
593 _ => panic!("unexpected error"),
594 }
595 }
596
597 #[rstest]
598 #[case::simple(
599 br#"("out","/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo","","")"#,
600 ("out".to_string(), Output {
601 path: Some(
602 StorePathRef::from_absolute_path("/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo".as_bytes()).unwrap().to_owned()),
603 ca_hash: None
604 })
605 )]
606 #[case::fod(
607 br#"("out","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar","r:sha256","08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba")"#,
608 ("out".to_string(), Output {
609 path: Some(
610 StorePathRef::from_absolute_path(
611 "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar".as_bytes()).unwrap().to_owned()),
612 ca_hash: Some(from_algo_and_mode_and_digest("r:sha256",
613 data_encoding::HEXLOWER.decode(b"08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba").unwrap() ).unwrap()),
614 })
615 )]
616 fn parse_output(#[case] input: &[u8], #[case] expected: (String, Output)) {
617 let (rest, parsed) = super::parse_output(input).expect("must parse");
618 assert!(rest.is_empty());
619 assert_eq!(expected, parsed);
620 }
621
622 #[rstest]
623 #[case::multi_out(
624 br#"[("lib","/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib","",""),("out","/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out","","")]"#,
625 &EXP_MULTI_OUTPUTS
626 )]
627 fn parse_outputs(#[case] input: &[u8], #[case] expected: &BTreeMap<String, Output>) {
628 let (rest, parsed) = super::parse_outputs(input).expect("must parse");
629 assert!(rest.is_empty());
630 assert_eq!(*expected, parsed);
631 }
632
633 #[rstest]
634 #[case::sha256_flat("sha256", &DIGEST_SHA256, CAHash::Flat(NIXHASH_SHA256.clone()))]
635 #[case::sha256_recursive("r:sha256", &DIGEST_SHA256, CAHash::Nar(NIXHASH_SHA256.clone()))]
636 fn test_from_algo_and_mode_and_digest(
637 #[case] algo_and_mode: &str,
638 #[case] digest: &[u8],
639 #[case] expected: CAHash,
640 ) {
641 assert_eq!(
642 expected,
643 from_algo_and_mode_and_digest(algo_and_mode, digest).unwrap()
644 );
645 }
646
647 #[test]
648 fn from_algo_and_mode_and_digest_failure() {
649 assert!(from_algo_and_mode_and_digest("r:sha256", []).is_err());
650 assert!(from_algo_and_mode_and_digest("ha256", DIGEST_SHA256).is_err());
651 }
652}