1use crate::{CliError, CliErrorKind};
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub struct ArgLimits {
15 pub max_words: usize,
16 pub max_word_bytes: usize,
17 pub max_total_bytes: usize,
18}
19
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct RawArgs {
22 words: Vec<String>,
23}
24
25impl Default for ArgLimits {
26 fn default() -> Self {
27 Self {
28 max_words: 1024,
29 max_word_bytes: 64 * 1024,
30 max_total_bytes: 1024 * 1024,
31 }
32 }
33}
34
35impl RawArgs {
36 pub fn from_env() -> Result<Self, CliError> {
37 let mut words = Vec::new();
38 for (index, word) in std::env::args_os().skip(1).enumerate() {
39 let word = word.into_string().map_err(|_| {
40 CliError::new(
41 CliErrorKind::InvalidInput,
42 format!("argument {} is not valid UTF-8", index + 1),
43 )
44 })?;
45 words.push(word);
46 }
47 Self::parse_with_limits(words, ArgLimits::default())
48 }
49
50 pub fn parse<I>(args: I) -> Result<Self, CliError>
51 where
52 I: IntoIterator,
53 I::Item: Into<String>,
54 {
55 Self::parse_with_limits(args, ArgLimits::default())
56 }
57
58 pub fn parse_with_limits<I>(args: I, limits: ArgLimits) -> Result<Self, CliError>
59 where
60 I: IntoIterator,
61 I::Item: Into<String>,
62 {
63 let mut words = Vec::new();
64 let mut total_bytes = 0usize;
65 for arg in args {
66 let value = arg.into();
67 validate_raw_word(&value, words.len(), total_bytes, limits)?;
68 total_bytes = total_bytes.saturating_add(value.len());
69 words.push(value);
70 }
71 Ok(Self { words })
72 }
73
74 pub fn words(&self) -> &[String] {
75 &self.words
76 }
77}
78
79fn validate_raw_word(
80 value: &str,
81 count: usize,
82 total: usize,
83 limits: ArgLimits,
84) -> Result<(), CliError> {
85 if count >= limits.max_words {
86 return Err(CliError::new(
87 CliErrorKind::InvalidInput,
88 format!("argument count exceeds limit of {}", limits.max_words),
89 ));
90 }
91 if value.len() > limits.max_word_bytes {
92 return Err(CliError::new(
93 CliErrorKind::InvalidInput,
94 format!(
95 "argument {} exceeds byte limit of {}",
96 count + 1,
97 limits.max_word_bytes
98 ),
99 ));
100 }
101 if total.saturating_add(value.len()) > limits.max_total_bytes {
102 return Err(CliError::new(
103 CliErrorKind::InvalidInput,
104 format!(
105 "total argument bytes exceed limit of {}",
106 limits.max_total_bytes
107 ),
108 ));
109 }
110 if value.contains('\0') {
111 return Err(CliError::new(
112 CliErrorKind::InvalidInput,
113 format!("argument {} contains a NUL byte", count + 1),
114 ));
115 }
116 Ok(())
117}