Security Advisories (2)
CVE-2024-58134 (2025-05-03)

Mojolicious versions from 0.999922 for Perl uses a hard coded string, or the application's class name, as a HMAC session secret by default. These predictable default secrets can be exploited to forge session cookies. An attacker who knows or guesses the secret could compute valid HMAC signatures for the session cookie, allowing them to tamper with or hijack another user's session.

CVE-2024-58135 (2025-05-03)

Mojolicious versions from 7.28 for Perl may generate weak HMAC session secrets. When creating a default app with the "mojo generate app" tool, a weak secret is written to the application's configuration file using the insecure rand() function, and used for authenticating and protecting the integrity of the application's sessions. This may allow an attacker to brute force the application's session keys.

NAME

Mojo::Util - Portable utility functions

SYNOPSIS

use Mojo::Util qw(b64_encode url_escape url_unescape);

my $str = 'test=23';
my $escaped = url_escape $str;
say url_unescape $escaped;
say b64_encode $escaped, '';

DESCRIPTION

Mojo::Util provides portable utility functions for Mojo.

FUNCTIONS

Mojo::Util implements the following functions, which can be imported individually.

b64_decode

my $bytes = b64_decode $b64;

Base64 decode bytes with MIME::Base64.

b64_encode

my $b64 = b64_encode $bytes;
my $b64 = b64_encode $bytes, "\n";

Base64 encode bytes with MIME::Base64, the line ending defaults to a newline.

camelize

my $camelcase = camelize $snakecase;

Convert snake_case string to CamelCase and replace - with ::.

# "FooBar"
camelize 'foo_bar';

# "FooBar::Baz"
camelize 'foo_bar-baz';

# "FooBar::Baz"
camelize 'FooBar::Baz';

class_to_file

my $file = class_to_file 'Foo::Bar';

Convert a class name to a file.

# "foo_bar"
class_to_file 'Foo::Bar';

# "foobar"
class_to_file 'FOO::Bar';

# "foo_bar"
class_to_file 'FooBar';

# "foobar"
class_to_file 'FOOBar';

class_to_path

my $path = class_to_path 'Foo::Bar';

Convert class name to path, as used by %INC.

# "Foo/Bar.pm"
class_to_path 'Foo::Bar';

# "FooBar.pm"
class_to_path 'FooBar';

decamelize

my $snakecase = decamelize $camelcase;

Convert CamelCase string to snake_case and replace :: with -.

# "foo_bar"
decamelize 'FooBar';

# "foo_bar-baz"
decamelize 'FooBar::Baz';

# "foo_bar-baz"
decamelize 'foo_bar-baz';

decode

my $chars = decode 'UTF-8', $bytes;

Decode bytes to characters with Encode, or return undef if decoding failed.

my $value = decrypt_cookie $encrypted, 'passw0rd', 'salt';

Decrypt cookie value encrypted with "encrypt_cookie", returns the decrypted value or undef.

deprecated

deprecated 'foo is DEPRECATED in favor of bar';

Warn about deprecated feature from perspective of caller. You can also set the MOJO_FATAL_DEPRECATIONS environment variable to make them die instead with Carp.

dumper

my $perl = dumper {some => 'data'};

Dump a Perl data structure with Data::Dumper.

encode

my $bytes = encode 'UTF-8', $chars;

Encode characters to bytes with Encode.

my $encrypted = encrypt_cookie $value, 'passw0rd', 'salt';

Encrypt cookie value.

extract_usage

my $usage = extract_usage;
my $usage = extract_usage '/home/sri/foo.pod';

Extract usage message from the SYNOPSIS section of a file containing POD documentation, defaults to using the file this function was called from.

# "Usage: APPLICATION test [OPTIONS]\n"
extract_usage;

=head1 SYNOPSIS

  Usage: APPLICATION test [OPTIONS]

=cut

generate_secret

my $secret = generate_secret;

Generate a random secret with a cryptographically secure random number generator if available, and a less secure fallback if not.

