From a93b37fa65b6dba277b44ec1adf50a69c7c3ed3a Mon Sep 17 00:00:00 2001 From: Toddr Bot Date: Thu, 9 Apr 2026 12:42:25 +0000 Subject: [PATCH 1/4] Add b64url_digest method for URL-safe base64 digests Adds b64url_digest() to Digest::base, returning the digest using the URL-safe base64 alphabet (- instead of +, _ instead of /) without padding, per RFC 4648 section 5. This is a convenience shortcut for the common pattern of encoding a digest for use in URLs and filenames. Closes #2 Co-Authored-By: Claude Opus 4.6 --- lib/Digest.pm | 13 +++++++++++++ lib/Digest/base.pm | 9 ++++++++- t/base.t | 45 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/lib/Digest.pm b/lib/Digest.pm index b62ef64..9369854 100644 --- a/lib/Digest.pm +++ b/lib/Digest.pm @@ -129,6 +129,12 @@ representation of the digest with any trailing padding removed. The string will be about 30% longer than the binary version. L tells you more about this encoding. +=item I + +Same as base64, but using the URL-safe alphabet (C<-> instead of C<+>, +C<_> instead of C) as defined in RFC 4648, section 5. This variant +is safe for use in URLs and filenames without additional encoding. + =back @@ -266,6 +272,13 @@ Same as $ctx->digest, but will return the digest in hexadecimal form. Same as $ctx->digest, but will return the digest as a base64 encoded string without padding. +=item $ctx->b64url_digest + +Same as $ctx->b64digest, but will return the digest using the +URL-safe base64 encoding (C<-> instead of C<+>, C<_> instead of C) +without padding. This is useful for embedding digests in URLs and +filenames. See L. + =item $ctx->base64_padded_digest Same as $ctx->digest, but will return the digest as a base64 encoded diff --git a/lib/Digest/base.pm b/lib/Digest/base.pm index 539559b..e8bb011 100644 --- a/lib/Digest/base.pm +++ b/lib/Digest/base.pm @@ -64,6 +64,13 @@ sub b64digest { return $b64; } +sub b64url_digest { + my $self = shift; + my $b64 = $self->b64digest(@_); + $b64 =~ tr[+/][-_]; + return $b64; +} + sub base64_padded_digest { my $self = shift; require MIME::Base64; @@ -87,7 +94,7 @@ Digest::base - Digest base class The C class provide implementations of the methods C and C in terms of C, and of the methods -C and C in terms of C. +C, C, and C in terms of C. Digest implementations might want to inherit from this class to get this implementations of the alternative I and I methods. diff --git a/t/base.t b/t/base.t index 153d4d4..0b92109 100644 --- a/t/base.t +++ b/t/base.t @@ -3,7 +3,7 @@ use strict; use warnings; -use Test::More tests => 13; +use Test::More tests => 18; use File::Temp 'tempfile'; @@ -86,3 +86,46 @@ is( $ctx->digest, "a0002" ); $ctx->add_bits( "abc", 32 ); is( $ctx->digest, "a0003" ); + +# b64url_digest - no +/ chars in this digest, so same as b64digest +$ctx->add("foo"); +is( $ctx->b64url_digest, $EBCDIC ? "hvDw8PM" : "ZjAwMDM", + "b64url_digest with no special chars" ); + +# b64url_digest with binary data containing + and / +{ + package BinDigest; + require Digest::base; + our @ISA = qw(Digest::base); + + sub new { + my $class = shift; + my $data = shift || ""; + bless \$data, $class; + } + + sub add { + my $self = shift; + $$self .= join("", @_); + return $self; + } + + sub digest { + my $self = shift; + my $d = $$self; + $$self = ""; + return $d; + } +} + +# \x03\xe0\x00 encodes to "A+AA" in standard base64 +my $bin = BinDigest->new("\x03\xe0\x00"); +is( $bin->b64digest, "A+AA", "b64digest with + char" ); +$bin = BinDigest->new("\x03\xe0\x00"); +is( $bin->b64url_digest, "A-AA", "b64url_digest translates + to -" ); + +# \xff\xff\xfe encodes to "///+" in standard base64 +$bin = BinDigest->new("\xff\xff\xfe"); +is( $bin->b64digest, "///+", "b64digest with / and + chars" ); +$bin = BinDigest->new("\xff\xff\xfe"); +is( $bin->b64url_digest, "___-", "b64url_digest translates / to _ and + to -" ); From a6e19da54210af6c4da94f29a3b3d0cac0b9994d Mon Sep 17 00:00:00 2001 From: Toddr Bot Date: Thu, 9 Apr 2026 12:44:22 +0000 Subject: [PATCH 2/4] Add digest_file_base64url to Digest::file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion to b64url_digest — provides the same URL-safe base64 output format through the file convenience API. Co-Authored-By: Claude Opus 4.6 --- lib/Digest/file.pm | 13 +++++++++++-- t/file.t | 12 ++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/Digest/file.pm b/lib/Digest/file.pm index 088fabf..09280a1 100644 --- a/lib/Digest/file.pm +++ b/lib/Digest/file.pm @@ -9,7 +9,7 @@ use Digest (); our $VERSION = "1.20"; our @ISA = qw(Exporter); -our @EXPORT_OK = qw(digest_file_ctx digest_file digest_file_hex digest_file_base64); +our @EXPORT_OK = qw(digest_file_ctx digest_file digest_file_hex digest_file_base64 digest_file_base64url); sub digest_file_ctx { my $file = shift; @@ -34,6 +34,10 @@ sub digest_file_base64 { digest_file_ctx(@_)->b64digest; } +sub digest_file_base64url { + digest_file_ctx(@_)->b64url_digest; +} + 1; __END__ @@ -52,7 +56,7 @@ Digest::file - Calculate digests of files =head1 DESCRIPTION -This module provide 3 convenience functions to calculate the digest +This module provides convenience functions to calculate the digest of files. The following functions are provided: =over @@ -76,6 +80,11 @@ Same as digest_file(), but return the digest in hex form. Same as digest_file(), but return the digest as a base64 encoded string. +=item digest_file_base64url( $file, $algorithm, [$arg,...] ) + +Same as digest_file(), but return the digest using URL-safe base64 +encoding (C<-> instead of C<+>, C<_> instead of C) without padding. + =back =head1 SEE ALSO diff --git a/t/file.t b/t/file.t index 48ef39d..a8d5bac 100644 --- a/t/file.t +++ b/t/file.t @@ -3,7 +3,7 @@ use strict; use warnings; -use Test::More tests => 5; +use Test::More tests => 6; use File::Temp 'tempfile'; @@ -32,7 +32,7 @@ use File::Temp 'tempfile'; } } -use Digest::file qw(digest_file digest_file_hex digest_file_base64); +use Digest::file qw(digest_file digest_file_hex digest_file_base64 digest_file_base64url); { my ( $fh, $file ) = tempfile( UNLINK => 1 ); @@ -50,6 +50,14 @@ use Digest::file qw(digest_file digest_file_hex digest_file_base64); is( digest_file_hex( $file, "Foo" ), "30303035" ); is( digest_file_base64( $file, "Foo" ), "MDAwNQ" ); } + + # digest_file_base64url uses URL-safe alphabet + if ( ord('A') == 193 ) { # EBCDIC. + is( digest_file_base64url( $file, "Foo" ), "8PDw9Q" ); + } + else { + is( digest_file_base64url( $file, "Foo" ), "MDAwNQ" ); + } } ok !eval { digest_file( "not-there.txt", "Foo" ) }; From c88d8fa6fe064b22682bbaa12a3583002c8cd68d Mon Sep 17 00:00:00 2001 From: Toddr Bot Date: Sat, 11 Apr 2026 12:28:23 +0000 Subject: [PATCH 3/4] ci: fix broken test matrix and update actions Docker Hub deprecated Image Format v1, breaking pulls for perl:5.8 through perl:5.24 containers on GitHub Actions. This caused all CI runs (including main branch) to fail on those versions. - Remove Perl 5.8-5.24 from matrix (Docker images no longer pullable) - Add Perl 5.34, 5.36, 5.38, 5.40 to matrix - Update actions/checkout from v2 to v4 The module still supports Perl 5.006+ at runtime; only CI coverage of versions older than 5.26 is affected by this infrastructure change. Supersedes #14 and #15. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/testsuite.yml | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml index 3cd0e3e..aae57d9 100644 --- a/.github/workflows/testsuite.yml +++ b/.github/workflows/testsuite.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - run: perl -V - name: install dependencies uses: perl-actions/install-with-cpm@v1 @@ -46,26 +46,21 @@ jobs: matrix: perl-version: [ + "5.40", + "5.38", + "5.36", + "5.34", "5.32", "5.30", "5.28", "5.26", - "5.24", - "5.22", - "5.20", - "5.18", - "5.16", - "5.14", - "5.12", - "5.10", - "5.8", ] container: image: perl:${{ matrix.perl-version }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - run: perl -V - name: install dependencies uses: perl-actions/install-with-cpm@v1 @@ -93,7 +88,7 @@ jobs: perl-version: [latest] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - run: perl -V - name: install dependencies uses: perl-actions/install-with-cpm@v1 From 124641c1c8fa25e6ba3447ebe035629f5f72230c Mon Sep 17 00:00:00 2001 From: Toddr Bot Date: Wed, 22 Apr 2026 12:44:02 +0000 Subject: [PATCH 4/4] ci: modernize CI matrix and add Windows testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidates CI improvements into a single change: - Remove Perl 5.8–5.24 from matrix (Docker Hub deprecated Image Format v1) - Add Perl 5.34, 5.36, 5.38, 5.40 to linux matrix - Update actions/checkout v2 → v4 (Node.js 12 → 20) - Add Windows CI job using Strawberry Perl Supersedes #14, #15, #18, and #20. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/testsuite.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml index aae57d9..23e101e 100644 --- a/.github/workflows/testsuite.yml +++ b/.github/workflows/testsuite.yml @@ -72,6 +72,37 @@ jobs: - name: make test run: make test + windows: + needs: [ubuntu] + env: + PERL_USE_UNSAFE_INC: 0 + AUTHOR_TESTING: 1 + AUTOMATED_TESTING: 1 + RELEASE_TESTING: 1 + + runs-on: windows-latest + + strategy: + fail-fast: false + matrix: + perl-version: [latest] + + steps: + - uses: actions/checkout@v4 + - name: Set up Perl + uses: shogo82148/actions-setup-perl@v1 + with: + perl-version: ${{ matrix.perl-version }} + - run: perl -V + - name: install dependencies + uses: perl-actions/install-with-cpm@v1 + with: + cpanfile: "cpanfile" + - name: Makefile.PL + run: perl Makefile.PL + - name: make test + run: gmake test + macOS: needs: [ubuntu] env: