echo icon indicating copy to clipboard operation
echo copied to clipboard

Add an AllowContentType middleware (based on chi)

Open pzolo85 opened this issue 2 years ago • 3 comments

Add an AllowContentType middleware

Checklist

  • [ x] Dependencies installed
  • [ x] No typos
  • [ x] Searched existing issues and docs

Expected behaviour

This is a very handy middleware available in Chi. I thought it would be a good idea to have one in echo.

Actual behaviour

N/A

Steps to reproduce

N/A

Working code to debug


func AllowContentType(contentTypes ...string) echo.MiddlewareFunc {
	if len(contentTypes) == 0 {
		panic("echo: allow-content middleware requires a valida content-type")
	}
	allowedContentTypes := make(map[string]struct{}, len(contentTypes))
	for _, ctype := range contentTypes {
		allowedContentTypes[strings.TrimSpace(strings.ToLower(ctype))] = struct{}{}
	}

	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			if c.Request().ContentLength == 0 {
				// skip check for empty content body
				return next(c)
			}
			s := strings.ToLower(strings.TrimSpace(c.Request().Header.Get("Content-Type")))
			if i := strings.Index(s, ";"); i > -1 {
				s = s[0:i]
			}
			if _, ok := allowedContentTypes[s]; ok {
				return next(c)
			}
			return echo.NewHTTPError(http.StatusUnsupportedMediaType)
		}
	}
}

Version/commit

pzolo85 avatar Nov 28 '23 12:11 pzolo85

@pzolo85

s := strings.ToLower(strings.TrimSpace(c.Request().Header.Get("Content-Type")))
  if i := strings.Index(s, ";"); i > -1 {
    s = s[0:i]
}

Why not rely on https://pkg.go.dev/mime#ParseMediaType instead?

jub0bs avatar Apr 01 '24 20:04 jub0bs

I do not know if this makes sense. If we leave out usual middleware boilerplate what you need is

func AllowContentType(contentTypes ...string) echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			mediaType, _, err := mime.ParseMediaType(c.Request().Header.Get("Content-Type"))
			if err != nil {
				return echo.NewHTTPError(http.StatusBadRequest, "invalid content-type value")
			}
			if slices.Contains(contentTypes, mediaType) {
				return next(c)
			}
			return echo.NewHTTPError(http.StatusUnsupportedMediaType)
		}
	}
}

so it is only mime.ParseMediaType + slices.Contains calls + error handling. Over time other people would add their special cases and the middleware "useful" code percentage will fall and code serving edge cases goes up.

It is not maybe that complex use case to build middleware for it.


Full example:

/*
curl --location 'http://localhost:8080/example' -d 'name=test'
*/
func main() {
	e := echo.New()

	e.Use(middleware.Logger())
	e.Use(middleware.Recover())
	
	e.Use(AllowContentType("application/x-www-form-urlencoded"))

	e.POST("/example", func(c echo.Context) error {
		return c.String(http.StatusOK, c.Path())
	})

	if err := e.Start(":8080"); err != nil && !errors.Is(err, http.ErrServerClosed) {
		log.Fatal(err)
	}
}

func AllowContentType(contentTypes ...string) echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			mediaType, _, err := mime.ParseMediaType(c.Request().Header.Get("Content-Type"))
			if err != nil {
				return echo.NewHTTPError(http.StatusBadRequest, "invalid content-type value")
			}
			if slices.Contains(contentTypes, mediaType) {
				return next(c)
			}
			return echo.NewHTTPError(http.StatusUnsupportedMediaType)
		}
	}
}

aldas avatar Apr 06 '24 13:04 aldas

I opened a PR #2655

grqphical avatar Jul 03 '24 02:07 grqphical