Skip to content

Fix #167 #168

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "json"
version = "0.12.0"
version = "0.12.1"
authors = ["Maciej Hirsz <[email protected]>"]
description = "JSON implementation in Rust"
repository = "https://github.com/maciejhirsz/json-rust"
Expand Down
39 changes: 36 additions & 3 deletions src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,20 @@ pub trait Generator {

#[inline(never)]
fn write_string_complex(&mut self, string: &str, mut start: usize) -> io::Result<()> {
self.write(string[ .. start].as_bytes())?;
self.write(&string.as_bytes()[ .. start])?;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the changes in this function prevent panics when indexing into a string at a byte offset that doesn't fall on a character boundary


for (index, ch) in string.bytes().enumerate().skip(start) {
let escape = ESCAPED[ch as usize];
if escape > 0 {
self.write(string[start .. index].as_bytes())?;
self.write(&string.as_bytes()[start .. index])?;
self.write(&[b'\\', escape])?;
start = index + 1;
}
if escape == b'u' {
write!(self.get_writer(), "{:04x}", ch)?;
}
}
self.write(string[start ..].as_bytes())?;
self.write(&string.as_bytes()[start ..])?;

self.write_char(b'"')
}
Expand Down Expand Up @@ -382,3 +382,36 @@ fn extend_from_slice(dst: &mut Vec<u8>, src: &[u8]) {
src_len);
}
}

#[cfg(test)]
mod tests {
use super::*;
use std::borrow::Borrow;
use crate::JsonValue;
use crate::parse;

// found while fuzzing the DumpGenerator

#[test]
fn should_not_panic_on_bad_bytes() {
let data = [0, 12, 128, 88, 64, 99].to_vec();
let s = unsafe {
String::from_utf8_unchecked(data)
};

let mut generator = DumpGenerator::new();
generator.write_string(&s);
}

#[test]
fn should_not_panic_on_bad_bytes_2() {
let data = b"\x48\x48\x48\x57\x03\xE8\x48\x48\xE8\x03\x8F\x48\x29\x48\x48";
let s = unsafe {
String::from_utf8_unchecked(data.to_vec())
};

let mut generator = DumpGenerator::new();
generator.write_string(&s);
}

}
150 changes: 147 additions & 3 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,11 +483,20 @@ impl<'a> Parser<'a> {
// having to be read from source to a buffer and then from a buffer to
// our target string. Nothing to be done about this, really.
fn read_complex_string<'b>(&mut self, start: usize) -> Result<&'b str> {
self.buffer.clear();
// Since string slices are returned by this function that are created via pointers into `self.buffer`
// we shouldn't be clearing or modifying the buffer in consecutive calls to this function. Instead
// we continuously append bytes to `self.buffer` and keep track of the starting offset of the buffer on each
// call to this function. Later when creating string slices that point to the contents of this buffer
// we use this starting offset to make sure that newly created slices point only to the bytes that were
// appended in the most recent call to this function.
//
// Failing to do this can result in the StackBlock `key` values being modified in place later.
let len = self.buffer.len();
//self.buffer.clear();
let mut ch = b'\\';

// TODO: Use fastwrite here as well
self.buffer.extend_from_slice(self.source[start .. self.index - 1].as_bytes());
self.buffer.extend_from_slice(&self.source.as_bytes()[start .. self.index - 1]);

loop {
if ALLOWED[ch as usize] {
Expand Down Expand Up @@ -533,7 +542,7 @@ impl<'a> Parser<'a> {
// issues here, we construct a new slice from raw parts, which
// then has lifetime bound to the outer function scope instead
// of the parser itself.
slice::from_raw_parts(self.buffer.as_ptr(), self.buffer.len())
slice::from_raw_parts(self.buffer[len .. ].as_ptr(), self.buffer.len() - len)
)
})
}
Expand Down Expand Up @@ -752,3 +761,138 @@ struct StackBlock(JsonValue, usize);
pub fn parse(source: &str) -> Result<JsonValue> {
Parser::new(source).parse()
}


#[cfg(test)]
mod tests {
use super::*;
use crate::stringify;
use crate::JsonValue;

#[macro_use]
use crate::object;
use crate::array;

use std::fs::File;
use std::io::prelude::*;

#[test]
fn it_should_parse_escaped_forward_slashes_with_quotes() {
// used to get around the fact that rust strings don't escape forward slashes
let mut file = File::open("tests/test_json_slashes_quotes").unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();

let actual = parse(&contents).unwrap();
let serialized = stringify(actual.clone());

assert_eq!(serialized, contents);
}

#[test]
fn it_should_parse_escaped_quotes() {
let contents = String::from("{\"ab\":\"c\\\"d\\\"e\"}");

let actual = parse(&contents).unwrap();
let serialized = stringify(actual.clone());

assert_eq!(serialized, contents);
}

#[test]
fn it_should_parse_basic_json_values() {
let s = "{\"a\":1,\"b\":true,\"c\":false,\"d\":null,\"e\":2}";
let actual = parse(s).unwrap();
let mut expected = object! {
"a" => 1,
"b" => true,
"c" => false,
"e" => 2
};
expected["d"] = JsonValue::Null;

assert_eq!(actual, expected);
}

#[test]
fn it_should_parse_json_arrays() {
let s = "{\"a\":1,\"b\":true,\"c\":false,\"d\":null,\"e\":2,\"f\":[1,2,3,false,true,[],{}]}";
let actual = parse(s).unwrap();
let mut expected = object! {
"a" => 1,
"b" => true,
"c" => false,
"e" => 2
};
expected["d"] = JsonValue::Null;
expected["f"] = array![
1,2,3,
false,
true,
array![],
object!{}
];

assert_eq!(actual, expected);
}

#[test]
fn it_should_parse_json_nested_object() {
let s = "{\"a\":1,\"b\":{\"c\":2,\"d\":{\"e\":{\"f\":{\"g\":3,\"h\":[]}}},\"i\":4,\"j\":[],\"k\":{\"l\":5,\"m\":{}}}}";
let actual = parse(s).unwrap();
let mut expected = object! {
"a" => 1,
"b" => object!{
"c" => 2,
"d" => object!{
"e" => object! {
"f" => object!{
"g" => 3,
"h" => array![]
}
}
},
"i" => 4,
"j" => array![],
"k" => object!{
"l" => 5,
"m" => object!{}
}
}
};

assert_eq!(actual, expected);
}

#[test]
fn it_should_parse_json_complex_object() {
let s = "{\"a\":1,\"b\":{\"c\":2,\"d\":{\"e\":{\"f\":{\"g\":3,\"h\":[{\"z\":1},{\"y\":2,\"x\":[{},{}]}]}}},\"i\":4,\"j\":[],\"k\":{\"l\":5,\"m\":{}}}}";
let actual = parse(s).unwrap();
let mut expected = object! {
"a" => 1,
"b" => object!{
"c" => 2,
"d" => object!{
"e" => object! {
"f" => object!{
"g" => 3,
"h" => array![
object!{"z" => 1},
object!{"y" => 2, "x" => array![object!{}, object!{}]}
]
}
}
},
"i" => 4,
"j" => array![],
"k" => object!{
"l" => 5,
"m" => object!{}
}
}
};

assert_eq!(actual, expected);
}

}
1 change: 1 addition & 0 deletions tests/test_json_slashes_quotes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"a\\/b":"c\"d\"e"}