Skip to content

Commit 93c11a7

Browse files
authored
fix(httpx): Resolve HTML escaping issue during JSON serialization (#5032)
1 parent 63ec989 commit 93c11a7

File tree

2 files changed

+124
-2
lines changed

2 files changed

+124
-2
lines changed

rest/httpx/responses.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package httpx
22

33
import (
4+
"bytes"
45
"context"
56
"encoding/json"
67
"errors"
@@ -172,8 +173,28 @@ func doHandleError(w http.ResponseWriter, err error, handler func(error) (int, a
172173
}
173174
}
174175

176+
func doMarshalJson(v any) ([]byte, error) {
177+
// why not use json.Marshal? https://github.com/golang/go/issues/28453
178+
// it change the behavior of json.Marshal, like & -> \u0026, < -> \u003c, > -> \u003e
179+
// which is not what we want in logic response
180+
buf := &bytes.Buffer{}
181+
enc := json.NewEncoder(buf)
182+
enc.SetEscapeHTML(false)
183+
if err := enc.Encode(v); err != nil {
184+
return nil, err
185+
}
186+
187+
bs := buf.Bytes()
188+
// Remove trailing newline added by json.Encoder.Encode
189+
if len(bs) > 0 && bs[len(bs)-1] == '\n' {
190+
bs = bs[:len(bs)-1]
191+
}
192+
193+
return bs, nil
194+
}
195+
175196
func doWriteJson(w http.ResponseWriter, code int, v any) error {
176-
bs, err := json.Marshal(v)
197+
bs, err := doMarshalJson(v)
177198
if err != nil {
178199
http.Error(w, err.Error(), http.StatusInternalServerError)
179200
return fmt.Errorf("marshal json failed, error: %w", err)

rest/httpx/responses_test.go

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ import (
1212
"testing"
1313

1414
"github.com/stretchr/testify/assert"
15-
"github.com/zeromicro/go-zero/core/logx"
1615
"google.golang.org/grpc/codes"
1716
"google.golang.org/grpc/status"
17+
18+
"github.com/zeromicro/go-zero/core/logx"
1819
)
1920

2021
type message struct {
@@ -443,3 +444,103 @@ func TestWriteJsonCtxMarshalFailed(t *testing.T) {
443444
})
444445
assert.Equal(t, http.StatusInternalServerError, w.code)
445446
}
447+
448+
func Test_doMarshalJson(t *testing.T) {
449+
type args struct {
450+
v any
451+
}
452+
tests := []struct {
453+
name string
454+
args args
455+
want []byte
456+
wantErr assert.ErrorAssertionFunc
457+
}{
458+
{
459+
name: "nil",
460+
args: args{nil},
461+
want: []byte("null"),
462+
wantErr: assert.NoError,
463+
},
464+
{
465+
name: "string",
466+
args: args{"hello"},
467+
want: []byte(`"hello"`),
468+
wantErr: assert.NoError,
469+
},
470+
{
471+
name: "int",
472+
args: args{42},
473+
want: []byte("42"),
474+
wantErr: assert.NoError,
475+
},
476+
{
477+
name: "bool",
478+
args: args{true},
479+
want: []byte("true"),
480+
wantErr: assert.NoError,
481+
},
482+
{
483+
name: "struct",
484+
args: args{
485+
struct {
486+
Name string `json:"name"`
487+
}{Name: "test"},
488+
},
489+
want: []byte(`{"name":"test"}`),
490+
wantErr: assert.NoError,
491+
},
492+
{
493+
name: "slice",
494+
args: args{[]int{1, 2, 3}},
495+
want: []byte("[1,2,3]"),
496+
wantErr: assert.NoError,
497+
},
498+
{
499+
name: "map",
500+
args: args{map[string]int{"a": 1, "b": 2}},
501+
want: []byte(`{"a":1,"b":2}`),
502+
wantErr: assert.NoError,
503+
},
504+
{
505+
name: "unmarshalable type",
506+
args: args{complex(1, 2)},
507+
want: nil,
508+
wantErr: assert.Error,
509+
},
510+
{
511+
name: "channel type",
512+
args: args{make(chan int)},
513+
want: nil,
514+
wantErr: assert.Error,
515+
},
516+
{
517+
name: "url with query params",
518+
args: args{"https://example.com/api?name=test&age=25"},
519+
want: []byte(`"https://example.com/api?name=test&age=25"`),
520+
wantErr: assert.NoError,
521+
},
522+
{
523+
name: "url with encoded query params",
524+
args: args{"https://example.com/api?data=hello%20world&special=%26%3D"},
525+
want: []byte(`"https://example.com/api?data=hello%20world&special=%26%3D"`),
526+
wantErr: assert.NoError,
527+
},
528+
{
529+
name: "url with multiple query params",
530+
args: args{"http://localhost:8080/users?page=1&limit=10&sort=name&order=asc"},
531+
want: []byte(`"http://localhost:8080/users?page=1&limit=10&sort=name&order=asc"`),
532+
wantErr: assert.NoError,
533+
},
534+
}
535+
for _, tt := range tests {
536+
t.Run(
537+
tt.name, func(t *testing.T) {
538+
got, err := doMarshalJson(tt.args.v)
539+
if !tt.wantErr(t, err, fmt.Sprintf("doMarshalJson(%v)", tt.args.v)) {
540+
return
541+
}
542+
assert.Equalf(t, string(tt.want), string(got), "doMarshalJson(%v)", tt.args.v)
543+
},
544+
)
545+
}
546+
}

0 commit comments

Comments
 (0)