Binder interface.
type DefaultBinder struct{}

// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
// Types that don't implement this, but do implement encoding.TextUnmarshaler
// will use that interface instead.
type BindUnmarshaler interface {
	// UnmarshalParam decodes and assigns a value from an form or query param.
	UnmarshalParam(param string) error
}

// bindMultipleUnmarshaler is used by binder to unmarshal multiple values from request at once to
// type implementing this int

]string) error
}

// BindPathValues binds path parameter values to bindable object
func BindPathValues(c *Context, target any) error {
	params := map[string][]string{}
	for _, param := range c.PathValues() {
		params[param.Name] = []string{param.Value}
	}

erface. For example request could have multiple query fields `?a=1&a=2&b=test` in that case
// for `a` following slice `["1", "2"] will be passed to unmarshaller.
type bindMultipleUnmarshaler interface {
	UnmarshalParams(params [


rrors in binder. Useful along with `FailFast()` method
		to do binding and returns on first problem
  `BindErrors()` returns all bind errors from binder and resets errors in binder.

	Types that are supported:
		* bool
		* float32
		* float64
		* int
		* int8
		* int16
		* int32
		* int64
		* uint
		* uint8/byte (does not support `bytes()`. Use BindUnmarshaler/CustomFunc to convert value from base64 etc to []byte{})
		* uint16
		* uint32
		* uint64
		* string
		* time
		* duration
		* BindUnmarshaler() inte

imeNano() - converts unix time with nanosecond precision (integer) to time.Time
		* CustomFunc() - callback function for your custom conversion logic. Signature `func(values []string) []error`
*/

// BindingError represents an error that occurred while bin

rface
		* TextUnmarshaler() interface
		* JSONUnmarshaler() interface
		* UnixTime() - converts unix time (integer) to time.Time
		* UnixTimeMilli() - converts unix time with millisecond precision (integer) to time.Time
		* UnixT


Name:  cmp.Or(tc.givenKey, "key"),
				Value: tc.givenValue,
			}})

			v, err := PathParam[bool](c, "key")
			if tc.expectErr != "" {
				assert.EqualError(t, err, tc.expectErr)
			} else {
				assert.NoError(t, err)
			}
			assert.Equal(t, tc.expect, v)
		})
	}
}

