mirror of
https://github.com/gin-gonic/gin.git
synced 2026-07-11 21:31:16 +08:00
Compare commits
4 Commits
602926c1bb
...
fdadb46445
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdadb46445 | ||
|
|
52ecf029bd | ||
|
|
440eb14ab8 | ||
|
|
ce6939ce40 |
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
4
.github/workflows/gin.yml
vendored
4
.github/workflows/gin.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
@ -61,7 +61,7 @@ jobs:
|
||||
cache: false
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
|
||||
2
.github/workflows/goreleaser.yml
vendored
2
.github/workflows/goreleaser.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
|
||||
2
.github/workflows/trivy-scan.yml
vendored
2
.github/workflows/trivy-scan.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
24
docs/doc.md
24
docs/doc.md
@ -9,6 +9,7 @@
|
||||
- [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options)
|
||||
- [Parameters in path](#parameters-in-path)
|
||||
- [Querystring parameters](#querystring-parameters)
|
||||
- [Retrieving the path of a registered handler](#retrieving-the-path-of-a-registered-handler)
|
||||
- [Multipart/Urlencoded Form](#multiparturlencoded-form)
|
||||
- [Another example: query + post form](#another-example-query--post-form)
|
||||
- [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters)
|
||||
@ -184,6 +185,29 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Retrieving the path of a registered handler
|
||||
|
||||
Once a handler is registered with the router, you can retrieve its path with the `PathFor` method:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
// Creates a gin router with default middleware:
|
||||
// logger and recovery (crash-free) middleware
|
||||
router := gin.Default()
|
||||
|
||||
getUser := func(c *Context) {
|
||||
// Handle the GET request.
|
||||
}
|
||||
router.GET("/users/:name", getUser)
|
||||
path := router.PathFor(getUser, ":name", "gopher")
|
||||
|
||||
print(path) // Prints /users/gopher
|
||||
|
||||
router.Run()
|
||||
}
|
||||
router := gin.Default()
|
||||
```
|
||||
|
||||
### Multipart/Urlencoded Form
|
||||
|
||||
```go
|
||||
|
||||
87
gin.go
87
gin.go
@ -11,7 +11,6 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@ -48,11 +47,6 @@ var defaultTrustedCIDRs = []*net.IPNet{
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+")
|
||||
regRemoveRepeatedChar = regexp.MustCompile("/{2,}")
|
||||
)
|
||||
|
||||
// HandlerFunc defines the handler used by gin middleware as return value.
|
||||
type HandlerFunc func(*Context)
|
||||
|
||||
@ -392,6 +386,24 @@ func (engine *Engine) Routes() (routes RoutesInfo) {
|
||||
return routes
|
||||
}
|
||||
|
||||
// Routes returns a slice of registered routes, including some useful information, such as:
|
||||
// the http method, path and the handler name.
|
||||
func (engine *Engine) Route(handler HandlerFunc) (route RouteInfo, ok bool) {
|
||||
handlerName := nameOfFunction(handler)
|
||||
routes := RoutesInfo{}
|
||||
for _, tree := range engine.trees {
|
||||
routes = iterate("", tree.method, routes, tree.root)
|
||||
|
||||
for _, route := range routes {
|
||||
if route.Handler == handlerName {
|
||||
return route, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RouteInfo{}, false
|
||||
}
|
||||
|
||||
func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
|
||||
path += root.path
|
||||
if len(root.handlers) > 0 {
|
||||
@ -685,6 +697,43 @@ func (engine *Engine) HandleContext(c *Context) {
|
||||
c.handlers = oldHandlers
|
||||
}
|
||||
|
||||
// PathFor returns the path registered for the specified handler function.
|
||||
// Route values are passed pair-wise as key value.
|
||||
func (engine *Engine) PathFor(handler HandlerFunc, values ...interface{}) string {
|
||||
route, ok := engine.Route(handler)
|
||||
|
||||
if !ok || len(route.Path) == 0 || len(values)%2 != 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
url := route.Path
|
||||
params := make(map[string]string)
|
||||
if len(values) > 0 {
|
||||
key := ""
|
||||
for k, v := range values {
|
||||
if k%2 == 0 {
|
||||
key = fmt.Sprint(v)
|
||||
} else {
|
||||
params[key] = fmt.Sprint(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
urls := strings.Split(url, "/")
|
||||
for _, v := range urls {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
if v[0:1] == ":" {
|
||||
if u, ok := params[v]; ok {
|
||||
delete(params, v)
|
||||
url = strings.Replace(url, v, u, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return url + toQuerystring(params)
|
||||
}
|
||||
|
||||
func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
httpMethod := c.Request.Method
|
||||
rPath := c.Request.URL.Path
|
||||
@ -776,8 +825,8 @@ func redirectTrailingSlash(c *Context) {
|
||||
req := c.Request
|
||||
p := req.URL.Path
|
||||
if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." {
|
||||
prefix = regSafePrefix.ReplaceAllString(prefix, "")
|
||||
prefix = regRemoveRepeatedChar.ReplaceAllString(prefix, "/")
|
||||
prefix = sanitizePathChars(prefix)
|
||||
prefix = removeRepeatedChar(prefix, '/')
|
||||
|
||||
p = prefix + "/" + req.URL.Path
|
||||
}
|
||||
@ -788,6 +837,17 @@ func redirectTrailingSlash(c *Context) {
|
||||
redirectRequest(c)
|
||||
}
|
||||
|
||||
// sanitizePathChars removes unsafe characters from path strings,
|
||||
// keeping only ASCII letters, ASCII numbers, forward slashes, and hyphens.
|
||||
func sanitizePathChars(s string) string {
|
||||
return strings.Map(func(r rune) rune {
|
||||
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '/' || r == '-' {
|
||||
return r
|
||||
}
|
||||
return -1
|
||||
}, s)
|
||||
}
|
||||
|
||||
func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
|
||||
req := c.Request
|
||||
rPath := req.URL.Path
|
||||
@ -813,3 +873,14 @@ func redirectRequest(c *Context) {
|
||||
http.Redirect(c.Writer, req, rURL, code)
|
||||
c.writermem.WriteHeaderNow()
|
||||
}
|
||||
|
||||
func toQuerystring(params map[string]string) string {
|
||||
if len(params) == 0 {
|
||||
return ""
|
||||
}
|
||||
u := "?"
|
||||
for k, v := range params {
|
||||
u += k + "=" + v + "&"
|
||||
}
|
||||
return strings.TrimRight(u, "&")
|
||||
}
|
||||
|
||||
19
gin_test.go
19
gin_test.go
@ -825,6 +825,25 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathFor(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
getIndex := func(c *Context) {}
|
||||
r.GET("/", getIndex)
|
||||
|
||||
users := r.Group("/users")
|
||||
|
||||
postUser := func(c *Context) {}
|
||||
users.POST("", postUser)
|
||||
|
||||
getUser := func(c *Context) {}
|
||||
users.GET("/:name", getUser)
|
||||
|
||||
assert.Equal(t, "/", r.PathFor(getIndex))
|
||||
assert.Equal(t, "/users", r.PathFor(postUser))
|
||||
assert.Equal(t, "/users/gopher", r.PathFor(getUser, ":name", "gopher"))
|
||||
}
|
||||
|
||||
func parseCIDR(cidr string) *net.IPNet {
|
||||
_, parsedCIDR, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
|
||||
55
path.go
55
path.go
@ -5,6 +5,8 @@
|
||||
|
||||
package gin
|
||||
|
||||
const stackBufSize = 128
|
||||
|
||||
// cleanPath is the URL version of path.Clean, it returns a canonical URL path
|
||||
// for p, eliminating . and .. elements.
|
||||
//
|
||||
@ -19,7 +21,6 @@ package gin
|
||||
//
|
||||
// If the result of this process is an empty string, "/" is returned.
|
||||
func cleanPath(p string) string {
|
||||
const stackBufSize = 128
|
||||
// Turn empty string into "/"
|
||||
if p == "" {
|
||||
return "/"
|
||||
@ -148,3 +149,55 @@ func bufApp(buf *[]byte, s string, w int, c byte) {
|
||||
}
|
||||
b[w] = c
|
||||
}
|
||||
|
||||
// removeRepeatedChar removes multiple consecutive 'char's from a string.
|
||||
// if s == "/a//b///c////" && char == '/', it returns "/a/b/c/"
|
||||
func removeRepeatedChar(s string, char byte) string {
|
||||
// Check if there are any consecutive chars
|
||||
hasRepeatedChar := false
|
||||
for i := 1; i < len(s); i++ {
|
||||
if s[i] == char && s[i-1] == char {
|
||||
hasRepeatedChar = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasRepeatedChar {
|
||||
return s
|
||||
}
|
||||
|
||||
// Reasonably sized buffer on stack to avoid allocations in the common case.
|
||||
buf := make([]byte, 0, stackBufSize)
|
||||
|
||||
// Invariants:
|
||||
// reading from s; r is index of next byte to process.
|
||||
// writing to buf; w is index of next byte to write.
|
||||
r := 0
|
||||
w := 0
|
||||
|
||||
for n := len(s); r < n; {
|
||||
if s[r] == char {
|
||||
// Write the first char
|
||||
bufApp(&buf, s, w, char)
|
||||
w++
|
||||
r++
|
||||
|
||||
// Skip all consecutive chars
|
||||
for r < n && s[r] == char {
|
||||
r++
|
||||
}
|
||||
} else {
|
||||
// Copy non-char character
|
||||
bufApp(&buf, s, w, s[r])
|
||||
w++
|
||||
r++
|
||||
}
|
||||
}
|
||||
|
||||
// If the original string was not modified (or only shortened at the end),
|
||||
// return the respective substring of the original string.
|
||||
// Otherwise, return a new string from the buffer.
|
||||
if len(buf) == 0 {
|
||||
return s[:w]
|
||||
}
|
||||
return string(buf[:w])
|
||||
}
|
||||
|
||||
47
path_test.go
47
path_test.go
@ -143,3 +143,50 @@ func BenchmarkPathCleanLong(b *testing.B) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveRepeatedChar(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
str string
|
||||
char byte
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
str: "",
|
||||
char: 'a',
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "noSlash",
|
||||
str: "abc",
|
||||
char: ',',
|
||||
want: "abc",
|
||||
},
|
||||
{
|
||||
name: "withSlash",
|
||||
str: "/a/b/c/",
|
||||
char: '/',
|
||||
want: "/a/b/c/",
|
||||
},
|
||||
{
|
||||
name: "withRepeatedSlashes",
|
||||
str: "/a//b///c////",
|
||||
char: '/',
|
||||
want: "/a/b/c/",
|
||||
},
|
||||
{
|
||||
name: "threeSlashes",
|
||||
str: "///",
|
||||
char: '/',
|
||||
want: "/",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
res := removeRepeatedChar(tc.str, tc.char)
|
||||
assert.Equal(t, tc.want, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user