Skip to content

Commit 57678e4

Browse files
Jamie Markledkegel-fastly
Jamie Markle
authored andcommitted
pass escapeHTML flag through generated code
1 parent aa0246c commit 57678e4

File tree

10 files changed

+268
-35
lines changed

10 files changed

+268
-35
lines changed

ffjson/encoder.go

+7-5
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ import (
2929
// It allows to encode many objects to a single writer.
3030
// This should not be used by more than one goroutine at the time.
3131
type Encoder struct {
32-
buf fflib.Buffer
33-
w io.Writer
34-
enc *json.Encoder
32+
buf fflib.Buffer
33+
w io.Writer
34+
enc *json.Encoder
35+
escapeHTML bool
3536
}
3637

3738
// SetEscapeHTML specifies whether problematic HTML characters
@@ -42,13 +43,14 @@ type Encoder struct {
4243
// In non-HTML settings where the escaping interferes with the readability
4344
// of the output, SetEscapeHTML(false) disables this behavior.
4445
func (enc *Encoder) SetEscapeHTML(on bool) {
46+
enc.escapeHTML = on
4547
enc.enc.SetEscapeHTML(on)
4648
}
4749

4850
// NewEncoder returns a reusable Encoder.
4951
// Output will be written to the supplied writer.
5052
func NewEncoder(w io.Writer) *Encoder {
51-
return &Encoder{w: w, enc: json.NewEncoder(w)}
53+
return &Encoder{w: w, enc: json.NewEncoder(w), escapeHTML: true}
5254
}
5355

5456
// Encode the data in the supplied value to the stream
@@ -59,7 +61,7 @@ func (e *Encoder) Encode(v interface{}) error {
5961
f, ok := v.(marshalerFaster)
6062
if ok {
6163
e.buf.Reset()
62-
err := f.MarshalJSONBuf(&e.buf)
64+
err := f.MarshalJSONBuf(&e.buf, e.escapeHTML)
6365
if err != nil {
6466
return err
6567
}

ffjson/marshal.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
)
2626

2727
type marshalerFaster interface {
28-
MarshalJSONBuf(buf fflib.EncodingBuffer) error
28+
MarshalJSONBuf(buf fflib.EncodingBuffer, escapeHTML bool) error
2929
}
3030

3131
type unmarshalFaster interface {
@@ -43,7 +43,7 @@ func Marshal(v interface{}) ([]byte, error) {
4343
f, ok := v.(marshalerFaster)
4444
if ok {
4545
buf := fflib.Buffer{}
46-
err := f.MarshalJSONBuf(&buf)
46+
err := f.MarshalJSONBuf(&buf, true)
4747
b := buf.Bytes()
4848
if err != nil {
4949
if len(b) > 0 {

fflib/v1/jsonstring.go

+12-14
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,19 @@ type JsonStringWriter interface {
3838
stringWriter
3939
}
4040

41-
func WriteJsonString(buf JsonStringWriter, s string) {
42-
WriteJson(buf, []byte(s))
41+
func WriteJsonString(buf JsonStringWriter, s string, escapeHTML bool) {
42+
WriteJson(buf, []byte(s), escapeHTML)
4343
}
4444

4545
/**
4646
* Function ported from encoding/json: func (e *encodeState) string(s string) (int, error)
4747
*/
48-
func WriteJson(buf JsonStringWriter, s []byte) {
48+
func WriteJson(buf JsonStringWriter, s []byte, escapeHTML bool) {
4949
buf.WriteByte('"')
5050
start := 0
5151
for i := 0; i < len(s); {
5252
if b := s[i]; b < utf8.RuneSelf {
53-
/*
54-
if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' {
55-
i++
56-
continue
57-
}
58-
*/
59-
if lt[b] == true {
53+
if htmlSafeSet[b] || (!escapeHTML && safeSet[b]) {
6054
i++
6155
continue
6256
}
@@ -74,11 +68,15 @@ func WriteJson(buf JsonStringWriter, s []byte) {
7468
case '\r':
7569
buf.WriteByte('\\')
7670
buf.WriteByte('r')
71+
case '\t':
72+
buf.WriteByte('\\')
73+
buf.WriteByte('t')
7774
default:
78-
// This encodes bytes < 0x20 except for \n and \r,
79-
// as well as < and >. The latter are escaped because they
80-
// can lead to security holes when user-controlled strings
81-
// are rendered into JSON and served to some browsers.
75+
// This encodes bytes < 0x20 except for \t, \n and \r.
76+
// If escapeHTML is set, it also escapes <, >, and &
77+
// because they can lead to security holes when
78+
// user-controlled strings are rendered into JSON
79+
// and served to some browsers.
8280
buf.WriteString(`\u00`)
8381
buf.WriteByte(hex[b>>4])
8482
buf.WriteByte(hex[b&0xF])

fflib/v1/jsonstring_test.go

+15-2
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,28 @@ import (
2424

2525
func TestWriteJsonString(t *testing.T) {
2626
var buf bytes.Buffer
27-
WriteJsonString(&buf, "foo")
27+
WriteJsonString(&buf, "foo", true)
2828
if string(buf.Bytes()) != `"foo"` {
2929
t.Fatalf("Expected: %v\nGot: %v", `"foo"`, string(buf.Bytes()))
3030
}
3131

3232
buf.Reset()
33-
WriteJsonString(&buf, `f"oo`)
33+
WriteJsonString(&buf, `f"oo`, true)
3434
if string(buf.Bytes()) != `"f\"oo"` {
3535
t.Fatalf("Expected: %v\nGot: %v", `"f\"oo"`, string(buf.Bytes()))
3636
}
37+
38+
buf.Reset()
39+
WriteJsonString(&buf, `&foo<bar>`, true)
40+
if string(buf.Bytes()) != `"\u0026foo\u003cbar\u003e"` {
41+
t.Fatalf("Expected: %v\nGot: %v", `\u0026foo\u003cbar\u003e`, string(buf.Bytes()))
42+
}
43+
44+
buf.Reset()
45+
WriteJsonString(&buf, `&foo<bar>`, false)
46+
if string(buf.Bytes()) != `"&foo<bar>"` {
47+
t.Fatalf("Expected: %v\nGot: %v", `"&foo<bar>"`, string(buf.Bytes()))
48+
}
49+
3750
// TODO(pquerna): all them important tests.
3851
}

fflib/v1/lexer.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ func (ffl *FFLexer) lexString() FFTok {
267267
return FFTok_error
268268
}
269269

270-
WriteJson(ffl.Output, ffl.buf.Bytes())
270+
WriteJson(ffl.Output, ffl.buf.Bytes(), false)
271271

272272
return FFTok_string
273273
} else {
@@ -548,7 +548,7 @@ func (ffl *FFLexer) scanField(start FFTok, capture bool) ([]byte, error) {
548548
//TODO(pquerna): so, other users expect this to be a quoted string :(
549549
if capture {
550550
ffl.buf.Reset()
551-
WriteJson(&ffl.buf, ffl.Output.Bytes())
551+
WriteJson(&ffl.buf, ffl.Output.Bytes(), false)
552552
return ffl.buf.Bytes(), nil
553553
} else {
554554
return nil, nil

fflib/v1/tables.go

+220
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
// this file is copied from the Go std library for ffjson internal use
2+
3+
// Copyright 2016 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package v1
8+
9+
import "unicode/utf8"
10+
11+
// safeSet holds the value true if the ASCII character with the given array
12+
// position can be represented inside a JSON string without any further
13+
// escaping.
14+
//
15+
// All values are true except for the ASCII control characters (0-31), the
16+
// double quote ("), and the backslash character ("\").
17+
var safeSet = [utf8.RuneSelf]bool{
18+
' ': true,
19+
'!': true,
20+
'"': false,
21+
'#': true,
22+
'$': true,
23+
'%': true,
24+
'&': true,
25+
'\'': true,
26+
'(': true,
27+
')': true,
28+
'*': true,
29+
'+': true,
30+
',': true,
31+
'-': true,
32+
'.': true,
33+
'/': true,
34+
'0': true,
35+
'1': true,
36+
'2': true,
37+
'3': true,
38+
'4': true,
39+
'5': true,
40+
'6': true,
41+
'7': true,
42+
'8': true,
43+
'9': true,
44+
':': true,
45+
';': true,
46+
'<': true,
47+
'=': true,
48+
'>': true,
49+
'?': true,
50+
'@': true,
51+
'A': true,
52+
'B': true,
53+
'C': true,
54+
'D': true,
55+
'E': true,
56+
'F': true,
57+
'G': true,
58+
'H': true,
59+
'I': true,
60+
'J': true,
61+
'K': true,
62+
'L': true,
63+
'M': true,
64+
'N': true,
65+
'O': true,
66+
'P': true,
67+
'Q': true,
68+
'R': true,
69+
'S': true,
70+
'T': true,
71+
'U': true,
72+
'V': true,
73+
'W': true,
74+
'X': true,
75+
'Y': true,
76+
'Z': true,
77+
'[': true,
78+
'\\': false,
79+
']': true,
80+
'^': true,
81+
'_': true,
82+
'`': true,
83+
'a': true,
84+
'b': true,
85+
'c': true,
86+
'd': true,
87+
'e': true,
88+
'f': true,
89+
'g': true,
90+
'h': true,
91+
'i': true,
92+
'j': true,
93+
'k': true,
94+
'l': true,
95+
'm': true,
96+
'n': true,
97+
'o': true,
98+
'p': true,
99+
'q': true,
100+
'r': true,
101+
's': true,
102+
't': true,
103+
'u': true,
104+
'v': true,
105+
'w': true,
106+
'x': true,
107+
'y': true,
108+
'z': true,
109+
'{': true,
110+
'|': true,
111+
'}': true,
112+
'~': true,
113+
'\u007f': true,
114+
}
115+
116+
// htmlSafeSet holds the value true if the ASCII character with the given
117+
// array position can be safely represented inside a JSON string, embedded
118+
// inside of HTML <script> tags, without any additional escaping.
119+
//
120+
// All values are true except for the ASCII control characters (0-31), the
121+
// double quote ("), the backslash character ("\"), HTML opening and closing
122+
// tags ("<" and ">"), and the ampersand ("&").
123+
var htmlSafeSet = [utf8.RuneSelf]bool{
124+
' ': true,
125+
'!': true,
126+
'"': false,
127+
'#': true,
128+
'$': true,
129+
'%': true,
130+
'&': false,
131+
'\'': true,
132+
'(': true,
133+
')': true,
134+
'*': true,
135+
'+': true,
136+
',': true,
137+
'-': true,
138+
'.': true,
139+
'/': true,
140+
'0': true,
141+
'1': true,
142+
'2': true,
143+
'3': true,
144+
'4': true,
145+
'5': true,
146+
'6': true,
147+
'7': true,
148+
'8': true,
149+
'9': true,
150+
':': true,
151+
';': true,
152+
'<': false,
153+
'=': true,
154+
'>': false,
155+
'?': true,
156+
'@': true,
157+
'A': true,
158+
'B': true,
159+
'C': true,
160+
'D': true,
161+
'E': true,
162+
'F': true,
163+
'G': true,
164+
'H': true,
165+
'I': true,
166+
'J': true,
167+
'K': true,
168+
'L': true,
169+
'M': true,
170+
'N': true,
171+
'O': true,
172+
'P': true,
173+
'Q': true,
174+
'R': true,
175+
'S': true,
176+
'T': true,
177+
'U': true,
178+
'V': true,
179+
'W': true,
180+
'X': true,
181+
'Y': true,
182+
'Z': true,
183+
'[': true,
184+
'\\': false,
185+
']': true,
186+
'^': true,
187+
'_': true,
188+
'`': true,
189+
'a': true,
190+
'b': true,
191+
'c': true,
192+
'd': true,
193+
'e': true,
194+
'f': true,
195+
'g': true,
196+
'h': true,
197+
'i': true,
198+
'j': true,
199+
'k': true,
200+
'l': true,
201+
'm': true,
202+
'n': true,
203+
'o': true,
204+
'p': true,
205+
'q': true,
206+
'r': true,
207+
's': true,
208+
't': true,
209+
'u': true,
210+
'v': true,
211+
'w': true,
212+
'x': true,
213+
'y': true,
214+
'z': true,
215+
'{': true,
216+
'|': true,
217+
'}': true,
218+
'~': true,
219+
'\u007f': true,
220+
}

0 commit comments

Comments
 (0)