Skip to content
This repository has been archived by the owner on Mar 23, 2021. It is now read-only.

Commit

Permalink
Properly escape all control chars when formatting (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
czechboy0 authored Sep 7, 2016
1 parent 535666f commit ebda409
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 16 deletions.
6 changes: 4 additions & 2 deletions Sources/Jay/Consts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ struct Const {
Const.FormFeed: Const.FormFeedChar,
Const.CarriageReturn: Const.CarriageReturnChar
]


static let ControlCharacters: Set<JChar> = Set(0x00...0x1f)

// Strings
static let QuotationMark: JChar = 0x22 // """
static let ReverseSolidus: JChar = 0x5c // "\"
Expand All @@ -66,7 +68,7 @@ struct Const {

static let UnicodeStart: JChar = 0x75 // "u"

static let Escape: JChar = Const.ReverseSolidus
static let Escape: JChar = Const.ReverseSolidus

static let SimpleEscaped: Set<JChar> = [
Const.QuotationMark,
Expand Down
16 changes: 16 additions & 0 deletions Sources/Jay/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,22 @@ extension JChar {
}
}

extension JChar {

func controlCharacterHexString() -> [JChar] {
var hex = String(self, radix: 16, uppercase: false).chars()

//control chars are always only two hex characters, so we either got 1 or 2 chars,
//pad to two
if hex.count == 1 {
hex = [Const.Zero] + hex
}

//and prepend with \u00, which is followed by our two hex bytes
return [Const.Escape, Const.UnicodeStart, Const.Zero, Const.Zero] + hex
}
}

extension String {

func chars() -> [JChar] {
Expand Down
9 changes: 8 additions & 1 deletion Sources/Jay/Formatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ extension JSON: JsonFormattable {

func format(to stream: JsonOutputStream, string: String) throws {

var contents = [JChar]()
var contents: [JChar] = []
for c in string.utf8 {

//if this is an escapable character, escape it
Expand All @@ -66,6 +66,13 @@ extension JSON: JsonFormattable {
continue
}

//control character that wasn't escaped above, just convert to
//an escaped unicode sequence, i.e. "\u0006" for "ACK"
if Const.ControlCharacters.contains(c) {
contents.append(contentsOf: c.controlCharacterHexString())
continue
}

//nothing to escape, just append byte
contents.append(c)
}
Expand Down
5 changes: 3 additions & 2 deletions Tests/JayTests/FormattingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,10 @@ class FormattingTests: XCTestCase {
}

func testString_Escaping() {
let json = ["he \r\n l \t l \n o w\"o\rrld "]
let json = ["he \r\n l \u{0006} \t l \n o w\"o\rrld \u{0015} "]
let data = try! Jay().dataFromJson(any: json)
XCTAssertEqual(data, "[\"he \\r\\n l \\t l \\n o w\\\"o\\rrld \"]".chars())
let res = "[\"he \\r\\n l \\u0006 \\t l \\n o w\\\"o\\rrld \\u0015 \"]".chars()
XCTAssertEqual(data, res)
}

func testVaporExample_Dict() {
Expand Down
41 changes: 30 additions & 11 deletions Tests/JayTests/ParsingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ extension ParsingTests {
("testEscape_Unicode_InvalidUnicode_MissingDigit", testEscape_Unicode_InvalidUnicode_MissingDigit),
("testEscape_Unicode_InvalidUnicode_MissingAllDigits", testEscape_Unicode_InvalidUnicode_MissingAllDigits),
("testEscape_SpecialChars", testEscape_SpecialChars),
("testString_Escaping", testString_Escaping),
("testString_Empty", testString_Empty),
("testString_Normal", testString_Normal),
("testString_Normal_WhitespaceInside", testString_Normal_WhitespaceInside),
Expand Down Expand Up @@ -378,7 +379,7 @@ class ParsingTests:XCTestCase {
XCTAssertNil(try? StringParser().unescapedCharacter(reader))
}

func testEscape_SpecialChars() {
func testEscape_SpecialChars() throws {

let chars: [JChar] = [
//regular translation
Expand All @@ -392,40 +393,58 @@ class ParsingTests:XCTestCase {
Const.Escape, Const.NewLineChar,
Const.Escape, Const.CarriageReturnChar,
Const.Escape, Const.HorizontalTabChar,
Const.Space

//basic control chars
Const.Escape, Const.UnicodeStart, Const.Zero, Const.Zero, Const.Zero, 0x36 /* ACK */,
Const.Escape, Const.UnicodeStart, Const.Zero, Const.Zero, 0x32, 0x31 /* NACK */,

Const.Space,
]

let reader = ByteReader(content: chars)
var char: UnicodeScalar

//regular

char = try! StringParser().unescapedCharacter(reader)
char = try StringParser().unescapedCharacter(reader)
XCTAssert(try! Const.QuotationMark.string() == String(char))

char = try! StringParser().unescapedCharacter(reader)
char = try StringParser().unescapedCharacter(reader)
XCTAssert(try! Const.ReverseSolidus.string() == String(char))

char = try! StringParser().unescapedCharacter(reader)
char = try StringParser().unescapedCharacter(reader)
XCTAssert(try! Const.Solidus.string() == String(char))


//rule-based

char = try! StringParser().unescapedCharacter(reader)
char = try StringParser().unescapedCharacter(reader)
XCTAssert(try! Const.Backspace.string() == String(char))

char = try! StringParser().unescapedCharacter(reader)
char = try StringParser().unescapedCharacter(reader)
XCTAssert(try! Const.FormFeed.string() == String(char))

char = try! StringParser().unescapedCharacter(reader)
char = try StringParser().unescapedCharacter(reader)
XCTAssertEqual(try! Const.NewLine.string(), String(char))

char = try! StringParser().unescapedCharacter(reader)
char = try StringParser().unescapedCharacter(reader)
XCTAssertEqual(try! Const.CarriageReturn.string(), String(char))

char = try! StringParser().unescapedCharacter(reader)
char = try StringParser().unescapedCharacter(reader)
XCTAssertEqual(try! Const.HorizontalTab.string(), String(char))

//basic control
char = try StringParser().unescapedCharacter(reader)
XCTAssert("\u{0006}" == String(char))

char = try StringParser().unescapedCharacter(reader)
XCTAssert("\u{0021}" == String(char))
}

func testString_Escaping() {
let reader = ByteReader(content: "[\"he \\r\\n l \\u0006 \\t l \\n o w\\\"o\\rrld \\u0015 \"]")
let json: [JSON] = [.string("he \r\n l \u{0006} \t l \n o w\"o\rrld \u{0015} ")]
let ret = try! Jay().jsonFromReader(reader)
ensureArray(ret, exp: json)
}

func testString_Empty() {
Expand Down

0 comments on commit ebda409

Please sign in to comment.