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}