MCP: a base64 thumbnail in a reply is sent as it is

Under automation read roots, a reply's base64 `thumbnail` (a library
item's PNG in `library.get`, a page's PNG in `document.pdfInfo`) was
searched for hidden library paths like any other text, so a short path
that happened to occur in the encoded bytes was rewritten and the image
broke. `thumbnail` now joins `dataBase64`, `pngBase64` and
`thumbnailBase64` as encoded data that is left unchanged; text under it
that is not base64 is still searched.
This commit is contained in:
dotCooCoo
2026-11-12 09:12:40 -08:00
parent 89e52eaabc
commit c8a6b4d9c3
3 changed files with 11 additions and 6 deletions
+3 -2
View File
@@ -572,8 +572,9 @@ struct Rewrite<'a> {
fold: bool,
}
/// The keys of encoded file data in a reply.
const BASE64_FIELDS: [&str; 3] = ["dataBase64", "pngBase64", "thumbnailBase64"];
/// The keys of encoded file data in a reply. `thumbnail` holds the PNG of a library item in
/// `library.get` and of a page in `document.pdfInfo`.
const BASE64_FIELDS: [&str; 4] = ["dataBase64", "pngBase64", "thumbnailBase64", "thumbnail"];
/// Is `s` written in the base64 alphabet, padded to a multiple of 4?
fn is_base64(s: &str) -> bool {
+7 -3
View File
@@ -40,14 +40,16 @@ fn hide_under_a_root(tag: &str, reply: &mut Value, hidden: &super::Hiding) {
let _ = std::fs::remove_dir_all(root);
}
/// Encoded file data in a reply, a base64 string under `dataBase64`, `pngBase64` or
/// `thumbnailBase64`, stays as it is when it holds a hidden library's path. Text that is not base64
/// under such a key, an object or a list under it, and any other key ending in `Base64` are searched.
/// Encoded file data in a reply, a base64 string under `dataBase64`, `pngBase64`, `thumbnailBase64`
/// or `thumbnail` (a library item's or a PDF page's PNG), stays as it is when it holds a hidden
/// library's path. Text that is not base64 under such a key, an object or a list under it, and any
/// other key ending in `Base64` are searched.
#[test]
fn a_reply_keeps_encoded_data_that_holds_a_hidden_path() {
let hidden = hiding(&[("loaded/assets/icon", "loaded/icon")], &[]);
let data = "QUJ+assets/iconREVGxQQ==";
let mut reply = json!({"ok": true, "result": {
"graphics": [{"name": "a", "thumbnail": data}, {"name": "b", "thumbnail": "assets/icon.png"}],
"files": [{"name": "a.png", "dataBase64": data, "pngBase64": "assets/icon.png", "thumbnailBase64": data}],
"status": "from assets/icon",
"dataBase64": {"note": "from assets/icon"},
@@ -58,6 +60,8 @@ fn a_reply_keeps_encoded_data_that_holds_a_hidden_path() {
let files = &reply["result"]["files"][0];
assert_eq!((&files["dataBase64"], &files["thumbnailBase64"]), (&json!(data), &json!(data)), "{reply}");
assert_eq!(files["pngBase64"], "icon.png", "{reply}");
let graphics = &reply["result"]["graphics"];
assert_eq!((&graphics[0]["thumbnail"], &graphics[1]["thumbnail"]), (&json!(data), &json!("icon.png")), "{reply}");
assert_eq!(reply["result"]["status"], "from icon", "{reply}");
assert_eq!(reply["result"]["dataBase64"]["note"], "from icon", "{reply}");
assert_eq!(reply["result"]["pngBase64"][0], "from icon", "{reply}");
+1 -1
View File
@@ -187,7 +187,7 @@ the suite) keep the agent to the folders you give it:
matched before the paths the request sent, so a sent path written over part of a hidden library's path does not
keep that path in the reply. A relative path is not replaced where it ends a longer path (`/work/data/icon.svg`
keeps `data/icon.svg`), and the absolute path it reads as from the app's working folder is replaced too. Encoded file data, a base64 string under `dataBase64`,
`pngBase64` or `thumbnailBase64`, is sent as it is.
`pngBase64`, `thumbnailBase64` or `thumbnail` (`library.get`, `document.pdfInfo`), is sent as it is.
- `swatch.library.list`, `brush.libraries`, `symbol.libraries` and `graphicStyle.libraries` list every library
but leave out the paths outside the read roots: a library's `path` and the user library folder in `userFolder`
are `null` there. A library loaded from a file outside the roots is listed as `loaded/<file name>`, numbered