1
//! New netdoc parsing arrangements, with `derive`
2
//!
3
//! # Parsing principles
4
//!
5
//! A parseable network document is a type implementing [`NetdocParseable`].
6
//! usually via the
7
//! [`NetdocParseable` derive=deftly macro`](crate::derive_deftly_template_NetdocParseable).
8
//!
9
//! A document type is responsible for recognising its own heading item.
10
//! Its parser will also be told other of structural items that it should not consume.
11
//! The structural lines can then be used to pass control to the appropriate parser.
12
//!
13
//! A "structural item" is a netdoc item that is defines the structure of the document.
14
//! This includes the intro items for whole documents,
15
//! the items that introduce document sections
16
//! (which we model by treating the section as a sub-document)
17
//! and signature items (which introduce the signatures at the end of the document,
18
//! and after which no non-signature items may appear).
19
//!
20
//! # Ordering
21
//!
22
//! We don't always parse things into a sorted order.
23
//! Sorting will be done when assembling documents, before outputting.
24
//!
25
//! # Types, and signature handling
26
//!
27
//! Most top-level network documents are signed somehow.
28
//! In this case there are three types:
29
//!
30
//!   * **`FooUnverified`**: a signed `Foo`, with its signatures, not yet verified.
31
//!     Implements [`NetdocParseableUnverified`],
32
//!     typically by invoking the
33
//!     [`NetdocUParseablenverified` derive macro](crate::derive_deftly_template_NetdocParseableUnverified)
34
//!     on `Foo`.
35
//!
36
//!     Type-specific methods are provided for verification,
37
//!     to obtain a `Foo`.
38
//!
39
//!   * **`Foo`**: the body data for the document.
40
//!     This doesn't contain any signatures.
41
//!     Having one of these to play with means signatures have already been validated.
42
//!     Can be parsed as part of the signed document,
43
//!     via the `NetdocParseable` implementation on `FooUnverified`,
44
//!     and then obtained via `.verify_...` method(s) on `FooUnverified`,
45
//!
46
//!   * **`FooSignatures`**: the signatures for a `Foo`.
47
//!     Implements `NetdocParseableSignatures`, via
48
//!     [derive](crate::derive_deftly_template_NetdocParseableSignatures),
49
//!     with `#[deftly(netdoc(signatures))]`.
50
//!
51
//! # Relationship to tor_netdoc::parse
52
//!
53
//! This is a completely new parsing approach, based on different principles.
54
//! The key principle is the recognition of "structural keywords",
55
//! recursively within a parsing stack, via the p`NetdocParseable`] trait.
56
//!
57
//! This allows the parser to be derived.  We have type-driven parsing
58
//! of whole Documents, Items, and their Arguments and Objects,
59
//! including of their multiplicity.
60
//!
61
//! The different keyword handling means we can't use most of the existing lexer,
62
//! and need new item parsing API:
63
//!
64
//!  * [`NetdocParseable`] trait.
65
//!  * [`KeywordRef`] type.
66
//!  * [`ItemStream`], [`UnparsedItem`], [`ArgumentStream`], [`UnparsedObject`].
67
//!
68
//! The different error handling means we have our own error types.
69
//! (The crate's existing parse errors have information that we don't track,
70
//! and is also a portmanteau error for parsing, writing, and other functions.)
71
//!
72
//! Document signing is handled in a more abstract way.
73
//!
74
//! Some old netdoc constructs are not supported.
75
//! For example, the obsolete `opt` prefix on safe-to-ignore Items.
76
//! The parser may make different decisions about netdocs with anomalous item ordering.
77

            
78
#[doc(hidden)]
79
#[macro_use]
80
pub mod internal_prelude;
81

            
82
#[macro_use]
83
mod structural;
84

            
85
#[macro_use]
86
mod derive;
87

            
88
mod error;
89
mod impls;
90
pub mod keyword;
91
mod lex;
92
mod lines;
93
pub mod multiplicity;
94
mod signatures;
95
mod traits;
96

            
97
use internal_prelude::*;
98

            
99
pub use error::{ArgumentError, ErrorProblem, ParseError, UnexpectedArgument, VerifyFailed};
100
pub use impls::times::NdaSystemTimeDeprecatedSyntax;
101
pub use keyword::KeywordRef;
102
pub use lex::{ArgumentStream, ItemStream, NoFurtherArguments, UnparsedItem, UnparsedObject};
103
pub use lines::{Lines, Peeked, StrExt};
104
pub use signatures::{
105
    HasUnverifiedParsedBody, NetdocParseableSignatures, NetdocParseableUnverified,
106
    SignatureHashInputs, SignatureHashesAccumulator, SignatureItemParseable, SignaturesData,
107
    sig_hashes,
108
};
109
#[allow(deprecated)]
110
#[deprecated]
111
pub use signatures::{check_validity_time, check_validity_time_tolerance};
112
pub use structural::{StopAt, StopPredicate};
113
pub use traits::{
114
    IsStructural, ItemArgumentParseable, ItemObjectParseable, ItemValueParseable, NetdocParseable,
115
    NetdocParseableFields,
116
};
117

            
118
#[doc(hidden)]
119
pub use derive::netdoc_parseable_derive_debug;
120

            
121
pub(crate) use internal_prelude::EP;
122

            
123
//---------- input ----------
124

            
125
/// Options for parsing
126
///
127
/// Specific document and type parsing methods may use these parameters
128
/// to control their parsing behaviour at run-time.
129
#[derive(educe::Educe, Debug, Clone)]
130
#[allow(clippy::manual_non_exhaustive)]
131
#[educe(Default)]
132
pub struct ParseOptions {
133
    /// Retain unknown values?
134
    ///
135
    /// Some field types, especially for flags fields, have the capability to retain
136
    /// unknown flags.  But, whereas known flags can be represented as single bits,
137
    /// representing unknown flags involves allocating and copying strings.
138
    /// Unless the document is to be reproduced, this is a waste of effort.
139
    ///
140
    /// Each document field type affected by this option should store the unknowns
141
    /// as `Unknown<HashSet<String>>` or similar.
142
    ///
143
    /// This feature should only be used where performance is important.
144
    /// For example, it is useful for types that appear in md consensus routerdescs,
145
    /// but less useful for types that appear only in a netstatus preamble.
146
    ///
147
    /// This is currently used for router flags.
148
    #[educe(Default(expression = "Unknown::new_discard()"))]
149
    pub retain_unknown_values: Unknown<()>,
150

            
151
    // Like `#[non_exhaustive]`, but doesn't prevent use of struct display syntax with `..`
152
    #[doc(hidden)]
153
    _private_non_exhaustive: (),
154
}
155

            
156
/// Input to a network document top-level parsing operation
157
#[derive(Debug, Clone, amplify::Getters)]
158
pub struct ParseInput<'s> {
159
    /// The actual document text
