mirror of
https://github.com/golang/go
synced 2024-11-22 04:44:39 -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/multipart\
|
||||
net\
|
||||
net/dict\
|
||||
net/textproto\
|
||||
netchan\
|
||||
nntp\
|
||||
once\
|
||||
@ -139,6 +141,7 @@ NOTEST=\
|
||||
http/pprof\
|
||||
image\
|
||||
image/jpeg\
|
||||
net/dict\
|
||||
rand\
|
||||
runtime\
|
||||
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