UA3F/http.go
2023-12-03 22:07:59 +08:00

289 lines
5.1 KiB
Go

package main
import (
"bytes"
"errors"
"strconv"
)
type header struct {
Name []byte
Value []byte
StartOffset int
EndOffset int
}
type HTTPParser struct {
Method, Path, Version []byte
Headers []header
TotalHeaders int
host []byte
hostRead bool
contentLength int64
contentLengthRead bool
}
const DefaultHeaderSlice = 10
// Create a new parser
func NewHTTPParser() *HTTPParser {
return NewSizedHTTPParser(DefaultHeaderSlice)
}
// Create a new parser allocating size for size headers
func NewSizedHTTPParser(size int) *HTTPParser {
return &HTTPParser{
Headers: make([]header, size),
TotalHeaders: size,
contentLength: -1,
}
}
var (
ErrBadProto = errors.New("bad protocol")
ErrMissingData = errors.New("missing data")
ErrUnsupported = errors.New("unsupported http feature")
)
const (
eNextHeader int = iota
eNextHeaderN
eHeader
eHeaderValueSpace
eHeaderValue
eHeaderValueN
eMLHeaderStart
eMLHeaderValue
)
// Parse the buffer as an HTTP Request. The buffer must contain the entire
// request or Parse will return ErrMissingData for the caller to get more
// data. (this thusly favors getting a completed request in a single Read()
// call).
//
// Returns the number of bytes used by the header (thus where the body begins).
// Also can return ErrUnsupported if an HTTP feature is detected but not supported.
func (hp *HTTPParser) Parse(input []byte) (int, error) {
var headers int
var path int
var ok bool
total := len(input)
method:
for i := 0; i < total; i++ {
switch input[i] {
case ' ', '\t':
hp.Method = input[0:i]
ok = true
path = i + 1
break method
}
}
if !ok {
return 0, ErrMissingData
}
var version int
ok = false
path:
for i := path; i < total; i++ {
switch input[i] {
case ' ', '\t':
ok = true
hp.Path = input[path:i]
version = i + 1
break path
}
}
if !ok {
return 0, ErrMissingData
}
var readN bool
ok = false
loop:
for i := version; i < total; i++ {
c := input[i]
switch readN {
case false:
switch c {
case '\r':
hp.Version = input[version:i]
readN = true
case '\n':
hp.Version = input[version:i]
headers = i + 1
ok = true
break loop
}
case true:
if c != '\n' {
return 0, errors.New("missing newline in version")
}
headers = i + 1
ok = true
break loop
}
}
if !ok {
return 0, ErrMissingData
}
var h int
var headerName []byte
state := eNextHeader
start := headers
for i := headers; i < total; i++ {
switch state {
case eNextHeader:
switch input[i] {
case '\r':
state = eNextHeaderN
case '\n':
return i + 1, nil
case ' ', '\t':
state = eMLHeaderStart
default:
start = i
state = eHeader
}
case eNextHeaderN:
if input[i] != '\n' {
return 0, ErrBadProto
}
return i + 1, nil
case eHeader:
if input[i] == ':' {
headerName = input[start:i]
state = eHeaderValueSpace
}
case eHeaderValueSpace:
switch input[i] {
case ' ', '\t':
continue
}
start = i
state = eHeaderValue
case eHeaderValue:
switch input[i] {
case '\r':
state = eHeaderValueN
case '\n':
state = eNextHeader
default:
continue
}
hp.Headers[h] = header{headerName, input[start:i], start, i}
h++
if h == hp.TotalHeaders {
newHeaders := make([]header, hp.TotalHeaders+10)
copy(newHeaders, hp.Headers)
hp.Headers = newHeaders
hp.TotalHeaders += 10
}
case eHeaderValueN:
if input[i] != '\n' {
return 0, ErrBadProto
}
state = eNextHeader
case eMLHeaderStart:
switch input[i] {
case ' ', '\t':
continue
}
start = i
state = eMLHeaderValue
case eMLHeaderValue:
switch input[i] {
case '\r':
state = eHeaderValueN
case '\n':
state = eNextHeader
default:
continue
}
cur := hp.Headers[h-1].Value
newheader := make([]byte, len(cur)+1+(i-start))
copy(newheader, cur)
copy(newheader[len(cur):], []byte(" "))
copy(newheader[len(cur)+1:], input[start:i])
hp.Headers[h-1].Value = newheader
}
}
return 0, ErrMissingData
}
// Return a value of a header matching name.
func (hp *HTTPParser) FindHeader(name []byte) (value []byte, startOffset, endOffset int) {
for _, header := range hp.Headers {
if bytes.Equal(header.Name, name) {
return header.Value, header.StartOffset, header.EndOffset
}
}
for _, header := range hp.Headers {
if bytes.EqualFold(header.Name, name) {
return header.Value, header.StartOffset, header.EndOffset
}
}
return nil, 0, 0
}
var cContentLength = []byte("Content-Length")
// Return the value of the Content-Length header.
// A value of -1 indicates the header was not set.
func (hp *HTTPParser) ContentLength() int64 {
if hp.contentLengthRead {
return hp.contentLength
}
header, _, _ := hp.FindHeader(cContentLength)
if header != nil {
i, err := strconv.ParseInt(string(header), 10, 0)
if err == nil {
hp.contentLength = i
}
}
hp.contentLengthRead = true
return hp.contentLength
}
// Return the value of the Host header
func (hp *HTTPParser) Host() []byte {
if hp.hostRead {
return hp.host
}
hp.hostRead = true
hp.host, _, _ = hp.FindHeader([]byte("Host"))
return hp.host
}