and
em => 1,
p => 1,
a => { href => qr/^http:/ }, # allow HTTP links
b => HTML::Element->new('strong'), # convert to
'*' => 0, # disallow everything else
);
=item permit(...)
Accepts a list of rules and assumes each rule will have a true
value. This allows you to be a little less verbose, since your
rule sets can look like this instead of a large data structure:
$safe->permit( qw/ strong em i b br / );
Though you're still free to include attributes and more complex
validation requirements, if you still need them:
$safe->permit( img => [ qw/ src alt / ], ... );
$safe->permit( a => { href => qr/^http:/ },
blockquote => [ qw/ cite id / ],
b => HTML::Element->new('strong'),
qw/ strong em /);
The value to each element should be an array, hash or code reference,
or an HTML::Element object, since the '=> 1' is always implied otherwise.
=item permit_only(...)
Like permit, but also assumes a default 'deny' policy. This is
equivalent to including this in your ruleset as passed to new():
'*' => undef
This will destroy any existing rule set in favor of the one you pass it.
If you would rather use a default 'ignore' policy, you could do
something like this:
$safe->permit_only(...);
$safe->ignore('*');
=item deny(...)
Like permit, but assumes each case will have a 'false' value by assuming a
'=> undef' for each element that isn't followed by an array reference.
This will cause any elements matching these rules to be stripped from
the document tree (along with any child elements). You cannot pass
a hash reference of attributes, a code reference or an HTML::Element
object as a value to an element, as in permit. If you need more complex
validation requirements, follow up with a permit() call or define them
in your call to new().
$safe->deny( a => ['href'], qw/ img object embed script style /);
=item deny_only(...)
Like deny, but assumes a default 'permit' policy. This is equivalent
to including this in your ruleset:
'*' => { '*' => 1 } # allow all elements and all attributes
This will destroy any existing rule set in favor of the one you pass it.
=item ignore(...)
Very similar to deny, this will cause a rule with an implied '=> 0' to
be created for the elements passed. Matching elements will be replaced
with their child elements, with the element itself being removed from
the document tree.
=item ignore_only(...)
Like ignore, but assumes a default 'permit' policy. See 'deny_only'.
=back
=head2 FILTER METHODS
=over 4
=item sanitize_tree($tree)
This runs the filter on a parse tree, as generated by HTML::TreeParser.
This WILL modify $tree. This function is used by the filter functions
below, so you don't have to deal with HTML::TreeParser unless you
want to.
=item filter_html($html)
Filters an HTML document using the configured rule set.
=item filter_html_fragment($html)
Filters an HTML fragment. Use this if you're filtering a chunk of
HTML that you're going to end up using within an existing document.
(In other words, it operates on $html as if it were a complete document,
but only ends up working on children of the tag.)
=item filter_xml($xml)
=item filter_xml_fragment($xml)
Like above, but operates on the data as though it were well-formed XML.
Use this if you intend on providing XHTML, for example.
=back
When the above functions encounter an attribute they're meant to filter,
the attribute will be deleted from the element, but the element will
survive. If you need to delete the entire element if an attribute
doesn't pass validation, set up a coderef for the element in your rule
set and use L methods to manipulate the element (e.g. by
calling C<$element->delete> or C<$element->replace_with_content> if
C<$element->attr('href')> doesn't pass muster.)
=head1 RULE SETS
A rule set is simply a list of elements and/or attributes and values
indicating whether those elements/attributes will be allowed, ignored,
or stripped from the parse tree. Generally rule sets should be passed
to new() at object creation time, though they can also be built piecemeal
through calls to permit, deny and/or ignore as described above.
Each element in the list should be followed by one of the following:
=over 4
=item a 'true' value
This indicates the element should be permitted as-is with no filtering
or modification (aside from any other filtering done to child elements).
=item 0
If a zero (or some other defined, false value) is given, the element
itself is deleted but child elements are brought up to replace it.
Use this when you wish to filter a bad formatting tag while preserving
the text it was formatting, for example.
=item undef
If an undef is given, the element and all of its children will be deleted.
This would remove a scripting tag and all of its contents from the
document tree, for example.
=item an HTML::Element object
A copy of this object will replace the element matching the rule.
The attributes in the replacement object will overlay the attributes of
the original object (after attribute filtering has been done through
the _ rule). If this element contains any child elements, they will
replace the children of the element fitting the rule. If you wish
to delete the content without necessarily providing any replacement,
create a child that's simply an empty text node.
=item a code reference
This would permit the element if, and only if, the coderef returned a
true value. The HTML::Element object in question is passed as the first
and only argument.
=item a hash reference
This implies the element itself is OK, but that some additional checking
of its attribute list is needed. This hash reference should contain
keys of attributes and values that in turn should be one of:
=over 4
=item a 'true' value
This would preserve the attribute.
=item a 'false' value
This would delete the attribute.
=item a regular expression
This would preserve the attribute if the regular expression matched.
=item a code reference
This would permit the attribute if and only if the coderef returned
a true value. The HTML::Element object, the attribute name and
attribute value are passed as arguments. $_ is also set to the
attribute value (which can be modified).
=back 4
=back 4
=head2 EXAMPLES
Here is a sample rule set, which might do a fair job at stripping out
potentially dangerous tags, though I put this together without too much
thought, so I wouldn't rely on it:
'script' => undef,
'style' => undef,
'*' => {
onclick => 0,
ondblclick => 0,
onselect => 0,
onmousedown => 0,
onmouseup => 0,
onmouseover => 0,
onmousemove => 0,
onmouseout => 0,
onfocus => 0,
onblur => 0,
onkeypress => 0,
onkeydown => 0,
onkeyup => 0,
onselect => 0,
onload => 0,
onunload => 0,
onerror => 0,
onsubmit => 0,
onreset => 0,
onchange => 0,
style => 0,
href => qr/^(?!(?:java)?script)/,
src => qr/^(?!(?:java)?script)/,
cite => sub { !/^(?:java)?script/ }, # same thing, mostly
'*' => 1,
},
'link' => {
rel => sub { not_member("stylesheet", @_) },
},
'object' => 0, # strip but let children show through
'embed' => undef,
'iframe' => undef,
'frameset' => undef,
'frame' => undef,
'applet' => undef,
'noframes' => 0,
'noscript' => 0,
# use a function like this to do some additional validation:
sub not_member { !/\b\Q$_[0]\E\b/i; } # maybe substitute it out instead
A web site incorporating user posts might want something a little more
strict:
em => 1,
strong => 1,
p => 1,
ol => 1,
ul => 1,
li => 1,
tt => 1,
a => 1,
img => 1,
span => 1,
blockquote => { cite => 1 },
_ => { # for all tags above, these attribute rules apply:
href => qr/^(?:http|ftp|mailto|sip):/i,
src => qr/^(?:http|ftp|data):/i,
title => 1,
# Maybe add an x- prefix to all ID's to avoid collisions
id => sub { $_ = "x-$_" },
xml:lang => 1,
lang => 1,
* => 0,
},
'*' => 0, # everything else is 'ignored'
script => undef, # except these, which are stripped along with children
style => undef,
Note the use of the _ element here, which is magic in that it allows you
to set up some global attributes while still leaving the * element free
to express a default 'deny' policy. The attributes specified here will
be applied to all of the explicitly defined elements (em, strong, etc.),
but they will not be applied to elements not present in the ruleset.
Attribute rule precedence goes from the tag-specific, the special "_" tag
and then the special "*" tag.
The following might be a simple way to force a 'b' tag to become a
'strong' tag, with the text within it surviving:
b => HTML::Element->new('strong');
Here's how you might strip out a 'script' tag while letting the user
know something is up:
script => HTML::Element
->new('p', class => 'script_warning')
->push_content("Warning: A