Skip to content

A param option's value is glued onto its short form without being checked - #1427

Merged
xroche merged 3 commits into
masterfrom
fix-1426
Aug 25, 2026
Merged

A param option's value is glued onto its short form without being checked#1427
xroche merged 3 commits into
masterfrom
fix-1426

Conversation

@xroche

@xroche xroche commented Aug 25, 2026

Copy link
Copy Markdown
Owner

The param class glued a long option's value onto its short form without looking at it. A value the short form cannot read converts short or not at all, so the option keeps a value nobody asked for, and the tail spills into the cluster loop as more short options. --long-names=yes and --long-names=none mirror in DOS 8.3 names, which is what --long-names=0 means, and --sockets=8I0 mirrors with no top index.html because the I0 reached -I0. Both exit 0. A param value is now a bounded digit run, the on/off mapping the class already had, or one separator with a digit after it where the parser reads one (-m takes N,N2, -%c a rate with a decimal point). Anything else draws the refusal the other classes have used since #1195, across all 27 param rows, asserted table-wide in the optalias self-test so a new row is covered the day it is added.

Refusal rather than a warning, because carrying on does not preserve the run: the value's characters turn other options on, so the warning would be followed by a wrong mirror under exit 0, which a script cannot see. Refusal is not a new outcome here either, since an uppercase junk value already exits 255 on every one of these rows, OFF reaching -O, with a message naming an option nobody typed. --cache=OFF now says Option --cache does not take the value OFF. Along the way: --cache's help string was --retries's, and 19 of these rows carry no help string at all, whose dangling tab read as a truncated message.

--structure is out of scope, having its own paramn class since #1418, which this branch is merged onto. Three gaps stay open behind that class, all pre-existing and all surviving #1418, which keeps 8I0-shaped gluing by design: --structure=I0 is byte-identical to -I0, exit 0 with index.html and the two GIFs silently missing; --structure=on <URL> maps to a bare -N, which eats the next argument, so the crawl mirrors nothing and still exits 0; and --long-names= with an empty value is the bare -L, which is 8.3, so the headline symptom stays reachable by that spelling.

Closes #1426

xroche and others added 3 commits August 25, 2026 07:41
The "param" class glued a long option's value onto its short form without
checking it. A value that is not a number converts to nothing, so the
option keeps its default, and the leftover characters reach the cluster
loop as more short options: --long-names=yes writes DOS 8.3 names and
--sockets=8I0 mirrors with no top index, both exit 0.

A param value is now a bounded digit run, the on/off mapping the class
already had, or the extra character a given parser reads (-m takes N,N2,
-%c a rate with a decimal point). Anything else draws the same refusal
the other classes have used since #1195. --structure is left out, being
the one param row that also reads a user template.

Also fixes --cache's help string, which was --retries', so the new
message quoted the wrong option.

Closes #1426

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
The gate admitted the comma and the dot anywhere in the value, so
--connection-per-second=1.2.3 passed it and reached maxconn as 1.2, the
silent-wrong-value class this change is about. Both are now one
occurrence with a digit after it, which also refuses --max-files=100,
and --max-files=,,,5 at the gate instead of downstream.

362's --structure case asserted a falsehood: the glued template exits 0
because the word is swallowed as a URL, not because it applied. The
exemption is now pinned in the optalias self-test, which sees the
expansion, and 362 asserts only that the rule does not refuse it.

Renamed the test into the *_engine-* glob so the Windows suite runs it;
it has no skip path, so the suite's expected-skip set is unchanged.
--cache=off/on now discriminates on the cache directory, so an inverted
on/off mapping reds 362 on its own terms. 19 param rows carry no help
string, whose dangling tab read as a truncated message; the four syntax
errors now drop the line when there is nothing to put on it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
#1418 gave --structure its own "paramn" class, so the -N guard this
branch carried to spare it is gone: the rule is scoped to "param",
which is now exactly the 27 rows the issue lists. Both sides' helpers
sit side by side, and #1418's own refusal drops its dangling help tab
the way the other four syntax errors here do.

362 tests the class boundary where it is real: --structure=1L0 is a
preset trailed by more short options, which the param rule would
refuse, and whose -L0 tail shows on disk. The self-test's --structure
assertions are dropped as duplicates of #1418's own block.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
@xroche
xroche enabled auto-merge (squash) August 25, 2026 06:43
@xroche
xroche merged commit 1a5a8d2 into master Aug 25, 2026
44 checks passed
@xroche
xroche deleted the fix-1426 branch August 25, 2026 07:53
xroche added a commit that referenced this pull request Aug 25, 2026
Two of the three spellings in #1434 mirrored the wrong thing at exit 0,
both because the value mapped onto the bare short form.

