mirror of
https://github.com/gin-gonic/gin.git
synced 2026-06-05 02:18:15 +08:00
Merge 528a95d48d992f168084a648a414740d3caec116 into d3ffc9985281dcf4d3bef604cce4e662b1a327a6
This commit is contained in:
commit
ebb4581038
@ -132,9 +132,10 @@ func (c *Context) Copy() *Context {
|
|||||||
cp.handlers = nil
|
cp.handlers = nil
|
||||||
cp.fullPath = c.fullPath
|
cp.fullPath = c.fullPath
|
||||||
|
|
||||||
cKeys := c.Keys
|
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
cp.Keys = maps.Clone(cKeys)
|
if c.Keys != nil {
|
||||||
|
cp.Keys = maps.Clone(c.Keys)
|
||||||
|
}
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
|
|
||||||
cParams := c.Params
|
cParams := c.Params
|
||||||
|
|||||||
17
recovery.go
17
recovery.go
@ -91,16 +91,25 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// secureRequestDump returns a sanitized HTTP request dump where the Authorization header,
|
// secureRequestDump returns a sanitized HTTP request dump where the Authorization
|
||||||
// if present, is replaced with a masked value ("Authorization: *") to avoid leaking sensitive credentials.
|
// and Proxy-Authorization headers, if present, are replaced with a masked value
|
||||||
|
// (e.g. "Authorization: *") to avoid leaking sensitive credentials.
|
||||||
//
|
//
|
||||||
// Currently, only the Authorization header is sanitized. All other headers and request data remain unchanged.
|
// Header name matching is case-insensitive since HTTP headers are case-insensitive
|
||||||
|
// per RFC 9110. All other headers and request data remain unchanged.
|
||||||
func secureRequestDump(r *http.Request) string {
|
func secureRequestDump(r *http.Request) string {
|
||||||
httpRequest, _ := httputil.DumpRequest(r, false)
|
httpRequest, _ := httputil.DumpRequest(r, false)
|
||||||
lines := strings.Split(bytesconv.BytesToString(httpRequest), "\r\n")
|
lines := strings.Split(bytesconv.BytesToString(httpRequest), "\r\n")
|
||||||
|
const (
|
||||||
|
authPrefix = "Authorization:"
|
||||||
|
proxyPrefix = "Proxy-Authorization:"
|
||||||
|
)
|
||||||
for i, line := range lines {
|
for i, line := range lines {
|
||||||
if strings.HasPrefix(line, "Authorization:") {
|
switch {
|
||||||
|
case len(line) >= len(authPrefix) && strings.EqualFold(line[:len(authPrefix)], authPrefix):
|
||||||
lines[i] = "Authorization: *"
|
lines[i] = "Authorization: *"
|
||||||
|
case len(line) >= len(proxyPrefix) && strings.EqualFold(line[:len(proxyPrefix)], proxyPrefix):
|
||||||
|
lines[i] = "Proxy-Authorization: *"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return strings.Join(lines, "\r\n")
|
return strings.Join(lines, "\r\n")
|
||||||
|
|||||||
@ -274,25 +274,67 @@ func TestSecureRequestDump(t *testing.T) {
|
|||||||
wantNotContain: "Bearer secret-token",
|
wantNotContain: "Bearer secret-token",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// Bypass http.Header.Set canonicalization to put a lowercase
|
||||||
|
// header name on the wire and verify case-insensitive matching.
|
||||||
name: "authorization header lowercase",
|
name: "authorization header lowercase",
|
||||||
req: func() *http.Request {
|
req: func() *http.Request {
|
||||||
r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||||
r.Header.Set("authorization", "some-secret")
|
r.Header["authorization"] = []string{"some-secret"}
|
||||||
return r
|
return r
|
||||||
}(),
|
}(),
|
||||||
wantContains: "Authorization: *",
|
wantContains: "Authorization: *",
|
||||||
wantNotContain: "some-secret",
|
wantNotContain: "some-secret",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "AUTHORIZATION header uppercase",
|
||||||
|
req: func() *http.Request {
|
||||||
|
r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||||
|
r.Header["AUTHORIZATION"] = []string{"UPPER-SECRET"}
|
||||||
|
return r
|
||||||
|
}(),
|
||||||
|
wantContains: "Authorization: *",
|
||||||
|
wantNotContain: "UPPER-SECRET",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Authorization header mixed case",
|
name: "Authorization header mixed case",
|
||||||
req: func() *http.Request {
|
req: func() *http.Request {
|
||||||
r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||||
r.Header.Set("AuThOrIzAtIoN", "token123")
|
r.Header["AuThOrIzAtIoN"] = []string{"token123"}
|
||||||
return r
|
return r
|
||||||
}(),
|
}(),
|
||||||
wantContains: "Authorization: *",
|
wantContains: "Authorization: *",
|
||||||
wantNotContain: "token123",
|
wantNotContain: "token123",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Proxy-Authorization header",
|
||||||
|
req: func() *http.Request {
|
||||||
|
r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||||
|
r.Header.Set("Proxy-Authorization", "Basic cHJveHk6c2VjcmV0")
|
||||||
|
return r
|
||||||
|
}(),
|
||||||
|
wantContains: "Proxy-Authorization: *",
|
||||||
|
wantNotContain: "Basic cHJveHk6c2VjcmV0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "proxy-authorization header lowercase",
|
||||||
|
req: func() *http.Request {
|
||||||
|
r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||||
|
r.Header["proxy-authorization"] = []string{"Basic bG93ZXI="}
|
||||||
|
return r
|
||||||
|
}(),
|
||||||
|
wantContains: "Proxy-Authorization: *",
|
||||||
|
wantNotContain: "Basic bG93ZXI=",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PROXY-AUTHORIZATION header uppercase",
|
||||||
|
req: func() *http.Request {
|
||||||
|
r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||||
|
r.Header["PROXY-AUTHORIZATION"] = []string{"Basic VVBQRVI="}
|
||||||
|
return r
|
||||||
|
}(),
|
||||||
|
wantContains: "Proxy-Authorization: *",
|
||||||
|
wantNotContain: "Basic VVBQRVI=",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "No Authorization header",
|
name: "No Authorization header",
|
||||||
req: func() *http.Request {
|
req: func() *http.Request {
|
||||||
|
|||||||
@ -160,11 +160,19 @@ func (r AsciiJSON) Render(w http.ResponseWriter) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
escapeBuf := make([]byte, 0, 6) // Preallocate 6 bytes for Unicode escape sequences
|
escapeBuf := make([]byte, 0, 12) // Preallocate for surrogate pair escape sequences
|
||||||
|
|
||||||
for _, r := range bytesconv.BytesToString(ret) {
|
for _, r := range bytesconv.BytesToString(ret) {
|
||||||
if r > unicode.MaxASCII {
|
if r > unicode.MaxASCII {
|
||||||
escapeBuf = fmt.Appendf(escapeBuf[:0], "\\u%04x", r) // Reuse escapeBuf
|
if r > 0xFFFF {
|
||||||
|
// Supplementary plane: encode as UTF-16 surrogate pair per RFC 8259
|
||||||
|
r -= 0x10000
|
||||||
|
high := 0xD800 + (r>>10)&0x3FF
|
||||||
|
low := 0xDC00 + r&0x3FF
|
||||||
|
escapeBuf = fmt.Appendf(escapeBuf[:0], "\\u%04x\\u%04x", high, low)
|
||||||
|
} else {
|
||||||
|
escapeBuf = fmt.Appendf(escapeBuf[:0], "\\u%04x", r)
|
||||||
|
}
|
||||||
buffer.Write(escapeBuf)
|
buffer.Write(escapeBuf)
|
||||||
} else {
|
} else {
|
||||||
buffer.WriteByte(byte(r))
|
buffer.WriteByte(byte(r))
|
||||||
|
|||||||
@ -261,6 +261,17 @@ func TestRenderAsciiJSON(t *testing.T) {
|
|||||||
assert.Equal(t, "3.1415926", w2.Body.String())
|
assert.Equal(t, "3.1415926", w2.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderAsciiJSONSupplementaryUnicode(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
data := map[string]string{"emoji": "😀"}
|
||||||
|
|
||||||
|
err := (AsciiJSON{data}).Render(w)
|
||||||
|
require.NoError(t, err)
|
||||||
|
// U+1F600 must be encoded as UTF-16 surrogate pair per RFC 8259.
|
||||||
|
// Use Contains to verify the surrogate pair encoding in the raw output.
|
||||||
|
assert.Contains(t, w.Body.String(), `\ud83d\ude00`)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRenderAsciiJSONFail(t *testing.T) {
|
func TestRenderAsciiJSONFail(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
data := make(chan int)
|
data := make(chan int)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user