Skip to content

fix: HTTP::Tiny redirect following and binary-safe mirror downloads#472

Merged
fglock merged 5 commits intomasterfrom
fix/http-tiny-redirect-mirror
Apr 9, 2026
Merged

fix: HTTP::Tiny redirect following and binary-safe mirror downloads#472
fglock merged 5 commits intomasterfrom
fix/http-tiny-redirect-mirror

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 9, 2026

Summary

  • Redirect following: Enable HttpClient.Redirect.NORMAL so HTTP 301/302/303/307/308 redirects are followed automatically. This fixes jcpan failing to fetch from CPAN mirrors that redirect (e.g. cpan.orgwww.cpan.org).
  • Status reason map: Expand getStatusReason() to cover all common HTTP status codes instead of returning "Unknown Status" for redirects and other codes.
  • Binary-safe mirror: Rewrite mirror() to use BodyHandlers.ofByteArray() instead of delegating to request() which uses ofString(). The string-based path corrupted binary downloads (.tar.gz files) by decoding bytes as UTF-8 then re-encoding with getBytes().
  • Response URL: Add url field to response hash reflecting the final URL after redirects.
  • Error handling: Add proper 599 error response in mirror() on IOException, matching Perl HTTP::Tiny behavior.

Test plan

  • make — all unit tests pass
  • ./jperl -e 'use HTTP::Tiny; ...' — 301 redirect from cpan.org now returns status=200
  • ./jcpan -t Image::ExifTool — downloads, checksums, configures, and runs full ExifTool test suite

Generated with Devin

fglock and others added 3 commits April 9, 2026 17:28
- Enable HttpClient.Redirect.NORMAL so 301/302/303/307/308 redirects
  are followed automatically (fixes jcpan failing to fetch from CPAN
  mirrors that redirect, e.g. cpan.org -> www.cpan.org)

- Expand getStatusReason() to cover all common HTTP status codes
  instead of returning "Unknown Status" for redirects

- Rewrite mirror() to use BodyHandlers.ofByteArray() instead of
  delegating to request() which uses ofString(). The string-based
  path corrupted binary downloads (.tar.gz files) by decoding bytes
  as UTF-8 then re-encoding with getBytes()

- Add url field to response hash (final URL after redirects)

- Add proper 599 error response in mirror() on IOException, matching
  Perl HTTP::Tiny behavior

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…flag contamination

This fixes binary data corruption in Image::ExifTool write operations where
UTF-8 flagged strings concatenated with byte buffers would cause the output
buffer to lose its BYTE_STRING type, leading to double-encoding on re-read.

Key changes:
- JVM backend .= (concat-assign): Use setPreservingByteString() instead of
  set() to maintain BYTE_STRING type when target was byte string and result
  fits in Latin-1
- Bytecode interpreter .= : Same BYTE_STRING preservation logic
- RuntimeScalar.setPreservingByteString(): New method for type-preserving
  assignment used by both backends
- RuntimeSubstrLvalue.set(): Preserve BYTE_STRING when parent is byte string
- Operator.java 4-arg substr: Return BYTE_STRING when original was BYTE_STRING
- is_utf8/_utf8_on/_utf8_off: Only operate on STRING type (not INTEGER/DOUBLE)
- Unpack format handlers: Set BYTE_STRING type for binary output
- RuntimeHash key creation: Use BYTE_STRING for ASCII-only keys
- StringOperators.stringConcat: Produce BYTE_STRING when both operands are
  non-UTF8 and result fits in Latin-1
- SprintfOperator: Produce BYTE_STRING for %c with byte values

ExifTool test improvements:
- PNG: 6/7 -> 7/7, IPTC: 7/8 -> 8/8, XMP: 53/54 -> 54/54
- Writer: 53/61 -> 60/61, GIF: 2/5 -> 5/5
- CanonRaw, FujiFilm, MIE, Nikon: all now pass fully

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…al'd code

When `split` was called without an explicit string argument (e.g., bare
`split` or `split /PAT/`), it relied on the runtime fallback to
getGlobalVariable("main::_"). This worked in JVM-compiled code but failed
in bytecode-interpreted code (eval'd strings because the bytecode compiler
compiled the empty argument list in scalar context, producing a spurious
value that was then treated as the string to split.

Fix: In parseSplit(), when no string argument is provided, explicitly add
-A as the default argument in the AST. This is consistent with how other
operators (defined, print, chomp) handle the -A default, and ensures both
JVM and bytecode backends correctly resolve -A at runtime.

This fixes ExifTool Writer test 6 (TimeCodes encoding), Geotag tests
2/4/9/10 (GPX parsing), and Geolocation test 5 (GPS track processing),
all of which used bare  inside eval'd code strings.

ExifTool test results: 113/113 files pass, 600/600 tests pass (100%).

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
EOF
)
@fglock fglock force-pushed the fix/http-tiny-redirect-mirror branch from d6d86ea to 86baa2f Compare April 9, 2026 15:30
fglock and others added 2 commits April 9, 2026 17:56
…ide effects

In stringConcat(), b.toString() may trigger FETCH on a tied variable,
which can modify the left operand (runtimeScalar) as a side effect.
The previous code cached aStr = runtimeScalar.toString() before calling
b.toString(), using a stale value. Now we read b first, matching Perl
behavior where the left SV reflects modifications from tied-var FETCHes.

Fixes regression in opbasic/concat.t test 253 (RT #132595).

Note: op/lexsub.t test 99 shows a -1 change, but this is a false
regression. The old is_utf8() incorrectly returned true for undef
(checking type != BYTE_STRING), which masked a pre-existing bug where
my sub in eval does not return a proper coderef. The new is_utf8()
correctly returns false for undef, matching Perl behavior.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…wraps with CREATE_REF

The bytecode compiler's backslash operator had a special case to avoid
wrapping CODE values with CREATE_REF, but it only matched \&name where
the operand was an IdentifierNode (regular package subs). For lexical
subs (my sub), the AST has &($hidden_var) where the operand is an
OperatorNode, which fell through to the generic path that applied
CREATE_REF, producing REF(CODE) instead of CODE.

The fix broadens the special case to match any \& expression, since
the & operator already produces a CODE value via CODE_DEREF_NONSTRICT.
This matches the JVM backend's createCodeReference behavior.

Fixes lexsub.t tests 99-100 (my sub with UTF-8 prototype).

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock merged commit 277545b into master Apr 9, 2026
2 checks passed
@fglock fglock deleted the fix/http-tiny-redirect-mirror branch April 9, 2026 16:47
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.

1 participant