55package slog
66
77import (
8+ "bytes"
89 "context"
910 "encoding/json"
1011 "errors"
@@ -81,6 +82,7 @@ func (h *JSONHandler) WithGroup(name string) Handler {
8182// - Floating-point NaNs and infinities are formatted as one of the strings
8283// "NaN", "+Inf" or "-Inf".
8384// - Levels are formatted as with Level.String.
85+ // - HTML characters are not escaped.
8486//
8587// Each call to Handle results in a single serialized call to io.Writer.Write.
8688func (h * JSONHandler ) Handle (r Record ) error {
@@ -146,11 +148,15 @@ func appendJSONValue(s *handleState, v Value) error {
146148}
147149
148150func appendJSONMarshal (buf * buffer.Buffer , v any ) error {
149- b , err := json .Marshal (v )
150- if err != nil {
151+ // Use a json.Encoder to avoid escaping HTML.
152+ var bb bytes.Buffer
153+ enc := json .NewEncoder (& bb )
154+ enc .SetEscapeHTML (false )
155+ if err := enc .Encode (v ); err != nil {
151156 return err
152157 }
153- buf .Write (b )
158+ bs := bb .Bytes ()
159+ buf .Write (bs [:len (bs )- 1 ]) // remove final newline
154160 return nil
155161}
156162
@@ -168,7 +174,7 @@ func appendEscapedJSONString(buf []byte, s string) []byte {
168174 start := 0
169175 for i := 0 ; i < len (s ); {
170176 if b := s [i ]; b < utf8 .RuneSelf {
171- if htmlSafeSet [b ] {
177+ if safeSet [b ] {
172178 i ++
173179 continue
174180 }
@@ -187,10 +193,6 @@ func appendEscapedJSONString(buf []byte, s string) []byte {
187193 char ('t' )
188194 default :
189195 // This encodes bytes < 0x20 except for \t, \n and \r.
190- // It also escapes <, >, and &
191- // because they can lead to security holes when
192- // user-controlled strings are rendered into JSON
193- // and served to some browsers.
194196 str (`u00` )
195197 char (hex [b >> 4 ])
196198 char (hex [b & 0xF ])
@@ -236,23 +238,22 @@ func appendEscapedJSONString(buf []byte, s string) []byte {
236238
237239var hex = "0123456789abcdef"
238240
239- // Copied from encoding/json/encode .go:encodeState.string .
241+ // Copied from encoding/json/tables .go.
240242//
241- // htmlSafeSet holds the value true if the ASCII character with the given
242- // array position can be safely represented inside a JSON string, embedded
243- // inside of HTML <script> tags, without any additional escaping.
243+ // safeSet holds the value true if the ASCII character with the given array
244+ // position can be represented inside a JSON string without any further
245+ // escaping.
244246//
245247// All values are true except for the ASCII control characters (0-31), the
246- // double quote ("), the backslash character ("\"), HTML opening and closing
247- // tags ("<" and ">"), and the ampersand ("&").
248- var htmlSafeSet = [utf8 .RuneSelf ]bool {
248+ // double quote ("), and the backslash character ("\").
249+ var safeSet = [utf8 .RuneSelf ]bool {
249250 ' ' : true ,
250251 '!' : true ,
251252 '"' : false ,
252253 '#' : true ,
253254 '$' : true ,
254255 '%' : true ,
255- '&' : false ,
256+ '&' : true ,
256257 '\'' : true ,
257258 '(' : true ,
258259 ')' : true ,
@@ -274,9 +275,9 @@ var htmlSafeSet = [utf8.RuneSelf]bool{
274275 '9' : true ,
275276 ':' : true ,
276277 ';' : true ,
277- '<' : false ,
278+ '<' : true ,
278279 '=' : true ,
279- '>' : false ,
280+ '>' : true ,
280281 '?' : true ,
281282 '@' : true ,
282283 'A' : true ,
0 commit comments