getopt

getopt
  'H|headers=s' => \my @headers,
  't|timeout=i' => \my $timeout,
  'v|verbose'   => \my $verbose;
getopt $array,
  'H|headers=s' => \my @headers,
  't|timeout=i' => \my $timeout,
  'v|verbose'   => \my $verbose;
getopt $array, ['pass_through'],
  'H|headers=s' => \my @headers,
  't|timeout=i' => \my $timeout,
  'v|verbose'   => \my $verbose;

Extract options from an array reference with Getopt::Long, but without changing its global configuration, defaults to using @ARGV. The configuration options no_auto_abbrev and no_ignore_case are enabled by default.

# Extract "charset" option
getopt ['--charset', 'UTF-8'], 'charset=s' => \my $charset;
say $charset;

gunzip

my $uncompressed = gunzip $compressed;

Uncompress bytes with IO::Compress::Gunzip.

gzip

my $compressed = gzip $uncompressed;

Compress bytes with IO::Compress::Gzip.

header_params

my ($params, $remainder) = header_params 'one=foo; two="bar", three=baz';

Extract HTTP header field parameters until the first comma according to RFC 5987.

hmac_sha1_sum

my $checksum = hmac_sha1_sum $bytes, 'passw0rd';

Generate HMAC-SHA1 checksum for bytes with Digest::SHA.

# "11cedfd5ec11adc0ec234466d8a0f2a83736aa68"
hmac_sha1_sum 'foo', 'passw0rd';

html_attr_unescape

my $str = html_attr_unescape $escaped;

Same as "html_unescape", but handles special rules from the HTML Living Standard for HTML attributes.

# "foo=bar&ltest=baz"
html_attr_unescape 'foo=bar&ltest=baz';

# "foo=bar<est=baz"
html_attr_unescape 'foo=bar&lt;est=baz';

html_unescape

my $str = html_unescape $escaped;

Unescape all HTML entities in string.

# "<div>"
html_unescape '&lt;div&gt;';

humanize_bytes

my $str = humanize_bytes 1234;

Turn number of bytes into a simplified human readable format.

# "1B"
humanize_bytes 1;

# "7.5GiB"
humanize_bytes 8007188480;

# "13GiB"
humanize_bytes 13443399680;

# "-685MiB"
humanize_bytes -717946880;

md5_bytes

my $checksum = md5_bytes $bytes;

Generate binary MD5 checksum for bytes with Digest::MD5.

md5_sum

my $checksum = md5_sum $bytes;

Generate MD5 checksum for bytes with Digest::MD5.

# "acbd18db4cc2f85cedef654fccc4a4d8"
md5_sum 'foo';

monkey_patch

monkey_patch $package, foo => sub {...};
monkey_patch $package, foo => sub {...}, bar => sub {...};

Monkey patch functions into package.

monkey_patch 'MyApp',
  one   => sub { say 'One!' },
  two   => sub { say 'Two!' },
  three => sub { say 'Three!' };

network_contains

my $bool = network_contains $network, $address;

Check that a given address is contained within a network in CIDR form. If the network is a single address, the addresses must be equivalent.

# True
network_contains('10.0.0.0/8', '10.10.10.10');
network_contains('10.10.10.10', '10.10.10.10');
network_contains('fc00::/7', 'fc::c0:ff:ee');

# False
network_contains('10.0.0.0/29', '10.10.10.10');
network_contains('10.10.10.12', '10.10.10.10');
network_contains('fc00::/7', '::1');

punycode_decode

my $str = punycode_decode $punycode;

Punycode decode string as described in RFC 3492.

# "bücher"
punycode_decode 'bcher-kva';

punycode_encode

my $punycode = punycode_encode $str;

Punycode encode string as described in RFC 3492.

# "bcher-kva"
punycode_encode 'bücher';

quote

my $quoted = quote $str;

Quote string.

scope_guard

my $guard = scope_guard sub {...};

