// Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package textproto import ( "bufio" "fmt" "io" ) // A Writer implements convenience methods for writing // requests or responses to a text protocol network connection. type Writer struct { W *bufio.Writer dot *dotWriter } // NewWriter returns a new [Writer] writing to w. func NewWriter(w *bufio.Writer) *Writer { return &Writer{W: w} } var crnl = []byte{'\r', '\n'} var dotcrnl = []byte{'.', '\r', '\n'} // PrintfLine writes the formatted output followed by \r\n. func (w *Writer) PrintfLine(format string, args ...any) error { w.closeDot() fmt.Fprintf(w.W, format, args...) w.W.Write(crnl) return w.W.Flush() } // DotWriter returns a writer that can be used to write a dot-encoding to w. // It takes care of inserting leading dots when necessary, // translating line-ending \n into \r\n, and adding the final .\r\n line // when the DotWriter is closed. The caller should close the // DotWriter before the next call to a method on w. // // See the documentation for the [Reader.DotReader] method for details about dot-encoding. func (w *Writer) DotWriter() io.WriteCloser { w.closeDot() w.dot = &dotWriter{w: w} return w.dot } func (w *Writer) closeDot() { if w.dot != nil { w.dot.Close() // sets w.dot = nil } } type dotWriter struct { w *Writer state int } const ( wstateBegin = iota // initial state; must be zero wstateBeginLine // beginning of line wstateCR // wrote \r (possibly at end of line) wstateData // writing data in middle of line ) func (d *dotWriter) Write(b []byte) (n int, err error) { bw := d.w.W for n < len(b) { c := b[n] switch d.state { case wstateBegin, wstateBeginLine: d.state = wstateData if c == '.' { // escape leading dot bw.WriteByte('.') } fallthrough case wstateData: if c == '\r' { d.state = wstateCR } if c == '\n' { bw.WriteByte('\r') d.state = wstateBeginLine } case wstateCR: d.state = wstateData if c == '\n' { d.state = wstateBeginLine } } if err = bw.WriteByte(c); err != nil { break } n++ } return } func (d *dotWriter) Close() error { if d.w.dot == d { d.w.dot = nil } bw := d.w.W switch d.state { default: bw.WriteByte('\r') fallthrough case wstateCR: bw.WriteByte('\n') fallthrough case wstateBeginLine: bw.Write(dotcrnl) } return bw.Flush() }