Repository: cloudwego/hertz
Pull request #130 (2022-07-11 13:33:29)

feat: add a warning log for invalid character in Cookie.Value
#### What type of PR is this?

feat

<!--
Add one of the following kinds:

build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
docs: Documentation only changes
feat: A new feature
optimize: A new optimization
fix: A bug fix
perf: A code change that improves performance
refactor: A code change that neither fixes a bug nor adds a feature
style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
test: Adding missing tests or correcting existing tests
chore: Changes to the build process or auxiliary tools and libraries such as documentation generation
-->

#### What this PR does / why we need it (English/Chinese):

EN: add a warning log for invalid character in Cookie.Value
ZH：如果 Cookie.Value 中存在非法字符，则打印告警日志

<!--
The description will be attached in Release Notes, 
so please describe it from user-oriented.
-->


Patch:
diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml
index 4ba7f71..2f5c0c0 100644
--- a/.github/workflows/pr-check.yml
+++ b/.github/workflows/pr-check.yml
@@ -9,7 +9,9 @@ jobs:
       - uses: actions/checkout@v3
 
       - name: Check License Header
-        uses: apache/skywalking-eyes/header@501a28d2fb4a9b962661987e50cf0219631b32ff
+        uses: apache/skywalking-eyes@main
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 
       - name: typos-action
         uses: crate-ci/typos@master