func TestPathParam_UnsupportedType(t *testing.T) {
	c := NewContext(nil, nil)
	c.SetPathValues(PathValues{{Name: "key", Value: "true"}})

	v, err := PathParam[[]bool](c, "key")

	expectErr := "code=400, message=path value, err=failed to parse va

		expect    bool
		expectErr string
	}{
		{
			name:     "ok",
			givenURL: "/?key=true",
			expect:   true,
		},
		{
			name:      "nok, non existent key",
			givenURL:  "/?different=true",
			expect:    false,
			expectErr: ErrNonExistentKey.Error(),

lue, err: unsupported value type: *[]bool, field=key"
	assert.EqualError(t, err, expectErr)
	assert.Equal(t, []bool(nil), v)
}

func TestQueryParam(t *testing.T) {
	var testCases = []struct {
		name      string
		givenURL  string


nse-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors

package echo

import "errors"

// ErrNonExistentKey is error that is returned when key does not exist
var ErrNonExistentKey = errors.New("non existent key")

// ErrInvalidKeyType is error that is returned when the value is not castable to expected type.
var ErrInvalidKeyType = errors.New("invalid key type")

// ContextGet retrieves a value from the context store or ErrNonExistentKey error the key is missing.
// Returns

ErrNonExistentKey
	}

	typed, ok := val.(T)
	if !ok {
		var zero T
		return zero, ErrInvalidKeyType
	}

	return typed, nil
}

// ContextGetOr retrieves a value from the context store or returns a default value when the key
// is missing. Returns ErrInvalid

 ErrInvalidKeyType error if the value is not castable to type T.
func ContextGet[T any](c *Context, key string) (T, error) {
	c.lock.RLock()
	defer c.lock.RUnlock()

	val, ok := c.store[key]
	if !ok {
		var zero T
		return zero,


ent              = "invalid content"
	userJSONInvalidType         = `{"id":"1","name":"Jon Snow"}`
	userXMLConvertNumberError   = `<user><id>Number one</id><name>Jon Snow</name></user>`
	userXMLUnsupportedTypeError = `<user><>Number one</><name>Jon Snow</name></user>`
)

const userJSONPretty = `{
  "id": 1,
  "name": "Jon Snow"
}`

const userXMLPretty = `<user>
  <id>1</id>
  <name>Jon Snow</name>
</user>`

var dummyQuery = url.Values{"dummy": []string{"useless"}}

func TestEcho(t *testing.T) {
	e := New()

sInternalServerError, rec.Code)
}

func TestNewWithConfig(t *testing.T) {
	e := NewWithConfig(Config{})
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()

	e.GET("/", func(c *Context) error {
		return c.String(http.Status

	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	// Router
	assert.NotNil(t, e.Router())

	e.HTTPErrorHandler(c, errors.New("error"))

	assert.Equal(t, http.Statu


package echo

import (
	"errors"
	"fmt"
	"net/http"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestHTTPError_StatusCode(t *testing.T) {
	var err error = &HTTPError{Code: http.StatusBadRequest, Message: "my error message"}

	code := 0
	var sc HTTPStatusCoder
	if errors.As(err, &sc) {
		code = sc.StatusCode()
	}
	assert.Equal(t, http.StatusBadRequest, code)
}

func TestHTTPError_Error(t *testing.T) {
	var testCases = []struct {
		name   string
		error  error
		expect string
	}{
		{
			name:   "

essage"},
			expect: "code=400, message=my error message",
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			assert.Equal(t, tc.expect, tc.error.Error())
		})
	}
}

func TestHTTPError_WrapUnwrap(t *testing.T) {
	err := &HTTPE

ok, without message",
			error:  &HTTPError{Code: http.StatusBadRequest},
			expect: "code=400, message=Bad Request",
		},
		{
			name:   "ok, with message",
			error:  &HTTPError{Code: http.StatusBadRequest, Message: "my error m


package echo

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestResponse(t *testing.T) {
	e := New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)
	res := NewResponse(rec, e.Logger)

	// Before
	res.Before(func() {
		c.Response().Header().Set(HeaderServer, "echo")
	})
	// After
	res.After(func() {
		c.Response().Header().Set(HeaderXFrameOptions, "DENY")
	})
	res.Write([]byte("test"))
	as

	res := NewResponse(rec, e.Logger)

	res.Write([]byte("test"))
	assert.Equal(t, http.StatusOK, rec.Code)
}

func TestResponse_Write_UsesSetResponseCode(t *testing.T) {
	e := New()
	rec := httptest.NewRecorder()
	res := NewResponse(rec, e.Logger)

	res.Stat

sert.Equal(t, "echo", rec.Header().Get(HeaderServer))
	assert.Equal(t, "DENY", rec.Header().Get(HeaderXFrameOptions))
}

func TestResponse_Write_FallsBackToDefaultStatus(t *testing.T) {
	e := New()
	rec := httptest.NewRecorder()


tx stdContext.Context, e *Echo) (string, error) {
	addrChan := make(chan string)
	errCh := make(chan error)

	go func() {
		errCh <- (&StartConfig{
			Address:         ":0",
			GracefulTimeout: 100 * time.Millisecond,
			ListenerAddrFunc: func(addr net.Addr) {
				addrChan <- addr.String()
			},
		}).Start(ctx, e)
	}()

	return waitForServerStart(addrChan, errCh)
}

func waitForServerStart(addrChan <-chan string, errCh <-chan error) (string, error) {
	waitCtx, cancel := stdContext.WithTimeout(stdContext.Bac

== http.ErrServerClosed { // was closed normally before listener callback was called. should not be possible
				return "", nil
			}
			// failed to start and we did not manage to get even listener part.
			return "", err
		}
	}
}

func doGet(url string) (

kground(), 200*time.Millisecond)
	defer cancel()

	// wait for addr to arrive
	for {
		select {
		case <-waitCtx.Done():
			return "", waitCtx.Err()
		case addr := <-addrChan:
			return addr, nil
		case err := <-errCh:
			if err


o.Context) error {
		c.Response().Write([]byte("test")) // For Content-Type sniffing
		return nil
	})

	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	err := h(c)
	assert.NoError(t, err)

	assert.Equal(t, "test", rec.Body.String())
}

func TestMustGzipWithConfig_panics(t *testing.T) {
	assert.Panics(t, func() {
		GzipWithConfig(GzipConfig{Level: 999})
	})
}

func TestGzip_AcceptEncodingHeader(t *testing.T) {
	h := Gzip()(fu

pScheme)

	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	err := h(c)
	assert.NoError(t, err)

	assert.Equal(t, gzipScheme, rec.Header().Get(echo.HeaderContentEncoding))
	assert.Contains(t, rec.Header().Get(echo.HeaderContentType), echo.MIMET

nc(c *echo.Context) error {
		c.Response().Write([]byte("test")) // For Content-Type sniffing
		return nil
	})

	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	req.Header.Set(echo.HeaderAcceptEncoding, gzi


equest      func() *http.Request
		givenPathValues   echo.PathValues
		whenLookups       string
		whenLimit         uint
		expectValues      []string
		expectSource      ExtractorSource
		expectCreateError string
		expectError       string
	}{
		{
			name: "ok, header",
			givenRequest: func() *http.Request {
				req := httptest.NewRequest(http.MethodGet, "/", nil)
				req.Header.Set(echo.HeaderAuthorization, "Bearer token")
				return req
			},
			whenLookups:  "header:Authorization:Bearer ",
			expectValu

hodPost, "/", strings.NewReader(f.Encode()))
				req.Header.Add(echo.HeaderContentType, echo.MIMEApplicationForm)
				return req
			},
			whenLookups:  "form:name",
			expectValues: []string{"Jon Snow"},
			expectSource: ExtractorSourceForm,
		},
		{
			na

es: []string{"token"},
			expectSource: ExtractorSourceHeader,
		},
		{
			name: "ok, form",
			givenRequest: func() *http.Request {
				f := make(url.Values)
				f.Set("name", "Jon Snow")

				req := httptest.NewRequest(http.Met


htText: © 2015 LabStack LLC and Echo contributors

package middleware

import (
	"fmt"
	"net/http"
	"runtime"

	"github.com/labstack/echo/v5"
)

// RecoverConfig defines the config for Recover middleware.
type RecoverConfig struct {
	// Skipper defines a function to skip middleware.
	Skipper Skipper

	// Size of the stack to be printed.
	// Optional. Default value 4KB.
	StackSize int

	// DisableStackAll disables formatting stack traces of all other goroutines
	// into buffer after the trace for the current

cover middleware config.
var DefaultRecoverConfig = RecoverConfig{
	Skipper:           DefaultSkipper,
	StackSize:         4 << 10, // 4 KB
	DisableStackAll:   false,
	DisablePrintStack: false,
}

// Recover returns a middleware which recovers from panics

 goroutine.
	// Optional. Default value false.
	DisableStackAll bool

	// DisablePrintStack disables printing stack trace.
	// Optional. Default value as false.
	DisablePrintStack bool
}

// DefaultRecoverConfig is the default Re


d do not affect which route handler is matched
	e.Use(RewriteWithConfig(RewriteConfig{
		Rules: map[string]string{
			"/old":              "/new",
			"/api/*":            "/$1",
			"/js/*":             "/public/javascripts/$1",
			"/users/*/orders/*": "/user/$1/order/$2",
		},
	}))
	e.GET("/public/*", func(c *echo.Context) error {
		return c.String(http.StatusOK, c.Param("*"))
	})
	e.GET("/*", func(c *echo.Context) error {
		return c.String(http.StatusOK, c.Param("*"))
	})

	var testCases = []struct {
		whe

ers",
			expectRequestRawPath: "",
		},
		{
			whenPath:             "/js/main.js",
			expectRoutePath:      "js/main.js",
			expectRequestPath:    "/public/javascripts/main.js",
			expectRequestRawPath: "",
		},
		{
			whenPath:             "/users/jack/o

nPath             string
		expectRoutePath      string
		expectRequestPath    string
		expectRequestRawPath string
	}{
		{
			whenPath:             "/api/users",
			expectRoutePath:      "api/users",
			expectRequestPath:    "/us


trI64" form:"PtrI64"`
	PtrI        *int       `json:"PtrI" form:"PtrI"`
	PtrI8       *int8      `json:"PtrI8" form:"PtrI8"`
	PtrF64      *float64   `json:"PtrF64" form:"PtrF64"`
	PtrUI8      *uint8     `json:"PtrUI8" form:"PtrUI8"`
	PtrUI64     *uint64    `json:"PtrUI64" form:"PtrUI64"`
	PtrUI16     *uint16    `json:"PtrUI16" form:"PtrUI16"`
	PtrS        *string    `json:"PtrS" form:"PtrS"`
	PtrUI32     *uint32    `json:"PtrUI32" form:"PtrUI32"`
	S           string     `json:"S" form:"S"`
	cantSet     strin

    `json:"UI64" form:"UI64"`
	UI          uint        `json:"UI" form:"UI"`
	I64         int64       `json:"I64" form:"I64"`
	F32         float32     `json:"F32" form:"F32"`
	UI32        uint32      `json:"UI32" form:"UI32"`
	I32         int32       `json

g
	DoesntExist string      `json:"DoesntExist" form:"DoesntExist"`
	SA          StringArray `json:"SA" form:"SA"`
	F64         float64     `json:"F64" form:"F64"`
	I           int         `json:"I" form:"I"`
	UI64        uint64


// run tests as external package to get real feel for API
package echo_test

import (
	"encoding/base64"
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"github.com/labstack/echo/v5"
)

func ExampleValueBinder_BindErrors() {
	// example route function that binds query params to different destinations and returns all bind errors in one go
	routeFunc := func(c *echo.Context) error {
		var opts struct {
			IDs    []int64
			Active bool
		}
		length := int64(50) // default length is 50

		b := echo.QueryParams

rror)
				log.Printf("in case you want to access what field: %s values: %v failed", bErr.Field, bErr.Values)
			}
			return fmt.Errorf("%v fields failed to bind", len(errs))
		}
		fmt.Printf("active = %v, length = %v, ids = %v", opts.Active, length, opts.I

Binder(c)

		errs := b.Int64("length", &length).
			Int64s("ids", &opts.IDs).
			Bool("active", &opts.Active).
			BindErrors() // returns all errors
		if errs != nil {
			for _, err := range errs {
				bErr := err.(*echo.BindingE


g time.Time values
type TimeOpts struct {
	// Layout specifies the format for parsing time values in request parameters.
	// It can be a standard Go time layout string or one of the special Unix time layouts.
	//
	// Parsing layout defaults to: echo.TimeLayout(time.RFC3339Nano)
	// - To convert to custom layout use `echo.TimeLayout("2006-01-02")`
	// - To convert unix timestamp (integer) to time.Time use `echo.TimeLayoutUnixTime`
	// - To convert unix timestamp in milliseconds to time.Time use `echo.TimeLay

 timezone information to set output time in given location.
	// Defaults to time.UTC
	ParseInLocation *time.Location

	// ToInLocation is location to which parsed time is converted to after parsing.
	// The parsed time will be converted using time.In(ToInL

outUnixTimeMilli`
	// - To convert unix timestamp in nanoseconds to time.Time use `echo.TimeLayoutUnixTimeNano`
	Layout TimeLayout

	// ParseInLocation is location used with time.ParseInLocation for layout that do not contain
	//


c := createTestContext(tc.whenURL, nil, map[string]string{"id": "999"})
			b := QueryParamsBinder(c).FailFast(tc.givenFailFast)
			id := int64(99)
			nr := int64(88)
			errs := b.Int64("id", &id).
				Int64("nr", &nr).
				BindErrors()

			assert.Len(t, errs, len(tc.expectError))
			for _, err := range errs {
				assert.Contains(t, tc.expectError, err.Error())
			}
		})
	}
}

func TestFormFieldBinder(t *testing.T) {
	e := New()
	body := `texta=foo&slice=5`
	req := httptest.NewRequest(http.MethodPost, "/api/

req, rec)

	b := FormFieldBinder(c)

	var texta string
	id := int64(99)
	nr := int64(88)
	var slice = make([]int64, 0)
	var notExisting = make([]int64, 0)
	err := b.
		Int64s("slice", &slice).
		Int64("id", &id).
		Int64("nr", &nr).
		String("texta", &text

search?id=1&nr=2&slice=3&slice=4", strings.NewReader(body))
	req.Header.Set(HeaderContentLength, strconv.Itoa(len(body)))
	req.Header.Set(HeaderContentType, MIMEApplicationForm)

	rec := httptest.NewRecorder()
	c := e.NewContext(


emory int64 = 32 << 20 // 32 MB
	indexPage           = "index.html"
)

// Context represents the context of the current HTTP request. It holds request and
// response objects, path, path parameters, data and registered handler.
type Context struct {
	request     *http.Request
	orgResponse *Response
	response    http.ResponseWriter
	query       url.Values

	// formParseMaxMemory is used for http.Request.ParseMultipartForm
	formParseMaxMemory int64

	route      *RouteInfo
	pathValues *PathValues

	store  map[

nyway
// these arguments are useful when creating context for tests and cases like that.
func NewContext(r *http.Request, w http.ResponseWriter, opts ...any) *Context {
	var e *Echo
	for _, opt := range opts {
		switch v := opt.(type) {
		case *Echo:
			e

string]any
	echo   *Echo
	logger *slog.Logger

	path string
	lock sync.RWMutex
}

// NewContext returns a new Context instance.
//
// Note: request,response and e can be left to nil as Echo.ServeHTTP will call c.Reset(req,resp) a


nse-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors

package echo

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestContextGetOK(t *testing.T) {
	c := NewContext(nil, nil)

	c.Set("key", int64(123))

	v, err := ContextGet[int64](c, "key")
	assert.NoError(t, err)
	assert.Equal(t, int64(123), v)
}

func TestContextGetNonExistentKey(t *testing.T) {
	c := NewContext(nil, nil)

	c.Set("key", int64(123))

	v, err := ContextGet[int64](c, "nope")
	assert.Er

 ErrInvalidKeyType)
	assert.Equal(t, false, v)
}

func TestContextGetOrOK(t *testing.T) {
	c := NewContext(nil, nil)

	c.Set("key", int64(123))

	v, err := ContextGetOr[int64](c, "key", 999)
	assert.NoError(t, err)
	assert.Equal(t, int64(123), v)
}

func T

rorIs(t, err, ErrNonExistentKey)
	assert.Equal(t, int64(0), v)
}

func TestContextGetInvalidCast(t *testing.T) {
	c := NewContext(nil, nil)

	c.Set("key", int64(123))

	v, err := ContextGet[bool](c, "key")
	assert.ErrorIs(t, err,


wardedFor: []string{"127.0.0.1, 127.0.1.1, "}},
	}}
	for i := 0; i < b.N; i++ {
		c.RealIP()
	}
}

func (t *Template) Render(c *Context, w io.Writer, name string, data any) error {
	return t.templates.ExecuteTemplate(w, name, data)
}

func TestContextEcho(t *testing.T) {
	e := New()
	req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON))
	rec := httptest.NewRecorder()

	c := e.NewContext(req, rec)

	assert.Equal(t, e, c.Echo())
}

func TestContextRequest(t *testing.T) {
	e := New()
	r

nse(t *testing.T) {
	e := New()
	req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON))
	rec := httptest.NewRecorder()

	c := e.NewContext(req, rec)

	assert.NotNil(t, c.Response())
}

func TestContextRenderTemplate(t *testing.T) {

eq := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON))
	rec := httptest.NewRecorder()

	c := e.NewContext(req, rec)

	assert.NotNil(t, c.Request())
	assert.Equal(t, req, c.Request())
}

func TestContextRespo


"errors"
	"fmt"
	"io/fs"
	"log/slog"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"sync"
	"sync/atomic"
	"syscall"
)

// Echo is the top-level framework instance.
//
// Goroutine safety: Do not mutate Echo instance fields after server has started. Accessing these
// fields from handlers/middlewares and changing field values at the same time leads to data-races.
// Same rule applies to adding new routes after server has been started - Adding a route is not Goroutine safe action.
typ

.Open() already assumes that file names are relative to FS root path and considers name with prefix `/` as invalid
	// so if you have `fs := os.DirFS("/tmp")` and you try to `fs.Open("/tmp/file.txt")` it will fail, but "file.txt"
	// would succeed. `echo.N

e Echo struct {
	serveHTTPFunc func(http.ResponseWriter, *http.Request)

	Binder Binder

	// Filesystem is the file system used for serving static files. Defaults to the current working directory (os.Getwd()).
	//
	// Note: fs.FS


It can be used for inner
// routes that share a common middleware or functionality that should be separate
// from the parent echo instance while still inheriting from it.
type Group struct {
	echo       *Echo
	prefix     string
	middleware []MiddlewareFunc
}

// Use implements `Echo#Use()` for sub-routes within the Group.
// Group middlewares are not executed on request when there is no matching route found.
func (g *Group) Use(middleware ...MiddlewareFunc) {
	g.middleware = append(g.middleware, middleware

)
}

// DELETE implements `Echo#DELETE()` for sub-routes within the Group. Panics on error.
func (g *Group) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
	return g.Add(http.MethodDelete, path, h, m...)
}

// GET implements `Echo#GET()

...)
}

// CONNECT implements `Echo#CONNECT()` for sub-routes within the Group. Panics on error.
func (g *Group) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
	return g.Add(http.MethodConnect, path, h, m...


 code by implementing HTTPStatusCoder interface
var (
	ErrBadRequest                  = &httpError{http.StatusBadRequest}            // 400
	ErrUnauthorized                = &httpError{http.StatusUnauthorized}          // 401
	ErrForbidden                   = &httpError{http.StatusForbidden}             // 403
	ErrNotFound                    = &httpError{http.StatusNotFound}              // 404
	ErrMethodNotAllowed            = &httpError{http.StatusMethodNotAllowed}      // 405
	ErrRequestTimeout

	ErrTooManyRequests             = &httpError{http.StatusTooManyRequests}       // 429
	ErrInternalServerError         = &httpError{http.StatusInternalServerError}   // 500
	ErrBadGateway                  = &httpError{http.StatusBadGateway}            // 5

    = &httpError{http.StatusRequestTimeout}        // 408
	ErrStatusRequestEntityTooLarge = &httpError{http.StatusRequestEntityTooLarge} // 413
	ErrUnsupportedMediaType        = &httpError{http.StatusUnsupportedMediaType}  // 415


SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors

// run tests as external package to get real feel for API
package echo_test

import (
	"encoding/json"
	"fmt"
	"github.com/labstack/echo/v5"
	"net/http"
	"net/http/httptest"
)

func ExampleDefaultHTTPErrorHandler() {
	e := echo.New()
	e.GET("/api/endpoint", func(c *echo.Context) error {
		return &apiError{
			Code: http.StatusBadRequest,
			Body: map[string]any{"message": "custom error"},
		}
	})

	req := http

pe apiError struct {
	Code int
	Body any
}

func (e *apiError) StatusCode() int {
	return e.Code
}

func (e *apiError) MarshalJSON() ([]byte, error) {
	type body struct {
		Error any `json:"error"`
	}
	return json.Marshal(body{Error: e.Body})
}

func (e *a

test.NewRequest(http.MethodGet, "/api/endpoint?err=1", nil)
	resp := httptest.NewRecorder()

	e.ServeHTTP(resp, req)

	fmt.Printf("%d %s", resp.Code, resp.Body.String())

	// Output: 400 {"error":{"message":"custom error"}}
}

ty


utesWillNotExecuteMiddlewareFor404(t *testing.T) {
	e := New()

	called := false
	mw := func(next HandlerFunc) HandlerFunc {
		return func(c *Context) error {
			called = true
			return c.NoContent(http.StatusTeapot)
		}
	}
	// even though group has middleware and routes when we have no match on some route the middlewares for that
	// group will not be executed
	g := e.Group("/group", mw)
	g.GET("/yes", handlerFunc)

	status, body := request(http.MethodGet, "/group/nope", e)
	assert.Equal(t, http.StatusNotF

vate", func(c *Context) error {
		return c.String(http.StatusTeapot, "OK")
	})

	status, body := request(http.MethodGet, "/api/users/activate", e)
	assert.Equal(t, http.StatusTeapot, status)
	assert.Equal(t, `OK`, body)
}

func TestGroupFile(t *testing.T)

ound, status)
	assert.Equal(t, `{"message":"Not Found"}`+"\n", body)

	assert.False(t, called)
}

func TestGroup_multiLevelGroup(t *testing.T) {
	e := New()

	api := e.Group("/api")
	users := api.Group("/users")
	users.GET("/acti


ol, auditing, geo-based access analysis and more.
Echo provides handy method [`Context#RealIP()`](https://godoc.org/github.com/labstack/echo#Context) for that.

However, it is not trivial to retrieve the _real_ IP address from requests especially when you put L7 proxies before the application.
In such situation, _real_ IP needs to be relayed on HTTP layer from proxies to your app, but you must not trust HTTP headers unconditionally.
Otherwise, you might give someone a chance of deceiving you. **A security r

ow you why and how.

> Note: if you don't set `Echo#IPExtractor` explicitly, Echo fallback to legacy behavior, which is not a good choice.

Let's start from two questions to know the right direction:

1. Do you put any HTTP (L7) proxy in front of the appli

isk!**

To retrieve IP address reliably/securely, you must let your application be aware of the entire architecture of your infrastructure.
In Echo, this can be done by configuring `Echo#IPExtractor` appropriately.
This guides sh


SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors

package echo

import (
	"encoding/json"
)

// DefaultJSONSerializer implements JSON encoding using encoding/json.
type DefaultJSONSerializer struct{}

// Serialize converts an interface into a json and writes it to the response.
// You can optionally use the indent parameter to produce pretty JSONs.
func (d DefaultJSONSerializer) Serialize(c *Context, target any, indent string) error {
	enc := json.NewEncoder(c.Response())
	if indent != "" {

arget)
}

// Deserialize reads a JSON from a request body and converts it into an interface.
func (d DefaultJSONSerializer) Deserialize(c *Context, target any) error {
	if err := json.NewDecoder(c.Request().Body).Decode(target); err != nil {
		return ErrBa

		enc.SetIndent("", indent)
	}
	return enc.Encode(t


tack LLC and Echo contributors

package echo

import "io"

// Renderer is the interface that wraps the Render function.
type Renderer interface {
	Render(c *Context, w io.Writer, templateName string, data any) error
}

// TemplateRenderer is helper to ease creating renderers for `html/template` and `text/template` packages.
// Example usage:
//
//		e.Renderer = &echo.TemplateRenderer{
//			Template: template.Must(template.ParseGlob("templates/*.html")),
//		}
//
//	  e.Renderer = &echo.TemplateRenderer{
//

e("Hello, {{.}}!")),
//		}
type TemplateRenderer struct {
	Template interface {
		ExecuteTemplate(wr io.Writer, name string, data any) error
	}
}

// Render renders the template with given data.
func (t *TemplateRenderer) Render(c *Context, w io.Writer, na


		})
	}
}

func TestTrustIPRange(t *testing.T) {
	var testCases = []struct {
		name       string
		givenRange string
		whenIP     string
		expect     bool
	}{
		{
			name: "ip is within trust range, IPV6 network range",
			// CIDR Notation: 2001:0db8:0000

			expect: true,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			checker := newIPChecker(tc.givenOptions)

			result := checker.trust(net.ParseIP(tc.whenIP))
			assert.Equal(t, tc.expect, result)


n http.ResponseWriter and implements its interface to be used
// by an HTTP handler to construct an HTTP response.
// See: https://golang.org/pkg/net/http/#ResponseWriter
type Response struct {
	http.ResponseWriter
	logger *slog.Logger
	// beforeFuncs are functions that are called just before the response (status) is written. Happens only once, during WriteHeader call.
	beforeFuncs []func()
	// afterFuncs are functions that are called just after the response is written. During every `Write` method call.
	af

ter: w, logger: logger}
}

// Before registers a function which is called just before the response (status) is written.
func (r *Response) Before(fn func()) {
	r.beforeFuncs = append(r.beforeFuncs, fn)
}

// After registers a function which is called just

terFuncs []func()
	Status     int
	Size       int64
	Committed  bool
}

// NewResponse creates a new instance of Response.
func NewResponse(w http.ResponseWriter, logger *slog.Logger) (r *Response) {
	return &Response{ResponseWri


htText: © 2015 LabStack LLC and Echo contributors

package echo

import (
	"github.com/stretchr/testify/assert"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

// Note this test is deliberately simple as there's not a lot to test.
// Just need to ensure it writes JSONs. The heavy work is done by the context methods.
func TestDefaultJSONCodec_Encode(t *testing.T) {
	e := New()
	req := httptest.NewRequest(http.MethodPost, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	// Ech

e(c, user{ID: 1, Name: "Jon Snow"}, "")
	if assert.NoError(t, err) {
		assert.Equal(t, userJSON+"\n", rec.Body.String())
	}

	req = httptest.NewRequest(http.MethodPost, "/", nil)
	rec = httptest.NewRecorder()
	c = e.NewContext(req, rec)
	err = enc.Serializ

o
	assert.Equal(t, e, c.Echo())

	// Request
	assert.NotNil(t, c.Request())

	// Response
	assert.NotNil(t, c.Response())

	//--------
	// Default JSON encoder
	//--------

	enc := new(DefaultJSONSerializer)

	err := enc.Serializ


package echo

import (
	"bytes"
	"errors"
	"fmt"
	"reflect"
	"runtime"
)

// Route contains information to adding/registering new route with the router.
// Method+Path pair uniquely identifies the Route. It is mandatory to provide Method+Path+Handler fields.
type Route struct {
	Method      string
	Path        string
	Name        string
	Handler     HandlerFunc
	Middlewares []MiddlewareFunc
}

// ToRouteInfo converts Route to RouteInfo
func (r Route) ToRouteInfo(params []string) RouteInfo {
	name := r.Name

dded group prefix and group middlewares it is grouped to.
func (r Route) WithPrefix(pathPrefix string, middlewares []MiddlewareFunc) Route {
	r.Path = pathPrefix + r.Path

	if len(middlewares) > 0 {
		m := make([]MiddlewareFunc, 0, len(middlewares)+len(r.M

	if name == "" {
		name = r.Method + ":" + r.Path
	}

	return RouteInfo{
		Method:     r.Method,
		Path:       r.Path,
		Parameters: append([]string(nil), params...),
		Name:       name,
	}
}

// WithPrefix recreates Route with a


with the Router and returns registered RouteInfo.
	//
	// Router may change Route.Path value in returned RouteInfo.Path.
	// Router generates RouteInfo.Parameters values from Route.Path.
	// Router generates RouteInfo.Name value if it is not provided.
	Add(routable Route) (RouteInfo, error)

	// Remove removes route from the Router.
	//
	// Router may choose not to implement this method.
	Remove(method string, path string) error

	// Routes returns information about all registered routes
	Routes() Routes

	// handler function.
	//
	// Router must populate Context during Router.Route call with:
	// - Context.InitializeRoute() (IMPORTANT! to reduce allocations use same slice that c.PathValues() returns)
	// - optionally can set additional information to Cont

// Route searches Router for matching route and applies it to the given context. In case when no matching method
	// was not found (405) or no matching route exists for path (404), router will return its implementation of 405/404


th:    "/initial1",
		Handler: handlerFunc,
	})
	assert.NoError(t, err)
	assert.Equal(t, len(router.Routes()), 1)

	err = router.Remove(http.MethodGet, "/initial1")
	assert.NoError(t, err)
	assert.Equal(t, len(router.Routes()), 0)
}

func TestConcurrentRouter_ConcurrentReads(t *testing.T) {
	router := NewConcurrentRouter(NewRouter(RouterConfig{}))

	testPaths := []string{"/route1", "/route2", "/route3", "/route4", "/route5"}
	for _, path := range testPaths {
		_, err := router.Add(Route{
			Method:  http.Me

Int64

	numGoroutines := 10
	routeCallsPerGoroutine := 50
	routesCallsPerGoroutine := 20

	for i := range numGoroutines {
		wg.Add(1)
		go func(goroutineID int) {
			defer wg.Done()

			// Call Route() 50 times
			for j := range routeCallsPerGoroutine {

thodGet,
			Path:    path,
			Handler: handlerFunc,
		})
		if err != nil {
			t.Fatal(err)
		}
	}

	// Launch 10 goroutines for concurrent reads
	var wg sync.WaitGroup
	var routeCallCount atomic.Int64
	var routesCallCount atomic.


/gopher/bumper320x180.png", ""},
		{"GET", "/gopher/bumper480x270.png", ""},
		{"GET", "/gopher/bumper640x360.png", ""},
		{"GET", "/gopher/doc.png", ""},
		{"GET", "/gopher/frontpage.png", ""},
		{"GET", "/gopher/gopherbw.png", ""},
		{"GET", "/gopher/gophercolor.png", ""},
		{"GET", "/gopher/gophercolor16x16.png", ""},
		{"GET", "/gopher/help.png", ""},
		{"GET", "/gopher/pkg.png", ""},
		{"GET", "/gopher/project.png", ""},
		{"GET", "/gopher/ref.png", ""},
		{"GET", "/gopher/run.png", ""},
		{"GET", "/go

pherrunning.jpg", ""},
		{"GET", "/gopher/pencil/gopherswim.jpg", ""},
		{"GET", "/gopher/pencil/gopherswrench.jpg", ""},
		{"GET", "/play/", ""},
		{"GET", "/play/fib.go", ""},
		{"GET", "/play/hello.go", ""},
		{"GET", "/play/life.go", ""},
		{"GET", "/p

pher/talks.png", ""},
		{"GET", "/gopher/pencil/", ""},
		{"GET", "/gopher/pencil/gopherhat.jpg", ""},
		{"GET", "/gopher/pencil/gopherhelmet.jpg", ""},
		{"GET", "/gopher/pencil/gophermega.jpg", ""},
		{"GET", "/gopher/pencil/go


c(c *Context) error {
		return nil
	}

	tmp := NameStruct{}

	var testCases = []struct {
		name            string
		whenHandlerFunc HandlerFunc
		expect          string
	}{
		{
			name: "ok, func as anonymous func",
			whenHandlerFunc: func(c *Context) error {
				return nil
			},
			expect: "github.com/labstack/echo/v5.TestHandlerName.func2",
		},
		{
			name:            "ok, func as named package variable",
			whenHandlerFunc: myNamedHandler,
			expect:          "github.com/labstack/echo/v5.init.func4",

d",
			whenHandlerFunc: tmp.getUsers,
			expect:          "github.com/labstack/echo/v5.(*NameStruct).getUsers-fm",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			name := HandlerName(tc.whenHandlerFunc)
			assert.Equal(t,

	},
		{
			name:            "ok, func as named function variable",
			whenHandlerFunc: myNameFuncVar,
			expect:          "github.com/labstack/echo/v5.TestHandlerName.func1",
		},
		{
			name:            "ok, func as struct metho


"
	"net"
	"net/http"
	"os"
	"sync"
	"time"
)

const (
	banner = "Echo (v%s). High performance, minimalist Go web framework https://echo.labstack.com"
)

// StartConfig is for creating configured http.Server instance to start serve http(s) requests with given Echo instance
type StartConfig struct {
	// Address specifies the address where listener will start listening on to serve HTTP(s) requests
	Address string

	// HideBanner instructs Start* method not to print banner when starting the Server.
	HideBanner

	// TLSConfig is used to configure TLS. If Listener is set, TLSConfig is not used to create the listener.
	TLSConfig *tls.Config

	// Listener is used to start server with the custom listener.
	Listener net.Listener
	// ListenerNetwork is used configure

bool
	// HidePort instructs Start* method not to print port when starting the Server.
	HidePort bool

	// CertFilesystem is filesystem is used to read `certFile` and `keyFile` when StartTLS method is called.
	CertFilesystem fs.FS


/ NewConcurrentRouter creates concurrency safe Router which routes can be added/removed safely
// even after http.Server has been started.
func NewConcurrentRouter(r Router) Router {
	return &concurrentRouter{
		mu:     sync.RWMutex{},
		router: r,
	}
}

type concurrentRouter struct {
	mu     sync.RWMutex
	router Router
}

func (r *concurrentRouter) Route(c *Context) HandlerFunc {
	r.mu.RLock()
	defer r.mu.RUnlock()

	return r.router.Route(c)
}

func (r *concurrentRouter) Routes() Routes {
	r.mu.RLock()
	de

)
}

func (r *concurrentRouter) Add(routable Route) (RouteInfo, error) {
	r.mu.Lock()
	defer r.mu.Unlock()

	return r.router.Add(routable)
}

func (r *concurrentRouter) Remove(method string, path string) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	return r

fer r.mu.RUnlock()

	return r.router.Routes().Clone(


package echo

import (
	"github.com/stretchr/testify/assert"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestVirtualHostHandler(t *testing.T) {
	okHandler := func(c *Context) error { return c.String(http.StatusOK, http.StatusText(http.StatusOK)) }
	teapotHandler := func(c *Context) error { return c.String(http.StatusTeapot, http.StatusText(http.StatusTeapot)) }
	acceptHandler := func(c *Context) error { return c.String(http.StatusAccepted, http.StatusText(http.StatusAccepted)) }
	teapotMiddleware :=

middle := New()
	middle.Use(teapotMiddleware)
	middle.GET("/", okHandler)
	middle.GET("/foo", okHandler)

	virtualHosts := NewVirtualHostHandler(map[string]*Echo{
		"ok.com":         ok,
		"teapot.com":     teapot,
		"middleware.com": middle,
	})
	virtualH

 MiddlewareFunc(func(next HandlerFunc) HandlerFunc { return teapotHandler })

	ok := New()
	ok.GET("/", okHandler)
	ok.GET("/foo", okHandler)

	teapot := New()
	teapot.GET("/", teapotHandler)
	teapot.GET("/foo", teapotHandler)


test"
	"strings"
	"testing"

	"github.com/labstack/echo/v5"
	"github.com/stretchr/testify/assert"
)

func TestBasicAuth(t *testing.T) {
	validatorFunc := func(c *echo.Context, u, p string) (bool, error) {
		// Use constant-time comparison to prevent timing attacks
		userMatch := subtle.ConstantTimeCompare([]byte(u), []byte("joe")) == 1
		passMatch := subtle.ConstantTimeCompare([]byte(p), []byte("secret")) == 1

		if userMatch && passMatch {
			return true, nil
		}

		// Special case for testing error handli

nAuth     []string
		expectHeader string
		expectErr    string
	}{
		{
			name:        "ok",
			givenConfig: defaultConfig,
			whenAuth:    []string{basic + " " + base64.StdEncoding.EncodeToString([]byte("joe:secret"))},
		},
		{
			name:        "ok, multi

ng
		if u == "error" {
			return false, errors.New(p)
		}

		return false, nil
	}
	defaultConfig := BasicAuthConfig{Validator: validatorFunc}

	var testCases = []struct {
		name         string
		givenConfig  BasicAuthConfig
		whe


wReader(hw))
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)
	h := func(c *echo.Context) error {
		body, err := io.ReadAll(c.Request().Body)
		if err != nil {
			return err
		}
		return c.String(http.StatusOK, string(body))
	}

	requestBody := ""
	responseBody := ""
	mw, err := BodyDumpConfig{Handler: func(c *echo.Context, reqBody, resBody []byte, err error) {
		requestBody = string(reqBody)
		responseBody = string(resBody)
	}}.ToMiddleware()
	assert.NoError(t, err)

	if assert.NoError(t, mw(h)(

isCalled := false
	mw, err := BodyDumpConfig{
		Skipper: func(c *echo.Context) bool {
			return true
		},
		Handler: func(c *echo.Context, reqBody, resBody []byte, err error) {
			isCalled = true
		},
	}.ToMiddleware()
	assert.NoError(t, err)

	req := http

c)) {
		assert.Equal(t, requestBody, hw)
		assert.Equal(t, responseBody, hw)
		assert.Equal(t, http.StatusOK, rec.Code)
		assert.Equal(t, hw, rec.Body.String())
	}

}

func TestBodyDump_skipper(t *testing.T) {
	e := echo.New()


htText: © 2015 LabStack LLC and Echo contributors

package middleware

import (
	"io"
	"net/http"
	"sync"

	"github.com/labstack/echo/v5"
)

// BodyLimitConfig defines the config for BodyLimitWithConfig middleware.
type BodyLimitConfig struct {
	// Skipper defines a function to skip middleware.
	Skipper Skipper

	// LimitBytes is maximum allowed size in bytes for a request body
	LimitBytes int64
}

type limitedReader struct {
	BodyLimitConfig
	reader io.ReadCloser
	read   int64
}

// BodyLimit returns a Bod

 both `Content-Length` request
// header and actual content read, which makes it super secure.
func BodyLimit(limitBytes int64) echo.MiddlewareFunc {
	return BodyLimitWithConfig(BodyLimitConfig{LimitBytes: limitBytes})
}

// BodyLimitWithConfig returns a B

yLimit middleware.
//
// BodyLimit middleware sets the maximum allowed size for a request body, if the size exceeds the configured limit, it
// sends "413 - Request Entity Too Large" response. The BodyLimit is determined based on


trings"
	"sync"

	"github.com/labstack/echo/v5"
)

const (
	gzipScheme = "gzip"
)

// GzipConfig defines the config for Gzip middleware.
type GzipConfig struct {
	// Skipper defines a function to skip middleware.
	Skipper Skipper

	// Gzip compression level.
	// Optional. Default value -1.
	Level int

	// Length threshold before gzip compression is applied.
	// Optional. Default value 0.
	//
	// Most of the time you will not need to change the default. Compressing
	// a short response might increase the tra

	//
	// See also:
	// https://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits
	MinLength int
}

type gzipResponseWriter struct {
	io.Writer
	http.ResponseWriter
	wroteHeader       bool
	wr

nsmitted data because of the
	// gzip format overhead. Compressing the response will also consume CPU
	// and time on the server and the client (for decompressing). Depending on
	// your use case such a threshold might be useful.


m/labstack/echo/v5"
)

// BodyDumpConfig defines the config for BodyDump middleware.
type BodyDumpConfig struct {
	// Skipper defines a function to skip middleware.
	Skipper Skipper

	// Handler receives request, response payloads and handler error if there are any.
	// Required.
	Handler BodyDumpHandler

	// MaxRequestBytes limits how much of the request body to dump.
	// If the request body exceeds this limit, only the first MaxRequestBytes
	// are dumped. The handler callback receives truncated data.
	//

limit, only the first MaxResponseBytes
	// are dumped. The handler callback receives truncated data.
	// Default: 5 * MB (5,242,880 bytes)
	// Set to -1 to disable limits (not recommended in production).
	MaxResponseBytes int64
}

// BodyDumpHandler receiv

 Default: 5 * MB (5,242,880 bytes)
	// Set to -1 to disable limits (not recommended in production).
	MaxRequestBytes int64

	// MaxResponseBytes limits how much of the response body to dump.
	// If the response body exceeds this


package middleware

import (
	"bytes"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/labstack/echo/v5"
	"github.com/stretchr/testify/assert"
)

func TestBodyLimitConfig_ToMiddleware(t *testing.T) {
	e := echo.New()
	hw := []byte("Hello, World!")
	req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(hw))
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)
	h := func(c *echo.Context) error {
		body, err := io.ReadAll(c.Request().Body)
		if err != nil {
			return err
		}

sert.Equal(t, http.StatusOK, rec.Code)
		assert.Equal(t, hw, rec.Body.Bytes())
	}

	// Based on content read (overlimit)
	mw, err = BodyLimitConfig{LimitBytes: 2}.ToMiddleware()
	assert.NoError(t, err)
	he := mw(h)(c).(echo.HTTPStatusCoder)
	assert.Equal(t

return c.String(http.StatusOK, string(body))
	}

	// Based on content length (within limit)
	mw, err := BodyLimitConfig{LimitBytes: 2 * MB}.ToMiddleware()
	assert.NoError(t, err)

	err = mw(h)(c)
	if assert.NoError(t, err) {
		as


package middleware

import (
	"bytes"
	"cmp"
	"encoding/base64"
	"errors"
	"strconv"
	"strings"

	"github.com/labstack/echo/v5"
)

// BasicAuthConfig defines the config for BasicAuthWithConfig middleware.
//
// SECURITY: The Validator function is responsible for securely comparing credentials.
// See BasicAuthValidator documentation for guidance on preventing timing attacks.
type BasicAuthConfig struct {
	// Skipper defines a function to skip middleware.
	Skipper Skipper

	// Validator is a function to vali

	// Realm is a string to define realm attribute of BasicAuthWithConfig.
	// Default value "Restricted".
	Realm string

	// AllowedCheckLimit set how many headers are allowed to be checked. This is useful
	// environments like corporate test environments wi

date BasicAuthWithConfig credentials. Note: if request contains multiple basic auth headers
	// this function would be called once for each header until first valid result is returned
	// Required.
	Validator BasicAuthValidator


r: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors

package middleware

import (
	"context"
	"errors"
	"time"

	"github.com/labstack/echo/v5"
)

// ContextTimeoutConfig defines the config for ContextTimeout middleware.
type ContextTimeoutConfig struct {
	// Skipper defines a function to skip middleware.
	Skipper Skipper

	// ErrorHandler is a function when error arises in middeware execution.
	ErrorHandler func(c *echo.Context, err error) error

	// Timeout configures a timeout for

imeout time.Duration) echo.MiddlewareFunc {
	return ContextTimeoutWithConfig(ContextTimeoutConfig{Timeout: timeout})
}

// ContextTimeoutWithConfig returns a Timeout middleware with config.
func ContextTimeoutWithConfig(config ContextTimeoutConfig) echo.Mi

the middleware
	Timeout time.Duration
}

// ContextTimeout returns a middleware which returns error (503 Service Unavailable error) to client
// when underlying method returns context.DeadlineExceeded error.
func ContextTimeout(t


 Access-Control-Allow-Origin
	// response header.  This header defines a list of origins that may access the
	// resource.
	//
	// Origin consist of following parts: `scheme + "://" + host + optional ":" + port`
	// Wildcard can be used, but has to be set explicitly []string{"*"}
	// Example: `https://example.com`, `http://example.com:8080`, `*`
	//
	// Security: use extreme caution when handling the origin, and carefully
	// validate any logic. Remember that attackers may register hostile domain names.
	//

// UnsafeAllowOriginFunc is an optional custom function to validate the origin. It takes the
	// origin as an argument and returns
	// - string, allowed origin
	// - bool, true if allowed or false otherwise.
	// - error, if an error is returned, it is retu

 See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
	// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
	//
	// Mandatory.
	AllowOrigins []string


n value that can be used to render CSRF token for form by handlers.
//
// We know that the client is using a browser that supports Sec-Fetch-Site header, so when the form is submitted in
// the future with this dummy token value it is OK. Although the request is safe, the template rendered by the
// handler may need this value to render CSRF token for form.
const CSRFUsingSecFetchSite = "_echo_csrf_using_sec_fetch_site_"

// CSRFConfig defines the config for CSRF middleware.
type CSRFConfig struct {
	// Ski

gin header "scheme://host[:port]".
	//
	// See [Origin]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin
	// See [Sec-Fetch-Site]: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#fetch-

pper defines a function to skip middleware.
	Skipper Skipper
	// TrustedOrigins permits any request with `Sec-Fetch-Site` header whose `Origin` header
	// exactly matches a configured origin.
	// Values should be formatted as Ori


package middleware

import (
	"compress/gzip"
	"io"
	"net/http"
	"sync"

	"github.com/labstack/echo/v5"
)

// DecompressConfig defines the config for Decompress middleware.
type DecompressConfig struct {
	// Skipper defines a function to skip middleware.
	Skipper Skipper

	// GzipDecompressPool defines an interface to provide the sync.Pool used to create/store Gzip readers
	GzipDecompressPool Decompressor

	// MaxDecompressedSize limits the maximum size of decompressed request body in bytes.
	// If the deco

ble limits (not recommended in production).
	MaxDecompressedSize int64
}

// GZIPEncoding content-encoding header if set to "gzip", decompress body contents.
const GZIPEncoding string = "gzip"

// Decompressor is used to get the sync.Pool used by the middl

mpressed body exceeds this limit, the middleware returns HTTP 413 error.
	// This prevents zip bomb attacks where small compressed payloads decompress to huge sizes.
	// Default: 100 * MB (104,857,600 bytes)
	// Set to -1 to disa


st (
	// extractorLimit is arbitrary number to limit values extractor can return. this limits possible resource exhaustion
	// attack vector
	extractorLimit = 20
)

// ExtractorSource is type to indicate source for extracted value
type ExtractorSource string

const (
	// ExtractorSourceHeader means value was extracted from request header
	ExtractorSourceHeader ExtractorSource = "header"
	// ExtractorSourceQuery means value was extracted from request query parameters
	ExtractorSourceQuery ExtractorSource = "

eCookie ExtractorSource = "cookie"
	// ExtractorSourceForm means value was extracted from request form values
	ExtractorSourceForm ExtractorSource = "form"
)

// ValueExtractorError is error type when middleware extractor is unable to extract value from lo

query"
	// ExtractorSourcePathParam means value was extracted from route path parameters
	ExtractorSourcePathParam ExtractorSource = "param"
	// ExtractorSourceCookie means value was extracted from request cookies
	ExtractorSourc


p/httptest"
	"net/url"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestContextTimeoutSkipper(t *testing.T) {
	t.Parallel()
	m := ContextTimeoutWithConfig(ContextTimeoutConfig{
		Skipper: func(context *echo.Context) bool {
			return true
		},
		Timeout: 10 * time.Millisecond,
	})

	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()

	e := echo.New()
	c := e.NewContext(req, rec)

	err := m(func(c *echo.Context) error {
		if err := sleepWithCont

ssert.EqualError(t, err, "response from handler")
}

func TestContextTimeoutWithTimeout0(t *testing.T) {
	t.Parallel()
	assert.Panics(t, func() {
		ContextTimeout(time.Duration(0))
	})
}

func TestContextTimeoutErrorOutInHandler(t *testing.T) {
	t.Parallel

ext(c.Request().Context(), time.Duration(20*time.Millisecond)); err != nil {
			return err
		}

		return errors.New("response from handler")
	})(c)

	// if not skipped we would have not returned error due context timeout logic
	a


nLookup: "header:X-CSRF-Token,form:csrf",
			givenCSRFCookie: "token",
			givenMethod:     http.MethodPost,
			givenHeaderTokens: map[string][]string{
				echo.HeaderXCSRFToken: {"invalid_token"},
			},
			givenFormTokens: map[string][]string{
				"csrf": {"token"},
			},
		},
		{
			name:            "ok, token from POST form",
			whenTokenLookup: "form:csrf",
			givenCSRFCookie: "token",
			givenMethod:     http.MethodPost,
			givenFormTokens: map[string][]string{
				"csrf": {"token"},
			},
		},
		{
			n

"token"},
			},
			expectError: "code=403, message=invalid csrf token",
		},
		{
			name:            "nok, invalid token from POST form",
			whenTokenLookup: "form:csrf",
			givenCSRFCookie: "token",
			givenMethod:     http.MethodPost,
			givenFormTokens:

ame:            "ok, token from POST form, second token passes",
			whenTokenLookup: "form:csrf",
			givenCSRFCookie: "token",
			givenMethod:     http.MethodPost,
			givenFormTokens: map[string][]string{
				"csrf": {"invalid",


 {
		c.Response().Write([]byte("test")) // For Content-Type sniffing
		return nil
	})

	// Decompress request body
	body := `{"name": "echo"}`
	gz, _ := gzipString(body)
	req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(gz)))
	req.Header.Set(echo.HeaderContentEncoding, GZIPEncoding)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	err := h(c)
	assert.NoError(t, err)

	assert.Equal(t, GZIPEncoding, req.Header.Get(echo.HeaderContentEncoding))
	b, err := io.ReadAll(req.Bod

NewRecorder()
	c := e.NewContext(req, rec)

	// Skip if no Content-Encoding header
	h := Decompress()(func(c *echo.Context) error {
		c.Response().Write([]byte("test")) // For Content-Type sniffing
		return nil
	})

	err := h(c)
	assert.NoError(t, err)
	as

y)
	assert.NoError(t, err)
	assert.Equal(t, body, string(b))
}

func TestDecompress_skippedIfNoHeader(t *testing.T) {
	e := echo.New()
	req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("test"))
	rec := httptest.


ses = []struct {
		name             string
		givenConfig      *CORSConfig
		whenMethod       string
		whenHeaders      map[string]string
		expectHeaders    map[string]string
		notExpectHeaders map[string]string
		expectErr        string
	}{
		{
			name: "ok, wildcard origin",
			givenConfig: &CORSConfig{
				AllowOrigins: []string{"*"},
			},
			whenHeaders:   map[string]string{echo.HeaderOrigin: "localhost"},
			expectHeaders: map[string]string{echo.HeaderAccessControlAllowOrigin: "*"},
		},
		{
			name: "

"ok, specific AllowOrigins and AllowCredentials",
			givenConfig: &CORSConfig{
				AllowOrigins:     []string{"http://localhost", "http://localhost:8080"},
				AllowCredentials: true,
				MaxAge:           3600,
			},
			whenHeaders: map[string]string{echo

ok, wildcard AllowedOrigin with no Origin header in request",
			givenConfig: &CORSConfig{
				AllowOrigins: []string{"*"},
			},
			notExpectHeaders: map[string]string{echo.HeaderAccessControlAllowOrigin: ""},
		},
		{
			name:


 KeyAuthConfig defines the config for KeyAuth middleware.
//
// SECURITY: The Validator function is responsible for securely comparing API keys.
// See KeyAuthValidator documentation for guidance on preventing timing attacks.
type KeyAuthConfig struct {
	// Skipper defines a function to skip middleware.
	Skipper Skipper

	// KeyLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
	// to extract key from the request.
	// Optional. Default value "header:Authori

uthorization: <auth-scheme> <authorisation-parameters>` where part that we
	//			want to cut is `<auth-scheme> ` note the space at the end.
	//			In case of basic authentication `Authorization: Basic <credentials>` prefix we want to remove is `Basic `.
	//

zation".
	// Possible values:
	// - "header:<name>" or "header:<name>:<cut-prefix>"
	// 			`<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header
	//			value has static prefix like `A


htText: © 2015 LabStack LLC and Echo contributors

package middleware

import (
	"net/http"

	"github.com/labstack/echo/v5"
)

// MethodOverrideConfig defines the config for MethodOverride middleware.
type MethodOverrideConfig struct {
	// Skipper defines a function to skip middleware.
	Skipper Skipper

	// Getter is a function that gets overridden method from the request.
	// Optional. Default values MethodFromHeader(echo.HeaderXHTTPMethodOverride).
	Getter MethodOverrideGetter
}

// MethodOverrideGetter i

ethodOverrideConfig{
	Skipper: DefaultSkipper,
	Getter:  MethodFromHeader(echo.HeaderXHTTPMethodOverride),
}

// MethodOverride returns a MethodOverride middleware.
// MethodOverride  middleware checks for the overridden method from the request and
// uses

s a function that gets overridden method from the request
type MethodOverrideGetter func(c *echo.Context) string

// DefaultMethodOverrideConfig is the default MethodOverride middleware config.
var DefaultMethodOverrideConfig = M


htText: © 2015 LabStack LLC and Echo contributors

package middleware

import (
	"net/http"
	"regexp"
	"strconv"
	"strings"

	"github.com/labstack/echo/v5"
)

// Skipper defines a function to skip middleware. Returning true skips processing the middleware.
type Skipper func(c *echo.Context) bool

// BeforeFunc defines a function which is executed just before the middleware.
type BeforeFunc func(c *echo.Context)

func captureTokens(pattern *regexp.Regexp, input string) *strings.Replacer {
	groups := pattern.

] = v
	}
	return strings.NewReplacer(replace...)
}

func rewriteRulesRegex(rewrite map[string]string) map[*regexp.Regexp]string {
	// Initialize
	rulesRegex := map[*regexp.Regexp]string{}
	for k, v := range rewrite {
		k = regexp.QuoteMeta(k)
		k = strings

FindAllStringSubmatch(input, -1)
	if groups == nil {
		return nil
	}
	values := groups[0][1:]
	replace := make([]string, 2*len(values))
	for i, v := range values {
		j := 2 * i
		replace[j] = "$" + strconv.Itoa(i+1)
		replace[j+1


e ProxyConfig struct {
	// Skipper defines a function to skip middleware.
	Skipper Skipper

	// Balancer defines a load balancing technique.
	// Required.
	Balancer ProxyBalancer

	// RetryCount defines the number of times a failed proxied request should be retried
	// using the next available ProxyTarget. Defaults to 0, meaning requests are never retried.
	RetryCount int

	// RetryFilter defines a function used to determine if a failed request to a
	// ProxyTarget should be retried. The RetryFilter will on

t is unavailable, the error will be an instance of
	// echo.HTTPError with a code of http.StatusBadGateway. In all other cases, the error
	// will indicate an internal error in the Proxy middleware. When a RetryFilter is not
	// specified, all requests tha

ly be called when the number
	// of previous retries is less than RetryCount. If the function returns true, the
	// request will be retried. The provided error indicates the reason for the request
	// failure. When the ProxyTarge


/v5"
	"golang.org/x/time/rate"
)

// RateLimiterStore is the interface to be implemented by custom stores.
type RateLimiterStore interface {
	Allow(identifier string) (bool, error)
}

// RateLimiterConfig defines the configuration for the rate limiter
type RateLimiterConfig struct {
	Skipper    Skipper
	BeforeFunc BeforeFunc
	// IdentifierExtractor uses *echo.Context to extract the identifier for a visitor
	IdentifierExtractor Extractor
	// Store defines a store for the rate limiter
	Store RateLimiterStore

andler func(c *echo.Context, identifier string, err error) error
}

// Extractor is used to extract data from *echo.Context
type Extractor func(c *echo.Context) (string, error)

// ErrRateLimitExceeded denotes an error raised when rate limit is exceeded
va

	// ErrorHandler provides a handler to be called when IdentifierExtractor returns an error
	ErrorHandler func(c *echo.Context, err error) error
	// DenyHandler provides a handler to be called when RateLimiter denies access
	DenyH


 to prevent timing attacks
	if subtle.ConstantTimeCompare([]byte(key), []byte("valid-key")) == 1 {
		return true, nil
	}

	// Special case for testing error handling
	if key == "error-key" { // Error path doesn't need constant-time
		return false, errors.New("some user defined error")
	}

	return false, nil
}

func TestKeyAuth(t *testing.T) {
	handlerCalled := false
	handler := func(c *echo.Context) error {
		handlerCalled = true
		return c.String(http.StatusOK, "test")
	}
	middlewareChain := KeyAuth(testKe

ewareChain(c)

	assert.NoError(t, err)
	assert.True(t, handlerCalled)
}

func TestKeyAuthWithConfig(t *testing.T) {
	var testCases = []struct {
		name                string
		givenRequestFunc    func() *http.Request
		givenRequest        func(req *http.Req

yValidator)(handler)

	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	req.Header.Set(echo.HeaderAuthorization, "Bearer valid-key")
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	err := middl


htText: © 2015 LabStack LLC and Echo contributors

package middleware

import (
	"bytes"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/labstack/echo/v5"
	"github.com/stretchr/testify/assert"
)

func TestMethodOverride(t *testing.T) {
	e := echo.New()
	m := MethodOverride()
	h := func(c *echo.Context) error {
		return c.String(http.StatusOK, "test")
	}

	// Override with http header
	req := httptest.NewRequest(http.MethodPost, "/", nil)
	rec := httptest.NewRecorder()
	req.Header.Set(echo.HeaderXH

h := func(c *echo.Context) error {
		return c.String(http.StatusOK, "test")
	}

	// Override with form parameter
	m, err := MethodOverrideConfig{Getter: MethodFromForm("_method")}.ToMiddleware()
	assert.NoError(t, err)
	req := httptest.NewRequest(http.Meth

TTPMethodOverride, http.MethodDelete)
	c := e.NewContext(req, rec)

	err := m(h)(c)
	assert.NoError(t, err)

	assert.Equal(t, http.MethodDelete, req.Method)

}

func TestMethodOverride_formParam(t *testing.T) {
	e := echo.New()


e(t2.URL)

	targets := []*ProxyTarget{
		{
			Name: "target 1",
			URL:  url1,
		},
		{
			Name: "target 2",
			URL:  url2,
		},
	}
	rb := NewRandomBalancer(nil)
	// must add targets:
	for _, target := range targets {
		assert.True(t, rb.AddTarget(target))
	}

	// must ignore duplicates:
	for _, target := range targets {
		assert.False(t, rb.AddTarget(target))
	}

	// Random
	e := echo.New()
	e.Use(ProxyWithConfig(ProxyConfig{Balancer: rb}))
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := http

 targets {
		assert.True(t, rb.RemoveTarget(target.Name))
	}

	assert.False(t, rb.RemoveTarget("unknown target"))

	// Round-robin
	rrb := NewRoundRobinBalancer(targets)
	e = echo.New()
	e.Use(ProxyWithConfig(ProxyConfig{Balancer: rrb}))

	rec = httptest.N

test.NewRecorder()
	e.ServeHTTP(rec, req)
	body := rec.Body.String()
	expected := map[string]bool{
		"target 1": true,
		"target 2": true,
	}
	assert.Condition(t, func() bool {
		return expected[body]
	})

	for _, target := range


package middleware

import (
	"bufio"
	"errors"
	"github.com/stretchr/testify/assert"
	"net"
	"net/http"
	"net/http/httptest"
	"regexp"
	"testing"
)

func TestRewriteURL(t *testing.T) {
	var testCases = []struct {
		whenURL       string
		expectPath    string
		expectRawPath string
		expectQuery   string
		expectErr     string
	}{
		{
			whenURL:       "http://localhost:8080/old",
			expectPath:    "/new",
			expectRawPath: "",
		},
		{ // encoded `ol%64` (decoded `old`) should not be rewritten to `/new`

+",
			expectRawPath: "",
			expectQuery:   "test=1",
		},
		{
			whenURL:       "http://localhost:8080/users/%20a/orders/%20aa",
			expectPath:    "/user/ a/order/ aa",
			expectRawPath: "",
		},
		{
			whenURL:       "http://localhost:8080/%47%6f%2f?test

	whenURL:       "/ol%64", // `%64` is decoded `d`
			expectPath:    "/old",
			expectRawPath: "/ol%64",
		},
		{
			whenURL:       "http://localhost:8080/users/+_+/orders/___++++?test=1",
			expectPath:    "/user/+_+/order/___+++


package middleware

import (
	"bytes"
	"errors"
	"log/slog"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/labstack/echo/v5"
	"github.com/stretchr/testify/assert"
)

func TestRecover(t *testing.T) {
	e := echo.New()
	buf := new(bytes.Buffer)
	e.Logger = slog.New(&discardHandler{})
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)
	h := Recover()(func(c *echo.Context) error {
		panic("test")
	})
	err := h(c)
	assert.Contains(t, err.Er

.Equal(t, http.StatusOK, rec.Code) // status is still untouched. err is returned from middleware chain
	assert.Contains(t, buf.String(), "")     // nothing is logged
}

func TestRecover_skipper(t *testing.T) {
	e := echo.New()

	req := httptest.NewRequest(

ror(), "[PANIC RECOVER] test goroutine")

	var pse *PanicStackError
	if errors.As(err, &pse) {
		assert.Contains(t, string(pse.Stack), "middleware/recover.go")
	} else {
		assert.Fail(t, "not of type PanicStackError")
	}

	assert


5"
	"github.com/stretchr/testify/assert"
)

type middlewareGenerator func() echo.MiddlewareFunc

func TestRedirectHTTPSRedirect(t *testing.T) {
	var testCases = []struct {
		whenHost         string
		whenHeader       http.Header
		expectLocation   string
		expectStatusCode int
	}{
		{
			whenHost:         "labstack.com",
			expectLocation:   "https://labstack.com/",
			expectStatusCode: http.StatusMovedPermanently,
		},
		{
			whenHost:         "labstack.com",
			whenHeader:       map[string][]string{echo.H

tc.whenHeader)

			assert.Equal(t, tc.expectStatusCode, res.Code)
			assert.Equal(t, tc.expectLocation, res.Header().Get(echo.HeaderLocation))
		})
	}
}

func TestRedirectHTTPSWWWRedirect(t *testing.T) {
	var testCases = []struct {
		whenHost         strin

eaderXForwardedProto: {"https"}},
			expectLocation:   "",
			expectStatusCode: http.StatusOK,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.whenHost, func(t *testing.T) {
			res := redirectTest(HTTPSRedirect, tc.whenHost,


package middleware

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/labstack/echo/v5"
	"github.com/stretchr/testify/assert"
)

func TestRequestID(t *testing.T) {
	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)
	handler := func(c *echo.Context) error {
		return c.String(http.StatusOK, "test")
	}

	rid := RequestID()
	h := rid(handler)
	err := h(c)
	assert.NoError(t, err)
	assert.Len(t, rec.Header().Get(echo.

estIDWithConfig(RequestIDConfig{
		Skipper: func(c *echo.Context) bool {
			return true
		},
		Generator: func() string {
			generatorCalled = true
			return "customGenerator"
		},
	}))

	req := httptest.NewRequest(http.MethodGet, "/", nil)
	res := httptes

HeaderXRequestID), 32)
}

func TestMustRequestIDWithConfig_skipper(t *testing.T) {
	e := echo.New()
	e.GET("/", func(c *echo.Context) error {
		return c.String(http.StatusTeapot, "test")
	})

	generatorCalled := false
	e.Use(Requ


Header.Set(echo.HeaderContentLength, strconv.Itoa(int(reader.Size())))
	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
	req.Header.Set(echo.HeaderXRealIP, "8.8.8.8")
	req.Header.Set("User-Agent", "curl/7.68.0")

	rec := httptest.NewRecorder()
	e.ServeHTTP(rec, req)

	logAttrs := map[string]any{}
	assert.NoError(t, json.Unmarshal(buf.Bytes(), &logAttrs))
	logAttrs["latency"] = 123
	logAttrs["time"] = "x"

	expect := map[string]any{
		"level":      "INFO",
		"msg":        "REQUEST",
		"metho

		"time":    "x",
		"latency": 123,
	}
	assert.Equal(t, expect, logAttrs)
}

func TestRequestLoggerError(t *testing.T) {
	old := slog.Default()
	t.Cleanup(func() {
		slog.SetDefault(old)
	})

	e := echo.New()
	buf := new(bytes.Buffer)
	e.Logger = slog.Ne

d":     "POST",
		"uri":        "/test",
		"status":     float64(418),
		"bytes_in":   "13",
		"host":       "example.com",
		"bytes_out":  float64(2),
		"user_agent": "curl/7.68.0",
		"remote_ip":  "8.8.8.8",
		"request_id": "",


 message=rate limit exceeded"},
		{id: "127.0.0.1", expectErr: "code=429, message=rate limit exceeded"},
		{id: "127.0.0.1", expectErr: "code=429, message=rate limit exceeded"},
		{id: "127.0.0.1", expectErr: "code=429, message=rate limit exceeded"},
	}

	for _, tc := range testCases {
		req := httptest.NewRequest(http.MethodGet, "/", nil)
		req.Header.Add(echo.HeaderXRealIP, tc.id)

		rec := httptest.NewRecorder()
		c := e.NewContext(req, rec)

		err := mw(handler)(c)
		if tc.expectErr != "" {
			assert.Eq

toreWithConfig(RateLimiterMemoryStoreConfig{Rate: 1, Burst: 3})

	assert.Panics(t, func() {
		RateLimiterWithConfig(RateLimiterConfig{})
	})

	assert.NotPanics(t, func() {
		RateLimiterWithConfig(RateLimiterConfig{Store: inMemoryStore})
	})
}

func TestRat

ualError(t, err, tc.expectErr)
		} else {
			assert.NoError(t, err)
		}
		assert.Equal(t, http.StatusOK, rec.Code)
	}
}

func TestMustRateLimiterWithConfig_panicBehaviour(t *testing.T) {
	var inMemoryStore = NewRateLimiterMemoryS


htText: © 2015 LabStack LLC and Echo contributors

package middleware

import (
	"github.com/labstack/echo/v5"
)

// RequestIDConfig defines the config for RequestID middleware.
type RequestIDConfig struct {
	// Skipper defines a function to skip middleware.
	Skipper Skipper

	// Generator defines a function to generate an ID.
	// Optional. Default value random.String(32).
	Generator func() string

	// RequestIDHandler defines a function which is executed for a request id.
	RequestIDHandler func(c *echo.Con

etHeader (`X-Request-ID`) header value or when
// the header value is empty, generates that value and sets request ID to response
// as RequestIDConfig.TargetHeader (`X-Request-Id`) value.
func RequestID() echo.MiddlewareFunc {
	return RequestIDWithConfig(

text, requestID string)

	// TargetHeader defines what header to look for to populate the id.
	// Optional. Default value is `X-Request-Id`
	TargetHeader string
}

// RequestID returns a middleware that reads RequestIDConfig.Targ


htText: © 2015 LabStack LLC and Echo contributors

package middleware

import (
	"errors"
	"maps"
	"regexp"

	"github.com/labstack/echo/v5"
)

// RewriteConfig defines the config for Rewrite middleware.
type RewriteConfig struct {
	// Skipper defines a function to skip middleware.
	Skipper Skipper

	// Rules defines the URL path rewrite rules. The values captured in asterisk can be
	// retrieved by index e.g. $1, $2 and so on.
	// Example:
	// "/old":              "/new",
	// "/api/*":            "/$1",
	//

apture group in the values can be retrieved by index e.g. $1, $2 and so on.
	// Example:
	// "^/old/[0.9]+/":     "/new",
	// "^/api/.+?/(.*)":     "/v2/$1",
	RegexRules map[*regexp.Regexp]string
}

// Rewrite returns a Rewrite middleware.
//
// Rewrite mi

 "/js/*":             "/public/javascripts/$1",
	// "/users/*/orders/*": "/user/$1/order/$2",
	// Required.
	Rules map[string]string

	// RegexRules defines the URL path rewrite rules using regexp.Rexexp with captures
	// Every c


directConfig defines the config for Redirect middleware.
type RedirectConfig struct {
	// Skipper defines a function to skip middleware.
	Skipper

	// Status code to be used when redirecting the request.
	// Optional. Default value http.StatusMovedPermanently.
	Code int

	redirect redirectLogic
}

// redirectLogic represents a function that given a scheme, host and uri
// can both: 1) determine if redirect is needed (will set ok accordingly) and
// 2) return the appropriate redirect url.
type redirectLogic

g is the HTTPS WWW Redirect middleware config.
var RedirectHTTPSWWWConfig = RedirectConfig{redirect: redirectHTTPSWWW}

// RedirectNonHTTPSWWWConfig is the non HTTPS WWW Redirect middleware config.
var RedirectNonHTTPSWWWConfig = RedirectConfig{redirect: r

func(scheme, host, uri string) (ok bool, url string)

const www = "www."

// RedirectHTTPSConfig is the HTTPS Redirect middleware config.
var RedirectHTTPSConfig = RedirectConfig{redirect: redirectHTTPS}

// RedirectHTTPSWWWConfi


fig for Secure middleware.
type SecureConfig struct {
	// Skipper defines a function to skip middleware.
	Skipper Skipper

	// XSSProtection provides protection against cross-site scripting attack (XSS)
	// by setting the `X-XSS-Protection` header.
	// Optional. Default value "1; mode=block".
	XSSProtection string

	// ContentTypeNosniff provides protection against overriding Content-Type
	// header by setting the `X-Content-Type-Options` header.
	// Optional. Default value "nosniff".
	ContentTypeNosniff st

 content is not embedded into other sites.provides protection against
	// clickjacking.
	// Optional. Default value "SAMEORIGIN".
	// Possible values:
	// - "SAMEORIGIN" - The page can only be displayed in a frame on the same origin as the page itself.
	//

ring

	// XFrameOptions can be used to indicate whether or not a browser should
	// be allowed to render a page in a <frame>, <iframe> or <object> .
	// Sites can use this to avoid clickjacking attacks, by ensuring that their
	//


package middleware

import (
	"errors"
	"net/http"
	"strings"

	"github.com/labstack/echo/v5"
)

// AddTrailingSlashConfig is the middleware config for adding trailing slash to the request.
type AddTrailingSlashConfig struct {
	// Skipper defines a function to skip middleware.
	Skipper Skipper

	// Status code to be used when redirecting the request.
	// Optional, but when provided the request is redirected using this code.
	// Valid status codes: [300...308]
	RedirectCode int
}

// AddTrailingSlash returns

ilingSlashConfig{})
}

// AddTrailingSlashWithConfig returns an AddTrailingSlash middleware with config or panics on invalid configuration.
func AddTrailingSlashWithConfig(config AddTrailingSlashConfig) echo.MiddlewareFunc {
	return toMiddlewareOrPanic(con

 a root level (before router) middleware which adds a
// trailing slash to the request `URL#Path`.
//
// Usage `Echo#Pre(AddTrailingSlash())`
func AddTrailingSlash() echo.MiddlewareFunc {
	return AddTrailingSlashWithConfig(AddTra


LogStatus:   true,
//		LogURI:      true,
//		HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code
//		LogValuesFunc: func(c *echo.Context, v middleware.RequestLoggerValues) error {
//			if v.Error == nil {
//				logger.LogAttrs(context.Background(), slog.LevelInfo, "REQUEST",
//					slog.String("uri", v.URI),
//					slog.Int("status", v.Status),
//				)
//			} else {
//				logger.LogAttrs(context.Background(), slog.LevelError, "REQUEST_ERROR",
//					sl

ddleware.RequestLoggerConfig{
//		LogStatus:   true,
//		LogURI:      true,
//		HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code
//		LogValuesFunc: func(c *echo.Context, v middleware.RequestLoggerVa

og.String("uri", v.URI),
//					slog.Int("status", v.Status),
//					slog.String("err", v.Error.Error()),
//				)
//			}
//			return nil
//		},
//	}))
//
// Example for `fmt.Printf`
// 	e.Use(middleware.RequestLoggerWithConfig(mi


5"
	"github.com/stretchr/testify/assert"
)

func TestAddTrailingSlashWithConfig(t *testing.T) {
	var testCases = []struct {
		whenURL        string
		whenMethod     string
		expectPath     string
		expectLocation []string
		expectStatus   int
	}{
		{
			whenURL:        "/add-slash",
			whenMethod:     http.MethodGet,
			expectPath:     "/add-slash",
			expectLocation: []string{`/add-slash/`},
		},
		{
			whenURL:        "/add-slash?key=value",
			whenMethod:     http.MethodGet,
			expectPath:     "/add-slas

or open redirect vulnerability
		{
			whenURL:        "http://localhost:1323/%5Cexample.com",
			expectPath:     `/\example.com`,
			expectLocation: []string{`/example.com/`},
		},
		{
			whenURL:        `http://localhost:1323/\example.com`,
			expectPath:

h",
			expectLocation: []string{`/add-slash/?key=value`},
		},
		{
			whenURL:        "/",
			whenMethod:     http.MethodConnect,
			expectPath:     "/",
			expectLocation: nil,
			expectStatus:   http.StatusOK,
		},
		// cases f


 root
	e.Use(StaticWithConfig(StaticConfig{
		Root: "testdata/dist/public",
	}))

	// all requests to `/api/*` will end up in echo handlers (assuming there is not `api` folder and files)
	api := e.Group("/api")
	users := api.Group("/users")
	users.GET("/info", func(c *echo.Context) error {
		return c.String(http.StatusOK, "users info")
	})

	req := httptest.NewRequest(http.MethodGet, "/api/users/info", nil)
	rec := httptest.NewRecorder()
	e.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	asse

ody.String(), "
Hello from index
\n")

}

func TestStatic(t *testing.T) {
	var testCases = []struct {
		name                 string
		givenConfig          *StaticConfig
		givenAttachedToGroup string
		whenURL              string
		expectContains

rt.Equal(t, "users info", rec.Body.String())

	req = httptest.NewRequest(http.MethodGet, "/index.html", nil)
	rec = httptest.NewRecorder()
	e.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Contains(t, rec.B


	"github.com/stretchr/testify/require"
)

type discardHandler struct {
	slog.JSONHandler
}

func (d *discardHandler) Enabled(context.Context, slog.Level) bool { return false }

func Test_matchScheme(t *testing.T) {
	tests := []struct {
		domain, pattern string
		expected        bool
	}{
		{
			domain:   "http://example.com",
			pattern:  "http://example.com",
			expected: true,
		},
		{
			domain:   "https://example.com",
			pattern:  "https://example.com",
			expected: true,
		},
		{
			domain:   "http://e

cted, matchScheme(v.domain, v.pattern))
	}
}

func TestRandomString(t *testing.T) {
	var testCases = []struct {
		name       string
		whenLength uint8
		expect     string
	}{
		{
			name:       "ok, 16",
			whenLength: 16,
		},
		{
			name:       "ok, 32",

xample.com",
			pattern:  "https://example.com",
			expected: false,
		},
		{
			domain:   "https://example.com",
			pattern:  "http://example.com",
			expected: false,
		},
	}

	for _, v := range tests {
		assert.Equal(t, v.expe


"
	"github.com/stretchr/testify/assert"
)

func TestServeWithHandler(t *testing.T) {
	handler := func(c *echo.Context) error {
		return c.String(http.StatusOK, c.QueryParam("key"))
	}
	testConf := ContextConfig{
		QueryValues: url.Values{"key": []string{"value"}},
	}

	resp := testConf.ServeWithHandler(t, handler)

	assert.Equal(t, http.StatusOK, resp.Code)
	assert.Equal(t, "value", resp.Body.String())
}

func TestServeWithHandler_error(t *testing.T) {
	handler := func(c *echo.Context) error {
		return echo

WithHandler(t, handler, customErrHandler)

	assert.Equal(t, http.StatusBadRequest, resp.Code)
	assert.Equal(t, `{"message":"something went wrong"}`+"\n", resp.Body.String())
}

func TestToContext_QueryValues(t *testing.T) {
	testConf := ContextConfig{
		Qu

.NewHTTPError(http.StatusBadRequest, "something went wrong")
	}
	testConf := ContextConfig{
		QueryValues: url.Values{"key": []string{"value"}},
	}

	customErrHandler := echo.DefaultHTTPErrorHandler(true)

	resp := testConf.Serve


package middleware

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/labstack/echo/v5"
	"github.com/stretchr/testify/assert"
)

func TestSecure(t *testing.T) {
	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)
	h := func(c *echo.Context) error {
		return c.String(http.StatusOK, "test")
	}

	// Default
	err := Secure()(h)(c)
	assert.NoError(t, err)

	assert.Equal(t, "1; mode=block", rec.Header().Get(echo.Header

sportSecurity))
	assert.Equal(t, "", rec.Header().Get(echo.HeaderContentSecurityPolicy))
	assert.Equal(t, "", rec.Header().Get(echo.HeaderReferrerPolicy))
}

func TestSecureWithConfig(t *testing.T) {
	e := echo.New()
	h := func(c *echo.Context) error {
		r

XXSSProtection))
	assert.Equal(t, "nosniff", rec.Header().Get(echo.HeaderXContentTypeOptions))
	assert.Equal(t, "SAMEORIGIN", rec.Header().Get(echo.HeaderXFrameOptions))
	assert.Equal(t, "", rec.Header().Get(echo.HeaderStrictTran


// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors

package middleware

import (
	"errors"
	"io/fs"
	"os"
)

// We ignore these errors as there could be handler that matches request path.
func isIgnorableOpenFileError(err error) bool {
	if os.IsNotExist(err) {
		return true
	}
	// As of Go 1.20 Windows path checks are more strict on the provided path and considers [UNC](https://en.wikipedia.org/wiki/Path_(computing)#UNC)
	// paths with missing host etc parts

	// errors in the middleware/handler chain. Otherwise we might end up with status 500 instead of finding a route
	// or return 404 not found.
	var pErr *fs.PathError
	if errors.As(err, &pErr) {
		err = pErr.Err
		return err.Error() == "invalid argument"

 as invalid. Previously it would result you `fs.ErrNotExist`.
	// Also `fs.Open` on all OSes does not accept ``, `.`, `..` at all.
	//
	// so we need to treat those errors the same as `fs.ErrNotExists` so we can continue handling


"
	"strings"
	"testing"

	"github.com/labstack/echo/v5"
)

// ContextConfig is configuration for creating echo.Context for testing purposes.
type ContextConfig struct {
	// Request will be used instead of default `httptest.NewRequest(http.MethodGet, "/", nil)`
	Request *http.Request

	// Response will be used instead of default `httptest.NewRecorder()`
	Response *httptest.ResponseRecorder

	// QueryValues will be set as Request.URL.RawQuery value
	QueryValues url.Values

	// Headers will be set as Request.H

ues creates form-urlencoded form out of given values. If there is no
	// `content-type` header it will be set to `application/x-www-form-urlencoded`
	// In case Request was not set the Request.Method is set to `POST`
	//
	// FormValues, MultipartForm and J

eader value
	Headers http.Header

	// PathValues initializes context.PathValues with given value.
	PathValues echo.PathValues

	// RouteInfo initializes context.RouteInfo() with given value
	RouteInfo *echo.RouteInfo

	// FormVal


dleware.
	Skipper Skipper

	// Root directory from where the static content is served (relative to given Filesystem).
	// `Root: "."` means root folder from Filesystem.
	// Required.
	Root string

	// Filesystem provides access to the static content.
	// Optional. Defaults to echo.Filesystem (serves files from `.` folder where executable is started)
	Filesystem fs.FS

	// Index file for serving a directory.
	// Optional. Default value "index.html".
	Index string

	// Enable HTML5 mode by forwarding all not-

gnoring of the base of the URL path.
	// Example: when assigning a static middleware to a non root path group,
	// the filesystem path is not doubled
	// Optional. Default value false.
	IgnoreBase bool

	// DisablePathUnescaping disables path parameter (pa

found requests to root so that
	// SPA (single-page application) can handle the routing.
	// Optional. Default value false.
	HTML5 bool

	// Enable directory browsing.
	// Optional. Default value false.
	Browse bool

	// Enable i


// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors

package echotest

import (
	"os"
	"path/filepath"
	"runtime"
	"testing"
)

type loadBytesOpts func([]byte) []byte

// TrimNewlineEnd instructs LoadBytes to remove `\n` from the end of loaded file.
func TrimNewlineEnd(bytes []byte) []byte {
	bLen := len(bytes)
	if bLen > 1 && bytes[bLen-1] == '\n' {
		bytes = bytes[:bLen-1]
	}
	return bytes
}

// LoadBytes is helper to load file contents relative to current

s(t *testing.T, name string, callDepth int) []byte {
	_, b, _, _ := runtime.Caller(callDepth)
	basepath := filepath.Dir(b)

	path := filepath.Join(basepath, name) // relative path
	bytes, err := os.ReadFile(path)
	if err != nil {
		t.Fatal(err)
	}
	return

(where test file is) package
// directory.
func LoadBytes(t *testing.T, name string, opts ...loadBytesOpts) []byte {
	bytes := loadBytes(t, name, 2)

	for _, f := range opts {
		bytes = f(bytes)
	}

	return bytes
}

func loadByte


htText: © 2015 LabStack LLC and Echo contributors

package middleware

import (
	"bufio"
	"crypto/rand"
	"fmt"
	"io"
	"net/url"
	"strings"
	"sync"
)

const (
	_ = int64(1 << (10 * iota)) // ignore first value by assigning to blank identifier
	// KB is 1 KiloByte = 1024 bytes
	KB
	// MB is 1 Megabyte = 1_048_576 bytes
	MB
	// GB is 1 Gigabyte = 1_073_741_824 bytes
	GB
	// TB is 1 Terabyte = 1_099_511_627_776 bytes
	TB
	// PB is 1 Petabyte = 1_125_899_906_842_624 bytes
	PB
	// EB is 1 Exabyte = 1_152_921_504_

andomStringGenerator(length uint8) func() string {
	return func() string {
		return randomString(length)
	}
}

// https://tip.golang.org/doc/go1.19#:~:text=Read%20no%20longer%20buffers%20random%20data%20obtained%20from%20the%20operating%20system%20between%

606_847_000 bytes
	EB
)

func matchScheme(domain, pattern string) bool {
	didx := strings.Index(domain, ":")
	pidx := strings.Index(pattern, ":")
	return didx != -1 && pidx != -1 && domain[:didx] == pattern[:pidx]
}

func createR