Files
dependabot[bot]GitHubpaskaldependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
638fa63e81 Bump the go-modules-updates group in /backend with 7 updates (#1995)
* Bump the go-modules-updates group in /backend with 7 updates

Bumps the go-modules-updates group in /backend with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) | `2.21.1` | `2.23.1` |
| [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) | `5.2.3` | `5.2.4` |
| [github.com/go-pkgz/rest](https://github.com/go-pkgz/rest) | `1.20.6` | `1.21.0` |
| [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) | `5.3.0` | `5.3.1` |
| [golang.org/x/crypto](https://github.com/golang/crypto) | `0.46.0` | `0.47.0` |
| [golang.org/x/image](https://github.com/golang/image) | `0.34.0` | `0.35.0` |
| [golang.org/x/net](https://github.com/golang/net) | `0.48.0` | `0.49.0` |


Updates `github.com/alecthomas/chroma/v2` from 2.21.1 to 2.23.1
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.21.1...v2.23.1)

Updates `github.com/go-chi/chi/v5` from 5.2.3 to 5.2.4
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.2.3...v5.2.4)

Updates `github.com/go-pkgz/rest` from 1.20.6 to 1.21.0
- [Release notes](https://github.com/go-pkgz/rest/releases)
- [Commits](https://github.com/go-pkgz/rest/compare/v1.20.6...v1.21.0)

Updates `github.com/golang-jwt/jwt/v5` from 5.3.0 to 5.3.1
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Commits](https://github.com/golang-jwt/jwt/compare/v5.3.0...v5.3.1)

Updates `golang.org/x/crypto` from 0.46.0 to 0.47.0
- [Commits](https://github.com/golang/crypto/compare/v0.46.0...v0.47.0)

Updates `golang.org/x/image` from 0.34.0 to 0.35.0
- [Commits](https://github.com/golang/image/compare/v0.34.0...v0.35.0)

Updates `golang.org/x/net` from 0.48.0 to 0.49.0
- [Commits](https://github.com/golang/net/compare/v0.48.0...v0.49.0)

---
updated-dependencies:
- dependency-name: github.com/alecthomas/chroma/v2
  dependency-version: 2.23.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules-updates
- dependency-name: github.com/go-chi/chi/v5
  dependency-version: 5.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-modules-updates
- dependency-name: github.com/go-pkgz/rest
  dependency-version: 1.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules-updates
- dependency-name: github.com/golang-jwt/jwt/v5
  dependency-version: 5.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-modules-updates
- dependency-name: golang.org/x/crypto
  dependency-version: 0.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules-updates
- dependency-name: golang.org/x/image
  dependency-version: 0.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules-updates
- dependency-name: golang.org/x/net
  dependency-version: 0.49.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules-updates
...

Signed-off-by: dependabot[bot] <support@github.com>

* Run go mod tidy in examples directory

Co-authored-by: paskal <712534+paskal@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: paskal <712534+paskal@users.noreply.github.com>
2026-02-14 19:48:24 -06:00

131 lines
3.2 KiB
Go

package rest
import (
"context"
"net/http"
"net/url"
"path"
"regexp"
"strings"
)
// CleanPath middleware cleans double slashes from URL path.
// For example, if a request is made to /users//1 or //users////1,
// it will be cleaned to /users/1 before routing.
// Trailing slashes are preserved: /users//1/ becomes /users/1/.
// Dot segments (. and ..) are intentionally NOT cleaned to preserve routing semantics.
func CleanPath(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rctx := r.Context()
// skip if already cleaned
if _, ok := rctx.Value(contextKey("cleanpath")).(bool); ok {
next.ServeHTTP(w, r)
return
}
p := r.URL.Path
cleaned := cleanDoubleSlashes(p)
if cleaned != p {
r.URL.Path = cleaned
if r.URL.RawPath != "" {
// clean double slashes in RawPath separately to preserve percent-encoding
r.URL.RawPath = cleanDoubleSlashes(r.URL.RawPath)
}
rctx = context.WithValue(rctx, contextKey("cleanpath"), true)
r = r.WithContext(rctx)
}
next.ServeHTTP(w, r)
})
}
// cleanDoubleSlashes removes consecutive slashes from path while preserving
// trailing slashes and dot segments (. and ..).
func cleanDoubleSlashes(p string) string {
if p == "" || p == "/" {
return p
}
var b strings.Builder
b.Grow(len(p))
prevSlash := false
for i := 0; i < len(p); i++ {
c := p[i]
if c == '/' {
if !prevSlash {
b.WriteByte(c)
}
prevSlash = true
} else {
b.WriteByte(c)
prevSlash = false
}
}
return b.String()
}
// StripSlashes middleware removes trailing slashes from URL path.
// For example, /users/1/ becomes /users/1.
// The root path "/" is preserved.
func StripSlashes(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
p := r.URL.Path
if len(p) > 1 && p[len(p)-1] == '/' {
r.URL.Path = p[:len(p)-1]
if r.URL.RawPath != "" {
r.URL.RawPath = strings.TrimSuffix(r.URL.RawPath, "/")
}
}
next.ServeHTTP(w, r)
})
}
// Rewrite middleware with from->to rule. Supports regex (like nginx) and prevents multiple rewrites
// example: Rewrite(`^/sites/(.*)/settings/$`, `/sites/settings/$1`
func Rewrite(from, to string) func(http.Handler) http.Handler {
reFrom := regexp.MustCompile(from)
f := func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// prevent double rewrites
if ctx != nil {
if _, ok := ctx.Value(contextKey("rewrite")).(bool); ok {
next.ServeHTTP(w, r)
return
}
}
if !reFrom.MatchString(r.URL.Path) {
next.ServeHTTP(w, r)
return
}
ru := reFrom.ReplaceAllString(r.URL.Path, to)
cru := path.Clean(ru)
if strings.HasSuffix(ru, "/") { // don't drop trailing slash
cru += "/"
}
u, e := url.Parse(cru)
if e != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
r.Header.Set("X-Original-URL", r.URL.RequestURI())
r.URL.Path = u.Path
r.URL.RawPath = u.RawPath
if u.RawQuery != "" {
r.URL.RawQuery = u.RawQuery
}
ctx = context.WithValue(ctx, contextKey("rewrite"), true)
next.ServeHTTP(w, r.WithContext(ctx))
}
return http.HandlerFunc(fn)
}
return f
}