package Valiant::HTML::SafeString;
use warnings;
use strict;
use Exporter 'import';
use HTML::Escape ();
use Scalar::Util ();
use Carp;
use overload
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 on the string and
then encapsulating it inside an instance of L. 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 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. This will just mark strings as safe without doing any
escaping first (for that see C) so be careful with this.
=head2 flattened_raw
Same as C 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 and C 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 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'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 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; Boolean context returns true unless the string is empty.
=head1 SEE ALSO
L, L
=head1 AUTHOR
See L
=head1 COPYRIGHT & LICENSE
See L
=cut