160
    #[getter(as_copy)]
161
    input: &'s str,
162

            
163
    /// Filename (for error reporting)
164
    #[getter(as_copy)]
165
    file: &'s str,
166

            
167
    /// Parsing options
168
    #[getter(as_ref, as_mut)]
169
    options: ParseOptions,
170
}
171

            
172
impl<'s> ParseInput<'s> {
173
    /// Prepare to parse an input string
174
34470
    pub fn new(input: &'s str, file: &'s str) -> Self {
175
34470
        ParseInput {
176
34470
            input,
177
34470
            file,
178
34470
            options: ParseOptions::default(),
179
34470
        }
180
34470
    }
181

            
182
    /// Enable retention of unknown values during parsing
183
    ///
184
    /// Convenience method to set
185
    /// [`.options_mut().retain_unknown_values`](ParseOptions::retain_unknown_values)
186
    /// to [`Unknown::Retained`].
187
    #[cfg(feature = "retain-unknown")]
188
16
    pub fn retain_unknown_values(&mut self) {
189
16
        self.options_mut().retain_unknown_values = Unknown::Retained(());
190
16
    }
191
}
192

            
193
//---------- parser ----------
194

            
195
/// Common code for `parse_netdoc` and `parse_netdoc_multiple`
196
///
197
/// Creates the `ItemStream`, calls `parse_completely`, and handles errors.
198
1614
fn parse_internal<T, D: NetdocParseable>(
199
1614
    input: &ParseInput<'_>,
200
1614
    parse_completely: impl FnOnce(&mut ItemStream) -> Result<T, ErrorProblem>,
201
1614
) -> Result<T, ParseError> {
202
1614
    let mut items = ItemStream::new(input);
203
1614
    parse_completely(&mut items).map_err(error_handler::<D>(input, &items))
204
1614
}
205

            
206
/// Return a function for converting `ErrorProblem` to `ParseError`
207
///
208
/// For use in `.map_err()`.
209
//
210
// Returning a closure means the usual kind of call site doesn't need to name `problem`.
211
2495
fn error_handler<D: NetdocParseable>(
212
2495
    input: &ParseInput<'_>,
213
2495
    items: &ItemStream<'_>,
214
2495
) -> impl Fn(ErrorProblem) -> ParseError {
215
    |problem| ParseError {
216
116
        problem,
217
116
        doctype: D::doctype_for_error(),
218
116
        file: input.file.to_owned(),
219
116
        lno: items.lno_for_error(),
220
116
        column: problem.column(),
221
116
    }
222
2495
}
223

            
224
/// Parse a network document - **toplevel entrypoint**
225
1474
pub fn parse_netdoc<D: NetdocParseable>(input: &ParseInput<'_>) -> Result<D, ParseError> {
226
1474
    parse_internal::<_, D>(input, |items| {
227
1474
        let doc = D::from_items(items, StopAt(false))?;
228
1366
        if let Some(_kw) = items.peek_keyword()? {
229
            return Err(EP::MultipleDocuments);
230
1366
        }
231
1366
        Ok(doc)
232
1474
    })
233
1474
}
234

            
235
/// Parse multiple concatenated network documents - **toplevel entrypoint**
236
48
pub fn parse_netdoc_multiple<D: NetdocParseable>(
237
48
    input: &ParseInput<'_>,
238
48
) -> Result<Vec<D>, ParseError> {
239
48
    parse_internal::<_, D>(input, |items| {
240
48
        let mut docs = vec![];
241
348
        while items.peek_keyword()?.is_some() {
242
300
            let doc = D::from_items(items, StopAt(false))?;
243
300
            docs.push(doc);
244
        }
245
48
        Ok(docs)
246
48
    })
247
48
}
248

            
249
/// Error from `multi_push_doc`
250
#[derive(Debug, Error)]
251
#[error("out of bounds bug")]
252
struct OutOfBoundsBug;
253

            
254
/// Add `(doc, start_pos, end_pos)` to `docs`, checking bounds
255
///
256
/// Helper function for use by `parse_netdoc_ multiple_*` functions that return offsets.
257
660
fn multi_push_doc<T>(
258
660
    docs: &mut Vec<(T, usize, usize)>,
259
660
    input: &ParseInput<'_>,
260
660
    doc: T,
261
660
    start_pos: usize,
262
660
    end_pos: usize,
263
660
) -> Result<(), OutOfBoundsBug> {
264
    // Check start_pos and end_pos are in range.
265
660
    if input.input.get(start_pos..end_pos).is_none() {
266
        return Err(OutOfBoundsBug);
267
660
    }
268

            
269
660
    docs.push((doc, start_pos, end_pos));
270
660
    Ok(())
271
660
}
272

            
273
/// Parse multiple network documents, also returning their offsets  - **toplevel entrypoint**
274
///
275
/// Each returned document is accompanied by the byte offsets of its start and end.
276
///
277
/// (The netdoc metaformat does not allow anything in between subsequent documents in a file,
278
/// so the end of one document is the start of the next.)
279
///
280
/// This returns byte offsets rather than string slices,
281
/// because the caller can always convert the offsets into string slices,
282
/// but it is not straightforward to convert string slices borrowed from some input string
283
/// into offsets, in a way that is obviously correct without nightly `str::substr_range`.
284
///
285
/// Interfacing code can assume that slicing the input string with the returned
286
/// [`usize`] values will not cause an out-of-bounds error, meaning runtime
287
/// checks are not necessary there.
288
92
pub fn parse_netdoc_multiple_with_offsets<D: NetdocParseable>(
289
92
    input: &ParseInput<'_>,
290
92
) -> Result<Vec<(D, usize, usize)>, ParseError> {
291
92
    parse_internal::<_, D>(input, |items| {
292
92
        let mut docs = vec![];
293
738
        while items.peek_keyword()?.is_some() {
294
646
            let start_pos = items.byte_position();
295
646
            let doc = D::from_items(items, StopAt(false))?;
296
646
            let end_pos = items.byte_position();
297

            
298
646
            multi_push_doc(&mut docs, input, doc, start_pos, end_pos)
299
646
                .map_err(|OutOfBoundsBug| ErrorProblem::Internal("out-of-bounds bug?"))?;
300
        }
301
92
        Ok(docs)
302
92
    })
303
92
}
304

            
305
/// Parse multiple network documents, with error recovery  - **toplevel entrypoint**
306
///
307
/// Parses multiple documents.  If an error is encountered, it is returned,
308
/// and parsing continues with the next document (if possible).
309
///
310
/// Each document or error is accompanied by the applicable byte offsets in the input document,
311
/// as with [`parse_netdoc_multiple_with_offsets`].
312
#[allow(clippy::type_complexity)] // Yes, the return type is complicated
313
2
pub fn parse_netdoc_multiple_sophisticated<D: NetdocParseable>(
314
2
    input: &ParseInput<'_>,
315
2
) -> Result<Vec<(Result<D, ParseError>, usize, usize)>, Bug> {
316
    // Largely separate from parse_netdoc_multiple_with_offsets because the differences
317
    // are control flow; attempts at unifying these led to very confusing code.
318

            
319
2
    let mut items = ItemStream::new(input);
320
2
    let mut docs = vec![];
321
14
    let mut push_doc = |doc, start, end| {
322
14
        multi_push_doc(&mut docs, input, doc, start, end)
323
14
            .map_err(into_internal!("while parsing netdoc sequence"))
324
14
    };
325

            
326
    'docs: loop {
327
14
        let start_pos = items.byte_position();
328

            
329
        // Insisting on a KeywordRef prevents mistaken omission of code in match arms.
330
14
        let _intro_kw: KeywordRef = match items.peek_keyword() {
331
14
            Ok(Some(kw)) => kw,
332
            Ok(None) => break 'docs,
333
            Err(e) => {
334
                push_doc(
335
                    Err(error_handler::<D>(input, &items)(e)),
336
                    start_pos,
337
                    items.whole_input().len(),
338
                )?;
339
                // can't continue
340
                break 'docs;
341
            }
342
        };
343

            
344
14
        let doc = D::from_items(&mut items, StopAt(false)) //
345
14
            .map_err(error_handler::<D>(input, &items));
346

            
347
14
        let is_err = doc.is_err();
348
14
        let end_pos = items.byte_position();
349
14
        push_doc(doc, start_pos, end_pos)?;
350

            
351
14
        if is_err {
352
            // Skip the rest of the erroneous document until we find the next intro item.
353
            //
354
            // If we get lexical errors during error recovery, we don't *also* report them
355
            // and instead, just stop processing the input; hence the `.unwrap_or(None)`.
356
            // (And this is why we insist on `break 'docs`, rather than just break.)
357
            //
358
            // Insisting on KeywordRef and UnparsedItem helps prevent mistakes.
359
6
            let _next_intro_kw: KeywordRef = 'skip: loop {
360
10
                let _discard_item: UnparsedItem = match items.peek_keyword().unwrap_or(None) {
361
2
                    None => break 'docs,
362
8
                    Some(kw) if D::is_intro_item_keyword(kw) => break 'skip kw,
363
2
                    Some(_other_kw) => match items.next().transpose().unwrap_or(None) {
364
2
                        Some(item) => item,
365
                        None => break 'docs,
366
                    },
367
                };
368
            };
369
6
        }
370
    }
371
2
    Ok(docs)
372
2
}