Example #1
0
File: chunked.go Project: erkl/heat
func (cr *chunkedReader) open() error {
	buf, err := xo.PeekTo(cr.r, '\n', 0)
	if err != nil {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		return err
	}

	// Quick check for invalid chunk size lines.
	if len(buf) < 2 {
		return ErrInvalidChunkedEncoding
	}

	// Trim the line ending.
	var end int

	if buf[len(buf)-2] == '\r' {
		end = len(buf) - 2
	} else {
		end = len(buf) - 1
	}

	for i, c := range buf[:end] {
		// Decode hex characters.
		if x := dehex[c]; x <= 0xf {
			if cr.n > 0x07ffffffffffffff {
				return ErrInvalidChunkedEncoding
			}

			cr.n = cr.n<<4 | int64(x)
			continue
		}

		// The line must begin with at least one valid digit.
		if i == 0 {
			return ErrInvalidChunkedEncoding
		}

		// Chunk extensions are weird and seemingly unused, but RFC 2616
		// section 3.6.1 states:
		//
		//   All HTTP/1.1 applications MUST be able to receive and decode
		//   the "chunked" transfer-coding, and MUST ignore chunk-extension
		//   extensions they do not understand.
		//
		// ...so that means we'll just have to deal with them.
		if c == ';' {
			break
		}

		// Any other case is an error.
		return ErrInvalidChunkedEncoding
	}

	return cr.r.Consume(len(buf))
}
Example #2
0
File: chunked.go Project: erkl/heat
func (cr *chunkedReader) close() error {
	buf, err := xo.PeekTo(cr.r, '\n', 0)
	if err != nil {
		return err
	}

	if len(buf) != 1 && buf[0] != '\r' {
		return ErrInvalidChunkedEncoding
	}

	return cr.r.Consume(len(buf))
}
Example #3
0
File: request.go Project: erkl/heat
// ReadRequestHeader reads an HTTP request header from r.
func ReadRequestHeader(r xo.Reader) (*Request, error) {
	var req = new(Request)

	// Fetch the whole Request-Line.
	buf, err := xo.PeekTo(r, '\n', 0)
	if err != nil {
		return nil, err
	}

	method, rest := strtok(buf, ' ')
	if len(method) == 0 || rest == nil {
		return nil, ErrRequestHeader
	}

	uri, rest := strtok(rest, ' ')
	if len(uri) == 0 || rest == nil {
		return nil, ErrRequestHeader
	}

	// Trim trailing CRLF/LF.
	if len(rest) < 2 {
		return nil, ErrRequestHeader
	} else if rest[len(rest)-2] == '\r' {
		rest = rest[:len(rest)-2]
	} else {
		rest = rest[:len(rest)-1]
	}

	req.Major, req.Minor, err = parseHTTPVersion(rest)
	if err != nil {
		return nil, ErrRequestVersion
	}

	req.Method = stringify(method)
	req.URI = string(uri)

	// Consume the Request-Line.
	if err := r.Consume(len(buf)); err != nil {
		return nil, err
	}

	// Read header fields.
	req.Fields, err = readHeader(r)
	if err != nil {
		if err == errMalformedHeader {
			err = ErrRequestHeader
		}
		return nil, err
	}

	return req, nil
}
Example #4
0
File: chunked.go Project: erkl/heat
func (cr *chunkedReader) discardTrailers() error {
	for {
		buf, err := xo.PeekTo(cr.r, '\n', 0)
		if err != nil {
			return err
		}

		// An empty line signals the end of trailers.
		done := (len(buf) == 1 || buf[0] == '\r')

		if err := cr.r.Consume(len(buf)); err != nil || done {
			return err
		}
	}
}
Example #5
0
File: fields.go Project: erkl/heat
func readHeader(r xo.Reader) (Fields, error) {
	var fields Fields

	for {
		buf, err := xo.PeekTo(r, '\n', 0)
		if err != nil {
			return nil, err
		}

		if c := buf[0]; c == '\n' || (c == '\r' && len(buf) == 2) {
			if err := r.Consume(len(buf)); err != nil {
				return nil, err
			} else {
				return fields, nil
			}
		} else if c == ' ' || c == '\t' {
			// Because the loop below will consume all continuation lines,
			// taking this branch must mean that the first fields field has
			// leading whitespace, which is illegal.
			return nil, errMalformedHeader
		}

		colon := bytes.IndexByte(buf, ':')
		if colon == -1 {
			return nil, errMalformedHeader
		}

		// Lines beginning with horizontal whitespace are continuations of
		// the field value on the previous line, meaning we have to read all
		// of them before we have a full field value.
		for off := len(buf); ; off = len(buf) {
			peek, err := xo.PeekTo(r, '\n', off)
			if err != nil {
				return nil, err
			}

			if c := peek[off]; c == ' ' || c == '\t' {
				buf = peek
			} else {
				break
			}
		}

		// Trim the field's name and value. The shrinkValue call will modify
		// buf in place, which is referencing the xo.Reader's internal storage.
		// This isn't ideal, but it will only matter if the Consume call fails,
		// which is impossible for correct xo.Readers.
		name := shrinkName(buf[:colon])
		if len(name) == 0 {
			return nil, errMalformedHeader
		}

		value := shrinkValue(buf[colon+1:])

		fields = append(fields, Field{
			Name:  stringify(name),
			Value: string(value),
		})

		// Consume the bytes we just parsed.
		if err := r.Consume(len(buf)); err != nil {
			return nil, err
		}
	}
}