Improve header parsing code

Because the net/http server removes \r\n from multi-line header values,
there's no need to to check for \r or \n when skipping whitespace in
headers (see https://godoc.org/net/textproto#Reader.ReadMIMEHeader).
Given this fact, the whitespace test can be simplified to b == ' ' || b
== '\t'.  There's no need for the isSpaceOctet bit field in octetTypes.

The isTokenOctet bit field is the only bit field remaining after the
removal of isSpaceOctet.  Simplify the code by replacing the
isTokenOctet bit test in octetTypes with an array of booleans called
isTokenOctet.

Declare isTokenOctet as a composite literal instead of constructing it
at runtime.

Add documentation to core functions for parsing HTTP headers.
This commit is contained in:
Steven Scott 2018-10-06 11:35:33 -04:00 committed by Gary Burd
parent 3130e8d3f1
commit a51a35ae32
2 changed files with 90 additions and 43 deletions

132
util.go
View File

@ -31,68 +31,113 @@ func generateChallengeKey() (string, error) {
return base64.StdEncoding.EncodeToString(p), nil return base64.StdEncoding.EncodeToString(p), nil
} }
// Octet types from RFC 2616. // Token octets per RFC 2616.
var octetTypes [256]byte var isTokenOctet = [256]bool{
'!': true,
const ( '#': true,
isTokenOctet = 1 << iota '$': true,
isSpaceOctet '%': true,
) '&': true,
'\'': true,
func init() { '*': true,
// From RFC 2616 '+': true,
// '-': true,
// OCTET = <any 8-bit sequence of data> '.': true,
// CHAR = <any US-ASCII character (octets 0 - 127)> '0': true,
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)> '1': true,
// CR = <US-ASCII CR, carriage return (13)> '2': true,
// LF = <US-ASCII LF, linefeed (10)> '3': true,
// SP = <US-ASCII SP, space (32)> '4': true,
// HT = <US-ASCII HT, horizontal-tab (9)> '5': true,
// <"> = <US-ASCII double-quote mark (34)> '6': true,
// CRLF = CR LF '7': true,
// LWS = [CRLF] 1*( SP | HT ) '8': true,
// TEXT = <any OCTET except CTLs, but including LWS> '9': true,
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> 'A': true,
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT 'B': true,
// token = 1*<any CHAR except CTLs or separators> 'C': true,
// qdtext = <any TEXT except <">> 'D': true,
'E': true,
for c := 0; c < 256; c++ { 'F': true,
var t byte 'G': true,
isCtl := c <= 31 || c == 127 'H': true,
isChar := 0 <= c && c <= 127 'I': true,
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0 'J': true,
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 { 'K': true,
t |= isSpaceOctet 'L': true,
} 'M': true,
if isChar && !isCtl && !isSeparator { 'N': true,
t |= isTokenOctet 'O': true,
} 'P': true,
octetTypes[c] = t 'Q': true,
} 'R': true,
'S': true,
'T': true,
'U': true,
'W': true,
'V': true,
'X': true,
'Y': true,
'Z': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'|': true,
'~': true,
} }
// skipSpace returns a slice of the string s with all leading RFC 2616 linear
// whitespace removed.
func skipSpace(s string) (rest string) { func skipSpace(s string) (rest string) {
i := 0 i := 0
for ; i < len(s); i++ { for ; i < len(s); i++ {
if octetTypes[s[i]]&isSpaceOctet == 0 { if b := s[i]; b != ' ' && b != '\t' {
break break
} }
} }
return s[i:] return s[i:]
} }
// nextToken returns the leading RFC 2616 token of s and the string following
// the token.
func nextToken(s string) (token, rest string) { func nextToken(s string) (token, rest string) {
i := 0 i := 0
for ; i < len(s); i++ { for ; i < len(s); i++ {
if octetTypes[s[i]]&isTokenOctet == 0 { if !isTokenOctet[s[i]] {
break break
} }
} }
return s[:i], s[i:] return s[:i], s[i:]
} }
// nextTokenOrQuoted returns the leading token or quoted string per RFC 2616
// and the string following the token or quoted string.
func nextTokenOrQuoted(s string) (value string, rest string) { func nextTokenOrQuoted(s string) (value string, rest string) {
if !strings.HasPrefix(s, "\"") { if !strings.HasPrefix(s, "\"") {
return nextToken(s) return nextToken(s)
@ -128,7 +173,8 @@ func nextTokenOrQuoted(s string) (value string, rest string) {
return "", "" return "", ""
} }
// equalASCIIFold returns true if s is equal to t with ASCII case folding. // equalASCIIFold returns true if s is equal to t with ASCII case folding as
// defined in RFC 4790.
func equalASCIIFold(s, t string) bool { func equalASCIIFold(s, t string) bool {
for s != "" && t != "" { for s != "" && t != "" {
sr, size := utf8.DecodeRuneInString(s) sr, size := utf8.DecodeRuneInString(s)

View File

@ -17,6 +17,7 @@ var equalASCIIFoldTests = []struct {
{"WebSocket", "websocket", true}, {"WebSocket", "websocket", true},
{"websocket", "WebSocket", true}, {"websocket", "WebSocket", true},
{"Öyster", "öyster", false}, {"Öyster", "öyster", false},
{"WebSocket", "WetSocket", false},
} }
func TestEqualASCIIFold(t *testing.T) { func TestEqualASCIIFold(t *testing.T) {