diff --git a/cmd/hz/internal/thrift/resolver.go b/cmd/hz/internal/thrift/resolver.go
index db7e066..2a05712 100644
--- a/cmd/hz/internal/thrift/resolver.go
+++ b/cmd/hz/internal/thrift/resolver.go
@@ -185,7 +185,7 @@ func (resolver *Resolver) ResolveConstantValue(constant *parser.ConstValue) (mod
 	case parser.ConstType_ConstDouble:
 		return model.DoubleExpression{Src: constant.TypedValue.GetDouble()}, nil
 	case parser.ConstType_ConstLiteral:
-		return model.StringExpression{Src: constant.TypedValue.GetLiteral()}, nil
+		return model.StringExpression{Src: string(constant.TypedValue.GetLiteral())}, nil
 	case parser.ConstType_ConstList:
 		eleType, err := switchConstantType(constant.Type)
 		if err != nil {
diff --git a/internal/bytesconv/bytesconv_table.go b/internal/bytesconv/bytesconv_table.go
index 88a18dd..5668f2e 100644
--- a/internal/bytesconv/bytesconv_table.go
+++ b/internal/bytesconv/bytesconv_table.go
@@ -50,4 +50,5 @@ const (
 	ToUpperTable                = "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~\u007f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
 	QuotedArgShouldEscapeTable  = "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
 	QuotedPathShouldEscapeTable = "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x01\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
+	ValidCookieValueTable       = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
 )
diff --git a/internal/bytesconv/bytesconv_table_gen.go b/internal/bytesconv/bytesconv_table_gen.go
index fe075e9..11fdfd9 100644
--- a/internal/bytesconv/bytesconv_table_gen.go
+++ b/internal/bytesconv/bytesconv_table_gen.go
@@ -140,6 +140,25 @@ func main() {
 		return a
 	}()
 
+	validCookieValueTable := func() [256]byte {
+		// The implementation here is equal to net/http validCookieValueByte(b byte)
+		// see https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1
+		var a [256]byte
+		for i := 0; i < 256; i++ {
+			a[i] = 0
+		}
+
+		for i := 0x20; i < 0x7f; i++ {
+			a[i] = 1
+		}
+
+		a['"'] = 0
+		a[';'] = 0
+		a['\\'] = 0
+
+		return a
+	}()
+
 	w := new(bytes.Buffer)
 	w.WriteString(pre)
 	fmt.Fprintf(w, "const Hex2intTable = %q\n", hex2intTable)
@@ -147,6 +166,7 @@ func main() {
 	fmt.Fprintf(w, "const ToUpperTable = %q\n", toUpperTable)
 	fmt.Fprintf(w, "const QuotedArgShouldEscapeTable = %q\n", quotedArgShouldEscapeTable)
 	fmt.Fprintf(w, "const QuotedPathShouldEscapeTable = %q\n", quotedPathShouldEscapeTable)
+	fmt.Fprintf(w, "const ValidCookieValueTable = %q\n", validCookieValueTable)
 
 	if err := ioutil.WriteFile("bytesconv_table.go", w.Bytes(), 0o660); err != nil {
 		log.Fatal(err)
diff --git a/pkg/app/context.go b/pkg/app/context.go
index 8409e5e..b564a52 100644
--- a/pkg/app/context.go
+++ b/pkg/app/context.go
@@ -492,7 +492,7 @@ func (ctx *RequestContext) RequestBodyStream() io.Reader {
 	return ctx.Request.BodyStream()
 }
 
-// MultipartForm returns request's multipart form.
+// MultipartForm returns requests's multipart form.
 //
 // Returns errNoMultipartForm if request's content-type
 // isn't 'multipart/form-data'.
@@ -686,7 +686,7 @@ func (ctx *RequestContext) Set(key string, value interface{}) {
 }
 
 // Get returns the value for the given key, ie: (value, true).
-// If the value does not exist it returns (nil, false)
+// If the value does not exists it returns (nil, false)
 func (ctx *RequestContext) Get(key string) (value interface{}, exists bool) {
 	ctx.mu.RLock()
 	value, exists = ctx.Keys[key]
diff --git a/pkg/app/fs.go b/pkg/app/fs.go
index f1bcaa4..84fbaa4 100644
--- a/pkg/app/fs.go
+++ b/pkg/app/fs.go
@@ -1012,7 +1012,7 @@ func stripTrailingSlashes(path []byte) []byte {
 }
 
 func isFileCompressible(f *os.File, minCompressRatio float64) bool {
-	// Try compressing the first 4kb of the file
+	// Try compressing the first 4kb of of the file
 	// and see if it can be compressed by more than
 	// the given minCompressRatio.
 	b := bytebufferpool.Get()
diff --git a/pkg/app/middlewares/server/basic_auth/basic_auth.go b/pkg/app/middlewares/server/basic_auth/basic_auth.go
index 65abb1c..4dfc85c 100644
--- a/pkg/app/middlewares/server/basic_auth/basic_auth.go
+++ b/pkg/app/middlewares/server/basic_auth/basic_auth.go
@@ -72,7 +72,7 @@ func constructPairs(accounts Accounts) pairs {
 }
 
 // BasicAuthForRealm returns a Basic HTTP Authorization middleware. It takes as arguments a map[string]string where
-// the key is the username and the value is the password, as well as the name of the Realm.
+// the key is the user name and the value is the password, as well as the name of the Realm.
 // If the realm is empty, "Authorization Required" will be used by default.
 // (see http://tools.ietf.org/html/rfc2617#section-1.2)
 func BasicAuthForRealm(accounts Accounts, realm, userKey string) app.HandlerFunc {
@@ -95,7 +95,7 @@ func BasicAuthForRealm(accounts Accounts, realm, userKey string) app.HandlerFunc
 
 // BasicAuth is a constructor of BasicAuth verifier to hertz middleware
 // It returns a Basic HTTP Authorization middleware. It takes as argument a map[string]string where
-// the key is the username and the value is the password.
+// the key is the user name and the value is the password.
 func BasicAuth(accounts Accounts) app.HandlerFunc {
 	return BasicAuthForRealm(accounts, "Authorization Required", "user")
 }
diff --git a/pkg/app/server/option.go b/pkg/app/server/option.go
index 94ab8fa..39cbe24 100644
--- a/pkg/app/server/option.go
+++ b/pkg/app/server/option.go
@@ -159,7 +159,7 @@ func WithMaxRequestBodySize(bs int) config.Option {
 	}}
 }
 
-// WithMaxKeepBodySize sets max size of request/response body to keep when recycled. Unit: byte
+// WithMaxKeepBodySize sets max size of request/response body to keep when recycle. Unit: byte
 //
 // Body buffer which larger than this size will be put back into buffer poll.
 func WithMaxKeepBodySize(bs int) config.Option {
diff --git a/pkg/common/config/option.go b/pkg/common/config/option.go
index d1c1233..115c6e5 100644
--- a/pkg/common/config/option.go
+++ b/pkg/common/config/option.go
@@ -75,7 +75,7 @@ func (o *Options) Apply(opts []Option) {
 func NewOptions(opts []Option) *Options {
 	options := &Options{
 		// Keep-alive timeout. When idle connection exceeds this time,
-		// server will send keep-alive packets to ensure it's a validated
+		// server will send keep-alive packets to ensure it's a validate
 		// connection.
 		//
 		// NOTE: Usually there is no need to care about this value, just
diff --git a/pkg/network/standard/connection.go b/pkg/network/standard/connection.go
index bd76aa8..87a9ccb 100644
--- a/pkg/network/standard/connection.go
+++ b/pkg/network/standard/connection.go
@@ -314,11 +314,7 @@ func (c *Conn) peekBuffer(i int, buf []byte) error {
 // next loads the buf with data of size i with moving read pointer.
 func (c *Conn) next(length int, b []byte) error {
 	c.peekBuffer(length, b)
-	err := c.Skip(length)
-	if err != nil {
-		return err
-	}
-	return c.Release()
+	return c.Skip(length)
 }
 
 // fill loads more data than size i, otherwise it will block read.
diff --git a/pkg/protocol/client/client.go b/pkg/protocol/client/client.go
index fab2bf9..33cec15 100644
--- a/pkg/protocol/client/client.go
+++ b/pkg/protocol/client/client.go
@@ -173,7 +173,7 @@ func GetURLDeadline(ctx context.Context, dst []byte, url string, deadline time.T
 	req.SetOptions(requestOptions...)
 
 	// Note that the request continues execution on errTimeout until
-	// client-specific ReadTimeout exceeds. This helps to limit load
+	// client-specific ReadTimeout exceeds. This helps limiting load
 	// on slow hosts by MaxConns* concurrent requests.
 	//
 	// Without this 'hack' the load on slow host could exceed MaxConns*
diff --git a/pkg/protocol/cookie.go b/pkg/protocol/cookie.go
index 3ddd6e5..85d2ba7 100644
--- a/pkg/protocol/cookie.go
+++ b/pkg/protocol/cookie.go
@@ -50,6 +50,7 @@ import (
 	"github.com/cloudwego/hertz/internal/bytestr"
 	"github.com/cloudwego/hertz/internal/nocopy"
 	"github.com/cloudwego/hertz/pkg/common/errors"
+	"github.com/cloudwego/hertz/pkg/common/hlog"
 	"github.com/cloudwego/hertz/pkg/common/utils"
 )
 
@@ -168,11 +169,13 @@ func (c *Cookie) SetKeyBytes(key []byte) {
 
 // SetValue sets cookie value.
 func (c *Cookie) SetValue(value string) {
+	warnIfInvalid(bytesconv.S2b(value))
 	c.value = append(c.value[:0], value...)
 }
 
 // SetValueBytes sets cookie value.
 func (c *Cookie) SetValueBytes(value []byte) {
+	warnIfInvalid(value)
 	c.value = append(c.value[:0], value...)
 }
 
@@ -366,7 +369,7 @@ func (c *Cookie) ParseBytes(src []byte) error {
 
 	for s.next(kv) {
 		if len(kv.key) != 0 {
-			// Case-insensitive switch on first char
+			// Case insensitive switch on first char
 			switch kv.key[0] | 0x20 {
 			case 'm':
 				if utils.CaseInsensitiveCompare(bytestr.StrCookieMaxAge, kv.key) {
@@ -404,7 +407,7 @@ func (c *Cookie) ParseBytes(src []byte) error {
 
 			case 's': // "samesite"
 				if utils.CaseInsensitiveCompare(bytestr.StrCookieSameSite, kv.key) {
-					// Case-insensitive switch on first char
+					// Case insensitive switch on first char
 					switch kv.value[0] | 0x20 {
 					case 'l': // "lax"
 						if utils.CaseInsensitiveCompare(bytestr.StrCookieSameSiteLax, kv.value) {
@@ -422,7 +425,7 @@ func (c *Cookie) ParseBytes(src []byte) error {
 				}
 			}
 		} else if len(kv.value) != 0 {
-			// Case-insensitive switch on first char
+			// Case insensitive switch on first char
 			switch kv.value[0] | 0x20 {
 			case 'h': // "httponly"
 				if utils.CaseInsensitiveCompare(bytestr.StrCookieHTTPOnly, kv.value) {
@@ -539,3 +542,14 @@ func getCookieKey(dst, src []byte) []byte {
 	}
 	return decodeCookieArg(dst, src, false)
 }
+
+func warnIfInvalid(value []byte) bool {
+	for i := range value {
+		if bytesconv.ValidCookieValueTable[value[i]] == 0 {
+			hlog.Warnf("HERTZ: Invalid byte %q in Cookie.Value, "+
+				"it may cause compatibility problems with user agents", value[i])
+			return false
+		}
+	}
+	return true
+}
diff --git a/pkg/protocol/header.go b/pkg/protocol/header.go
index 430ec99..fe0cae8 100644
--- a/pkg/protocol/header.go
+++ b/pkg/protocol/header.go
@@ -1273,7 +1273,7 @@ func (h *RequestHeader) UserAgent() []byte {
 //     * conteNT-tYPE -> Content-Type
 //     * foo-bar-baz -> Foo-Bar-Baz
 //
-// Disable header names' normalization only if you know what are you doing.
+// Disable header names' normalization only if know what are you doing.
 func (h *RequestHeader) DisableNormalizing() {
 	h.disableNormalizing = true
 }
@@ -1446,7 +1446,7 @@ func (h *RequestHeader) SetMethodBytes(method []byte) {
 //     * conteNT-tYPE -> Content-Type
 //     * foo-bar-baz -> Foo-Bar-Baz
 //
-// Disable header names' normalization only if you know what are you doing.
+// Disable header names' normalization only if know what are you doing.
 func (h *ResponseHeader) DisableNormalizing() {
 	h.disableNormalizing = true
 }
diff --git a/pkg/protocol/http1/client.go b/pkg/protocol/http1/client.go
index d179a79..f52ccda 100644
--- a/pkg/protocol/http1/client.go
+++ b/pkg/protocol/http1/client.go
@@ -1154,7 +1154,7 @@ type ClientOptions struct {
 
 	// Maximum duration for waiting for a free connection.
 	//
-	// By default will not wait, return errNoFreeConns immediately
+	// By default will not waiting, return errNoFreeConns immediately
 	MaxConnWaitTimeout time.Duration
 
 	// RetryIf controls whether a retry should be attempted after an error.
diff --git a/pkg/protocol/request.go b/pkg/protocol/request.go
index 31aef40..7969265 100644
--- a/pkg/protocol/request.go
+++ b/pkg/protocol/request.go
@@ -204,7 +204,7 @@ func (req *Request) PostArgString() []byte {
 	return req.postArgs.QueryString()
 }
 
-// MultipartForm returns request's multipart form.
+// MultipartForm returns requests's multipart form.
 //
 // Returns errNoMultipartForm if request's Content-Type
 // isn't 'multipart/form-data'.
@@ -815,7 +815,7 @@ func AcquireRequest() *Request {
 
 // ReleaseRequest returns req acquired via AcquireRequest to request pool.
 //
-// It is forbidden accessing req and/or its members after returning
+// It is forbidden accessing req and/or its' members after returning
 // it to request pool.
 func ReleaseRequest(req *Request) {
 	req.Reset()
diff --git a/pkg/protocol/response.go b/pkg/protocol/response.go
index a170ffd..14d2f1c 100644
--- a/pkg/protocol/response.go
+++ b/pkg/protocol/response.go
@@ -383,7 +383,7 @@ func AcquireResponse() *Response {
 
 // ReleaseResponse return resp acquired via AcquireResponse to response pool.
 //
-// It is forbidden accessing resp and/or its members after returning
+// It is forbidden accessing resp and/or its' members after returning
 // it to response pool.
 func ReleaseResponse(resp *Response) {
 	resp.Reset()
diff --git a/pkg/protocol/suite/server.go b/pkg/protocol/suite/server.go
index b489e0d..f4cff7c 100644
--- a/pkg/protocol/suite/server.go
+++ b/pkg/protocol/suite/server.go
@@ -92,7 +92,7 @@ func (c *Config) LoadAll(core Core) (serverMap ServerMap, err error) {
 	return serverMap, nil
 }
 
-// New return an empty Config suite, use .Add() to add protocol impl
+// New return a empty Config suite, use .Add() to add protocol impl
 func New() *Config {
 	c := &Config{configMap: make(map[string]ServerFactory)}
 	return c
diff --git a/pkg/route/engine.go b/pkg/route/engine.go
index 0076bad..10f49b0 100644
--- a/pkg/route/engine.go
+++ b/pkg/route/engine.go
@@ -155,7 +155,7 @@ type Engine struct {
 	ctxPool sync.Pool
 
 	// Function to handle panics recovered from http handlers.
-	// It should be used to generate an error page and return the http error code
+	// It should be used to generate a error page and return the http error code
 	// 500 (Internal Server Error).
 	// The handler can be used to keep your server from crashing because of
 	// unrecovered panics.
@@ -698,7 +698,7 @@ func redirectFixedPath(c *app.RequestContext, root *node, trailingSlash bool) bo
 	return false
 }
 
-// NoRoute adds handlers for NoRoute. It returns a 404 code by default.
+// NoRoute adds handlers for NoRoute. It return a 404 code by default.
 func (engine *Engine) NoRoute(handlers ...app.HandlerFunc) {
 	engine.noRoute = handlers
 	engine.rebuild404Handlers()
@@ -756,7 +756,7 @@ func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
 	engine.funcMap = funcMap
 }
 
-// Delims sets template left and right delims and returns an Engine instance.
+// Delims sets template left and right delims and returns a Engine instance.
 func (engine *Engine) Delims(left, right string) *Engine {
 	engine.delims = render.Delims{Left: left, Right: right}
 	return engine
diff --git a/pkg/route/tree.go b/pkg/route/tree.go
index 4a648b3..0e279f6 100644
--- a/pkg/route/tree.go
+++ b/pkg/route/tree.go
@@ -152,7 +152,13 @@ func checkPathValid(path string) (valid bool) {
 // addRoute adds a node with the given handle to the path.
 func (r *router) addRoute(path string, h app.HandlersChain) {
 	checkPathValid(path)
-
+	// Validate path
+	if path == nilString {
+		path = "/"
+	}
+	if path[0] != '/' {
+		path = "/" + path
+	}
 	pnames := []string{} // Param names
 	ppath := path        // Pristine path
 


Test patch:
diff --git a/pkg/common/bytebufferpool/pool_test.go b/pkg/common/bytebufferpool/pool_test.go
index 1c2bc7e..278bb29 100644
--- a/pkg/common/bytebufferpool/pool_test.go
+++ b/pkg/common/bytebufferpool/pool_test.go
@@ -105,7 +105,7 @@ func TestPoolVariousSizesConcurrent(t *testing.T) {
 
 func testPoolVariousSizes(t *testing.T) {
 	for i := 0; i < steps+1; i++ {
-		n := 1 << uint32(i)
+		n := (1 << uint32(i))
 
 		testGetPut(t, n)
 		testGetPut(t, n+1)
diff --git a/pkg/protocol/cookie_test.go b/pkg/protocol/cookie_test.go
index dca3469..f7637e5 100644
--- a/pkg/protocol/cookie_test.go
+++ b/pkg/protocol/cookie_test.go
@@ -42,6 +42,7 @@
 package protocol
 
 import (
+	"math/rand"
 	"strings"
 	"testing"
 	"time"
@@ -357,3 +358,26 @@ func testCookieParse(t *testing.T, s, expectedS string) {
 		t.Fatalf("unexpected cookies %q. Expecting %q. Original %q", result, expectedS, s)
 	}
 }
+
+func Test_WarnIfInvalid(t *testing.T) {
+	assert.False(t, warnIfInvalid([]byte(";")))
+	assert.False(t, warnIfInvalid([]byte("\\")))
+	assert.False(t, warnIfInvalid([]byte("\"")))
+	assert.True(t, warnIfInvalid([]byte("")))
+	for i := 0; i < 5; i++ {
+		validCookie := getValidCookie()
+		assert.True(t, warnIfInvalid(validCookie))
+	}
+}
+
+func getValidCookie() []byte {
+	var validCookie []byte
+	for i := 0; i < 100; i++ {
+		r := rand.Intn(0x78-0x20) + 0x20
+		if r == ';' || r == '\\' || r == '"' {
+			continue
+		}
+		validCookie = append(validCookie, byte(r))
+	}
+	return validCookie
+}