diff --git a/ctx.go b/ctx.go index e70c3672..218bd538 100644 --- a/ctx.go +++ b/ctx.go @@ -497,7 +497,7 @@ func (c *Ctx) Is(extension string) bool { } return strings.HasPrefix( - utils.TrimLeft(utils.GetString(c.fasthttp.Request.Header.ContentType()), ' '), + utils.TrimLeft(utils.UnsafeString(c.fasthttp.Request.Header.ContentType()), ' '), extensionHeader, ) } @@ -704,8 +704,8 @@ func (c *Ctx) QueryParser(out interface{}) error { data := make(map[string][]string) c.fasthttp.QueryArgs().VisitAll(func(key []byte, val []byte) { - k := utils.GetString(key) - v := utils.GetString(val) + k := utils.UnsafeString(key) + v := utils.UnsafeString(val) if strings.Index(v, ",") > -1 && equalFieldType(out, reflect.Slice, k) { values := strings.Split(v, ",") for i := 0; i < len(values); i++ { @@ -920,7 +920,7 @@ func (c *Ctx) SendFile(file string, compress ...bool) error { }) // Keep original path for mutable params - c.pathOriginal = utils.ImmutableString(c.pathOriginal) + c.pathOriginal = utils.SafeString(c.pathOriginal) // Disable compression if len(compress) <= 0 || !compress[0] { // https://github.com/valyala/fasthttp/blob/master/fs.go#L46 @@ -995,7 +995,7 @@ func (c *Ctx) Set(key string, val string) { } func (c *Ctx) setCanonical(key string, val string) { - c.fasthttp.Response.Header.SetCanonical(utils.GetBytes(key), utils.GetBytes(val)) + c.fasthttp.Response.Header.SetCanonical(utils.UnsafeBytes(key), utils.UnsafeBytes(val)) } // Subdomains returns a string slice of subdomains in the domain name of the request. @@ -1072,7 +1072,7 @@ func (c *Ctx) WriteString(s string) (int, error) { // XHR returns a Boolean property, that is true, if the request's X-Requested-With header field is XMLHttpRequest, // indicating that the request was issued by a client library (such as jQuery). func (c *Ctx) XHR() bool { - return utils.EqualsFold(utils.GetBytes(c.Get(HeaderXRequestedWith)), []byte("xmlhttprequest")) + return utils.EqualsFold(utils.UnsafeBytes(c.Get(HeaderXRequestedWith)), []byte("xmlhttprequest")) } // prettifyPath ... diff --git a/helpers.go b/helpers.go index 3ddbcace..464304cd 100644 --- a/helpers.go +++ b/helpers.go @@ -111,7 +111,7 @@ func removeNewLines(raw string) string { } buf[i] = ' ' } - raw = utils.GetString(buf) + raw = utils.UnsafeString(buf) bytebufferpool.Put(bb) return raw @@ -373,13 +373,13 @@ func (c *testConn) SetReadDeadline(t time.Time) error { return nil } func (c *testConn) SetWriteDeadline(t time.Time) error { return nil } // getString converts byte slice to a string without memory allocation. -var getString = utils.GetString +var getString = utils.UnsafeString var getStringImmutable = func(b []byte) string { return string(b) } // getBytes converts string to a byte slice without memory allocation. -var getBytes = utils.GetBytes +var getBytes = utils.UnsafeBytes var getBytesImmutable = func(s string) (b []byte) { return []byte(s) } diff --git a/helpers_test.go b/helpers_test.go index 87d2a9d9..223eee2c 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -19,7 +19,7 @@ import ( func Benchmark_Utils_RemoveNewLines(b *testing.B) { withNL := "foo\r\nSet-Cookie:%20SESSIONID=MaliciousValue\r\n" withoutNL := "foo Set-Cookie:%20SESSIONID=MaliciousValue " - expected := utils.ImmutableString(withoutNL) + expected := utils.SafeString(withoutNL) var res string b.Run("withNewlines", func(b *testing.B) { diff --git a/middleware/basicauth/basicauth.go b/middleware/basicauth/basicauth.go index 2100659f..347d0e35 100644 --- a/middleware/basicauth/basicauth.go +++ b/middleware/basicauth/basicauth.go @@ -117,7 +117,7 @@ func New(config Config) fiber.Handler { if raw, err := base64.StdEncoding.DecodeString(auth[6:]); err == nil { // Convert to string - cred := utils.GetString(raw) + cred := utils.UnsafeString(raw) // Find semicolumn for i := 0; i < len(cred); i++ { diff --git a/middleware/csrf/csrf.go b/middleware/csrf/csrf.go index e2941959..bb9f4cf6 100644 --- a/middleware/csrf/csrf.go +++ b/middleware/csrf/csrf.go @@ -131,7 +131,7 @@ func New(config ...Config) fiber.Handler { return fiber.ErrForbidden } // Some magic to compare both cookie and client csrf token - if subtle.ConstantTimeCompare(utils.GetBytes(token), utils.GetBytes(csrf)) != 1 { + if subtle.ConstantTimeCompare(utils.UnsafeBytes(token), utils.UnsafeBytes(csrf)) != 1 { // Comparison failed, return forbidden return fiber.ErrForbidden } diff --git a/utils/convert.go b/utils/convert.go index eea4560c..38c2f3d7 100644 --- a/utils/convert.go +++ b/utils/convert.go @@ -13,13 +13,13 @@ import ( // #nosec G103 // GetString returns a string pointer without allocation -func GetString(b []byte) string { +func UnsafeString(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } // #nosec G103 // GetBytes returns a byte pointer without allocation -func GetBytes(s string) (bs []byte) { +func UnsafeBytes(s string) (bs []byte) { sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) bh := (*reflect.SliceHeader)(unsafe.Pointer(&bs)) bh.Data = sh.Data @@ -28,9 +28,33 @@ func GetBytes(s string) (bs []byte) { return } +// SafeString copies a string to make it immutable +func SafeString(s string) string { + return string(UnsafeBytes(s)) +} + +// SafeBytes copies a slive to make it immutable +func SafeBytes(b []byte) []byte { + tmp := make([]byte, len(b)) + copy(tmp, b) + return tmp +} + +// #nosec G103 +// GetString returns a string pointer without allocation +func GetString(b []byte) string { + return UnsafeString(b) +} + +// #nosec G103 +// GetBytes returns a byte pointer without allocation +func GetBytes(s string) []byte { + return UnsafeBytes(s) +} + // ImmutableString copies a string to make it immutable func ImmutableString(s string) string { - return string(GetBytes(s)) + return SafeString(s) } const (