///|
fn json_object(
value : Json,
path : String,
) -> Map[String, Json] raise SieveError {
match value {
Object(obj) => obj
_ => {
fail("json.type", "\{path}: expected object", origin())
Map([])
}
}
}
///|
fn json_keys(
obj : Map[String, Json],
allowed : Array[String],
path : String,
) -> Unit raise SieveError {
for key, _ in obj {
if !allowed.contains(key) {
fail("json.field", "\{path}: unknown field \{key}", origin())
}
}
}
///|
fn json_required(
obj : Map[String, Json],
key : String,
path : String,
) -> Json raise SieveError {
match obj.get(key) {
Some(value) => value
None => {
fail("json.missing", "\{path}.\{key}: missing required field", origin())
Json::null()
}
}
}
///|
fn json_string(value : Json, path : String) -> String raise SieveError {
match value {
String(text) => text
_ => {
fail("json.type", "\{path}: expected string", origin())
""
}
}
}
///|
fn json_integer(value : Json, path : String) -> Int raise SieveError {
match value {
Number(n, ..) if n >= 0.0 && n <= 2147483647.0 => {
let integer = n.to_int()
if integer.to_double() != n {
fail("json.integer", "\{path}: expected nonnegative integer", origin())
}
integer
}
_ => {
fail(
"json.integer",
"\{path}: expected nonnegative 31-bit integer",
origin(),
)
0
}
}
}
///|
fn json_array(value : Json, path : String) -> Array[Json] raise SieveError {
match value {
Array(items) => items
_ => {
fail("json.type", "\{path}: expected array", origin())
[]
}
}
}
///|
fn parse_json_bounded(text : String, limits : Limits) -> Json raise SieveError {
limits.check()
if text.length() > limits.source_chars {
fail("limit.json", "JSON input exceeds configured limit", origin())
}
check_json_depth(text)
@json.parse(text) catch {
_ => {
fail("json.syntax", "invalid JSON or nesting limit exceeded", origin())
Json::null()
}
}
}
///|
pub fn Message::from_json(
value : Json,
limits? : Limits = Limits::default(),
) -> Message raise SieveError {
limits.check()
let root = json_object(value, "$")
json_keys(root, ["headers", "size", "envelope"], "$")
let raw = json_array(json_required(root, "headers", "$"), "$.headers")
if raw.length() > limits.headers {
fail("limit.headers", "too many headers", origin())
}
let headers : Array[Header] = []
for i = 0; i < raw.length(); i = i + 1 {
let path = "$.headers[\{i}]"
let obj = json_object(raw[i], path)
json_keys(obj, ["name", "value"], path)
headers.push({
name: json_string(json_required(obj, "name", path), path + ".name"),
value: json_string(json_required(obj, "value", path), path + ".value"),
})
}
let size = json_integer(json_required(root, "size", "$"), "$.size")
let mut sender = ""
let recipients : Array[String] = []
match root.get("envelope") {
None => ()
Some(value) => {
let envelope = json_object(value, "$.envelope")
json_keys(envelope, ["from", "to"], "$.envelope")
match envelope.get("from") {
None => ()
Some(value) => sender = json_string(value, "$.envelope.from")
}
match envelope.get("to") {
None => ()
Some(value) => {
let list = json_array(value, "$.envelope.to")
if list.length() > limits.headers {
fail("limit.recipients", "too many recipients", origin())
}
for i = 0; i < list.length(); i = i + 1 {
recipients.push(json_string(list[i], "$.envelope.to[\{i}]"))
}
}
}
}
}
Message::new(
headers,
size,
envelope_from=sender,
envelope_to=recipients,
limits~,
)
}
///|
pub fn parse_message_json(
text : String,
limits? : Limits = Limits::default(),
) -> Message raise SieveError {
Message::from_json(parse_json_bounded(text, limits), limits~)
}
///|
pub fn Message::as_json(self : Message) -> Json {
Json::object({
"headers": Json::array(
self.headers.map(header => {
Json::object({
"name": Json::string(header.name),
"value": Json::string(header.value),
})
}),
),
"size": Json::number(self.size.to_double()),
"envelope": Json::object({
"from": Json::string(self.envelope_from),
"to": Json::array(self.envelope_to.map(Json::string)),
}),
})
}
///|
fn span_json(span : Span) -> Json {
Json::object({
"start": Json::number(span.start.to_double()),
"end": Json::number(span.end.to_double()),
"line": Json::number(span.line.to_double()),
"column": Json::number(span.column.to_double()),
})
}
///|
pub fn Diagnostic::as_json(self : Diagnostic) -> Json {
Json::object({
"code": Json::string(self.code),
"message": Json::string(self.message),
"span": span_json(self.span),
"hint": Json::string(diagnostic_hint(self.code)),
})
}
///|
fn action_json(action : Action, redact : Bool) -> Json {
match action {
Keep => Json::object({ "kind": Json::string("keep") })
Discard => Json::object({ "kind": Json::string("discard") })
FileInto(target) =>
Json::object({
"kind": Json::string("fileinto"),
"target": Json::string(if redact { "" } else { target }),
})
Redirect(target) =>
Json::object({
"kind": Json::string("redirect"),
"target": Json::string(if redact { "" } else { target }),
})
}
}
///|
pub fn Plan::as_json(
self : Plan,
include_trace? : Bool = true,
redact_destinations? : Bool = false,
) -> Json {
let obj : Map[String, Json] = {
"schema_version": Json::number(1.0),
"actions": Json::array(
self.actions.map(action => action_json(action, redact_destinations)),
),
"implicit_keep": Json::boolean(self.implicit_keep),
"steps": Json::number(self.steps.to_double()),
"comparisons": Json::number(self.comparisons.to_double()),
}
if include_trace {
obj["trace"] = Json::array(
self.trace.map(event => {
Json::object({
"kind": Json::string(event.kind),
"label": Json::string(event.label),
"span": span_json(event.span),
"matched": match event.matched {
Some(value) => Json::boolean(value)
None => Json::null()
},
})
}),
)
}
Json::object(obj)
}
///|
pub fn ScriptAudit::as_json(self : ScriptAudit) -> Json {
Json::object({
"warnings": Json::array(self.warnings.map(Diagnostic::as_json)),
"commands": Json::number(self.commands.to_double()),
"predicates": Json::number(self.predicates.to_double()),
"max_depth": Json::number(self.max_depth.to_double()),
"literal_redirects": Json::array(self.literal_redirects.map(Json::string)),
"literal_mailboxes": Json::array(self.literal_mailboxes.map(Json::string)),
})
}
///|
pub fn run_json(
source : String,
message_json : String,
limits? : Limits = Limits::default(),
options? : ExecutionOptions = ExecutionOptions::default(),
) -> Json raise SieveError {
compile(source, limits~)
.run(parse_message_json(message_json, limits~), options~)
.as_json()
}
///|
fn check_json_depth(text : String) -> Unit raise SieveError {
let mut depth = 0
let mut quoted = false
let mut escaped = false
for c in text {
if quoted {
if escaped {
escaped = false
} else if c == '\\' {
escaped = true
} else if c == '"' {
quoted = false
}
} else if c == '"' {
quoted = true
} else if c == '[' || c == '{' {
depth += 1
if depth > 32 {
fail("json.depth", "JSON nesting exceeds 32", origin())
}
} else if c == ']' || c == '}' {
depth -= 1
}
}
}