1use serde_json::{json, Map, Value};
5
6pub const CORE: &str = "urn:ietf:params:jmap:core";
7pub const MAIL: &str = "urn:ietf:params:jmap:mail";
8pub const SUBMISSION: &str = "urn:ietf:params:jmap:submission";
9pub const VACATION: &str = "urn:ietf:params:jmap:vacationresponse";
10pub const CAPABILITIES: &[&str] = &[CORE, MAIL, SUBMISSION, VACATION];
11
12pub const MAX_CALLS: usize = 16;
13pub const MAX_OBJECTS_IN_GET: usize = 256;
14pub const MAX_OBJECTS_IN_SET: usize = 128;
15pub const MAX_DEPTH: usize = 32;
17
18#[derive(Debug, PartialEq)]
20pub struct Problem {
21 pub kind: &'static str,
22 pub detail: String,
23 pub limit: Option<&'static str>,
24}
25
26impl Problem {
27 fn new(kind: &'static str, detail: impl Into<String>) -> Self {
28 Self {
29 kind,
30 detail: detail.into(),
31 limit: None,
32 }
33 }
34 pub fn body(&self) -> Value {
35 let mut b = json!({"type": format!("urn:ietf:params:jmap:error:{}", self.kind), "status": 400, "detail": self.detail});
36 if let Some(l) = self.limit {
37 b["limit"] = json!(l);
38 }
39 b
40 }
41}
42
43pub fn method_error(kind: &str, description: &str) -> Value {
45 let mut e = json!({"type": kind});
46 if !description.is_empty() {
47 e["description"] = json!(description);
48 }
49 e
50}
51
52pub fn depth(v: &Value) -> usize {
53 match v {
54 Value::Array(a) => 1 + a.iter().map(depth).max().unwrap_or(0),
55 Value::Object(o) => 1 + o.values().map(depth).max().unwrap_or(0),
56 _ => 0,
57 }
58}
59
60pub struct Call {
61 pub name: String,
62 pub arguments: Map<String, Value>,
63 pub id: String,
64}
65
66pub struct Request {
67 pub using: Vec<String>,
68 pub calls: Vec<Call>,
69 pub created_ids: Option<Map<String, Value>>,
70}
71
72pub fn parse(body: &[u8]) -> Result<Request, Problem> {
74 let v: Value = serde_json::from_slice(body)
75 .map_err(|_| Problem::new("notJSON", "the body is not JSON"))?;
76 if depth(&v) > MAX_DEPTH {
77 return Err(Problem::new("notRequest", "the request nests too deeply"));
78 }
79 let obj = v
80 .as_object()
81 .ok_or_else(|| Problem::new("notRequest", "a request is a JSON object"))?;
82 let using: Vec<String> = obj
83 .get("using")
84 .and_then(Value::as_array)
85 .ok_or_else(|| Problem::new("notRequest", "using is an array of capability URIs"))?
86 .iter()
87 .map(|u| u.as_str().map(str::to_owned))
88 .collect::<Option<_>>()
89 .ok_or_else(|| Problem::new("notRequest", "using lists strings"))?;
90 if let Some(u) = using.iter().find(|u| !CAPABILITIES.contains(&u.as_str())) {
91 return Err(Problem::new(
92 "unknownCapability",
93 format!("the server does not support {u}"),
94 ));
95 }
96 let raw = obj
97 .get("methodCalls")
98 .and_then(Value::as_array)
99 .ok_or_else(|| Problem::new("notRequest", "methodCalls is an array"))?;
100 if raw.len() > MAX_CALLS {
101 return Err(Problem {
102 limit: Some("maxCallsInRequest"),
103 ..Problem::new("limit", format!("at most {MAX_CALLS} method calls"))
104 });
105 }
106 let mut calls = Vec::with_capacity(raw.len());
107 for c in raw {
108 let shape = || Problem::new("notRequest", "each method call is [name, arguments, id]");
109 let c = c.as_array().filter(|c| c.len() == 3).ok_or_else(shape)?;
110 let name = c[0].as_str().ok_or_else(shape)?;
111 let arguments = c[1].as_object().ok_or_else(shape)?;
112 let id = c[2].as_str().ok_or_else(shape)?;
113 calls.push(Call {
114 name: name.to_owned(),
115 arguments: arguments.clone(),
116 id: id.to_owned(),
117 });
118 }
119 let created_ids = match obj.get("createdIds") {
120 None => None,
121 Some(Value::Object(m)) => Some(m.clone()),
122 Some(_) => return Err(Problem::new("notRequest", "createdIds is an object")),
123 };
124 Ok(Request {
125 using,
126 calls,
127 created_ids,
128 })
129}
130
131pub fn capability(method: &str) -> Option<&'static str> {
133 let (kind, verb) = method.split_once('/')?;
134 if kind == "Core" {
135 return (verb == "echo").then_some(CORE);
136 }
137 let verbs: &[&str] = match kind {
138 "Mailbox" | "Email" => &["get", "changes", "query", "queryChanges", "set"],
139 "Thread" => &["get", "changes"],
140 "SearchSnippet" => &["get"],
141 "Identity" | "EmailSubmission" => &["get", "changes", "set"],
142 "VacationResponse" => &["get", "set"],
143 _ => return None,
144 };
145 if !verbs.contains(&verb)
146 && !(kind == "Email" && matches!(verb, "copy" | "import" | "parse"))
147 && !(kind == "EmailSubmission" && matches!(verb, "query" | "queryChanges"))
148 {
149 return None;
150 }
151 Some(match kind {
152 "Identity" | "EmailSubmission" => SUBMISSION,
153 "VacationResponse" => VACATION,
154 _ => MAIL,
155 })
156}
157
158pub fn check_limits(method: &str, args: &Map<String, Value>) -> Option<Value> {
160 let verb = method.split_once('/').map(|(_, v)| v).unwrap_or_default();
161 if verb == "get" {
162 if let Some(ids) = args.get("ids").and_then(Value::as_array) {
163 if ids.len() > MAX_OBJECTS_IN_GET {
164 return Some(method_error(
165 "requestTooLarge",
166 &format!("at most {MAX_OBJECTS_IN_GET} ids"),
167 ));
168 }
169 }
170 }
171 if verb == "set" {
172 let count = |k: &str| match args.get(k) {
173 Some(Value::Object(m)) => m.len(),
174 Some(Value::Array(a)) => a.len(),
175 _ => 0,
176 };
177 if count("create") + count("update") + count("destroy") > MAX_OBJECTS_IN_SET {
178 return Some(method_error(
179 "requestTooLarge",
180 &format!("at most {MAX_OBJECTS_IN_SET} objects"),
181 ));
182 }
183 }
184 None
185}
186
187pub fn pointer(v: &Value, path: &str) -> Option<Value> {
189 if path.is_empty() {
190 return Some(v.clone());
191 }
192 let rest = path.strip_prefix('/')?;
193 let (token, tail) = match rest.split_once('/') {
194 Some((t, tail)) => (t, format!("/{tail}")),
195 None => (rest, String::new()),
196 };
197 let token = token.replace("~1", "/").replace("~0", "~");
198 match v {
199 Value::Array(items) if token == "*" => {
200 let mut out = Vec::new();
201 for item in items {
202 match pointer(item, &tail)? {
203 Value::Array(inner) => out.extend(inner),
204 other => out.push(other),
205 }
206 }
207 Some(Value::Array(out))
208 }
209 Value::Array(items) => pointer(items.get(token.parse::<usize>().ok()?)?, &tail),
210 Value::Object(m) => pointer(m.get(&token)?, &tail),
211 _ => None,
212 }
213}
214
215pub fn resolve_references(
218 args: &Map<String, Value>,
219 earlier: &[(String, Value, String)],
220) -> Result<Map<String, Value>, Value> {
221 let mut out = Map::new();
222 for (k, v) in args {
223 let Some(plain) = k.strip_prefix('#') else {
224 out.insert(k.clone(), v.clone());
225 continue;
226 };
227 if args.contains_key(plain) {
228 return Err(method_error(
229 "invalidArguments",
230 &format!("both {plain} and #{plain} are given"),
231 ));
232 }
233 let bad = |why: &str| method_error("invalidResultReference", why);
234 let r = v
235 .as_object()
236 .ok_or_else(|| bad("a reference is an object"))?;
237 let (Some(of), Some(name), Some(path)) = (
238 r.get("resultOf").and_then(Value::as_str),
239 r.get("name").and_then(Value::as_str),
240 r.get("path").and_then(Value::as_str),
241 ) else {
242 return Err(bad("a reference has resultOf, name and path"));
243 };
244 let source = earlier
245 .iter()
246 .find(|(_, _, id)| id == of)
247 .ok_or_else(|| bad(&format!("no earlier response has id {of}")))?;
248 if source.0 != name {
249 return Err(bad(&format!(
250 "the response {of} is {}, not {name}",
251 source.0
252 )));
253 }
254 let value = pointer(&source.1, path)
255 .ok_or_else(|| bad(&format!("{path} does not resolve in {of}")))?;
256 out.insert(plain.to_owned(), value);
257 }
258 Ok(out)
259}
260
261pub fn substitute_creation_ids(v: &Value, created: &Map<String, Value>) -> Value {
264 let lookup = |s: &str| {
265 s.strip_prefix('#')
266 .and_then(|c| created.get(c))
267 .and_then(Value::as_str)
268 .map(str::to_owned)
269 };
270 match v {
271 Value::String(s) => lookup(s).map(Value::String).unwrap_or_else(|| v.clone()),
272 Value::Array(a) => Value::Array(
273 a.iter()
274 .map(|x| substitute_creation_ids(x, created))
275 .collect(),
276 ),
277 Value::Object(m) => Value::Object(
278 m.iter()
279 .map(|(k, x)| {
280 (
281 lookup(k).unwrap_or_else(|| k.clone()),
282 substitute_creation_ids(x, created),
283 )
284 })
285 .collect(),
286 ),
287 _ => v.clone(),
288 }
289}
290
291pub fn record_created(response: &Value, created: &mut Map<String, Value>) {
293 if let Some(m) = response.get("created").and_then(Value::as_object) {
294 for (cid, obj) in m {
295 if let Some(id) = obj.get("id").and_then(Value::as_str) {
296 created.insert(cid.clone(), json!(id));
297 }
298 }
299 }
300}