-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rely on stdlib header parsing implementation to extract header names
- Loading branch information
Showing
3 changed files
with
53 additions
and
157 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,165 +1,43 @@ | ||
package http1parser | ||
|
||
import "errors" | ||
|
||
var ( | ||
ErrBadProto = errors.New("bad protocol") | ||
ErrMissingData = errors.New("missing data") | ||
import ( | ||
"errors" | ||
"net/textproto" | ||
"strings" | ||
) | ||
|
||
const ( | ||
_eNextHeader int = iota | ||
_eNextHeaderN | ||
_eHeader | ||
_eHeaderValueSpace | ||
_eHeaderValue | ||
_eHeaderValueN | ||
_eMLHeaderStart | ||
_eMLHeaderValue | ||
) | ||
var ErrBadProto = errors.New("bad protocol") | ||
|
||
// Http1ExtractHeaders is an HTTP/1.0 and HTTP/1.1 header-only parser, | ||
// to extract the original header names for the received request. | ||
// Fully inspired by https://github.com/evanphx/wildcat | ||
func Http1ExtractHeaders(input []byte) ([]string, error) { | ||
total := len(input) | ||
var path, version, headers int | ||
var headerNames []string | ||
|
||
// First line: METHOD PATH VERSION | ||
var methodOk bool | ||
for i := 0; i < total; i++ { | ||
switch input[i] { | ||
case ' ', '\t': | ||
methodOk = true | ||
path = i + 1 | ||
} | ||
if methodOk { | ||
break | ||
} | ||
} | ||
|
||
if !methodOk { | ||
return nil, ErrMissingData | ||
} | ||
|
||
var pathOk bool | ||
for i := path; i < total; i++ { | ||
switch input[i] { | ||
case ' ', '\t': | ||
pathOk = true | ||
version = i + 1 | ||
} | ||
if pathOk { | ||
break | ||
} | ||
// Fully inspired by readMIMEHeader() in | ||
// https://github.com/golang/go/blob/master/src/net/textproto/reader.go | ||
func Http1ExtractHeaders(r *textproto.Reader) ([]string, error) { | ||
// Discard first line, it doesn't contain useful information, and it has | ||
// already been validated in http.ReadRequest() | ||
if _, err := r.ReadLine(); err != nil { | ||
return nil, err | ||
} | ||
|
||
if !pathOk { | ||
return nil, ErrMissingData | ||
// The first line cannot start with a leading space. | ||
if buf, err := r.R.Peek(1); err == nil && (buf[0] == ' ' || buf[0] == '\t') { | ||
return nil, ErrBadProto | ||
} | ||
|
||
var versionOk bool | ||
var readN bool | ||
for i := version; i < total; i++ { | ||
c := input[i] | ||
|
||
switch readN { | ||
case false: | ||
switch c { | ||
case '\r': | ||
readN = true | ||
case '\n': | ||
headers = i + 1 | ||
versionOk = true | ||
} | ||
case true: | ||
if c != '\n' { | ||
return nil, ErrBadProto | ||
} | ||
headers = i + 1 | ||
versionOk = true | ||
} | ||
if versionOk { | ||
break | ||
var headerNames []string | ||
for { | ||
kv, err := r.ReadContinuedLine() | ||
if len(kv) == 0 { | ||
// We have finished to parse the headers if we receive empty | ||
// data without an error | ||
return headerNames, err | ||
} | ||
} | ||
|
||
if !versionOk { | ||
return nil, ErrMissingData | ||
} | ||
|
||
// Header parsing | ||
state := _eNextHeader | ||
start := headers | ||
|
||
for i := headers; i < total; i++ { | ||
switch state { | ||
case _eNextHeader: | ||
switch input[i] { | ||
case '\r': | ||
state = _eNextHeaderN | ||
case '\n': | ||
return headerNames, nil | ||
case ' ', '\t': | ||
state = _eMLHeaderStart | ||
default: | ||
start = i | ||
state = _eHeader | ||
} | ||
case _eNextHeaderN: | ||
if input[i] != '\n' { | ||
return nil, ErrBadProto | ||
} | ||
|
||
return headerNames, nil | ||
case _eHeader: | ||
if input[i] == ':' { | ||
headerName := input[start:i] | ||
headerNames = append(headerNames, string(headerName)) | ||
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 | ||
} | ||
case _eHeaderValueN: | ||
if input[i] != '\n' { | ||
return nil, 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 | ||
} | ||
// Key ends at first colon. | ||
k, _, ok := strings.Cut(kv, ":") | ||
if !ok { | ||
return nil, ErrBadProto | ||
} | ||
headerNames = append(headerNames, k) | ||
} | ||
|
||
return nil, ErrMissingData | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters