blob: ce77181edc81bb5a9e6984ad583e8b3ff4b5d142 [file] [log] [blame]
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file contains bindings to convert from JSON to C++ base::Value objects.
// The code related to `base::Value` can be found in 'values.rs'
// and 'values_deserialization.rs'.
use crate::values::ValueSlotRef;
use crate::values_deserialization::ValueVisitor;
use cxx::CxxString;
use serde::de::Deserializer;
use serde_json_lenient::de::SliceRead;
use std::pin::Pin;
// UTF8 byte order mark.
const UTF8_BOM: [u8; 3] = [0xef, 0xbb, 0xbf];
#[cxx::bridge(namespace=base::rs_glue)]
mod ffi {
/// Options for parsing JSON inputs. Largely a mirror of the C++ `base::JSONParserOptions`
/// bitflags, represented as a friendlier struct-of-bools instead.
#[namespace=base::ffi::json::json_parser]
struct JsonOptions {
/// Allows commas to exist after the last element in structures.
allow_trailing_commas: bool,
/// If set the parser replaces invalid code points (i.e. lone surrogates) with the Unicode
/// replacement character (U+FFFD). If not set, invalid code points trigger a hard error and
/// parsing fails.
replace_invalid_characters: bool,
/// Allows both C (/* */) and C++ (//) style comments.
allow_comments: bool,
/// Permits unescaped ASCII control characters (such as unescaped \r and \n) in the range
/// [0x00,0x1F].
allow_control_chars: bool,
/// Permits \\v vertical tab escapes.
allow_vert_tab: bool,
/// Permits \\xNN escapes as described above.
allow_x_escapes: bool,
/// The maximum recursion depth to walk while parsing nested JSON objects. JSON beyond the
/// specified depth will be ignored.
max_depth: usize,
}
unsafe extern "C++" {
include!("base/rs_glue/values_glue.h");
type ValueSlot = crate::rs_glue::ffi::ValueSlot;
}
extern "Rust" {
#[namespace=base::ffi::json::json_parser]
pub fn decode_json_from_cpp(
json: &[u8],
options: JsonOptions,
value_slot: Pin<&mut ValueSlot>,
error_line: &mut i32,
error_column: &mut i32,
error_message: Pin<&mut CxxString>,
) -> bool;
}
}
pub type JsonOptions = ffi::JsonOptions;
impl JsonOptions {
/// Construct a JsonOptions with common Chromium extensions.
///
/// Per base::JSONParserOptions::JSON_PARSE_CHROMIUM_EXTENSIONS:
///
/// This parser historically accepted, without configuration flags,
/// non-standard JSON extensions. This enables that traditional parsing
/// behavior.
pub fn with_chromium_extensions(max_depth: usize) -> JsonOptions {
JsonOptions {
allow_trailing_commas: false,
replace_invalid_characters: false,
allow_comments: true,
allow_control_chars: true,
allow_vert_tab: true,
allow_x_escapes: true,
max_depth,
}
}
}
/// Decode some JSON into C++ base::Value object tree.
///
/// This function takes and returns normal Rust types. For an equivalent which
/// can be called from C++, see `decode_json_from_cpp`.
///
/// # Args:
///
/// * `json`: the JSON. Note that this is a slice of bytes rather than a string,
/// which in Rust terms means it hasn't yet been validated to be
/// legitimate UTF8. The JSON decoding will do that.
/// * `options`: configuration options for non-standard JSON extensions
/// * `value_slot`: a space into which to construct a base::Value
///
/// It always strips a UTF8 BOM from the start of the string, if one is found.
///
/// Return: a serde_json_lenient::Error or Ok.
///
/// It is be desirable in future to expose this API to other Rust code inside
/// and outside //base. TODO(crbug/1287209): work out API norms and then add
/// 'pub' to do this.
pub fn decode_json(
json: &[u8],
options: JsonOptions,
value_slot: ValueSlotRef,
) -> Result<(), serde_json_lenient::Error> {
let mut to_parse = json;
if to_parse.len() >= 3 && to_parse[0..3] == UTF8_BOM {
to_parse = &to_parse[3..];
}
let mut deserializer = serde_json_lenient::Deserializer::new(SliceRead::new(
&to_parse,
options.replace_invalid_characters,
options.allow_control_chars,
options.allow_vert_tab,
options.allow_x_escapes,
));
// By default serde_json[rc] has a recursion limit of 128.
// As we want different recursion limits in different circumstances,
// we disable its own recursion tracking and use our own.
deserializer.disable_recursion_limit();
deserializer.set_ignore_trailing_commas(options.allow_trailing_commas);
deserializer.set_allow_comments(options.allow_comments);
// The C++ parser starts counting nesting levels from the first item
// inside the outermost dict. We start counting from the
// absl::optional<base::Value> and also count the outermost dict,
// therefore we start with -2 to match C++ behavior.
let result =
deserializer.deserialize_any(ValueVisitor::new(value_slot, options.max_depth - 2))?;
deserializer.end()?;
Ok(result)
}
/// Decode some JSON into a `base::Value`; for calling by C++.
///
/// See `decode_json` for an equivalent which takes and returns idiomatic Rust
/// types, and a little bit more information about the implementation.
///
/// # Args
///
/// * `json`: a slice of input JSON unsigned characters.
/// * `options`: configuration options for non-standard JSON extensions
/// * `value_slot`: a space into which to construct a base::Value
/// * `error_line`/`error_column`/`error_message`: populated with details of
/// any decode error.
///
/// # Returns
///
/// A Boolean indicating whether the decode succeeded.
fn decode_json_from_cpp(
json: &[u8],
options: ffi::JsonOptions,
value_slot: Pin<&mut ffi::ValueSlot>,
error_line: &mut i32,
error_column: &mut i32,
mut error_message: Pin<&mut CxxString>,
) -> bool {
let value_slot = ValueSlotRef::from(value_slot);
match decode_json(json, options, value_slot) {
Err(err) => {
*error_line = err.line().try_into().unwrap_or(-1);
*error_column = err.column().try_into().unwrap_or(-1);
error_message.as_mut().clear();
// The following line pulls in a lot of binary bloat, due to all the formatter
// implementations required to stringify error messages. This error message is used in
// only a couple of places outside unit tests so we could consider trying
// to eliminate.
error_message.as_mut().push_str(&err.to_string());
false
}
Ok(_) => true,
}
}