import (
"bufio"
"bytes"
- "container/vector"
"io"
"io/ioutil"
"os"
"strconv"
+ "strings"
)
// BUG(rsc): To let callers manage exposure to denial of service
// 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()
+ line, err := r.readLineSlice()
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--
- }
+ line, err := r.readLineSlice()
+ if line != nil {
+ buf := make([]byte, len(line))
+ copy(buf, line)
+ line = buf
}
- return line[0:n], err
+ return line, err
+}
+
+func (r *Reader) readLineSlice() ([]byte, os.Error) {
+ r.closeDot()
+ line, _, err := r.R.ReadLine()
+ return line, err
}
// ReadContinuedLine reads a possibly continued line from r,
// A line consisting of only white space is never continued.
//
func (r *Reader) ReadContinuedLine() (string, os.Error) {
- line, err := r.ReadContinuedLineBytes()
+ line, err := r.readContinuedLineSlice()
return string(line), err
}
// ReadContinuedLineBytes is like ReadContinuedLine but
// returns a []byte instead of a string.
func (r *Reader) ReadContinuedLineBytes() ([]byte, os.Error) {
+ line, err := r.readContinuedLineSlice()
+ if line != nil {
+ buf := make([]byte, len(line))
+ copy(buf, line)
+ line = buf
+ }
+ return line, err
+}
+
+func (r *Reader) readContinuedLineSlice() ([]byte, os.Error) {
// Read the first line.
- line, err := r.ReadLineBytes()
+ line, err := r.readLineSlice()
if err != nil {
return line, err
}
}
line = trim(line)
+ copied := false
+ if r.R.Buffered() < 1 {
+ // ReadByte will flush the buffer; make a copy of the slice.
+ copied = true
+ line = append([]byte(nil), line...)
+ }
+
// Look for a continuation line.
c, err := r.R.ReadByte()
if err != nil {
return line, nil
}
+ if !copied {
+ // The next readLineSlice will invalidate the previous one.
+ line = append(make([]byte, 0, len(line)*2), line...)
+ }
+
// Read continuation lines.
for {
// Consume leading spaces; one already gone.
}
}
var cont []byte
- cont, err = r.ReadLineBytes()
+ cont, err = r.readLineSlice()
cont = trim(cont)
line = append(line, ' ')
line = append(line, cont...)
if err != nil {
return
}
+ return parseCodeLine(line, expectCode)
+}
+
+func parseCodeLine(line string, expectCode int) (code int, continued bool, message string, err os.Error) {
if len(line) < 4 || line[3] != ' ' && line[3] != '-' {
err = ProtocolError("short response: " + line)
return
return
}
-// ReadResponse reads a multi-line response of the form
+// 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).
+//
+// where code is a 3-digit status code. The first line starts with the
+// code and a hyphen. The response is terminated by a line that starts
+// with the same code followed by a space. Each line in message is
+// separated by a newline (\n).
+//
+// See page 36 of RFC 959 (http://www.ietf.org/rfc/rfc959.txt) for
+// details.
//
// If the prefix of the status does not match the digits in expectCode,
// ReadResponse returns with err set to &Error{code, message}.
func (r *Reader) ReadResponse(expectCode int) (code int, message string, err os.Error) {
code, continued, message, err := r.readCodeLine(expectCode)
for err == nil && continued {
+ line, err := r.ReadLine()
+ if err != nil {
+ return 0, "", err
+ }
+
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))
+ code2, continued, moreMessage, err = parseCodeLine(line, expectCode)
+ if err != nil || code2 != code {
+ message += "\n" + strings.TrimRight(line, "\r\n")
+ continued = true
+ continue
}
message += "\n" + moreMessage
}
// 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
+// 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
// 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 v []string
var err os.Error
for {
var line string
}
line = line[1:]
}
- v.Push(line)
+ v = append(v, line)
}
return v, err
}
func (r *Reader) ReadMIMEHeader() (MIMEHeader, os.Error) {
m := make(MIMEHeader)
for {
- kv, err := r.ReadContinuedLineBytes()
+ kv, err := r.readContinuedLineSlice()
if len(kv) == 0 {
return m, err
}
}
value := string(kv[i:])
- v := vector.StringVector(m[key])
- v.Push(value)
- m[key] = v
+ m[key] = append(m[key], value)
if err != nil {
return m, err