Source file src/mime/quotedprintable/writer.go

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package quotedprintable
     6  
     7  import "io"
     8  
     9  const lineMaxLen = 76
    10  
    11  // A Writer is a quoted-printable writer that implements io.WriteCloser.
    12  type Writer struct {
    13  	// Binary mode treats the writer's input as pure binary and processes end of
    14  	// line bytes as binary data.
    15  	Binary bool
    16  
    17  	w    io.Writer
    18  	i    int
    19  	line [78]byte
    20  	cr   bool
    21  }
    22  
    23  // NewWriter returns a new Writer that writes to w.
    24  func NewWriter(w io.Writer) *Writer {
    25  	return &Writer{w: w}
    26  }
    27  
    28  // Write encodes p using quoted-printable encoding and writes it to the
    29  // underlying io.Writer. It limits line length to 76 characters. The encoded
    30  // bytes are not necessarily flushed until the Writer is closed.
    31  func (w *Writer) Write(p []byte) (n int, err error) {
    32  	for i, b := range p {
    33  		switch {
    34  		// Simple writes are done in batch.
    35  		case b >= '!' && b <= '~' && b != '=':
    36  			continue
    37  		case isWhitespace(b) || !w.Binary && (b == '\n' || b == '\r'):
    38  			continue
    39  		}
    40  
    41  		if i > n {
    42  			if err := w.write(p[n:i]); err != nil {
    43  				return n, err
    44  			}
    45  			n = i
    46  		}
    47  
    48  		if err := w.encode(b); err != nil {
    49  			return n, err
    50  		}
    51  		n++
    52  	}
    53  
    54  	if n == len(p) {
    55  		return n, nil
    56  	}
    57  
    58  	if err := w.write(p[n:]); err != nil {
    59  		return n, err
    60  	}
    61  
    62  	return len(p), nil
    63  }
    64  
    65  // Close closes the Writer, flushing any unwritten data to the underlying
    66  // io.Writer, but does not close the underlying io.Writer.
    67  func (w *Writer) Close() error {
    68  	if err := w.checkLastByte(); err != nil {
    69  		return err
    70  	}
    71  
    72  	return w.flush()
    73  }
    74  
    75  // write limits text encoded in quoted-printable to 76 characters per line.
    76  func (w *Writer) write(p []byte) error {
    77  	for _, b := range p {
    78  		if b == '\n' || b == '\r' {
    79  			// If the previous byte was \r, the CRLF has already been inserted.
    80  			if w.cr && b == '\n' {
    81  				w.cr = false
    82  				continue
    83  			}
    84  
    85  			if b == '\r' {
    86  				w.cr = true
    87  			}
    88  
    89  			if err := w.checkLastByte(); err != nil {
    90  				return err
    91  			}
    92  			if err := w.insertCRLF(); err != nil {
    93  				return err
    94  			}
    95  			continue
    96  		}
    97  
    98  		if w.i == lineMaxLen-1 {
    99  			if err := w.insertSoftLineBreak(); err != nil {
   100  				return err
   101  			}
   102  		}
   103  
   104  		w.line[w.i] = b
   105  		w.i++
   106  		w.cr = false
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  func (w *Writer) encode(b byte) error {
   113  	if lineMaxLen-1-w.i < 3 {
   114  		if err := w.insertSoftLineBreak(); err != nil {
   115  			return err
   116  		}
   117  	}
   118  
   119  	w.line[w.i] = '='
   120  	w.line[w.i+1] = upperhex[b>>4]
   121  	w.line[w.i+2] = upperhex[b&0x0f]
   122  	w.i += 3
   123  
   124  	return nil
   125  }
   126  
   127  const upperhex = "0123456789ABCDEF"
   128  
   129  // checkLastByte encodes the last buffered byte if it is a space or a tab.
   130  func (w *Writer) checkLastByte() error {
   131  	if w.i == 0 {
   132  		return nil
   133  	}
   134  
   135  	b := w.line[w.i-1]
   136  	if isWhitespace(b) {
   137  		w.i--
   138  		if err := w.encode(b); err != nil {
   139  			return err
   140  		}
   141  	}
   142  
   143  	return nil
   144  }
   145  
   146  func (w *Writer) insertSoftLineBreak() error {
   147  	w.line[w.i] = '='
   148  	w.i++
   149  
   150  	return w.insertCRLF()
   151  }
   152  
   153  func (w *Writer) insertCRLF() error {
   154  	w.line[w.i] = '\r'
   155  	w.line[w.i+1] = '\n'
   156  	w.i += 2
   157  
   158  	return w.flush()
   159  }
   160  
   161  func (w *Writer) flush() error {
   162  	if _, err := w.w.Write(w.line[:w.i]); err != nil {
   163  		return err
   164  	}
   165  
   166  	w.i = 0
   167  	return nil
   168  }
   169  
   170  func isWhitespace(b byte) bool {
   171  	return b == ' ' || b == '\t'
   172  }
   173  

View as plain text