Compare commits

...
Author SHA1 Message Date
Dylan M. Taylor e67e4ee3f0 merge main into fix/sec-markdown-inline-scans 2026-11-11 01:01:53 +00:00
Dylan M. Taylor a34c94e4c1 packaging: use [[ for bash tests (SonarCloud S7688) (#6224)
Behaviour-preserving: `[[ ]]` in place of `[ ]` for bash tests (SonarCloud shell:S7688). Every script has a bash shebang; no test uses `-a`/`-o`, escaped parentheses or an unquoted pattern on the right of `=`. `bash -n` passes on each script; the rest is left to CI.

- `packaging/freebsd/package.sh`
2026-11-10 18:45:02 -05:00
FredApps a42c9eaab3 Merge pull request #6187 from storytold/sonar/format-mandated-sha1
sonar: SHA-1 in ODT decryption is the format's, not a choice
2026-11-10 18:43:18 +01:00
Dylan M. Taylor 4462548ce3 Merge main (keep both progress-log lines) 2026-11-03 22:50:25 +00:00
Dylan M. Taylor 0c7f14f7a6 fix(formats): skip the comment-end search when no --> follows an inline <!-- 2026-11-03 11:41:02 +00:00
4 changed files with 24 additions and 6 deletions
+1
View File
@@ -150,6 +150,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
## Progress log
- 2026-11-03: Security: Markdown import looks for the end of an inline `<!--` comment only when one follows it, so a paragraph of unclosed comment openers no longer searches the rest of the text once per opener. Readiness unchanged.
- 2026-11-08: `wordcraft-cli convert --strict` (#6005) and `render --strict` (#6008): a PDF or PNG export (render: the page it writes) refuses, exit 3 and nothing written, when it would draw a character no font has or leave out a picture (missing part, external target, link only, undecodable or unsupported format); the report is on stderr with one JSON line. Engine: `strict: true` on `file.save`, `file.saveAs`, `file.exportPdf` and `file.exportPng`, and the pure `file.strictReport`. External picture targets are never fetched. The result depends on the host's installed fonts (#5624); readiness estimates unchanged.
- 2026-11-05: ODT tables with a usable width keep their column proportions when the file gives only shares (gap 19): LibreOffice's CV template opened and saved with equal columns.
- 2026-11-05: ODT font sizes reach Asian and complex-script text (gap 19): Chinese text set to 24 pt stayed at the default size in LibreOffice.
+7 -3
View File
@@ -276,6 +276,9 @@ fn tokenize(s: &str, base: &Fmt, depth: usize, refs: &Refs) -> Vec<Node> {
// text. A scan pays for what it reads; earlier bracket matches are looked up in `known`.
let ticks = Backticks::new(s);
let mut links = LinkScan { text: s, refs, ticks: &ticks, known: HashMap::new(), budget: s.len().saturating_mul(8).saturating_add(8192) };
// Where the last comment end is: a comment opener after it has none to find, so a run of
// unclosed `<!--` doesn't search the rest of the text once each.
let last_comment_end = s.rfind("-->");
let b = s.as_bytes();
let mut i = 0usize;
let mut text_start = 0usize;
@@ -366,7 +369,7 @@ fn tokenize(s: &str, base: &Fmt, depth: usize, refs: &Refs) -> Vec<Node> {
}
}
b'<' => {
if let Some((end, parsed)) = angle_inlines(s, i, base) {
if let Some((end, parsed)) = angle_inlines(s, i, base, last_comment_end) {
flush!();
nodes.extend(parsed);
i = end;
@@ -394,8 +397,9 @@ fn tokenize(s: &str, base: &Fmt, depth: usize, refs: &Refs) -> Vec<Node> {
nodes
}
fn angle_inlines(s: &str, start: usize, base: &Fmt) -> Option<(usize, Vec<Node>)> {
if let Some(pos) = s.get(start..).and_then(|rest| rest.strip_prefix("<!--")).and_then(|rest| rest.find("-->")) {
fn angle_inlines(s: &str, start: usize, base: &Fmt, last_comment_end: Option<usize>) -> Option<(usize, Vec<Node>)> {
let closable = last_comment_end.is_some_and(|e| e >= start + 4);
if let Some(pos) = s.get(start..).and_then(|rest| rest.strip_prefix("<!--")).filter(|_| closable).and_then(|rest| rest.find("-->")) {
return Some((start + 4 + pos + 3, Vec::new()));
}
let rest = s.get(start + 1..).unwrap_or("");
+13
View File
@@ -797,3 +797,16 @@ fn heading_trailing_hash_survives_round_trip() {
assert_eq!(export(&import(&md)), want, "second export of {src:?}");
}
}
#[test]
fn unclosed_inline_comments_are_text_without_a_search_each() {
// #3714-style: each unclosed `<!--` searched the rest of the paragraph for `-->`.
let src = "<!--".repeat(200_000);
let t = std::time::Instant::now();
let text: String = inl(&src).into_iter().filter_map(|i| if let Inline::Text(t, _) = i { Some(t) } else { None }).collect();
assert_eq!(text, src);
assert!(t.elapsed() < std::time::Duration::from_secs(10), "{:?}", t.elapsed());
// A comment that does close still goes, after an unclosed one too.
let text: String = inl("a <!-- b --> c <!-- d").into_iter().filter_map(|i| if let Inline::Text(t, _) = i { Some(t) } else { None }).collect();
assert_eq!(text, "a c <!-- d");
}
+3 -3
View File
@@ -55,7 +55,7 @@ esac
# Only binaries for this machine run here (the --version check at the end); a FreeBSD target built
# on another OS never does.
HOST_ARCH="$(release_arch "$(uname -m)" 2>/dev/null)" || HOST_ARCH="$(uname -m)"
if [ -n "${CARGO_BUILD_TARGET:-}" ] && [ "$(uname -s)" != FreeBSD ]; then
if [[ -n "${CARGO_BUILD_TARGET:-}" ]] && [[ "$(uname -s)" != FreeBSD ]]; then
HOST_ARCH="$(uname -s) $HOST_ARCH"
fi
BASENAME="wordcraft-$VERSION-freebsd-$ARCH"
@@ -96,12 +96,12 @@ mkdir -p "$DIST"
tar -C "$WORK" -czf "$DIST/$BASENAME.tar.gz" "$BASENAME"
echo "wrote $DIST/$BASENAME.tar.gz"
if [ -z "${CARGO_BUILD_TARGET:-}" ] && [ "$ARCH" = "$HOST_ARCH" ]; then
if [[ -z "${CARGO_BUILD_TARGET:-}" ]] && [[ "$ARCH" = "$HOST_ARCH" ]]; then
"$STAGE/bin/wordcraft-cli" --version
else
# They cannot run here, so check what they are instead: executables, not libraries or objects.
# An older file(1) calls a PIE a shared object; its interpreter still marks it as a program.
want="$ARCH"; [ "$ARCH" != x86_64 ] || want=x86-64
want="$ARCH"; [[ "$ARCH" != x86_64 ]] || want=x86-64
for b in "$STAGE"/bin/*; do
case "$(file -b "$b")" in
ELF*" executable, "*"$want"*FreeBSD* | ELF*"$want"*FreeBSD*interpreter*) ;;