1#![no_std]
77#![doc(test(
78 no_crate_inject,
79 attr(allow(
80 dead_code,
81 unused_variables,
82 clippy::undocumented_unsafe_blocks,
83 clippy::unused_trait_names,
84 ))
85))]
86#![forbid(unsafe_code)]
87#![warn(
88 missing_debug_implementations,
90 missing_docs,
91 clippy::alloc_instead_of_core,
92 clippy::exhaustive_enums,
93 clippy::exhaustive_structs,
94 clippy::impl_trait_in_params,
95 clippy::std_instead_of_alloc,
97 clippy::std_instead_of_core,
98)]
99#![allow(clippy::inline_always)]
100
101extern crate alloc;
102extern crate std;
103
104#[cfg(test)]
105#[path = "gen/tests/assert_impl.rs"]
106mod assert_impl;
107#[cfg(test)]
108#[path = "gen/tests/track_size.rs"]
109mod track_size;
110
111mod error;
112
113use alloc::{borrow::Cow, boxed::Box, string::String, vec, vec::Vec};
114use core::{mem, ops::Range, str};
115use std::collections::HashMap;
116
117use smallvec::SmallVec;
118
119pub use self::error::Error;
120use self::error::{ErrorKind, Result};
121
122#[allow(clippy::missing_panics_doc)]
124pub fn parse(text: &str) -> Result<Dockerfile<'_>> {
125 let mut p = ParseIter::new(text)?;
126 let mut s = p.s;
127
128 let mut instructions = Vec::with_capacity(p.text.len() / 60);
129 let mut stages = Vec::with_capacity(1);
130 let mut named_stages = 0;
131 let mut current_stage = None;
132 while let Some((&b, s_next)) = s.split_first() {
133 let instruction =
134 parse_instruction(&mut p, &mut s, b, s_next).map_err(|e| e.into_error(&p))?;
135 match instruction {
136 Instruction::From(from) => {
137 named_stages += from.as_.is_some() as usize;
138 let new_stage = instructions.len();
139 if let Some(prev_stage) = current_stage.replace(new_stage) {
140 stages.push(prev_stage..new_stage);
141 }
142 instructions.push(Instruction::From(from));
143 }
144 arg @ Instruction::Arg(..) => instructions.push(arg),
145 instruction => {
146 if current_stage.is_none() {
147 return Err(error::expected("FROM", instruction.instruction_span().start)
148 .into_error(&p));
149 }
150 instructions.push(instruction);
151 }
152 }
153 skip_comments_and_whitespaces(&mut s, p.escape_byte);
154 }
155 if let Some(current_stage) = current_stage {
156 stages.push(current_stage..instructions.len());
157 }
158
159 if stages.is_empty() {
160 return Err(error::no_stage().into_error(&p));
162 }
163 let mut stages_by_name = HashMap::with_capacity(named_stages);
167 for (i, stage) in stages.iter().enumerate() {
168 let Instruction::From(from) = &instructions[stage.start] else { unreachable!() };
169 if let Some((_as, name)) = &from.as_ {
170 if let Some(first_occurrence) = stages_by_name.insert(name.value.clone(), i) {
171 let Instruction::From(from) = &instructions[stages[first_occurrence].start] else {
172 unreachable!()
173 };
174 let first_start = from.as_.as_ref().unwrap().1.span.start;
175 let second_start = name.span.start;
176 return Err(error::duplicate_name(first_start, second_start).into_error(&p));
177 }
178 }
179 }
180
181 Ok(Dockerfile { parser_directives: p.parser_directives, instructions, stages, stages_by_name })
182}
183
184pub fn parse_iter(text: &str) -> Result<ParseIter<'_>> {
189 ParseIter::new(text)
190}
191
192#[derive(Debug)]
194#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
195#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
196pub struct Dockerfile<'a> {
197 pub parser_directives: ParserDirectives<'a>,
199 pub instructions: Vec<Instruction<'a>>,
201 #[cfg_attr(feature = "serde", serde(skip))]
202 stages: Vec<Range<usize>>,
203 #[cfg_attr(feature = "serde", serde(skip))]
204 stages_by_name: HashMap<Cow<'a, str>, usize>,
205}
206impl<'a> Dockerfile<'a> {
207 #[allow(clippy::missing_panics_doc)] #[must_use]
210 pub fn global_args<'b>(&'b self) -> impl ExactSizeIterator<Item = &'b ArgInstruction<'a>> {
211 self.instructions[..self.stages.first().unwrap().start].iter().map(|arg| {
212 let Instruction::Arg(arg) = arg else { unreachable!() };
213 arg
214 })
215 }
216 #[must_use]
218 pub fn stage<'b>(&'b self, name: &str) -> Option<Stage<'a, 'b>> {
219 let i = *self.stages_by_name.get(name)?;
220 let stage = &self.stages[i];
221 let Instruction::From(from) = &self.instructions[stage.start] else { unreachable!() };
222 Some(Stage { from, instructions: &self.instructions[stage.start + 1..stage.end] })
223 }
224 #[must_use]
226 pub fn stages<'b>(&'b self) -> impl ExactSizeIterator<Item = Stage<'a, 'b>> {
227 self.stages.iter().map(move |stage| {
228 let Instruction::From(from) = &self.instructions[stage.start] else { unreachable!() };
229 Stage { from, instructions: &self.instructions[stage.start + 1..stage.end] }
230 })
231 }
232}
233#[derive(Debug)]
235#[non_exhaustive]
236pub struct Stage<'a, 'b> {
237 pub from: &'b FromInstruction<'a>,
239 pub instructions: &'b [Instruction<'a>],
241}
242
243#[derive(Debug)]
247#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
248#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
249#[non_exhaustive]
250pub struct ParserDirectives<'a> {
251 pub syntax: Option<ParserDirective<&'a str>>,
255 pub escape: Option<ParserDirective<char>>,
259 pub check: Option<ParserDirective<&'a str>>,
263}
264#[derive(Debug)]
266#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
267#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
268pub struct ParserDirective<T> {
269 start: usize,
274 pub value: Spanned<T>,
279}
280impl<T> ParserDirective<T> {
281 #[must_use]
286 pub fn span(&self) -> Span {
287 self.start..self.value.span.end
288 }
289}
290
291#[derive(Debug)]
293#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
294#[cfg_attr(feature = "serde", serde(tag = "kind"))]
295#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
296#[non_exhaustive]
297pub enum Instruction<'a> {
299 Add(AddInstruction<'a>),
301 Arg(ArgInstruction<'a>),
303 Cmd(CmdInstruction<'a>),
305 Copy(CopyInstruction<'a>),
307 Entrypoint(EntrypointInstruction<'a>),
309 Env(EnvInstruction<'a>),
311 Expose(ExposeInstruction<'a>),
313 From(FromInstruction<'a>),
315 Healthcheck(HealthcheckInstruction<'a>),
317 Label(LabelInstruction<'a>),
319 Maintainer(MaintainerInstruction<'a>),
321 Onbuild(OnbuildInstruction<'a>),
323 Run(RunInstruction<'a>),
325 Shell(ShellInstruction<'a>),
327 Stopsignal(StopsignalInstruction<'a>),
329 User(UserInstruction<'a>),
331 Volume(VolumeInstruction<'a>),
333 Workdir(WorkdirInstruction<'a>),
335}
336impl Instruction<'_> {
337 fn instruction_span(&self) -> Span {
338 match self {
339 Instruction::Add(instruction) => instruction.add.span.clone(),
340 Instruction::Arg(instruction) => instruction.arg.span.clone(),
341 Instruction::Cmd(instruction) => instruction.cmd.span.clone(),
342 Instruction::Copy(instruction) => instruction.copy.span.clone(),
343 Instruction::Entrypoint(instruction) => instruction.entrypoint.span.clone(),
344 Instruction::Env(instruction) => instruction.env.span.clone(),
345 Instruction::Expose(instruction) => instruction.expose.span.clone(),
346 Instruction::From(instruction) => instruction.from.span.clone(),
347 Instruction::Healthcheck(instruction) => instruction.healthcheck.span.clone(),
348 Instruction::Label(instruction) => instruction.label.span.clone(),
349 Instruction::Maintainer(instruction) => instruction.maintainer.span.clone(),
350 Instruction::Onbuild(instruction) => instruction.onbuild.span.clone(),
351 Instruction::Run(instruction) => instruction.run.span.clone(),
352 Instruction::Shell(instruction) => instruction.shell.span.clone(),
353 Instruction::Stopsignal(instruction) => instruction.stopsignal.span.clone(),
354 Instruction::User(instruction) => instruction.user.span.clone(),
355 Instruction::Volume(instruction) => instruction.volume.span.clone(),
356 Instruction::Workdir(instruction) => instruction.workdir.span.clone(),
357 }
358 }
359}
360#[derive(Debug)]
364#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
365#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
366#[non_exhaustive]
367pub struct AddInstruction<'a> {
368 pub add: Keyword,
373 pub options: SmallVec<[Flag<'a>; 1]>,
378 pub src: SmallVec<[Source<'a>; 1]>,
384 pub dest: UnescapedString<'a>,
389}
390#[derive(Debug)]
394#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
395#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
396#[non_exhaustive]
397pub struct ArgInstruction<'a> {
398 pub arg: Keyword,
403 pub arguments: UnescapedString<'a>,
409}
410#[derive(Debug)]
414#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
415#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
416#[non_exhaustive]
417pub struct CmdInstruction<'a> {
418 pub cmd: Keyword,
423 pub arguments: Command<'a>,
428}
429#[derive(Debug)]
433#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
434#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
435#[non_exhaustive]
436pub struct CopyInstruction<'a> {
437 pub copy: Keyword,
442 pub options: SmallVec<[Flag<'a>; 1]>,
447 pub src: SmallVec<[Source<'a>; 1]>,
453 pub dest: UnescapedString<'a>,
458}
459#[derive(Debug)]
462#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
463#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
464#[non_exhaustive]
465pub enum Source<'a> {
466 Path(UnescapedString<'a>),
468 HereDoc(HereDoc<'a>),
470}
471#[derive(Debug)]
475#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
476#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
477#[non_exhaustive]
478pub struct EntrypointInstruction<'a> {
479 pub entrypoint: Keyword,
484 pub arguments: Command<'a>,
489}
490#[derive(Debug)]
494#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
495#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
496#[non_exhaustive]
497pub struct EnvInstruction<'a> {
498 pub env: Keyword,
503 pub arguments: UnescapedString<'a>,
509}
510#[derive(Debug)]
514#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
515#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
516#[non_exhaustive]
517pub struct ExposeInstruction<'a> {
518 pub expose: Keyword,
523 pub arguments: SmallVec<[UnescapedString<'a>; 1]>,
528}
529#[derive(Debug)]
533#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
534#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
535#[non_exhaustive]
536pub struct FromInstruction<'a> {
537 pub from: Keyword,
542 pub options: Vec<Flag<'a>>,
547 pub image: UnescapedString<'a>,
552 pub as_: Option<(Keyword, UnescapedString<'a>)>,
557}
558#[derive(Debug)]
562#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
563#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
564#[non_exhaustive]
565pub struct HealthcheckInstruction<'a> {
566 pub healthcheck: Keyword,
571 pub options: Vec<Flag<'a>>,
576 pub arguments: HealthcheckArguments<'a>,
581}
582#[derive(Debug)]
584#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
585#[cfg_attr(feature = "serde", serde(tag = "kind"))]
586#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
587#[non_exhaustive]
588pub enum HealthcheckArguments<'a> {
589 #[non_exhaustive]
591 Cmd {
592 cmd: Keyword,
597 arguments: Command<'a>,
602 },
603 #[non_exhaustive]
605 None {
606 none: Keyword,
611 },
612}
613#[derive(Debug)]
617#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
618#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
619#[non_exhaustive]
620pub struct LabelInstruction<'a> {
621 pub label: Keyword,
626 pub arguments: UnescapedString<'a>,
632}
633#[derive(Debug)]
637#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
638#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
639#[non_exhaustive]
640pub struct MaintainerInstruction<'a> {
641 pub maintainer: Keyword,
646 pub name: UnescapedString<'a>,
651}
652#[derive(Debug)]
656#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
657#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
658#[non_exhaustive]
659pub struct OnbuildInstruction<'a> {
660 pub onbuild: Keyword,
665 pub instruction: Box<Instruction<'a>>,
670}
671#[derive(Debug)]
675#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
676#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
677#[non_exhaustive]
678pub struct RunInstruction<'a> {
679 pub run: Keyword,
684 pub options: SmallVec<[Flag<'a>; 1]>,
689 pub arguments: Command<'a>,
694 pub here_docs: Vec<HereDoc<'a>>,
702}
703#[derive(Debug)]
707#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
708#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
709#[non_exhaustive]
710pub struct ShellInstruction<'a> {
711 pub shell: Keyword,
716 pub arguments: SmallVec<[UnescapedString<'a>; 4]>,
723}
724#[derive(Debug)]
728#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
729#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
730#[non_exhaustive]
731pub struct StopsignalInstruction<'a> {
732 pub stopsignal: Keyword,
737 pub arguments: UnescapedString<'a>,
742}
743#[derive(Debug)]
747#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
748#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
749#[non_exhaustive]
750pub struct UserInstruction<'a> {
751 pub user: Keyword,
756 pub arguments: UnescapedString<'a>,
761}
762#[derive(Debug)]
766#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
767#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
768#[non_exhaustive]
769pub struct VolumeInstruction<'a> {
770 pub volume: Keyword,
775 pub arguments: JsonOrStringArray<'a, 1>,
780}
781#[derive(Debug)]
785#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
786#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
787#[non_exhaustive]
788pub struct WorkdirInstruction<'a> {
789 pub workdir: Keyword,
794 pub arguments: UnescapedString<'a>,
799}
800
801#[derive(Debug)]
803#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
804#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
805#[non_exhaustive]
806pub struct Keyword {
807 #[allow(missing_docs)]
808 pub span: Span,
809}
810
811#[derive(Debug)]
813#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
814#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
815pub struct Flag<'a> {
816 flag_start: usize,
821 pub name: UnescapedString<'a>,
826 pub value: Option<UnescapedString<'a>>,
831}
832impl Flag<'_> {
833 #[must_use]
838 pub fn flag_span(&self) -> Span {
839 self.flag_start..self.name.span.end
840 }
841 #[must_use]
846 pub fn span(&self) -> Span {
847 match &self.value {
848 Some(v) => self.flag_start..v.span.end,
849 None => self.flag_span(),
850 }
851 }
852}
853
854#[derive(Debug, PartialEq)]
856#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
857#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
858#[non_exhaustive]
859pub struct UnescapedString<'a> {
860 #[allow(missing_docs)]
861 pub span: Span,
862 #[allow(missing_docs)]
863 pub value: Cow<'a, str>,
864}
865
866#[derive(Debug)]
873#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
874#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
875#[non_exhaustive]
876pub enum Command<'a> {
877 Exec(Spanned<SmallVec<[UnescapedString<'a>; 1]>>),
880 Shell(Spanned<&'a str>),
882}
883
884#[derive(Debug)]
889#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
890#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
891#[allow(clippy::exhaustive_enums)]
892pub enum JsonOrStringArray<'a, const N: usize> {
893 Json(Spanned<SmallVec<[UnescapedString<'a>; N]>>),
895 String(SmallVec<[UnescapedString<'a>; N]>),
897}
898
899#[derive(Debug)]
901#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
902#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
903#[non_exhaustive]
904pub struct HereDoc<'a> {
905 #[allow(missing_docs)]
906 pub span: Span,
907 pub expand: bool,
909 #[allow(missing_docs)]
910 pub value: Cow<'a, str>,
911}
912
913#[derive(Debug)]
915#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
916#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
917#[allow(clippy::exhaustive_structs)]
918pub struct Spanned<T> {
919 #[allow(missing_docs)]
920 pub span: Span,
921 #[allow(missing_docs)]
922 pub value: T,
923}
924
925#[allow(missing_docs)]
926pub type Span = Range<usize>;
927
928#[allow(missing_debug_implementations)]
935#[must_use = "iterators are lazy and do nothing unless consumed"]
936pub struct ParseIter<'a> {
937 text: &'a str,
938 s: &'a [u8],
939 escape_byte: u8,
940 has_stage: bool,
941 in_onbuild: bool,
942 parser_directives: ParserDirectives<'a>,
943}
944impl<'a> ParseIter<'a> {
945 fn new(mut text: &'a str) -> Result<Self> {
946 if text.as_bytes().starts_with(UTF8_BOM) {
948 text = &text[UTF8_BOM.len()..];
949 }
950 let mut p = Self {
951 text,
952 s: text.as_bytes(),
953 escape_byte: DEFAULT_ESCAPE_BYTE,
954 has_stage: false,
955 in_onbuild: false,
956 parser_directives: ParserDirectives {
957 syntax: None,
959 escape: None,
960 check: None,
962 },
963 };
964
965 parse_parser_directives(&mut p).map_err(|e| e.into_error(&p))?;
966
967 skip_comments_and_whitespaces(&mut p.s, p.escape_byte);
971 Ok(p)
972 }
973}
974impl<'a> Iterator for ParseIter<'a> {
975 type Item = Result<Instruction<'a>>;
976 #[inline]
977 fn next(&mut self) -> Option<Self::Item> {
978 let p = self;
979 let mut s = p.s;
980 if let Some((&b, s_next)) = s.split_first() {
981 let instruction = match parse_instruction(p, &mut s, b, s_next) {
982 Ok(i) => i,
983 Err(e) => return Some(Err(e.into_error(p))),
984 };
985 match &instruction {
986 Instruction::From(..) => {
987 p.has_stage = true;
988 }
989 Instruction::Arg(..) => {}
990 instruction => {
991 if !p.has_stage {
992 return Some(Err(error::expected(
993 "FROM",
994 instruction.instruction_span().start,
995 )
996 .into_error(p)));
997 }
998 }
999 }
1000 skip_comments_and_whitespaces(&mut s, p.escape_byte);
1001 p.s = s;
1002 return Some(Ok(instruction));
1003 }
1004 if !p.has_stage {
1005 return Some(Err(error::no_stage().into_error(p)));
1007 }
1008 None
1009 }
1010}
1011
1012const DEFAULT_ESCAPE_BYTE: u8 = b'\\';
1013
1014fn parse_parser_directives(p: &mut ParseIter<'_>) -> Result<(), ErrorKind> {
1015 while let Some((&b'#', s_next)) = p.s.split_first() {
1016 p.s = s_next;
1017 skip_spaces_no_escape(&mut p.s);
1018 let directive_start = p.text.len() - p.s.len();
1019 if token(&mut p.s, b"SYNTAX") {
1020 skip_spaces_no_escape(&mut p.s);
1021 if let Some((&b'=', s_next)) = p.s.split_first() {
1022 p.s = s_next;
1023 if p.parser_directives.syntax.is_some() {
1024 p.parser_directives.syntax = None;
1026 p.parser_directives.escape = None;
1027 p.parser_directives.check = None;
1028 p.escape_byte = DEFAULT_ESCAPE_BYTE;
1029 skip_this_line_no_escape(&mut p.s);
1030 break;
1031 }
1032 skip_spaces_no_escape(&mut p.s);
1033 let value_start = p.text.len() - p.s.len();
1034 skip_non_whitespace_no_escape(&mut p.s);
1035 let end = p.text.len() - p.s.len();
1036 let value = p.text[value_start..end].trim_ascii_end();
1037 p.parser_directives.syntax = Some(ParserDirective {
1038 start: directive_start,
1039 value: Spanned { span: value_start..value_start + value.len(), value },
1040 });
1041 skip_this_line_no_escape(&mut p.s);
1042 continue;
1043 }
1044 } else if token(&mut p.s, b"CHECK") {
1045 skip_spaces_no_escape(&mut p.s);
1046 if let Some((&b'=', s_next)) = p.s.split_first() {
1047 p.s = s_next;
1048 if p.parser_directives.check.is_some() {
1049 p.parser_directives.syntax = None;
1051 p.parser_directives.escape = None;
1052 p.parser_directives.check = None;
1053 p.escape_byte = DEFAULT_ESCAPE_BYTE;
1054 skip_this_line_no_escape(&mut p.s);
1055 break;
1056 }
1057 skip_spaces_no_escape(&mut p.s);
1058 let value_start = p.text.len() - p.s.len();
1059 skip_non_whitespace_no_escape(&mut p.s);
1060 let end = p.text.len() - p.s.len();
1061 let value = p.text[value_start..end].trim_ascii_end();
1062 p.parser_directives.check = Some(ParserDirective {
1063 start: directive_start,
1064 value: Spanned { span: value_start..value_start + value.len(), value },
1065 });
1066 skip_this_line_no_escape(&mut p.s);
1067 continue;
1068 }
1069 } else if token(&mut p.s, b"ESCAPE") {
1070 skip_spaces_no_escape(&mut p.s);
1071 if let Some((&b'=', s_next)) = p.s.split_first() {
1072 p.s = s_next;
1073 if p.parser_directives.escape.is_some() {
1074 p.parser_directives.syntax = None;
1076 p.parser_directives.escape = None;
1077 p.parser_directives.check = None;
1078 p.escape_byte = DEFAULT_ESCAPE_BYTE;
1079 skip_this_line_no_escape(&mut p.s);
1080 break;
1081 }
1082 skip_spaces_no_escape(&mut p.s);
1083 let value_start = p.text.len() - p.s.len();
1084 skip_non_whitespace_no_escape(&mut p.s);
1085 let end = p.text.len() - p.s.len();
1086 let value = p.text[value_start..end].trim_ascii_end();
1087 match value {
1088 "`" => p.escape_byte = b'`',
1089 "\\" => {}
1090 _ => return Err(error::invalid_escape(value_start)),
1091 }
1092 p.parser_directives.escape = Some(ParserDirective {
1093 start: directive_start,
1094 value: Spanned {
1095 span: value_start..value_start + value.len(),
1096 value: p.escape_byte as char,
1097 },
1098 });
1099 skip_this_line_no_escape(&mut p.s);
1100 continue;
1101 }
1102 }
1103 skip_this_line_no_escape(&mut p.s);
1104 break;
1105 }
1106 Ok(())
1107}
1108
1109#[inline]
1110fn parse_instruction<'a>(
1111 p: &mut ParseIter<'a>,
1112 s: &mut &'a [u8],
1113 b: u8,
1114 s_next: &'a [u8],
1115) -> Result<Instruction<'a>, ErrorKind> {
1116 let instruction_start = p.text.len() - s.len();
1117 *s = s_next;
1118 match b & TO_UPPER8 {
1120 b'A' => {
1121 if token(s, &b"ARG"[1..]) {
1122 let instruction_span = instruction_start..p.text.len() - s.len();
1123 if spaces_or_line_end(s, p.escape_byte) {
1124 return parse_arg(p, s, Keyword { span: instruction_span });
1125 }
1126 } else if token(s, &b"ADD"[1..]) {
1127 let instruction_span = instruction_start..p.text.len() - s.len();
1128 if spaces_or_line_end(s, p.escape_byte) {
1129 let add = Keyword { span: instruction_span };
1130 let (options, src, dest) = parse_add_or_copy(p, s, &add)?;
1131 return Ok(Instruction::Add(AddInstruction { add, options, src, dest }));
1132 }
1133 } else if token_slow(s, &b"ARG"[1..], p.escape_byte) {
1134 let instruction_span = instruction_start..p.text.len() - s.len();
1135 if spaces_or_line_end(s, p.escape_byte) {
1136 return parse_arg(p, s, Keyword { span: instruction_span });
1137 }
1138 } else if token_slow(s, &b"ADD"[1..], p.escape_byte) {
1139 let instruction_span = instruction_start..p.text.len() - s.len();
1140 if spaces_or_line_end(s, p.escape_byte) {
1141 let add = Keyword { span: instruction_span };
1142 let (options, src, dest) = parse_add_or_copy(p, s, &add)?;
1143 return Ok(Instruction::Add(AddInstruction { add, options, src, dest }));
1144 }
1145 }
1146 }
1147 b'C' => {
1148 if token(s, &b"COPY"[1..]) {
1149 let instruction_span = instruction_start..p.text.len() - s.len();
1150 if spaces_or_line_end(s, p.escape_byte) {
1151 let copy = Keyword { span: instruction_span };
1152 let (options, src, dest) = parse_add_or_copy(p, s, ©)?;
1153 return Ok(Instruction::Copy(CopyInstruction { copy, options, src, dest }));
1154 }
1155 } else if token(s, &b"CMD"[1..]) {
1156 let instruction_span = instruction_start..p.text.len() - s.len();
1157 if spaces_or_line_end(s, p.escape_byte) {
1158 return parse_cmd(p, s, Keyword { span: instruction_span });
1159 }
1160 } else if token_slow(s, &b"COPY"[1..], p.escape_byte) {
1161 let instruction_span = instruction_start..p.text.len() - s.len();
1162 if spaces_or_line_end(s, p.escape_byte) {
1163 let copy = Keyword { span: instruction_span };
1164 let (options, src, dest) = parse_add_or_copy(p, s, ©)?;
1165 return Ok(Instruction::Copy(CopyInstruction { copy, options, src, dest }));
1166 }
1167 } else if token_slow(s, &b"CMD"[1..], p.escape_byte) {
1168 let instruction_span = instruction_start..p.text.len() - s.len();
1169 if spaces_or_line_end(s, p.escape_byte) {
1170 return parse_cmd(p, s, Keyword { span: instruction_span });
1171 }
1172 }
1173 }
1174 b'E' => {
1175 if token(s, &b"ENV"[1..]) {
1176 let instruction_span = instruction_start..p.text.len() - s.len();
1177 if spaces_or_line_end(s, p.escape_byte) {
1178 return parse_env(p, s, Keyword { span: instruction_span });
1179 }
1180 } else if token(s, &b"EXPOSE"[1..]) {
1181 let instruction_span = instruction_start..p.text.len() - s.len();
1182 if spaces_or_line_end(s, p.escape_byte) {
1183 return parse_expose(p, s, Keyword { span: instruction_span });
1184 }
1185 } else if token(s, &b"ENTRYPOINT"[1..]) {
1186 let instruction_span = instruction_start..p.text.len() - s.len();
1187 if spaces_or_line_end(s, p.escape_byte) {
1188 return parse_entrypoint(p, s, Keyword { span: instruction_span });
1189 }
1190 } else if token_slow(s, &b"ENV"[1..], p.escape_byte) {
1191 let instruction_span = instruction_start..p.text.len() - s.len();
1192 if spaces_or_line_end(s, p.escape_byte) {
1193 return parse_env(p, s, Keyword { span: instruction_span });
1194 }
1195 } else if token_slow(s, &b"EXPOSE"[1..], p.escape_byte) {
1196 let instruction_span = instruction_start..p.text.len() - s.len();
1197 if spaces_or_line_end(s, p.escape_byte) {
1198 return parse_expose(p, s, Keyword { span: instruction_span });
1199 }
1200 } else if token_slow(s, &b"ENTRYPOINT"[1..], p.escape_byte) {
1201 let instruction_span = instruction_start..p.text.len() - s.len();
1202 if spaces_or_line_end(s, p.escape_byte) {
1203 return parse_entrypoint(p, s, Keyword { span: instruction_span });
1204 }
1205 }
1206 }
1207 b'F' => {
1208 if token(s, &b"FROM"[1..]) || token_slow(s, &b"FROM"[1..], p.escape_byte) {
1209 let instruction_span = instruction_start..p.text.len() - s.len();
1210 if spaces_or_line_end(s, p.escape_byte) {
1211 return parse_from(p, s, Keyword { span: instruction_span });
1212 }
1213 }
1214 }
1215 b'H' => {
1216 if token(s, &b"HEALTHCHECK"[1..]) || token_slow(s, &b"HEALTHCHECK"[1..], p.escape_byte)
1217 {
1218 let instruction_span = instruction_start..p.text.len() - s.len();
1219 if spaces_or_line_end(s, p.escape_byte) {
1220 return parse_healthcheck(p, s, Keyword { span: instruction_span });
1221 }
1222 }
1223 }
1224 b'L' => {
1225 if token(s, &b"LABEL"[1..]) || token_slow(s, &b"LABEL"[1..], p.escape_byte) {
1226 let instruction_span = instruction_start..p.text.len() - s.len();
1227 if spaces_or_line_end(s, p.escape_byte) {
1228 return parse_label(p, s, Keyword { span: instruction_span });
1229 }
1230 }
1231 }
1232 b'M' => {
1233 if token(s, &b"MAINTAINER"[1..]) || token_slow(s, &b"MAINTAINER"[1..], p.escape_byte) {
1234 let instruction_span = instruction_start..p.text.len() - s.len();
1235 if spaces_or_line_end(s, p.escape_byte) {
1236 return parse_maintainer(p, s, Keyword { span: instruction_span });
1237 }
1238 }
1239 }
1240 b'O' => {
1241 if token(s, &b"ONBUILD"[1..]) || token_slow(s, &b"ONBUILD"[1..], p.escape_byte) {
1242 let instruction_span = instruction_start..p.text.len() - s.len();
1243 if spaces_or_line_end(s, p.escape_byte) {
1244 return parse_onbuild(p, s, Keyword { span: instruction_span });
1245 }
1246 }
1247 }
1248 b'R' => {
1249 if token(s, &b"RUN"[1..]) || token_slow(s, &b"RUN"[1..], p.escape_byte) {
1250 let instruction_span = instruction_start..p.text.len() - s.len();
1251 if spaces_or_line_end(s, p.escape_byte) {
1252 return parse_run(p, s, Keyword { span: instruction_span });
1253 }
1254 }
1255 }
1256 b'S' => {
1257 if token(s, &b"SHELL"[1..]) {
1258 let instruction_span = instruction_start..p.text.len() - s.len();
1259 if spaces_or_line_end(s, p.escape_byte) {
1260 return parse_shell(p, s, Keyword { span: instruction_span });
1261 }
1262 } else if token(s, &b"STOPSIGNAL"[1..]) {
1263 let instruction_span = instruction_start..p.text.len() - s.len();
1264 if spaces_or_line_end(s, p.escape_byte) {
1265 return parse_stopsignal(p, s, Keyword { span: instruction_span });
1266 }
1267 } else if token_slow(s, &b"SHELL"[1..], p.escape_byte) {
1268 let instruction_span = instruction_start..p.text.len() - s.len();
1269 if spaces_or_line_end(s, p.escape_byte) {
1270 return parse_shell(p, s, Keyword { span: instruction_span });
1271 }
1272 } else if token_slow(s, &b"STOPSIGNAL"[1..], p.escape_byte) {
1273 let instruction_span = instruction_start..p.text.len() - s.len();
1274 if spaces_or_line_end(s, p.escape_byte) {
1275 return parse_stopsignal(p, s, Keyword { span: instruction_span });
1276 }
1277 }
1278 }
1279 b'U' => {
1280 if token(s, &b"USER"[1..]) || token_slow(s, &b"USER"[1..], p.escape_byte) {
1281 let instruction_span = instruction_start..p.text.len() - s.len();
1282 if spaces_or_line_end(s, p.escape_byte) {
1283 return parse_user(p, s, Keyword { span: instruction_span });
1284 }
1285 }
1286 }
1287 b'V' => {
1288 if token(s, &b"VOLUME"[1..]) || token_slow(s, &b"VOLUME"[1..], p.escape_byte) {
1289 let instruction_span = instruction_start..p.text.len() - s.len();
1290 if spaces_or_line_end(s, p.escape_byte) {
1291 return parse_volume(p, s, Keyword { span: instruction_span });
1292 }
1293 }
1294 }
1295 b'W' => {
1296 if token(s, &b"WORKDIR"[1..]) || token_slow(s, &b"WORKDIR"[1..], p.escape_byte) {
1297 let instruction_span = instruction_start..p.text.len() - s.len();
1298 if spaces_or_line_end(s, p.escape_byte) {
1299 return parse_workdir(p, s, Keyword { span: instruction_span });
1300 }
1301 }
1302 }
1303 _ => {}
1304 }
1305 Err(error::unknown_instruction(instruction_start))
1306}
1307
1308#[inline]
1309fn parse_arg<'a>(
1310 p: &mut ParseIter<'a>,
1311 s: &mut &'a [u8],
1312 instruction: Keyword,
1313) -> Result<Instruction<'a>, ErrorKind> {
1314 debug_assert!(token_slow(
1315 &mut p.text[instruction.span.clone()].as_bytes(),
1316 b"ARG",
1317 p.escape_byte,
1318 ));
1319 let arguments = collect_non_line_unescaped_consume_line(s, p.text, p.escape_byte);
1320 if arguments.value.is_empty() {
1321 return Err(error::at_least_one_argument(instruction.span.start));
1322 }
1323 Ok(Instruction::Arg(ArgInstruction { arg: instruction, arguments }))
1324}
1325
1326#[inline]
1327fn parse_add_or_copy<'a>(
1328 p: &mut ParseIter<'a>,
1329 s: &mut &'a [u8],
1330 instruction: &Keyword,
1331) -> Result<(SmallVec<[Flag<'a>; 1]>, SmallVec<[Source<'a>; 1]>, UnescapedString<'a>), ErrorKind> {
1332 debug_assert!(
1333 token_slow(&mut p.text[instruction.span.clone()].as_bytes(), b"ADD", p.escape_byte,)
1334 || token_slow(&mut p.text[instruction.span.clone()].as_bytes(), b"COPY", p.escape_byte,)
1335 );
1336 let options = parse_options(s, p.text, p.escape_byte);
1337 if is_maybe_json(s) {
1338 let mut tmp = *s;
1339 if let Ok(((src, dest), _array_span)) = parse_json_array::<(
1340 SmallVec<[Source<'_>; 1]>,
1341 Option<_>,
1342 )>(&mut tmp, p.text, p.escape_byte)
1343 {
1344 debug_assert!(is_line_end(tmp.first()));
1345 if tmp.is_empty() {
1346 *s = &[];
1347 } else {
1348 *s = &tmp[1..];
1349 }
1350 if src.is_empty() {
1351 return Err(error::at_least_two_arguments(instruction.span.start));
1352 }
1353 return Ok((options, src, dest.unwrap()));
1354 }
1355 }
1356 let (mut src, dest) = collect_space_separated_unescaped_consume_line::<(
1357 SmallVec<[Source<'_>; 1]>,
1358 Option<_>,
1359 )>(s, p.text, p.escape_byte);
1360 if src.is_empty() {
1361 return Err(error::at_least_two_arguments(instruction.span.start));
1362 }
1363 for src in &mut src {
1364 let Source::Path(path) = src else { unreachable!() };
1365 let Some(mut delim) = path.value.as_bytes().strip_prefix(b"<<") else { continue };
1366 if delim.is_empty() {
1367 continue;
1368 }
1369 let mut strip_tab = false;
1370 let mut quote = None;
1371 if let Some((&b'-', delim_next)) = delim.split_first() {
1372 strip_tab = true;
1373 delim = delim_next;
1374 }
1375 if let Some((&b, delim_next)) = delim.split_first() {
1376 if matches!(b, b'"' | b'\'') {
1377 quote = Some(b);
1378 delim = delim_next;
1379 if delim.last() != Some(&b) {
1380 return Err(error::expected_quote(
1381 b,
1382 delim.last().copied(),
1383 p.text.len() - s.len(),
1384 ));
1385 }
1386 delim = &delim[..delim.len() - 1];
1387 }
1388 }
1389 if strip_tab {
1390 let (here_doc, span) = collect_here_doc_strip_tab(s, p.text, p.escape_byte, delim)?;
1391 *src = Source::HereDoc(HereDoc { span, expand: quote.is_none(), value: here_doc });
1392 } else {
1393 let (here_doc, span) = collect_here_doc_no_strip_tab(s, p.text, p.escape_byte, delim)?;
1394 *src =
1395 Source::HereDoc(HereDoc { span, expand: quote.is_none(), value: here_doc.into() });
1396 }
1397 }
1398 Ok((options, src, dest.unwrap()))
1399}
1400
1401#[allow(clippy::unnecessary_wraps)]
1402#[inline]
1403fn parse_cmd<'a>(
1404 p: &mut ParseIter<'a>,
1405 s: &mut &'a [u8],
1406 instruction: Keyword,
1407) -> Result<Instruction<'a>, ErrorKind> {
1408 debug_assert!(token_slow(
1409 &mut p.text[instruction.span.clone()].as_bytes(),
1410 b"CMD",
1411 p.escape_byte,
1412 ));
1413 if is_maybe_json(s) {
1414 let mut tmp = *s;
1415 if let Ok((arguments, array_span)) =
1416 parse_json_array::<SmallVec<[_; 1]>>(&mut tmp, p.text, p.escape_byte)
1417 {
1418 debug_assert!(is_line_end(tmp.first()));
1419 if tmp.is_empty() {
1420 *s = &[];
1421 } else {
1422 *s = &tmp[1..];
1423 }
1424 return Ok(Instruction::Cmd(CmdInstruction {
1427 cmd: instruction,
1428 arguments: Command::Exec(Spanned { span: array_span, value: arguments }),
1429 }));
1430 }
1431 }
1432 let arguments_start = p.text.len() - s.len();
1433 skip_this_line(s, p.escape_byte);
1434 let end = p.text.len() - s.len();
1435 let arguments = p.text[arguments_start..end].trim_ascii_end();
1436 Ok(Instruction::Cmd(CmdInstruction {
1437 cmd: instruction,
1438 arguments: Command::Shell(Spanned {
1439 span: arguments_start..arguments_start + arguments.len(),
1440 value: arguments,
1441 }),
1442 }))
1443}
1444
1445#[inline]
1446fn parse_env<'a>(
1447 p: &mut ParseIter<'a>,
1448 s: &mut &'a [u8],
1449 instruction: Keyword,
1450) -> Result<Instruction<'a>, ErrorKind> {
1451 debug_assert!(token_slow(
1452 &mut p.text[instruction.span.clone()].as_bytes(),
1453 b"ENV",
1454 p.escape_byte,
1455 ));
1456 let arguments = collect_non_line_unescaped_consume_line(s, p.text, p.escape_byte);
1457 if arguments.value.is_empty() {
1458 return Err(error::at_least_one_argument(instruction.span.start));
1459 }
1460 Ok(Instruction::Env(EnvInstruction { env: instruction, arguments }))
1461}
1462
1463#[inline]
1464fn parse_expose<'a>(
1465 p: &mut ParseIter<'a>,
1466 s: &mut &'a [u8],
1467 instruction: Keyword,
1468) -> Result<Instruction<'a>, ErrorKind> {
1469 debug_assert!(token_slow(
1470 &mut p.text[instruction.span.clone()].as_bytes(),
1471 b"EXPOSE",
1472 p.escape_byte,
1473 ));
1474 let arguments: SmallVec<[_; 1]> =
1475 collect_space_separated_unescaped_consume_line(s, p.text, p.escape_byte);
1476 if arguments.is_empty() {
1477 return Err(error::at_least_one_argument(instruction.span.start));
1478 }
1479 Ok(Instruction::Expose(ExposeInstruction { expose: instruction, arguments }))
1480}
1481
1482#[inline]
1483fn parse_entrypoint<'a>(
1484 p: &mut ParseIter<'a>,
1485 s: &mut &'a [u8],
1486 instruction: Keyword,
1487) -> Result<Instruction<'a>, ErrorKind> {
1488 debug_assert!(token_slow(
1489 &mut p.text[instruction.span.clone()].as_bytes(),
1490 b"ENTRYPOINT",
1491 p.escape_byte,
1492 ));
1493 if is_maybe_json(s) {
1494 let mut tmp = *s;
1495 if let Ok((arguments, array_span)) =
1496 parse_json_array::<SmallVec<[_; 1]>>(&mut tmp, p.text, p.escape_byte)
1497 {
1498 debug_assert!(is_line_end(tmp.first()));
1499 if tmp.is_empty() {
1500 *s = &[];
1501 } else {
1502 *s = &tmp[1..];
1503 }
1504 if arguments.is_empty() {
1505 return Err(error::at_least_one_argument(instruction.span.start));
1506 }
1507 return Ok(Instruction::Entrypoint(EntrypointInstruction {
1508 entrypoint: instruction,
1509 arguments: Command::Exec(Spanned { span: array_span, value: arguments }),
1510 }));
1511 }
1512 }
1513 let arguments_start = p.text.len() - s.len();
1514 skip_this_line(s, p.escape_byte);
1515 let end = p.text.len() - s.len();
1516 let arguments = p.text[arguments_start..end].trim_ascii_end();
1517 if arguments.is_empty() {
1518 return Err(error::at_least_one_argument(instruction.span.start));
1519 }
1520 Ok(Instruction::Entrypoint(EntrypointInstruction {
1521 entrypoint: instruction,
1522 arguments: Command::Shell(Spanned {
1523 span: arguments_start..arguments_start + arguments.len(),
1524 value: arguments,
1525 }),
1526 }))
1527}
1528
1529#[inline]
1530fn parse_from<'a>(
1531 p: &mut ParseIter<'a>,
1532 s: &mut &'a [u8],
1533 instruction: Keyword,
1534) -> Result<Instruction<'a>, ErrorKind> {
1535 debug_assert!(token_slow(
1536 &mut p.text[instruction.span.clone()].as_bytes(),
1537 b"FROM",
1538 p.escape_byte,
1539 ));
1540 let options = parse_options(s, p.text, p.escape_byte);
1541 let image = collect_non_whitespace_unescaped(s, p.text, p.escape_byte);
1544 if image.value.is_empty() {
1545 return Err(error::at_least_one_argument(instruction.span.start));
1546 }
1547 let mut as_ = None;
1548 if skip_spaces(s, p.escape_byte) {
1549 let as_start = p.text.len() - s.len();
1550 if token(s, b"AS") || token_slow(s, b"AS", p.escape_byte) {
1551 let as_span = as_start..p.text.len() - s.len();
1552 if !skip_spaces(s, p.escape_byte) {
1553 return Err(error::expected("AS", as_start));
1554 }
1555 let name = collect_non_whitespace_unescaped(s, p.text, p.escape_byte);
1556 skip_spaces(s, p.escape_byte);
1557 if !is_line_end(s.first()) {
1558 return Err(error::expected("newline or eof", p.text.len() - s.len()));
1559 }
1560 as_ = Some((Keyword { span: as_span }, name));
1561 } else if !is_line_end(s.first()) {
1562 return Err(error::expected("AS", as_start));
1563 }
1564 }
1565 Ok(Instruction::From(FromInstruction { from: instruction, options, image, as_ }))
1566}
1567
1568#[inline]
1569fn parse_healthcheck<'a>(
1570 p: &mut ParseIter<'a>,
1571 s: &mut &'a [u8],
1572 instruction: Keyword,
1573) -> Result<Instruction<'a>, ErrorKind> {
1574 debug_assert!(token_slow(
1575 &mut p.text[instruction.span.clone()].as_bytes(),
1576 b"HEALTHCHECK",
1577 p.escape_byte,
1578 ));
1579 let options = parse_options(s, p.text, p.escape_byte);
1580 let Some((&b, s_next)) = s.split_first() else {
1581 return Err(error::expected("CMD or NONE", p.text.len() - s.len()));
1582 };
1583 let cmd_or_none_start = p.text.len() - s.len();
1584 match b & TO_UPPER8 {
1585 b'C' => {
1586 *s = s_next;
1587 if token(s, &b"CMD"[1..]) || token_slow(s, &b"CMD"[1..], p.escape_byte) {
1588 let cmd_span = cmd_or_none_start..p.text.len() - s.len();
1589 let cmd_keyword = Keyword { span: cmd_span };
1590 if spaces_or_line_end(s, p.escape_byte) {
1591 if is_maybe_json(s) {
1592 let mut tmp = *s;
1593 if let Ok((arguments, array_span)) =
1594 parse_json_array::<SmallVec<[_; 1]>>(&mut tmp, p.text, p.escape_byte)
1595 {
1596 debug_assert!(is_line_end(tmp.first()));
1597 if tmp.is_empty() {
1598 *s = &[];
1599 } else {
1600 *s = &tmp[1..];
1601 }
1602 if arguments.is_empty() {
1603 return Err(error::at_least_one_argument(instruction.span.start));
1604 }
1605 return Ok(Instruction::Healthcheck(HealthcheckInstruction {
1606 healthcheck: instruction,
1607 options,
1608 arguments: HealthcheckArguments::Cmd {
1609 cmd: cmd_keyword,
1610 arguments: Command::Exec(Spanned {
1611 span: array_span,
1612 value: arguments,
1613 }),
1614 },
1615 }));
1616 }
1617 }
1618 let arguments_start = p.text.len() - s.len();
1619 skip_this_line(s, p.escape_byte);
1620 let end = p.text.len() - s.len();
1621 let arguments = p.text[arguments_start..end].trim_ascii_end();
1622 return Ok(Instruction::Healthcheck(HealthcheckInstruction {
1623 healthcheck: instruction,
1624 options,
1625 arguments: HealthcheckArguments::Cmd {
1626 cmd: cmd_keyword,
1627 arguments: Command::Shell(Spanned {
1628 span: arguments_start..arguments_start + arguments.len(),
1629 value: arguments,
1630 }),
1631 },
1632 }));
1633 }
1634 }
1635 }
1636 b'N' => {
1637 *s = s_next;
1638 if token(s, &b"NONE"[1..]) || token_slow(s, &b"NONE"[1..], p.escape_byte) {
1639 let none_span = cmd_or_none_start..p.text.len() - s.len();
1640 skip_spaces(s, p.escape_byte);
1641 if !is_line_end(s.first()) {
1642 return Err(error::other(
1643 "HEALTHCHECK NONE does not accept arguments",
1644 p.text.len() - s.len(),
1645 ));
1646 }
1647 let none_keyword = Keyword { span: none_span };
1649 return Ok(Instruction::Healthcheck(HealthcheckInstruction {
1650 healthcheck: instruction,
1651 options,
1652 arguments: HealthcheckArguments::None { none: none_keyword },
1653 }));
1654 }
1655 }
1656 _ => {}
1657 }
1658 Err(error::expected("CMD or NONE", p.text.len() - s.len()))
1659}
1660
1661#[inline]
1662fn parse_label<'a>(
1663 p: &mut ParseIter<'a>,
1664 s: &mut &'a [u8],
1665 instruction: Keyword,
1666) -> Result<Instruction<'a>, ErrorKind> {
1667 debug_assert!(token_slow(
1668 &mut p.text[instruction.span.clone()].as_bytes(),
1669 b"LABEL",
1670 p.escape_byte,
1671 ));
1672 let arguments = collect_non_line_unescaped_consume_line(s, p.text, p.escape_byte);
1673 if arguments.value.is_empty() {
1674 return Err(error::at_least_one_argument(instruction.span.start));
1675 }
1676 Ok(Instruction::Label(LabelInstruction { label: instruction, arguments }))
1677}
1678
1679#[cold]
1680fn parse_maintainer<'a>(
1681 p: &mut ParseIter<'a>,
1682 s: &mut &'a [u8],
1683 instruction: Keyword,
1684) -> Result<Instruction<'a>, ErrorKind> {
1685 debug_assert!(token_slow(
1686 &mut p.text[instruction.span.clone()].as_bytes(),
1687 b"MAINTAINER",
1688 p.escape_byte,
1689 ));
1690 let name = collect_non_line_unescaped_consume_line(s, p.text, p.escape_byte);
1691 if name.value.is_empty() {
1692 return Err(error::exactly_one_argument(instruction.span.start));
1693 }
1694 Ok(Instruction::Maintainer(MaintainerInstruction { maintainer: instruction, name }))
1695}
1696
1697#[inline]
1698fn parse_onbuild<'a>(
1699 p: &mut ParseIter<'a>,
1700 s: &mut &'a [u8],
1701 instruction: Keyword,
1702) -> Result<Instruction<'a>, ErrorKind> {
1703 debug_assert!(token_slow(
1704 &mut p.text[instruction.span.clone()].as_bytes(),
1705 b"ONBUILD",
1706 p.escape_byte,
1707 ));
1708 if mem::replace(&mut p.in_onbuild, true) {
1710 return Err(error::other("ONBUILD ONBUILD is not allowed", instruction.span.start));
1711 }
1712 let Some((&b, s_next)) = s.split_first() else {
1713 return Err(error::expected("instruction after ONBUILD", instruction.span.start));
1714 };
1715 let inner_instruction = parse_instruction(p, s, b, s_next)?;
1738 p.in_onbuild = false;
1739 Ok(Instruction::Onbuild(OnbuildInstruction {
1740 onbuild: instruction,
1741 instruction: Box::new(inner_instruction),
1742 }))
1743}
1744
1745#[inline]
1746fn parse_run<'a>(
1747 p: &mut ParseIter<'a>,
1748 s: &mut &'a [u8],
1749 instruction: Keyword,
1750) -> Result<Instruction<'a>, ErrorKind> {
1751 debug_assert!(token_slow(
1752 &mut p.text[instruction.span.clone()].as_bytes(),
1753 b"RUN",
1754 p.escape_byte,
1755 ));
1756 let options = parse_options(s, p.text, p.escape_byte);
1757 if is_maybe_json(s) {
1758 let mut tmp = *s;
1759 if let Ok((arguments, array_span)) =
1760 parse_json_array::<SmallVec<[_; 1]>>(&mut tmp, p.text, p.escape_byte)
1761 {
1762 debug_assert!(is_line_end(tmp.first()));
1763 if tmp.is_empty() {
1764 *s = &[];
1765 } else {
1766 *s = &tmp[1..];
1767 }
1768 if arguments.is_empty() {
1769 return Err(error::at_least_one_argument(instruction.span.start));
1770 }
1771 return Ok(Instruction::Run(RunInstruction {
1772 run: instruction,
1773 options,
1774 arguments: Command::Exec(Spanned { span: array_span, value: arguments }),
1775 here_docs: vec![],
1777 }));
1778 }
1779 }
1780
1781 let mut strip_tab = false;
1783 let mut quote = None;
1784 let mut pos = 2;
1785 if s.len() >= 5 && s.starts_with(b"<<") && {
1787 if s[pos] == b'-' {
1788 strip_tab = true;
1789 pos += 1;
1790 }
1791 if matches!(s[pos], b'"' | b'\'') {
1792 quote = Some(s[pos]);
1793 pos += 1;
1794 }
1795 s[pos].is_ascii_alphanumeric()
1797 } {
1798 *s = &s[pos..];
1799 let delim_start = p.text.len() - s.len();
1800 while let Some((&b, s_next)) = s.split_first() {
1802 if b.is_ascii_alphanumeric() {
1803 *s = s_next;
1804 continue;
1805 }
1806 break;
1807 }
1808 let delim = &p.text.as_bytes()[delim_start..p.text.len() - s.len()];
1809 if let Some(quote) = quote {
1810 if let Some((&b, s_next)) = s.split_first() {
1811 if b != quote {
1812 return Err(error::expected_quote(quote, Some(b), p.text.len() - s.len()));
1813 }
1814 *s = s_next;
1815 } else {
1816 return Err(error::expected_quote(quote, None, p.text.len() - s.len()));
1817 }
1818 }
1819 let arguments_start = p.text.len() - s.len();
1821 skip_this_line(s, p.escape_byte);
1822 let end = p.text.len() - s.len();
1823 let arguments = p.text[arguments_start..end].trim_ascii_end();
1824 let here_doc = if strip_tab {
1825 let (here_doc, span) = collect_here_doc_strip_tab(s, p.text, p.escape_byte, delim)?;
1826 HereDoc { span, expand: quote.is_none(), value: here_doc }
1827 } else {
1828 let (here_doc, span) = collect_here_doc_no_strip_tab(s, p.text, p.escape_byte, delim)?;
1829 HereDoc { span, expand: quote.is_none(), value: here_doc.into() }
1830 };
1831 return Ok(Instruction::Run(RunInstruction {
1832 run: instruction,
1833 options,
1834 arguments: Command::Shell(Spanned {
1835 span: arguments_start..arguments_start + arguments.len(),
1836 value: arguments,
1837 }),
1838 here_docs: vec![here_doc],
1840 }));
1841 }
1842
1843 let arguments_start = p.text.len() - s.len();
1844 skip_this_line(s, p.escape_byte);
1845 let end = p.text.len() - s.len();
1846 let arguments = p.text[arguments_start..end].trim_ascii_end();
1847 Ok(Instruction::Run(RunInstruction {
1848 run: instruction,
1849 options,
1850 arguments: Command::Shell(Spanned {
1851 span: arguments_start..arguments_start + arguments.len(),
1852 value: arguments,
1853 }),
1854 here_docs: vec![],
1855 }))
1856}
1857
1858#[inline]
1859fn parse_shell<'a>(
1860 p: &mut ParseIter<'a>,
1861 s: &mut &'a [u8],
1862 instruction: Keyword,
1863) -> Result<Instruction<'a>, ErrorKind> {
1864 debug_assert!(token_slow(
1865 &mut p.text[instruction.span.clone()].as_bytes(),
1866 b"SHELL",
1867 p.escape_byte,
1868 ));
1869 if !is_maybe_json(s) {
1870 return Err(error::expected("JSON array", p.text.len() - s.len()));
1871 }
1872 match parse_json_array::<SmallVec<[_; 4]>>(s, p.text, p.escape_byte) {
1873 Ok((arguments, _array_span)) => {
1874 if !s.is_empty() {
1875 *s = &s[1..];
1876 }
1877 if arguments.is_empty() {
1878 return Err(error::at_least_one_argument(instruction.span.start));
1879 }
1880 Ok(Instruction::Shell(ShellInstruction { shell: instruction, arguments }))
1881 }
1882 Err(array_start) => Err(error::json(array_start)),
1883 }
1884}
1885
1886#[inline]
1887fn parse_stopsignal<'a>(
1888 p: &mut ParseIter<'a>,
1889 s: &mut &'a [u8],
1890 instruction: Keyword,
1891) -> Result<Instruction<'a>, ErrorKind> {
1892 debug_assert!(token_slow(
1893 &mut p.text[instruction.span.clone()].as_bytes(),
1894 b"STOPSIGNAL",
1895 p.escape_byte,
1896 ));
1897 let arguments = collect_non_line_unescaped_consume_line(s, p.text, p.escape_byte);
1899 if arguments.value.is_empty() {
1900 return Err(error::exactly_one_argument(instruction.span.start));
1901 }
1902 Ok(Instruction::Stopsignal(StopsignalInstruction { stopsignal: instruction, arguments }))
1903}
1904
1905#[inline]
1906fn parse_user<'a>(
1907 p: &mut ParseIter<'a>,
1908 s: &mut &'a [u8],
1909 instruction: Keyword,
1910) -> Result<Instruction<'a>, ErrorKind> {
1911 debug_assert!(token_slow(
1912 &mut p.text[instruction.span.clone()].as_bytes(),
1913 b"USER",
1914 p.escape_byte,
1915 ));
1916 let arguments = collect_non_line_unescaped_consume_line(s, p.text, p.escape_byte);
1918 if arguments.value.is_empty() {
1919 return Err(error::exactly_one_argument(instruction.span.start));
1920 }
1921 Ok(Instruction::User(UserInstruction { user: instruction, arguments }))
1922}
1923
1924#[inline]
1925fn parse_volume<'a>(
1926 p: &mut ParseIter<'a>,
1927 s: &mut &'a [u8],
1928 instruction: Keyword,
1929) -> Result<Instruction<'a>, ErrorKind> {
1930 debug_assert!(token_slow(
1931 &mut p.text[instruction.span.clone()].as_bytes(),
1932 b"VOLUME",
1933 p.escape_byte,
1934 ));
1935 if is_maybe_json(s) {
1936 let mut tmp = *s;
1937 if let Ok((arguments, array_span)) = parse_json_array(&mut tmp, p.text, p.escape_byte) {
1938 debug_assert!(is_line_end(tmp.first()));
1939 if tmp.is_empty() {
1940 *s = &[];
1941 } else {
1942 *s = &tmp[1..];
1943 }
1944 return Ok(Instruction::Volume(VolumeInstruction {
1946 volume: instruction,
1947 arguments: JsonOrStringArray::Json(Spanned { span: array_span, value: arguments }),
1948 }));
1949 }
1950 }
1951 let arguments: SmallVec<[_; 1]> =
1952 collect_space_separated_unescaped_consume_line(s, p.text, p.escape_byte);
1953 if arguments.is_empty() {
1954 return Err(error::at_least_one_argument(instruction.span.start));
1956 }
1957 Ok(Instruction::Volume(VolumeInstruction {
1958 volume: instruction,
1959 arguments: JsonOrStringArray::String(arguments),
1960 }))
1961}
1962
1963#[inline]
1964fn parse_workdir<'a>(
1965 p: &mut ParseIter<'a>,
1966 s: &mut &'a [u8],
1967 instruction: Keyword,
1968) -> Result<Instruction<'a>, ErrorKind> {
1969 debug_assert!(token_slow(
1970 &mut p.text[instruction.span.clone()].as_bytes(),
1971 b"WORKDIR",
1972 p.escape_byte,
1973 ));
1974 let arguments = collect_non_line_unescaped_consume_line(s, p.text, p.escape_byte);
1976 if arguments.value.is_empty() {
1977 return Err(error::exactly_one_argument(instruction.span.start));
1978 }
1979 Ok(Instruction::Workdir(WorkdirInstruction { workdir: instruction, arguments }))
1980}
1981
1982const LINE: u8 = 1 << 0;
1987const SPACE: u8 = 1 << 1;
1989const WHITESPACE: u8 = 1 << 2;
1991const COMMENT: u8 = 1 << 3;
1993const DOUBLE_QUOTE: u8 = 1 << 4;
1995const POSSIBLE_ESCAPE: u8 = 1 << 5;
1997const EQ: u8 = 1 << 6;
1999
2000static TABLE: [u8; 256] = {
2001 let mut table = [0; 256];
2002 let mut i = 0;
2003 loop {
2004 match i {
2005 b' ' | b'\t' => table[i as usize] = WHITESPACE | SPACE,
2006 b'\n' | b'\r' => table[i as usize] = WHITESPACE | LINE,
2007 b'#' => table[i as usize] = COMMENT,
2008 b'"' => table[i as usize] = DOUBLE_QUOTE,
2009 b'\\' | b'`' => table[i as usize] = POSSIBLE_ESCAPE,
2010 b'=' => table[i as usize] = EQ,
2011 _ => {}
2012 }
2013 if i == u8::MAX {
2014 break;
2015 }
2016 i += 1;
2017 }
2018 table
2019};
2020
2021const UTF8_BOM: &[u8] = &[0xEF, 0xBB, 0xBF];
2022
2023trait Store<T>: Sized {
2024 fn new() -> Self;
2025 fn push(&mut self, val: T);
2026}
2027impl<T> Store<T> for Vec<T> {
2028 #[inline]
2029 fn new() -> Self {
2030 Self::new()
2031 }
2032 #[inline]
2033 fn push(&mut self, val: T) {
2034 self.push(val);
2035 }
2036}
2037impl<T, const N: usize> Store<T> for SmallVec<[T; N]> {
2038 #[inline]
2039 fn new() -> Self {
2040 Self::new()
2041 }
2042 #[inline]
2043 fn push(&mut self, val: T) {
2044 self.push(val);
2045 }
2046}
2047impl<'a, const N: usize> Store<UnescapedString<'a>>
2048 for (SmallVec<[Source<'a>; N]>, Option<UnescapedString<'a>>)
2049{
2050 #[inline]
2051 fn new() -> Self {
2052 (SmallVec::new(), None)
2053 }
2054 #[inline]
2055 fn push(&mut self, val: UnescapedString<'a>) {
2056 if let Some(val) = self.1.replace(val) {
2057 self.0.push(Source::Path(val));
2058 }
2059 }
2060}
2061
2062#[inline]
2063fn parse_options<'a, S: Store<Flag<'a>>>(s: &mut &[u8], start: &'a str, escape_byte: u8) -> S {
2064 let mut options = S::new();
2065 'outer: while let Some((&b'-', mut s_next)) = s.split_first() {
2066 loop {
2067 let Some((&b, s_next_next)) = s_next.split_first() else {
2068 break 'outer;
2069 };
2070 if b == b'-' {
2071 s_next = s_next_next;
2072 break;
2073 }
2074 if skip_line_escape(&mut s_next, b, s_next_next, escape_byte) {
2075 skip_line_escape_followup(&mut s_next, escape_byte);
2076 continue;
2077 }
2078 break 'outer;
2079 }
2080 let flag_start = start.len() - s.len();
2081 *s = s_next;
2082 let name = collect_until_unescaped::<{ WHITESPACE | EQ }>(s, start, escape_byte);
2083 let Some((&b'=', s_next)) = s.split_first() else {
2084 options.push(Flag { flag_start, name, value: None });
2085 skip_spaces(s, escape_byte);
2086 continue;
2087 };
2088 *s = s_next;
2089 let value = collect_non_whitespace_unescaped(s, start, escape_byte);
2090 options.push(Flag { flag_start, name, value: Some(value) });
2091 skip_spaces(s, escape_byte);
2092 }
2093 options
2094}
2095
2096fn parse_json_array<'a, S: Store<UnescapedString<'a>>>(
2097 s: &mut &[u8],
2098 start: &'a str,
2099 escape_byte: u8,
2100) -> Result<(S, Span), usize> {
2101 debug_assert_eq!(s.first(), Some(&b'['));
2102 debug_assert_ne!(s.get(1), Some(&b'['));
2103 let mut res = S::new();
2104 let array_start = start.len() - s.len();
2105 *s = &s[1..];
2106 skip_spaces(s, escape_byte);
2107 let (&b, s_next) = s.split_first().ok_or(array_start)?;
2108 match b {
2109 b'"' => {
2110 *s = s_next;
2111 loop {
2112 let full_word_start = start.len() - s.len();
2113 let mut word_start = full_word_start;
2114 let mut buf = String::new();
2115 loop {
2116 let (&b, s_next) = s.split_first().ok_or(array_start)?;
2117 if TABLE[b as usize] & (LINE | DOUBLE_QUOTE | POSSIBLE_ESCAPE) == 0 {
2118 *s = s_next;
2119 continue;
2120 }
2121 match b {
2122 b'"' => break,
2123 b'\n' | b'\r' => return Err(array_start),
2124 _ => {}
2125 }
2126 let word_end = start.len() - s.len();
2127 if skip_line_escape(s, b, s_next, escape_byte) {
2128 skip_line_escape_followup(s, escape_byte);
2129 buf.push_str(&start[word_start..word_end]);
2131 word_start = start.len() - s.len();
2132 continue;
2133 }
2134 if b == b'\\' {
2135 let word_end = start.len() - s.len();
2137 buf.push_str(&start[word_start..word_end]);
2138 *s = s_next;
2139 let (new, new_start) = match *s.first().ok_or(array_start)? {
2140 b @ (b'"' | b'\\' | b'/') => (b as char, 1),
2141 b'b' => ('\x08', 1),
2142 b'f' => ('\x0c', 1),
2143 b'n' => ('\n', 1),
2144 b'r' => ('\r', 1),
2145 b't' => ('\t', 1),
2146 b'u' => (parse_json_hex_escape(s, array_start)?, 5),
2147 _ => return Err(array_start), };
2149 buf.push(new);
2150 *s = &s[new_start..];
2151 word_start = start.len() - s.len();
2152 continue;
2153 }
2154 *s = s_next;
2155 }
2156 let word_end = start.len() - s.len();
2157 let value = if buf.is_empty() {
2158 Cow::Borrowed(&start[word_start..word_end])
2160 } else {
2161 buf.push_str(&start[word_start..word_end]);
2162 Cow::Owned(buf)
2163 };
2164 res.push(UnescapedString { span: full_word_start..word_end, value });
2165 *s = &s[1..]; skip_spaces(s, escape_byte);
2167 let (&b, s_next) = s.split_first().ok_or(array_start)?;
2168 match b {
2169 b',' => {
2170 *s = s_next;
2171 skip_spaces(s, escape_byte);
2172 let (&b, s_next) = s.split_first().ok_or(array_start)?;
2173 if b == b'"' {
2174 *s = s_next;
2175 continue;
2176 }
2177 return Err(array_start);
2178 }
2179 b']' => {
2180 *s = s_next;
2181 break;
2182 }
2183 _ => return Err(array_start),
2184 }
2185 }
2186 }
2187 b']' => *s = s_next,
2188 _ => return Err(array_start),
2189 }
2190 let array_end = start.len() - s.len();
2191 skip_spaces(s, escape_byte);
2192 if !is_line_end(s.first()) {
2193 return Err(array_start);
2194 }
2195 Ok((res, array_start..array_end))
2196}
2197#[cold]
2199fn parse_json_hex_escape(s: &mut &[u8], array_start: usize) -> Result<char, usize> {
2200 fn decode_hex_escape(s: &mut &[u8], array_start: usize) -> Result<u16, usize> {
2201 if s.len() < 4 {
2202 return Err(array_start); }
2204
2205 let mut n = 0;
2206 for _ in 0..4 {
2207 let ch = decode_hex_val(s[0]);
2208 *s = &s[1..];
2209 match ch {
2210 None => return Err(array_start), Some(val) => {
2212 n = (n << 4) + val;
2213 }
2214 }
2215 }
2216 Ok(n)
2217 }
2218
2219 fn decode_hex_val(val: u8) -> Option<u16> {
2220 let n = HEX_DECODE_TABLE[val as usize] as u16;
2221 if n == u8::MAX as u16 { None } else { Some(n) }
2222 }
2223
2224 let c = match decode_hex_escape(s, array_start)? {
2225 _n @ 0xDC00..=0xDFFF => return Err(array_start), n1 @ 0xD800..=0xDBFF => {
2232 if s.first() == Some(&b'\\') {
2233 *s = &s[1..];
2234 } else {
2235 return Err(array_start); }
2237
2238 if s.first() == Some(&b'u') {
2239 *s = &s[1..];
2240 } else {
2241 return Err(array_start); }
2243
2244 let n2 = decode_hex_escape(s, array_start)?;
2245
2246 if n2 < 0xDC00 || n2 > 0xDFFF {
2247 return Err(array_start); }
2249
2250 let n = ((((n1 - 0xD800) as u32) << 10) | (n2 - 0xDC00) as u32) + 0x1_0000;
2251
2252 match char::from_u32(n) {
2253 Some(c) => c,
2254 None => return Err(array_start), }
2256 }
2257
2258 n => char::from_u32(n as u32).unwrap(),
2261 };
2262 Ok(c)
2263}
2264#[allow(clippy::needless_raw_string_hashes)]
2265#[test]
2266fn test_parse_json_array() {
2267 let t = r#"[]"#;
2269 let mut s = t.as_bytes();
2270 assert_eq!(&*parse_json_array::<Vec<_>>(&mut s, t, b'\\').unwrap().0, &[]);
2271 assert_eq!(s, b"");
2272 let t = r#"[ ]"#;
2273 let mut s = t.as_bytes();
2274 assert_eq!(&*parse_json_array::<Vec<_>>(&mut s, t, b'\\').unwrap().0, &[]);
2275 assert_eq!(s, b"");
2276 let t = r#"["abc"]"#;
2278 let mut s = t.as_bytes();
2279 assert_eq!(&*parse_json_array::<Vec<_>>(&mut s, t, b'\\').unwrap().0, &[UnescapedString {
2280 span: 2..5,
2281 value: "abc".into()
2282 }]);
2283 assert_eq!(s, b"");
2284 let t = "[\"ab\",\"c\" , \"de\" ] \n";
2286 let mut s = t.as_bytes();
2287 assert_eq!(&*parse_json_array::<Vec<_>>(&mut s, t, b'\\').unwrap().0, &[
2288 UnescapedString { span: 2..4, value: "ab".into() },
2289 UnescapedString { span: 7..8, value: "c".into() },
2290 UnescapedString { span: 14..16, value: "de".into() },
2291 ]);
2292 assert_eq!(s, b"\n");
2293 let t = "[\"a\\\"\\\\\\/\\b\\f\\n\\r\\tbc\"]";
2296 let mut s = t.as_bytes();
2297 assert_eq!(&*parse_json_array::<Vec<_>>(&mut s, t, b'\\').unwrap().0, &[UnescapedString {
2298 span: 2..21,
2299 value: "a\"\\/\x08\x0c\n\r\tbc".into()
2300 }]);
2301 assert_eq!(s, b"");
2302
2303 let t = r#"['abc']"#;
2305 let mut s = t.as_bytes();
2306 assert_eq!(parse_json_array::<Vec<_>>(&mut s, t, b'\\'), Err(0));
2307 assert_eq!(s, br#"'abc']"#);
2308 let t = r#"["abc",]"#;
2310 let mut s = t.as_bytes();
2311 assert_eq!(parse_json_array::<Vec<_>>(&mut s, t, b'\\'), Err(0));
2312 assert_eq!(s, br#"]"#);
2313 let t = r#"["abc"] c"#;
2315 let mut s = t.as_bytes();
2316 assert_eq!(parse_json_array::<Vec<_>>(&mut s, t, b'\\'), Err(0));
2317 assert_eq!(s, br#"c"#);
2318 let t = "[\"ab\\c\"]";
2320 let mut s = t.as_bytes();
2321 assert_eq!(parse_json_array::<Vec<_>>(&mut s, t, b'\\'), Err(0));
2322 assert_eq!(s, b"c\"]");
2323 }
2325
2326#[inline]
2329fn skip_spaces_no_escape(s: &mut &[u8]) -> bool {
2330 let start = *s;
2331 while let Some((&b, s_next)) = s.split_first() {
2332 if TABLE[b as usize] & SPACE != 0 {
2333 *s = s_next;
2334 continue;
2335 }
2336 break;
2337 }
2338 start.len() != s.len()
2339}
2340#[inline]
2343fn skip_spaces(s: &mut &[u8], escape_byte: u8) -> bool {
2344 let mut has_space = false;
2345 while let Some((&b, s_next)) = s.split_first() {
2346 let t = TABLE[b as usize];
2347 if t & (SPACE | POSSIBLE_ESCAPE) != 0 {
2348 if t & SPACE != 0 {
2349 *s = s_next;
2350 has_space = true;
2351 continue;
2352 }
2353 if skip_line_escape(s, b, s_next, escape_byte) {
2354 skip_line_escape_followup(s, escape_byte);
2355 continue;
2356 }
2357 }
2358 break;
2359 }
2360 has_space
2361}
2362#[inline]
2365fn spaces_or_line_end(s: &mut &[u8], escape_byte: u8) -> bool {
2366 let mut has_space = false;
2367 loop {
2368 let Some((&b, s_next)) = s.split_first() else { return true };
2369 {
2370 let t = TABLE[b as usize];
2371 if t & (WHITESPACE | POSSIBLE_ESCAPE) != 0 {
2372 if t & SPACE != 0 {
2373 *s = s_next;
2374 has_space = true;
2375 continue;
2376 }
2377 if t & LINE != 0 {
2378 return true;
2379 }
2380 if skip_line_escape(s, b, s_next, escape_byte) {
2381 skip_line_escape_followup(s, escape_byte);
2382 continue;
2383 }
2384 }
2385 break;
2386 }
2387 }
2388 has_space
2389}
2390
2391#[inline]
2392fn skip_comments_and_whitespaces(s: &mut &[u8], escape_byte: u8) {
2393 while let Some((&b, s_next)) = s.split_first() {
2394 let t = TABLE[b as usize];
2395 if t & (WHITESPACE | COMMENT | POSSIBLE_ESCAPE) != 0 {
2396 if t & WHITESPACE != 0 {
2397 *s = s_next;
2398 continue;
2399 }
2400 if t & COMMENT != 0 {
2401 *s = s_next;
2402 skip_this_line_no_escape(s);
2403 continue;
2404 }
2405 if skip_line_escape(s, b, s_next, escape_byte) {
2406 skip_line_escape_followup(s, escape_byte);
2407 continue;
2408 }
2409 }
2410 break;
2411 }
2412}
2413
2414#[inline]
2415fn is_line_end(b: Option<&u8>) -> bool {
2416 matches!(b, Some(b'\n' | b'\r') | None)
2417}
2418#[inline]
2419fn is_maybe_json(s: &[u8]) -> bool {
2420 s.first() == Some(&b'[') && s.get(1) != Some(&b'[')
2423}
2424
2425#[inline]
2426fn collect_here_doc_no_strip_tab<'a>(
2427 s: &mut &[u8],
2428 start: &'a str,
2429 _escape_byte: u8,
2430 delim: &[u8],
2431) -> Result<(&'a str, Span), ErrorKind> {
2432 let here_doc_start = start.len() - s.len();
2433 loop {
2434 if s.len() < delim.len() {
2435 return Err(error::expected_here_doc_end(delim, start.len() - s.len()));
2436 }
2437 if s.starts_with(delim) && is_line_end(s.get(delim.len())) {
2438 break;
2439 }
2440 skip_this_line_no_escape(s);
2441 }
2442 let end = start.len() - s.len();
2443 *s = &s[delim.len()..];
2444 if !s.is_empty() {
2445 *s = &s[1..];
2446 }
2447 let span = here_doc_start..end;
2448 Ok((&start[span.clone()], span))
2449}
2450#[inline]
2451fn collect_here_doc_strip_tab<'a>(
2452 s: &mut &[u8],
2453 start: &'a str,
2454 _escape_byte: u8,
2455 delim: &[u8],
2456) -> Result<(Cow<'a, str>, Span), ErrorKind> {
2457 let here_doc_start = start.len() - s.len();
2458 let mut current_start = here_doc_start;
2459 let mut res = String::new();
2460 loop {
2461 if s.len() < delim.len() {
2462 return Err(error::expected_here_doc_end(delim, start.len() - s.len()));
2463 }
2464 if let Some((&b'\t', s_next)) = s.split_first() {
2465 let end = start.len() - s.len();
2466 res.push_str(&start[current_start..end]);
2467 *s = s_next;
2468 while let Some((&b'\t', s_next)) = s.split_first() {
2469 *s = s_next;
2470 }
2471 current_start = start.len() - s.len();
2472 }
2473 if s.starts_with(delim) && is_line_end(s.get(delim.len())) {
2474 break;
2475 }
2476 skip_this_line_no_escape(s);
2477 }
2478 let end = start.len() - s.len();
2479 *s = &s[delim.len()..];
2480 if !s.is_empty() {
2481 *s = &s[1..];
2482 }
2483 let span = here_doc_start..end;
2484 if here_doc_start == current_start {
2485 Ok((Cow::Borrowed(&start[span.clone()]), span))
2486 } else {
2487 res.push_str(&start[current_start..end]);
2488 Ok((Cow::Owned(res), span))
2489 }
2490}
2491#[inline]
2493fn collect_space_separated_unescaped_consume_line<'a, S: Store<UnescapedString<'a>>>(
2494 s: &mut &[u8],
2495 start: &'a str,
2496 escape_byte: u8,
2497) -> S {
2498 let mut res = S::new();
2499 loop {
2500 let val = collect_non_whitespace_unescaped(s, start, escape_byte);
2501 if !val.value.is_empty() {
2502 res.push(val);
2503 if skip_spaces(s, escape_byte) {
2504 continue;
2505 }
2506 }
2507 debug_assert!(is_line_end(s.first()));
2508 if !s.is_empty() {
2509 *s = &s[1..];
2510 }
2511 break;
2512 }
2513 res
2514}
2515#[inline]
2516fn collect_non_whitespace_unescaped<'a>(
2517 s: &mut &[u8],
2518 start: &'a str,
2519 escape_byte: u8,
2520) -> UnescapedString<'a> {
2521 collect_until_unescaped::<WHITESPACE>(s, start, escape_byte)
2522}
2523#[inline]
2524fn collect_non_line_unescaped_consume_line<'a>(
2525 s: &mut &[u8],
2526 start: &'a str,
2527 escape_byte: u8,
2528) -> UnescapedString<'a> {
2529 let mut val = collect_until_unescaped::<LINE>(s, start, escape_byte);
2530 debug_assert!(is_line_end(s.first()));
2531 if !s.is_empty() {
2532 *s = &s[1..];
2533 }
2534 match &mut val.value {
2536 Cow::Borrowed(v) => {
2537 while let Some(b' ' | b'\t') = v.as_bytes().last() {
2538 *v = &v[..v.len() - 1];
2539 val.span.end -= 1;
2540 }
2541 }
2542 Cow::Owned(v) => {
2543 while let Some(b' ' | b'\t') = v.as_bytes().last() {
2544 v.pop();
2545 val.span.end -= 1;
2546 }
2547 }
2548 }
2549 val
2550}
2551#[inline]
2552fn collect_until_unescaped<'a, const UNTIL_MASK: u8>(
2553 s: &mut &[u8],
2554 start: &'a str,
2555 escape_byte: u8,
2556) -> UnescapedString<'a> {
2557 let full_word_start = start.len() - s.len();
2558 let mut word_start = full_word_start;
2559 let mut buf = String::new();
2560 while let Some((&b, s_next)) = s.split_first() {
2561 let t = TABLE[b as usize];
2562 if t & (UNTIL_MASK | POSSIBLE_ESCAPE) != 0 {
2563 if t & UNTIL_MASK != 0 {
2564 break;
2565 }
2566 let word_end = start.len() - s.len();
2567 if skip_line_escape(s, b, s_next, escape_byte) {
2568 skip_line_escape_followup(s, escape_byte);
2569 buf.push_str(&start[word_start..word_end]);
2570 word_start = start.len() - s.len();
2571 continue;
2572 }
2573 }
2574 *s = s_next;
2575 }
2576 let word_end = start.len() - s.len();
2577 let value = if buf.is_empty() {
2578 Cow::Borrowed(&start[word_start..word_end])
2580 } else {
2581 buf.push_str(&start[word_start..word_end]);
2582 Cow::Owned(buf)
2583 };
2584 UnescapedString { span: full_word_start..word_end, value }
2585}
2586
2587#[inline]
2590fn skip_non_whitespace_no_escape(s: &mut &[u8]) -> bool {
2591 let start = *s;
2592 while let Some((&b, s_next)) = s.split_first() {
2593 if TABLE[b as usize] & WHITESPACE != 0 {
2594 break;
2595 }
2596 *s = s_next;
2597 }
2598 start.len() != s.len()
2599}
2600#[inline]
2619fn skip_line_escape<'a>(s: &mut &'a [u8], b: u8, s_next: &'a [u8], escape_byte: u8) -> bool {
2620 if b == escape_byte {
2621 if let Some((&b, mut s_next)) = s_next.split_first() {
2622 if b == b'\n' {
2623 *s = s_next;
2624 return true;
2625 }
2626 if b == b'\r' {
2627 if s_next.first() == Some(&b'\n') {
2628 *s = &s_next[1..];
2629 } else {
2630 *s = s_next;
2631 }
2632 return true;
2633 }
2634 if TABLE[b as usize] & SPACE != 0 {
2637 skip_spaces_no_escape(&mut s_next);
2638 if let Some((&b, s_next)) = s_next.split_first() {
2639 if b == b'\n' {
2640 *s = s_next;
2641 return true;
2642 }
2643 if b == b'\r' {
2644 if s_next.first() == Some(&b'\n') {
2645 *s = &s_next[1..];
2646 } else {
2647 *s = s_next;
2648 }
2649 return true;
2650 }
2651 }
2652 }
2653 }
2654 }
2655 false
2656}
2657#[inline]
2658fn skip_line_escape_followup(s: &mut &[u8], _escape_byte: u8) {
2659 while let Some((&b, mut s_next)) = s.split_first() {
2660 let t = TABLE[b as usize];
2661 if t & (WHITESPACE | COMMENT) != 0 {
2662 if t & SPACE != 0 {
2663 skip_spaces_no_escape(&mut s_next);
2665 if let Some((&b, s_next)) = s_next.split_first() {
2666 let t = TABLE[b as usize];
2667 if t & (COMMENT | LINE) != 0 {
2668 *s = s_next;
2670 if t & COMMENT != 0 {
2671 skip_this_line_no_escape(s);
2672 }
2673 continue;
2674 }
2675 }
2676 } else {
2677 *s = s_next;
2679 if t & COMMENT != 0 {
2680 skip_this_line_no_escape(s);
2681 }
2682 continue;
2683 }
2684 }
2685 break;
2686 }
2687}
2688
2689#[inline]
2690fn skip_this_line_no_escape(s: &mut &[u8]) {
2691 while let Some((&b, s_next)) = s.split_first() {
2692 *s = s_next;
2693 if TABLE[b as usize] & LINE != 0 {
2694 break;
2695 }
2696 }
2697}
2698#[inline]
2700fn skip_this_line(s: &mut &[u8], escape_byte: u8) {
2701 let mut has_space_only = 0;
2702 while let Some((&b, s_next)) = s.split_first() {
2703 let t = TABLE[b as usize];
2704 if t & (LINE | COMMENT | POSSIBLE_ESCAPE) != 0 {
2705 if t & LINE != 0 {
2706 *s = s_next;
2707 break;
2708 }
2709 if has_space_only != 0 && t & COMMENT != 0 {
2710 *s = s_next;
2711 skip_this_line_no_escape(s);
2712 continue;
2713 }
2714 if skip_line_escape(s, b, s_next, escape_byte) {
2715 skip_line_escape_followup(s, escape_byte);
2716 has_space_only = SPACE;
2717 continue;
2718 }
2719 }
2720 has_space_only &= t;
2721 *s = s_next;
2722 }
2723}
2724
2725#[inline(always)]
2726fn token(s: &mut &[u8], token: &'static [u8]) -> bool {
2727 let matched = starts_with_ignore_ascii_case(s, token);
2728 if matched {
2729 *s = &s[token.len()..];
2730 true
2731 } else {
2732 false
2733 }
2734}
2735#[cold]
2736fn token_slow(s: &mut &[u8], mut token: &'static [u8], escape_byte: u8) -> bool {
2737 debug_assert!(!token.is_empty() && token.iter().all(|&n| n & TO_UPPER8 == n));
2738 if s.len() < token.len() {
2739 return false;
2740 }
2741 let mut tmp = *s;
2742 while let Some((&b, tmp_next)) = tmp.split_first() {
2743 if b & TO_UPPER8 == token[0] {
2744 tmp = tmp_next;
2745 token = &token[1..];
2746 if token.is_empty() {
2747 *s = tmp;
2748 return true;
2749 }
2750 continue;
2751 }
2752 if skip_line_escape(&mut tmp, b, tmp_next, escape_byte) {
2753 skip_line_escape_followup(&mut tmp, escape_byte);
2754 continue;
2755 }
2756 break;
2757 }
2758 false
2759}
2760
2761const TO_UPPER8: u8 = 0xDF;
2762const TO_UPPER64: u64 = 0xDFDFDFDFDFDFDFDF;
2763
2764#[inline(always)] fn starts_with_ignore_ascii_case(mut s: &[u8], mut needle: &'static [u8]) -> bool {
2766 debug_assert!(!needle.is_empty() && needle.iter().all(|&n| n & TO_UPPER8 == n));
2767 if s.len() < needle.len() {
2768 return false;
2769 }
2770 if needle.len() == 1 {
2771 return needle[0] == s[0] & TO_UPPER8;
2772 }
2773 if needle.len() >= 8 {
2774 loop {
2775 if u64::from_ne_bytes(needle[..8].try_into().unwrap())
2776 != u64::from_ne_bytes(s[..8].try_into().unwrap()) & TO_UPPER64
2777 {
2778 return false;
2779 }
2780 needle = &needle[8..];
2781 s = &s[8..];
2782 if needle.len() < 8 {
2783 if needle.is_empty() {
2784 return true;
2785 }
2786 break;
2787 }
2788 }
2789 }
2790 let s = {
2791 let mut buf = [0; 8];
2792 buf[..needle.len()].copy_from_slice(&s[..needle.len()]);
2793 u64::from_ne_bytes(buf)
2794 };
2795 let needle = {
2796 let mut buf = [0; 8];
2797 buf[..needle.len()].copy_from_slice(needle);
2798 u64::from_ne_bytes(buf)
2799 };
2800 needle == s & TO_UPPER64
2801}
2802#[test]
2803fn test_starts_with_ignore_ascii_case() {
2804 assert!(starts_with_ignore_ascii_case(b"ABC", b"ABC"));
2805 assert!(starts_with_ignore_ascii_case(b"abc", b"ABC"));
2806 assert!(starts_with_ignore_ascii_case(b"AbC", b"ABC"));
2807 assert!(!starts_with_ignore_ascii_case(b"ABB", b"ABC"));
2808 assert!(starts_with_ignore_ascii_case(b"ABCDEFGH", b"ABCDEFGH"));
2809 assert!(starts_with_ignore_ascii_case(b"abcdefgh", b"ABCDEFGH"));
2810 assert!(starts_with_ignore_ascii_case(b"AbCdEfGh", b"ABCDEFGH"));
2811 assert!(!starts_with_ignore_ascii_case(b"ABCDEFGc", b"ABCDEFGH"));
2812 assert!(starts_with_ignore_ascii_case(
2813 b"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
2814 b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2815 ));
2816 assert!(starts_with_ignore_ascii_case(
2817 b"abcdefghijklmnopqrstuvwxyz",
2818 b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2819 ));
2820 assert!(starts_with_ignore_ascii_case(
2821 b"aBcDeFgHiJkLmNoPqRsTuVwXyZ",
2822 b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2823 ));
2824 assert!(!starts_with_ignore_ascii_case(
2825 b"aBcDeFgHiJkLmNoPqRsTuVwXyc",
2826 b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2827 ));
2828}
2829
2830#[rustfmt::skip]
2832static HEX_DECODE_TABLE: [u8; 256] = {
2833 const __: u8 = u8::MAX;
2834 [
2835 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, __, __, __, __, __, __, __, 10, 11, 12, 13, 14, 15, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 10, 11, 12, 13, 14, 15, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, ]
2853};