├── go.mod ├── LICENSE ├── writer_test.go ├── README.md ├── writer.go ├── octet.go ├── lexer_test.go ├── head_test.go ├── option.go ├── cookie.go ├── head.go ├── lexer.go ├── cookie_test.go ├── httphead.go └── httphead_test.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gobwas/httphead 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Sergey Kamardin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /writer_test.go: -------------------------------------------------------------------------------- 1 | package httphead 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "testing" 8 | ) 9 | 10 | func ExampleWriteOptions() { 11 | opts := []Option{ 12 | NewOption("foo", map[string]string{ 13 | "param": "hello, world!", 14 | }), 15 | NewOption("bar", nil), 16 | NewOption("b a z", nil), 17 | } 18 | 19 | buf := bytes.Buffer{} 20 | bw := bufio.NewWriter(&buf) 21 | 22 | WriteOptions(bw, opts) 23 | bw.Flush() 24 | 25 | // Output: foo;param="hello, world!",bar,"b a z" 26 | fmt.Println(buf.String()) 27 | } 28 | 29 | func TestWriteOptions(t *testing.T) { 30 | for _, test := range []struct { 31 | options []Option 32 | exp string 33 | }{ 34 | { 35 | options: []Option{ 36 | NewOption("foo", map[string]string{"bar": "baz"}), 37 | }, 38 | exp: "foo;bar=baz", 39 | }, 40 | { 41 | options: []Option{ 42 | NewOption("foo", map[string]string{"bar": "baz"}), 43 | NewOption("a", nil), 44 | NewOption("b", map[string]string{"c": "10"}), 45 | }, 46 | exp: "foo;bar=baz,a,b;c=10", 47 | }, 48 | { 49 | options: []Option{ 50 | NewOption("foo", map[string]string{"a b c": "10,2"}), 51 | }, 52 | exp: `foo;"a b c"="10,2"`, 53 | }, 54 | { 55 | options: []Option{ 56 | NewOption(`"foo"`, nil), 57 | NewOption(`"bar"`, nil), 58 | }, 59 | exp: `"\"foo\"","\"bar\""`, 60 | }, 61 | } { 62 | t.Run("", func(t *testing.T) { 63 | buf := bytes.Buffer{} 64 | bw := bufio.NewWriter(&buf) 65 | 66 | WriteOptions(bw, test.options) 67 | 68 | if err := bw.Flush(); err != nil { 69 | t.Fatal(err) 70 | } 71 | if act := buf.String(); act != test.exp { 72 | t.Errorf("WriteOptions = %#q; want %#q", act, test.exp) 73 | } 74 | }) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # httphead.[go](https://golang.org) 2 | 3 | [![GoDoc][godoc-image]][godoc-url] 4 | 5 | > Tiny HTTP header value parsing library in go. 6 | 7 | ## Overview 8 | 9 | This library contains low-level functions for scanning HTTP RFC2616 compatible header value grammars. 10 | 11 | ## Install 12 | 13 | ```shell 14 | go get github.com/gobwas/httphead 15 | ``` 16 | 17 | ## Example 18 | 19 | The example below shows how multiple-choise HTTP header value could be parsed with this library: 20 | 21 | ```go 22 | options, ok := httphead.ParseOptions([]byte(`foo;bar=1,baz`), nil) 23 | fmt.Println(options, ok) 24 | // Output: [{foo map[bar:1]} {baz map[]}] true 25 | ``` 26 | 27 | The low-level example below shows how to optimize keys skipping and selection 28 | of some key: 29 | 30 | ```go 31 | // The right part of full header line like: 32 | // X-My-Header: key;foo=bar;baz,key;baz 33 | header := []byte(`foo;a=0,foo;a=1,foo;a=2,foo;a=3`) 34 | 35 | // We want to search key "foo" with an "a" parameter that equal to "2". 36 | var ( 37 | foo = []byte(`foo`) 38 | a = []byte(`a`) 39 | v = []byte(`2`) 40 | ) 41 | var found bool 42 | httphead.ScanOptions(header, func(i int, key, param, value []byte) Control { 43 | if !bytes.Equal(key, foo) { 44 | return ControlSkip 45 | } 46 | if !bytes.Equal(param, a) { 47 | if bytes.Equal(value, v) { 48 | // Found it! 49 | found = true 50 | return ControlBreak 51 | } 52 | return ControlSkip 53 | } 54 | return ControlContinue 55 | }) 56 | ``` 57 | 58 | For more usage examples please see [docs][godoc-url] or package tests. 59 | 60 | [godoc-image]: https://godoc.org/github.com/gobwas/httphead?status.svg 61 | [godoc-url]: https://godoc.org/github.com/gobwas/httphead 62 | [travis-image]: https://travis-ci.org/gobwas/httphead.svg?branch=master 63 | [travis-url]: https://travis-ci.org/gobwas/httphead 64 | -------------------------------------------------------------------------------- /writer.go: -------------------------------------------------------------------------------- 1 | package httphead 2 | 3 | import "io" 4 | 5 | var ( 6 | comma = []byte{','} 7 | equality = []byte{'='} 8 | semicolon = []byte{';'} 9 | quote = []byte{'"'} 10 | escape = []byte{'\\'} 11 | ) 12 | 13 | // WriteOptions write options list to the dest. 14 | // It uses the same form as {Scan,Parse}Options functions: 15 | // values = 1#value 16 | // value = token *( ";" param ) 17 | // param = token [ "=" (token | quoted-string) ] 18 | // 19 | // It wraps valuse into the quoted-string sequence if it contains any 20 | // non-token characters. 21 | func WriteOptions(dest io.Writer, options []Option) (n int, err error) { 22 | w := writer{w: dest} 23 | for i, opt := range options { 24 | if i > 0 { 25 | w.write(comma) 26 | } 27 | 28 | writeTokenSanitized(&w, opt.Name) 29 | 30 | for _, p := range opt.Parameters.data() { 31 | w.write(semicolon) 32 | writeTokenSanitized(&w, p.key) 33 | if len(p.value) != 0 { 34 | w.write(equality) 35 | writeTokenSanitized(&w, p.value) 36 | } 37 | } 38 | } 39 | return w.result() 40 | } 41 | 42 | // writeTokenSanitized writes token as is or as quouted string if it contains 43 | // non-token characters. 44 | // 45 | // Note that is is not expects LWS sequnces be in s, cause LWS is used only as 46 | // header field continuation: 47 | // "A CRLF is allowed in the definition of TEXT only as part of a header field 48 | // continuation. It is expected that the folding LWS will be replaced with a 49 | // single SP before interpretation of the TEXT value." 50 | // See https://tools.ietf.org/html/rfc2616#section-2 51 | // 52 | // That is we sanitizing s for writing, so there could not be any header field 53 | // continuation. 54 | // That is any CRLF will be escaped as any other control characters not allowd in TEXT. 55 | func writeTokenSanitized(bw *writer, bts []byte) { 56 | var qt bool 57 | var pos int 58 | for i := 0; i < len(bts); i++ { 59 | c := bts[i] 60 | if !OctetTypes[c].IsToken() && !qt { 61 | qt = true 62 | bw.write(quote) 63 | } 64 | if OctetTypes[c].IsControl() || c == '"' { 65 | if !qt { 66 | qt = true 67 | bw.write(quote) 68 | } 69 | bw.write(bts[pos:i]) 70 | bw.write(escape) 71 | bw.write(bts[i : i+1]) 72 | pos = i + 1 73 | } 74 | } 75 | if !qt { 76 | bw.write(bts) 77 | } else { 78 | bw.write(bts[pos:]) 79 | bw.write(quote) 80 | } 81 | } 82 | 83 | type writer struct { 84 | w io.Writer 85 | n int 86 | err error 87 | } 88 | 89 | func (w *writer) write(p []byte) { 90 | if w.err != nil { 91 | return 92 | } 93 | var n int 94 | n, w.err = w.w.Write(p) 95 | w.n += n 96 | return 97 | } 98 | 99 | func (w *writer) result() (int, error) { 100 | return w.n, w.err 101 | } 102 | -------------------------------------------------------------------------------- /octet.go: -------------------------------------------------------------------------------- 1 | package httphead 2 | 3 | // OctetType desribes character type. 4 | // 5 | // From the "Basic Rules" chapter of RFC2616 6 | // See https://tools.ietf.org/html/rfc2616#section-2.2 7 | // 8 | // OCTET = 9 | // CHAR = 10 | // UPALPHA = 11 | // LOALPHA = 12 | // ALPHA = UPALPHA | LOALPHA 13 | // DIGIT = 14 | // CTL = 15 | // CR = 16 | // LF = 17 | // SP = 18 | // HT = 19 | // <"> = 20 | // CRLF = CR LF 21 | // LWS = [CRLF] 1*( SP | HT ) 22 | // 23 | // Many HTTP/1.1 header field values consist of words separated by LWS 24 | // or special characters. These special characters MUST be in a quoted 25 | // string to be used within a parameter value (as defined in section 26 | // 3.6). 27 | // 28 | // token = 1* 29 | // separators = "(" | ")" | "<" | ">" | "@" 30 | // | "," | ";" | ":" | "\" | <"> 31 | // | "/" | "[" | "]" | "?" | "=" 32 | // | "{" | "}" | SP | HT 33 | type OctetType byte 34 | 35 | // IsChar reports whether octet is CHAR. 36 | func (t OctetType) IsChar() bool { return t&octetChar != 0 } 37 | 38 | // IsControl reports whether octet is CTL. 39 | func (t OctetType) IsControl() bool { return t&octetControl != 0 } 40 | 41 | // IsSeparator reports whether octet is separator. 42 | func (t OctetType) IsSeparator() bool { return t&octetSeparator != 0 } 43 | 44 | // IsSpace reports whether octet is space (SP or HT). 45 | func (t OctetType) IsSpace() bool { return t&octetSpace != 0 } 46 | 47 | // IsToken reports whether octet is token. 48 | func (t OctetType) IsToken() bool { return t&octetToken != 0 } 49 | 50 | const ( 51 | octetChar OctetType = 1 << iota 52 | octetControl 53 | octetSpace 54 | octetSeparator 55 | octetToken 56 | ) 57 | 58 | // OctetTypes is a table of octets. 59 | var OctetTypes [256]OctetType 60 | 61 | func init() { 62 | for c := 32; c < 256; c++ { 63 | var t OctetType 64 | if c <= 127 { 65 | t |= octetChar 66 | } 67 | if 0 <= c && c <= 31 || c == 127 { 68 | t |= octetControl 69 | } 70 | switch c { 71 | case '(', ')', '<', '>', '@', ',', ';', ':', '"', '/', '[', ']', '?', '=', '{', '}', '\\': 72 | t |= octetSeparator 73 | case ' ', '\t': 74 | t |= octetSpace | octetSeparator 75 | } 76 | 77 | if t.IsChar() && !t.IsControl() && !t.IsSeparator() && !t.IsSpace() { 78 | t |= octetToken 79 | } 80 | 81 | OctetTypes[c] = t 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lexer_test.go: -------------------------------------------------------------------------------- 1 | package httphead 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestScannerSkipEscaped(t *testing.T) { 9 | for _, test := range []struct { 10 | in []byte 11 | c byte 12 | pos int 13 | }{ 14 | { 15 | in: []byte(`foo,bar`), 16 | c: ',', 17 | pos: 4, 18 | }, 19 | { 20 | in: []byte(`foo\,bar,baz`), 21 | c: ',', 22 | pos: 9, 23 | }, 24 | } { 25 | s := NewScanner(test.in) 26 | s.SkipEscaped(test.c) 27 | if act, exp := s.pos, test.pos; act != exp { 28 | t.Errorf("unexpected scanner pos: %v; want %v", act, exp) 29 | } 30 | } 31 | } 32 | 33 | type readCase struct { 34 | label string 35 | in []byte 36 | out []byte 37 | err bool 38 | } 39 | 40 | var quotedStringCases = []readCase{ 41 | { 42 | label: "nonterm", 43 | in: []byte(`"`), 44 | out: []byte(``), 45 | err: true, 46 | }, 47 | { 48 | label: "empty", 49 | in: []byte(`""`), 50 | out: []byte(``), 51 | }, 52 | { 53 | label: "simple", 54 | in: []byte(`"hello, world!"`), 55 | out: []byte(`hello, world!`), 56 | }, 57 | { 58 | label: "quoted", 59 | in: []byte(`"hello, \"world\"!"`), 60 | out: []byte(`hello, "world"!`), 61 | }, 62 | { 63 | label: "quoted", 64 | in: []byte(`"\"hello\", \"world\"!"`), 65 | out: []byte(`"hello", "world"!`), 66 | }, 67 | } 68 | 69 | var commentCases = []readCase{ 70 | { 71 | label: "nonterm", 72 | in: []byte(`(hello`), 73 | out: []byte(``), 74 | err: true, 75 | }, 76 | { 77 | label: "empty", 78 | in: []byte(`()`), 79 | out: []byte(``), 80 | }, 81 | { 82 | label: "simple", 83 | in: []byte(`(hello)`), 84 | out: []byte(`hello`), 85 | }, 86 | { 87 | label: "quoted", 88 | in: []byte(`(hello\)\(world)`), 89 | out: []byte(`hello)(world`), 90 | }, 91 | { 92 | label: "nested", 93 | in: []byte(`(hello(world))`), 94 | out: []byte(`hello(world)`), 95 | }, 96 | } 97 | 98 | type readTest struct { 99 | label string 100 | cases []readCase 101 | fn func(*Scanner) bool 102 | } 103 | 104 | var readTests = []readTest{ 105 | { 106 | "ReadString", 107 | quotedStringCases, 108 | (*Scanner).fetchQuotedString, 109 | }, 110 | { 111 | "ReadComment", 112 | commentCases, 113 | (*Scanner).fetchComment, 114 | }, 115 | } 116 | 117 | func TestScannerRead(t *testing.T) { 118 | for _, bunch := range readTests { 119 | for _, test := range bunch.cases { 120 | t.Run(bunch.label+" "+test.label, func(t *testing.T) { 121 | l := &Scanner{data: []byte(test.in)} 122 | if ok := bunch.fn(l); ok != !test.err { 123 | t.Errorf("l.%s() = %v; want %v", bunch.label, ok, !test.err) 124 | return 125 | } 126 | if !bytes.Equal(test.out, l.itemBytes) { 127 | t.Errorf("l.%s() = %s; want %s", bunch.label, string(l.itemBytes), string(test.out)) 128 | } 129 | }) 130 | } 131 | 132 | } 133 | } 134 | 135 | func BenchmarkScannerReadString(b *testing.B) { 136 | for _, bench := range quotedStringCases { 137 | b.Run(bench.label, func(b *testing.B) { 138 | for i := 0; i < b.N; i++ { 139 | l := &Scanner{data: []byte(bench.in)} 140 | _ = l.fetchQuotedString() 141 | } 142 | }) 143 | } 144 | } 145 | 146 | func BenchmarkScannerReadComment(b *testing.B) { 147 | for _, bench := range commentCases { 148 | b.Run(bench.label, func(b *testing.B) { 149 | for i := 0; i < b.N; i++ { 150 | l := &Scanner{data: []byte(bench.in)} 151 | _ = l.fetchComment() 152 | } 153 | }) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /head_test.go: -------------------------------------------------------------------------------- 1 | package httphead 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestParseRequestLine(t *testing.T) { 9 | for _, test := range []struct { 10 | name string 11 | line string 12 | exp RequestLine 13 | fail bool 14 | }{ 15 | { 16 | line: "", 17 | fail: true, 18 | }, 19 | { 20 | line: "GET", 21 | fail: true, 22 | }, 23 | { 24 | line: "GET ", 25 | fail: true, 26 | }, 27 | { 28 | line: "GET ", 29 | fail: true, 30 | }, 31 | { 32 | line: "GET ", 33 | fail: true, 34 | }, 35 | { 36 | line: "GET / HTTP/1.1", 37 | exp: RequestLine{ 38 | Method: []byte("GET"), 39 | URI: []byte("/"), 40 | Version: Version{1, 1}, 41 | }, 42 | }, 43 | } { 44 | t.Run(test.name, func(t *testing.T) { 45 | r, ok := ParseRequestLine([]byte(test.line)) 46 | if test.fail && ok { 47 | t.Fatalf("unexpected successful parsing") 48 | } 49 | if !test.fail && !ok { 50 | t.Fatalf("unexpected parse error") 51 | } 52 | if test.fail { 53 | return 54 | } 55 | if act, exp := r.Method, test.exp.Method; !bytes.Equal(act, exp) { 56 | t.Errorf("unexpected parsed method: %q; want %q", act, exp) 57 | } 58 | if act, exp := r.URI, test.exp.URI; !bytes.Equal(act, exp) { 59 | t.Errorf("unexpected parsed uri: %q; want %q", act, exp) 60 | } 61 | if act, exp := r.Version, test.exp.Version; act != exp { 62 | t.Errorf("unexpected parsed version: %+v; want %+v", act, exp) 63 | } 64 | }) 65 | } 66 | } 67 | 68 | func TestParseResponseLine(t *testing.T) { 69 | for _, test := range []struct { 70 | name string 71 | line string 72 | exp ResponseLine 73 | fail bool 74 | }{ 75 | { 76 | line: "", 77 | fail: true, 78 | }, 79 | { 80 | line: "HTTP/1.1", 81 | fail: true, 82 | }, 83 | { 84 | line: "HTTP/1.1 ", 85 | fail: true, 86 | }, 87 | { 88 | line: "HTTP/1.1 ", 89 | fail: true, 90 | }, 91 | { 92 | line: "HTTP/1.1 ", 93 | fail: true, 94 | }, 95 | { 96 | line: "HTTP/1.1 200 OK", 97 | exp: ResponseLine{ 98 | Version: Version{1, 1}, 99 | Status: 200, 100 | Reason: []byte("OK"), 101 | }, 102 | }, 103 | } { 104 | t.Run(test.name, func(t *testing.T) { 105 | r, ok := ParseResponseLine([]byte(test.line)) 106 | if test.fail && ok { 107 | t.Fatalf("unexpected successful parsing") 108 | } 109 | if !test.fail && !ok { 110 | t.Fatalf("unexpected parse error") 111 | } 112 | if test.fail { 113 | return 114 | } 115 | if act, exp := r.Version, test.exp.Version; act != exp { 116 | t.Errorf("unexpected parsed version: %+v; want %+v", act, exp) 117 | } 118 | if act, exp := r.Status, test.exp.Status; act != exp { 119 | t.Errorf("unexpected parsed status: %d; want %d", act, exp) 120 | } 121 | if act, exp := r.Reason, test.exp.Reason; !bytes.Equal(act, exp) { 122 | t.Errorf("unexpected parsed reason: %q; want %q", act, exp) 123 | } 124 | }) 125 | } 126 | } 127 | 128 | var versionCases = []struct { 129 | in []byte 130 | major int 131 | minor int 132 | ok bool 133 | }{ 134 | {[]byte("HTTP/1.1"), 1, 1, true}, 135 | {[]byte("HTTP/1.0"), 1, 0, true}, 136 | {[]byte("HTTP/1.2"), 1, 2, true}, 137 | {[]byte("HTTP/42.1092"), 42, 1092, true}, 138 | } 139 | 140 | func TestParseHttpVersion(t *testing.T) { 141 | for _, c := range versionCases { 142 | t.Run(string(c.in), func(t *testing.T) { 143 | major, minor, ok := ParseVersion(c.in) 144 | if major != c.major || minor != c.minor || ok != c.ok { 145 | t.Errorf( 146 | "parseHttpVersion([]byte(%q)) = %v, %v, %v; want %v, %v, %v", 147 | string(c.in), major, minor, ok, c.major, c.minor, c.ok, 148 | ) 149 | } 150 | }) 151 | } 152 | } 153 | 154 | func BenchmarkParseHttpVersion(b *testing.B) { 155 | for _, c := range versionCases { 156 | b.Run(string(c.in), func(b *testing.B) { 157 | for i := 0; i < b.N; i++ { 158 | _, _, _ = ParseVersion(c.in) 159 | } 160 | }) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /option.go: -------------------------------------------------------------------------------- 1 | package httphead 2 | 3 | import ( 4 | "bytes" 5 | "sort" 6 | ) 7 | 8 | // Option represents a header option. 9 | type Option struct { 10 | Name []byte 11 | Parameters Parameters 12 | } 13 | 14 | // Size returns number of bytes need to be allocated for use in opt.Copy. 15 | func (opt Option) Size() int { 16 | return len(opt.Name) + opt.Parameters.bytes 17 | } 18 | 19 | // Copy copies all underlying []byte slices into p and returns new Option. 20 | // Note that p must be at least of opt.Size() length. 21 | func (opt Option) Copy(p []byte) Option { 22 | n := copy(p, opt.Name) 23 | opt.Name = p[:n] 24 | opt.Parameters, p = opt.Parameters.Copy(p[n:]) 25 | return opt 26 | } 27 | 28 | // Clone is a shorthand for making slice of opt.Size() sequenced with Copy() 29 | // call. 30 | func (opt Option) Clone() Option { 31 | return opt.Copy(make([]byte, opt.Size())) 32 | } 33 | 34 | // String represents option as a string. 35 | func (opt Option) String() string { 36 | return "{" + string(opt.Name) + " " + opt.Parameters.String() + "}" 37 | } 38 | 39 | // NewOption creates named option with given parameters. 40 | func NewOption(name string, params map[string]string) Option { 41 | p := Parameters{} 42 | for k, v := range params { 43 | p.Set([]byte(k), []byte(v)) 44 | } 45 | return Option{ 46 | Name: []byte(name), 47 | Parameters: p, 48 | } 49 | } 50 | 51 | // Equal reports whether option is equal to b. 52 | func (opt Option) Equal(b Option) bool { 53 | if bytes.Equal(opt.Name, b.Name) { 54 | return opt.Parameters.Equal(b.Parameters) 55 | } 56 | return false 57 | } 58 | 59 | // Parameters represents option's parameters. 60 | type Parameters struct { 61 | pos int 62 | bytes int 63 | arr [8]pair 64 | dyn []pair 65 | } 66 | 67 | // Equal reports whether a equal to b. 68 | func (p Parameters) Equal(b Parameters) bool { 69 | switch { 70 | case p.dyn == nil && b.dyn == nil: 71 | case p.dyn != nil && b.dyn != nil: 72 | default: 73 | return false 74 | } 75 | 76 | ad, bd := p.data(), b.data() 77 | if len(ad) != len(bd) { 78 | return false 79 | } 80 | 81 | sort.Sort(pairs(ad)) 82 | sort.Sort(pairs(bd)) 83 | 84 | for i := 0; i < len(ad); i++ { 85 | av, bv := ad[i], bd[i] 86 | if !bytes.Equal(av.key, bv.key) || !bytes.Equal(av.value, bv.value) { 87 | return false 88 | } 89 | } 90 | return true 91 | } 92 | 93 | // Size returns number of bytes that needed to copy p. 94 | func (p *Parameters) Size() int { 95 | return p.bytes 96 | } 97 | 98 | // Copy copies all underlying []byte slices into dst and returns new 99 | // Parameters. 100 | // Note that dst must be at least of p.Size() length. 101 | func (p *Parameters) Copy(dst []byte) (Parameters, []byte) { 102 | ret := Parameters{ 103 | pos: p.pos, 104 | bytes: p.bytes, 105 | } 106 | if p.dyn != nil { 107 | ret.dyn = make([]pair, len(p.dyn)) 108 | for i, v := range p.dyn { 109 | ret.dyn[i], dst = v.copy(dst) 110 | } 111 | } else { 112 | for i, p := range p.arr { 113 | ret.arr[i], dst = p.copy(dst) 114 | } 115 | } 116 | return ret, dst 117 | } 118 | 119 | // Get returns value by key and flag about existence such value. 120 | func (p *Parameters) Get(key string) (value []byte, ok bool) { 121 | for _, v := range p.data() { 122 | if string(v.key) == key { 123 | return v.value, true 124 | } 125 | } 126 | return nil, false 127 | } 128 | 129 | // Set sets value by key. 130 | func (p *Parameters) Set(key, value []byte) { 131 | p.bytes += len(key) + len(value) 132 | 133 | if p.pos < len(p.arr) { 134 | p.arr[p.pos] = pair{key, value} 135 | p.pos++ 136 | return 137 | } 138 | 139 | if p.dyn == nil { 140 | p.dyn = make([]pair, len(p.arr), len(p.arr)+1) 141 | copy(p.dyn, p.arr[:]) 142 | } 143 | p.dyn = append(p.dyn, pair{key, value}) 144 | } 145 | 146 | // ForEach iterates over parameters key-value pairs and calls cb for each one. 147 | func (p *Parameters) ForEach(cb func(k, v []byte) bool) { 148 | for _, v := range p.data() { 149 | if !cb(v.key, v.value) { 150 | break 151 | } 152 | } 153 | } 154 | 155 | // String represents parameters as a string. 156 | func (p *Parameters) String() (ret string) { 157 | ret = "[" 158 | for i, v := range p.data() { 159 | if i > 0 { 160 | ret += " " 161 | } 162 | ret += string(v.key) + ":" + string(v.value) 163 | } 164 | return ret + "]" 165 | } 166 | 167 | func (p *Parameters) data() []pair { 168 | if p.dyn != nil { 169 | return p.dyn 170 | } 171 | return p.arr[:p.pos] 172 | } 173 | 174 | type pair struct { 175 | key, value []byte 176 | } 177 | 178 | func (p pair) copy(dst []byte) (pair, []byte) { 179 | n := copy(dst, p.key) 180 | p.key = dst[:n] 181 | m := n + copy(dst[n:], p.value) 182 | p.value = dst[n:m] 183 | 184 | dst = dst[m:] 185 | 186 | return p, dst 187 | } 188 | 189 | type pairs []pair 190 | 191 | func (p pairs) Len() int { return len(p) } 192 | func (p pairs) Less(a, b int) bool { return bytes.Compare(p[a].key, p[b].key) == -1 } 193 | func (p pairs) Swap(a, b int) { p[a], p[b] = p[b], p[a] } 194 | -------------------------------------------------------------------------------- /cookie.go: -------------------------------------------------------------------------------- 1 | package httphead 2 | 3 | import ( 4 | "bytes" 5 | ) 6 | 7 | // ScanCookie scans cookie pairs from data using DefaultCookieScanner.Scan() 8 | // method. 9 | func ScanCookie(data []byte, it func(key, value []byte) bool) bool { 10 | return DefaultCookieScanner.Scan(data, it) 11 | } 12 | 13 | // DefaultCookieScanner is a CookieScanner which is used by ScanCookie(). 14 | // Note that it is intended to have the same behavior as http.Request.Cookies() 15 | // has. 16 | var DefaultCookieScanner = CookieScanner{} 17 | 18 | // CookieScanner contains options for scanning cookie pairs. 19 | // See https://tools.ietf.org/html/rfc6265#section-4.1.1 20 | type CookieScanner struct { 21 | // DisableNameValidation disables name validation of a cookie. If false, 22 | // only RFC2616 "tokens" are accepted. 23 | DisableNameValidation bool 24 | 25 | // DisableValueValidation disables value validation of a cookie. If false, 26 | // only RFC6265 "cookie-octet" characters are accepted. 27 | // 28 | // Note that Strict option also affects validation of a value. 29 | // 30 | // If Strict is false, then scanner begins to allow space and comma 31 | // characters inside the value for better compatibility with non standard 32 | // cookies implementations. 33 | DisableValueValidation bool 34 | 35 | // BreakOnPairError sets scanner to immediately return after first pair syntax 36 | // validation error. 37 | // If false, scanner will try to skip invalid pair bytes and go ahead. 38 | BreakOnPairError bool 39 | 40 | // Strict enables strict RFC6265 mode scanning. It affects name and value 41 | // validation, as also some other rules. 42 | // If false, it is intended to bring the same behavior as 43 | // http.Request.Cookies(). 44 | Strict bool 45 | } 46 | 47 | // Scan maps data to name and value pairs. Usually data represents value of the 48 | // Cookie header. 49 | func (c CookieScanner) Scan(data []byte, it func(name, value []byte) bool) bool { 50 | lexer := &Scanner{data: data} 51 | 52 | const ( 53 | statePair = iota 54 | stateBefore 55 | ) 56 | 57 | state := statePair 58 | 59 | for lexer.Buffered() > 0 { 60 | switch state { 61 | case stateBefore: 62 | // Pairs separated by ";" and space, according to the RFC6265: 63 | // cookie-pair *( ";" SP cookie-pair ) 64 | // 65 | // Cookie pairs MUST be separated by (";" SP). So our only option 66 | // here is to fail as syntax error. 67 | a, b := lexer.Peek2() 68 | if a != ';' { 69 | return false 70 | } 71 | 72 | state = statePair 73 | 74 | advance := 1 75 | if b == ' ' { 76 | advance++ 77 | } else if c.Strict { 78 | return false 79 | } 80 | 81 | lexer.Advance(advance) 82 | 83 | case statePair: 84 | if !lexer.FetchUntil(';') { 85 | return false 86 | } 87 | 88 | var value []byte 89 | name := lexer.Bytes() 90 | if i := bytes.IndexByte(name, '='); i != -1 { 91 | value = name[i+1:] 92 | name = name[:i] 93 | } else if c.Strict { 94 | if !c.BreakOnPairError { 95 | goto nextPair 96 | } 97 | return false 98 | } 99 | 100 | if !c.Strict { 101 | trimLeft(name) 102 | } 103 | if !c.DisableNameValidation && !ValidCookieName(name) { 104 | if !c.BreakOnPairError { 105 | goto nextPair 106 | } 107 | return false 108 | } 109 | 110 | if !c.Strict { 111 | value = trimRight(value) 112 | } 113 | value = stripQuotes(value) 114 | if !c.DisableValueValidation && !ValidCookieValue(value, c.Strict) { 115 | if !c.BreakOnPairError { 116 | goto nextPair 117 | } 118 | return false 119 | } 120 | 121 | if !it(name, value) { 122 | return true 123 | } 124 | 125 | nextPair: 126 | state = stateBefore 127 | } 128 | } 129 | 130 | return true 131 | } 132 | 133 | // ValidCookieValue reports whether given value is a valid RFC6265 134 | // "cookie-octet" bytes. 135 | // 136 | // cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E 137 | // ; US-ASCII characters excluding CTLs, 138 | // ; whitespace DQUOTE, comma, semicolon, 139 | // ; and backslash 140 | // 141 | // Note that the false strict parameter disables errors on space 0x20 and comma 142 | // 0x2c. This could be useful to bring some compatibility with non-compliant 143 | // clients/servers in the real world. 144 | // It acts the same as standard library cookie parser if strict is false. 145 | func ValidCookieValue(value []byte, strict bool) bool { 146 | if len(value) == 0 { 147 | return true 148 | } 149 | for _, c := range value { 150 | switch c { 151 | case '"', ';', '\\': 152 | return false 153 | case ',', ' ': 154 | if strict { 155 | return false 156 | } 157 | default: 158 | if c <= 0x20 { 159 | return false 160 | } 161 | if c >= 0x7f { 162 | return false 163 | } 164 | } 165 | } 166 | return true 167 | } 168 | 169 | // ValidCookieName reports wheter given bytes is a valid RFC2616 "token" bytes. 170 | func ValidCookieName(name []byte) bool { 171 | for _, c := range name { 172 | if !OctetTypes[c].IsToken() { 173 | return false 174 | } 175 | } 176 | return true 177 | } 178 | 179 | func stripQuotes(bts []byte) []byte { 180 | if last := len(bts) - 1; last > 0 && bts[0] == '"' && bts[last] == '"' { 181 | return bts[1:last] 182 | } 183 | return bts 184 | } 185 | 186 | func trimLeft(p []byte) []byte { 187 | var i int 188 | for i < len(p) && OctetTypes[p[i]].IsSpace() { 189 | i++ 190 | } 191 | return p[i:] 192 | } 193 | 194 | func trimRight(p []byte) []byte { 195 | j := len(p) 196 | for j > 0 && OctetTypes[p[j-1]].IsSpace() { 197 | j-- 198 | } 199 | return p[:j] 200 | } 201 | -------------------------------------------------------------------------------- /head.go: -------------------------------------------------------------------------------- 1 | package httphead 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | ) 7 | 8 | // Version contains protocol major and minor version. 9 | type Version struct { 10 | Major int 11 | Minor int 12 | } 13 | 14 | // RequestLine contains parameters parsed from the first request line. 15 | type RequestLine struct { 16 | Method []byte 17 | URI []byte 18 | Version Version 19 | } 20 | 21 | // ResponseLine contains parameters parsed from the first response line. 22 | type ResponseLine struct { 23 | Version Version 24 | Status int 25 | Reason []byte 26 | } 27 | 28 | // SplitRequestLine splits given slice of bytes into three chunks without 29 | // parsing. 30 | func SplitRequestLine(line []byte) (method, uri, version []byte) { 31 | return split3(line, ' ') 32 | } 33 | 34 | // ParseRequestLine parses http request line like "GET / HTTP/1.0". 35 | func ParseRequestLine(line []byte) (r RequestLine, ok bool) { 36 | var i int 37 | for i = 0; i < len(line); i++ { 38 | c := line[i] 39 | if !OctetTypes[c].IsToken() { 40 | if i > 0 && c == ' ' { 41 | break 42 | } 43 | return 44 | } 45 | } 46 | if i == len(line) { 47 | return 48 | } 49 | 50 | var proto []byte 51 | r.Method = line[:i] 52 | r.URI, proto = split2(line[i+1:], ' ') 53 | if len(r.URI) == 0 { 54 | return 55 | } 56 | if major, minor, ok := ParseVersion(proto); ok { 57 | r.Version.Major = major 58 | r.Version.Minor = minor 59 | return r, true 60 | } 61 | 62 | return r, false 63 | } 64 | 65 | // SplitResponseLine splits given slice of bytes into three chunks without 66 | // parsing. 67 | func SplitResponseLine(line []byte) (version, status, reason []byte) { 68 | return split3(line, ' ') 69 | } 70 | 71 | // ParseResponseLine parses first response line into ResponseLine struct. 72 | func ParseResponseLine(line []byte) (r ResponseLine, ok bool) { 73 | var ( 74 | proto []byte 75 | status []byte 76 | ) 77 | proto, status, r.Reason = split3(line, ' ') 78 | if major, minor, ok := ParseVersion(proto); ok { 79 | r.Version.Major = major 80 | r.Version.Minor = minor 81 | } else { 82 | return r, false 83 | } 84 | if n, ok := IntFromASCII(status); ok { 85 | r.Status = n 86 | } else { 87 | return r, false 88 | } 89 | // TODO(gobwas): parse here r.Reason fot TEXT rule: 90 | // TEXT = 92 | return r, true 93 | } 94 | 95 | var ( 96 | httpVersion10 = []byte("HTTP/1.0") 97 | httpVersion11 = []byte("HTTP/1.1") 98 | httpVersionPrefix = []byte("HTTP/") 99 | ) 100 | 101 | // ParseVersion parses major and minor version of HTTP protocol. 102 | // It returns parsed values and true if parse is ok. 103 | func ParseVersion(bts []byte) (major, minor int, ok bool) { 104 | switch { 105 | case bytes.Equal(bts, httpVersion11): 106 | return 1, 1, true 107 | case bytes.Equal(bts, httpVersion10): 108 | return 1, 0, true 109 | case len(bts) < 8: 110 | return 111 | case !bytes.Equal(bts[:5], httpVersionPrefix): 112 | return 113 | } 114 | 115 | bts = bts[5:] 116 | 117 | dot := bytes.IndexByte(bts, '.') 118 | if dot == -1 { 119 | return 120 | } 121 | major, ok = IntFromASCII(bts[:dot]) 122 | if !ok { 123 | return 124 | } 125 | minor, ok = IntFromASCII(bts[dot+1:]) 126 | if !ok { 127 | return 128 | } 129 | 130 | return major, minor, true 131 | } 132 | 133 | // ReadLine reads line from br. It reads until '\n' and returns bytes without 134 | // '\n' or '\r\n' at the end. 135 | // It returns err if and only if line does not end in '\n'. Note that read 136 | // bytes returned in any case of error. 137 | // 138 | // It is much like the textproto/Reader.ReadLine() except the thing that it 139 | // returns raw bytes, instead of string. That is, it avoids copying bytes read 140 | // from br. 141 | // 142 | // textproto/Reader.ReadLineBytes() is also makes copy of resulting bytes to be 143 | // safe with future I/O operations on br. 144 | // 145 | // We could control I/O operations on br and do not need to make additional 146 | // copy for safety. 147 | func ReadLine(br *bufio.Reader) ([]byte, error) { 148 | var line []byte 149 | for { 150 | bts, err := br.ReadSlice('\n') 151 | if err == bufio.ErrBufferFull { 152 | // Copy bytes because next read will discard them. 153 | line = append(line, bts...) 154 | continue 155 | } 156 | // Avoid copy of single read. 157 | if line == nil { 158 | line = bts 159 | } else { 160 | line = append(line, bts...) 161 | } 162 | if err != nil { 163 | return line, err 164 | } 165 | // Size of line is at least 1. 166 | // In other case bufio.ReadSlice() returns error. 167 | n := len(line) 168 | // Cut '\n' or '\r\n'. 169 | if n > 1 && line[n-2] == '\r' { 170 | line = line[:n-2] 171 | } else { 172 | line = line[:n-1] 173 | } 174 | return line, nil 175 | } 176 | } 177 | 178 | // ParseHeaderLine parses HTTP header as key-value pair. It returns parsed 179 | // values and true if parse is ok. 180 | func ParseHeaderLine(line []byte) (k, v []byte, ok bool) { 181 | colon := bytes.IndexByte(line, ':') 182 | if colon == -1 { 183 | return 184 | } 185 | k = trim(line[:colon]) 186 | for _, c := range k { 187 | if !OctetTypes[c].IsToken() { 188 | return nil, nil, false 189 | } 190 | } 191 | v = trim(line[colon+1:]) 192 | return k, v, true 193 | } 194 | 195 | // IntFromASCII converts ascii encoded decimal numeric value from HTTP entities 196 | // to an integer. 197 | func IntFromASCII(bts []byte) (ret int, ok bool) { 198 | // ASCII numbers all start with the high-order bits 0011. 199 | // If you see that, and the next bits are 0-9 (0000 - 1001) you can grab those 200 | // bits and interpret them directly as an integer. 201 | var n int 202 | if n = len(bts); n < 1 { 203 | return 0, false 204 | } 205 | for i := 0; i < n; i++ { 206 | if bts[i]&0xf0 != 0x30 { 207 | return 0, false 208 | } 209 | ret += int(bts[i]&0xf) * pow(10, n-i-1) 210 | } 211 | return ret, true 212 | } 213 | 214 | const ( 215 | toLower = 'a' - 'A' // for use with OR. 216 | toUpper = ^byte(toLower) // for use with AND. 217 | ) 218 | 219 | // CanonicalizeHeaderKey is like standard textproto/CanonicalMIMEHeaderKey, 220 | // except that it operates with slice of bytes and modifies it inplace without 221 | // copying. 222 | func CanonicalizeHeaderKey(k []byte) { 223 | upper := true 224 | for i, c := range k { 225 | if upper && 'a' <= c && c <= 'z' { 226 | k[i] &= toUpper 227 | } else if !upper && 'A' <= c && c <= 'Z' { 228 | k[i] |= toLower 229 | } 230 | upper = c == '-' 231 | } 232 | } 233 | 234 | // pow for integers implementation. 235 | // See Donald Knuth, The Art of Computer Programming, Volume 2, Section 4.6.3 236 | func pow(a, b int) int { 237 | p := 1 238 | for b > 0 { 239 | if b&1 != 0 { 240 | p *= a 241 | } 242 | b >>= 1 243 | a *= a 244 | } 245 | return p 246 | } 247 | 248 | func split3(p []byte, sep byte) (p1, p2, p3 []byte) { 249 | a := bytes.IndexByte(p, sep) 250 | b := bytes.IndexByte(p[a+1:], sep) 251 | if a == -1 || b == -1 { 252 | return p, nil, nil 253 | } 254 | b += a + 1 255 | return p[:a], p[a+1 : b], p[b+1:] 256 | } 257 | 258 | func split2(p []byte, sep byte) (p1, p2 []byte) { 259 | i := bytes.IndexByte(p, sep) 260 | if i == -1 { 261 | return p, nil 262 | } 263 | return p[:i], p[i+1:] 264 | } 265 | 266 | func trim(p []byte) []byte { 267 | var i, j int 268 | for i = 0; i < len(p) && (p[i] == ' ' || p[i] == '\t'); { 269 | i++ 270 | } 271 | for j = len(p); j > i && (p[j-1] == ' ' || p[j-1] == '\t'); { 272 | j-- 273 | } 274 | return p[i:j] 275 | } 276 | -------------------------------------------------------------------------------- /lexer.go: -------------------------------------------------------------------------------- 1 | package httphead 2 | 3 | import ( 4 | "bytes" 5 | ) 6 | 7 | // ItemType encodes type of the lexing token. 8 | type ItemType int 9 | 10 | const ( 11 | // ItemUndef reports that token is undefined. 12 | ItemUndef ItemType = iota 13 | // ItemToken reports that token is RFC2616 token. 14 | ItemToken 15 | // ItemSeparator reports that token is RFC2616 separator. 16 | ItemSeparator 17 | // ItemString reports that token is RFC2616 quouted string. 18 | ItemString 19 | // ItemComment reports that token is RFC2616 comment. 20 | ItemComment 21 | // ItemOctet reports that token is octet slice. 22 | ItemOctet 23 | ) 24 | 25 | // Scanner represents header tokens scanner. 26 | // See https://tools.ietf.org/html/rfc2616#section-2 27 | type Scanner struct { 28 | data []byte 29 | pos int 30 | 31 | itemType ItemType 32 | itemBytes []byte 33 | 34 | err bool 35 | } 36 | 37 | // NewScanner creates new RFC2616 data scanner. 38 | func NewScanner(data []byte) *Scanner { 39 | return &Scanner{data: data} 40 | } 41 | 42 | // Next scans for next token. It returns true on successful scanning, and false 43 | // on error or EOF. 44 | func (l *Scanner) Next() bool { 45 | c, ok := l.nextChar() 46 | if !ok { 47 | return false 48 | } 49 | switch c { 50 | case '"': // quoted-string; 51 | return l.fetchQuotedString() 52 | 53 | case '(': // comment; 54 | return l.fetchComment() 55 | 56 | case '\\', ')': // unexpected chars; 57 | l.err = true 58 | return false 59 | 60 | default: 61 | return l.fetchToken() 62 | } 63 | } 64 | 65 | // FetchUntil fetches ItemOctet from current scanner position to first 66 | // occurence of the c or to the end of the underlying data. 67 | func (l *Scanner) FetchUntil(c byte) bool { 68 | l.resetItem() 69 | if l.pos == len(l.data) { 70 | return false 71 | } 72 | return l.fetchOctet(c) 73 | } 74 | 75 | // Peek reads byte at current position without advancing it. On end of data it 76 | // returns 0. 77 | func (l *Scanner) Peek() byte { 78 | if l.pos == len(l.data) { 79 | return 0 80 | } 81 | return l.data[l.pos] 82 | } 83 | 84 | // Peek2 reads two first bytes at current position without advancing it. 85 | // If there not enough data it returs 0. 86 | func (l *Scanner) Peek2() (a, b byte) { 87 | if l.pos == len(l.data) { 88 | return 0, 0 89 | } 90 | if l.pos+1 == len(l.data) { 91 | return l.data[l.pos], 0 92 | } 93 | return l.data[l.pos], l.data[l.pos+1] 94 | } 95 | 96 | // Buffered reporst how many bytes there are left to scan. 97 | func (l *Scanner) Buffered() int { 98 | return len(l.data) - l.pos 99 | } 100 | 101 | // Advance moves current position index at n bytes. It returns true on 102 | // successful move. 103 | func (l *Scanner) Advance(n int) bool { 104 | l.pos += n 105 | if l.pos > len(l.data) { 106 | l.pos = len(l.data) 107 | return false 108 | } 109 | return true 110 | } 111 | 112 | // Skip skips all bytes until first occurence of c. 113 | func (l *Scanner) Skip(c byte) { 114 | if l.err { 115 | return 116 | } 117 | // Reset scanner state. 118 | l.resetItem() 119 | 120 | if i := bytes.IndexByte(l.data[l.pos:], c); i == -1 { 121 | // Reached the end of data. 122 | l.pos = len(l.data) 123 | } else { 124 | l.pos += i + 1 125 | } 126 | } 127 | 128 | // SkipEscaped skips all bytes until first occurence of non-escaped c. 129 | func (l *Scanner) SkipEscaped(c byte) { 130 | if l.err { 131 | return 132 | } 133 | // Reset scanner state. 134 | l.resetItem() 135 | 136 | if i := ScanUntil(l.data[l.pos:], c); i == -1 { 137 | // Reached the end of data. 138 | l.pos = len(l.data) 139 | } else { 140 | l.pos += i + 1 141 | } 142 | } 143 | 144 | // Type reports current token type. 145 | func (l *Scanner) Type() ItemType { 146 | return l.itemType 147 | } 148 | 149 | // Bytes returns current token bytes. 150 | func (l *Scanner) Bytes() []byte { 151 | return l.itemBytes 152 | } 153 | 154 | func (l *Scanner) nextChar() (byte, bool) { 155 | // Reset scanner state. 156 | l.resetItem() 157 | 158 | if l.err { 159 | return 0, false 160 | } 161 | l.pos += SkipSpace(l.data[l.pos:]) 162 | if l.pos == len(l.data) { 163 | return 0, false 164 | } 165 | return l.data[l.pos], true 166 | } 167 | 168 | func (l *Scanner) resetItem() { 169 | l.itemType = ItemUndef 170 | l.itemBytes = nil 171 | } 172 | 173 | func (l *Scanner) fetchOctet(c byte) bool { 174 | i := l.pos 175 | if j := bytes.IndexByte(l.data[l.pos:], c); j == -1 { 176 | // Reached the end of data. 177 | l.pos = len(l.data) 178 | } else { 179 | l.pos += j 180 | } 181 | 182 | l.itemType = ItemOctet 183 | l.itemBytes = l.data[i:l.pos] 184 | 185 | return true 186 | } 187 | 188 | func (l *Scanner) fetchToken() bool { 189 | n, t := ScanToken(l.data[l.pos:]) 190 | if n == -1 { 191 | l.err = true 192 | return false 193 | } 194 | 195 | l.itemType = t 196 | l.itemBytes = l.data[l.pos : l.pos+n] 197 | l.pos += n 198 | 199 | return true 200 | } 201 | 202 | func (l *Scanner) fetchQuotedString() (ok bool) { 203 | l.pos++ 204 | 205 | n := ScanUntil(l.data[l.pos:], '"') 206 | if n == -1 { 207 | l.err = true 208 | return false 209 | } 210 | 211 | l.itemType = ItemString 212 | l.itemBytes = RemoveByte(l.data[l.pos:l.pos+n], '\\') 213 | l.pos += n + 1 214 | 215 | return true 216 | } 217 | 218 | func (l *Scanner) fetchComment() (ok bool) { 219 | l.pos++ 220 | 221 | n := ScanPairGreedy(l.data[l.pos:], '(', ')') 222 | if n == -1 { 223 | l.err = true 224 | return false 225 | } 226 | 227 | l.itemType = ItemComment 228 | l.itemBytes = RemoveByte(l.data[l.pos:l.pos+n], '\\') 229 | l.pos += n + 1 230 | 231 | return true 232 | } 233 | 234 | // ScanUntil scans for first non-escaped character c in given data. 235 | // It returns index of matched c and -1 if c is not found. 236 | func ScanUntil(data []byte, c byte) (n int) { 237 | for { 238 | i := bytes.IndexByte(data[n:], c) 239 | if i == -1 { 240 | return -1 241 | } 242 | n += i 243 | if n == 0 || data[n-1] != '\\' { 244 | break 245 | } 246 | n++ 247 | } 248 | return 249 | } 250 | 251 | // ScanPairGreedy scans for complete pair of opening and closing chars in greedy manner. 252 | // Note that first opening byte must not be present in data. 253 | func ScanPairGreedy(data []byte, open, close byte) (n int) { 254 | var m int 255 | opened := 1 256 | for { 257 | i := bytes.IndexByte(data[n:], close) 258 | if i == -1 { 259 | return -1 260 | } 261 | n += i 262 | // If found index is not escaped then it is the end. 263 | if n == 0 || data[n-1] != '\\' { 264 | opened-- 265 | } 266 | 267 | for m < i { 268 | j := bytes.IndexByte(data[m:i], open) 269 | if j == -1 { 270 | break 271 | } 272 | m += j + 1 273 | opened++ 274 | } 275 | 276 | if opened == 0 { 277 | break 278 | } 279 | 280 | n++ 281 | m = n 282 | } 283 | return 284 | } 285 | 286 | // RemoveByte returns data without c. If c is not present in data it returns 287 | // the same slice. If not, it copies data without c. 288 | func RemoveByte(data []byte, c byte) []byte { 289 | j := bytes.IndexByte(data, c) 290 | if j == -1 { 291 | return data 292 | } 293 | 294 | n := len(data) - 1 295 | 296 | // If character is present, than allocate slice with n-1 capacity. That is, 297 | // resulting bytes could be at most n-1 length. 298 | result := make([]byte, n) 299 | k := copy(result, data[:j]) 300 | 301 | for i := j + 1; i < n; { 302 | j = bytes.IndexByte(data[i:], c) 303 | if j != -1 { 304 | k += copy(result[k:], data[i:i+j]) 305 | i = i + j + 1 306 | } else { 307 | k += copy(result[k:], data[i:]) 308 | break 309 | } 310 | } 311 | 312 | return result[:k] 313 | } 314 | 315 | // SkipSpace skips spaces and lws-sequences from p. 316 | // It returns number ob bytes skipped. 317 | func SkipSpace(p []byte) (n int) { 318 | for len(p) > 0 { 319 | switch { 320 | case len(p) >= 3 && 321 | p[0] == '\r' && 322 | p[1] == '\n' && 323 | OctetTypes[p[2]].IsSpace(): 324 | p = p[3:] 325 | n += 3 326 | case OctetTypes[p[0]].IsSpace(): 327 | p = p[1:] 328 | n++ 329 | default: 330 | return 331 | } 332 | } 333 | return 334 | } 335 | 336 | // ScanToken scan for next token in p. It returns length of the token and its 337 | // type. It do not trim p. 338 | func ScanToken(p []byte) (n int, t ItemType) { 339 | if len(p) == 0 { 340 | return 0, ItemUndef 341 | } 342 | 343 | c := p[0] 344 | switch { 345 | case OctetTypes[c].IsSeparator(): 346 | return 1, ItemSeparator 347 | 348 | case OctetTypes[c].IsToken(): 349 | for n = 1; n < len(p); n++ { 350 | c := p[n] 351 | if !OctetTypes[c].IsToken() { 352 | break 353 | } 354 | } 355 | return n, ItemToken 356 | 357 | default: 358 | return -1, ItemUndef 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /cookie_test.go: -------------------------------------------------------------------------------- 1 | package httphead 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net/http" 7 | "testing" 8 | ) 9 | 10 | type cookieTuple struct { 11 | name, value []byte 12 | } 13 | 14 | var cookieCases = []struct { 15 | label string 16 | in []byte 17 | ok bool 18 | exp []cookieTuple 19 | 20 | c CookieScanner 21 | }{ 22 | { 23 | label: "simple", 24 | in: []byte(`foo=bar`), 25 | ok: true, 26 | exp: []cookieTuple{ 27 | {[]byte(`foo`), []byte(`bar`)}, 28 | }, 29 | }, 30 | { 31 | label: "simple", 32 | in: []byte(`foo=bar; bar=baz`), 33 | ok: true, 34 | exp: []cookieTuple{ 35 | {[]byte(`foo`), []byte(`bar`)}, 36 | {[]byte(`bar`), []byte(`baz`)}, 37 | }, 38 | }, 39 | { 40 | label: "duplicate", 41 | in: []byte(`foo=bar; bar=baz; foo=bar`), 42 | ok: true, 43 | exp: []cookieTuple{ 44 | {[]byte(`foo`), []byte(`bar`)}, 45 | {[]byte(`bar`), []byte(`baz`)}, 46 | {[]byte(`foo`), []byte(`bar`)}, 47 | }, 48 | }, 49 | { 50 | label: "quoted", 51 | in: []byte(`foo="bar"`), 52 | ok: true, 53 | exp: []cookieTuple{ 54 | {[]byte(`foo`), []byte(`bar`)}, 55 | }, 56 | }, 57 | { 58 | label: "empty value", 59 | in: []byte(`foo=`), 60 | ok: true, 61 | exp: []cookieTuple{ 62 | {[]byte(`foo`), []byte{}}, 63 | }, 64 | }, 65 | { 66 | label: "empty value", 67 | in: []byte(`foo=; bar=baz`), 68 | ok: true, 69 | exp: []cookieTuple{ 70 | {[]byte(`foo`), []byte{}}, 71 | {[]byte(`bar`), []byte(`baz`)}, 72 | }, 73 | }, 74 | { 75 | label: "quote as value", 76 | in: []byte(`foo="; bar=baz`), 77 | ok: true, 78 | exp: []cookieTuple{ 79 | {[]byte(`foo`), []byte{'"'}}, 80 | {[]byte(`bar`), []byte(`baz`)}, 81 | }, 82 | c: CookieScanner{ 83 | DisableValueValidation: true, 84 | }, 85 | }, 86 | { 87 | label: "quote as value", 88 | in: []byte(`foo="; bar=baz`), 89 | ok: true, 90 | exp: []cookieTuple{ 91 | {[]byte(`bar`), []byte(`baz`)}, 92 | }, 93 | }, 94 | { 95 | label: "skip invalid key", 96 | in: []byte(`foo@example.com=1; bar=baz`), 97 | ok: true, 98 | exp: []cookieTuple{ 99 | {[]byte("bar"), []byte("baz")}, 100 | }, 101 | }, 102 | { 103 | label: "skip invalid value", 104 | in: []byte(`foo="1; bar=baz`), 105 | ok: true, 106 | exp: []cookieTuple{ 107 | {[]byte("bar"), []byte("baz")}, 108 | }, 109 | }, 110 | { 111 | label: "trailing semicolon", 112 | in: []byte(`foo=bar;`), 113 | ok: true, 114 | exp: []cookieTuple{ 115 | {[]byte(`foo`), []byte(`bar`)}, 116 | }, 117 | }, 118 | { 119 | label: "trailing semicolon strict", 120 | in: []byte(`foo=bar;`), 121 | ok: false, 122 | exp: []cookieTuple{ 123 | {[]byte(`foo`), []byte(`bar`)}, 124 | }, 125 | c: CookieScanner{ 126 | Strict: true, 127 | }, 128 | }, 129 | { 130 | label: "want space between", 131 | in: []byte(`foo=bar;bar=baz`), 132 | ok: true, 133 | exp: []cookieTuple{ 134 | {[]byte(`foo`), []byte(`bar`)}, 135 | {[]byte(`bar`), []byte(`baz`)}, 136 | }, 137 | }, 138 | { 139 | label: "want space between strict", 140 | in: []byte(`foo=bar;bar=baz`), 141 | ok: false, 142 | exp: []cookieTuple{ 143 | {[]byte(`foo`), []byte(`bar`)}, 144 | }, 145 | c: CookieScanner{ 146 | Strict: true, 147 | }, 148 | }, 149 | { 150 | label: "value single dquote", 151 | in: []byte(`foo="bar`), 152 | ok: true, 153 | }, 154 | { 155 | label: "value single dquote", 156 | in: []byte(`foo=bar"`), 157 | ok: true, 158 | }, 159 | { 160 | label: "value single dquote", 161 | in: []byte(`foo="bar`), 162 | ok: false, 163 | c: CookieScanner{ 164 | BreakOnPairError: true, 165 | }, 166 | }, 167 | { 168 | label: "value single dquote", 169 | in: []byte(`foo=bar"`), 170 | ok: false, 171 | c: CookieScanner{ 172 | BreakOnPairError: true, 173 | }, 174 | }, 175 | { 176 | label: "value whitespace", 177 | in: []byte(`foo=bar `), 178 | ok: true, 179 | exp: []cookieTuple{ 180 | {[]byte(`foo`), []byte(`bar`)}, 181 | }, 182 | }, 183 | { 184 | label: "value whitespace strict", 185 | in: []byte(`foo=bar `), 186 | ok: false, 187 | c: CookieScanner{ 188 | Strict: true, 189 | BreakOnPairError: true, 190 | }, 191 | }, 192 | { 193 | label: "value whitespace", 194 | in: []byte(`foo=b ar`), 195 | ok: true, 196 | exp: []cookieTuple{ 197 | {[]byte(`foo`), []byte(`b ar`)}, 198 | }, 199 | }, 200 | { 201 | label: "value whitespace strict", 202 | in: []byte(`foo=b ar`), 203 | ok: false, 204 | c: CookieScanner{ 205 | Strict: true, 206 | BreakOnPairError: true, 207 | }, 208 | }, 209 | { 210 | label: "value whitespace strict", 211 | in: []byte(`foo= bar`), 212 | ok: false, 213 | c: CookieScanner{ 214 | Strict: true, 215 | BreakOnPairError: true, 216 | }, 217 | }, 218 | { 219 | label: "value quoted whitespace", 220 | in: []byte(`foo="b ar"`), 221 | ok: true, 222 | exp: []cookieTuple{ 223 | {[]byte(`foo`), []byte(`b ar`)}, 224 | }, 225 | }, 226 | { 227 | label: "value quoted whitespace strict", 228 | in: []byte(`foo="b ar"`), 229 | c: CookieScanner{ 230 | Strict: true, 231 | BreakOnPairError: true, 232 | }, 233 | }, 234 | { 235 | label: "parse ok without values", 236 | in: []byte(`foo;bar;baz=10`), 237 | ok: true, 238 | exp: []cookieTuple{ 239 | {[]byte(`foo`), []byte(``)}, 240 | {[]byte(`bar`), []byte(``)}, 241 | {[]byte(`baz`), []byte(`10`)}, 242 | }, 243 | c: CookieScanner{ 244 | Strict: false, 245 | }, 246 | }, 247 | { 248 | label: "strict parse ok without values", 249 | in: []byte(`foo; bar; baz=10`), 250 | ok: true, 251 | exp: []cookieTuple{ 252 | {[]byte(`baz`), []byte(`10`)}, 253 | }, 254 | c: CookieScanner{ 255 | Strict: true, 256 | }, 257 | }, 258 | { 259 | label: "parse ok without values", 260 | in: []byte(`foo;`), 261 | ok: true, 262 | exp: []cookieTuple{ 263 | {[]byte(`foo`), []byte(``)}, 264 | }, 265 | c: CookieScanner{ 266 | Strict: false, 267 | }, 268 | }, 269 | { 270 | label: "strict parse err without values", 271 | in: []byte(`foo;`), 272 | ok: false, 273 | exp: []cookieTuple{}, 274 | c: CookieScanner{ 275 | Strict: true, 276 | }, 277 | }, 278 | } 279 | 280 | func TestScanCookie(t *testing.T) { 281 | for _, test := range cookieCases { 282 | t.Run(test.label, func(t *testing.T) { 283 | var act []cookieTuple 284 | 285 | ok := test.c.Scan(test.in, func(k, v []byte) bool { 286 | act = append(act, cookieTuple{k, v}) 287 | return true 288 | }) 289 | if ok != test.ok { 290 | t.Errorf("unexpected result: %v; want %v", ok, test.ok) 291 | } 292 | if an, en := len(act), len(test.exp); an != en { 293 | t.Errorf("unexpected length of result: %d; want %d", an, en) 294 | } else { 295 | for i, ev := range test.exp { 296 | if av := act[i]; !bytes.Equal(av.name, ev.name) || !bytes.Equal(av.value, ev.value) { 297 | t.Errorf( 298 | "unexpected %d-th tuple: %#q=%#q; want %#q=%#q", i, 299 | string(av.name), string(av.value), 300 | string(ev.name), string(ev.value), 301 | ) 302 | } 303 | } 304 | } 305 | 306 | if test.c != DefaultCookieScanner { 307 | return 308 | } 309 | 310 | // Compare with standard library. 311 | req := http.Request{ 312 | Header: http.Header{ 313 | "Cookie": []string{string(test.in)}, 314 | }, 315 | } 316 | std := req.Cookies() 317 | if an, sn := len(act), len(std); an != sn { 318 | t.Errorf("length of result: %d; standard lib returns %d; details:\n%s", an, sn, dumpActStd(act, std)) 319 | } else { 320 | for i := 0; i < an; i++ { 321 | if a, s := act[i], std[i]; string(a.name) != s.Name || string(a.value) != s.Value { 322 | t.Errorf("%d-th cookie not equal:\n%s", i, dumpActStd(act, std)) 323 | break 324 | } 325 | } 326 | } 327 | }) 328 | } 329 | } 330 | 331 | func BenchmarkScanCookie(b *testing.B) { 332 | for _, test := range cookieCases { 333 | b.Run(test.label, func(b *testing.B) { 334 | for i := 0; i < b.N; i++ { 335 | test.c.Scan(test.in, func(_, _ []byte) bool { 336 | return true 337 | }) 338 | } 339 | }) 340 | if test.c == DefaultCookieScanner { 341 | b.Run(test.label+"_std", func(b *testing.B) { 342 | r := http.Request{ 343 | Header: http.Header{ 344 | "Cookie": []string{string(test.in)}, 345 | }, 346 | } 347 | for i := 0; i < b.N; i++ { 348 | _ = r.Cookies() 349 | } 350 | }) 351 | } 352 | } 353 | } 354 | 355 | func dumpActStd(act []cookieTuple, std []*http.Cookie) string { 356 | var buf bytes.Buffer 357 | fmt.Fprintf(&buf, "actual:\n") 358 | for i, p := range act { 359 | fmt.Fprintf(&buf, "\t#%d: %#q=%#q\n", i, p.name, p.value) 360 | } 361 | fmt.Fprintf(&buf, "standard:\n") 362 | for i, c := range std { 363 | fmt.Fprintf(&buf, "\t#%d: %#q=%#q\n", i, c.Name, c.Value) 364 | } 365 | return buf.String() 366 | } 367 | -------------------------------------------------------------------------------- /httphead.go: -------------------------------------------------------------------------------- 1 | // Package httphead contains utils for parsing HTTP and HTTP-grammar compatible 2 | // text protocols headers. 3 | // 4 | // That is, this package first aim is to bring ability to easily parse 5 | // constructions, described here https://tools.ietf.org/html/rfc2616#section-2 6 | package httphead 7 | 8 | import ( 9 | "bytes" 10 | "strings" 11 | ) 12 | 13 | // ScanTokens parses data in this form: 14 | // 15 | // list = 1#token 16 | // 17 | // It returns false if data is malformed. 18 | func ScanTokens(data []byte, it func([]byte) bool) bool { 19 | lexer := &Scanner{data: data} 20 | 21 | var ok bool 22 | for lexer.Next() { 23 | switch lexer.Type() { 24 | case ItemToken: 25 | ok = true 26 | if !it(lexer.Bytes()) { 27 | return true 28 | } 29 | case ItemSeparator: 30 | if !isComma(lexer.Bytes()) { 31 | return false 32 | } 33 | default: 34 | return false 35 | } 36 | } 37 | 38 | return ok && !lexer.err 39 | } 40 | 41 | // ParseOptions parses all header options and appends it to given slice of 42 | // Option. It returns flag of successful (wellformed input) parsing. 43 | // 44 | // Note that appended options are all consist of subslices of data. That is, 45 | // mutation of data will mutate appended options. 46 | func ParseOptions(data []byte, options []Option) ([]Option, bool) { 47 | var i int 48 | index := -1 49 | return options, ScanOptions(data, func(idx int, name, attr, val []byte) Control { 50 | if idx != index { 51 | index = idx 52 | i = len(options) 53 | options = append(options, Option{Name: name}) 54 | } 55 | if attr != nil { 56 | options[i].Parameters.Set(attr, val) 57 | } 58 | return ControlContinue 59 | }) 60 | } 61 | 62 | // SelectFlag encodes way of options selection. 63 | type SelectFlag byte 64 | 65 | // String represetns flag as string. 66 | func (f SelectFlag) String() string { 67 | var flags [2]string 68 | var n int 69 | if f&SelectCopy != 0 { 70 | flags[n] = "copy" 71 | n++ 72 | } 73 | if f&SelectUnique != 0 { 74 | flags[n] = "unique" 75 | n++ 76 | } 77 | return "[" + strings.Join(flags[:n], "|") + "]" 78 | } 79 | 80 | const ( 81 | // SelectCopy causes selector to copy selected option before appending it 82 | // to resulting slice. 83 | // If SelectCopy flag is not passed to selector, then appended options will 84 | // contain sub-slices of the initial data. 85 | SelectCopy SelectFlag = 1 << iota 86 | 87 | // SelectUnique causes selector to append only not yet existing option to 88 | // resulting slice. Unique is checked by comparing option names. 89 | SelectUnique 90 | ) 91 | 92 | // OptionSelector contains configuration for selecting Options from header value. 93 | type OptionSelector struct { 94 | // Check is a filter function that applied to every Option that possibly 95 | // could be selected. 96 | // If Check is nil all options will be selected. 97 | Check func(Option) bool 98 | 99 | // Flags contains flags for options selection. 100 | Flags SelectFlag 101 | 102 | // Alloc used to allocate slice of bytes when selector is configured with 103 | // SelectCopy flag. It will be called with number of bytes needed for copy 104 | // of single Option. 105 | // If Alloc is nil make is used. 106 | Alloc func(n int) []byte 107 | } 108 | 109 | // Select parses header data and appends it to given slice of Option. 110 | // It also returns flag of successful (wellformed input) parsing. 111 | func (s OptionSelector) Select(data []byte, options []Option) ([]Option, bool) { 112 | var current Option 113 | var has bool 114 | index := -1 115 | 116 | alloc := s.Alloc 117 | if alloc == nil { 118 | alloc = defaultAlloc 119 | } 120 | check := s.Check 121 | if check == nil { 122 | check = defaultCheck 123 | } 124 | 125 | ok := ScanOptions(data, func(idx int, name, attr, val []byte) Control { 126 | if idx != index { 127 | if has && check(current) { 128 | if s.Flags&SelectCopy != 0 { 129 | current = current.Copy(alloc(current.Size())) 130 | } 131 | options = append(options, current) 132 | has = false 133 | } 134 | if s.Flags&SelectUnique != 0 { 135 | for i := len(options) - 1; i >= 0; i-- { 136 | if bytes.Equal(options[i].Name, name) { 137 | return ControlSkip 138 | } 139 | } 140 | } 141 | index = idx 142 | current = Option{Name: name} 143 | has = true 144 | } 145 | if attr != nil { 146 | current.Parameters.Set(attr, val) 147 | } 148 | 149 | return ControlContinue 150 | }) 151 | if has && check(current) { 152 | if s.Flags&SelectCopy != 0 { 153 | current = current.Copy(alloc(current.Size())) 154 | } 155 | options = append(options, current) 156 | } 157 | 158 | return options, ok 159 | } 160 | 161 | func defaultAlloc(n int) []byte { return make([]byte, n) } 162 | func defaultCheck(Option) bool { return true } 163 | 164 | // Control represents operation that scanner should perform. 165 | type Control byte 166 | 167 | const ( 168 | // ControlContinue causes scanner to continue scan tokens. 169 | ControlContinue Control = iota 170 | // ControlBreak causes scanner to stop scan tokens. 171 | ControlBreak 172 | // ControlSkip causes scanner to skip current entity. 173 | ControlSkip 174 | ) 175 | 176 | // ScanOptions parses data in this form: 177 | // 178 | // values = 1#value 179 | // value = token *( ";" param ) 180 | // param = token [ "=" (token | quoted-string) ] 181 | // 182 | // It calls given callback with the index of the option, option itself and its 183 | // parameter (attribute and its value, both could be nil). Index is useful when 184 | // header contains multiple choises for the same named option. 185 | // 186 | // Given callback should return one of the defined Control* values. 187 | // ControlSkip means that passed key is not in caller's interest. That is, all 188 | // parameters of that key will be skipped. 189 | // ControlBreak means that no more keys and parameters should be parsed. That 190 | // is, it must break parsing immediately. 191 | // ControlContinue means that caller want to receive next parameter and its 192 | // value or the next key. 193 | // 194 | // It returns false if data is malformed. 195 | func ScanOptions(data []byte, it func(index int, option, attribute, value []byte) Control) bool { 196 | lexer := &Scanner{data: data} 197 | 198 | var ok bool 199 | var state int 200 | const ( 201 | stateKey = iota 202 | stateParamBeforeName 203 | stateParamName 204 | stateParamBeforeValue 205 | stateParamValue 206 | ) 207 | 208 | var ( 209 | index int 210 | key, param, value []byte 211 | mustCall bool 212 | ) 213 | for lexer.Next() { 214 | var ( 215 | call bool 216 | growIndex int 217 | ) 218 | 219 | t := lexer.Type() 220 | v := lexer.Bytes() 221 | 222 | switch t { 223 | case ItemToken: 224 | switch state { 225 | case stateKey, stateParamBeforeName: 226 | key = v 227 | state = stateParamBeforeName 228 | mustCall = true 229 | case stateParamName: 230 | param = v 231 | state = stateParamBeforeValue 232 | mustCall = true 233 | case stateParamValue: 234 | value = v 235 | state = stateParamBeforeName 236 | call = true 237 | default: 238 | return false 239 | } 240 | 241 | case ItemString: 242 | if state != stateParamValue { 243 | return false 244 | } 245 | value = v 246 | state = stateParamBeforeName 247 | call = true 248 | 249 | case ItemSeparator: 250 | switch { 251 | case isComma(v) && state == stateKey: 252 | // Nothing to do. 253 | 254 | case isComma(v) && state == stateParamBeforeName: 255 | state = stateKey 256 | // Make call only if we have not called this key yet. 257 | call = mustCall 258 | if !call { 259 | // If we have already called callback with the key 260 | // that just ended. 261 | index++ 262 | } else { 263 | // Else grow the index after calling callback. 264 | growIndex = 1 265 | } 266 | 267 | case isComma(v) && state == stateParamBeforeValue: 268 | state = stateKey 269 | growIndex = 1 270 | call = true 271 | 272 | case isSemicolon(v) && state == stateParamBeforeName: 273 | state = stateParamName 274 | 275 | case isSemicolon(v) && state == stateParamBeforeValue: 276 | state = stateParamName 277 | call = true 278 | 279 | case isEquality(v) && state == stateParamBeforeValue: 280 | state = stateParamValue 281 | 282 | default: 283 | return false 284 | } 285 | 286 | default: 287 | return false 288 | } 289 | 290 | if call { 291 | switch it(index, key, param, value) { 292 | case ControlBreak: 293 | // User want to stop to parsing parameters. 294 | return true 295 | 296 | case ControlSkip: 297 | // User want to skip current param. 298 | state = stateKey 299 | lexer.SkipEscaped(',') 300 | 301 | case ControlContinue: 302 | // User is interested in rest of parameters. 303 | // Nothing to do. 304 | 305 | default: 306 | panic("unexpected control value") 307 | } 308 | ok = true 309 | param = nil 310 | value = nil 311 | mustCall = false 312 | index += growIndex 313 | } 314 | } 315 | if mustCall { 316 | ok = true 317 | it(index, key, param, value) 318 | } 319 | 320 | return ok && !lexer.err 321 | } 322 | 323 | func isComma(b []byte) bool { 324 | return len(b) == 1 && b[0] == ',' 325 | } 326 | func isSemicolon(b []byte) bool { 327 | return len(b) == 1 && b[0] == ';' 328 | } 329 | func isEquality(b []byte) bool { 330 | return len(b) == 1 && b[0] == '=' 331 | } 332 | -------------------------------------------------------------------------------- /httphead_test.go: -------------------------------------------------------------------------------- 1 | package httphead 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "math/rand" 7 | "testing" 8 | ) 9 | 10 | func ExampleScanTokens() { 11 | var values []string 12 | 13 | ScanTokens([]byte(`a,b,c`), func(v []byte) bool { 14 | values = append(values, string(v)) 15 | return v[0] != 'b' 16 | }) 17 | 18 | fmt.Println(values) 19 | // Output: [a b] 20 | } 21 | 22 | func ExampleScanOptions() { 23 | foo := map[string]string{} 24 | 25 | ScanOptions([]byte(`foo;bar=1;baz`), func(index int, key, param, value []byte) Control { 26 | foo[string(param)] = string(value) 27 | return ControlContinue 28 | }) 29 | 30 | fmt.Printf("bar:%s baz:%s", foo["bar"], foo["baz"]) 31 | // Output: bar:1 baz: 32 | } 33 | 34 | func ExampleParseOptions() { 35 | options, ok := ParseOptions([]byte(`foo;bar=1,baz`), nil) 36 | fmt.Println(options, ok) 37 | // Output: [{foo [bar:1]} {baz []}] true 38 | } 39 | 40 | func ExampleParseOptionsLifetime() { 41 | data := []byte(`foo;bar=1,baz`) 42 | options, ok := ParseOptions(data, nil) 43 | copy(data, []byte(`xxx;yyy=0,zzz`)) 44 | fmt.Println(options, ok) 45 | // Output: [{xxx [yyy:0]} {zzz []}] true 46 | } 47 | 48 | var listCases = []struct { 49 | label string 50 | in []byte 51 | ok bool 52 | exp [][]byte 53 | }{ 54 | { 55 | label: "simple", 56 | in: []byte(`a,b,c`), 57 | ok: true, 58 | exp: [][]byte{ 59 | []byte(`a`), 60 | []byte(`b`), 61 | []byte(`c`), 62 | }, 63 | }, 64 | { 65 | label: "simple", 66 | in: []byte(`a,b,,c`), 67 | ok: true, 68 | exp: [][]byte{ 69 | []byte(`a`), 70 | []byte(`b`), 71 | []byte(`c`), 72 | }, 73 | }, 74 | { 75 | label: "simple", 76 | in: []byte(`a,b;c`), 77 | ok: false, 78 | exp: [][]byte{ 79 | []byte(`a`), 80 | []byte(`b`), 81 | }, 82 | }, 83 | } 84 | 85 | func TestScanTokens(t *testing.T) { 86 | for _, test := range listCases { 87 | t.Run(test.label, func(t *testing.T) { 88 | var act [][]byte 89 | ok := ScanTokens(test.in, func(v []byte) bool { 90 | act = append(act, v) 91 | return true 92 | }) 93 | if ok != test.ok { 94 | t.Errorf("unexpected result: %v; want %v", ok, test.ok) 95 | } 96 | if an, en := len(act), len(test.exp); an != en { 97 | t.Errorf("unexpected length of result: %d; want %d", an, en) 98 | } else { 99 | for i, ev := range test.exp { 100 | if av := act[i]; !bytes.Equal(av, ev) { 101 | t.Errorf("unexpected %d-th value: %#q; want %#q", i, string(av), string(ev)) 102 | } 103 | } 104 | } 105 | }) 106 | } 107 | } 108 | 109 | func BenchmarkScanTokens(b *testing.B) { 110 | for _, bench := range listCases { 111 | b.Run(bench.label, func(b *testing.B) { 112 | for i := 0; i < b.N; i++ { 113 | _ = ScanTokens(bench.in, func(v []byte) bool { return true }) 114 | } 115 | }) 116 | } 117 | } 118 | 119 | func randASCII(dst []byte) { 120 | for i := 0; i < len(dst); i++ { 121 | dst[i] = byte(rand.Intn('z'-'a')) + 'a' 122 | } 123 | } 124 | 125 | type tuple struct { 126 | index int 127 | option, attribute, value []byte 128 | } 129 | 130 | var parametersCases = []struct { 131 | label string 132 | in []byte 133 | ok bool 134 | exp []tuple 135 | }{ 136 | { 137 | label: "simple", 138 | in: []byte(`a,b,c`), 139 | ok: true, 140 | exp: []tuple{ 141 | {index: 0, option: []byte(`a`)}, 142 | {index: 1, option: []byte(`b`)}, 143 | {index: 2, option: []byte(`c`)}, 144 | }, 145 | }, 146 | { 147 | label: "simple", 148 | in: []byte(`a,b,c;foo=1;bar=2`), 149 | ok: true, 150 | exp: []tuple{ 151 | {index: 0, option: []byte(`a`)}, 152 | {index: 1, option: []byte(`b`)}, 153 | {index: 2, option: []byte(`c`), attribute: []byte(`foo`), value: []byte(`1`)}, 154 | {index: 2, option: []byte(`c`), attribute: []byte(`bar`), value: []byte(`2`)}, 155 | }, 156 | }, 157 | { 158 | label: "simple", 159 | in: []byte(`c;foo;bar=2`), 160 | ok: true, 161 | exp: []tuple{ 162 | {index: 0, option: []byte(`c`), attribute: []byte(`foo`)}, 163 | {index: 0, option: []byte(`c`), attribute: []byte(`bar`), value: []byte(`2`)}, 164 | }, 165 | }, 166 | { 167 | label: "simple", 168 | in: []byte(`foo;bar=1;baz`), 169 | ok: true, 170 | exp: []tuple{ 171 | {index: 0, option: []byte(`foo`), attribute: []byte(`bar`), value: []byte(`1`)}, 172 | {index: 0, option: []byte(`foo`), attribute: []byte(`baz`)}, 173 | }, 174 | }, 175 | { 176 | label: "simple_quoted", 177 | in: []byte(`c;bar="2"`), 178 | ok: true, 179 | exp: []tuple{ 180 | {index: 0, option: []byte(`c`), attribute: []byte(`bar`), value: []byte(`2`)}, 181 | }, 182 | }, 183 | { 184 | label: "simple_dup", 185 | in: []byte(`c;bar=1,c;bar=2`), 186 | ok: true, 187 | exp: []tuple{ 188 | {index: 0, option: []byte(`c`), attribute: []byte(`bar`), value: []byte(`1`)}, 189 | {index: 1, option: []byte(`c`), attribute: []byte(`bar`), value: []byte(`2`)}, 190 | }, 191 | }, 192 | { 193 | label: "all", 194 | in: []byte(`foo;a=1;b=2;c=3,bar;z,baz`), 195 | ok: true, 196 | exp: []tuple{ 197 | {index: 0, option: []byte(`foo`), attribute: []byte(`a`), value: []byte(`1`)}, 198 | {index: 0, option: []byte(`foo`), attribute: []byte(`b`), value: []byte(`2`)}, 199 | {index: 0, option: []byte(`foo`), attribute: []byte(`c`), value: []byte(`3`)}, 200 | {index: 1, option: []byte(`bar`), attribute: []byte(`z`)}, 201 | {index: 2, option: []byte(`baz`)}, 202 | }, 203 | }, 204 | { 205 | label: "comma", 206 | in: []byte(`foo;a=1,, , ,bar;b=2`), 207 | ok: true, 208 | exp: []tuple{ 209 | {index: 0, option: []byte(`foo`), attribute: []byte(`a`), value: []byte(`1`)}, 210 | {index: 1, option: []byte(`bar`), attribute: []byte(`b`), value: []byte(`2`)}, 211 | }, 212 | }, 213 | } 214 | 215 | func TestParameters(t *testing.T) { 216 | for _, test := range parametersCases { 217 | t.Run(test.label, func(t *testing.T) { 218 | var act []tuple 219 | 220 | ok := ScanOptions(test.in, func(index int, key, param, value []byte) Control { 221 | act = append(act, tuple{index, key, param, value}) 222 | return ControlContinue 223 | }) 224 | 225 | if ok != test.ok { 226 | t.Errorf("unexpected result: %v; want %v", ok, test.ok) 227 | } 228 | if an, en := len(act), len(test.exp); an != en { 229 | t.Errorf("unexpected length of result: %d; want %d", an, en) 230 | return 231 | } 232 | 233 | for i, e := range test.exp { 234 | a := act[i] 235 | 236 | if a.index != e.index || !bytes.Equal(a.option, e.option) || !bytes.Equal(a.attribute, e.attribute) || !bytes.Equal(a.value, e.value) { 237 | t.Errorf( 238 | "unexpected %d-th tuple: #%d %#q[%#q = %#q]; want #%d %#q[%#q = %#q]", 239 | i, 240 | a.index, string(a.option), string(a.attribute), string(a.value), 241 | e.index, string(e.option), string(e.attribute), string(e.value), 242 | ) 243 | } 244 | } 245 | }) 246 | } 247 | } 248 | 249 | func BenchmarkParameters(b *testing.B) { 250 | for _, bench := range parametersCases { 251 | b.Run(bench.label, func(b *testing.B) { 252 | for i := 0; i < b.N; i++ { 253 | _ = ScanOptions(bench.in, func(_ int, _, _, _ []byte) Control { return ControlContinue }) 254 | } 255 | }) 256 | } 257 | } 258 | 259 | var selectOptionsCases = []struct { 260 | label string 261 | selector OptionSelector 262 | in []byte 263 | p []Option 264 | exp []Option 265 | ok bool 266 | }{ 267 | { 268 | label: "simple", 269 | selector: OptionSelector{ 270 | Flags: SelectCopy | SelectUnique, 271 | }, 272 | in: []byte(`foo;a=1,foo;a=2`), 273 | p: nil, 274 | exp: []Option{ 275 | NewOption("foo", map[string]string{"a": "1"}), 276 | }, 277 | ok: true, 278 | }, 279 | { 280 | label: "simple", 281 | selector: OptionSelector{ 282 | Flags: SelectUnique, 283 | }, 284 | in: []byte(`foo;a=1,foo;a=2`), 285 | p: make([]Option, 0, 2), 286 | exp: []Option{ 287 | NewOption("foo", map[string]string{"a": "1"}), 288 | }, 289 | ok: true, 290 | }, 291 | { 292 | label: "multiparam_stack", 293 | selector: OptionSelector{ 294 | Flags: SelectUnique, 295 | }, 296 | in: []byte(`foo;a=1;b=2;c=3;d=4;e=5;f=6;g=7;h=8,bar`), 297 | p: make([]Option, 0, 2), 298 | exp: []Option{ 299 | NewOption("foo", map[string]string{ 300 | "a": "1", 301 | "b": "2", 302 | "c": "3", 303 | "d": "4", 304 | "e": "5", 305 | "f": "6", 306 | "g": "7", 307 | "h": "8", 308 | }), 309 | NewOption("bar", nil), 310 | }, 311 | ok: true, 312 | }, 313 | { 314 | label: "multiparam_stack", 315 | selector: OptionSelector{ 316 | Flags: SelectCopy | SelectUnique, 317 | }, 318 | in: []byte(`foo;a=1;b=2;c=3;d=4;e=5;f=6;g=7;h=8,bar`), 319 | p: make([]Option, 0, 2), 320 | exp: []Option{ 321 | NewOption("foo", map[string]string{ 322 | "a": "1", 323 | "b": "2", 324 | "c": "3", 325 | "d": "4", 326 | "e": "5", 327 | "f": "6", 328 | "g": "7", 329 | "h": "8", 330 | }), 331 | NewOption("bar", nil), 332 | }, 333 | ok: true, 334 | }, 335 | { 336 | label: "multiparam_heap", 337 | selector: OptionSelector{ 338 | Flags: SelectCopy | SelectUnique, 339 | }, 340 | in: []byte(`foo;a=1;b=2;c=3;d=4;e=5;f=6;g=7;h=8;i=9;j=10,bar`), 341 | p: make([]Option, 0, 2), 342 | exp: []Option{ 343 | NewOption("foo", map[string]string{ 344 | "a": "1", 345 | "b": "2", 346 | "c": "3", 347 | "d": "4", 348 | "e": "5", 349 | "f": "6", 350 | "g": "7", 351 | "h": "8", 352 | "i": "9", 353 | "j": "10", 354 | }), 355 | NewOption("bar", nil), 356 | }, 357 | ok: true, 358 | }, 359 | } 360 | 361 | func TestSelectOptions(t *testing.T) { 362 | for _, test := range selectOptionsCases { 363 | t.Run(test.label+test.selector.Flags.String(), func(t *testing.T) { 364 | act, ok := test.selector.Select(test.in, test.p) 365 | if ok != test.ok { 366 | t.Errorf("SelectOptions(%q) wellformed sign is %v; want %v", string(test.in), ok, test.ok) 367 | } 368 | if !optionsEqual(act, test.exp) { 369 | t.Errorf("SelectOptions(%q) = %v; want %v", string(test.in), act, test.exp) 370 | } 371 | }) 372 | } 373 | } 374 | 375 | func BenchmarkSelectOptions(b *testing.B) { 376 | for _, test := range selectOptionsCases { 377 | s := test.selector 378 | b.Run(test.label+s.Flags.String(), func(b *testing.B) { 379 | for i := 0; i < b.N; i++ { 380 | _, _ = s.Select(test.in, test.p) 381 | } 382 | }) 383 | } 384 | } 385 | 386 | func optionsEqual(a, b []Option) bool { 387 | if len(a) != len(b) { 388 | return false 389 | } 390 | for i := 0; i < len(a); i++ { 391 | if !a[i].Equal(b[i]) { 392 | return false 393 | } 394 | } 395 | return true 396 | } 397 | 398 | func TestOptionCopy(t *testing.T) { 399 | for i, test := range []struct { 400 | pairs int 401 | }{ 402 | {4}, 403 | {16}, 404 | } { 405 | 406 | name := []byte(fmt.Sprintf("test:%d", i)) 407 | n := make([]byte, len(name)) 408 | copy(n, name) 409 | opt := Option{Name: n} 410 | 411 | pairs := make([]pair, test.pairs) 412 | for i := 0; i < len(pairs); i++ { 413 | pair := pair{make([]byte, 8), make([]byte, 8)} 414 | randASCII(pair.key) 415 | randASCII(pair.value) 416 | pairs[i] = pair 417 | 418 | k, v := make([]byte, len(pair.key)), make([]byte, len(pair.value)) 419 | copy(k, pair.key) 420 | copy(v, pair.value) 421 | 422 | opt.Parameters.Set(k, v) 423 | } 424 | 425 | cp := opt.Copy(make([]byte, opt.Size())) 426 | 427 | memset(opt.Name, 'x') 428 | for _, p := range opt.Parameters.data() { 429 | memset(p.key, 'x') 430 | memset(p.value, 'x') 431 | } 432 | 433 | if !bytes.Equal(cp.Name, name) { 434 | t.Errorf("name was not copied properly: %q; want %q", string(cp.Name), string(name)) 435 | } 436 | for i, p := range cp.Parameters.data() { 437 | exp := pairs[i] 438 | if !bytes.Equal(p.key, exp.key) || !bytes.Equal(p.value, exp.value) { 439 | t.Errorf( 440 | "%d-th pair was not copied properly: %q=%q; want %q=%q", 441 | i, string(p.key), string(p.value), string(exp.key), string(exp.value), 442 | ) 443 | } 444 | } 445 | } 446 | } 447 | 448 | func memset(dst []byte, v byte) { 449 | copy(dst, bytes.Repeat([]byte{v}, len(dst))) 450 | } 451 | --------------------------------------------------------------------------------