mirror of
https://github.com/valyala/fasthttp.git
synced 2026-06-14 15:56:44 +03:00
Do case insensitive comparisons for headers and cookies
This commit is contained in:
committed by
Kirill Danshin
parent
c6fd90e432
commit
7796335d5f
@@ -17,6 +17,44 @@ import (
|
||||
"github.com/valyala/fasthttp/fasthttputil"
|
||||
)
|
||||
|
||||
func TestClientHeaderCase(t *testing.T) {
|
||||
ln := fasthttputil.NewInmemoryListener()
|
||||
defer ln.Close()
|
||||
|
||||
go func() {
|
||||
c, err := ln.Accept()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c.Write([]byte("HTTP/1.1 200 OK\r\n" +
|
||||
"content-type: text/plain\r\n" +
|
||||
"transfer-encoding: chunked\r\n\r\n" +
|
||||
"24\r\nThis is the data in the first chunk \r\n" +
|
||||
"1B\r\nand this is the second one \r\n" +
|
||||
"0\r\n\r\n",
|
||||
))
|
||||
}()
|
||||
|
||||
c := &Client{
|
||||
Dial: func(addr string) (net.Conn, error) {
|
||||
return ln.Dial()
|
||||
},
|
||||
ReadTimeout: time.Millisecond * 10,
|
||||
|
||||
// Even without name normalizing we should parse headers correctly.
|
||||
DisableHeaderNamesNormalizing: true,
|
||||
}
|
||||
|
||||
code, body, err := c.Get(nil, "http://example.com")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if code != 200 {
|
||||
t.Errorf("expected status code 200 got %d", code)
|
||||
} else if string(body) != "This is the data in the first chunk and this is the second one " {
|
||||
t.Errorf("wrong body: %q", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientReadTimeout(t *testing.T) {
|
||||
// This test is rather slow and increase the total test time
|
||||
// from 2.5 seconds to 6.5 seconds.
|
||||
|
||||
@@ -272,34 +272,49 @@ func (c *Cookie) ParseBytes(src []byte) error {
|
||||
c.value = append(c.value[:0], kv.value...)
|
||||
|
||||
for s.next(kv) {
|
||||
if len(kv.key) == 0 && len(kv.value) == 0 {
|
||||
continue
|
||||
}
|
||||
switch string(kv.key) {
|
||||
case "expires":
|
||||
v := b2s(kv.value)
|
||||
// Try the same two formats as net/http
|
||||
// See: https://github.com/golang/go/blob/00379be17e63a5b75b3237819392d2dc3b313a27/src/net/http/cookie.go#L133-L135
|
||||
exptime, err := time.ParseInLocation(time.RFC1123, v, time.UTC)
|
||||
if err != nil {
|
||||
exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", v)
|
||||
if err != nil {
|
||||
return err
|
||||
if len(kv.key) != 0 {
|
||||
// Case insensitive switch on first char
|
||||
switch kv.key[0] | 0x20 {
|
||||
case 'e': // "expires"
|
||||
if caseInsensitiveCompare(strCookieExpires, kv.key) {
|
||||
v := b2s(kv.value)
|
||||
// Try the same two formats as net/http
|
||||
// See: https://github.com/golang/go/blob/00379be17e63a5b75b3237819392d2dc3b313a27/src/net/http/cookie.go#L133-L135
|
||||
exptime, err := time.ParseInLocation(time.RFC1123, v, time.UTC)
|
||||
if err != nil {
|
||||
exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
c.expire = exptime
|
||||
}
|
||||
|
||||
case 'd': // "domain"
|
||||
if caseInsensitiveCompare(strCookieDomain, kv.key) {
|
||||
c.domain = append(c.domain[:0], kv.value...)
|
||||
}
|
||||
|
||||
case 'p': // "path"
|
||||
if caseInsensitiveCompare(strCookiePath, kv.key) {
|
||||
c.path = append(c.path[:0], kv.value...)
|
||||
}
|
||||
}
|
||||
c.expire = exptime
|
||||
case "domain":
|
||||
c.domain = append(c.domain[:0], kv.value...)
|
||||
case "path":
|
||||
c.path = append(c.path[:0], kv.value...)
|
||||
case "":
|
||||
switch string(kv.value) {
|
||||
case "HttpOnly":
|
||||
c.httpOnly = true
|
||||
case "secure":
|
||||
c.secure = true
|
||||
|
||||
} else if len(kv.value) != 0 {
|
||||
// Case insensitive switch on first char
|
||||
switch kv.value[0] | 0x20 {
|
||||
case 'h': // "httponly"
|
||||
if caseInsensitiveCompare(strCookieHTTPOnly, kv.value) {
|
||||
c.httpOnly = true
|
||||
}
|
||||
|
||||
case 's': // "secure"
|
||||
if caseInsensitiveCompare(strCookieSecure, kv.value) {
|
||||
c.secure = true
|
||||
}
|
||||
}
|
||||
}
|
||||
} // else empty or no match
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -412,3 +427,17 @@ func decodeCookieArg(dst, src []byte, skipQuotes bool) []byte {
|
||||
}
|
||||
return append(dst[:0], src...)
|
||||
}
|
||||
|
||||
// caseInsensitiveCompare does a case insensitive equality comparison of
|
||||
// two []byte. Assumes only letters need to be matched.
|
||||
func caseInsensitiveCompare(a, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(a); i++ {
|
||||
if a[i]|0x20 != b[i]|0x20 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
+1
-1
@@ -175,7 +175,7 @@ func TestCookieParse(t *testing.T) {
|
||||
testCookieParse(t, "foo=", "foo=")
|
||||
testCookieParse(t, `foo="bar"`, "foo=bar")
|
||||
testCookieParse(t, `"foo"=bar`, `"foo"=bar`)
|
||||
testCookieParse(t, "foo=bar; domain=aaa.com; path=/foo/bar", "foo=bar; domain=aaa.com; path=/foo/bar")
|
||||
testCookieParse(t, "foo=bar; Domain=aaa.com; PATH=/foo/bar", "foo=bar; domain=aaa.com; path=/foo/bar")
|
||||
testCookieParse(t, " xxx = yyy ; path=/a/b;;;domain=foobar.com ; expires= Tue, 10 Nov 2009 23:00:00 GMT ; ;;",
|
||||
"xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b")
|
||||
}
|
||||
|
||||
@@ -1773,36 +1773,49 @@ func (h *ResponseHeader) parseHeaders(buf []byte) (int, error) {
|
||||
var err error
|
||||
var kv *argsKV
|
||||
for s.next() {
|
||||
switch string(s.key) {
|
||||
case "Content-Type":
|
||||
h.contentType = append(h.contentType[:0], s.value...)
|
||||
case "Server":
|
||||
h.server = append(h.server[:0], s.value...)
|
||||
case "Content-Length":
|
||||
if h.contentLength != -1 {
|
||||
if h.contentLength, err = parseContentLength(s.value); err != nil {
|
||||
h.contentLength = -2
|
||||
} else {
|
||||
h.contentLengthBytes = append(h.contentLengthBytes[:0], s.value...)
|
||||
if len(s.key) > 0 {
|
||||
switch s.key[0] | 0x20 {
|
||||
case 'c':
|
||||
if caseInsensitiveCompare(s.key, strContentType) {
|
||||
h.contentType = append(h.contentType[:0], s.value...)
|
||||
continue
|
||||
} else if caseInsensitiveCompare(s.key, strContentLength) {
|
||||
if h.contentLength != -1 {
|
||||
if h.contentLength, err = parseContentLength(s.value); err != nil {
|
||||
h.contentLength = -2
|
||||
} else {
|
||||
h.contentLengthBytes = append(h.contentLengthBytes[:0], s.value...)
|
||||
}
|
||||
}
|
||||
continue
|
||||
} else if caseInsensitiveCompare(s.key, strConnection) {
|
||||
if bytes.Equal(s.value, strClose) {
|
||||
h.connectionClose = true
|
||||
} else {
|
||||
h.connectionClose = false
|
||||
h.h = appendArgBytes(h.h, s.key, s.value)
|
||||
}
|
||||
continue
|
||||
}
|
||||
case 's':
|
||||
if caseInsensitiveCompare(s.key, strServer) {
|
||||
h.server = append(h.server[:0], s.value...)
|
||||
continue
|
||||
} else if caseInsensitiveCompare(s.key, strSetCookie) {
|
||||
h.cookies, kv = allocArg(h.cookies)
|
||||
kv.key = getCookieKey(kv.key, s.value)
|
||||
kv.value = append(kv.value[:0], s.value...)
|
||||
continue
|
||||
}
|
||||
case 't':
|
||||
if caseInsensitiveCompare(s.key, strTransferEncoding) {
|
||||
if !bytes.Equal(s.value, strIdentity) {
|
||||
h.contentLength = -1
|
||||
h.h = setArgBytes(h.h, strTransferEncoding, strChunked)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
case "Transfer-Encoding":
|
||||
if !bytes.Equal(s.value, strIdentity) {
|
||||
h.contentLength = -1
|
||||
h.h = setArgBytes(h.h, strTransferEncoding, strChunked)
|
||||
}
|
||||
case "Set-Cookie":
|
||||
h.cookies, kv = allocArg(h.cookies)
|
||||
kv.key = getCookieKey(kv.key, s.value)
|
||||
kv.value = append(kv.value[:0], s.value...)
|
||||
case "Connection":
|
||||
if bytes.Equal(s.value, strClose) {
|
||||
h.connectionClose = true
|
||||
} else {
|
||||
h.connectionClose = false
|
||||
h.h = appendArgBytes(h.h, s.key, s.value)
|
||||
}
|
||||
default:
|
||||
h.h = appendArgBytes(h.h, s.key, s.value)
|
||||
}
|
||||
}
|
||||
@@ -1835,36 +1848,51 @@ func (h *RequestHeader) parseHeaders(buf []byte) (int, error) {
|
||||
s.disableNormalizing = h.disableNormalizing
|
||||
var err error
|
||||
for s.next() {
|
||||
switch string(s.key) {
|
||||
case "Host":
|
||||
h.host = append(h.host[:0], s.value...)
|
||||
case "User-Agent":
|
||||
h.userAgent = append(h.userAgent[:0], s.value...)
|
||||
case "Content-Type":
|
||||
h.contentType = append(h.contentType[:0], s.value...)
|
||||
case "Content-Length":
|
||||
if h.contentLength != -1 {
|
||||
if h.contentLength, err = parseContentLength(s.value); err != nil {
|
||||
h.contentLength = -2
|
||||
} else {
|
||||
h.contentLengthBytes = append(h.contentLengthBytes[:0], s.value...)
|
||||
if len(s.key) > 0 {
|
||||
switch s.key[0] | 0x20 {
|
||||
case 'h':
|
||||
if caseInsensitiveCompare(s.key, strHost) {
|
||||
h.host = append(h.host[:0], s.value...)
|
||||
continue
|
||||
}
|
||||
case 'u':
|
||||
if caseInsensitiveCompare(s.key, strUserAgent) {
|
||||
h.userAgent = append(h.userAgent[:0], s.value...)
|
||||
continue
|
||||
}
|
||||
case 'c':
|
||||
if caseInsensitiveCompare(s.key, strContentType) {
|
||||
h.contentType = append(h.contentType[:0], s.value...)
|
||||
continue
|
||||
} else if caseInsensitiveCompare(s.key, strContentLength) {
|
||||
if h.contentLength != -1 {
|
||||
if h.contentLength, err = parseContentLength(s.value); err != nil {
|
||||
h.contentLength = -2
|
||||
} else {
|
||||
h.contentLengthBytes = append(h.contentLengthBytes[:0], s.value...)
|
||||
}
|
||||
}
|
||||
continue
|
||||
} else if caseInsensitiveCompare(s.key, strConnection) {
|
||||
if bytes.Equal(s.value, strClose) {
|
||||
h.connectionClose = true
|
||||
} else {
|
||||
h.connectionClose = false
|
||||
h.h = appendArgBytes(h.h, s.key, s.value)
|
||||
}
|
||||
continue
|
||||
}
|
||||
case 't':
|
||||
if caseInsensitiveCompare(s.key, strTransferEncoding) {
|
||||
if !bytes.Equal(s.value, strIdentity) {
|
||||
h.contentLength = -1
|
||||
h.h = setArgBytes(h.h, strTransferEncoding, strChunked)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
case "Transfer-Encoding":
|
||||
if !bytes.Equal(s.value, strIdentity) {
|
||||
h.contentLength = -1
|
||||
h.h = setArgBytes(h.h, strTransferEncoding, strChunked)
|
||||
}
|
||||
case "Connection":
|
||||
if bytes.Equal(s.value, strClose) {
|
||||
h.connectionClose = true
|
||||
} else {
|
||||
h.connectionClose = false
|
||||
h.h = appendArgBytes(h.h, s.key, s.value)
|
||||
}
|
||||
default:
|
||||
h.h = appendArgBytes(h.h, s.key, s.value)
|
||||
}
|
||||
h.h = appendArgBytes(h.h, s.key, s.value)
|
||||
}
|
||||
if s.err != nil {
|
||||
h.connectionClose = true
|
||||
|
||||
Reference in New Issue
Block a user