imap_types/
response.rs

1//! # 7. Server Responses
2
3use std::{
4    borrow::Cow,
5    fmt::{Debug, Display, Formatter},
6    num::{NonZeroU32, TryFromIntError},
7};
8
9#[cfg(feature = "arbitrary")]
10use arbitrary::Arbitrary;
11use base64::{engine::general_purpose::STANDARD as _base64, Engine};
12#[cfg(feature = "bounded-static")]
13use bounded_static::ToStatic;
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Serialize};
16
17use crate::{
18    auth::AuthMechanism,
19    core::{impl_try_from, AString, Atom, Charset, NonEmptyVec, QuotedChar, Tag, Text},
20    error::ValidationError,
21    extensions::{
22        compress::CompressionAlgorithm,
23        enable::CapabilityEnable,
24        quota::{QuotaGet, Resource},
25    },
26    fetch::MessageDataItem,
27    flag::{Flag, FlagNameAttribute, FlagPerm},
28    mailbox::Mailbox,
29    response::error::{ContinueError, FetchError},
30    status::StatusDataItem,
31};
32
33/// An IMAP greeting.
34///
35/// Note: Don't use `code: None` *and* a `text` that starts with "[" as this would be ambiguous in IMAP.
36// TODO(301)
37#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
38#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
39#[derive(Debug, Clone, PartialEq, Eq, Hash)]
40pub struct Greeting<'a> {
41    pub kind: GreetingKind,
42    pub code: Option<Code<'a>>,
43    pub text: Text<'a>,
44}
45
46impl<'a> Greeting<'a> {
47    pub fn new(
48        kind: GreetingKind,
49        code: Option<Code<'a>>,
50        text: &'a str,
51    ) -> Result<Self, ValidationError> {
52        Ok(Greeting {
53            kind,
54            code,
55            text: text.try_into()?,
56        })
57    }
58
59    pub fn ok(code: Option<Code<'a>>, text: &'a str) -> Result<Self, ValidationError> {
60        Ok(Greeting {
61            kind: GreetingKind::Ok,
62            code,
63            text: text.try_into()?,
64        })
65    }
66
67    pub fn preauth(code: Option<Code<'a>>, text: &'a str) -> Result<Self, ValidationError> {
68        Ok(Greeting {
69            kind: GreetingKind::PreAuth,
70            code,
71            text: text.try_into()?,
72        })
73    }
74
75    pub fn bye(code: Option<Code<'a>>, text: &'a str) -> Result<Self, ValidationError> {
76        Ok(Greeting {
77            kind: GreetingKind::Bye,
78            code,
79            text: text.try_into()?,
80        })
81    }
82}
83
84#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
85#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
86#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
87#[derive(Debug, Clone, PartialEq, Eq, Hash)]
88/// IMAP4rev1 defines three possible greetings at connection startup.
89pub enum GreetingKind {
90    /// The connection is not yet authenticated.
91    ///
92    /// (Advice: A LOGIN command is needed.)
93    Ok,
94    /// The connection has already been authenticated by external means.
95    ///
96    /// (Advice: No LOGIN command is needed.)
97    PreAuth,
98    /// The server is not willing to accept a connection from this client.
99    ///
100    /// (Advice: The server closes the connection immediately.)
101    Bye,
102}
103
104#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
105#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
106#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
107#[derive(Debug, Clone, PartialEq, Eq, Hash)]
108pub enum Response<'a> {
109    /// Command continuation request responses use the token "+" instead of a
110    /// tag.  These responses are sent by the server to indicate acceptance
111    /// of an incomplete client command and readiness for the remainder of
112    /// the command.
113    CommandContinuationRequest(CommandContinuationRequest<'a>),
114    /// All server data is untagged. An untagged response is indicated by the
115    /// token "*" instead of a tag. Untagged status responses indicate server
116    /// greeting, or server status that does not indicate the completion of a
117    /// command (for example, an impending system shutdown alert).
118    Data(Data<'a>),
119    /// Status responses can be tagged or untagged.  Tagged status responses
120    /// indicate the completion result (OK, NO, or BAD status) of a client
121    /// command, and have a tag matching the command.
122    Status(Status<'a>),
123}
124
125/// ## 7.1. Server Responses - Status Responses
126///
127/// Status responses are OK, NO, BAD, PREAUTH and BYE.
128/// OK, NO, and BAD can be tagged or untagged.
129/// PREAUTH and BYE are always untagged.
130/// Status responses MAY include an OPTIONAL "response code" (see [`Code`](crate::response::Code).)
131///
132/// Note: Don't use `code: None` *and* a `text` that starts with "[" as this would be ambiguous in IMAP.
133// TODO(301)
134#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
135#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
136#[derive(Debug, Clone, PartialEq, Eq, Hash)]
137pub enum Status<'a> {
138    /// ### 7.1.1. OK Response
139    ///
140    /// The OK response indicates an information message from the server.
141    Ok {
142        /// When tagged, it indicates successful completion of the associated
143        /// command.  The human-readable text MAY be presented to the user as
144        /// an information message.
145        ///
146        /// The untagged form indicates an information-only message; the nature
147        /// of the information MAY be indicated by a response code.
148        ///
149        /// The untagged form is also used as one of three possible greetings
150        /// at connection startup.  It indicates that the connection is not
151        /// yet authenticated and that a LOGIN command is needed.
152        tag: Option<Tag<'a>>,
153        /// Response code (optional)
154        code: Option<Code<'a>>,
155        /// Human-readable text (must be at least 1 character!)
156        text: Text<'a>,
157    },
158
159    /// ### 7.1.2. NO Response
160    ///
161    /// The NO response indicates an operational error message from the server.
162    No {
163        /// When tagged, it indicates unsuccessful completion of the
164        /// associated command.  The untagged form indicates a warning; the
165        /// command can still complete successfully.
166        tag: Option<Tag<'a>>,
167        /// Response code (optional)
168        code: Option<Code<'a>>,
169        /// The human-readable text describes the condition. (must be at least 1 character!)
170        text: Text<'a>,
171    },
172
173    /// ### 7.1.3. BAD Response
174    ///
175    /// The BAD response indicates an error message from the server.
176    Bad {
177        /// When tagged, it reports a protocol-level error in the client's command;
178        /// the tag indicates the command that caused the error.  The untagged
179        /// form indicates a protocol-level error for which the associated
180        /// command can not be determined; it can also indicate an internal
181        /// server failure.
182        tag: Option<Tag<'a>>,
183        /// Response code (optional)
184        code: Option<Code<'a>>,
185        /// The human-readable text describes the condition. (must be at least 1 character!)
186        text: Text<'a>,
187    },
188
189    /// ### 7.1.5. BYE Response
190    ///
191    /// The BYE response is always untagged, and indicates that the server
192    /// is about to close the connection.
193    ///
194    /// The BYE response is sent under one of four conditions:
195    ///
196    ///    1) as part of a normal logout sequence.  The server will close
197    ///       the connection after sending the tagged OK response to the
198    ///       LOGOUT command.
199    ///
200    ///    2) as a panic shutdown announcement.  The server closes the
201    ///       connection immediately.
202    ///
203    ///    3) as an announcement of an inactivity autologout.  The server
204    ///       closes the connection immediately.
205    ///
206    ///    4) as one of three possible greetings at connection startup,
207    ///       indicating that the server is not willing to accept a
208    ///       connection from this client.  The server closes the
209    ///       connection immediately.
210    ///
211    /// The difference between a BYE that occurs as part of a normal
212    /// LOGOUT sequence (the first case) and a BYE that occurs because of
213    /// a failure (the other three cases) is that the connection closes
214    /// immediately in the failure case.  In all cases the client SHOULD
215    /// continue to read response data from the server until the
216    /// connection is closed; this will ensure that any pending untagged
217    /// or completion responses are read and processed.
218    Bye {
219        /// Response code (optional)
220        code: Option<Code<'a>>,
221        /// The human-readable text MAY be displayed to the user in a status
222        /// report by the client. (must be at least 1 character!)
223        text: Text<'a>,
224    },
225}
226
227impl<'a> Status<'a> {
228    // FIXME(API)
229    pub fn ok<T>(tag: Option<Tag<'a>>, code: Option<Code<'a>>, text: T) -> Result<Self, T::Error>
230    where
231        T: TryInto<Text<'a>>,
232    {
233        Ok(Status::Ok {
234            tag,
235            code,
236            text: text.try_into()?,
237        })
238    }
239
240    // FIXME(API)
241    pub fn no<T>(tag: Option<Tag<'a>>, code: Option<Code<'a>>, text: T) -> Result<Self, T::Error>
242    where
243        T: TryInto<Text<'a>>,
244    {
245        Ok(Status::No {
246            tag,
247            code,
248            text: text.try_into()?,
249        })
250    }
251
252    // FIXME(API)
253    pub fn bad<T>(tag: Option<Tag<'a>>, code: Option<Code<'a>>, text: T) -> Result<Self, T::Error>
254    where
255        T: TryInto<Text<'a>>,
256    {
257        Ok(Status::Bad {
258            tag,
259            code,
260            text: text.try_into()?,
261        })
262    }
263
264    pub fn bye<T>(code: Option<Code<'a>>, text: T) -> Result<Self, T::Error>
265    where
266        T: TryInto<Text<'a>>,
267    {
268        Ok(Status::Bye {
269            code,
270            text: text.try_into()?,
271        })
272    }
273
274    // ---------------------------------------------------------------------------------------------
275
276    pub fn tag(&self) -> Option<&Tag> {
277        match self {
278            Status::Ok { tag, .. } | Status::No { tag, .. } | Status::Bad { tag, .. } => {
279                tag.as_ref()
280            }
281            Status::Bye { .. } => None,
282        }
283    }
284
285    pub fn code(&self) -> Option<&Code> {
286        match self {
287            Status::Ok { code, .. }
288            | Status::No { code, .. }
289            | Status::Bad { code, .. }
290            | Status::Bye { code, .. } => code.as_ref(),
291        }
292    }
293
294    pub fn text(&self) -> &Text {
295        match self {
296            Status::Ok { text, .. }
297            | Status::No { text, .. }
298            | Status::Bad { text, .. }
299            | Status::Bye { text, .. } => text,
300        }
301    }
302}
303
304/// ## 7.2 - 7.4 Server and Mailbox Status; Mailbox Size; Message Status
305#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
306#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
307#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
308#[derive(Debug, Clone, PartialEq, Eq, Hash)]
309pub enum Data<'a> {
310    // ## 7.2. Server Responses - Server and Mailbox Status
311    //
312    // These responses are always untagged.  This is how server and mailbox
313    // status data are transmitted from the server to the client.  Many of
314    // these responses typically result from a command with the same name.
315    /// ### 7.2.1. CAPABILITY Response
316    ///
317    /// * Contents: capability listing
318    ///
319    /// The CAPABILITY response occurs as a result of a CAPABILITY
320    /// command.  The capability listing contains a space-separated
321    /// listing of capability names that the server supports.  The
322    /// capability listing MUST include the atom "IMAP4rev1".
323    ///
324    /// In addition, client and server implementations MUST implement the
325    /// STARTTLS, LOGINDISABLED, and AUTH=PLAIN (described in [IMAP-TLS])
326    /// capabilities.  See the Security Considerations section for
327    /// important information.
328    ///
329    /// A capability name which begins with "AUTH=" indicates that the
330    /// server supports that particular authentication mechanism.
331    ///
332    /// The LOGINDISABLED capability indicates that the LOGIN command is
333    /// disabled, and that the server will respond with a tagged NO
334    /// response to any attempt to use the LOGIN command even if the user
335    /// name and password are valid.  An IMAP client MUST NOT issue the
336    /// LOGIN command if the server advertises the LOGINDISABLED
337    /// capability.
338    ///
339    /// Other capability names indicate that the server supports an
340    /// extension, revision, or amendment to the IMAP4rev1 protocol.
341    /// Server responses MUST conform to this document until the client
342    /// issues a command that uses the associated capability.
343    ///
344    /// Capability names MUST either begin with "X" or be standard or
345    /// standards-track IMAP4rev1 extensions, revisions, or amendments
346    /// registered with IANA.  A server MUST NOT offer unregistered or
347    /// non-standard capability names, unless such names are prefixed with
348    /// an "X".
349    ///
350    /// Client implementations SHOULD NOT require any capability name
351    /// other than "IMAP4rev1", and MUST ignore any unknown capability
352    /// names.
353    ///
354    /// A server MAY send capabilities automatically, by using the
355    /// CAPABILITY response code in the initial PREAUTH or OK responses,
356    /// and by sending an updated CAPABILITY response code in the tagged
357    /// OK response as part of a successful authentication.  It is
358    /// unnecessary for a client to send a separate CAPABILITY command if
359    /// it recognizes these automatic capabilities.
360    Capability(NonEmptyVec<Capability<'a>>),
361
362    /// ### 7.2.2. LIST Response
363    ///
364    /// The LIST response occurs as a result of a LIST command.  It
365    /// returns a single name that matches the LIST specification.  There
366    /// can be multiple LIST responses for a single LIST command.
367    ///
368    /// The hierarchy delimiter is a character used to delimit levels of
369    /// hierarchy in a mailbox name.  A client can use it to create child
370    /// mailboxes, and to search higher or lower levels of naming
371    /// hierarchy.  All children of a top-level hierarchy node MUST use
372    /// the same separator character.  A NIL hierarchy delimiter means
373    /// that no hierarchy exists; the name is a "flat" name.
374    ///
375    /// The name represents an unambiguous left-to-right hierarchy, and
376    /// MUST be valid for use as a reference in LIST and LSUB commands.
377    /// Unless \Noselect is indicated, the name MUST also be valid as an
378    /// argument for commands, such as SELECT, that accept mailbox names.
379    List {
380        /// Name attributes
381        items: Vec<FlagNameAttribute<'a>>,
382        /// Hierarchy delimiter
383        delimiter: Option<QuotedChar>,
384        /// Name
385        mailbox: Mailbox<'a>,
386    },
387
388    /// ### 7.2.3. LSUB Response
389    ///
390    /// The LSUB response occurs as a result of an LSUB command.  It
391    /// returns a single name that matches the LSUB specification.  There
392    /// can be multiple LSUB responses for a single LSUB command.  The
393    /// data is identical in format to the LIST response.
394    Lsub {
395        /// Name attributes
396        items: Vec<FlagNameAttribute<'a>>,
397        /// Hierarchy delimiter
398        delimiter: Option<QuotedChar>,
399        /// Name
400        mailbox: Mailbox<'a>,
401    },
402
403    /// ### 7.2.4 STATUS Response
404    ///
405    /// The STATUS response occurs as a result of an STATUS command.  It
406    /// returns the mailbox name that matches the STATUS specification and
407    /// the requested mailbox status information.
408    Status {
409        /// Name
410        mailbox: Mailbox<'a>,
411        /// Status parenthesized list
412        items: Cow<'a, [StatusDataItem]>,
413    },
414
415    /// ### 7.2.5. SEARCH Response
416    ///
417    /// * Contents: zero or more numbers
418    ///
419    /// The SEARCH response occurs as a result of a SEARCH or UID SEARCH
420    /// command.  The number(s) refer to those messages that match the
421    /// search criteria.  For SEARCH, these are message sequence numbers;
422    /// for UID SEARCH, these are unique identifiers.  Each number is
423    /// delimited by a space.
424    Search(Vec<NonZeroU32>),
425
426    /// ### 7.2.6.  FLAGS Response
427    ///
428    /// * Contents: flag parenthesized list
429    ///
430    /// The FLAGS response occurs as a result of a SELECT or EXAMINE
431    /// command.  The flag parenthesized list identifies the flags (at a
432    /// minimum, the system-defined flags) that are applicable for this
433    /// mailbox.  Flags other than the system flags can also exist,
434    /// depending on server implementation.
435    ///
436    /// The update from the FLAGS response MUST be recorded by the client.
437    Flags(Vec<Flag<'a>>),
438
439    // ## 7.3. Server Responses - Mailbox Size
440    //
441    // These responses are always untagged.  This is how changes in the size
442    // of the mailbox are transmitted from the server to the client.
443    // Immediately following the "*" token is a number that represents a
444    // message count.
445    /// ### 7.3.1. EXISTS Response
446    ///
447    /// The EXISTS response reports the number of messages in the mailbox.
448    /// This response occurs as a result of a SELECT or EXAMINE command,
449    /// and if the size of the mailbox changes (e.g., new messages).
450    ///
451    /// The update from the EXISTS response MUST be recorded by the client.
452    Exists(u32),
453
454    /// ### 7.3.2. RECENT Response
455    ///
456    /// The RECENT response reports the number of messages with the
457    /// \Recent flag set.  This response occurs as a result of a SELECT or
458    /// EXAMINE command, and if the size of the mailbox changes (e.g., new
459    /// messages).
460    ///
461    ///   Note: It is not guaranteed that the message sequence
462    ///   numbers of recent messages will be a contiguous range of
463    ///   the highest n messages in the mailbox (where n is the
464    ///   value reported by the RECENT response).  Examples of
465    ///   situations in which this is not the case are: multiple
466    ///   clients having the same mailbox open (the first session
467    ///   to be notified will see it as recent, others will
468    ///   probably see it as non-recent), and when the mailbox is
469    ///   re-ordered by a non-IMAP agent.
470    ///
471    ///   The only reliable way to identify recent messages is to
472    ///   look at message flags to see which have the \Recent flag
473    ///   set, or to do a SEARCH RECENT.
474    ///
475    /// The update from the RECENT response MUST be recorded by the client.
476    Recent(u32),
477
478    // ## 7.4. Server Responses - Message Status
479    //
480    // These responses are always untagged.  This is how message data are
481    // transmitted from the server to the client, often as a result of a
482    // command with the same name.  Immediately following the "*" token is a
483    // number that represents a message sequence number.
484    /// ### 7.4.1. EXPUNGE Response
485    ///
486    /// The EXPUNGE response reports that the specified message sequence
487    /// number has been permanently removed from the mailbox.  The message
488    /// sequence number for each successive message in the mailbox is
489    /// immediately decremented by 1, and this decrement is reflected in
490    /// message sequence numbers in subsequent responses (including other
491    /// untagged EXPUNGE responses).
492    ///
493    /// The EXPUNGE response also decrements the number of messages in the
494    /// mailbox; it is not necessary to send an EXISTS response with the
495    /// new value.
496    ///
497    /// As a result of the immediate decrement rule, message sequence
498    /// numbers that appear in a set of successive EXPUNGE responses
499    /// depend upon whether the messages are removed starting from lower
500    /// numbers to higher numbers, or from higher numbers to lower
501    /// numbers.  For example, if the last 5 messages in a 9-message
502    /// mailbox are expunged, a "lower to higher" server will send five
503    /// untagged EXPUNGE responses for message sequence number 5, whereas
504    /// a "higher to lower server" will send successive untagged EXPUNGE
505    /// responses for message sequence numbers 9, 8, 7, 6, and 5.
506    ///
507    /// An EXPUNGE response MUST NOT be sent when no command is in
508    /// progress, nor while responding to a FETCH, STORE, or SEARCH
509    /// command.  This rule is necessary to prevent a loss of
510    /// synchronization of message sequence numbers between client and
511    /// server.  A command is not "in progress" until the complete command
512    /// has been received; in particular, a command is not "in progress"
513    /// during the negotiation of command continuation.
514    ///
515    ///   Note: UID FETCH, UID STORE, and UID SEARCH are different
516    ///   commands from FETCH, STORE, and SEARCH.  An EXPUNGE
517    ///   response MAY be sent during a UID command.
518    ///
519    /// The update from the EXPUNGE response MUST be recorded by the client.
520    Expunge(NonZeroU32),
521
522    /// ### 7.4.2. FETCH Response
523    ///
524    /// The FETCH response returns data about a message to the client.
525    /// The data are pairs of data item names and their values in
526    /// parentheses.  This response occurs as the result of a FETCH or
527    /// STORE command, as well as by unilateral server decision (e.g.,
528    /// flag updates).
529    Fetch {
530        /// Sequence number.
531        seq: NonZeroU32,
532        /// Message data items.
533        items: NonEmptyVec<MessageDataItem<'a>>,
534    },
535
536    Enabled {
537        capabilities: Vec<CapabilityEnable<'a>>,
538    },
539
540    Quota {
541        /// Quota root.
542        root: AString<'a>,
543        /// List of quotas.
544        quotas: NonEmptyVec<QuotaGet<'a>>,
545    },
546
547    QuotaRoot {
548        /// Mailbox name.
549        mailbox: Mailbox<'a>,
550        /// List of quota roots.
551        roots: Vec<AString<'a>>,
552    },
553}
554
555impl<'a> Data<'a> {
556    pub fn capability<C>(caps: C) -> Result<Self, C::Error>
557    where
558        C: TryInto<NonEmptyVec<Capability<'a>>>,
559    {
560        Ok(Self::Capability(caps.try_into()?))
561    }
562
563    // TODO
564    // pub fn list() -> Self {
565    //     unimplemented!()
566    // }
567
568    // TODO
569    // pub fn lsub() -> Self {
570    //     unimplemented!()
571    // }
572
573    // TODO
574    // pub fn status() -> Self {
575    //     unimplemented!()
576    // }
577
578    // TODO
579    // pub fn search() -> Self {
580    //     unimplemented!()
581    // }
582
583    // TODO
584    // pub fn flags() -> Self {
585    //     unimplemented!()
586    // }
587
588    pub fn expunge(seq: u32) -> Result<Self, TryFromIntError> {
589        Ok(Self::Expunge(NonZeroU32::try_from(seq)?))
590    }
591
592    pub fn fetch<S, I>(seq: S, items: I) -> Result<Self, FetchError<S::Error, I::Error>>
593    where
594        S: TryInto<NonZeroU32>,
595        I: TryInto<NonEmptyVec<MessageDataItem<'a>>>,
596    {
597        let seq = seq.try_into().map_err(FetchError::SeqOrUid)?;
598        let items = items.try_into().map_err(FetchError::InvalidItems)?;
599
600        Ok(Self::Fetch { seq, items })
601    }
602}
603
604/// ## 7.5. Server Responses - Command Continuation Request
605///
606/// The command continuation request response is indicated by a "+" token
607/// instead of a tag.  This form of response indicates that the server is
608/// ready to accept the continuation of a command from the client.  The
609/// remainder of this response is a line of text.
610///
611/// This response is used in the AUTHENTICATE command to transmit server
612/// data to the client, and request additional client data.  This
613/// response is also used if an argument to any command is a literal.
614///
615/// The client is not permitted to send the octets of the literal unless
616/// the server indicates that it is expected.  This permits the server to
617/// process commands and reject errors on a line-by-line basis.  The
618/// remainder of the command, including the CRLF that terminates a
619/// command, follows the octets of the literal.  If there are any
620/// additional command arguments, the literal octets are followed by a
621/// space and those arguments.
622#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
623#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
624#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
625#[derive(Debug, Clone, PartialEq, Eq, Hash)]
626#[doc(alias = "Continue")]
627#[doc(alias = "Continuation")]
628#[doc(alias = "ContinuationRequest")]
629pub enum CommandContinuationRequest<'a> {
630    Basic(CommandContinuationRequestBasic<'a>),
631    Base64(Cow<'a, [u8]>),
632}
633
634impl<'a> CommandContinuationRequest<'a> {
635    pub fn basic<T>(code: Option<Code<'a>>, text: T) -> Result<Self, ContinueError<T::Error>>
636    where
637        T: TryInto<Text<'a>>,
638    {
639        Ok(Self::Basic(CommandContinuationRequestBasic::new(
640            code, text,
641        )?))
642    }
643
644    pub fn base64<'data: 'a, D>(data: D) -> Self
645    where
646        D: Into<Cow<'data, [u8]>>,
647    {
648        Self::Base64(data.into())
649    }
650}
651
652#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
653#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
654#[derive(Debug, Clone, PartialEq, Eq, Hash)]
655pub struct CommandContinuationRequestBasic<'a> {
656    code: Option<Code<'a>>,
657    text: Text<'a>,
658}
659
660impl<'a> CommandContinuationRequestBasic<'a> {
661    /// Create a basic continuation request.
662    ///
663    /// Note: To avoid ambiguities in the IMAP standard, this constructor ensures that:
664    /// * iff `code` is `None`, `text` must not start with `[`.
665    /// * iff `code` is `None`, `text` must *not* be valid according to base64.
666    /// Otherwise, we could send a `Continue::Basic` that is interpreted as `Continue::Base64`.
667    pub fn new<T>(code: Option<Code<'a>>, text: T) -> Result<Self, ContinueError<T::Error>>
668    where
669        T: TryInto<Text<'a>>,
670    {
671        let text = text.try_into().map_err(ContinueError::Text)?;
672
673        // Ambiguity #1
674        if code.is_none() && text.as_ref().starts_with('[') {
675            return Err(ContinueError::Ambiguity);
676        }
677
678        // Ambiguity #2
679        if code.is_none() && _base64.decode(text.inner()).is_ok() {
680            return Err(ContinueError::Ambiguity);
681        }
682
683        Ok(Self { code, text })
684    }
685
686    pub fn code(&self) -> Option<&Code<'a>> {
687        self.code.as_ref()
688    }
689
690    pub fn text(&self) -> &Text<'a> {
691        &self.text
692    }
693}
694
695/// A response code consists of data inside square brackets in the form of an atom,
696/// possibly followed by a space and arguments.  The response code
697/// contains additional information or status codes for client software
698/// beyond the OK/NO/BAD condition, and are defined when there is a
699/// specific action that a client can take based upon the additional
700/// information.
701///
702/// The currently defined response codes are:
703#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
704#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
705#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
706#[derive(Debug, Clone, PartialEq, Eq, Hash)]
707pub enum Code<'a> {
708    /// `ALERT`
709    ///
710    /// The human-readable text contains a special alert that MUST be
711    /// presented to the user in a fashion that calls the user's
712    /// attention to the message.
713    Alert,
714
715    /// `BADCHARSET`
716    ///
717    /// Optionally followed by a parenthesized list of charsets.  A
718    /// SEARCH failed because the given charset is not supported by
719    /// this implementation.  If the optional list of charsets is
720    /// given, this lists the charsets that are supported by this
721    /// implementation.
722    BadCharset {
723        allowed: Vec<Charset<'a>>,
724    },
725
726    /// `CAPABILITY`
727    ///
728    /// Followed by a list of capabilities.  This can appear in the
729    /// initial OK or PREAUTH response to transmit an initial
730    /// capabilities list.  This makes it unnecessary for a client to
731    /// send a separate CAPABILITY command if it recognizes this
732    /// response.
733    Capability(NonEmptyVec<Capability<'a>>), // FIXME(misuse): List must contain IMAP4REV1
734
735    /// `PARSE`
736    ///
737    /// The human-readable text represents an error in parsing the
738    /// [RFC-2822] header or [MIME-IMB] headers of a message in the
739    /// mailbox.
740    Parse,
741
742    /// `PERMANENTFLAGS`
743    ///
744    /// Followed by a parenthesized list of flags, indicates which of
745    /// the known flags the client can change permanently.  Any flags
746    /// that are in the FLAGS untagged response, but not the
747    /// PERMANENTFLAGS list, can not be set permanently.  If the client
748    /// attempts to STORE a flag that is not in the PERMANENTFLAGS
749    /// list, the server will either ignore the change or store the
750    /// state change for the remainder of the current session only.
751    /// The PERMANENTFLAGS list can also include the special flag \*,
752    /// which indicates that it is possible to create new keywords by
753    /// attempting to store those flags in the mailbox.
754    PermanentFlags(Vec<FlagPerm<'a>>),
755
756    /// `READ-ONLY`
757    ///
758    /// The mailbox is selected read-only, or its access while selected
759    /// has changed from read-write to read-only.
760    ReadOnly,
761
762    /// `READ-WRITE`
763    ///
764    /// The mailbox is selected read-write, or its access while
765    /// selected has changed from read-only to read-write.
766    ReadWrite,
767
768    /// `TRYCREATE`
769    ///
770    /// An APPEND or COPY attempt is failing because the target mailbox
771    /// does not exist (as opposed to some other reason).  This is a
772    /// hint to the client that the operation can succeed if the
773    /// mailbox is first created by the CREATE command.
774    TryCreate,
775
776    /// `UIDNEXT`
777    ///
778    /// Followed by a decimal number, indicates the next unique
779    /// identifier value.  Refer to section 2.3.1.1 for more
780    /// information.
781    UidNext(NonZeroU32),
782
783    /// `UIDVALIDITY`
784    ///
785    /// Followed by a decimal number, indicates the unique identifier
786    /// validity value.  Refer to section 2.3.1.1 for more information.
787    UidValidity(NonZeroU32),
788
789    /// `UNSEEN`
790    ///
791    /// Followed by a decimal number, indicates the number of the first
792    /// message without the \Seen flag set.
793    Unseen(NonZeroU32),
794
795    /// IMAP4 Login Referrals (RFC 2221)
796    // TODO(misuse): the imap url is more complicated than that...
797    #[cfg(any(feature = "ext_mailbox_referrals", feature = "ext_login_referrals"))]
798    #[cfg_attr(
799        docsrs,
800        doc(cfg(any(feature = "ext_mailbox_referrals", feature = "ext_login_referrals")))
801    )]
802    Referral(Cow<'a, str>),
803
804    CompressionActive,
805
806    /// SHOULD be returned in the tagged NO response to an APPEND/COPY/MOVE when the addition of the
807    /// message(s) puts the target mailbox over any one of its quota limits.
808    OverQuota,
809
810    /// Server got a non-synchronizing literal larger than 4096 bytes.
811    TooBig,
812
813    /// Additional response codes defined by particular client or server
814    /// implementations SHOULD be prefixed with an "X" until they are
815    /// added to a revision of this protocol.  Client implementations
816    /// SHOULD ignore response codes that they do not recognize.
817    ///
818    /// ---
819    ///
820    /// ```abnf
821    /// atom [SP 1*<any TEXT-CHAR except "]">]`
822    /// ```
823    ///
824    /// Note: We use this as a fallback for everything that was not recognized as
825    ///       `Code`. This includes, e.g., variants with missing parameters, etc.
826    Other(CodeOther<'a>),
827}
828
829impl<'a> Code<'a> {
830    pub fn badcharset(allowed: Vec<Charset<'a>>) -> Self {
831        Self::BadCharset { allowed }
832    }
833
834    pub fn capability<C>(caps: C) -> Result<Self, C::Error>
835    where
836        C: TryInto<NonEmptyVec<Capability<'a>>>,
837    {
838        Ok(Self::Capability(caps.try_into()?))
839    }
840
841    pub fn permanentflags(flags: Vec<FlagPerm<'a>>) -> Self {
842        Self::PermanentFlags(flags)
843    }
844
845    pub fn uidnext(uidnext: u32) -> Result<Self, TryFromIntError> {
846        Ok(Self::UidNext(NonZeroU32::try_from(uidnext)?))
847    }
848
849    pub fn uidvalidity(uidnext: u32) -> Result<Self, TryFromIntError> {
850        Ok(Self::UidValidity(NonZeroU32::try_from(uidnext)?))
851    }
852
853    pub fn unseen(uidnext: u32) -> Result<Self, TryFromIntError> {
854        Ok(Self::Unseen(NonZeroU32::try_from(uidnext)?))
855    }
856}
857
858/// An (unknown) code.
859///
860/// It's guaranteed that this type can't represent any code from [`Code`].
861#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
862#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
863#[derive(Clone, PartialEq, Eq, Hash)]
864pub struct CodeOther<'a>(Cow<'a, [u8]>);
865
866// We want a more readable `Debug` implementation.
867impl<'a> Debug for CodeOther<'a> {
868    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
869        struct BStr<'a>(&'a Cow<'a, [u8]>);
870
871        impl<'a> Debug for BStr<'a> {
872            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
873                write!(
874                    f,
875                    "b\"{}\"",
876                    crate::utils::escape_byte_string(self.0.as_ref())
877                )
878            }
879        }
880
881        f.debug_tuple("CodeOther").field(&BStr(&self.0)).finish()
882    }
883}
884
885impl<'a> CodeOther<'a> {
886    /// Constructs an unsupported code without validation.
887    ///
888    /// # Warning: IMAP conformance
889    ///
890    /// The caller must ensure that `data` is valid. Failing to do so may create invalid/unparsable
891    /// IMAP messages, or even produce unintended protocol flows. Do not call this constructor with
892    /// untrusted data.
893    #[cfg(feature = "unvalidated")]
894    #[cfg_attr(docsrs, doc(cfg(feature = "unvalidated")))]
895    pub fn unvalidated<D: 'a>(data: D) -> Self
896    where
897        D: Into<Cow<'a, [u8]>>,
898    {
899        Self(data.into())
900    }
901
902    pub fn inner(&self) -> &[u8] {
903        self.0.as_ref()
904    }
905}
906
907#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
908#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
909#[derive(Debug, Clone, PartialEq, Eq, Hash)]
910#[non_exhaustive]
911pub enum Capability<'a> {
912    Imap4Rev1,
913    Auth(AuthMechanism<'a>),
914    #[cfg(feature = "starttls")]
915    #[cfg_attr(docsrs, doc(cfg(feature = "starttls")))]
916    LoginDisabled,
917    #[cfg(feature = "starttls")]
918    #[cfg_attr(docsrs, doc(cfg(feature = "starttls")))]
919    StartTls,
920    /// See RFC 2177.
921    Idle,
922    /// See RFC 2193.
923    #[cfg(feature = "ext_mailbox_referrals")]
924    #[cfg_attr(docsrs, doc(cfg(feature = "ext_mailbox_referrals")))]
925    MailboxReferrals,
926    /// See RFC 2221.
927    #[cfg(feature = "ext_login_referrals")]
928    #[cfg_attr(docsrs, doc(cfg(feature = "ext_login_referrals")))]
929    LoginReferrals,
930    SaslIr,
931    /// See RFC 5161.
932    Enable,
933    Compress {
934        algorithm: CompressionAlgorithm,
935    },
936    /// See RFC 2087 and RFC 9208
937    Quota,
938    /// See RFC 9208.
939    QuotaRes(Resource<'a>),
940    /// See RFC 9208.
941    QuotaSet,
942    /// See RFC 7888.
943    LiteralPlus,
944    LiteralMinus,
945    /// See RFC 6851.
946    Move,
947    /// Other/Unknown
948    Other(CapabilityOther<'a>),
949}
950
951impl<'a> Display for Capability<'a> {
952    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
953        match self {
954            Self::Imap4Rev1 => write!(f, "IMAP4REV1"),
955            Self::Auth(mechanism) => write!(f, "AUTH={}", mechanism),
956            #[cfg(feature = "starttls")]
957            Self::LoginDisabled => write!(f, "LOGINDISABLED"),
958            #[cfg(feature = "starttls")]
959            Self::StartTls => write!(f, "STARTTLS"),
960            #[cfg(feature = "ext_mailbox_referrals")]
961            Self::MailboxReferrals => write!(f, "MAILBOX-REFERRALS"),
962            #[cfg(feature = "ext_login_referrals")]
963            Self::LoginReferrals => write!(f, "LOGIN-REFERRALS"),
964            Self::SaslIr => write!(f, "SASL-IR"),
965            Self::Idle => write!(f, "IDLE"),
966            Self::Enable => write!(f, "ENABLE"),
967            Self::Compress { algorithm } => write!(f, "COMPRESS={}", algorithm),
968            Self::Quota => write!(f, "QUOTA"),
969            Self::QuotaRes(resource) => write!(f, "QUOTA=RES-{}", resource),
970            Self::QuotaSet => write!(f, "QUOTASET"),
971            Self::LiteralPlus => write!(f, "LITERAL+"),
972            Self::LiteralMinus => write!(f, "LITERAL-"),
973            Self::Move => write!(f, "MOVE"),
974            Self::Other(other) => write!(f, "{}", other.0),
975        }
976    }
977}
978
979impl_try_from!(Atom<'a>, 'a, &'a [u8], Capability<'a>);
980impl_try_from!(Atom<'a>, 'a, Vec<u8>, Capability<'a>);
981impl_try_from!(Atom<'a>, 'a, &'a str, Capability<'a>);
982impl_try_from!(Atom<'a>, 'a, String, Capability<'a>);
983
984impl<'a> From<Atom<'a>> for Capability<'a> {
985    fn from(atom: Atom<'a>) -> Self {
986        fn split_once_cow<'a>(
987            cow: Cow<'a, str>,
988            pattern: &str,
989        ) -> Option<(Cow<'a, str>, Cow<'a, str>)> {
990            match cow {
991                Cow::Borrowed(str) => {
992                    if let Some((left, right)) = str.split_once(pattern) {
993                        return Some((Cow::Borrowed(left), Cow::Borrowed(right)));
994                    }
995
996                    None
997                }
998                Cow::Owned(string) => {
999                    // TODO(efficiency)
1000                    if let Some((left, right)) = string.split_once(pattern) {
1001                        return Some((Cow::Owned(left.to_owned()), Cow::Owned(right.to_owned())));
1002                    }
1003
1004                    None
1005                }
1006            }
1007        }
1008
1009        let cow = atom.into_inner();
1010
1011        match cow.to_ascii_lowercase().as_ref() {
1012            "imap4rev1" => Self::Imap4Rev1,
1013            #[cfg(feature = "starttls")]
1014            "logindisabled" => Self::LoginDisabled,
1015            #[cfg(feature = "starttls")]
1016            "starttls" => Self::StartTls,
1017            "idle" => Self::Idle,
1018            #[cfg(feature = "ext_mailbox_referrals")]
1019            "mailbox-referrals" => Self::MailboxReferrals,
1020            #[cfg(feature = "ext_login_referrals")]
1021            "login-referrals" => Self::LoginReferrals,
1022            "sasl-ir" => Self::SaslIr,
1023            "enable" => Self::Enable,
1024            "quota" => Self::Quota,
1025            "quotaset" => Self::QuotaSet,
1026            "literal+" => Self::LiteralPlus,
1027            "literal-" => Self::LiteralMinus,
1028            "move" => Self::Move,
1029            _ => {
1030                // TODO(efficiency)
1031                if let Some((left, right)) = split_once_cow(cow.clone(), "=") {
1032                    match left.as_ref().to_ascii_lowercase().as_ref() {
1033                        "auth" => {
1034                            if let Ok(mechanism) = AuthMechanism::try_from(right) {
1035                                return Self::Auth(mechanism);
1036                            }
1037                        }
1038                        "compress" => {
1039                            if let Ok(atom) = Atom::try_from(right) {
1040                                if let Ok(algorithm) = CompressionAlgorithm::try_from(atom) {
1041                                    return Self::Compress { algorithm };
1042                                }
1043                            }
1044                        }
1045                        "quota" => {
1046                            if let Some((_, right)) =
1047                                right.as_ref().to_ascii_lowercase().split_once("res-")
1048                            {
1049                                // TODO(efficiency)
1050                                if let Ok(resource) = Resource::try_from(right.to_owned()) {
1051                                    return Self::QuotaRes(resource);
1052                                }
1053                            }
1054                        }
1055                        _ => {}
1056                    }
1057                }
1058
1059                Self::Other(CapabilityOther(Atom(cow)))
1060            }
1061        }
1062    }
1063}
1064
1065/// An (unknown) capability.
1066///
1067/// It's guaranteed that this type can't represent any capability from [`Capability`].
1068#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
1069#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1070#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1071pub struct CapabilityOther<'a>(Atom<'a>);
1072
1073/// Error-related types.
1074pub mod error {
1075    use thiserror::Error;
1076
1077    #[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
1078    pub enum ContinueError<T> {
1079        #[error("invalid text")]
1080        Text(T),
1081        #[error("ambiguity detected")]
1082        Ambiguity,
1083    }
1084
1085    #[derive(Clone, Debug, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
1086    pub enum FetchError<S, I> {
1087        #[error("Invalid sequence or UID: {0:?}")]
1088        SeqOrUid(S),
1089        #[error("Invalid items: {0:?}")]
1090        InvalidItems(I),
1091    }
1092}
1093
1094#[cfg(test)]
1095mod tests {
1096    use super::*;
1097
1098    #[test]
1099    fn test_conversion_data() {
1100        let _ = Data::capability(vec![Capability::Imap4Rev1]).unwrap();
1101        let _ = Data::fetch(1, vec![MessageDataItem::Rfc822Size(123)]).unwrap();
1102    }
1103
1104    #[test]
1105    fn test_conversion_continue_failing() {
1106        let tests = [
1107            CommandContinuationRequest::basic(None, ""),
1108            CommandContinuationRequest::basic(Some(Code::ReadWrite), ""),
1109        ];
1110
1111        for test in tests {
1112            println!("{:?}", test);
1113            assert!(test.is_err());
1114        }
1115    }
1116}