From 838c40c84274909f2f28c62be55836a5214c8baa Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Wed, 28 Oct 2026 13:28:11 +0100 Subject: [PATCH 1/2] Bump the vendored engine to eddf187c, engine master The engine's lang-symbols.tsv lands with this pin, so the build_strings.sh switch becomes possible in a later change. The 33 engine commits add and remove no source, so Android.mk's explicit list is unchanged. HTTRACK_VERSIONID is still 3.50.5, so versionName stays as it is. The nested coucal pointer moves with it. hts_bundle_from_paste is still exported and now forwards to a new hts_bundle_from_paste_flags, which holds the body. So htslibjni.c is untouched, and the test that pins the write ordering follows the body into that function. That test's guard was also a weak needle. Inverting the engine's `if (!` on the read-back left it green, because the search walked back to an earlier `if (!`. Both now read as one string, and four mutants of the engine source turn the test red. Signed-off-by: Xavier Roche Co-Authored-By: Claude Opus 5 (1M context) --- app/src/main/jni/httrack | 2 +- .../httrack/android/BrowserLoginEngineLimitsTest.java | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/src/main/jni/httrack b/app/src/main/jni/httrack index 6bb24d1..eddf187 160000 --- a/app/src/main/jni/httrack +++ b/app/src/main/jni/httrack @@ -1 +1 @@ -Subproject commit 6bb24d1a11d1927f5a935071d7a1bbb16e048e19 +Subproject commit eddf187ca9e181108975e77a7b43feb785b37cac diff --git a/app/src/test/java/com/httrack/android/BrowserLoginEngineLimitsTest.java b/app/src/test/java/com/httrack/android/BrowserLoginEngineLimitsTest.java index a2ba6f2..6818382 100644 --- a/app/src/test/java/com/httrack/android/BrowserLoginEngineLimitsTest.java +++ b/app/src/test/java/com/httrack/android/BrowserLoginEngineLimitsTest.java @@ -108,12 +108,15 @@ public void theBundleWriterIsAnExportedEngineCallThatReadsItsOwnOutputBack() thr assertTrue("hts_bundle_from_paste is no longer exported", source("httrack-library.h").contains("HTSEXT_API hts_boolean hts_bundle_from_paste(")); final String paste = source("htsbundle.c"); - // Inside that function, not anywhere in the file, and the failure has to stop the write. - final String body = TestSources.balancedBlock(paste, + final String wrapper = TestSources.balancedBlock(paste, TestSources.indexOf(paste, "HTSEXT_API hts_boolean hts_bundle_from_paste(")); - final int read = body.indexOf("bundle_load_src(opt, &check, &src"); + assertTrue(wrapper, wrapper.contains("return hts_bundle_from_paste_flags(")); + // Inside the function our call reaches, not anywhere in the file, and the failure has to + // stop the write. + final String body = TestSources.balancedBlock(paste, + TestSources.indexOf(paste, "HTSEXT_API hts_boolean hts_bundle_from_paste_flags(")); + final int read = body.indexOf("if (!bundle_load_src(opt, &check, &src"); assertTrue(body, read != -1); - assertTrue(body, body.startsWith("if (!", body.lastIndexOf("if (!", read))); assertTrue(body, body.indexOf("PASTE_FAIL", read) != -1); assertTrue(body, body.indexOf("cookie_write_replace", read) > read); } From 1093370ab7c147f4b4c4de85f4c4d121ecc834eb Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Wed, 28 Oct 2026 13:35:28 +0100 Subject: [PATCH 2/2] Pin the read-back's own guard, not the text after it Two mutants of the engine source survived the test: replacing the guard's PASTE_FAIL with a success, and making it conditional. Both leave a bundle the read-back rejected in the project folder, which is the property the test claims. The guard and its whole statement now read as one regex, and the write has to come after that statement rather than after the call. The test also never checked that the JNI calls the entry point it inspects. Switching htslibjni.c to the flags call left it reading an unrelated wrapper, so it asserts that call now. Seven mutants of the engine source turn it red, and reflowing the guard across lines does not. Signed-off-by: Xavier Roche Co-Authored-By: Claude Opus 5 (1M context) --- .../android/BrowserLoginEngineLimitsTest.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/src/test/java/com/httrack/android/BrowserLoginEngineLimitsTest.java b/app/src/test/java/com/httrack/android/BrowserLoginEngineLimitsTest.java index 6818382..abe1fa4 100644 --- a/app/src/test/java/com/httrack/android/BrowserLoginEngineLimitsTest.java +++ b/app/src/test/java/com/httrack/android/BrowserLoginEngineLimitsTest.java @@ -105,20 +105,24 @@ public void theCapsKeepEveryLineUnderTheEnginesLineMax() throws IOException { */ @Test public void theBundleWriterIsAnExportedEngineCallThatReadsItsOwnOutputBack() throws IOException { + assertTrue("the JNI calls the flags entry point now, so this test reads the wrong function", + TestSources.read(TestSources.mainFile("jni/htslibjni.c")).contains( + "hts_bundle_from_paste(paste, folder, agent, url")); assertTrue("hts_bundle_from_paste is no longer exported", source("httrack-library.h").contains("HTSEXT_API hts_boolean hts_bundle_from_paste(")); final String paste = source("htsbundle.c"); final String wrapper = TestSources.balancedBlock(paste, TestSources.indexOf(paste, "HTSEXT_API hts_boolean hts_bundle_from_paste(")); assertTrue(wrapper, wrapper.contains("return hts_bundle_from_paste_flags(")); - // Inside the function our call reaches, not anywhere in the file, and the failure has to - // stop the write. final String body = TestSources.balancedBlock(paste, TestSources.indexOf(paste, "HTSEXT_API hts_boolean hts_bundle_from_paste_flags(")); - final int read = body.indexOf("if (!bundle_load_src(opt, &check, &src"); - assertTrue(body, read != -1); - assertTrue(body, body.indexOf("PASTE_FAIL", read) != -1); - assertTrue(body, body.indexOf("cookie_write_replace", read) > read); + // Failing has to be the guard's whole statement, or a bundle the read-back rejected still + // reaches the folder. + final int read = TestSources.indexOf(body, "!bundle_load_src("); + final int ends = body.indexOf(';', read); + final String guard = body.substring(read, ends + 1).replaceAll("\\s+", " "); + assertTrue(guard, guard.matches("!bundle_load_src\\(.*\\)\\) PASTE_FAIL\\(.*;")); + assertTrue(body, body.indexOf("cookie_write_replace") > ends); } /** Our file has to leave the mirror's own cookies.txt room in the same jar. */