mail_internals_ng/encoder/
mod.rs

1//! This module provides the encoding buffer.
2//!
3//! The encoding buffer is the buffer header implementations
4//! write there data to. It provides a view special aspects
5//! to make it more robust.
6//!
7//! For example it handles the writing of trailing newlines for headers
8//! (if they don't do it) and it fails if you write utf-8 data
9//! (in the header) to an buffer which knows that the used mail
10//! type doesn't support it.
11//!
12//! There is also a special `tracing` (cargo) feature which
13//! will make it's usage slower, but which will keep track of
14//! what data was inserted in which way making debugging and
15//! writing tests easier. (Through it should _only_ be enabled
16//! for testing and maybe debugging in some cases).
17use std::borrow::Cow;
18use std::str;
19
20use failure::Fail;
21use soft_ascii_string::{SoftAsciiStr, SoftAsciiChar};
22
23use grammar::is_atext;
24use ::utils::{
25    is_utf8_continuation_byte,
26    vec_insert_bytes
27};
28use ::MailType;
29use ::error::{
30    EncodingError, EncodingErrorKind,
31    UNKNOWN, UTF_8, US_ASCII
32};
33
34#[cfg(feature="traceing")]
35#[cfg_attr(test, macro_use)]
36mod trace;
37#[cfg_attr(test, macro_use)]
38mod encodable;
39
40
41#[cfg(feature="traceing")]
42pub use self::trace::*;
43pub use self::encodable::*;
44
45/// as specified in RFC 5322 not including CRLF
46pub const LINE_LEN_SOFT_LIMIT: usize = 78;
47/// as specified in RFC 5322 (mail) + RFC 5321 (smtp) not including CRLF
48pub const LINE_LEN_HARD_LIMIT: usize = 998;
49
50pub const NEWLINE: &str = "\r\n";
51pub const NEWLINE_WITH_SPACE: &str = "\r\n ";
52
53
54/// EncodingBuffer for a Mail providing a buffer for encodable traits.
55pub struct EncodingBuffer {
56    mail_type: MailType,
57    buffer: Vec<u8>,
58    #[cfg(feature="traceing")]
59    pub trace: Vec<TraceToken>
60}
61
62impl EncodingBuffer {
63
64    /// Create a new buffer only allowing input compatible with a the specified mail type.
65    pub fn new(mail_type: MailType) -> Self {
66        EncodingBuffer {
67            mail_type,
68            buffer: Vec::new(),
69            #[cfg(feature="traceing")]
70            trace: Vec::new()
71        }
72    }
73
74    /// Returns the mail type for which the buffer was created.
75    pub fn mail_type( &self ) -> MailType {
76        self.mail_type
77    }
78
79    /// returns a new EncodingWriter which contains
80    /// a mutable reference to the current string buffer
81    ///
82    pub fn writer(&mut self) -> EncodingWriter {
83        #[cfg(not(feature="traceing"))]
84        {
85            EncodingWriter::new(self.mail_type, &mut self.buffer)
86        }
87        #[cfg(feature="traceing")]
88        {
89            EncodingWriter::new(self.mail_type, &mut self.buffer, &mut self.trace)
90        }
91    }
92
93    /// calls the provided function with a EncodingWriter cleaning up afterwards
94    ///
95    /// After calling `func` with the EncodingWriter following cleanup is performed:
96    /// - if `func` returned an error `handle.undo_header()` is called, this won't
97    ///   undo anything before a `finish_header()` call but will discard partial
98    ///   writes
99    /// - if `func` succeeded `handle.finish_header()` is called
100    pub fn write_header_line<FN>(&mut self, func: FN) -> Result<(), EncodingError>
101        where FN: FnOnce(&mut EncodingWriter) -> Result<(), EncodingError>
102    {
103        let mut handle  = self.writer();
104        match func(&mut handle) {
105            Ok(()) => {
106                handle.finish_header();
107                Ok(())
108            },
109            Err(e) => {
110                handle.undo_header();
111                Err(e)
112            }
113        }
114
115    }
116
117    pub fn write_blank_line(&mut self) {
118        //TODO/BENCH push_str vs. extends(&[u8])
119        self.buffer.extend(NEWLINE.as_bytes());
120        #[cfg(feature="traceing")]
121        { self.trace.push(TraceToken::BlankLine); }
122    }
123
124    /// writes a body to the internal buffer, without verifying it's correctness
125    pub fn write_body_unchecked(&mut self, body: &impl AsRef<[u8]>) {
126        let slice = body.as_ref();
127        self.buffer.extend(slice);
128        if !slice.ends_with(NEWLINE.as_bytes()) {
129            self.buffer.extend(NEWLINE.as_bytes());
130        }
131    }
132
133    //TODO impl. a alt. `write_body(body,  boundaries)` which:
134    // - checks the body (us-ascii or mime8bit/internationalized)
135    // - checks for orphan '\r'/'\n' and 0 bytes
136    // - check that no string in boundaries appears in the text
137    //   - this probably requires creating a regex for each body
138    //     through as boundaries are "fixed" there might be an more
139    //     efficient algorithm then a regex (i.e. using tries)
140
141    /// # Error
142    ///
143    /// This can fail if a body does not contain valid utf8.
144    pub fn as_str(&self) -> Result<&str, EncodingError> {
145        str::from_utf8(self.buffer.as_slice())
146            .map_err(|err| {
147                EncodingError::from((
148                    err.context(EncodingErrorKind::InvalidTextEncoding {
149                        expected_encoding: UTF_8,
150                        got_encoding: UNKNOWN
151                    }),
152                    self.mail_type()
153                ))
154            })
155    }
156
157    /// Converts the internal buffer into an utf-8 string if possible.
158    pub fn to_string(&self) -> Result<String, EncodingError> {
159        Ok(self.as_str()?.to_owned())
160    }
161
162    /// Lossy conversion of the internal buffer to an string.
163    pub fn to_string_lossy(&self) -> Cow<str> {
164        String::from_utf8_lossy(self.buffer.as_slice())
165    }
166
167    /// Return a slice view to the underlying buffer.
168    pub fn as_slice(&self) -> &[u8] {
169        &self.buffer
170    }
171
172}
173
174
175impl Into<Vec<u8>> for EncodingBuffer {
176    fn into(self) -> Vec<u8> {
177        self.buffer
178    }
179}
180
181impl Into<(MailType, Vec<u8>)> for EncodingBuffer {
182    fn into(self) -> (MailType, Vec<u8>) {
183        (self.mail_type, self.buffer)
184    }
185}
186
187#[cfg(feature="traceing")]
188impl Into<(MailType, Vec<u8>, Vec<TraceToken>)> for EncodingBuffer {
189    fn into(self) -> (MailType, Vec<u8>, Vec<TraceToken>) {
190        let EncodingBuffer { mail_type, buffer, trace } = self;
191        (mail_type, buffer, trace)
192    }
193}
194
195/// A handle providing method to write to the underlying buffer
196/// keeping track of newlines the current line length and places
197/// where the line can be broken so that the soft line length
198/// limit (78) and the hard length limit (998) can be kept.
199///
200/// It's basically a string buffer which know how to brake
201/// lines at the right place.
202///
203/// Note any act of writing a header through `EncodingWriter`
204/// has to be concluded by either calling `finish_header` or `undo_header`.
205/// If not this handle will panic in _test_ builds when being dropped
206/// (and the thread is not already panicing) as writes through the handle are directly
207/// writes to the underlying buffer which now contains malformed/incomplete
208/// data. (Note that this Handle does not own any Drop types so if
209/// needed `forget`-ing it won't leak any memory)
210///
211///
212pub struct EncodingWriter<'a> {
213    buffer: &'a mut Vec<u8>,
214    #[cfg(feature="traceing")]
215    trace: &'a mut Vec<TraceToken>,
216    mail_type: MailType,
217    line_start_idx: usize,
218    last_fws_idx: usize,
219    skipped_cr: bool,
220    /// if there had ben non WS chars since the last FWS
221    /// or last line start, if there had been a line
222    /// start since the last fws.
223    content_since_fws: bool,
224    /// represents if there had ben non WS chars before the last FWS
225    /// on the current line (false if there was no FWS yet on the current
226    /// line).
227    content_before_fws: bool,
228    /// represents if if a FWS was just marked (opt-FWS) or was written out
229    last_fws_has_char: bool,
230    header_start_idx: usize,
231    #[cfg(feature="traceing")]
232    trace_start_idx: usize
233}
234
235#[cfg(feature="traceing")]
236impl<'a> Drop for EncodingWriter<'a> {
237
238    fn drop(&mut self) {
239        use std::thread;
240        if !thread::panicking() &&  self.has_unfinished_parts() {
241            // we really should panic as the back buffer i.e. the mail will contain
242            // some partially written header which definitely is a bug
243            panic!("dropped Handle which partially wrote header to back buffer (use `finish_header` or `discard`)")
244        }
245    }
246}
247
248impl<'inner> EncodingWriter<'inner> {
249
250    #[cfg(not(feature="traceing"))]
251    fn new(
252        mail_type: MailType,
253        buffer: &'inner mut Vec<u8>,
254    ) -> Self {
255        let start_idx = buffer.len();
256        EncodingWriter {
257            buffer,
258            mail_type,
259            line_start_idx: start_idx,
260            last_fws_idx: start_idx,
261            skipped_cr: false,
262            content_since_fws: false,
263            content_before_fws: false,
264            header_start_idx: start_idx,
265            last_fws_has_char: false,
266        }
267    }
268
269    #[cfg(feature="traceing")]
270    fn new(
271        mail_type: MailType,
272        buffer: &'inner mut Vec<u8>,
273        trace: &'inner mut Vec<TraceToken>
274    ) -> Self {
275        let start_idx = buffer.len();
276        let trace_start_idx = trace.len();
277        EncodingWriter {
278            buffer,
279            trace,
280            mail_type,
281            line_start_idx: start_idx,
282            last_fws_idx: start_idx,
283            skipped_cr: false,
284            content_since_fws: false,
285            content_before_fws: false,
286            header_start_idx: start_idx,
287            last_fws_has_char: false,
288            trace_start_idx
289        }
290    }
291
292    fn reinit(&mut self) {
293        let start_idx = self.buffer.len();
294        self.line_start_idx = start_idx;
295        self.last_fws_idx = start_idx;
296        self.skipped_cr = false;
297        self.content_since_fws = false;
298        self.content_before_fws = false;
299        self.header_start_idx = start_idx;
300        #[cfg(feature="traceing")]
301        { self.trace_start_idx = self.trace.len(); }
302    }
303
304    /// Returns true if this type thinks we are in the process of writing a header.
305    #[inline]
306    pub fn has_unfinished_parts(&self) -> bool {
307        self.buffer.len() != self.header_start_idx
308    }
309
310    /// Returns the associated mail type.
311    #[inline]
312    pub fn mail_type(&self) -> MailType {
313        self.mail_type
314    }
315
316    /// Returns true if the current line has content, i.e. any non WS char.
317    #[inline]
318    pub fn line_has_content(&self) -> bool {
319        self.content_before_fws | self.content_since_fws
320    }
321
322    /// Returns the length of the current line in bytes.
323    #[inline]
324    pub fn current_line_byte_length(&self) -> usize {
325        self.buffer.len() - self.line_start_idx
326    }
327
328    /// marks the current position a a place where a soft
329    /// line break (i.e. "\r\n ") can be inserted
330    ///
331    /// # Trace (test build only)
332    /// does push a `MarkFWS` Token
333    pub fn mark_fws_pos(&mut self) {
334        #[cfg(feature="traceing")]
335        { self.trace.push(TraceToken::MarkFWS) }
336        self.content_before_fws |= self.content_since_fws;
337        self.content_since_fws = false;
338        self.last_fws_idx = self.buffer.len();
339        self.last_fws_has_char = false;
340    }
341
342    /// writes a ascii char to the underlying buffer
343    ///
344    /// # Error
345    /// - fails if the hard line length limit is breached and the
346    ///   line can not be broken with soft line breaks
347    /// - buffer would contain a orphan '\r' or '\n' after the write
348    ///
349    /// # Trace (test build only)
350    /// does push `NowChar` and then can push `Text`,`CRLF`
351    pub fn write_char(&mut self, ch: SoftAsciiChar) -> Result<(), EncodingError>  {
352        #[cfg(feature="traceing")]
353        { self.trace.push(TraceToken::NowChar) }
354        let mut buffer = [0xff_u8; 4];
355        let ch: char = ch.into();
356        let slice = ch.encode_utf8(&mut buffer);
357        self.internal_write_char(slice)
358    }
359
360    /// writes a ascii str to the underlying buffer
361    ///
362    /// # Error
363    /// - fails if the hard line length limit is breached and the
364    ///   line can not be broken with soft line breaks
365    /// - buffer would contain a orphan '\r' or '\n' after the write
366    ///
367    /// Note that in case of an error part of the content might already
368    /// have been written to the buffer, therefore it is recommended
369    /// to call `undo_header` after an error (especially if the
370    /// handle is doped after this!)
371    ///
372    /// # Trace (test build only)
373    /// does push `NowStr` and then can push `Text`,`CRLF`
374    ///
375    pub fn write_str(&mut self, s: &SoftAsciiStr)  -> Result<(), EncodingError>  {
376        #[cfg(feature="traceing")]
377        { self.trace.push(TraceToken::NowStr) }
378        self.internal_write_str(s.as_str())
379    }
380
381
382    /// writes a utf8 str into a buffer for an internationalized mail
383    ///
384    /// # Error (ConditionalWriteResult)
385    /// - fails with `ConditionFailure` if the underlying MailType
386    ///    is not Internationalized
387    /// - fails with `GeneralFailure` if the hard line length limit is reached
388    /// - or if the buffer would contain a orphan '\r' or '\n' after the write
389    ///
390    /// Note that in case of an error part of the content might already
391    /// have been written to the buffer, therefore it is recommended
392    /// to call `undo_header` after an error (especially if the
393    /// handle is droped after this!)
394    ///
395    /// # Trace (test build only)
396    /// does push `NowUtf8` and then can push `Text`,`CRLF`
397    pub fn write_if_utf8<'short>(&'short mut self, s: &str)
398        -> ConditionalWriteResult<'short, 'inner>
399    {
400        if self.mail_type().is_internationalized() {
401            #[cfg(feature="traceing")]
402            { self.trace.push(TraceToken::NowUtf8) }
403            self.internal_write_str(s).into()
404        } else {
405            ConditionalWriteResult::ConditionFailure(self)
406        }
407    }
408
409    pub fn write_utf8(&mut self, s: &str) -> Result<(), EncodingError> {
410        if self.mail_type().is_internationalized() {
411            #[cfg(feature="traceing")]
412            { self.trace.push(TraceToken::NowUtf8) }
413            self.internal_write_str(s)
414        } else {
415            let mut err = EncodingError::from((
416                EncodingErrorKind::InvalidTextEncoding {
417                    expected_encoding: US_ASCII,
418                    got_encoding: UTF_8
419                },
420                self.mail_type()
421            ));
422            let raw_line = &self.buffer[self.line_start_idx..];
423            let mut line = String::from_utf8_lossy(raw_line).into_owned();
424            line.push_str(s);
425            err.set_str_context(line);
426            Err(err)
427        }
428    }
429
430    /// Writes a str assumed to be atext if it is atext given the mail type
431    ///
432    /// This method is mainly an optimization as the "is atext" and is
433    /// "is ascii if MailType is Ascii" aspects are checked at the same
434    /// time resulting in a str which you know is ascii _if_ the mail
435    /// type is Ascii and which might be non-us-ascii if the mail type
436    /// is Internationalized.
437    ///
438    /// # Error (ConditionalWriteResult)
439    /// - fails with `ConditionFailure` if the text is not valid atext,
440    ///   this indirectly also includes the utf8/Internationalization check
441    ///   as the `atext` grammar differs between normal and internationalized
442    ///   mail.
443    /// - fails with `GeneralFailure` if the hard line length limit is reached and
444    ///   the line can't be broken with soft line breaks
445    /// - or if buffer would contain a orphan '\r' or '\n' after the write
446    ///   (excluding a tailing `'\r'` as it is still valid if followed by an
447    ///    `'\n'`)
448    ///
449    /// Note that in case of an error part of the content might already
450    /// have been written to the buffer, therefore it is recommended
451    /// to call `undo_header` after an error (especially if the
452    /// handle is doped after this!)
453    ///
454    /// # Trace (test build only)
455    /// does push `NowAText` and then can push `Text`
456    ///
457    pub fn write_if_atext<'short>(&'short mut self, s: &str)
458        -> ConditionalWriteResult<'short, 'inner>
459    {
460        if s.chars().all( |ch| is_atext( ch, self.mail_type() ) ) {
461            #[cfg(feature="traceing")]
462            { self.trace.push(TraceToken::NowAText) }
463            // the ascii or not aspect is already converted by `is_atext`
464            self.internal_write_str(s).into()
465        } else {
466            ConditionalWriteResult::ConditionFailure(self)
467        }
468    }
469
470    /// passes the input `s` to the condition evaluation function `cond` and
471    /// then writes it _without additional checks_ to the buffer if `cond` returned
472    /// true
473    ///
474    pub fn write_if<'short, FN>(&'short mut self, s: &str, cond: FN)
475        -> ConditionalWriteResult<'short, 'inner>
476        where FN: FnOnce(&str) -> bool
477    {
478        if cond(s) {
479            #[cfg(feature="traceing")]
480            { self.trace.push(TraceToken::NowCondText) }
481            // the ascii or not aspect is already converted by `is_atext`
482            self.internal_write_str(s).into()
483        } else {
484            ConditionalWriteResult::ConditionFailure(self)
485        }
486    }
487
488    /// writes a string to the encoder without checking if it is compatible
489    /// with the mail type, if not used correctly this can write Utf8 to
490    /// an Ascii Mail, which is incorrect but has to be safe wrt. rust's safety.
491    ///
492    /// Use it as a replacement for cases similar to following:
493    ///
494    /// ```ignore
495    /// check_if_text_if_valid(text)?;
496    /// if mail_type.is_internationalized() {
497    ///     handle.write_utf8(text)?;
498    /// } else {
499    ///     handle.write_str(SoftAsciiStr::from_unchecked(text))?;
500    /// }
501    /// ```
502    ///
503    /// ==> instead ==>
504    ///
505    /// ```ignore
506    /// check_if_text_if_valid(text)?;
507    /// handle.write_str_unchecked(text)?;
508    /// ```
509    ///
510    /// through is gives a different tracing its roughly equivalent.
511    ///
512    pub fn write_str_unchecked( &mut self, s: &str) -> Result<(), EncodingError> {
513        #[cfg(feature="traceing")]
514        { self.trace.push(TraceToken::NowUnchecked) }
515        self.internal_write_str(s)
516    }
517
518    /// like finish_header, but won't start a new line
519    ///
520    /// This is meant to be used when _miss-using_ the
521    /// writer to write a "think", which is not a full
522    /// header. E.g. for testing if a header component
523    /// is written correctly. So you _normally_ should
524    /// not use it.
525    pub fn commit_partial_header(&mut self) {
526        #[cfg(feature="traceing")]
527        { if let Some(&TraceToken::End) = self.trace.last() {}
528            else { self.trace.push(TraceToken::End) } }
529        self.reinit();
530    }
531
532    /// finishes the writing of a header
533    ///
534    /// It makes sure the header ends in "\r\n".
535    /// If the header ends in a orphan '\r' this
536    /// method will just "use" it for the "\r\n".
537    ///
538    /// If the header ends in a CRLF/start of buffer
539    /// followed by only WS (' ' or '\t' ) the valid
540    /// header ending is reached by truncating away
541    /// the WS padding. This is needed as "blank" lines
542    /// are not allowed.
543    ///
544    /// # Trace (test build only)
545    /// - can push 0-1 of `[CRLF, TruncateToCRLF]`
546    /// - then does push `End`
547    /// - calling `finish_current()` multiple times in a row
548    ///   will not generate multiple `End` tokens, just one
549    pub fn finish_header(&mut self) {
550        self.start_new_line();
551        #[cfg(feature="traceing")]
552        { if let Some(&TraceToken::End) = self.trace.last() {}
553            else { self.trace.push(TraceToken::End) } }
554        self.reinit();
555    }
556
557    /// undoes all writes to the internal buffer
558    /// since the last `finish_header` or `undo_header` or
559    /// creation of this handle
560    ///
561    /// # Trace (test build only)
562    /// also removes tokens pushed since the last
563    /// `finish_header` or `undo_header` or creation of
564    /// this handle
565    ///
566    pub fn undo_header(&mut self) {
567        self.buffer.truncate(self.header_start_idx);
568        #[cfg(feature="traceing")]
569        { self.trace.truncate(self.trace_start_idx); }
570        self.reinit();
571    }
572
573
574
575    //---------------------------------------------------------------------------------------------/
576    //-/////////////////////////// methods only using the public iface   /////////////////////////-/
577
578    /// calls mark_fws_pos and then writes a space
579    ///
580    /// This method exists for convenience.
581    ///
582    /// Note that it can not fail a you just pushed
583    /// a place to brake the line before writing a space.
584    ///
585    /// Note that currently soft line breaks will not
586    /// collapse whitespace. As such if you use `write_fws`
587    /// and then the line is broken at that position it will
588    /// start with two spaces (one from `\r\n ` and one which
589    /// had been there before).
590    pub fn write_fws(&mut self) {
591        self.mark_fws_pos();
592        self.last_fws_has_char = true;
593        // OK: Can not error as we just marked a fws pos.
594        let _ = self.write_char(SoftAsciiChar::from_unchecked(' '));
595    }
596
597
598
599    //---------------------------------------------------------------------------------------------/
600    //-///////////////////////////          private methods               ////////////////////////-/
601
602    /// this might partial write some data and then fail.
603    /// while we could implement a undo option it makes
604    /// little sense for the use case the generally available
605    /// `undo_header` is enough.
606    fn internal_write_str(&mut self, s: &str)  -> Result<(), EncodingError>  {
607        if s.is_empty() {
608            return Ok(());
609        }
610        //TODO I think I wrote a iterator for this somewhere
611        let mut start = 0;
612        // the first byte is never a continuation byte so we start
613        // scanning at the second byte
614        for (idx_m1, bch) in s.as_bytes()[1..].iter().enumerate() {
615            if !is_utf8_continuation_byte(*bch) {
616                // the idx is 1 smaller then it should so add 1
617                let end = idx_m1 + 1;
618                self.internal_write_char(&s[start..end])?;
619                start = end;
620            }
621        }
622
623        //write last letter
624        self.internal_write_char(&s[start..])?;
625        Ok(())
626    }
627
628    /// if the line has at last one non-WS char a new line
629    /// will be started by adding `\r\n` if the current line
630    /// only consists of WS then a new line will be started by
631    /// removing the blank line (not that WS are only ' ' and '\r')
632    fn start_new_line(&mut self) {
633        if self.line_has_content() {
634            #[cfg(feature="traceing")]
635            { self.trace.push(TraceToken::CRLF) }
636
637            self.buffer.push(b'\r');
638            self.buffer.push(b'\n');
639        } else {
640            #[cfg(feature="traceing")]
641            {
642                if self.buffer.len() > self.line_start_idx {
643                    self.trace.push(TraceToken::TruncateToCRLF);
644                }
645            }
646            // e.g. if we "broke" the line on a tailing space => "\r\n  "
647            // this would not be valid so we cut awy the trailing white space
648            // be if we have "ab  " we do not want to cut away the trailing
649            // whitespace but just add "\r\n"
650            self.buffer.truncate(self.line_start_idx);
651        }
652        self.line_start_idx = self.buffer.len();
653        self.content_since_fws = false;
654        self.content_before_fws = false;
655        self.last_fws_idx = self.line_start_idx;
656
657    }
658
659    fn break_line_on_fws(&mut self) -> bool {
660        if self.content_before_fws && self.last_fws_idx > self.line_start_idx {
661            let newline =
662                if self.last_fws_has_char {
663                    debug_assert!([b' ', b'\t'].contains(&self.buffer[self.last_fws_idx]));
664                    NEWLINE
665                } else {
666                    NEWLINE_WITH_SPACE
667                };
668
669            vec_insert_bytes(&mut self.buffer, self.last_fws_idx, newline.as_bytes());
670            self.line_start_idx = self.last_fws_idx + 2;
671            self.content_before_fws = false;
672            true
673        } else {
674            false
675        }
676    }
677
678    /// # Constraints
679    ///
680    /// `unchecked_utf8_char` is expected to be exactly
681    /// one char, which means it's 1-4 bytes in length.
682    ///
683    /// The reason why a slice is expected instead of a
684    /// char is, that this function will at some point push
685    /// to a byte buffer requiring a `&[u8]` and many function
686    /// calling this function can directly produce a &[u8]/&str.
687    ///
688    /// # Panic
689    ///
690    /// Panics if `unchecked_utf8_char` is empty.
691    /// If debug assertions are enabled it also panics, if
692    /// unchecked_utf8_char is more than just one char.
693    fn internal_write_char(&mut self, unchecked_utf8_char: &str) -> Result<(), EncodingError> {
694        debug_assert_eq!(unchecked_utf8_char.chars().count(), 1);
695
696        let bch = unchecked_utf8_char.as_bytes()[0];
697        if bch == b'\n' {
698            if self.skipped_cr {
699                self.start_new_line()
700            } else {
701                ec_bail!(
702                    mail_type: self.mail_type(),
703                    kind: Malformed
704                );
705            }
706            self.skipped_cr = false;
707            return Ok(());
708        } else {
709            if self.skipped_cr {
710                ec_bail!(
711                    mail_type: self.mail_type(),
712                    kind: Malformed
713                );
714            }
715            if bch == b'\r' {
716                self.skipped_cr = true;
717                return Ok(());
718            } else {
719                self.skipped_cr = false;
720            }
721        }
722
723        if self.current_line_byte_length() >= LINE_LEN_SOFT_LIMIT {
724            self.break_line_on_fws();
725
726            if self.current_line_byte_length() >= LINE_LEN_HARD_LIMIT {
727                ec_bail!(
728                    mail_type: self.mail_type(),
729                    kind: HardLineLengthLimitBreached
730                );
731            }
732        }
733
734        self.buffer.extend(unchecked_utf8_char.as_bytes());
735        #[cfg(feature="traceing")]
736        {
737            //FIXME[rust/nll]: just use a `if let`-`else` with NLL's
738            let need_new =
739                if let Some(&mut TraceToken::Text(ref mut string)) = self.trace.last_mut() {
740                    string.push_str(unchecked_utf8_char);
741                    false
742                } else {
743                    true
744                };
745            if need_new {
746                let mut string = String::new();
747                string.push_str(unchecked_utf8_char);
748                self.trace.push(TraceToken::Text(string))
749            }
750
751        }
752
753        // we can't allow "blank" lines
754        if bch != b' ' && bch != b'\t' {
755            // if there is no fws this is equiv to line_has_content
756            // else line_has_content = self.content_before_fws|self.content_since_fws
757            self.content_since_fws = true;
758        }
759        Ok(())
760    }
761}
762
763pub enum ConditionalWriteResult<'a, 'b: 'a> {
764    Ok,
765    ConditionFailure(&'a mut EncodingWriter<'b>),
766    GeneralFailure(EncodingError)
767}
768
769impl<'a, 'b: 'a> From<Result<(), EncodingError>> for ConditionalWriteResult<'a, 'b> {
770    fn from(v: Result<(), EncodingError>) -> Self {
771        match v {
772            Ok(()) => ConditionalWriteResult::Ok,
773            Err(e) => ConditionalWriteResult::GeneralFailure(e)
774        }
775    }
776}
777
778impl<'a, 'b: 'a> ConditionalWriteResult<'a, 'b> {
779
780    #[inline]
781    pub fn handle_condition_failure<FN>(self, func: FN) -> Result<(), EncodingError>
782        where FN: FnOnce(&mut EncodingWriter) -> Result<(), EncodingError>
783    {
784        use self::ConditionalWriteResult as CWR;
785
786        match self {
787            CWR::Ok => Ok(()),
788            CWR::ConditionFailure(handle) => {
789                func(handle)
790            },
791            CWR::GeneralFailure(err) => Err(err)
792        }
793    }
794}
795
796
797
798
799
800#[cfg(test)]
801mod test {
802
803    use soft_ascii_string::{ SoftAsciiChar, SoftAsciiStr};
804    use ::MailType;
805    use ::error::EncodingErrorKind;
806
807    use super::TraceToken::*;
808    use super::{EncodingBuffer as _Encoder};
809
810    mod test_test_utilities {
811        use encoder::TraceToken::*;
812        use super::super::simplify_trace_tokens;
813
814        #[test]
815        fn does_simplify_tokens_strip_nows() {
816            let inp = vec![
817                NowChar,
818                Text("h".into()),
819                CRLF,
820                NowStr,
821                Text("y yo".into()),
822                CRLF,
823                NowUtf8,
824                Text(", what's".into()),
825                CRLF,
826                NowUnchecked,
827                Text("up!".into()),
828                CRLF,
829                NowAText,
830                Text("abc".into())
831            ];
832            let out = simplify_trace_tokens(inp);
833            assert_eq!(out, vec![
834                Text("h".into()),
835                CRLF,
836                Text("y yo".into()),
837                CRLF,
838                Text(", what's".into()),
839                CRLF,
840                Text("up!".into()),
841                CRLF,
842                Text("abc".into())
843            ])
844
845        }
846
847        #[test]
848        fn simplify_does_collapse_text() {
849            let inp = vec![
850                NowChar,
851                Text("h".into()),
852                NowStr,
853                Text("y yo".into()),
854                NowUtf8,
855                Text(", what's".into()),
856                NowUnchecked,
857                Text(" up! ".into()),
858                NowAText,
859                Text("abc".into())
860            ];
861            let out = simplify_trace_tokens(inp);
862            assert_eq!(out, vec![
863                Text("hy yo, what's up! abc".into())
864            ]);
865        }
866
867        #[test]
868        fn simplify_works_with_empty_text() {
869            let inp = vec![
870                NowStr,
871                Text("".into()),
872                CRLF,
873            ];
874            assert_eq!(simplify_trace_tokens(inp), vec![
875                Text("".into()),
876                CRLF
877            ])
878        }
879
880        #[test]
881        fn simplify_works_with_trailing_empty_text() {
882            let inp = vec![
883                Text("a".into()),
884                CRLF,
885                Text("".into()),
886            ];
887            assert_eq!(simplify_trace_tokens(inp), vec![
888                Text("a".into()),
889                CRLF,
890                Text("".into())
891            ])
892        }
893
894    }
895
896    mod EncodableInHeader {
897        #![allow(non_snake_case)]
898        use super::super::*;
899        use self::TraceToken::*;
900
901        #[test]
902        fn is_implemented_for_closures() {
903            let closure = enc_func!(|handle: &mut EncodingWriter| {
904                handle.write_utf8("hy ho")
905            });
906
907            let mut encoder = EncodingBuffer::new(MailType::Internationalized);
908            {
909                let mut handle = encoder.writer();
910                assert_ok!(closure.encode(&mut handle));
911                handle.finish_header();
912            }
913            assert_eq!(encoder.trace.as_slice(), &[
914                NowUtf8,
915                Text("hy ho".into()),
916                CRLF,
917                End
918            ])
919        }
920    }
921
922
923    mod EncodingBuffer {
924        #![allow(non_snake_case)]
925        use super::*;
926        use super::{ _Encoder as EncodingBuffer };
927
928        #[test]
929        fn new_encoder() {
930            let encoder = EncodingBuffer::new(MailType::Internationalized);
931            assert_eq!(encoder.mail_type(), MailType::Internationalized);
932        }
933
934        #[test]
935        fn write_body_unchecked() {
936            let mut encoder = EncodingBuffer::new(MailType::Ascii);
937            let body1 = "una body\r\n";
938            let body2 = "another body";
939
940            encoder.write_body_unchecked(&body1);
941            encoder.write_blank_line();
942            encoder.write_body_unchecked(&body2);
943
944            assert_eq!(
945                encoder.as_slice(),
946                concat!(
947                    "una body\r\n",
948                    "\r\n",
949                    "another body\r\n"
950                ).as_bytes()
951            )
952        }
953    }
954
955
956    mod EncodingWriter {
957        #![allow(non_snake_case)]
958        use std::mem;
959        use std::str;
960
961        use super::*;
962        use super::{ _Encoder as EncodingBuffer };
963
964        #[test]
965        fn commit_partial_and_drop_does_not_panic() {
966            let mut encoder = EncodingBuffer::new(MailType::Ascii);
967            {
968                let mut handle = encoder.writer();
969                assert_ok!(handle.write_str(SoftAsciiStr::from_unchecked("12")));
970                handle.commit_partial_header();
971            }
972            assert_eq!(encoder.as_slice(), b"12");
973        }
974
975        #[test]
976        fn undo_does_undo() {
977            let mut encoder = EncodingBuffer::new(MailType::Ascii);
978            {
979                let mut handle = encoder.writer();
980                assert_ok!(
981                    handle.write_str(SoftAsciiStr::from_unchecked("Header-One: 12")));
982                handle.undo_header();
983            }
984            assert_eq!(encoder.as_slice(), b"");
985        }
986
987        #[test]
988        fn undo_does_not_undo_to_much() {
989            let mut encoder = EncodingBuffer::new(MailType::Ascii);
990            {
991                let mut handle = encoder.writer();
992                assert_ok!(handle.write_str(SoftAsciiStr::from_str("Header-One: 12").unwrap()));
993                handle.finish_header();
994                assert_ok!(handle.write_str(SoftAsciiStr::from_str("ups: sa").unwrap()));
995                handle.undo_header();
996            }
997            assert_eq!(encoder.as_slice(), b"Header-One: 12\r\n");
998        }
999
1000        #[test]
1001        fn finish_adds_crlf_if_needed() {
1002            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1003            {
1004                let mut handle = encoder.writer();
1005                assert_ok!(handle.write_str(SoftAsciiStr::from_str("Header-One: 12").unwrap()));
1006                handle.finish_header();
1007            }
1008            assert_eq!(encoder.as_slice(), b"Header-One: 12\r\n");
1009        }
1010
1011        #[test]
1012        fn finish_does_not_add_crlf_if_not_needed() {
1013            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1014            {
1015                let mut handle = encoder.writer();
1016                assert_ok!(handle.write_str(SoftAsciiStr::from_str("Header-One: 12\r\n").unwrap()));
1017                handle.finish_header();
1018            }
1019            assert_eq!(encoder.as_slice(), b"Header-One: 12\r\n");
1020        }
1021
1022        #[test]
1023        fn finish_does_truncat_if_needed() {
1024            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1025            {
1026                let mut handle = encoder.writer();
1027                assert_ok!(handle.write_str(SoftAsciiStr::from_str("Header-One: 12\r\n   ").unwrap()));
1028                handle.finish_header();
1029            }
1030            assert_eq!(encoder.as_slice(), b"Header-One: 12\r\n");
1031        }
1032
1033
1034        #[test]
1035        fn finish_can_handle_fws() {
1036            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1037            {
1038                let mut handle = encoder.writer();
1039                assert_ok!(handle.write_str(SoftAsciiStr::from_str("Header-One: 12 +\r\n 4").unwrap()));
1040                handle.finish_header();
1041            }
1042            assert_eq!(encoder.as_slice(), b"Header-One: 12 +\r\n 4\r\n");
1043        }
1044
1045        #[test]
1046        fn finish_only_truncats_if_needed() {
1047            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1048            {
1049                let mut handle = encoder.writer();
1050                assert_ok!(handle.write_str(
1051                    SoftAsciiStr::from_str("Header-One: 12 +\r\n 4  ").unwrap()));
1052                handle.finish_header();
1053            }
1054            assert_eq!(encoder.as_slice(), b"Header-One: 12 +\r\n 4  \r\n");
1055        }
1056
1057
1058        #[test]
1059        fn orphan_lf_error() {
1060            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1061            {
1062                let mut handle = encoder.writer();
1063                assert_err!(handle.write_str(SoftAsciiStr::from_str("H: \na").unwrap()));
1064                handle.undo_header()
1065            }
1066        }
1067        #[test]
1068        fn orphan_cr_error() {
1069            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1070            {
1071                let mut handle = encoder.writer();
1072                assert_err!(handle.write_str(SoftAsciiStr::from_str("H: \ra").unwrap()));
1073                handle.undo_header()
1074            }
1075        }
1076
1077        #[test]
1078        fn orphan_trailing_lf() {
1079            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1080            {
1081                let mut handle = encoder.writer();
1082                assert_err!(handle.write_str(SoftAsciiStr::from_str("H: a\n").unwrap()));
1083                handle.undo_header();
1084            }
1085        }
1086
1087        #[test]
1088        fn orphan_trailing_cr() {
1089            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1090            {
1091                let mut handle = encoder.writer();
1092                assert_ok!(handle.write_str(SoftAsciiStr::from_str("H: a\r").unwrap()));
1093                //it's fine not to error in the trailing \r case as we want to write
1094                //a \r\n anyway
1095                handle.finish_header();
1096            }
1097            assert_eq!(encoder.as_slice(), b"H: a\r\n");
1098        }
1099
1100         #[test]
1101        fn soft_line_limit_can_be_breached() {
1102            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1103            {
1104                let mut handle = encoder.writer();
1105                for _ in 0u32..500 {
1106                    assert_ok!(handle.internal_write_char("a"));
1107                }
1108                handle.finish_header();
1109            }
1110        }
1111
1112        #[test]
1113        fn hard_line_limit_can_not_be_breached() {
1114            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1115            {
1116                let mut handle = encoder.writer();
1117                for _ in 0u32..998 {
1118                    assert_ok!(handle.internal_write_char("a"));
1119                }
1120
1121                assert_err!(handle.internal_write_char("b"));
1122                handle.finish_header();
1123            }
1124        }
1125
1126        #[test]
1127        fn break_line_on_fws() {
1128            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1129            {
1130                let mut handle = encoder.writer();
1131                assert_ok!(handle.write_str(SoftAsciiStr::from_str("A23456789:").unwrap()));
1132                handle.mark_fws_pos();
1133                assert_ok!(handle.write_str(SoftAsciiStr::from_str(concat!(
1134                    "20_3456789",
1135                    "30_3456789",
1136                    "40_3456789",
1137                    "50_3456789",
1138                    "60_3456789",
1139                    "70_3456789",
1140                    "12345678XX"
1141                )).unwrap()));
1142                handle.finish_header();
1143            }
1144            assert_eq!(
1145                encoder.as_str().unwrap(),
1146                concat!(
1147                    "A23456789:\r\n ",
1148                    "20_3456789",
1149                    "30_3456789",
1150                    "40_3456789",
1151                    "50_3456789",
1152                    "60_3456789",
1153                    "70_3456789",
1154                    "12345678XX\r\n"
1155                )
1156            );
1157        }
1158
1159        #[test]
1160        fn break_line_on_fws_does_not_insert_unessesary_space() {
1161            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1162            {
1163                let mut handle = encoder.writer();
1164                assert_ok!(handle.write_str(SoftAsciiStr::from_str("A23456789:").unwrap()));
1165                handle.write_fws();
1166                assert_ok!(handle.write_str(SoftAsciiStr::from_str(concat!(
1167                    "20_3456789",
1168                    "30_3456789",
1169                    "40_3456789",
1170                    "50_3456789",
1171                    "60_3456789",
1172                    "70_3456789",
1173                    "12345678XX"
1174                )).unwrap()));
1175                handle.finish_header();
1176            }
1177
1178            assert_eq!(
1179                encoder.as_str().unwrap(),
1180                concat!(
1181                    "A23456789:\r\n ",
1182                    "20_3456789",
1183                    "30_3456789",
1184                    "40_3456789",
1185                    "50_3456789",
1186                    "60_3456789",
1187                    "70_3456789",
1188                    "12345678XX\r\n"
1189                )
1190            );
1191        }
1192
1193
1194        #[test]
1195        fn to_long_unbreakable_line() {
1196            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1197            {
1198                let mut handle = encoder.writer();
1199                assert_ok!(handle.write_str(SoftAsciiStr::from_str("A23456789:").unwrap()));
1200                handle.mark_fws_pos();
1201                assert_ok!(handle.write_str(SoftAsciiStr::from_str(concat!(
1202                    "10_3456789",
1203                    "20_3456789",
1204                    "30_3456789",
1205                    "40_3456789",
1206                    "50_3456789",
1207                    "60_3456789",
1208                    "70_3456789",
1209                    "80_3456789",
1210                    "90_3456789",
1211                    "00_3456789",
1212                )).unwrap()));
1213                handle.finish_header();
1214            }
1215            assert_eq!(
1216                encoder.as_str().unwrap(),
1217                concat!(
1218                    "A23456789:\r\n ",
1219                    "10_3456789",
1220                    "20_3456789",
1221                    "30_3456789",
1222                    "40_3456789",
1223                    "50_3456789",
1224                    "60_3456789",
1225                    "70_3456789",
1226                    "80_3456789",
1227                    "90_3456789",
1228                    "00_3456789\r\n",
1229                )
1230            );
1231        }
1232
1233        #[test]
1234        fn multiple_lines_breaks() {
1235            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1236            {
1237                let mut handle = encoder.writer();
1238                assert_ok!(handle.write_str(SoftAsciiStr::from_str("A23456789:").unwrap()));
1239                handle.mark_fws_pos();
1240                assert_ok!(handle.write_str(SoftAsciiStr::from_str(concat!(
1241                    "10_3456789",
1242                    "20_3456789",
1243                    "30_3456789",
1244                    "40_3456789",
1245                    "50_3456789",
1246                    "60_3456789",
1247                    "70_3456789",
1248                )).unwrap()));
1249                handle.mark_fws_pos();
1250                assert_ok!(handle.write_str(SoftAsciiStr::from_str(concat!(
1251                    "10_3456789",
1252                    "20_3456789",
1253                    "30_3456789",
1254                    "40_3456789",
1255                )).unwrap()));
1256                handle.finish_header();
1257            }
1258            assert_eq!(
1259                encoder.as_str().unwrap(),
1260                concat!(
1261                    "A23456789:\r\n ",
1262                    "10_3456789",
1263                    "20_3456789",
1264                    "30_3456789",
1265                    "40_3456789",
1266                    "50_3456789",
1267                    "60_3456789",
1268                    "70_3456789\r\n ",
1269                    "10_3456789",
1270                    "20_3456789",
1271                    "30_3456789",
1272                    "40_3456789\r\n",
1273                )
1274            );
1275        }
1276
1277        #[test]
1278        fn hard_line_limit() {
1279            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1280            {
1281                let mut handle = encoder.writer();
1282                for x in 0..998 {
1283                    if let Err(_) = handle.write_char(SoftAsciiChar::from_unchecked('X')) {
1284                        panic!("error when writing char nr.: {:?}", x+1)
1285                    }
1286                }
1287                let res = &[
1288                    handle.write_char(SoftAsciiChar::from_unchecked('X')).is_err(),
1289                    handle.write_char(SoftAsciiChar::from_unchecked('X')).is_err(),
1290                    handle.write_char(SoftAsciiChar::from_unchecked('X')).is_err(),
1291                    handle.write_char(SoftAsciiChar::from_unchecked('X')).is_err(),
1292                ];
1293                assert_eq!(
1294                    res, &[true, true, true, true]
1295                );
1296                handle.undo_header();
1297            }
1298        }
1299
1300        #[test]
1301        fn write_utf8_fail_on_ascii_mail() {
1302            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1303            {
1304                let mut handle = encoder.writer();
1305                assert_err!(handle.write_utf8("↓"));
1306                handle.undo_header();
1307            }
1308        }
1309
1310        #[test]
1311        fn write_utf8_ascii_string_fail_on_ascii_mail() {
1312            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1313            {
1314                let mut handle = encoder.writer();
1315                assert_err!(handle.write_utf8("just_ascii"));
1316                handle.undo_header();
1317            }
1318        }
1319
1320        #[test]
1321        fn write_utf8_ok_on_internationalized_mail() {
1322            let mut encoder = EncodingBuffer::new(MailType::Internationalized);
1323            {
1324                let mut handle = encoder.writer();
1325                assert_ok!(handle.write_utf8("❤"));
1326                handle.finish_header();
1327            }
1328            assert_eq!(encoder.as_str().unwrap(), "❤\r\n");
1329        }
1330
1331        #[test]
1332        fn try_write_atext_ascii() {
1333            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1334            {
1335                let mut handle = encoder.writer();
1336                assert_ok!(handle.write_if_atext("hoho")
1337                    .handle_condition_failure(|_|panic!("no condition failur expected")));
1338                let mut had_cond_failure = false;
1339                assert_ok!(handle.write_if_atext("a(b")
1340                    .handle_condition_failure(|_| {had_cond_failure=true; Ok(())}));
1341                assert!(had_cond_failure);
1342                assert_ok!(handle.write_if_atext("")
1343                    .handle_condition_failure(|_|panic!("no condition failur expected")));
1344                handle.finish_header();
1345            }
1346            assert_eq!(encoder.as_slice(), b"hoho\r\n");
1347        }
1348
1349        #[test]
1350        fn try_write_atext_internationalized() {
1351            let mut encoder = EncodingBuffer::new(MailType::Internationalized);
1352            {
1353                let mut handle = encoder.writer();
1354                assert_ok!(handle.write_if_atext("hoho")
1355                    .handle_condition_failure(|_|panic!("no condition failur expected")));
1356                let mut had_cond_failure = false;
1357                assert_ok!(handle.write_if_atext("a(b")
1358                    .handle_condition_failure(|_| {had_cond_failure=true; Ok(())}));
1359                assert!(had_cond_failure);
1360                assert_ok!(handle.write_if_atext("❤")
1361                    .handle_condition_failure(|_|panic!("no condition failur expected")));
1362                handle.finish_header();
1363            }
1364            assert_eq!(encoder.as_str().unwrap(), "hoho❤\r\n");
1365        }
1366
1367        #[test]
1368        fn multiple_finish_calls_are_ok() {
1369            let mut encoder = EncodingBuffer::new(MailType::Internationalized);
1370            {
1371                let mut handle = encoder.writer();
1372                assert_ok!(handle.write_if_atext("hoho")
1373                    .handle_condition_failure(|_|panic!("no condition failur expected")));
1374                let mut had_cond_failure = false;
1375                assert_ok!(handle.write_if_atext("a(b")
1376                    .handle_condition_failure(|_| {had_cond_failure=true; Ok(())}));
1377                assert!(had_cond_failure);
1378                assert_ok!(handle.write_if_atext("❤")
1379                    .handle_condition_failure(|_|panic!("no condition failur expected")));
1380                handle.finish_header();
1381                handle.finish_header();
1382                handle.finish_header();
1383                handle.finish_header();
1384            }
1385            assert_eq!(encoder.as_str().unwrap(), "hoho❤\r\n");
1386        }
1387
1388        #[test]
1389        fn multiple_finish_and_undo_calls() {
1390            let mut encoder = EncodingBuffer::new(MailType::Internationalized);
1391            {
1392                let mut handle = encoder.writer();
1393                assert_ok!(handle.write_if_atext("hoho")
1394                    .handle_condition_failure(|_|panic!("no condition failur expected")));
1395                handle.undo_header();
1396                handle.finish_header();
1397                handle.undo_header();
1398                handle.undo_header();
1399            }
1400            assert_eq!(encoder.as_slice(), b"");
1401        }
1402
1403        #[test]
1404        fn header_body_header() {
1405            let mut encoder = EncodingBuffer::new(MailType::Internationalized);
1406            {
1407                let mut handle = encoder.writer();
1408                assert_ok!(handle.write_utf8("H: yay"));
1409                handle.finish_header();
1410            }
1411            encoder.write_body_unchecked(&"da body");
1412            {
1413                let mut handle = encoder.writer();
1414                assert_ok!(handle.write_utf8("❤"));
1415                handle.finish_header();
1416            }
1417            assert_eq!(
1418                encoder.as_slice(),
1419                concat!(
1420                    "H: yay\r\n",
1421                    "da body\r\n",
1422                    "❤\r\n"
1423                ).as_bytes()
1424            );
1425        }
1426
1427        #[test]
1428        fn has_unfinished_parts() {
1429            let mut encoder = EncodingBuffer::new(MailType::Internationalized);
1430            {
1431                let mut handle = encoder.writer();
1432                assert_ok!(handle.write_utf8("Abc:"));
1433                assert!(handle.has_unfinished_parts());
1434                handle.undo_header();
1435                assert_not!(handle.has_unfinished_parts());
1436                assert_ok!(handle.write_utf8("Abc: c"));
1437                assert!(handle.has_unfinished_parts());
1438                handle.finish_header();
1439                assert_not!(handle.has_unfinished_parts());
1440            }
1441        }
1442
1443        #[test]
1444        fn drop_without_write_is_ok() {
1445            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1446            let handle = encoder.writer();
1447            mem::drop(handle)
1448        }
1449
1450        #[test]
1451        fn drop_after_undo_is_ok() {
1452            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1453            let mut handle = encoder.writer();
1454            assert_ok!(handle.write_str(SoftAsciiStr::from_str("Header-One").unwrap()));
1455            handle.undo_header();
1456            mem::drop(handle);
1457        }
1458
1459        #[test]
1460        fn drop_after_finish_is_ok() {
1461            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1462            let mut handle = encoder.writer();
1463            assert_ok!(handle.write_str(SoftAsciiStr::from_str("Header-One: 12").unwrap()));
1464            handle.finish_header();
1465            mem::drop(handle);
1466        }
1467
1468        #[should_panic]
1469        #[test]
1470        fn drop_unfinished_panics() {
1471            let mut encoder = EncodingBuffer::new(MailType::Ascii);
1472            let mut handle = encoder.writer();
1473            assert_ok!(handle.write_str(SoftAsciiStr::from_str("Header-One:").unwrap()));
1474            mem::drop(handle);
1475        }
1476
1477        #[test]
1478        fn trace_and_undo() {
1479            let mut encoder = EncodingBuffer::new(MailType::Internationalized);
1480            {
1481                let mut handle = encoder.writer();
1482                assert_ok!(handle.write_utf8("something"));
1483                handle.mark_fws_pos();
1484                assert_ok!(handle.write_utf8("<else>"));
1485                handle.undo_header();
1486            }
1487            assert_eq!(encoder.trace.len(), 0);
1488        }
1489
1490        #[test]
1491        fn trace_and_undo_does_do_to_much() {
1492            let mut encoder = EncodingBuffer::new(MailType::Internationalized);
1493            {
1494                let mut handle = encoder.writer();
1495                assert_ok!(handle.write_utf8("H: a"));
1496                handle.finish_header();
1497                assert_ok!(handle.write_utf8("something"));
1498                handle.mark_fws_pos();
1499                assert_ok!(handle.write_utf8("<else>"));
1500                handle.undo_header();
1501            }
1502            assert_eq!(encoder.trace, vec![
1503                NowUtf8,
1504                Text("H: a".into()),
1505                CRLF,
1506                End
1507            ]);
1508        }
1509
1510        #[test]
1511        fn trace_traces() {
1512            let mut encoder = EncodingBuffer::new(MailType::Internationalized);
1513            {
1514                let mut handle = encoder.writer();
1515                assert_ok!(handle.write_str(SoftAsciiStr::from_str("Header").unwrap()));
1516                assert_ok!(handle.write_char(SoftAsciiChar::from_unchecked(':')));
1517                let mut had_cond_failure = false;
1518                assert_ok!(handle.write_if_atext("a(b)c")
1519                    .handle_condition_failure(|_|{had_cond_failure=true; Ok(())}));
1520                assert_ok!(handle.write_if_atext("abc")
1521                    .handle_condition_failure(|_|panic!("unexpected cond failure")));
1522                assert_ok!(handle.write_utf8("❤"));
1523                assert_ok!(handle.write_str_unchecked("remove me\r\n"));
1524                assert_ok!(handle.write_utf8("   "));
1525                handle.finish_header()
1526            }
1527            assert_eq!(encoder.trace, vec![
1528                NowStr,
1529                Text("Header".into()),
1530                NowChar,
1531                Text(":".into()),
1532                NowAText,
1533                Text("abc".into()),
1534                NowUtf8,
1535                Text("❤".into()),
1536                NowUnchecked,
1537                Text("remove me".into()),
1538                CRLF,
1539                NowUtf8,
1540                Text("   ".into()),
1541                TruncateToCRLF,
1542                End
1543            ]);
1544        }
1545
1546        #[test]
1547        fn with_handle_on_error() {
1548            let mut encoder = EncodingBuffer::new(MailType::Internationalized);
1549            let res = encoder.write_header_line(|hdl| {
1550                hdl.write_utf8("some partial writes")?;
1551                Err(EncodingErrorKind::Other { kind: "error ;=)" }.into())
1552            });
1553            assert_err!(res);
1554            assert_eq!(encoder.trace, vec![]);
1555            assert_eq!(encoder.as_slice(), b"");
1556        }
1557
1558        #[test]
1559        fn with_handle_partial_writes() {
1560            let mut encoder = EncodingBuffer::new(MailType::Internationalized);
1561            let res = encoder.write_header_line(|hdl| {
1562                hdl.write_utf8("X-A: 12")
1563            });
1564            assert_ok!(res);
1565            assert_eq!(encoder.trace, vec![
1566                NowUtf8,
1567                Text("X-A: 12".into()),
1568                CRLF,
1569                End
1570            ]);
1571            assert_eq!(encoder.as_slice(), b"X-A: 12\r\n");
1572        }
1573
1574        #[test]
1575        fn with_handle_ok() {
1576            let mut encoder = EncodingBuffer::new(MailType::Internationalized);
1577            let res = encoder.write_header_line(|hdl| {
1578                hdl.write_utf8("X-A: 12")?;
1579                hdl.finish_header();
1580                Ok(())
1581            });
1582            assert_ok!(res);
1583            assert_eq!(encoder.trace, vec![
1584                NowUtf8,
1585                Text("X-A: 12".into()),
1586                CRLF,
1587                End,
1588            ]);
1589            assert_eq!(encoder.as_slice(), b"X-A: 12\r\n")
1590        }
1591
1592        #[test]
1593        fn douple_write_fws() {
1594            let mut encoder = EncodingBuffer::new(MailType::Internationalized);
1595            let res = encoder.write_header_line(|hdl| {
1596                hdl.write_fws();
1597                hdl.write_fws();
1598                Ok(())
1599            });
1600            assert_ok!(res);
1601            assert_eq!(encoder.trace, vec![
1602                MarkFWS, NowChar, Text(" ".to_owned()),
1603                MarkFWS, NowChar, Text(" ".to_owned()),
1604                TruncateToCRLF,
1605                End
1606            ]);
1607            assert_eq!(encoder.as_slice(), b"")
1608        }
1609
1610        #[test]
1611        fn double_write_fws_then_long_line() {
1612            let long_line = concat!(
1613                "10_3456789",
1614                "20_3456789",
1615                "30_3456789",
1616                "40_3456789",
1617                "50_3456789",
1618                "60_3456789",
1619                "70_3456789",
1620                "80_3456789",
1621            );
1622            let mut encoder = EncodingBuffer::new(MailType::Internationalized);
1623            let res = encoder.write_header_line(|hdl| {
1624                hdl.write_fws();
1625                hdl.write_fws();
1626                hdl.write_utf8(long_line)?;
1627                Ok(())
1628            });
1629            assert_ok!(res);
1630            assert_eq!(encoder.trace, vec![
1631                MarkFWS, NowChar, Text(" ".to_owned()),
1632                MarkFWS, NowChar, Text(" ".to_owned()),
1633                NowUtf8, Text(long_line.to_owned()),
1634                CRLF,
1635                End
1636            ]);
1637            assert_eq!(encoder.as_slice(), format!("  {}\r\n", long_line).as_bytes())
1638        }
1639
1640        #[test]
1641        fn semantic_ws_are_not_eaten_with_line_breaking() {
1642            let long_line_1 = concat!(
1643                "Header:789",
1644                "20_3456789",
1645                "30_3456789",
1646                "40_3456789",
1647                "50_3456789",
1648                "60_3456789",
1649            );
1650            let long_line_2 = concat!(
1651                " xxxxxxxxx",
1652                "80_3456789"
1653            );
1654
1655            let expected_res = concat!(
1656                "Header:789",
1657                "20_3456789",
1658                "30_3456789",
1659                "40_3456789",
1660                "50_3456789",
1661                "60_3456789",
1662                "\r\n ",
1663                " xxxxxxxxx",
1664                "80_3456789",
1665                "\r\n"
1666            );
1667
1668            let mut encoder = EncodingBuffer::new(MailType::Internationalized);
1669            encoder.write_header_line(|hdl| {
1670                hdl.write_utf8(long_line_1).unwrap();
1671                hdl.mark_fws_pos();
1672                hdl.write_utf8(long_line_2).unwrap();
1673                hdl.finish_header();
1674                Ok(())
1675            }).unwrap();
1676
1677            let got = str::from_utf8(encoder.as_slice()).unwrap();
1678            assert_eq!(expected_res, got);
1679        }
1680    }
1681
1682    ec_test! {
1683        does_ec_test_work,
1684        {
1685            use super::EncodingWriter;
1686            enc_func!(|x: &mut EncodingWriter| {
1687                x.write_utf8("hy")
1688            })
1689        } => Utf8 => [
1690            Text "hy"
1691        ]
1692    }
1693
1694    ec_test! {
1695        does_ec_test_work_with_encode_closure,
1696        {
1697            use super::EncodingWriter;
1698            let think = "hy";
1699            enc_closure!(move |x: &mut EncodingWriter| {
1700                x.write_utf8(think)
1701            })
1702        } => Utf8 => [
1703            Text "hy"
1704        ]
1705    }
1706
1707    ec_test! {
1708        does_ec_test_allow_early_return,
1709        {
1710            use super::EncodingWriter;
1711            // this is just a type system test, if it compiles it can bail
1712            if false { ec_bail!(kind: Other { kind: "if false ..." }) }
1713            enc_func!(|x: &mut EncodingWriter| {
1714                x.write_utf8("hy")
1715            })
1716        } => Utf8 => [
1717            Text "hy"
1718        ]
1719    }
1720
1721    mod trait_object {
1722        use super::super::*;
1723
1724        #[derive(Default, Clone, PartialEq, Debug)]
1725        struct TestType(&'static str);
1726
1727        impl EncodableInHeader for TestType {
1728            fn encode(&self, encoder:  &mut EncodingWriter) -> Result<(), EncodingError> {
1729                encoder.write_utf8(self.0)
1730            }
1731
1732            fn boxed_clone(&self) -> Box<EncodableInHeader> {
1733                Box::new(self.clone())
1734            }
1735        }
1736
1737        #[derive(Default, Clone, PartialEq, Debug)]
1738        struct AnotherType(&'static str);
1739
1740        impl EncodableInHeader for AnotherType {
1741            fn encode(&self, encoder:  &mut EncodingWriter) -> Result<(), EncodingError> {
1742                encoder.write_utf8(self.0)
1743            }
1744
1745            fn boxed_clone(&self) -> Box<EncodableInHeader> {
1746                Box::new(self.clone())
1747            }
1748        }
1749
1750        #[test]
1751        fn is() {
1752            let tt = TestType::default();
1753            let erased: &EncodableInHeader = &tt;
1754            assert_eq!( true, erased.is::<TestType>() );
1755            assert_eq!( false, erased.is::<AnotherType>());
1756        }
1757
1758        #[test]
1759        fn downcast_ref() {
1760            let tt = TestType::default();
1761            let erased: &EncodableInHeader = &tt;
1762            let res: Option<&TestType> = erased.downcast_ref::<TestType>();
1763            assert_eq!( Some(&tt), res );
1764            assert_eq!( None, erased.downcast_ref::<AnotherType>() );
1765        }
1766
1767        #[test]
1768        fn downcast_mut() {
1769            let mut tt_nr2 = TestType::default();
1770            let mut tt = TestType::default();
1771            let erased: &mut EncodableInHeader = &mut tt;
1772            {
1773                let res: Option<&mut TestType> = erased.downcast_mut::<TestType>();
1774                assert_eq!( Some(&mut tt_nr2), res );
1775            }
1776            assert_eq!( None, erased.downcast_mut::<AnotherType>() );
1777        }
1778
1779        #[test]
1780        fn downcast() {
1781            let tt = Box::new( TestType::default() );
1782            let erased: Box<EncodableInHeader> = tt;
1783            let erased = assert_err!(erased.downcast::<AnotherType>());
1784            let _: Box<TestType> = assert_ok!(erased.downcast::<TestType>());
1785        }
1786    }
1787}