diff --git a/app/src/main/java/com/httrack/android/OptionValues.java b/app/src/main/java/com/httrack/android/OptionValues.java index d56d525..76bfee1 100644 --- a/app/src/main/java/com/httrack/android/OptionValues.java +++ b/app/src/main/java/com/httrack/android/OptionValues.java @@ -21,6 +21,7 @@ package com.httrack.android; +import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.util.regex.Pattern; @@ -103,6 +104,29 @@ public static boolean isAtMost(final String value, final long max) { } } + /** Lowest non-zero rate the engine takes for -%c, and the highest. */ + private static final String MIN_RATE = "0.000001"; + + private static final String MAX_RATE = new BigDecimal(Float.MAX_VALUE) + .toPlainString(); + + /** + * The rate brought inside the range the engine accepts. The engine panics on + * a rate between zero and 0.000001 or above a float, and a zero means no + * limit, so it stays. + * + * @param value + * A value isDecimal accepted + * @return the value, or the nearest rate the engine takes + */ + public static String clampRate(final String value) { + final double rate = Double.parseDouble(value); + if (rate > 0 && rate < Double.parseDouble(MIN_RATE)) { + return MIN_RATE; + } + return rate > Float.MAX_VALUE ? MAX_RATE : value; + } + /** * True if the value is at most max bytes of UTF-8. An engine buffer is a byte * count, so a limit measured in characters lets a non-ASCII value through. diff --git a/app/src/main/java/com/httrack/android/OptionsMapper.java b/app/src/main/java/com/httrack/android/OptionsMapper.java index 4d6e5c5..002df20 100755 --- a/app/src/main/java/com/httrack/android/OptionsMapper.java +++ b/app/src/main/java/com/httrack/android/OptionsMapper.java @@ -1736,9 +1736,12 @@ public void emit(final List commandline, final String value) { * as a URL, which turns -r1.5 into an exclusion filter. */ /* Over the cap the engine refuses the command line or misreads the * value, and no layout bounds these fields. */ + /* A rate the engine refuses is clamped, because dropping it would leave + * the crawl with no rate limit at all. */ if (fraction ? OptionValues.isDecimal(value) : OptionValues.isAtMost( value, max)) { - commandline.add("-" + option + value); + commandline.add( + "-" + option + (fraction ? OptionValues.clampRate(value) : value)); } else { refused(commandline, value); } diff --git a/app/src/main/jni/Android.mk b/app/src/main/jni/Android.mk index 8bd1a6d..e49247f 100755 --- a/app/src/main/jni/Android.mk +++ b/app/src/main/jni/Android.mk @@ -74,6 +74,7 @@ LOCAL_SRC_FILES := httrack/src/htscore.c httrack/src/htsparse.c \ httrack/src/htstools.c httrack/src/htswizard.c httrack/src/htsalias.c \ httrack/src/htsthread.c httrack/src/htsindex.c httrack/src/htsbauth.c \ httrack/src/htsbundle.c \ + httrack/src/htsbase.c \ httrack/src/htsmd5.c httrack/src/htszlib.c httrack/src/htswrap.c \ httrack/src/htsconcat.c httrack/src/htsmodules.c \ httrack/src/htscharset.c httrack/src/punycode.c \ @@ -113,6 +114,8 @@ LOCAL_CFLAGS += -O3 -g3 -funwind-tables -fPIC \ -Wno-nested-externs \ -D_REENTRANT -DPIC -DANDROID -D_ANDROID -DHAVE_CONFIG_H -DINET6 -DHTS_INTERNAL_BUILD \ -DLIBHTTRACK_EXPORTS -DZLIB_CONST -DHTS_INTHASH_USES_MD5 -DLIBICONV_PLUG +# HTS_INTERNAL_BYTECODE shows the engine-only half of the headers to the library. +LOCAL_CFLAGS += -DHTS_INTERNAL_BYTECODE # The app never asks the engine for a self-test. The sources are gone from # LOCAL_SRC_FILES above too, because htsselftest_int.h #errors under this macro. LOCAL_CFLAGS += -DHTS_NO_SELFTEST diff --git a/app/src/main/jni/httrack b/app/src/main/jni/httrack index 483d5d4..7a923f4 160000 --- a/app/src/main/jni/httrack +++ b/app/src/main/jni/httrack @@ -1 +1 @@ -Subproject commit 483d5d47a6243cf57d1495e473a09a09519f3dea +Subproject commit 7a923f4047df1bec18e2ce1b4403c7038d055021 diff --git a/app/src/test/java/com/httrack/android/NumericOptionValueTest.java b/app/src/test/java/com/httrack/android/NumericOptionValueTest.java index 63598f4..580b04c 100644 --- a/app/src/test/java/com/httrack/android/NumericOptionValueTest.java +++ b/app/src/test/java/com/httrack/android/NumericOptionValueTest.java @@ -141,6 +141,51 @@ public void aSizeTheEngineWouldPanicOnNeverReachesIt() { } } + /** The float option's own bounds, which the pinned engine enforces. */ + private static final String MIN_RATE = "0.000001"; + + private static final String MAX_RATE = + "340282346638528859811704183484516925440"; + + /* The engine panics on a rate between zero and the floor, and on one above a + float, where it used to cast whatever it read. */ + @Test + public void aRateTheEngineWouldPanicOnIsClampedToTheBound() { + final OptionMapper mapper = new SimpleOption(FLOAT_OPTION, true); + for (final String value : new String[] { "0.0000001", "0.0000000001", + "0.00000099" }) { + assertEquals("-" + FLOAT_OPTION + MIN_RATE, emit(mapper, value).get(0)); + } + assertEquals("-" + FLOAT_OPTION + MAX_RATE, + emit(mapper, "99999999999999999999999999999999999999999").get(0)); + } + + /** A zero means no limit and the engine still takes it. */ + @Test + public void aRateTheEngineTakesIsSentUnchanged() { + final OptionMapper mapper = new SimpleOption(FLOAT_OPTION, true); + for (final String value : new String[] { "0", "0.0", MIN_RATE, "0.5", "5.0", + "100" }) { + assertEquals("-" + FLOAT_OPTION + value, emit(mapper, value).get(0)); + } + } + + /* The bounds live in the engine, so read them back: a moved floor must fail + here rather than ship a rate the engine panics on. */ + @Test + public void theClampedBoundsAreTheOnesTheEngineChecks() throws IOException { + final String main = TestSources + .read(TestSources.engineFile("src/htscoremain.c")); + final String gate = TestSources.balancedBlock(main, + TestSources.indexOf(main, "case 'c': {")); + assertTrue("the engine no longer bounds -" + FLOAT_OPTION + " at 1e-6", + gate.contains("v >= 1e-6f")); + assertTrue("the engine no longer bounds -" + FLOAT_OPTION + " at FLT_MAX", + gate.contains("v <= FLT_MAX")); + assertEquals(1e-6, Double.parseDouble(MIN_RATE), 0.0); + assertEquals(Float.MAX_VALUE, Double.parseDouble(MAX_RATE), 0.0); + } + @Test public void isPositiveRejectsWhatIsDigitsAccepts() { assertTrue(OptionValues.isDigits("0"));