Create anonymous scope guard object that will execute the passed callback when the object is destroyed.

# Execute closure at end of scope
{
  my $guard = scope_guard sub { say "Mojo!" };
  say "Hello";
}

secure_compare

my $bool = secure_compare $str1, $str2;

Constant time comparison algorithm to prevent timing attacks. The secret string should be the second argument, to avoid leaking information about the length of the string.

sha1_bytes

my $checksum = sha1_bytes $bytes;

Generate binary SHA1 checksum for bytes with Digest::SHA.

sha1_sum

my $checksum = sha1_sum $bytes;

Generate SHA1 checksum for bytes with Digest::SHA.

# "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
sha1_sum 'foo';

slugify

my $slug = slugify $string;
my $slug = slugify $string, $bool;

Returns a URL slug generated from the input string. Non-word characters are removed, the string is trimmed and lowercased, and whitespace characters are replaced by a dash. By default, non-ASCII characters are normalized to ASCII word characters or removed, but if a true value is passed as the second parameter, all word characters will be allowed in the result according to unicode semantics.

# "joel-is-a-slug"
slugify 'Joel is a slug';

# "this-is-my-resume"
slugify 'This is: my - résumé! ☃ ';

# "this-is-my-résumé"
slugify 'This is: my - résumé! ☃ ', 1;
my $tree = split_cookie_header 'a=b; expires=Thu, 07 Aug 2008 07:07:59 GMT';

Same as "split_header", but handles expires values from RFC 6265.

split_header

my $tree = split_header 'foo="bar baz"; test=123, yada';

Split HTTP header value into key/value pairs, each comma separated part gets its own array reference, and keys without a value get undef assigned.

# "one"
split_header('one; two="three four", five=six')->[0][0];

# "two"
split_header('one; two="three four", five=six')->[0][2];

# "three four"
split_header('one; two="three four", five=six')->[0][3];

# "five"
split_header('one; two="three four", five=six')->[1][0];

# "six"
split_header('one; two="three four", five=six')->[1][1];

steady_time

my $time = steady_time;

High resolution time elapsed from an arbitrary fixed point in the past, resilient to time jumps if a monotonic clock is available through Time::HiRes.

tablify

my $table = tablify [['foo', 'bar'], ['baz', 'yada']];

Row-oriented generator for text tables.

# "foo   bar\nyada  yada\nbaz   yada\n"
tablify [['foo', 'bar'], ['yada', 'yada'], ['baz', 'yada']];

term_escape

my $escaped = term_escape $str;

Escape all POSIX control characters except for \n.

# "foo\\x09bar\\x0d\n"
term_escape "foo\tbar\r\n";

trim

my $trimmed = trim $str;

Trim whitespace characters from both ends of string.

# "foo bar"
trim '  foo bar  ';

unindent

my $unindented = unindent $str;

Unindent multi-line string.

# "foo\nbar\nbaz\n"
unindent "  foo\n  bar\n  baz\n";

unquote

my $str = unquote $quoted;

Unquote string.

url_escape

my $escaped = url_escape $str;
my $escaped = url_escape $str, '^A-Za-z0-9\-._~';

Percent encode unsafe characters in string as described in RFC 3986, the pattern used defaults to ^A-Za-z0-9\-._~.

# "foo%3Bbar"
url_escape 'foo;bar';

url_unescape

my $str = url_unescape $escaped;

Decode percent encoded characters in string as described in RFC 3986.

# "foo;bar"
url_unescape 'foo%3Bbar';

xml_escape

my $escaped = xml_escape $str;

Escape unsafe characters &, <, >, " and ' in string, but do not escape Mojo::ByteStream objects.

# "&lt;div&gt;"
xml_escape '<div>';

# "<div>"
use Mojo::ByteStream qw(b);
xml_escape b('<div>');

xor_encode

my $encoded = xor_encode $str, $key;

XOR encode string with variable length key.

SEE ALSO

Mojolicious, Mojolicious::Guides, https://mojolicious.org.