diff --git a/app/src/main/java/com/httrack/android/BrowserLoginActivity.java b/app/src/main/java/com/httrack/android/BrowserLoginActivity.java index 7ab5f03..8b087a1 100644 --- a/app/src/main/java/com/httrack/android/BrowserLoginActivity.java +++ b/app/src/main/java/com/httrack/android/BrowserLoginActivity.java @@ -348,7 +348,8 @@ public void onClickLoginDone(final View view) { HTTrackLib.LoginBundle bundle = null; for (final String url : BrowserLoginPolicy.addressesToTry(web.getUrl(), openedUrl)) { bundle = HTTrackLib.writeLoginBundle(result.text, bundleDir.getAbsolutePath(), - web.getSettings().getUserAgentString(), url); + web.getSettings().getUserAgentString(), url, + BrowserLoginPolicy.keptHosts(byHost.keySet())); if (bundle != null) { break; } diff --git a/app/src/main/java/com/httrack/android/BrowserLoginPolicy.java b/app/src/main/java/com/httrack/android/BrowserLoginPolicy.java index cf1829c..68b68c8 100644 --- a/app/src/main/java/com/httrack/android/BrowserLoginPolicy.java +++ b/app/src/main/java/com/httrack/android/BrowserLoginPolicy.java @@ -4,6 +4,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.regex.Matcher; @@ -96,6 +97,22 @@ static String hostOf(final String url) { return host.toLowerCase(Locale.ROOT); } + /** + * Joins HOSTS into the comma-separated list the engine keeps cookies for. + * + * @param hosts + * the hosts that answered with a cookie, in the order they were asked + * @return the list, or null for none, which leaves the address's own cookies alone + */ + static String keptHosts(final Collection hosts) { + final StringBuilder list = new StringBuilder(); + + for (final String host : hosts) { + list.append(list.length() == 0 ? "" : ",").append(host); + } + return list.length() == 0 ? null : list.toString(); + } + /** * The addresses to offer the engine for this login, best first. The login stops on the page * the user wants copied, so that address leads. The engine refuses an address it cannot diff --git a/app/src/main/java/com/httrack/android/jni/HTTrackLib.java b/app/src/main/java/com/httrack/android/jni/HTTrackLib.java index 6ff498b..d7515bf 100755 --- a/app/src/main/java/com/httrack/android/jni/HTTrackLib.java +++ b/app/src/main/java/com/httrack/android/jni/HTTrackLib.java @@ -154,10 +154,13 @@ public LoginBundle(final String url, final int cookies, final int hosts) { * the browser's User-Agent, sent to the bundle's sites alone * @param url * the address the login ended on + * @param hosts + * the hosts whose cookies to keep besides the address's own, comma-separated, or + * null to keep the address's own alone * @return what it wrote, or null when the engine refused the login */ public static native LoginBundle writeLoginBundle(String paste, String folder, String agent, - String url); + String url, String hosts); /** * Has a native fault been recovered in this process ? diff --git a/app/src/main/jni/htslibjni.c b/app/src/main/jni/htslibjni.c index ce5b5e6..f127f53 100755 --- a/app/src/main/jni/htslibjni.c +++ b/app/src/main/jni/htslibjni.c @@ -696,8 +696,13 @@ JNICALL jobject Java_com_httrack_android_jni_HTTrackLib_writeLoginBundle(JNIEnv* jstring opaste, jstring ofolder, jstring oagent, - jstring ourl) { + jstring ourl, + jstring ohosts) { const char *paste, *folder, *agent, *url; + /* NULL is a value, not a mistake. It asks for the address's own cookies + alone. */ + const char* hosts = NULL; + int got_hosts; jobject bundle = NULL; UNUSED(clazz); @@ -716,12 +721,18 @@ JNICALL jobject Java_com_httrack_android_jni_HTTrackLib_writeLoginBundle(JNIEnv* folder = (*env)->GetStringUTFChars(env, ofolder, NULL); agent = (*env)->GetStringUTFChars(env, oagent, NULL); url = (*env)->GetStringUTFChars(env, ourl, NULL); - if (paste != NULL && folder != NULL && agent != NULL && url != NULL) { + if (ohosts != NULL) { + hosts = (*env)->GetStringUTFChars(env, ohosts, NULL); + } + got_hosts = ohosts == NULL || hosts != NULL; + if (paste != NULL && folder != NULL && agent != NULL && url != NULL && got_hosts) { hts_paste_result result; memset(&result, 0, sizeof(result)); result.size = sizeof(result); - if (hts_bundle_from_paste(paste, folder, agent, url, HTS_FALSE, &result)) { + /* 0u: no HTS_PASTE_* flag. */ + if (hts_bundle_from_paste_hosts(paste, folder, agent, url, hosts, 0u, + &result)) { jstring written = (*env)->NewStringUTF(env, result.url); if (written != NULL) { @@ -733,6 +744,9 @@ JNICALL jobject Java_com_httrack_android_jni_HTTrackLib_writeLoginBundle(JNIEnv* debug("the engine refused the login: %s", result.error); } } + if (hosts != NULL) { + (*env)->ReleaseStringUTFChars(env, ohosts, hosts); + } if (url != NULL) { (*env)->ReleaseStringUTFChars(env, ourl, url); } diff --git a/app/src/main/jni/httrack b/app/src/main/jni/httrack index b097228..483d5d4 160000 --- a/app/src/main/jni/httrack +++ b/app/src/main/jni/httrack @@ -1 +1 @@ -Subproject commit b0972289468f41652f88708300d6fa0e33472b3f +Subproject commit 483d5d47a6243cf57d1495e473a09a09519f3dea diff --git a/app/src/test/java/com/httrack/android/BrowserLoginEngineLimitsTest.java b/app/src/test/java/com/httrack/android/BrowserLoginEngineLimitsTest.java index abe1fa4..deeafb7 100644 --- a/app/src/test/java/com/httrack/android/BrowserLoginEngineLimitsTest.java +++ b/app/src/test/java/com/httrack/android/BrowserLoginEngineLimitsTest.java @@ -1,6 +1,7 @@ package com.httrack.android; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.IOException; @@ -105,17 +106,21 @@ 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(")); + /* Comments and spacing stripped, so reflowing the call cannot fail this. The argument + list is pinned whole, because the same call with a NULL hosts keeps the older + behaviour and reads as the fix. */ + final String jni = TestSources.withoutCommentsAndStrings( + TestSources.read(TestSources.mainFile("jni/htslibjni.c"))).replaceAll("\\s+", " "); + assertTrue(jni, jni.contains( + "hts_bundle_from_paste_hosts(paste, folder, agent, url, hosts, 0u, &result)")); + assertFalse("the entry point without a hosts list keeps no intermediate cookies", + jni.contains("hts_bundle_from_paste(paste,")); + assertTrue("hts_bundle_from_paste_hosts is no longer exported", + source("httrack-library.h").contains( + "HTSEXT_API hts_boolean hts_bundle_from_paste_hosts(")); 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(")); final String body = TestSources.balancedBlock(paste, - TestSources.indexOf(paste, "HTSEXT_API hts_boolean hts_bundle_from_paste_flags(")); + TestSources.indexOf(paste, "HTSEXT_API hts_boolean hts_bundle_from_paste_hosts(")); // 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("); diff --git a/app/src/test/java/com/httrack/android/BrowserLoginPolicyTest.java b/app/src/test/java/com/httrack/android/BrowserLoginPolicyTest.java index f41bdac..6e69997 100644 --- a/app/src/test/java/com/httrack/android/BrowserLoginPolicyTest.java +++ b/app/src/test/java/com/httrack/android/BrowserLoginPolicyTest.java @@ -204,6 +204,24 @@ public void theTypedCredentialsDoNotTravelToAnotherHost() { assertEquals(cleartext, named("https://user:pw@a.example/", cleartext)); } + /** + * The login keeps the cookies of every host that answered with one, so a crawl can + * authenticate on a site the login passed through. + */ + @Test + public void everyHostThatAnsweredWithACookieIsNamed() { + assertEquals("a.example,idp.example", + BrowserLoginPolicy.keptHosts(Arrays.asList("a.example", "idp.example"))); + // The engine's separator is a comma, and nothing else. + assertEquals("a.example", BrowserLoginPolicy.keptHosts(Arrays.asList("a.example"))); + } + + /** Naming no host asks the engine for the address's own cookies alone, as it did before. */ + @Test + public void namingNoHostKeepsTheEnginesOlderBehaviour() { + assertNull(BrowserLoginPolicy.keptHosts(new ArrayList())); + } + /** * The landed address leads, because that is the page to copy. The opened one follows, so a * landed address the engine refuses still leaves a login to save. diff --git a/app/src/test/java/com/httrack/android/BrowserLoginWiringTest.java b/app/src/test/java/com/httrack/android/BrowserLoginWiringTest.java index d22fc00..8c59ae0 100644 --- a/app/src/test/java/com/httrack/android/BrowserLoginWiringTest.java +++ b/app/src/test/java/com/httrack/android/BrowserLoginWiringTest.java @@ -222,12 +222,15 @@ public void theBundleIsNamedForTheAddressTheLoginLandedOn() throws IOException { TestSources.indexOf(source, "public void onClickLoginDone(")); assertTrue(done, done.contains("BrowserLoginPolicy.addressesToTry(web.getUrl(), openedUrl)")); // The loop's own address, or every attempt would name the one the window was opened for. - assertTrue(done, done.contains("getUserAgentString(), url);")); + assertTrue(done, done.contains("getUserAgentString(), url,")); // The loop stops at the first address the engine accepts, rather than at the last. assertTrue(done, done.contains("if (bundle != null) {\n break;")); final String create = TestSources.balancedBlock(source, TestSources.indexOf(source, "protected void onCreate(")); assertTrue(create, create.contains("openedUrl = BrowserLoginPolicy.withoutUserInfo(url)")); + /* The hosts that answered, not the hosts asked: a host with no cookie carries nothing + to keep, and naming it would widen the bundle's scope for nothing. */ + assertTrue(done, done.contains("BrowserLoginPolicy.keptHosts(byHost.keySet())")); final String caller = TestSources .withoutCommentsAndStrings(TestSources.javaSource("HTTrackActivity")); final String apply = TestSources.balancedBlock(caller,