diff options
Diffstat (limited to 'src/pkg/net/textproto')
-rw-r--r-- | src/pkg/net/textproto/Makefile | 15 | ||||
-rw-r--r-- | src/pkg/net/textproto/header.go | 43 | ||||
-rw-r--r-- | src/pkg/net/textproto/pipeline.go | 117 | ||||
-rw-r--r-- | src/pkg/net/textproto/reader.go | 492 | ||||
-rw-r--r-- | src/pkg/net/textproto/reader_test.go | 140 | ||||
-rw-r--r-- | src/pkg/net/textproto/textproto.go | 121 | ||||
-rw-r--r-- | src/pkg/net/textproto/writer.go | 119 | ||||
-rw-r--r-- | src/pkg/net/textproto/writer_test.go | 35 |
8 files changed, 0 insertions, 1082 deletions
diff --git a/src/pkg/net/textproto/Makefile b/src/pkg/net/textproto/Makefile deleted file mode 100644 index cadf3ab69..000000000 --- a/src/pkg/net/textproto/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -# 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.inc - -TARG=net/textproto -GOFILES=\ - header.go\ - pipeline.go\ - reader.go\ - textproto.go\ - writer.go\ - -include ../../../Make.pkg diff --git a/src/pkg/net/textproto/header.go b/src/pkg/net/textproto/header.go deleted file mode 100644 index 288deb2ce..000000000 --- a/src/pkg/net/textproto/header.go +++ /dev/null @@ -1,43 +0,0 @@ -// 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 - -// A MIMEHeader represents a MIME-style header mapping -// keys to sets of values. -type MIMEHeader map[string][]string - -// Add adds the key, value pair to the header. -// It appends to any existing values associated with key. -func (h MIMEHeader) Add(key, value string) { - key = CanonicalMIMEHeaderKey(key) - h[key] = append(h[key], value) -} - -// Set sets the header entries associated with key to -// the single element value. It replaces any existing -// values associated with key. -func (h MIMEHeader) Set(key, value string) { - h[CanonicalMIMEHeaderKey(key)] = []string{value} -} - -// Get gets the first value associated with the given key. -// If there are no values associated with the key, Get returns "". -// Get is a convenience method. For more complex queries, -// access the map directly. -func (h MIMEHeader) Get(key string) string { - if h == nil { - return "" - } - v := h[CanonicalMIMEHeaderKey(key)] - if len(v) == 0 { - return "" - } - return v[0] -} - -// Del deletes the values associated with key. -func (h MIMEHeader) Del(key string) { - h[CanonicalMIMEHeaderKey(key)] = nil, false -} diff --git a/src/pkg/net/textproto/pipeline.go b/src/pkg/net/textproto/pipeline.go deleted file mode 100644 index 8c25884b3..000000000 --- a/src/pkg/net/textproto/pipeline.go +++ /dev/null @@ -1,117 +0,0 @@ -// 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 - } -} diff --git a/src/pkg/net/textproto/reader.go b/src/pkg/net/textproto/reader.go deleted file mode 100644 index e65374903..000000000 --- a/src/pkg/net/textproto/reader.go +++ /dev/null @@ -1,492 +0,0 @@ -// 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 -} - -// 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 = append(line, ' ') - line = append(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 -} - -func (r *Reader) readCodeLine(expectCode int) (code int, continued bool, message string, err os.Error) { - line, err := r.ReadLine() - if err != nil { - return - } - if len(line) < 4 || line[3] != ' ' && line[3] != '-' { - err = ProtocolError("short response: " + line) - return - } - continued = line[3] == '-' - 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 -} - -// 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]. -// -// If the response is multi-line, ReadCodeLine returns an error. -// -// An expectCode <= 0 disables the check of the status code. -// -func (r *Reader) ReadCodeLine(expectCode int) (code int, message string, err os.Error) { - code, continued, message, err := r.readCodeLine(expectCode) - if err == nil && continued { - err = ProtocolError("unexpected multi-line response: " + message) - } - return -} - -// ReadResponse reads a multi-line response of the form -// code-message line 1 -// code-message line 2 -// ... -// code message line n -// where code is a 3-digit status code. Each line should have the same code. -// The response is terminated by a line that uses a space between the code and -// the message line rather than a dash. Each line in message is separated by -// a newline (\n). -// -// If the prefix of the status does not match the digits in expectCode, -// ReadResponse 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) ReadResponse(expectCode int) (code int, message string, err os.Error) { - code, continued, message, err := r.readCodeLine(expectCode) - for err == nil && continued { - var code2 int - var moreMessage string - code2, continued, moreMessage, err = r.readCodeLine(expectCode) - if code != code2 { - err = ProtocolError("status code mismatch: " + strconv.Itoa(code) + ", " + strconv.Itoa(code2)) - } - message += "\n" + moreMessage - } - 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 protocols such as 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 CanonicalMIMEHeaderKey(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": {"Value 1", "Value 2"}, -// "Long-Key": {"Even Longer Value"}, -// } -// -func (r *Reader) ReadMIMEHeader() (MIMEHeader, os.Error) { - m := make(MIMEHeader) - 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 := CanonicalMIMEHeaderKey(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") -} - -// CanonicalMIMEHeaderKey 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 CanonicalMIMEHeaderKey(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) -} diff --git a/src/pkg/net/textproto/reader_test.go b/src/pkg/net/textproto/reader_test.go deleted file mode 100644 index 0658e58b8..000000000 --- a/src/pkg/net/textproto/reader_test.go +++ /dev/null @@ -1,140 +0,0 @@ -// 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{ - {"a-b-c", "A-B-C"}, - {"a-1-c", "A-1-C"}, - {"User-Agent", "User-Agent"}, - {"uSER-aGENT", "User-Agent"}, - {"user-agent", "User-Agent"}, - {"USER-AGENT", "User-Agent"}, -} - -func TestCanonicalMIMEHeaderKey(t *testing.T) { - for _, tt := range canonicalHeaderKeyTests { - if s := CanonicalMIMEHeaderKey(tt.in); s != tt.out { - t.Errorf("CanonicalMIMEHeaderKey(%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 := MIMEHeader{ - "My-Key": {"Value 1", "Value 2"}, - "Long-Key": {"Even Longer Value"}, - } - if !reflect.DeepEqual(m, want) || err != nil { - t.Fatalf("ReadMIMEHeader: %v, %v; want %v", m, err, want) - } -} diff --git a/src/pkg/net/textproto/textproto.go b/src/pkg/net/textproto/textproto.go deleted file mode 100644 index 9f19b5495..000000000 --- a/src/pkg/net/textproto/textproto.go +++ /dev/null @@ -1,121 +0,0 @@ -// 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 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 -} diff --git a/src/pkg/net/textproto/writer.go b/src/pkg/net/textproto/writer.go deleted file mode 100644 index 4e705f6c3..000000000 --- a/src/pkg/net/textproto/writer.go +++ /dev/null @@ -1,119 +0,0 @@ -// 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() -} diff --git a/src/pkg/net/textproto/writer_test.go b/src/pkg/net/textproto/writer_test.go deleted file mode 100644 index e03ab5e15..000000000 --- a/src/pkg/net/textproto/writer_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// 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) - } -} |