The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

use strict;
use Exporter 'import';
use HTML::Escape ();
use Scalar::Util ();
use Carp;
bool => sub { shift->to_bool },
'""' => sub { shift->to_string },
'+' => sub {
my ($self, $other, $reverse) = @_;
croak "Can only join two safe string objects" unless (ref($other) eq ref($self));
return $reverse
? raw("${other}${self}")
: raw("${self}${other}");
},
fallback => 1;
our @EXPORT_OK = qw(raw flattened_raw safe flattened_safe is_safe escape_html safe_concat concat);
our %EXPORT_TAGS = (all => \@EXPORT_OK, core => ['raw', 'safe', 'escape_html', 'safe_concat']);
sub _make_safe {
my $string_to_make_safe = shift;
return bless(\$string_to_make_safe, 'Valiant::HTML::SafeString');
}
sub escape_html {
my $string = shift;
return HTML::Escape::escape_html($string);
}
sub raw {
if(scalar(@_) == 1) {
return is_safe($_[0]) ? $_[0] : _make_safe($_[0])
} else {
return map { is_safe($_) ? $_ : _make_safe($_) } @_;
}
}
sub flattened_raw {
my $string = join '', map { is_safe($_) ? $_->to_string : $_ } @_;
return _make_safe $string;
}
sub is_safe {
my $string_to_test = shift;
return 0 unless defined($string_to_test); # must be defined
return 0 unless Scalar::Util::blessed($string_to_test); # must be a blessed object
return 1 if $string_to_test->isa('Valiant::HTML::SafeString'); # must be a blessed object of the right class
return 0;
}
sub safe {
if(scalar(@_) == 1) {
return is_safe($_[0]) ? $_[0] : _make_safe(escape_html($_[0]))
} else {
return map { is_safe($_) ? $_ : _make_safe(escape_html($_)) } @_;
}
}
sub safe_concat { return flattened_safe(@_) }
sub flattened_safe {
my $string = join '', map { is_safe($_) ? $_->to_string : escape_html($_) } grep { defined($_) } @_;
return _make_safe $string;
}
sub new {
my $class = shift;
return flattened_safe(@_);
}
sub concat { return flattened_safe(@_) }
sub to_string { return ${$_[0]} }
sub to_bool { return ${$_[0]} ? 1 : 0 }
sub append {
my ($self, @rest) = @_;
my $string = join '', map { is_safe($_) ? $_->to_string : escape_html($_) } grep { defined($_) } @rest;
${$self} .= $string;
}
1;
=head1 NAME
Valiant::HTML::SafeString - String rendering safety
=head1 SYNOPSIS
use Valiant::HTML::SafeString 'safe', 'escape';
=head1 DESCRIPTION
Protecting your templates from the various types of character injection attacks is
a prime concern for anyone working with the HTML user interface. This class provides
some methods and exports to make this job easier.
=head1 EXPORTABLE FUNCTIONS
The following functions can be exported by this library
=head2 safe
Given a string or array, returns such marked as 'safe' by using C<html_escape> on the string and
then encapsulating it inside an instance of L<Valiant::HTML::SafeString>. You can safely pass arguments
to this since if the string is already marked safe we just return it unaltered.
=head2 flattened_safe
Same as C<safe> but always returns a string even if you pass an array of strings (they are all
joined together).
=head2 raw
Given a string or array of strings, return each marked as safe (by encapsulating it inside an
instance of L<Valiant::HTML::SafeString>. This will just mark strings as safe without doing any
escaping first (for that see C<safe>) so be careful with this.
=head2 flattened_raw
Same as C<raw> but always returns a string even if you pass an array of strings (they are all
joined together).
=head2 is_safe
Given a string return a boolean indicating if its marked safe or not. Since C<safe> and C<raw> never
double the escapulations / escaping, you probably never need this but saw no reason to not expose it.
=head2 escape_html
A wrapper on L<HTML::Escape> just to make your life a bit easier
=head1 CLASS METHODS
This package exposes the folllowing class methods
=head2 new
my $safe_string = Valiant::HTML::SafeString->new(@strings);
Given a string, or array of strings, returns a single string that has been C<html_escape>'d as needed
and encapulated in an instance. Its safe to pass arguments to this without testing since if a string
is already marked safe we don't do any extra escaping (although you will get a new instance).
=head1 INSTANCE METHODS
Instances of L<Valiant::HTML::SafeString> expose the following public methods
=head2 concat
Returns a new safe string which appends a list of strings to the old one, making those new strings
'safe' as needed. Basically this will escape any strings not marked safe already and then joins them
altogether in a single safe string.
=head2 to_string
Returns the raw string, suitable for display.
=head2 to_bool
Returns a boolean indicating if the string is empty or not.
=head1 OVERLOADING
String context calles C<to_string>; Boolean context returns true unless the string is empty.
=head1 SEE ALSO
L<Valiant>, L<Valiant::HTML::FormBuilder>
=head1 AUTHOR
See L<Valiant>
=head1 COPYRIGHT & LICENSE
See L<Valiant>
=cut