Home | History | Annotate | Download | only in textproto
      1 // Copyright 2010 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 textproto
      6 
      7 import (
      8 	"bufio"
      9 	"fmt"
     10 	"io"
     11 )
     12 
     13 // A Writer implements convenience methods for writing
     14 // requests or responses to a text protocol network connection.
     15 type Writer struct {
     16 	W   *bufio.Writer
     17 	dot *dotWriter
     18 }
     19 
     20 // NewWriter returns a new Writer writing to w.
     21 func NewWriter(w *bufio.Writer) *Writer {
     22 	return &Writer{W: w}
     23 }
     24 
     25 var crnl = []byte{'\r', '\n'}
     26 var dotcrnl = []byte{'.', '\r', '\n'}
     27 
     28 // PrintfLine writes the formatted output followed by \r\n.
     29 func (w *Writer) PrintfLine(format string, args ...interface{}) error {
     30 	w.closeDot()
     31 	fmt.Fprintf(w.W, format, args...)
     32 	w.W.Write(crnl)
     33 	return w.W.Flush()
     34 }
     35 
     36 // DotWriter returns a writer that can be used to write a dot-encoding to w.
     37 // It takes care of inserting leading dots when necessary,
     38 // translating line-ending \n into \r\n, and adding the final .\r\n line
     39 // when the DotWriter is closed. The caller should close the
     40 // DotWriter before the next call to a method on w.
     41 //
     42 // See the documentation for Reader's DotReader method for details about dot-encoding.
     43 func (w *Writer) DotWriter() io.WriteCloser {
     44 	w.closeDot()
     45 	w.dot = &dotWriter{w: w}
     46 	return w.dot
     47 }
     48 
     49 func (w *Writer) closeDot() {
     50 	if w.dot != nil {
     51 		w.dot.Close() // sets w.dot = nil
     52 	}
     53 }
     54 
     55 type dotWriter struct {
     56 	w     *Writer
     57 	state int
     58 }
     59 
     60 const (
     61 	wstateBeginLine = iota // beginning of line; initial state; must be zero
     62 	wstateCR               // wrote \r (possibly at end of line)
     63 	wstateData             // writing data in middle of line
     64 )
     65 
     66 func (d *dotWriter) Write(b []byte) (n int, err error) {
     67 	bw := d.w.W
     68 	for n < len(b) {
     69 		c := b[n]
     70 		switch d.state {
     71 		case wstateBeginLine:
     72 			d.state = wstateData
     73 			if c == '.' {
     74 				// escape leading dot
     75 				bw.WriteByte('.')
     76 			}
     77 			fallthrough
     78 
     79 		case wstateData:
     80 			if c == '\r' {
     81 				d.state = wstateCR
     82 			}
     83 			if c == '\n' {
     84 				bw.WriteByte('\r')
     85 				d.state = wstateBeginLine
     86 			}
     87 
     88 		case wstateCR:
     89 			d.state = wstateData
     90 			if c == '\n' {
     91 				d.state = wstateBeginLine
     92 			}
     93 		}
     94 		if err = bw.WriteByte(c); err != nil {
     95 			break
     96 		}
     97 		n++
     98 	}
     99 	return
    100 }
    101 
    102 func (d *dotWriter) Close() error {
    103 	if d.w.dot == d {
    104 		d.w.dot = nil
    105 	}
    106 	bw := d.w.W
    107 	switch d.state {
    108 	default:
    109 		bw.WriteByte('\r')
    110 		fallthrough
    111 	case wstateCR:
    112 		bw.WriteByte('\n')
    113 		fallthrough
    114 	case wstateBeginLine:
    115 		bw.Write(dotcrnl)
    116 	}
    117 	return bw.Flush()
    118 }
    119