1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
use serde::Serialize;
use crate::{escape_into, json};
/// The stores' state as the client reads it back before it hydrates: a `<script type="application/json">`,
/// which a `script-src 'self'` policy does not run, so the page needs no nonce for it. `<`, `>`, `&`
/// and the two line separators are written as JSON escapes, so no value can end the element or be
/// read as markup inside it.
///
/// `id` is the element's `id`, which the client looks the state up by: `"__pinia"` is what
/// `hydrateState` from `ferrovue/client` reads by default.
///
/// # Example
///
/// ```
/// let state = serde_json::json!({ "prefs": { "theme": "</script>" } });
/// let mut page = String::new();
/// ferrovue::state_script_into(&mut page, "__pinia", &state);
/// assert_eq!(
/// page,
/// r#"<script type="application/json" id="__pinia">{"prefs":{"theme":"\u003c/script\u003e"}}</script>"#
/// );
/// ```
pub fn state_script_into(out: &mut String, id: &str, state: &impl Serialize) {
json_script_into(out, id, &json::to_string(state));
}
pub(crate) fn json_script_into(out: &mut String, id: &str, json: &str) {
out.push_str("<script type=\"application/json\" id=\"");
escape_into(out, id);
out.push_str("\">");
json_escaped_into(out, json);
out.push_str("</script>");
}
pub(crate) fn json_escaped_into(out: &mut String, json: &str) {
out.reserve(json.len());
let bytes = json.as_bytes();
let (mut last, mut i) = (0, 0);
while i < bytes.len() {
let (rep, width) = match bytes[i] {
b'<' => ("\\u003c", 1),
b'>' => ("\\u003e", 1),
b'&' => ("\\u0026", 1),
0xE2 if bytes.get(i + 1) == Some(&0x80) && bytes.get(i + 2) == Some(&0xA8) => {
("\\u2028", 3)
}
0xE2 if bytes.get(i + 1) == Some(&0x80) && bytes.get(i + 2) == Some(&0xA9) => {
("\\u2029", 3)
}
_ => {
i += 1;
continue;
}
};
out.push_str(&json[last..i]);
out.push_str(rep);
i += width;
last = i;
}
out.push_str(&json[last..]);
}
#[cfg(test)]
mod tests;