github.com/likebike/go--@v0.0.0-20190911215757-0bd925d16e96/go/src/mime/quotedprintable/writer.go (about) 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 }