Four tools and a document writer, plus the storage half of the phone: until now the picker's answer (a content:// URI) reached the engine as a path, and it said "cannot read" -- the app had no way to hand a phone's file over. - md_to_html (new, src/tools/web.c + cst_web_md_document()): markdown in, one self-contained HTML document out -- doctype, utf-8, viewport, escaped title, a small stylesheet; no script, no external request. It is written into the workspace and delivered as a file card (for_user, so it never enters a request), the way deliver_file does. The default name comes from the source file's stem or, for pasted markdown, from the title with what a path cannot carry turned into '-'. It has a preview, so an approval prompt says "render x -> y.html" before anything is written. - download (src/tools/web.c): an http(s) URL into a file in the workspace, streamed with a 128 MB ceiling; a redirect chain, the same CA handling and the same refusals as web_fetch, but the bytes stay on disk (a half file is removed on any failure, including HTTP >= 400). The name comes from the URL when the caller names only a directory. - read_pdf (src/tools/fs_tools.c): the text layer and page count of a PDF, which read_file refuses because the bytes are binary -- and a PDF is exactly what arrives from a phone. Same extractor an attachment uses, its own 4 MiB input ceiling, 256 KiB of text by default with a note when cut. - session_search (new, src/tools/session_tools.c): every stored session -- this one and all the ones before it -- searched for a substring, reporting the session's id, message count and title, then the matching lines with their number and role. Case-insensitive by default, capped at 20 matches. - device_info (same file, perm_name "device"): version, system/arch/CPU count, workspace, sandbox, model and permissions, config/temp/program directories, pid -- so the model stops guessing where it runs. - Tools 3 and 4 come with tests/test_tools_ext.c (also in both build files): the document is a whole page, a script tag and a javascript: link do not survive, a hostile title stays text and cannot write outside the workspace, the preview names the file, read_pdf/download refuse what they must (no network in a test), session_search finds a fixture session and says so when nothing matches, device_info names the workspace. README's tool table grew five rows. - Android storage: the picker (SAF) answers with a content:// URI, and the engine opens files by path, so the app now copies what it points at into its own directory first -- app/android/src/xyz/whcself/cstagent/ AttachFile.java, called through a new app::materializeAttachment() (ToolProvider.h: one API for every platform; a desktop strips file:// and refuses content:// with a reason). The copy needs no permission at all: the grant travels with the URI. - "All files access" (MANAGE_EXTERNAL_STORAGE) is the other half: it is what lets the engine read a path in shared storage that a task names rather than picks. Android has no runtime dialog for it, so main.cpp opens the one Settings screen once (AttachFile.java's requestAllFilesAccess, with the all-apps screen as a fallback) and never insists: without it everything works, one picked file at a time. On the NX789J the screen came up, the app-op reads back "allow", and a relaunch no longer asks. - Composer.qml hands a content:// URI through untouched (it is not a path, and decoding it would corrupt it); AgentCore::attachFile() turns whatever the picker gave into a path first. Desktop: build clean, make check 36/36 (the new test included, also under ASan), --uitest all green, --selftest and cst-app-selftest OK. The APK builds and installs (86 MB, release-signed) with the new Java class in its dex. Version 0.9.6.
53 lines
2.1 KiB
C
53 lines
2.1 KiB
C
#ifndef CST_WEB_MD_H
|
|
#define CST_WEB_MD_H
|
|
|
|
/* Markdown for the WebUI: the same sashidown renderer the TUI uses, but
|
|
* HTML output with a strict allowlist.
|
|
*
|
|
* sashidown renders straight to HTML and escapes text, quotes and '&', so
|
|
* no raw markup can reach the browser. Two holes remain, and both matter
|
|
* because every byte rendered here comes from a model or a tool: a link's
|
|
* href keeps any scheme ("javascript:") and an image's src may point
|
|
* anywhere (a tracking pixel, or worse). cst_web_md_* therefore rewrites
|
|
* tags instead of passing them through: unknown tags are dropped, hrefs
|
|
* are limited to http/https/mailto/relative, and images to the session's
|
|
* own attachment endpoint.
|
|
*
|
|
* The streaming form renders confirmed HTML as the model talks, which is
|
|
* the CST property the project is built on: nothing is emitted before it
|
|
* is safe to emit. A tag split across two deltas is held back until its
|
|
* '>' arrives. */
|
|
|
|
#include <stddef.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/* Called with chunks of sanitized HTML; return non-zero to stop. */
|
|
typedef int (*cst_web_md_emit_fn)(const char *html, size_t len, void *ud);
|
|
|
|
typedef struct cst_web_md cst_web_md_t;
|
|
|
|
cst_web_md_t *cst_web_md_new(cst_web_md_emit_fn emit, void *ud);
|
|
/* Feed model output. Returns 0, or -1 on OOM. */
|
|
int cst_web_md_feed(cst_web_md_t *s, const char *data, size_t len);
|
|
/* Flush the pending input and close the renderer. */
|
|
int cst_web_md_finish(cst_web_md_t *s);
|
|
void cst_web_md_free(cst_web_md_t *s);
|
|
|
|
/* One-shot: markdown -> sanitized HTML (malloc'd, never NULL on success). */
|
|
char *cst_web_md_html(const char *md, size_t len);
|
|
/* One-shot: markdown -> a whole HTML document (doctype, charset, title, a
|
|
* small stylesheet; no script and no external request), for a file handed to
|
|
* the user. title is optional; NULL or empty means "Document". */
|
|
char *cst_web_md_document(const char *md, size_t len, const char *title);
|
|
/* The sanitizer alone (tests, and any HTML that did not come from here). */
|
|
char *cst_web_md_sanitize(const char *html, size_t len);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* CST_WEB_MD_H */
|