package reader // DISCLAIMER // ---------- // Some of the content of this file is inspired or copied from // the golang standard library import ( "bufio" "fmt" "io" ) // Maximum line length var maxLineLength = 4096 // ChunkReader struct type ChunkReader struct { reader *bufio.Reader // the reader isEnded bool // If we are done (2 consecutive CRLF) } // NewReader creates a new reader func NewReader(r io.Reader) *ChunkReader { br, ok := r.(*bufio.Reader) if !ok { br = bufio.NewReader(r) } return &ChunkReader{reader: br} } // Read reads a chunk, err is io.EOF when done func (r *ChunkReader) Read() ([]byte, error) { // 1. If already ended if r.isEnded { return nil, io.EOF } // 2. Read line var line []byte line, err := r.reader.ReadSlice('\n') // 3. manage errors if err == io.EOF { err = io.ErrUnexpectedEOF } if err != nil { return nil, err } if len(line) > maxLineLength { return nil, fmt.Errorf("HTTP line %d exceeded buffer size %d", len(line), maxLineLength) } // 4. Trim line = removeTrailingSpace(line) // 5. Manage ending line if len(line) == 0 { r.isEnded = true return line, io.EOF } return line, nil } func removeTrailingSpace(b []byte) []byte { for len(b) > 0 && isASCIISpace(b[len(b)-1]) { b = b[:len(b)-1] } return b } func isASCIISpace(b byte) bool { return b == ' ' || b == '\t' || b == '\r' || b == '\n' }