mirror of
https://github.com/gin-gonic/gin.git
synced 2026-09-04 14:49:27 +08:00
Merge ed773a4d385e0429757a0ebbe8aa8900ce10b44b into dcaa4296d111981ffb31ac3eba90bb63e1eb5ab9
This commit is contained in:
commit
ea8e375072
@ -10,6 +10,7 @@ import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"unicode"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/gin-gonic/gin/codec/json"
|
||||
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||
@ -160,11 +161,20 @@ func (r AsciiJSON) Render(w http.ResponseWriter) error {
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
escapeBuf := make([]byte, 0, 6) // Preallocate 6 bytes for Unicode escape sequences
|
||||
escapeBuf := make([]byte, 0, 13) // Preallocate for worst case: two \uXXXX surrogate pair escapes
|
||||
|
||||
for _, r := range bytesconv.BytesToString(ret) {
|
||||
if r > unicode.MaxASCII {
|
||||
escapeBuf = fmt.Appendf(escapeBuf[:0], "\\u%04x", r) // Reuse escapeBuf
|
||||
if r > 0xFFFF {
|
||||
// Non-BMP character (above U+FFFF): encode as a UTF-16 surrogate pair.
|
||||
// A JSON \u escape is exactly 4 hex digits, so code points requiring more
|
||||
// than 4 digits must be split into a high/low surrogate pair per RFC 8259 §7.
|
||||
high, low := utf16.EncodeRune(r)
|
||||
escapeBuf = fmt.Appendf(escapeBuf[:0], "\\u%04x\\u%04x", high, low)
|
||||
} else {
|
||||
// BMP character (U+0080–U+FFFF): a single \uXXXX escape suffices.
|
||||
escapeBuf = fmt.Appendf(escapeBuf[:0], "\\u%04x", r)
|
||||
}
|
||||
buffer.Write(escapeBuf)
|
||||
} else {
|
||||
buffer.WriteByte(byte(r))
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"html/template"
|
||||
@ -261,6 +262,36 @@ func TestRenderAsciiJSON(t *testing.T) {
|
||||
assert.Equal(t, "3.1415926", w2.Body.String())
|
||||
}
|
||||
|
||||
func TestRenderAsciiJSONNonBMP(t *testing.T) {
|
||||
// Non-BMP characters (code points above U+FFFF) must be encoded as UTF-16
|
||||
// surrogate pairs in JSON \u escapes (RFC 8259 §7). Previously, AsciiJSON
|
||||
// emitted a single \uXXXXX escape with 5+ hex digits, which is syntactically
|
||||
// valid JSON but silently decodes to the wrong character.
|
||||
inputs := []string{
|
||||
"\U0001F600", // U+1F600 GRINNING FACE
|
||||
"\U0001F602", // U+1F602 FACE WITH TEARS OF JOY
|
||||
"\U0001F680", // U+1F680 ROCKET
|
||||
"\U00020000", // U+20000 CJK Extension B
|
||||
}
|
||||
for _, input := range inputs {
|
||||
w := httptest.NewRecorder()
|
||||
err := (AsciiJSON{input}).Render(w)
|
||||
require.NoError(t, err)
|
||||
|
||||
body := w.Body.String()
|
||||
|
||||
// Output must be pure ASCII.
|
||||
for i, b := range []byte(body) {
|
||||
assert.Less(t, b, byte(128), "non-ASCII byte at index %d in rendered output for input %q", i, input)
|
||||
}
|
||||
|
||||
// Round-trip: json.Unmarshal must recover the original string.
|
||||
var decoded string
|
||||
require.NoError(t, json.Unmarshal([]byte(body), &decoded), "input %q", input)
|
||||
assert.Equal(t, input, decoded, "round-trip failed for %q", input)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderAsciiJSONFail(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := make(chan int)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user