1use std::str::FromStr;
4
5use crate::parse2::{
6 ErrorProblem, IsStructural, ItemStream, KeywordRef, NetdocParseable, ParseInput,
7};
8
9use ErrorProblem as EP;
10
11use crate::parse2::poc::netstatus::NetworkStatusVote; #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, derive_more::AsRef)]
86pub struct EncodedAuthCert(#[as_ref(str)] String);
87
88#[derive(Copy, Clone, Debug, Eq, PartialEq)]
90enum ItemSequenceChecker {
91 Intro,
93 Body,
95 End,
97}
98
99struct IsOurStructural;
101
102const INTRO_KEYWORD: &str = "dir-key-certificate-version";
104const FINAL_KEYWORD: &str = "dir-key-certification";
106
107impl EncodedAuthCert {
108 pub fn as_str(&self) -> &str {
110 &self.0
111 }
112}
113
114impl ItemSequenceChecker {
115 fn start() -> Self {
117 use ItemSequenceChecker::*;
118 Intro
119 }
120
121 fn keyword(&mut self, kw: KeywordRef<'_>) -> Result<Option<IsOurStructural>, EP> {
123 use ItemSequenceChecker::*;
124
125 let mut change_state = |from, to| {
126 if *self == from {
127 *self = to;
128 Ok(Some(IsOurStructural))
129 } else {
130 Err(EP::OtherBadDocument("authcert bad structure"))
131 }
132 };
133
134 if kw == INTRO_KEYWORD {
135 change_state(Intro, Body)
136 } else if kw == FINAL_KEYWORD {
137 change_state(Body, End)
138 } else if *self != Body {
139 Err(EP::OtherBadDocument(
140 "authcert loose body item or missing intro keyword",
141 ))
142 } else if let Some(IsStructural) = NetworkStatusVote::is_structural_keyword(kw) {
143 Err(EP::OtherBadDocument(
144 "authcert with vote structural keyword",
145 ))
146 } else if kw == "fingerprint" || kw.as_str().starts_with("dir-") {
147 Ok(None)
148 } else {
149 Err(EP::OtherBadDocument(
150 "authcert body keyword not dir- or fingerprint",
151 ))
152 }
153 }
154
155 fn finish(self) -> Result<(), EP> {
157 use ItemSequenceChecker::*;
158 match self {
159 End => Ok(()),
160 _other => Err(EP::OtherBadDocument(
161 "authcert missing end (signature) item",
162 )),
163 }
164 }
165}
166
167fn extra_lexical_checks(s: &str) -> Result<(), EP> {
172 let _without_trailing_nl = s
175 .strip_suffix("\n")
177 .ok_or(EP::OtherBadDocument("missing final newline"))?;
178
179 Ok(())
180}
181
182fn check(s: &str) -> Result<(), EP> {
184 extra_lexical_checks(s)?;
185
186 let input = ParseInput::new(s, "<authcert string>");
188 let mut lex = ItemStream::new(&input).map_err(|e| e.problem)?;
189 let mut seq = ItemSequenceChecker::start();
190 while let Some(item) = lex.next_item()? {
191 seq.keyword(item.keyword())?;
192 }
193 seq.finish()
194}
195
196impl TryFrom<String> for EncodedAuthCert {
197 type Error = ErrorProblem;
198 fn try_from(s: String) -> Result<Self, EP> {
199 check(&s)?;
200 Ok(EncodedAuthCert(s))
201 }
202}
203
204impl FromStr for EncodedAuthCert {
205 type Err = ErrorProblem;
206 fn from_str(s: &str) -> Result<Self, EP> {
207 s.to_owned().try_into()
208 }
209}
210
211impl NetdocParseable for EncodedAuthCert {
212 fn doctype_for_error() -> &'static str {
213 "encoded authority key certificate"
214 }
215
216 fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
217 kw == INTRO_KEYWORD
218 }
219 fn is_structural_keyword(kw: KeywordRef<'_>) -> Option<IsStructural> {
220 (Self::is_intro_item_keyword(kw) || kw == FINAL_KEYWORD).then_some(IsStructural)
221 }
222
223 fn from_items(input: &mut ItemStream<'_>, stop_at: stop_at!()) -> Result<Self, EP> {
224 let start_pos = input.byte_position();
225 let mut seq = ItemSequenceChecker::start();
226 while seq != ItemSequenceChecker::End {
227 let item = input.next_item()?.ok_or(EP::MissingItem {
228 keyword: FINAL_KEYWORD,
229 })?;
230
231 let kw = item.keyword();
232
233 match seq.keyword(kw)? {
234 Some(IsOurStructural) => {} None => {
236 if stop_at.stop_at(kw) {
237 return Err(EP::Internal(
238 "bug! parent document structural keyword found while trying to process an embedded authcert, but was accepted by ItemSequenceChecker; authcert embedded in something other than a vote?",
239 ));
240 }
241 }
242 }
243 }
244 seq.finish()?;
245
246 let text = input
247 .whole_input()
248 .get(start_pos..)
249 .expect("start_pos wasn't included in the body so far?!");
250
251 extra_lexical_checks(text)?;
252
253 if let Some(next_item) = input.peek_keyword()? {
254 if !stop_at.stop_at(next_item) {
255 return Err(EP::OtherBadDocument(
256 "unexpected loose items after embedded authcert",
257 ));
258 }
259 }
260
261 Ok(EncodedAuthCert(text.to_string()))
262 }
263}
264#[cfg(test)]
265mod test {
266 #![allow(clippy::bool_assert_comparison)]
268 #![allow(clippy::clone_on_copy)]
269 #![allow(clippy::dbg_macro)]
270 #![allow(clippy::mixed_attributes_style)]
271 #![allow(clippy::print_stderr)]
272 #![allow(clippy::print_stdout)]
273 #![allow(clippy::single_char_pattern)]
274 #![allow(clippy::unwrap_used)]
275 #![allow(clippy::unchecked_time_subtraction)]
276 #![allow(clippy::useless_vec)]
277 #![allow(clippy::needless_pass_by_value)]
278 use super::*;
280 use crate::parse2::parse_netdoc;
281 use derive_deftly::Deftly;
282 use std::fmt::{Debug, Display};
283
284 #[derive(Debug, Deftly)]
285 #[derive_deftly(NetdocParseable)]
286 #[allow(unused)]
287 struct Embeds {
288 e_intro: (),
289 #[deftly(netdoc(subdoc))]
290 cert: EncodedAuthCert,
291 #[deftly(netdoc(subdoc))]
292 subdocs: Vec<Subdoc>,
293 }
294 #[derive(Debug, Deftly)]
295 #[derive_deftly(NetdocParseable)]
296 #[allow(unused)]
297 struct Subdoc {
298 dir_e_subdoc: (),
299 }
300
301 fn chk(exp_sole: Result<(), &str>, exp_embed: Result<(), &str>, doc: &str) {
302 fn chk1<T: Debug, E: Debug + tor_error::ErrorReport + Display>(
303 exp: Result<(), &str>,
304 doc: &str,
305 what: &str,
306 got: Result<T, E>,
307 ) {
308 eprintln!("==========\n---- {what} 8<- ----\n{doc}---- ->8 {what} ----\n");
309 match got {
310 Err(got_e) => {
311 let got_m = got_e.report().to_string();
312 eprintln!("{what}, got error: {got_e:?}");
313 eprintln!("{what}, got error: {got_m:?}");
314 let exp_m = exp.expect_err("expected success!");
315 assert!(
316 got_m.contains(exp_m),
317 "{what}, expected different error: {exp_m:?}"
318 );
319 }
320 y @ Ok(_) => {
321 eprintln!("got {y:?}");
322 assert!(exp.is_ok(), "{what}, unexpected success; expected: {exp:?}");
323 }
324 }
325 }
326 chk1(exp_sole, doc, "from_str", EncodedAuthCert::from_str(doc));
327 chk1(
328 exp_sole,
329 doc,
330 "From<String>",
331 EncodedAuthCert::try_from(doc.to_owned()),
332 );
333 let embeds = format!(
334 r"e-intro
335ignored
336{doc}dir-e-subdoc
337dir-ignored-2
338"
339 );
340 let parse_input = ParseInput::new(&embeds, "<embeds>");
341 chk1(
342 exp_embed,
343 &embeds,
344 "embedded",
345 parse_netdoc::<Embeds>(&parse_input),
346 );
347 }
348
349 #[test]
350 fn bad_authcerts() {
351 NetworkStatusVote::is_structural_keyword(KeywordRef::new("dir-source").unwrap())
352 .expect("structural dir-source");
353
354 chk(
359 Err("missing final newline"),
360 Err("missing item encoded authority key certificate"),
361 r"",
362 );
363 chk(
364 Err("authcert loose body item or missing intro keyword"),
365 Err("missing item encoded authority key certificate"),
366 r"wrong-intro
367",
368 );
369 chk(
370 Err("missing final newline"),
371 Err("missing item dir-key-certification"),
372 r"dir-key-certificate-version
373dir-missing-nl",
374 );
375 chk(
376 Err("authcert bad structure"),
377 Err("authcert bad structure"),
378 r"dir-key-certificate-version
379dir-key-certificate-version
380",
381 );
382 chk(
383 Err("authcert body keyword not dir- or fingerprint"),
384 Err("authcert body keyword not dir- or fingerprint"),
385 r"dir-key-certificate-version
386wrong-item
387dir-key-certification
388",
389 );
390 chk(
391 Err("authcert with vote structural keyword"),
392 Err("authcert with vote structural keyword"),
393 r"dir-key-certificate-version
394r
395dir-key-certification
396",
397 );
398 chk(
399 Err("authcert with vote structural keyword"),
400 Err("authcert with vote structural keyword"),
401 r"dir-key-certificate-version
402dir-source
403dir-key-certification
404",
405 );
406 chk(
407 Ok(()), Err("bug! parent document structural keyword found"),
409 r"dir-key-certificate-version
410dir-e-subdoc
411dir-key-certification
412",
413 );
414 chk(
415 Err("authcert with vote structural keyword"),
416 Err("authcert with vote structural keyword"),
417 r"dir-key-certificate-version
418dir-example-item
419r
420",
421 );
422 chk(
423 Err("authcert loose body item or missing intro keyword"),
424 Err("unexpected loose items after embedded authcert"),
425 r"dir-key-certificate-version
426dir-example-item
427dir-key-certification
428dir-extra-item
429r
430",
431 );
432 chk(
433 Err("authcert bad structure"),
434 Err("authcert bad structure"),
435 r"dir-key-certificate-version
436dir-key-certificate-version
437dir-example-item
438dir-key-certification
439dir-key-certification
440r
441",
442 );
443 chk(
444 Err("authcert bad structure"),
445 Err("unexpected loose items after embedded authcert"),
446 r"dir-key-certificate-version
447dir-example-item
448dir-key-certification
449dir-key-certification
450r
451",
452 );
453 }
454}