1 //! Error type from parsing a document, and the position where it occurred
2 use thiserror::Error;
3 
4 use crate::types::policy::PolicyError;
5 use std::fmt;
6 
7 /// A position within a directory object. Used to tell where an error
8 /// occurred.
9 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
10 #[non_exhaustive]
11 pub enum Pos {
12     /// The error did not occur at any particular position.
13     ///
14     /// This can happen when the error is something like a missing entry:
15     /// the entry is supposed to go _somewhere_, but we can't say where.
16     None,
17     /// The error occurred at an unknown position.
18     ///
19     /// We should avoid using this case.
20     Unknown,
21     /// The error occurred at an invalid offset within the string, or
22     /// outside the string entirely.
23     ///
24     /// This can only occur because of an internal error of some kind.
25     Invalid(usize),
26     /// The error occurred at a particular byte within the string.
27     ///
28     /// We try to convert these to a Pos before displaying them to the user.
29     Byte {
30         /// Byte offset within a string.
31         off: usize,
32     },
33     /// The error occurred at a particular line (and possibly at a
34     /// particular byte within the line.)
35     PosInLine {
36         /// Line offset within a string.
37         line: usize,
38         /// Byte offset within the line.
39         byte: usize,
40     },
41     /// The error occurred at a position in memory.  This shouldn't be
42     /// exposed to the user, but rather should be mapped to a position
43     /// in the string.
44     Raw {
45         /// A raw pointer to the position where the error occurred.
46         ptr: *const u8,
47     },
48 }
49 
50 // It's okay to send a Pos to another thread, even though its Raw
51 // variant contains a pointer. That's because we never dereference the
52 // pointer: we only compare it to another pointer representing a
53 // string.
54 //
55 // TODO: Find a better way to have Pos work.
56 unsafe impl Send for Pos {}
57 unsafe impl Sync for Pos {}
58 
59 impl Pos {
60     /// Construct a Pos from an offset within a &str slice.
from_offset(s: &str, off: usize) -> Self61     pub fn from_offset(s: &str, off: usize) -> Self {
62         if off > s.len() || !s.is_char_boundary(off) {
63             Pos::Invalid(off)
64         } else {
65             let s = &s[..off];
66             let last_nl = s.rfind('\n');
67             match last_nl {
68                 Some(pos) => {
69                     let newlines = s.bytes().filter(|b| *b == b'\n').count();
70                     Pos::PosInLine {
71                         line: newlines + 1,
72                         byte: off - pos,
73                     }
74                 }
75                 None => Pos::PosInLine {
76                     line: 1,
77                     byte: off + 1,
78                 },
79             }
80         }
81     }
82     /// Construct a Pos from a slice of some other string.  This
83     /// Pos won't be terribly helpful, but it may be converted
84     /// into a useful Pos with `within`.
at(s: &str) -> Self85     pub fn at(s: &str) -> Self {
86         let ptr = s.as_ptr();
87         Pos::Raw { ptr }
88     }
89     /// Construct Pos from the end of some other string.
at_end_of(s: &str) -> Self90     pub fn at_end_of(s: &str) -> Self {
91         let ending = &s[s.len()..];
92         Pos::at(ending)
93     }
94     /// Construct a position from a byte offset.
from_byte(off: usize) -> Self95     pub fn from_byte(off: usize) -> Self {
96         Pos::Byte { off }
97     }
98     /// Construct a position from a line and a byte offset within that line.
from_line(line: usize, byte: usize) -> Self99     pub fn from_line(line: usize, byte: usize) -> Self {
100         Pos::PosInLine { line, byte }
101     }
102     /// If this position appears within `s`, and has not yet been mapped to
103     /// a line-and-byte position, return its offset.
offset_within(&self, s: &str) -> Option<usize>104     pub(crate) fn offset_within(&self, s: &str) -> Option<usize> {
105         match self {
106             Pos::Byte { off } => Some(*off),
107             Pos::Raw { ptr } => offset_in(*ptr, s),
108             _ => None,
109         }
110     }
111     /// Given a position, if it was at a byte offset, convert it to a
112     /// line-and-byte position within `s`.
113     ///
114     /// Requires that this position was actually generated from `s`.
115     /// If it was not, the results here may be nonsensical.
116     ///
117     /// TODO: I wish I knew an efficient safe way to do this that
118     /// guaranteed that we we always talking about the right string.
within(self, s: &str) -> Self119     pub fn within(self, s: &str) -> Self {
120         match self {
121             Pos::Byte { off } => Self::from_offset(s, off),
122             Pos::Raw { ptr } => {
123                 if let Some(off) = offset_in(ptr, s) {
124                     Self::from_offset(s, off)
125                 } else {
126                     self
127                 }
128             }
129             _ => self,
130         }
131     }
132 }
133 
134 /// If `ptr` is within `s`, return its byte offset.
offset_in(ptr: *const u8, s: &str) -> Option<usize>135 fn offset_in(ptr: *const u8, s: &str) -> Option<usize> {
136     // We need to confirm that 'ptr' falls within 's' in order
137     // to subtract it meaningfully and find its offset.
138     // Otherwise, we'll get a bogus result.
139     //
140     // Fortunately, we _only_ get a bogus result: we don't
141     // hit unsafe behavior.
142     let ptr_u = ptr as usize;
143     let start_u = s.as_ptr() as usize;
144     let end_u = (s.as_ptr() as usize) + s.len();
145     if start_u <= ptr_u && ptr_u < end_u {
146         Some(ptr_u - start_u)
147     } else {
148         None
149     }
150 }
151 
152 impl fmt::Display for Pos {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result153     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154         use Pos::*;
155         match self {
156             None => write!(f, ""),
157             Unknown => write!(f, " at unknown position"),
158             Invalid(off) => write!(f, " at invalid offset at index {}", off),
159             Byte { off } => write!(f, " at byte {}", off),
160             PosInLine { line, byte } => write!(f, " on line {}, byte {}", line, byte),
161             Raw { ptr } => write!(f, " at {:?}", ptr),
162         }
163     }
164 }
165 
166 /// An error that occurred while parsing a directory object of some kind.
167 #[derive(Error, Debug, Clone, PartialEq)]
168 #[non_exhaustive]
169 pub enum Error {
170     /// An internal error in the parser: these should never happen.
171     #[error("internal error{0}")]
172     Internal(Pos), // TODO string.
173     /// An entry was found with no keyword.
174     #[error("no keyword for entry{0}")]
175     MissingKeyword(Pos),
176     /// An entry was found with no newline at the end.
177     #[error("line truncated before newline{0}")]
178     TruncatedLine(Pos),
179     /// A bad string was found in the keyword position.
180     #[error("invalid keyword{0}")]
181     BadKeyword(Pos),
182     /// We found an ill-formed "BEGIN FOO" tag.
183     #[error("invalid PEM BEGIN tag{0}")]
184     BadObjectBeginTag(Pos),
185     /// We found an ill-formed "END FOO" tag.
186     #[error("invalid PEM END tag{0}")]
187     BadObjectEndTag(Pos),
188     /// We found a "BEGIN FOO" tag with an "END FOO" tag that didn't match.
189     #[error("mismatched PEM tags{0}")]
190     BadObjectMismatchedTag(Pos),
191     /// We found a base64 object with an invalid base64 encoding.
192     #[error("invalid base64 in object around byte {0}")]
193     BadObjectBase64(Pos),
194     /// The document is not supposed to contain more than one of some
195     /// kind of entry, but we found one anyway.
196     #[error("duplicate entry for {0}{1}")]
197     DuplicateToken(&'static str, Pos),
198     /// The document is not supposed to contain any of some particular kind
199     /// of entry, but we found one anyway.
200     #[error("entry {0} unexpected{1}")]
201     UnexpectedToken(&'static str, Pos),
202     /// The document is supposed to contain any of some particular kind
203     /// of entry, but we didn't find one one anyway.
204     #[error("didn't find required entry {0}")]
205     MissingToken(&'static str),
206     /// The document was supposed to have one of these, but not where we
207     /// found it.
208     #[error("found {0} out of place{1}")]
209     MisplacedToken(&'static str, Pos),
210     /// We found more arguments on an entry than it is allowed to have.
211     #[error("too many arguments for {0}{1}")]
212     TooManyArguments(&'static str, Pos),
213     /// We didn't fine enough arguments for some entry.
214     #[error("too few arguments for {0}{1}")]
215     TooFewArguments(&'static str, Pos),
216     /// We found an object attached to an entry that isn't supposed to
217     /// have one.
218     #[error("unexpected object for {0}{1}")]
219     UnexpectedObject(&'static str, Pos),
220     /// An entry was supposed to have an object, but it didn't.
221     #[error("missing object for {0}{1}")]
222     MissingObject(&'static str, Pos),
223     /// We found an object on an entry, but the type was wrong.
224     #[error("wrong object type for entry{0}")]
225     WrongObject(Pos),
226     /// We tried to find an argument that we were sure would be there,
227     /// but it wasn't!
228     ///
229     /// This error should never occur in correct code; it should be
230     /// caught earlier by TooFewArguments.
231     #[error("missing argument for entry{0}")]
232     MissingArgument(Pos),
233     /// We found an argument that couldn't be parsed.
234     #[error("bad argument for entry{0}: {1}")]
235     BadArgument(Pos, String), // converting to a string doesn't sit well with me. XXXX
236     /// We found an object that couldn't be parsed after it was decoded.
237     #[error("bad object for entry{0}: {1}")]
238     BadObjectVal(Pos, String), // converting to a string doesn't sit well with me. XXXX
239     /// There was some signature that we couldn't validate.
240     #[error("couldn't validate signature{0}")]
241     BadSignature(Pos), // say which kind of signature. TODO
242     /// There was a tor version we couldn't parse.
243     #[error("couldn't parse Tor version{0}")]
244     BadTorVersion(Pos),
245     /// There was an ipv4 or ipv6 policy entry that we couldn't parse.
246     #[error("invalid policy entry{0}: {1}")]
247     BadPolicy(Pos, #[source] PolicyError),
248     /// An object was expired or not yet valid.
249     #[error("untimely object{0}: {1}")]
250     Untimely(Pos, #[source] tor_checkable::TimeValidityError),
251     /// An underlying byte sequence couldn't be decoded.
252     #[error("decoding error{0}: {1}")]
253     Undecodable(Pos, #[source] tor_bytes::Error),
254     /// Versioned document with an unrecognized version.
255     #[error("unrecognized document version {0}")]
256     BadDocumentVersion(u32),
257     /// Unexpected document type
258     #[error("unexpected document type")]
259     BadDocumentType,
260     /// Document or section started with wrong token
261     #[error("Wrong starting token {0}{1}")]
262     WrongStartingToken(String, Pos),
263     /// Document or section ended with wrong token
264     #[error("Wrong ending token {0}{1}")]
265     WrongEndingToken(String, Pos),
266     /// Items not sorted as expected
267     #[error("Incorrect sort order{0}")]
268     WrongSortOrder(Pos),
269     /// A consensus lifetime was ill-formed.
270     #[error("Invalid consensus lifetime")]
271     InvalidLifetime,
272     /// We're unable to finish building an object, for some reason.
273     #[error("Unable to construct object: {0}")]
274     CannotBuild(&'static str),
275 }
276 
277 impl Error {
278     /// Helper: return a mutable reference to this error's position (if any)
pos_mut(&mut self) -> Option<&mut Pos>279     fn pos_mut(&mut self) -> Option<&mut Pos> {
280         use Error::*;
281         match self {
282             Internal(p) => Some(p),
283             MissingKeyword(p) => Some(p),
284             TruncatedLine(p) => Some(p),
285             BadKeyword(p) => Some(p),
286             BadObjectBeginTag(p) => Some(p),
287             BadObjectEndTag(p) => Some(p),
288             BadObjectMismatchedTag(p) => Some(p),
289             BadObjectBase64(p) => Some(p),
290             DuplicateToken(_, p) => Some(p),
291             UnexpectedToken(_, p) => Some(p),
292             MissingToken(_) => None,
293             MisplacedToken(_, p) => Some(p),
294             TooManyArguments(_, p) => Some(p),
295             TooFewArguments(_, p) => Some(p),
296             UnexpectedObject(_, p) => Some(p),
297             MissingObject(_, p) => Some(p),
298             WrongObject(p) => Some(p),
299             MissingArgument(p) => Some(p),
300             BadArgument(p, _) => Some(p),
301             BadObjectVal(p, _) => Some(p),
302             BadSignature(p) => Some(p),
303             BadTorVersion(p) => Some(p),
304             BadPolicy(p, _) => Some(p),
305             Untimely(p, _) => Some(p),
306             Undecodable(p, _) => Some(p),
307             BadDocumentVersion(_) => None,
308             BadDocumentType => None,
309             WrongStartingToken(_, p) => Some(p),
310             WrongEndingToken(_, p) => Some(p),
311             WrongSortOrder(p) => Some(p),
312             InvalidLifetime => None,
313             CannotBuild(_) => None,
314         }
315     }
316 
317     /// Helper: return this error's position.
pos(&self) -> Pos318     pub(crate) fn pos(&self) -> Pos {
319         // XXXX This duplicate code is yucky. We should refactor this error
320         // type to use an ErrorKind pattern.
321         use Error::*;
322         let pos = match self {
323             Internal(p) => Some(p),
324             MissingKeyword(p) => Some(p),
325             TruncatedLine(p) => Some(p),
326             BadKeyword(p) => Some(p),
327             BadObjectBeginTag(p) => Some(p),
328             BadObjectEndTag(p) => Some(p),
329             BadObjectMismatchedTag(p) => Some(p),
330             BadObjectBase64(p) => Some(p),
331             DuplicateToken(_, p) => Some(p),
332             UnexpectedToken(_, p) => Some(p),
333             MissingToken(_) => None,
334             MisplacedToken(_, p) => Some(p),
335             TooManyArguments(_, p) => Some(p),
336             TooFewArguments(_, p) => Some(p),
337             UnexpectedObject(_, p) => Some(p),
338             MissingObject(_, p) => Some(p),
339             WrongObject(p) => Some(p),
340             MissingArgument(p) => Some(p),
341             BadArgument(p, _) => Some(p),
342             BadObjectVal(p, _) => Some(p),
343             BadSignature(p) => Some(p),
344             BadTorVersion(p) => Some(p),
345             BadPolicy(p, _) => Some(p),
346             Untimely(p, _) => Some(p),
347             Undecodable(p, _) => Some(p),
348             BadDocumentVersion(_) => None,
349             BadDocumentType => None,
350             WrongStartingToken(_, p) => Some(p),
351             WrongEndingToken(_, p) => Some(p),
352             WrongSortOrder(p) => Some(p),
353             InvalidLifetime => None,
354             CannotBuild(_) => None,
355         };
356         *pos.unwrap_or(&Pos::Unknown)
357     }
358 
359     /// Return a new error based on this one, with any byte-based
360     /// position mapped to some line within a string.
within(mut self, s: &str) -> Error361     pub fn within(mut self, s: &str) -> Error {
362         if let Some(p) = self.pos_mut() {
363             *p = p.within(s);
364         }
365         self
366     }
367 
368     /// Return a new error based on this one, with the position (if
369     /// any) replaced by 'p'.
at_pos(mut self, p: Pos) -> Error370     pub fn at_pos(mut self, p: Pos) -> Error {
371         if let Some(mypos) = self.pos_mut() {
372             *mypos = p;
373         }
374         self
375     }
376 
377     /// Return a new error based on this one, with the position (if
378     /// replaced by 'p' if it had no position before.
or_at_pos(mut self, p: Pos) -> Error379     pub fn or_at_pos(mut self, p: Pos) -> Error {
380         if let Some(mypos) = self.pos_mut() {
381             if *mypos == Pos::None {
382                 *mypos = p;
383             }
384         }
385         self
386     }
387 }
388 
389 /// Create a From<> implementation to construct an Error::BadArgument
390 /// from another error type.
391 macro_rules! derive_from_err{
392     { $etype:ty } => {
393         impl From<$etype> for Error {
394             fn from(e: $etype) -> Error {
395                 Error::BadArgument(Pos::None, e.to_string())
396             }
397         }
398     }
399 }
400 derive_from_err! {std::num::ParseIntError}
401 derive_from_err! {std::net::AddrParseError}
402 
403 impl From<crate::types::policy::PolicyError> for Error {
from(e: crate::types::policy::PolicyError) -> Error404     fn from(e: crate::types::policy::PolicyError) -> Error {
405         Error::BadPolicy(Pos::None, e)
406     }
407 }
408 
409 impl From<tor_bytes::Error> for Error {
from(e: tor_bytes::Error) -> Error410     fn from(e: tor_bytes::Error) -> Error {
411         Error::Undecodable(Pos::None, e)
412     }
413 }
414 
415 impl From<tor_checkable::TimeValidityError> for Error {
from(e: tor_checkable::TimeValidityError) -> Error416     fn from(e: tor_checkable::TimeValidityError) -> Error {
417         Error::Untimely(Pos::None, e)
418     }
419 }
420 
421 impl From<signature::Error> for Error {
from(_e: signature::Error) -> Error422     fn from(_e: signature::Error) -> Error {
423         Error::BadSignature(Pos::None)
424     }
425 }
426