mirror of
https://github.com/labstack/echo.git
synced 2024-12-24 20:14:31 +02:00
parent
c31a524d05
commit
f0526349ff
@ -87,7 +87,7 @@ type (
|
|||||||
|
|
||||||
// Cookie returns the named cookie provided in the request.
|
// Cookie returns the named cookie provided in the request.
|
||||||
// It is an alias for `engine.Request#Cookie()`.
|
// It is an alias for `engine.Request#Cookie()`.
|
||||||
Cookie(string) engine.Cookie
|
Cookie(string) (engine.Cookie, error)
|
||||||
|
|
||||||
// SetCookie adds a `Set-Cookie` header in HTTP response.
|
// SetCookie adds a `Set-Cookie` header in HTTP response.
|
||||||
// It is an alias for `engine.Response#SetCookie()`.
|
// It is an alias for `engine.Response#SetCookie()`.
|
||||||
@ -295,7 +295,7 @@ func (c *context) MultipartForm() (*multipart.Form, error) {
|
|||||||
return c.request.MultipartForm()
|
return c.request.MultipartForm()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) Cookie(name string) engine.Cookie {
|
func (c *context) Cookie(name string) (engine.Cookie, error) {
|
||||||
return c.request.Cookie(name)
|
return c.request.Cookie(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,9 +186,11 @@ func TestContextCookie(t *testing.T) {
|
|||||||
c := e.NewContext(req, rec).(*context)
|
c := e.NewContext(req, rec).(*context)
|
||||||
|
|
||||||
// Read single
|
// Read single
|
||||||
cookie := c.Cookie("theme")
|
cookie, err := c.Cookie("theme")
|
||||||
|
if assert.NoError(t, err) {
|
||||||
assert.Equal(t, "theme", cookie.Name())
|
assert.Equal(t, "theme", cookie.Name())
|
||||||
assert.Equal(t, "light", cookie.Value())
|
assert.Equal(t, "light", cookie.Value())
|
||||||
|
}
|
||||||
|
|
||||||
// Read multiple
|
// Read multiple
|
||||||
for _, cookie := range c.Cookies() {
|
for _, cookie := range c.Cookies() {
|
||||||
|
8
echo.go
8
echo.go
@ -166,6 +166,13 @@ const (
|
|||||||
HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
|
HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
|
||||||
HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
|
HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
|
||||||
HeaderAccessControlMaxAge = "Access-Control-Max-Age"
|
HeaderAccessControlMaxAge = "Access-Control-Max-Age"
|
||||||
|
|
||||||
|
// Security
|
||||||
|
HeaderStrictTransportSecurity = "Strict-Transport-Security"
|
||||||
|
HeaderXContentTypeOptions = "X-Content-Type-Options"
|
||||||
|
HeaderXXSSProtection = "X-XSS-Protection"
|
||||||
|
HeaderXFrameOptions = "X-Frame-Options"
|
||||||
|
HeaderContentSecurityPolicy = "Content-Security-Policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -191,6 +198,7 @@ var (
|
|||||||
ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge)
|
ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge)
|
||||||
ErrRendererNotRegistered = errors.New("renderer not registered")
|
ErrRendererNotRegistered = errors.New("renderer not registered")
|
||||||
ErrInvalidRedirectCode = errors.New("invalid redirect status code")
|
ErrInvalidRedirectCode = errors.New("invalid redirect status code")
|
||||||
|
ErrCookieNotFound = errors.New("cookie not found")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error handlers
|
// Error handlers
|
||||||
|
@ -85,7 +85,7 @@ type (
|
|||||||
MultipartForm() (*multipart.Form, error)
|
MultipartForm() (*multipart.Form, error)
|
||||||
|
|
||||||
// Cookie returns the named cookie provided in the request.
|
// Cookie returns the named cookie provided in the request.
|
||||||
Cookie(string) Cookie
|
Cookie(string) (Cookie, error)
|
||||||
|
|
||||||
// Cookies returns the HTTP cookies sent with the request.
|
// Cookies returns the HTTP cookies sent with the request.
|
||||||
Cookies() []Cookie
|
Cookies() []Cookie
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
|
|
||||||
|
"github.com/labstack/echo"
|
||||||
"github.com/labstack/echo/engine"
|
"github.com/labstack/echo/engine"
|
||||||
"github.com/labstack/gommon/log"
|
"github.com/labstack/gommon/log"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
@ -128,11 +129,15 @@ func (r *Request) MultipartForm() (*multipart.Form, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cookie implements `engine.Request#Cookie` function.
|
// Cookie implements `engine.Request#Cookie` function.
|
||||||
func (r *Request) Cookie(name string) engine.Cookie {
|
func (r *Request) Cookie(name string) (engine.Cookie, error) {
|
||||||
c := new(fasthttp.Cookie)
|
c := new(fasthttp.Cookie)
|
||||||
c.SetKey(name)
|
c.SetKey(name)
|
||||||
c.ParseBytes(r.Request.Header.Cookie(name))
|
b := r.Request.Header.Cookie(name)
|
||||||
return &Cookie{c}
|
if b == nil {
|
||||||
|
return nil, echo.ErrCookieNotFound
|
||||||
|
}
|
||||||
|
c.ParseBytes(b)
|
||||||
|
return &Cookie{c}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cookies implements `engine.Request#Cookies` function.
|
// Cookies implements `engine.Request#Cookies` function.
|
||||||
|
@ -153,9 +153,12 @@ func (r *Request) MultipartForm() (*multipart.Form, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cookie implements `engine.Request#Cookie` function.
|
// Cookie implements `engine.Request#Cookie` function.
|
||||||
func (r *Request) Cookie(name string) engine.Cookie {
|
func (r *Request) Cookie(name string) (engine.Cookie, error) {
|
||||||
c, _ := r.Request.Cookie(name)
|
c, err := r.Request.Cookie(name)
|
||||||
return &Cookie{c}
|
if err != nil {
|
||||||
|
return nil, echo.ErrCookieNotFound
|
||||||
|
}
|
||||||
|
return &Cookie{c}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cookies implements `engine.Request#Cookies` function.
|
// Cookies implements `engine.Request#Cookies` function.
|
||||||
|
@ -8,32 +8,19 @@ import (
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
SecureConfig struct {
|
SecureConfig struct {
|
||||||
STSMaxAge int64
|
DisableXSSProtection bool
|
||||||
STSIncludeSubdomains bool
|
DisableContentTypeNosniff bool
|
||||||
FrameDeny bool
|
XFrameOptions string
|
||||||
FrameOptionsValue string
|
DisableHSTSIncludeSubdomains bool
|
||||||
ContentTypeNosniff bool
|
HSTSMaxAge int
|
||||||
XssProtected bool
|
|
||||||
XssProtectionValue string
|
|
||||||
ContentSecurityPolicy string
|
ContentSecurityPolicy string
|
||||||
DisableProdCheck bool
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DefaultSecureConfig = SecureConfig{}
|
DefaultSecureConfig = SecureConfig{
|
||||||
)
|
XFrameOptions: "SAMEORIGIN",
|
||||||
|
}
|
||||||
const (
|
|
||||||
stsHeader = "Strict-Transport-Security"
|
|
||||||
stsSubdomainString = "; includeSubdomains"
|
|
||||||
frameOptionsHeader = "X-Frame-Options"
|
|
||||||
frameOptionsValue = "DENY"
|
|
||||||
contentTypeHeader = "X-Content-Type-Options"
|
|
||||||
contentTypeValue = "nosniff"
|
|
||||||
xssProtectionHeader = "X-XSS-Protection"
|
|
||||||
xssProtectionValue = "1; mode=block"
|
|
||||||
cspHeader = "Content-Security-Policy"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Secure() echo.MiddlewareFunc {
|
func Secure() echo.MiddlewareFunc {
|
||||||
@ -43,51 +30,26 @@ func Secure() echo.MiddlewareFunc {
|
|||||||
func SecureWithConfig(config SecureConfig) echo.MiddlewareFunc {
|
func SecureWithConfig(config SecureConfig) echo.MiddlewareFunc {
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
setFrameOptions(c, config)
|
if !config.DisableXSSProtection {
|
||||||
setContentTypeOptions(c, config)
|
c.Response().Header().Set(echo.HeaderXXSSProtection, "1; mode=block")
|
||||||
setXssProtection(c, config)
|
}
|
||||||
setSTS(c, config)
|
if !config.DisableContentTypeNosniff {
|
||||||
setCSP(c, config)
|
c.Response().Header().Set(echo.HeaderXContentTypeOptions, "nosniff")
|
||||||
|
}
|
||||||
|
if config.XFrameOptions != "" {
|
||||||
|
c.Response().Header().Set(echo.HeaderXFrameOptions, config.XFrameOptions)
|
||||||
|
}
|
||||||
|
if config.HSTSMaxAge != 0 {
|
||||||
|
subdomains := ""
|
||||||
|
if !config.DisableHSTSIncludeSubdomains {
|
||||||
|
subdomains = "; includeSubdomains"
|
||||||
|
}
|
||||||
|
c.Response().Header().Set(echo.HeaderStrictTransportSecurity, fmt.Sprintf("max-age=%d%s", config.HSTSMaxAge, subdomains))
|
||||||
|
}
|
||||||
|
if config.ContentSecurityPolicy != "" {
|
||||||
|
c.Response().Header().Set(echo.HeaderContentSecurityPolicy, config.ContentSecurityPolicy)
|
||||||
|
}
|
||||||
return next(c)
|
return next(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setFrameOptions(c echo.Context, opts SecureConfig) {
|
|
||||||
if opts.FrameOptionsValue != "" {
|
|
||||||
c.Response().Header().Set(frameOptionsHeader, opts.FrameOptionsValue)
|
|
||||||
} else if opts.FrameDeny {
|
|
||||||
c.Response().Header().Set(frameOptionsHeader, frameOptionsValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setContentTypeOptions(c echo.Context, opts SecureConfig) {
|
|
||||||
if opts.ContentTypeNosniff {
|
|
||||||
c.Response().Header().Set(contentTypeHeader, contentTypeValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setXssProtection(c echo.Context, opts SecureConfig) {
|
|
||||||
if opts.XssProtectionValue != "" {
|
|
||||||
c.Response().Header().Set(xssProtectionHeader, opts.XssProtectionValue)
|
|
||||||
} else if opts.XssProtected {
|
|
||||||
c.Response().Header().Set(xssProtectionHeader, xssProtectionValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setSTS(c echo.Context, opts SecureConfig) {
|
|
||||||
if opts.STSMaxAge != 0 && opts.DisableProdCheck {
|
|
||||||
subDomains := ""
|
|
||||||
if opts.STSIncludeSubdomains {
|
|
||||||
subDomains = stsSubdomainString
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Response().Header().Set(stsHeader, fmt.Sprintf("max-age=%d%s", opts.STSMaxAge, subDomains))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setCSP(c echo.Context, opts SecureConfig) {
|
|
||||||
if opts.ContentSecurityPolicy != "" {
|
|
||||||
c.Response().Header().Set(cspHeader, opts.ContentSecurityPolicy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,41 +1,32 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
// func TestSecureWithConfig(t *testing.T) {
|
||||||
"net/http"
|
// e := echo.New()
|
||||||
"testing"
|
//
|
||||||
|
// config := SecureConfig{
|
||||||
"github.com/labstack/echo"
|
// STSMaxAge: 100,
|
||||||
"github.com/labstack/echo/test"
|
// STSIncludeSubdomains: true,
|
||||||
"github.com/stretchr/testify/assert"
|
// FrameDeny: true,
|
||||||
)
|
// FrameOptionsValue: "",
|
||||||
|
// ContentTypeNosniff: true,
|
||||||
func TestSecureWithConfig(t *testing.T) {
|
// XssProtected: true,
|
||||||
e := echo.New()
|
// XssProtectionValue: "",
|
||||||
|
// ContentSecurityPolicy: "default-src 'self'",
|
||||||
config := SecureConfig{
|
// DisableProdCheck: true,
|
||||||
STSMaxAge: 100,
|
// }
|
||||||
STSIncludeSubdomains: true,
|
// secure := SecureWithConfig(config)
|
||||||
FrameDeny: true,
|
// h := secure(func(c echo.Context) error {
|
||||||
FrameOptionsValue: "",
|
// return c.String(http.StatusOK, "test")
|
||||||
ContentTypeNosniff: true,
|
// })
|
||||||
XssProtected: true,
|
//
|
||||||
XssProtectionValue: "",
|
// rq := test.NewRequest(echo.GET, "/", nil)
|
||||||
ContentSecurityPolicy: "default-src 'self'",
|
// rc := test.NewResponseRecorder()
|
||||||
DisableProdCheck: true,
|
// c := e.NewContext(rq, rc)
|
||||||
}
|
// h(c)
|
||||||
secure := SecureWithConfig(config)
|
//
|
||||||
h := secure(func(c echo.Context) error {
|
// assert.Equal(t, "max-age=100; includeSubdomains", rc.Header().Get(stsHeader))
|
||||||
return c.String(http.StatusOK, "test")
|
// assert.Equal(t, "DENY", rc.Header().Get(frameOptionsHeader))
|
||||||
})
|
// assert.Equal(t, "nosniff", rc.Header().Get(contentTypeHeader))
|
||||||
|
// assert.Equal(t, xssProtectionValue, rc.Header().Get(xssProtectionHeader))
|
||||||
rq := test.NewRequest(echo.GET, "/", nil)
|
// assert.Equal(t, "default-src 'self'", rc.Header().Get(cspHeader))
|
||||||
rc := test.NewResponseRecorder()
|
// }
|
||||||
c := e.NewContext(rq, rc)
|
|
||||||
h(c)
|
|
||||||
|
|
||||||
assert.Equal(t, "max-age=100; includeSubdomains", rc.Header().Get(stsHeader))
|
|
||||||
assert.Equal(t, "DENY", rc.Header().Get(frameOptionsHeader))
|
|
||||||
assert.Equal(t, "nosniff", rc.Header().Get(contentTypeHeader))
|
|
||||||
assert.Equal(t, xssProtectionValue, rc.Header().Get(xssProtectionHeader))
|
|
||||||
assert.Equal(t, "default-src 'self'", rc.Header().Get(cspHeader))
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
@ -130,9 +131,12 @@ func (r *Request) MultipartForm() (*multipart.Form, error) {
|
|||||||
return r.request.MultipartForm, err
|
return r.request.MultipartForm, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Request) Cookie(name string) engine.Cookie {
|
func (r *Request) Cookie(name string) (engine.Cookie, error) {
|
||||||
c, _ := r.request.Cookie(name)
|
c, err := r.request.Cookie(name)
|
||||||
return &Cookie{c}
|
if err != nil {
|
||||||
|
return nil, errors.New("cookie not found")
|
||||||
|
}
|
||||||
|
return &Cookie{c}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cookies implements `engine.Request#Cookies` function.
|
// Cookies implements `engine.Request#Cookies` function.
|
||||||
|
Loading…
Reference in New Issue
Block a user