`--structure=on` expanded to a bare `-N`, which reads the next word as a
user template, so `--structure=on <URL>` swallowed the URL and mirrored
nothing. "on" now names the default preset (`-N0`), the layout "off"
already selected; #1418's glue rules are otherwise untouched.

`--long-names=` expanded to a bare `-L`, whose `sscanf` converted
nothing and left the switch to re-map the value already stored, landing
on DOS 8.3 names (`-L1 -L` did the same). Rather than refuse the empty
value I fixed the short form: a bare `-L` now selects L1, the default
the help and man page already star. Refusing would have taken `sockets=`
and the config-file `long-names=` spelling with it, and this way the
empty value means the option's own default. `-L0`, `--long-names=off`
and `--long-names=2` are unchanged.

Rewriting that parse surfaced a third bug, pre-existing and folded in
here: the digit-run bound #1418 added counts digits rather than reading
the value, so a zero-padded run falls off it. `-L0000000001` selected
ISO9660, `-N0000000001` lost its preset, and `--structure=0000000001`
was refused. `optalias_digits_fit` skips the leading zeros first, shared
by the four sites that read such a run.

The 27 `param` rows keep #1427's 16-character cap: its bound is a
magnitude, not a run length, so `optalias_digits_fit` does not fit there
— 9 significant digits would refuse `--advanced-maxlinks=2000000000`,
and it cannot see the second operand of `-m N,N2`. Filed as #1437.

The third spelling in the issue, `--structure=I0`, has been refused
since #1418 landed after the issue was measured, and test 368 pins that.
What survives is `--structure=8I0`: the digits-then-cluster form #1418
glues on purpose, and the same shape as the `--structure=1L0` that
#1427's test pins as accepted. No rule separates the two, so that one
wants a decision rather than a patch.

Closes #1434

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
xroche added a commit that referenced this pull request Aug 25, 2026
#1439)

`optalias_param_takes()` (#1427) bounded a `param` row's value at 16
characters, which is a length where a magnitude is meant. It refused
`--depth=0000000000000000001` for its padding alone, and accepted
`--sockets=1234567890123456`, sixteen digits that `sscanf("%d")` then
read as 1015724736 simultaneous connections. Each digit run is now
converted with `strtoll` and range-checked against what the option's own
parser holds: `INT_MAX` for the rows reading an `int`, `INT64_MAX` for
`-m`, `-M` and `-G` on their `LLint` and for `-%c`, whose float takes
any run `strtoll` converts. The check runs per operand, so the second
half of `-m N,N2` and `-%c 0.5` is covered too. `optalias_suffix()`
carried a copy of the same bare 16, so the six `level` rows get the same
treatment.

The behaviour change is the fix rather than a side effect of it, and it
goes both ways. Refused from now on: a value on an int-valued long form
whose digit run exceeds 2147483647 and which the old cap let through, so
no longer than 16 characters. `--sockets=1234567890123456`,
`--max-rate=3000000000`, `--depth=2147483648`, `--timeout=99999999999`,
`--generate-errors=3000000000`, and every value of that shape on the
other int rows. All of them were undefined at `sscanf("%d")` and wrapped
in practice: 1015724736 sockets, a rate of -1294967296, a depth of
-2147483648, a timeout of 1215752191 seconds. None landed on a sane
value by accident either, since `-c` clamps its wrapped negative back to
one socket, which is still not what was asked for.

Accepted from now on: a zero-padded value wherever its magnitude fits,
and on `-m`, `-M`, `-G` and `-%c` any 17-to-19-digit run up to
`INT64_MAX`. `--max-size=9223372036854775807` is refused on master and
taken here, which is right, because that is what the `LLint` behind it
holds.

The old cap was also the only thing keeping a value short enough to glue
into the 1024-byte expansion buffer, where `strlcatbuff` aborts rather
than clips. So a length bound stays, at the glue site and sized to the
room the short form leaves: without it `httrack --depth $(printf '0%.0s'
$(seq 1022))` exits 134 with a backtrace where master printed a syntax
error. It now also reaches a class the old cap never covered, the
`param0` rows, where `httrack --allow <1023 chars>` aborts on master and
draws the same syntax error here.

369 asserts both directions, through the engine's own expansion and on
disk, and reds on six mutants: the length cap put back,
`optalias_run_fits` forced true or stripped of its `max`, the `LLint`
arm narrowed to `-M` and `-%c`, a leading sign accepted, the capacity
guard removed, and `optalias_suffix` reverted. `st_optalias`'s
table-wide loop carries the padding, `INT_MAX` and sign probes across
all 27 `param` rows.

Closes #1437

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A param option's value is glued into a short-option cluster without being checked

1 participant