Skip to content

Commit 315ed1f

Browse files
committed
std/encoding/json: fix HTML escape handling
1 parent e2f027b commit 315ed1f

File tree

3 files changed

+57
-19
lines changed

3 files changed

+57
-19
lines changed

std/encoding/json/decode.jule

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -785,7 +785,7 @@ impl jsonDecoder {
785785
// Structs:
786786
// Decode as JSON objects with only visible fields of struct.
787787
//
788-
// The private and anonymous fields will be igored.
788+
// The private and anonymous fields will be ignored.
789789
// If the field is public, the field name will be used.
790790
// If the field have a json tag, the json tag will be used even if field is private or anonymous.
791791
// If the field have json tag but it is duplicate, the field will be ignored.

std/encoding/json/encode.jule

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -245,14 +245,18 @@ impl jsonEncoder {
245245
const match {
246246
| i > 0:
247247
self.buf.writeByte(',')
248-
}
249-
const match {
250-
| i > 0 && useIndent:
251-
self.total += self.depth * self.indent
252-
self.buf.writeByte('\n')
248+
const match {
249+
| useIndent:
250+
self.total += self.depth * self.indent
251+
self.buf.writeByte('\n')
252+
}
253253
}
254254
self.buf.writeStr("\"")
255-
self.buf.writeStr(name)
255+
if self.escapeHTML {
256+
appendHTMLEscape(self.buf, unsafe::StrBytes(name))
257+
} else {
258+
self.buf.writeStr(name)
259+
}
256260
self.buf.writeStr("\":")
257261
const fieldValue = vt.FieldByIndex(i)
258262
comptime::TypeAlias(fieldType, fieldValue.Type())
@@ -276,7 +280,6 @@ impl jsonEncoder {
276280
fn encodeMap[T, Flag](mut self, &t: T)! {
277281
const tt = comptime::TypeOf(T)
278282
const keyT = tt.Key()
279-
const valT = tt.Value()
280283
match keyT.Kind() {
281284
| comptime::Str
282285
| comptime::Int
@@ -298,7 +301,6 @@ impl jsonEncoder {
298301
self.encodeNil()
299302
ret
300303
}
301-
const quoted = keyT.Kind() == comptime::Str
302304
const useIndent = comptime::TypeOf(Flag) == comptime::TypeOf(encodeIndent)
303305
self.buf.writeByte('{')
304306
const match {
@@ -308,7 +310,6 @@ impl jsonEncoder {
308310
self.buf.writeByte('\n')
309311
}
310312
mut first := true
311-
comptime::TypeAlias(keyType, tt.Key())
312313
comptime::TypeAlias(valType, tt.Value())
313314
for k, v in t {
314315
if !first {
@@ -322,14 +323,8 @@ impl jsonEncoder {
322323
// No need to check Flag. The key type cannot be/include
323324
// indentation-sensitive type. Look at this function's
324325
// implementation to see match-type conditions for the key type.
325-
const match {
326-
| quoted:
327-
self.encode[keyType, Flag](k) else { error(error) }
328-
|:
329-
self.buf.writeByte('"')
330-
self.encode[keyType, Flag](k) else { error(error) }
331-
self.buf.writeByte('"')
332-
}
326+
key := resolveKeyName(k)
327+
self.encodeStr(key)
333328
self.buf.writeByte(':')
334329
const match {
335330
| useIndent:
@@ -528,7 +523,7 @@ fn encoder(): jsonEncoder {
528523
// Structs:
529524
// Encode as JSON objects with only visible fields of struct.
530525
//
531-
// The private and anonymous fields will be igored.
526+
// The private and anonymous fields will be ignored.
532527
// If the field is public, the field name will be used.
533528
// If the field have a json tag, the json tag will be used even if field is private or anonymous.
534529
// If the field have json tag but it is duplicate, the field will be ignored.
@@ -662,4 +657,17 @@ fn isValidTag(s: str): bool {
662657
}
663658
}
664659
ret true
660+
}
661+
662+
fn resolveKeyName[T](k: T): str {
663+
const t = comptime::TypeOf(T)
664+
const match t.Kind() {
665+
| comptime::Str:
666+
ret str(k)
667+
| comptime::Int | comptime::I8 | comptime::I16 | comptime::I32 | comptime::I64:
668+
ret conv::FormatInt(i64(k), 10)
669+
| comptime::Uintptr | comptime::Uint | comptime::U8 | comptime::U16 | comptime::U32 | comptime::U64:
670+
ret conv::FormatUint(u64(k), 10)
671+
}
672+
panic("unexpected map key type")
665673
}

std/encoding/json/escape.jule

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,34 @@ fn isSafe(b: byte): bool {
2121
// tags ("<" and ">"), and the ampersand ("&").
2222
fn isHTMLSafe(b: byte): bool {
2323
ret b != '"' && b != '&' && b != '<' && b != '>' && b != '\\' && b > 31
24+
}
25+
26+
fn appendHTMLEscape(mut &dst: buffer, src: []byte) {
27+
// The characters can only appear in string literals,
28+
// so just scan the string one byte at a time.
29+
mut start := 0
30+
for i, c in src {
31+
if c == '<' || c == '>' || c == '&' {
32+
dst.write(src[start:i])
33+
dst.writeByte('\\')
34+
dst.writeByte('u')
35+
dst.writeByte('0')
36+
dst.writeByte('0')
37+
dst.writeByte(hex[c>>4])
38+
dst.writeByte(hex[c&0xF])
39+
start = i + 1
40+
}
41+
// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
42+
if c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
43+
dst.write(src[start:i])
44+
dst.writeByte('\\')
45+
dst.writeByte('u')
46+
dst.writeByte('2')
47+
dst.writeByte('0')
48+
dst.writeByte('2')
49+
dst.writeByte(hex[src[i+2]&0xF])
50+
start = i + len("\u2029")
51+
}
52+
}
53+
dst.write(src[start:])
2454
}

0 commit comments

Comments
 (0)