mirror of
https://github.com/golang/go
synced 2024-11-25 10:07:56 -07:00
net/textproto: new package, with example net/dict
Generic text-based network protcol library for SMTP-like protocols. HTTP and NNTP should be changed to use this package, and I expect that SMTP and POP3 will be able to use it too. R=cemeyer, nigeltao_golang, r CC=golang-dev, petar-m https://golang.org/cl/889041
This commit is contained in:
parent
18063d4686
commit
844a86317d
@ -95,6 +95,8 @@ DIRS=\
|
|||||||
mime\
|
mime\
|
||||||
mime/multipart\
|
mime/multipart\
|
||||||
net\
|
net\
|
||||||
|
net/dict\
|
||||||
|
net/textproto\
|
||||||
netchan\
|
netchan\
|
||||||
nntp\
|
nntp\
|
||||||
once\
|
once\
|
||||||
@ -139,6 +141,7 @@ NOTEST=\
|
|||||||
http/pprof\
|
http/pprof\
|
||||||
image\
|
image\
|
||||||
image/jpeg\
|
image/jpeg\
|
||||||
|
net/dict\
|
||||||
rand\
|
rand\
|
||||||
runtime\
|
runtime\
|
||||||
runtime/pprof\
|
runtime/pprof\
|
||||||
|
7
src/pkg/net/dict/Makefile
Normal file
7
src/pkg/net/dict/Makefile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
include ../../../Make.$(GOARCH)
|
||||||
|
|
||||||
|
TARG=net/dict
|
||||||
|
GOFILES=\
|
||||||
|
dict.go\
|
||||||
|
|
||||||
|
include ../../../Make.pkg
|
205
src/pkg/net/dict/dict.go
Normal file
205
src/pkg/net/dict/dict.go
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
// 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 dict implements the Dictionary Server Protocol
|
||||||
|
// as defined in RFC 2229.
|
||||||
|
package dict
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/vector"
|
||||||
|
"net/textproto"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Client represents a client connection to a dictionary server.
|
||||||
|
type Client struct {
|
||||||
|
text *textproto.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial returns a new client connected to a dictionary server at
|
||||||
|
// addr on the given network.
|
||||||
|
func Dial(network, addr string) (*Client, os.Error) {
|
||||||
|
text, err := textproto.Dial(network, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, _, err = text.ReadCodeLine(220)
|
||||||
|
if err != nil {
|
||||||
|
text.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Client{text: text}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the connection to the dictionary server.
|
||||||
|
func (c *Client) Close() os.Error {
|
||||||
|
return c.text.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Dict represents a dictionary available on the server.
|
||||||
|
type Dict struct {
|
||||||
|
Name string // short name of dictionary
|
||||||
|
Desc string // long description
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dicts returns a list of the dictionaries available on the server.
|
||||||
|
func (c *Client) Dicts() ([]Dict, os.Error) {
|
||||||
|
id, err := c.text.Cmd("SHOW DB")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.text.StartResponse(id)
|
||||||
|
defer c.text.EndResponse(id)
|
||||||
|
|
||||||
|
lines, err := c.text.ReadDotLines()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, _, err = c.text.ReadCodeLine(250)
|
||||||
|
|
||||||
|
dicts := make([]Dict, len(lines))
|
||||||
|
for i := range dicts {
|
||||||
|
d := &dicts[i]
|
||||||
|
a, _ := fields(lines[i])
|
||||||
|
if len(a) < 2 {
|
||||||
|
return nil, textproto.ProtocolError("invalid dictionary: " + lines[i])
|
||||||
|
}
|
||||||
|
d.Name = a[0]
|
||||||
|
d.Desc = a[1]
|
||||||
|
}
|
||||||
|
return dicts, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Defn represents a definition.
|
||||||
|
type Defn struct {
|
||||||
|
Dict Dict // Dict where definition was found
|
||||||
|
Word string // Word being defined
|
||||||
|
Text []byte // Definition text, typically multiple lines
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define requests the definition of the given word.
|
||||||
|
// The argument dict names the dictionary to use,
|
||||||
|
// the Name field of a Dict returned by Dicts.
|
||||||
|
//
|
||||||
|
// The special dictionary name "!" means to look in all the
|
||||||
|
// server's dictionaries.
|
||||||
|
// The special dictionary name "*" means to look in all the
|
||||||
|
// server's dictionaries in turn, stopping after finding the word
|
||||||
|
// in one of them.
|
||||||
|
func (c *Client) Define(dict, word string) ([]*Defn, os.Error) {
|
||||||
|
id, err := c.text.Cmd("DEFINE %s %q", dict, word)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.text.StartResponse(id)
|
||||||
|
defer c.text.EndResponse(id)
|
||||||
|
|
||||||
|
_, line, err := c.text.ReadCodeLine(150)
|
||||||
|
a, _ := fields(line)
|
||||||
|
if len(a) < 1 {
|
||||||
|
return nil, textproto.ProtocolError("malformed response: " + line)
|
||||||
|
}
|
||||||
|
n, err := strconv.Atoi(a[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, textproto.ProtocolError("invalid definition count: " + a[0])
|
||||||
|
}
|
||||||
|
def := make([]*Defn, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
_, line, err = c.text.ReadCodeLine(151)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a, _ := fields(line)
|
||||||
|
if len(a) < 3 {
|
||||||
|
// skip it, to keep protocol in sync
|
||||||
|
i--
|
||||||
|
n--
|
||||||
|
def = def[0:n]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d := &Defn{Word: a[0], Dict: Dict{a[1], a[2]}}
|
||||||
|
d.Text, err = c.text.ReadDotBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
def[i] = d
|
||||||
|
}
|
||||||
|
_, _, err = c.text.ReadCodeLine(250)
|
||||||
|
return def, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields returns the fields in s.
|
||||||
|
// Fields are space separated unquoted words
|
||||||
|
// or quoted with single or double quote.
|
||||||
|
func fields(s string) ([]string, os.Error) {
|
||||||
|
var v vector.StringVector
|
||||||
|
i := 0
|
||||||
|
for {
|
||||||
|
for i < len(s) && (s[i] == ' ' || s[i] == '\t') {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i >= len(s) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if s[i] == '"' || s[i] == '\'' {
|
||||||
|
q := s[i]
|
||||||
|
// quoted string
|
||||||
|
var j int
|
||||||
|
for j = i + 1; ; j++ {
|
||||||
|
if j >= len(s) {
|
||||||
|
return nil, textproto.ProtocolError("malformed quoted string")
|
||||||
|
}
|
||||||
|
if s[j] == '\\' {
|
||||||
|
j++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s[j] == q {
|
||||||
|
j++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.Push(unquote(s[i+1 : j-1]))
|
||||||
|
i = j
|
||||||
|
} else {
|
||||||
|
// atom
|
||||||
|
var j int
|
||||||
|
for j = i; j < len(s); j++ {
|
||||||
|
if s[j] == ' ' || s[j] == '\t' || s[j] == '\\' || s[j] == '"' || s[j] == '\'' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.Push(s[i:j])
|
||||||
|
i = j
|
||||||
|
}
|
||||||
|
if i < len(s) {
|
||||||
|
c := s[i]
|
||||||
|
if c != ' ' && c != '\t' {
|
||||||
|
return nil, textproto.ProtocolError("quotes not on word boundaries")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unquote(s string) string {
|
||||||
|
if strings.Index(s, "\\") < 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
b := []byte(s)
|
||||||
|
w := 0
|
||||||
|
for r := 0; r < len(b); r++ {
|
||||||
|
c := b[r]
|
||||||
|
if c == '\\' {
|
||||||
|
r++
|
||||||
|
c = b[r]
|
||||||
|
}
|
||||||
|
b[w] = c
|
||||||
|
w++
|
||||||
|
}
|
||||||
|
return string(b[0:w])
|
||||||
|
}
|
14
src/pkg/net/textproto/Makefile
Normal file
14
src/pkg/net/textproto/Makefile
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
include ../../../Make.$(GOARCH)
|
||||||
|
|
||||||
|
TARG=net/textproto
|
||||||
|
GOFILES=\
|
||||||
|
pipeline.go\
|
||||||
|
reader.go\
|
||||||
|
textproto.go\
|
||||||
|
writer.go\
|
||||||
|
|
||||||
|
include ../../../Make.pkg
|
117
src/pkg/net/textproto/pipeline.go
Normal file
117
src/pkg/net/textproto/pipeline.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
// 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 (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Pipeline manages a pipelined in-order request/response sequence.
|
||||||
|
//
|
||||||
|
// To use a Pipeline p to manage multiple clients on a connection,
|
||||||
|
// each client should run:
|
||||||
|
//
|
||||||
|
// id := p.Next() // take a number
|
||||||
|
//
|
||||||
|
// p.StartRequest(id) // wait for turn to send request
|
||||||
|
// «send request»
|
||||||
|
// p.EndRequest(id) // notify Pipeline that request is sent
|
||||||
|
//
|
||||||
|
// p.StartResponse(id) // wait for turn to read response
|
||||||
|
// «read response»
|
||||||
|
// p.EndResponse(id) // notify Pipeline that response is read
|
||||||
|
//
|
||||||
|
// A pipelined server can use the same calls to ensure that
|
||||||
|
// responses computed in parallel are written in the correct order.
|
||||||
|
type Pipeline struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
id uint
|
||||||
|
request sequencer
|
||||||
|
response sequencer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next id for a request/response pair.
|
||||||
|
func (p *Pipeline) Next() uint {
|
||||||
|
p.mu.Lock()
|
||||||
|
id := p.id
|
||||||
|
p.id++
|
||||||
|
p.mu.Unlock()
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartRequest blocks until it is time to send (or, if this is a server, receive)
|
||||||
|
// the request with the given id.
|
||||||
|
func (p *Pipeline) StartRequest(id uint) {
|
||||||
|
p.request.Start(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndRequest notifies p that the request with the given id has been sent
|
||||||
|
// (or, if this is a server, received).
|
||||||
|
func (p *Pipeline) EndRequest(id uint) {
|
||||||
|
p.request.End(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartResponse blocks until it is time to receive (or, if this is a server, send)
|
||||||
|
// the request with the given id.
|
||||||
|
func (p *Pipeline) StartResponse(id uint) {
|
||||||
|
p.response.Start(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndResponse notifies p that the response with the given id has been received
|
||||||
|
// (or, if this is a server, sent).
|
||||||
|
func (p *Pipeline) EndResponse(id uint) {
|
||||||
|
p.response.End(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A sequencer schedules a sequence of numbered events that must
|
||||||
|
// happen in order, one after the other. The event numbering must start
|
||||||
|
// at 0 and increment without skipping. The event number wraps around
|
||||||
|
// safely as long as there are not 2^32 simultaneous events pending.
|
||||||
|
type sequencer struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
id uint
|
||||||
|
wait map[uint]chan uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start waits until it is time for the event numbered id to begin.
|
||||||
|
// That is, except for the first event, it waits until End(id-1) has
|
||||||
|
// been called.
|
||||||
|
func (s *sequencer) Start(id uint) {
|
||||||
|
s.mu.Lock()
|
||||||
|
if s.id == id {
|
||||||
|
s.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c := make(chan uint)
|
||||||
|
if s.wait == nil {
|
||||||
|
s.wait = make(map[uint]chan uint)
|
||||||
|
}
|
||||||
|
s.wait[id] = c
|
||||||
|
s.mu.Unlock()
|
||||||
|
<-c
|
||||||
|
}
|
||||||
|
|
||||||
|
// End notifies the sequencer that the event numbered id has completed,
|
||||||
|
// allowing it to schedule the event numbered id+1. It is a run-time error
|
||||||
|
// to call End with an id that is not the number of the active event.
|
||||||
|
func (s *sequencer) End(id uint) {
|
||||||
|
s.mu.Lock()
|
||||||
|
if s.id != id {
|
||||||
|
panic("out of sync")
|
||||||
|
}
|
||||||
|
id++
|
||||||
|
s.id = id
|
||||||
|
if s.wait == nil {
|
||||||
|
s.wait = make(map[uint]chan uint)
|
||||||
|
}
|
||||||
|
c, ok := s.wait[id]
|
||||||
|
if ok {
|
||||||
|
s.wait[id] = nil, false
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
if ok {
|
||||||
|
c <- 1
|
||||||
|
}
|
||||||
|
}
|
452
src/pkg/net/textproto/reader.go
Normal file
452
src/pkg/net/textproto/reader.go
Normal file
@ -0,0 +1,452 @@
|
|||||||
|
// 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"
|
||||||
|
"bytes"
|
||||||
|
"container/vector"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BUG(rsc): To let callers manage exposure to denial of service
|
||||||
|
// attacks, Reader should allow them to set and reset a limit on
|
||||||
|
// the number of bytes read from the connection.
|
||||||
|
|
||||||
|
// A Reader implements convenience methods for reading requests
|
||||||
|
// or responses from a text protocol network connection.
|
||||||
|
type Reader struct {
|
||||||
|
R *bufio.Reader
|
||||||
|
dot *dotReader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader returns a new Reader reading from r.
|
||||||
|
func NewReader(r *bufio.Reader) *Reader {
|
||||||
|
return &Reader{R: r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadLine reads a single line from r,
|
||||||
|
// eliding the final \n or \r\n from the returned string.
|
||||||
|
func (r *Reader) ReadLine() (string, os.Error) {
|
||||||
|
line, err := r.ReadLineBytes()
|
||||||
|
return string(line), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadLineBytes is like ReadLine but returns a []byte instead of a string.
|
||||||
|
func (r *Reader) ReadLineBytes() ([]byte, os.Error) {
|
||||||
|
r.closeDot()
|
||||||
|
line, err := r.R.ReadBytes('\n')
|
||||||
|
n := len(line)
|
||||||
|
if n > 0 && line[n-1] == '\n' {
|
||||||
|
n--
|
||||||
|
if n > 0 && line[n-1] == '\r' {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return line[0:n], err
|
||||||
|
}
|
||||||
|
|
||||||
|
var space = []byte{' '}
|
||||||
|
|
||||||
|
// ReadContinuedLine reads a possibly continued line from r,
|
||||||
|
// eliding the final trailing ASCII white space.
|
||||||
|
// Lines after the first are considered continuations if they
|
||||||
|
// begin with a space or tab character. In the returned data,
|
||||||
|
// continuation lines are separated from the previous line
|
||||||
|
// only by a single space: the newline and leading white space
|
||||||
|
// are removed.
|
||||||
|
//
|
||||||
|
// For example, consider this input:
|
||||||
|
//
|
||||||
|
// Line 1
|
||||||
|
// continued...
|
||||||
|
// Line 2
|
||||||
|
//
|
||||||
|
// The first call to ReadContinuedLine will return "Line 1 continued..."
|
||||||
|
// and the second will return "Line 2".
|
||||||
|
//
|
||||||
|
// A line consisting of only white space is never continued.
|
||||||
|
//
|
||||||
|
func (r *Reader) ReadContinuedLine() (string, os.Error) {
|
||||||
|
line, err := r.ReadContinuedLineBytes()
|
||||||
|
return string(line), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim returns s with leading and trailing spaces and tabs removed.
|
||||||
|
// It does not assume Unicode or UTF-8.
|
||||||
|
func trim(s []byte) []byte {
|
||||||
|
i := 0
|
||||||
|
for i < len(s) && (s[i] == ' ' || s[i] == '\t') {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
n := len(s)
|
||||||
|
for n > i && (s[n-1] == ' ' || s[n-1] == '\t') {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
return s[i:n]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadContinuedLineBytes is like ReadContinuedLine but
|
||||||
|
// returns a []byte instead of a string.
|
||||||
|
func (r *Reader) ReadContinuedLineBytes() ([]byte, os.Error) {
|
||||||
|
// Read the first line.
|
||||||
|
line, err := r.ReadLineBytes()
|
||||||
|
if err != nil {
|
||||||
|
return line, err
|
||||||
|
}
|
||||||
|
if len(line) == 0 { // blank line - no continuation
|
||||||
|
return line, nil
|
||||||
|
}
|
||||||
|
line = trim(line)
|
||||||
|
|
||||||
|
// Look for a continuation line.
|
||||||
|
c, err := r.R.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
// Delay err until we read the byte next time.
|
||||||
|
return line, nil
|
||||||
|
}
|
||||||
|
if c != ' ' && c != '\t' {
|
||||||
|
// Not a continuation.
|
||||||
|
r.R.UnreadByte()
|
||||||
|
return line, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read continuation lines.
|
||||||
|
for {
|
||||||
|
// Consume leading spaces; one already gone.
|
||||||
|
for {
|
||||||
|
c, err = r.R.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if c != ' ' && c != '\t' {
|
||||||
|
r.R.UnreadByte()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var cont []byte
|
||||||
|
cont, err = r.ReadLineBytes()
|
||||||
|
cont = trim(cont)
|
||||||
|
line = bytes.Add(line, space)
|
||||||
|
line = bytes.Add(line, cont)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for leading space on next line.
|
||||||
|
if c, err = r.R.ReadByte(); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if c != ' ' && c != '\t' {
|
||||||
|
r.R.UnreadByte()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay error until next call.
|
||||||
|
if len(line) > 0 {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return line, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadCodeLine reads a response code line of the form
|
||||||
|
// code message
|
||||||
|
// where code is a 3-digit status code and the message
|
||||||
|
// extends to the rest of the line. An example of such a line is:
|
||||||
|
// 220 plan9.bell-labs.com ESMTP
|
||||||
|
//
|
||||||
|
// If the prefix of the status does not match the digits in expectCode,
|
||||||
|
// ReadCodeLine returns with err set to &Error{code, message}.
|
||||||
|
// For example, if expectCode is 31, an error will be returned if
|
||||||
|
// the status is not in the range [310,319].
|
||||||
|
//
|
||||||
|
// An expectCode <= 0 disables the check of the status code.
|
||||||
|
//
|
||||||
|
func (r *Reader) ReadCodeLine(expectCode int) (code int, message string, err os.Error) {
|
||||||
|
line, err := r.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(line) < 4 || line[3] != ' ' {
|
||||||
|
err = ProtocolError("short response: " + line)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
code, err = strconv.Atoi(line[0:3])
|
||||||
|
if err != nil || code < 100 {
|
||||||
|
err = ProtocolError("invalid response code: " + line)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message = line[4:]
|
||||||
|
if 1 <= expectCode && expectCode < 10 && code/100 != expectCode ||
|
||||||
|
10 <= expectCode && expectCode < 100 && code/10 != expectCode ||
|
||||||
|
100 <= expectCode && expectCode < 1000 && code != expectCode {
|
||||||
|
err = &Error{code, message}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DotReader returns a new Reader that satisfies Reads using the
|
||||||
|
// decoded text of a dot-encoded block read from r.
|
||||||
|
// The returned Reader is only valid until the next call
|
||||||
|
// to a method on r.
|
||||||
|
//
|
||||||
|
// Dot encoding is a common framing used for data blocks
|
||||||
|
// in text protcols like SMTP. The data consists of a sequence
|
||||||
|
// of lines, each of which ends in "\r\n". The sequence itself
|
||||||
|
// ends at a line containing just a dot: ".\r\n". Lines beginning
|
||||||
|
// with a dot are escaped with an additional dot to avoid
|
||||||
|
// looking like the end of the sequence.
|
||||||
|
//
|
||||||
|
// The decoded form returned by the Reader's Read method
|
||||||
|
// rewrites the "\r\n" line endings into the simpler "\n",
|
||||||
|
// removes leading dot escapes if present, and stops with error os.EOF
|
||||||
|
// after consuming (and discarding) the end-of-sequence line.
|
||||||
|
func (r *Reader) DotReader() io.Reader {
|
||||||
|
r.closeDot()
|
||||||
|
r.dot = &dotReader{r: r}
|
||||||
|
return r.dot
|
||||||
|
}
|
||||||
|
|
||||||
|
type dotReader struct {
|
||||||
|
r *Reader
|
||||||
|
state int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read satisfies reads by decoding dot-encoded data read from d.r.
|
||||||
|
func (d *dotReader) Read(b []byte) (n int, err os.Error) {
|
||||||
|
// Run data through a simple state machine to
|
||||||
|
// elide leading dots, rewrite trailing \r\n into \n,
|
||||||
|
// and detect ending .\r\n line.
|
||||||
|
const (
|
||||||
|
stateBeginLine = iota // beginning of line; initial state; must be zero
|
||||||
|
stateDot // read . at beginning of line
|
||||||
|
stateDotCR // read .\r at beginning of line
|
||||||
|
stateCR // read \r (possibly at end of line)
|
||||||
|
stateData // reading data in middle of line
|
||||||
|
stateEOF // reached .\r\n end marker line
|
||||||
|
)
|
||||||
|
br := d.r.R
|
||||||
|
for n < len(b) && d.state != stateEOF {
|
||||||
|
var c byte
|
||||||
|
c, err = br.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
if err == os.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch d.state {
|
||||||
|
case stateBeginLine:
|
||||||
|
if c == '.' {
|
||||||
|
d.state = stateDot
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c == '\r' {
|
||||||
|
d.state = stateCR
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d.state = stateData
|
||||||
|
|
||||||
|
case stateDot:
|
||||||
|
if c == '\r' {
|
||||||
|
d.state = stateDotCR
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c == '\n' {
|
||||||
|
d.state = stateEOF
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d.state = stateData
|
||||||
|
|
||||||
|
case stateDotCR:
|
||||||
|
if c == '\n' {
|
||||||
|
d.state = stateEOF
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Not part of .\r\n.
|
||||||
|
// Consume leading dot and emit saved \r.
|
||||||
|
br.UnreadByte()
|
||||||
|
c = '\r'
|
||||||
|
d.state = stateData
|
||||||
|
|
||||||
|
case stateCR:
|
||||||
|
if c == '\n' {
|
||||||
|
d.state = stateBeginLine
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Not part of \r\n. Emit saved \r
|
||||||
|
br.UnreadByte()
|
||||||
|
c = '\r'
|
||||||
|
d.state = stateData
|
||||||
|
|
||||||
|
case stateData:
|
||||||
|
if c == '\r' {
|
||||||
|
d.state = stateCR
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c == '\n' {
|
||||||
|
d.state = stateBeginLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b[n] = c
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
if err == nil && d.state == stateEOF {
|
||||||
|
err = os.EOF
|
||||||
|
}
|
||||||
|
if err != nil && d.r.dot == d {
|
||||||
|
d.r.dot = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeDot drains the current DotReader if any,
|
||||||
|
// making sure that it reads until the ending dot line.
|
||||||
|
func (r *Reader) closeDot() {
|
||||||
|
if r.dot == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf := make([]byte, 128)
|
||||||
|
for r.dot != nil {
|
||||||
|
// When Read reaches EOF or an error,
|
||||||
|
// it will set r.dot == nil.
|
||||||
|
r.dot.Read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadDotBytes reads a dot-encoding and returns the decoded data.
|
||||||
|
//
|
||||||
|
// See the documentation for the DotReader method for details about dot-encoding.
|
||||||
|
func (r *Reader) ReadDotBytes() ([]byte, os.Error) {
|
||||||
|
return ioutil.ReadAll(r.DotReader())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadDotLines reads a dot-encoding and returns a slice
|
||||||
|
// containing the decoded lines, with the final \r\n or \n elided from each.
|
||||||
|
//
|
||||||
|
// See the documentation for the DotReader method for details about dot-encoding.
|
||||||
|
func (r *Reader) ReadDotLines() ([]string, os.Error) {
|
||||||
|
// We could use ReadDotBytes and then Split it,
|
||||||
|
// but reading a line at a time avoids needing a
|
||||||
|
// large contiguous block of memory and is simpler.
|
||||||
|
var v vector.StringVector
|
||||||
|
var err os.Error
|
||||||
|
for {
|
||||||
|
var line string
|
||||||
|
line, err = r.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
if err == os.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dot by itself marks end; otherwise cut one dot.
|
||||||
|
if len(line) > 0 && line[0] == '.' {
|
||||||
|
if len(line) == 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
line = line[1:]
|
||||||
|
}
|
||||||
|
v.Push(line)
|
||||||
|
}
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadMIMEHeader reads a MIME-style header from r.
|
||||||
|
// The header is a sequence of possibly continued Key: Value lines
|
||||||
|
// ending in a blank line.
|
||||||
|
// The returned map m maps CanonicalHeaderKey(key) to a
|
||||||
|
// sequence of values in the same order encountered in the input.
|
||||||
|
//
|
||||||
|
// For example, consider this input:
|
||||||
|
//
|
||||||
|
// My-Key: Value 1
|
||||||
|
// Long-Key: Even
|
||||||
|
// Longer Value
|
||||||
|
// My-Key: Value 2
|
||||||
|
//
|
||||||
|
// Given that input, ReadMIMEHeader returns the map:
|
||||||
|
//
|
||||||
|
// map[string][]string{
|
||||||
|
// "My-Key": []string{"Value 1", "Value 2"},
|
||||||
|
// "Long-Key": []string{"Even Longer Value"},
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
func (r *Reader) ReadMIMEHeader() (map[string][]string, os.Error) {
|
||||||
|
m := make(map[string][]string)
|
||||||
|
for {
|
||||||
|
kv, err := r.ReadContinuedLineBytes()
|
||||||
|
if len(kv) == 0 {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key ends at first colon; must not have spaces.
|
||||||
|
i := bytes.IndexByte(kv, ':')
|
||||||
|
if i < 0 || bytes.IndexByte(kv[0:i], ' ') >= 0 {
|
||||||
|
return m, ProtocolError("malformed MIME header line: " + string(kv))
|
||||||
|
}
|
||||||
|
key := CanonicalHeaderKey(string(kv[0:i]))
|
||||||
|
|
||||||
|
// Skip initial spaces in value.
|
||||||
|
i++ // skip colon
|
||||||
|
for i < len(kv) && (kv[i] == ' ' || kv[i] == '\t') {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
value := string(kv[i:])
|
||||||
|
|
||||||
|
v := vector.StringVector(m[key])
|
||||||
|
v.Push(value)
|
||||||
|
m[key] = v
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanonicalHeaderKey returns the canonical format of the
|
||||||
|
// MIME header key s. The canonicalization converts the first
|
||||||
|
// letter and any letter following a hyphen to upper case;
|
||||||
|
// the rest are converted to lowercase. For example, the
|
||||||
|
// canonical key for "accept-encoding" is "Accept-Encoding".
|
||||||
|
func CanonicalHeaderKey(s string) string {
|
||||||
|
// Quick check for canonical encoding.
|
||||||
|
needUpper := true
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
if needUpper && 'a' <= c && c <= 'z' {
|
||||||
|
goto MustRewrite
|
||||||
|
}
|
||||||
|
if !needUpper && 'A' <= c && c <= 'Z' {
|
||||||
|
goto MustRewrite
|
||||||
|
}
|
||||||
|
needUpper = c == '-'
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
|
||||||
|
MustRewrite:
|
||||||
|
// Canonicalize: first letter upper case
|
||||||
|
// and upper case after each dash.
|
||||||
|
// (Host, User-Agent, If-Modified-Since).
|
||||||
|
// MIME headers are ASCII only, so no Unicode issues.
|
||||||
|
a := []byte(s)
|
||||||
|
upper := true
|
||||||
|
for i, v := range a {
|
||||||
|
if upper && 'a' <= v && v <= 'z' {
|
||||||
|
a[i] = v + 'A' - 'a'
|
||||||
|
}
|
||||||
|
if !upper && 'A' <= v && v <= 'Z' {
|
||||||
|
a[i] = v + 'a' - 'A'
|
||||||
|
}
|
||||||
|
upper = v == '-'
|
||||||
|
}
|
||||||
|
return string(a)
|
||||||
|
}
|
140
src/pkg/net/textproto/reader_test.go
Normal file
140
src/pkg/net/textproto/reader_test.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
// 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"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type canonicalHeaderKeyTest struct {
|
||||||
|
in, out string
|
||||||
|
}
|
||||||
|
|
||||||
|
var canonicalHeaderKeyTests = []canonicalHeaderKeyTest{
|
||||||
|
canonicalHeaderKeyTest{"a-b-c", "A-B-C"},
|
||||||
|
canonicalHeaderKeyTest{"a-1-c", "A-1-C"},
|
||||||
|
canonicalHeaderKeyTest{"User-Agent", "User-Agent"},
|
||||||
|
canonicalHeaderKeyTest{"uSER-aGENT", "User-Agent"},
|
||||||
|
canonicalHeaderKeyTest{"user-agent", "User-Agent"},
|
||||||
|
canonicalHeaderKeyTest{"USER-AGENT", "User-Agent"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCanonicalHeaderKey(t *testing.T) {
|
||||||
|
for _, tt := range canonicalHeaderKeyTests {
|
||||||
|
if s := CanonicalHeaderKey(tt.in); s != tt.out {
|
||||||
|
t.Errorf("CanonicalHeaderKey(%q) = %q, want %q", tt.in, s, tt.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func reader(s string) *Reader {
|
||||||
|
return NewReader(bufio.NewReader(strings.NewReader(s)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadLine(t *testing.T) {
|
||||||
|
r := reader("line1\nline2\n")
|
||||||
|
s, err := r.ReadLine()
|
||||||
|
if s != "line1" || err != nil {
|
||||||
|
t.Fatalf("Line 1: %s, %v", s, err)
|
||||||
|
}
|
||||||
|
s, err = r.ReadLine()
|
||||||
|
if s != "line2" || err != nil {
|
||||||
|
t.Fatalf("Line 2: %s, %v", s, err)
|
||||||
|
}
|
||||||
|
s, err = r.ReadLine()
|
||||||
|
if s != "" || err != os.EOF {
|
||||||
|
t.Fatalf("EOF: %s, %v", s, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadContinuedLine(t *testing.T) {
|
||||||
|
r := reader("line1\nline\n 2\nline3\n")
|
||||||
|
s, err := r.ReadContinuedLine()
|
||||||
|
if s != "line1" || err != nil {
|
||||||
|
t.Fatalf("Line 1: %s, %v", s, err)
|
||||||
|
}
|
||||||
|
s, err = r.ReadContinuedLine()
|
||||||
|
if s != "line 2" || err != nil {
|
||||||
|
t.Fatalf("Line 2: %s, %v", s, err)
|
||||||
|
}
|
||||||
|
s, err = r.ReadContinuedLine()
|
||||||
|
if s != "line3" || err != nil {
|
||||||
|
t.Fatalf("Line 3: %s, %v", s, err)
|
||||||
|
}
|
||||||
|
s, err = r.ReadContinuedLine()
|
||||||
|
if s != "" || err != os.EOF {
|
||||||
|
t.Fatalf("EOF: %s, %v", s, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadCodeLine(t *testing.T) {
|
||||||
|
r := reader("123 hi\n234 bye\n345 no way\n")
|
||||||
|
code, msg, err := r.ReadCodeLine(0)
|
||||||
|
if code != 123 || msg != "hi" || err != nil {
|
||||||
|
t.Fatalf("Line 1: %d, %s, %v", code, msg, err)
|
||||||
|
}
|
||||||
|
code, msg, err = r.ReadCodeLine(23)
|
||||||
|
if code != 234 || msg != "bye" || err != nil {
|
||||||
|
t.Fatalf("Line 2: %d, %s, %v", code, msg, err)
|
||||||
|
}
|
||||||
|
code, msg, err = r.ReadCodeLine(346)
|
||||||
|
if code != 345 || msg != "no way" || err == nil {
|
||||||
|
t.Fatalf("Line 3: %d, %s, %v", code, msg, err)
|
||||||
|
}
|
||||||
|
if e, ok := err.(*Error); !ok || e.Code != code || e.Msg != msg {
|
||||||
|
t.Fatalf("Line 3: wrong error %v\n", err)
|
||||||
|
}
|
||||||
|
code, msg, err = r.ReadCodeLine(1)
|
||||||
|
if code != 0 || msg != "" || err != os.EOF {
|
||||||
|
t.Fatalf("EOF: %d, %s, %v", code, msg, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadDotLines(t *testing.T) {
|
||||||
|
r := reader("dotlines\r\n.foo\r\n..bar\n...baz\nquux\r\n\r\n.\r\nanother\n")
|
||||||
|
s, err := r.ReadDotLines()
|
||||||
|
want := []string{"dotlines", "foo", ".bar", "..baz", "quux", ""}
|
||||||
|
if !reflect.DeepEqual(s, want) || err != nil {
|
||||||
|
t.Fatalf("ReadDotLines: %v, %v", s, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err = r.ReadDotLines()
|
||||||
|
want = []string{"another"}
|
||||||
|
if !reflect.DeepEqual(s, want) || err != io.ErrUnexpectedEOF {
|
||||||
|
t.Fatalf("ReadDotLines2: %v, %v", s, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadDotBytes(t *testing.T) {
|
||||||
|
r := reader("dotlines\r\n.foo\r\n..bar\n...baz\nquux\r\n\r\n.\r\nanot.her\r\n")
|
||||||
|
b, err := r.ReadDotBytes()
|
||||||
|
want := []byte("dotlines\nfoo\n.bar\n..baz\nquux\n\n")
|
||||||
|
if !reflect.DeepEqual(b, want) || err != nil {
|
||||||
|
t.Fatalf("ReadDotBytes: %q, %v", b, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err = r.ReadDotBytes()
|
||||||
|
want = []byte("anot.her\n")
|
||||||
|
if !reflect.DeepEqual(b, want) || err != io.ErrUnexpectedEOF {
|
||||||
|
t.Fatalf("ReadDotBytes2: %q, %v", b, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadMIMEHeader(t *testing.T) {
|
||||||
|
r := reader("my-key: Value 1 \r\nLong-key: Even \n Longer Value\r\nmy-Key: Value 2\r\n\n")
|
||||||
|
m, err := r.ReadMIMEHeader()
|
||||||
|
want := map[string][]string{
|
||||||
|
"My-Key": []string{"Value 1", "Value 2"},
|
||||||
|
"Long-Key": []string{"Even Longer Value"},
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(m, want) || err != nil {
|
||||||
|
t.Fatalf("ReadMIMEHeader: %v, %v; want %v", m, err, want)
|
||||||
|
}
|
||||||
|
}
|
122
src/pkg/net/textproto/textproto.go
Normal file
122
src/pkg/net/textproto/textproto.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// The textproto package implements generic support for
|
||||||
|
// text-based request/response protocols in the style of
|
||||||
|
// HTTP, NNTP, and SMTP.
|
||||||
|
//
|
||||||
|
// The package provides:
|
||||||
|
//
|
||||||
|
// Error, which represents a numeric error response from
|
||||||
|
// a server.
|
||||||
|
//
|
||||||
|
// Pipeline, to manage pipelined requests and responses
|
||||||
|
// in a client.
|
||||||
|
//
|
||||||
|
// Reader, to read numeric response code lines,
|
||||||
|
// key: value headers, lines wrapped with leading spaces
|
||||||
|
// on continuation lines, and whole text blocks ending
|
||||||
|
// with a dot on a line by itself.
|
||||||
|
//
|
||||||
|
// Writer, to write dot-encoded text blocks.
|
||||||
|
//
|
||||||
|
package textproto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An Error represents a numeric error response from a server.
|
||||||
|
type Error struct {
|
||||||
|
Code int
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) String() string {
|
||||||
|
return fmt.Sprintf("%03d %s", e.Code, e.Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A ProtocolError describes a protocol violation such
|
||||||
|
// as an invalid response or a hung-up connection.
|
||||||
|
type ProtocolError string
|
||||||
|
|
||||||
|
func (p ProtocolError) String() string {
|
||||||
|
return string(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Conn represents a textual network protocol connection.
|
||||||
|
// It consists of a Reader and Writer to manage I/O
|
||||||
|
// and a Pipeline to sequence concurrent requests on the connection.
|
||||||
|
// These embedded types carry methods with them;
|
||||||
|
// see the documentation of those types for details.
|
||||||
|
type Conn struct {
|
||||||
|
Reader
|
||||||
|
Writer
|
||||||
|
Pipeline
|
||||||
|
conn io.ReadWriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConn returns a new Conn using conn for I/O.
|
||||||
|
func NewConn(conn io.ReadWriteCloser) *Conn {
|
||||||
|
return &Conn{
|
||||||
|
Reader: Reader{R: bufio.NewReader(conn)},
|
||||||
|
Writer: Writer{W: bufio.NewWriter(conn)},
|
||||||
|
conn: conn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the connection.
|
||||||
|
func (c *Conn) Close() os.Error {
|
||||||
|
return c.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the given address on the given network using net.Dial
|
||||||
|
// and then returns a new Conn for the connection.
|
||||||
|
func Dial(network, addr string) (*Conn, os.Error) {
|
||||||
|
c, err := net.Dial(network, "", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewConn(c), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cmd is a convenience method that sends a command after
|
||||||
|
// waiting its turn in the pipeline. The command text is the
|
||||||
|
// result of formatting format with args and appending \r\n.
|
||||||
|
// Cmd returns the id of the command, for use with StartResponse and EndResponse.
|
||||||
|
//
|
||||||
|
// For example, a client might run a HELP command that returns a dot-body
|
||||||
|
// by using:
|
||||||
|
//
|
||||||
|
// id, err := c.Cmd("HELP")
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// c.StartResponse(id)
|
||||||
|
// defer c.EndResponse(id)
|
||||||
|
//
|
||||||
|
// if _, _, err = c.ReadCodeLine(110); err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// text, err := c.ReadDotAll()
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// return c.ReadCodeLine(250)
|
||||||
|
//
|
||||||
|
func (c *Conn) Cmd(format string, args ...interface{}) (id uint, err os.Error) {
|
||||||
|
id = c.Next()
|
||||||
|
c.StartRequest(id)
|
||||||
|
err = c.PrintfLine(format, args)
|
||||||
|
c.EndRequest(id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
119
src/pkg/net/textproto/writer.go
Normal file
119
src/pkg/net/textproto/writer.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
// 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"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 ...interface{}) os.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 Reader's 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 (
|
||||||
|
wstateBeginLine = iota // beginning of line; initial state; must be zero
|
||||||
|
wstateCR // wrote \r (possibly at end of line)
|
||||||
|
wstateData // writing data in middle of line
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *dotWriter) Write(b []byte) (n int, err os.Error) {
|
||||||
|
bw := d.w.W
|
||||||
|
for n < len(b) {
|
||||||
|
c := b[n]
|
||||||
|
switch d.state {
|
||||||
|
case 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() os.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()
|
||||||
|
}
|
35
src/pkg/net/textproto/writer_test.go
Normal file
35
src/pkg/net/textproto/writer_test.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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"
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrintfLine(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
w := NewWriter(bufio.NewWriter(&buf))
|
||||||
|
err := w.PrintfLine("foo %d", 123)
|
||||||
|
if s := buf.String(); s != "foo 123\r\n" || err != nil {
|
||||||
|
t.Fatalf("s=%q; err=%s", s, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDotWriter(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
w := NewWriter(bufio.NewWriter(&buf))
|
||||||
|
d := w.DotWriter()
|
||||||
|
n, err := d.Write([]byte("abc\n.def\n..ghi\n.jkl\n."))
|
||||||
|
if n != 21 || err != nil {
|
||||||
|
t.Fatalf("Write: %d, %s", n, err)
|
||||||
|
}
|
||||||
|
d.Close()
|
||||||
|
want := "abc\r\n..def\r\n...ghi\r\n..jkl\r\n..\r\n.\r\n"
|
||||||
|
if s := buf.String(); s != want {
|
||||||
|
t.Fatalf("wrote %q", s)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user