Description
Issue Description
Echo's CORS middleware misclassifies all OPTIONS
requests as preflight requests, thereby unduly preventing requests from hitting user-registered OPTIONS
endpoints.
I've previously discussed the general problem on my personal blog and how Echo suffers from it in issue #2510.
Checklist
- Dependencies installed
- No typos
- Searched existing issues and docs
Expected behaviour
(For this section and the next, please refer to the server code below.)
Consider the OPTIONS
requests resulting from the following two curl
commands:
curl -v -XOPTIONS \
localhost:8080/hello
curl -v -XOPTIONS \
-H "Origin: https://example.com" \
localhost:8080/hello
According to the Fetch standard, neither request is a preflight request, because
- the first one lacks both an
Origin
header and anAccess-Control-Request-Method
header, and - the second one lacks an
Access-Control-Request-Method
header.
Therefore, those requests should get through the CORS middleware, exercise the handler registered on /hello
, and get a response of this kind:
HTTP/1.1 204 No Content
Allow: GET, OPTIONS
Date: Mon, 23 Oct 2023 12:30:33 GMT
In contrast, an OPTIONS
requests resulting from the following curl
command is a bona fide preflight request; as such, it should be (and is) intercepted and handled by the CORS middleware:
curl -v -XOPTIONS \
-H "Origin: https://example.com" \
-H "Access-Control-Request-Method: PUT" \
localhost:8080/hello
HTTP/1.1 204 No Content
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Origin: *
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
-snip-
Actual behaviour
The first two aforementioned OPTIONS
requests get intercepted and handled by the CORS middleware rather than by the handler registered on OPTIONS /hello
.
Steps to reproduce
- Run the program to start the server.
- Exercise it via
curl
(see commands above).
Working code to debug
package main
import (
"errors"
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
AllowMethods: []string{http.MethodPut},
}))
e.GET("/hello", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
e.OPTIONS("/hello", func(c echo.Context) error {
c.Response().Header().Set("Allow", "GET, OPTIONS")
c.Response().WriteHeader(http.StatusNoContent)
return nil
})
if err := e.Start(":8080"); err != nil && !errors.Is(err, http.ErrServerClosed) {
e.Logger.Fatal(err)
}
}
Version/commit
Echo v4.11.2. See my go.mod
below:
module whatever
go 1.21.3
require github.com/labstack/echo/v4 v4.11.2
require (
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
)