@@ -5,7 +5,10 @@ import (
55 "bytes"
66 "encoding/base64"
77 "encoding/binary"
8+ "fmt"
9+ "net/textproto"
810 "net/url"
11+ "reflect"
912 "strings"
1013 "testing"
1114)
@@ -139,3 +142,93 @@ func FuzzURIParse(f *testing.F) {
139142 }
140143 })
141144}
145+
146+ func FuzzTestHeaderScanner (f * testing.F ) {
147+ f .Add ([]byte ("Host: example.com\r \n User-Agent: Go-http-client/1.1\r \n Accept-Encoding: gzip, deflate\r \n \r \n " ))
148+ f .Add ([]byte ("Content-Type: application/x-www-form-urlencoded\r \n Content-Length: 27\r \n \r \n name=John+Doe&age=30" ))
149+
150+ f .Fuzz (func (t * testing.T , data []byte ) {
151+ if ! bytes .Contains (data , []byte ("\r \n \r \n " )) {
152+ return
153+ }
154+
155+ t .Logf ("%q" , data )
156+
157+ tmp , herr := textproto .NewReader (bufio .NewReader (bytes .NewReader (data ))).ReadMIMEHeader ()
158+ h := map [string ][]string (tmp )
159+
160+ var s headerScanner
161+ s .b = data
162+ f := make (map [string ][]string )
163+ for s .next () {
164+ // ReadMIMEHeader normalizes header keys, headerScanner doesn't by default.
165+ normalizeHeaderKey (s .key , false )
166+
167+ // textproto.ReadMIMEHeader will validate the header value, since we compare
168+ // errors we should do this as well.
169+ for _ , c := range s .value {
170+ if ! validHeaderValueByte (c ) {
171+ s .err = fmt .Errorf ("malformed MIME header: invalid byte %q in value %q for key %q" , c , s .value , s .key )
172+ }
173+ }
174+ if s .err != nil {
175+ break
176+ }
177+
178+ key := string (s .key )
179+ value := string (s .value )
180+
181+ if _ , ok := f [key ]; ! ok {
182+ f [key ] = []string {}
183+ }
184+ f [key ] = append (f [key ], value )
185+ }
186+
187+ if s .err != nil && herr == nil {
188+ t .Errorf ("unexpected error from headerScanner: %v: %v" , s .err , h )
189+ } else if s .err == nil && herr != nil {
190+ t .Errorf ("unexpected error from textproto.NewReader: %v: %v" , herr , f )
191+ }
192+
193+ if ! reflect .DeepEqual (h , f ) {
194+ t .Errorf ("headers mismatch:\n textproto: %v\n fasthttp: %v" , h , f )
195+ }
196+ })
197+ }
198+
199+ func FuzzRequestReadLimitBodyAllocations (f * testing.F ) {
200+ f .Add ([]byte ("POST /a HTTP/1.1\r \n Host: a.com\r \n Transfer-Encoding: chunked\r \n Content-Type: aa\r \n \r \n 6\r \n foobar\r \n 3\r \n baz\r \n 0\r \n foobar\r \n \r \n " ), 1024 )
201+ f .Add ([]byte ("POST /a HTTP/1.1\r \n Host: a.com\r \n WithTabs: \t v1 \t \r \n WithTabs-Start: \t \t v1 \r \n WithTabs-End: v1 \t \t \t \t \r \n WithTabs-Multi-Line: \t v1 \t ;\r \n \t v2 \t ;\r \n \t v3\r \n \r \n " ), 1024 )
202+
203+ f .Fuzz (func (t * testing.T , body []byte , maxBodySize int ) {
204+ if len (body ) > 1024 * 1024 || maxBodySize > 1024 * 1024 {
205+ return
206+ }
207+ // Only test with a max for the body, otherwise a very large Content-Length will just OOM.
208+ if maxBodySize <= 0 {
209+ return
210+ }
211+
212+ t .Logf ("%d %q" , maxBodySize , body )
213+
214+ req := Request {}
215+ a := bytes .NewReader (body )
216+ b := bufio .NewReader (a )
217+
218+ if err := req .ReadLimitBody (b , maxBodySize ); err != nil {
219+ return
220+ }
221+
222+ n := testing .AllocsPerRun (200 , func () {
223+ req .Reset ()
224+ a .Reset (body )
225+ b .Reset (a )
226+
227+ _ = req .ReadLimitBody (b , maxBodySize )
228+ })
229+
230+ if n != 0 {
231+ t .Fatalf ("expected 0 allocations, got %f" , n )
232+ }
233+ })
234+ }
0 commit comments