|
Lines 1-94
Link Here
|
| 1 |
#!/usr/bin/perl |
1 |
#!/usr/bin/perl |
| 2 |
|
2 |
|
|
|
3 |
# Copyright 2025 Koha Development team |
| 4 |
# |
| 5 |
# This file is part of Koha |
| 6 |
# |
| 7 |
# Koha is free software; you can redistribute it and/or modify it |
| 8 |
# under the terms of the GNU General Public License as published by |
| 9 |
# the Free Software Foundation; either version 3 of the License, or |
| 10 |
# (at your option) any later version. |
| 11 |
# |
| 12 |
# Koha is distributed in the hope that it will be useful, but |
| 13 |
# WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 15 |
# GNU General Public License for more details. |
| 16 |
# |
| 17 |
# You should have received a copy of the GNU General Public License |
| 18 |
# along with Koha; if not, see <http://www.gnu.org/licenses> |
| 19 |
|
| 3 |
use Modern::Perl; |
20 |
use Modern::Perl; |
| 4 |
|
21 |
|
| 5 |
$| = 1; |
22 |
use Test::More tests => 7; |
| 6 |
use Test::NoWarnings; |
23 |
use Test::NoWarnings; |
| 7 |
use Test::More tests => 27; |
24 |
use Test::Exception; |
| 8 |
use Test::Warn; |
25 |
use Test::Warn; |
| 9 |
|
26 |
|
| 10 |
BEGIN { |
27 |
BEGIN { |
| 11 |
use FindBin; |
|
|
| 12 |
use lib $FindBin::Bin; |
| 13 |
use_ok('C4::Scrubber'); |
28 |
use_ok('C4::Scrubber'); |
| 14 |
} |
29 |
} |
| 15 |
|
30 |
|
| 16 |
sub pretty_line { |
31 |
subtest 'new() constructor tests' => sub { |
| 17 |
my $max = 54; |
32 |
plan tests => 8; |
| 18 |
(@_) or return "#" x $max . "\n"; |
|
|
| 19 |
my $phrase = " " . shift() . " "; |
| 20 |
my $half = "#" x ( ( $max - length($phrase) ) / 2 ); |
| 21 |
return $half . $phrase . $half . "\n"; |
| 22 |
} |
| 23 |
|
33 |
|
| 24 |
my ( $scrubber, $html, $result, @types, $collapse ); |
34 |
my $scrubber; |
| 25 |
$collapse = 1; |
35 |
lives_ok { $scrubber = C4::Scrubber->new() } 'Constructor with no parameters succeeds'; |
| 26 |
@types = qw(default comment note); |
36 |
isa_ok( $scrubber, 'HTML::Scrubber', 'Constructor returns HTML::Scrubber object' ); |
| 27 |
$html = q| |
37 |
|
| 28 |
<![CDATA[selfdestruct]]]> |
38 |
lives_ok { $scrubber = C4::Scrubber->new('default') } 'Constructor with default type succeeds'; |
| 29 |
<?php echo(" EVIL EVIL EVIL "); ?> <!-- COMMENT --> |
39 |
isa_ok( $scrubber, 'HTML::Scrubber', 'Constructor with default type returns HTML::Scrubber object' ); |
| 30 |
<hr> <!-- TMPL_VAR NAME="password" --> |
|
|
| 31 |
<style type="text/css">body{display:none;}</style> |
| 32 |
<link media="screen" type="text/css" rev="stylesheet" rel="stylesheet" href="css.css"> |
| 33 |
<I FAKE="attribute" > I am ITALICS with fake="attribute" </I><br /> |
| 34 |
<em FAKE="attribute" > I am em with fake="attribute" </em><br /> |
| 35 |
<B> I am BOLD </B><br /> |
| 36 |
<span style="background-image: url(http://hackersite.cn/porno.jpg);"> I am a span w/ style. Bad style.</span> |
| 37 |
<span> I am a span trying to inject a link: <a href="badlink.html"> link </a></span> |
| 38 |
<br> |
| 39 |
<A NAME="evil"> |
| 40 |
<A HREF="javascript:alert('OMG YOO R HACKED');">I am a link firing javascript.</A> |
| 41 |
<br /> |
| 42 |
<A HREF="image/bigone.jpg" ONMOUSEOVER="alert('OMG YOO R HACKED');"> |
| 43 |
<IMG SRC="image/smallone.jpg" ALT="ONMOUSEOVER JAVASCRIPT"> |
| 44 |
</A> |
| 45 |
</A> <br> |
| 46 |
At the end here, I actually have some regular text. |
| 47 |
|; |
| 48 |
|
| 49 |
ok( $scrubber = C4::Scrubber->new(), "Constructor: C4::Scrubber->new()" ); |
| 50 |
|
| 51 |
isa_ok( $scrubber, 'HTML::Scrubber', 'Constructor returns HTML::Scrubber object' ); |
| 52 |
|
| 53 |
warning_like { $scrubber->default() } '', "\$scrubber->default ran without fault."; |
| 54 |
warning_like { $scrubber->comment() } '', "\$scrubber->comment ran without fault."; |
| 55 |
warning_like { $scrubber->process() } '', "\$scrubber->process ran without fault."; |
| 56 |
|
| 57 |
ok( $result = $scrubber->scrub($html), "Getting scrubbed text (type: [default])" ); |
| 58 |
|
| 59 |
foreach (@types) { |
| 60 |
ok( $scrubber = C4::Scrubber->new($_), "testing Constructor: C4::Scrubber->new($_)" ); |
| 61 |
|
| 62 |
warning_like { $scrubber->default() } '', "\$scrubber->default ran without fault."; |
| 63 |
warning_like { $scrubber->comment() } '', "\$scrubber->comment ran without fault."; |
| 64 |
warning_like { $scrubber->process() } '', "\$scrubber->process ran without fault."; |
| 65 |
|
| 66 |
ok( $result = $scrubber->scrub($html), "Getting scrubbed text (type: $_)" ); |
| 67 |
} |
| 68 |
|
40 |
|
| 69 |
#Test for invalid new entry |
41 |
lives_ok { $scrubber = C4::Scrubber->new('comment') } 'Constructor with comment type succeeds'; |
| 70 |
eval { |
42 |
isa_ok( $scrubber, 'HTML::Scrubber', 'Constructor with comment type returns HTML::Scrubber object' ); |
| 71 |
C4::Scrubber->new(""); |
43 |
|
| 72 |
fail("test should fail on entry of ''"); |
44 |
lives_ok { $scrubber = C4::Scrubber->new('note') } 'Constructor with note type succeeds'; |
|
|
45 |
isa_ok( $scrubber, 'HTML::Scrubber', 'Constructor with note type returns HTML::Scrubber object' ); |
| 73 |
}; |
46 |
}; |
| 74 |
if ($@) { |
|
|
| 75 |
pass("Test should have failed on entry of '' (empty string) and it did. YAY!"); |
| 76 |
} |
| 77 |
|
47 |
|
| 78 |
eval { |
48 |
subtest 'constructor error handling' => sub { |
| 79 |
C4::Scrubber->new("Client"); |
49 |
plan tests => 5; |
| 80 |
fail("test should fail on entry of 'Client'"); |
50 |
|
|
|
51 |
my $scrubber; |
| 52 |
lives_ok { $scrubber = C4::Scrubber->new(undef) } 'Constructor with undef type succeeds (treated as default)'; |
| 53 |
isa_ok( $scrubber, 'HTML::Scrubber', 'Constructor with undef type returns HTML::Scrubber object' ); |
| 54 |
|
| 55 |
throws_ok { |
| 56 |
C4::Scrubber->new(''); |
| 57 |
} |
| 58 |
qr/New called with unrecognized type/, 'Constructor throws exception for empty string type'; |
| 59 |
|
| 60 |
throws_ok { |
| 61 |
C4::Scrubber->new('invalid_type'); |
| 62 |
} |
| 63 |
qr/New called with unrecognized type/, 'Constructor throws exception for invalid type'; |
| 64 |
|
| 65 |
throws_ok { |
| 66 |
C4::Scrubber->new('Client'); |
| 67 |
} |
| 68 |
qr/New called with unrecognized type/, 'Constructor throws exception for Client type'; |
| 69 |
}; |
| 70 |
|
| 71 |
subtest 'default scrubber functionality' => sub { |
| 72 |
plan tests => 5; |
| 73 |
|
| 74 |
my $scrubber = C4::Scrubber->new('default'); |
| 75 |
|
| 76 |
my $malicious_html = q| |
| 77 |
<![CDATA[selfdestruct]]]> |
| 78 |
<?php echo("EVIL EVIL EVIL"); ?> |
| 79 |
<script>alert('XSS Attack!');</script> |
| 80 |
<style type="text/css">body{display:none;}</style> |
| 81 |
<link href="evil.css" rel="stylesheet"> |
| 82 |
<img src="x" onerror="alert('XSS')"> |
| 83 |
<a href="javascript:alert('XSS')">Click me</a> |
| 84 |
<p onclick="alert('XSS')">Paragraph</p> |
| 85 |
<div style="background:url(javascript:alert('XSS'))">Content</div> |
| 86 |
Plain text content |
| 87 |
|; |
| 88 |
|
| 89 |
my $result = $scrubber->scrub($malicious_html); |
| 90 |
|
| 91 |
unlike( $result, qr/<script/i, 'Script tags are removed' ); |
| 92 |
unlike( $result, qr/<style/i, 'Style tags are removed' ); |
| 93 |
unlike( $result, qr/<link/i, 'Link tags are removed' ); |
| 94 |
unlike( $result, qr/javascript:/i, 'JavaScript URLs are removed' ); |
| 95 |
like( $result, qr/Plain text content/, 'Plain text content is preserved' ); |
| 96 |
}; |
| 97 |
|
| 98 |
subtest 'comment scrubber functionality' => sub { |
| 99 |
plan tests => 10; |
| 100 |
|
| 101 |
my $scrubber = C4::Scrubber->new('comment'); |
| 102 |
|
| 103 |
my $test_html = |
| 104 |
'<p>Paragraph</p><b>Bold</b><i>Italic</i><em>Emphasis</em><big>Big</big><small>Small</small><strong>Strong</strong><br><u>Underline</u><hr><span>Span</span><div>Div</div><script>Evil</script>'; |
| 105 |
|
| 106 |
my $result = $scrubber->scrub($test_html); |
| 107 |
|
| 108 |
like( $result, qr/<b>Bold<\/b>/, 'Bold tags are preserved' ); |
| 109 |
like( $result, qr/<i>Italic<\/i>/, 'Italic tags are preserved' ); |
| 110 |
like( $result, qr/<em>Emphasis<\/em>/, 'Em tags are preserved' ); |
| 111 |
like( $result, qr/<big>Big<\/big>/, 'Big tags are preserved' ); |
| 112 |
like( $result, qr/<small>Small<\/small>/, 'Small tags are preserved' ); |
| 113 |
like( $result, qr/<strong>Strong<\/strong>/, 'Strong tags are preserved' ); |
| 114 |
like( $result, qr/<br>/, 'Break tags are preserved' ); |
| 115 |
|
| 116 |
unlike( $result, qr/<p>/, 'Paragraph tags are removed' ); |
| 117 |
unlike( $result, qr/<span>/, 'Span tags are removed' ); |
| 118 |
unlike( $result, qr/<script>/, 'Script tags are removed' ); |
| 81 |
}; |
119 |
}; |
| 82 |
if ($@) { |
|
|
| 83 |
pass("Test should have failed on entry of 'Client' and it did. YAY!"); |
| 84 |
} |
| 85 |
|
120 |
|
| 86 |
my $scrub_text = |
121 |
subtest 'note scrubber functionality' => sub { |
| 87 |
'<div><span><p><b>bold</b><i>ital</i><em>emphatic</em><big>embiggen</big><small>shrink</small><strong>strongbad</strong><br><u>under</u><hr><ol><li>ordered item</li></ol><ul><li>unordered item</li></ul><dl><dt>term</dt><dd>definition</dd></dl></p></span></div>'; |
122 |
plan tests => 22; |
| 88 |
my $scrub_comment = |
123 |
|
| 89 |
'<b>bold</b><i>ital</i><em>emphatic</em><big>embiggen</big><small>shrink</small><strong>strongbad</strong><br>underordered itemunordered itemtermdefinition'; |
124 |
my $scrubber = C4::Scrubber->new('note'); |
| 90 |
is( C4::Scrubber->new('comment')->scrub($scrub_text), $scrub_comment, "Comment scrubber removes expected elements" ); |
125 |
|
| 91 |
is( |
126 |
my $comprehensive_html = |
| 92 |
C4::Scrubber->new('note')->scrub($scrub_text), $scrub_text, |
127 |
'<div><span><p><b>Bold</b><i>Italic</i><em>Emphasis</em><big>Big</big><small>Small</small><strong>Strong</strong><br><u>Underline</u><hr><ol><li>Ordered item 1</li><li>Ordered item 2</li></ol><ul><li>Unordered item 1</li><li>Unordered item 2</li></ul><dl><dt>Term</dt><dd>Definition</dd></dl></p></span></div>'; |
| 93 |
"Note scrubber removes (additional) expected elements" |
128 |
|
| 94 |
); |
129 |
my $result = $scrubber->scrub($comprehensive_html); |
|
|
130 |
|
| 131 |
like( $result, qr/<div>/, 'Div tags are preserved' ); |
| 132 |
like( $result, qr/<span>/, 'Span tags are preserved' ); |
| 133 |
like( $result, qr/<p>/, 'Paragraph tags are preserved' ); |
| 134 |
like( $result, qr/<b>Bold<\/b>/, 'Bold tags are preserved' ); |
| 135 |
like( $result, qr/<i>Italic<\/i>/, 'Italic tags are preserved' ); |
| 136 |
like( $result, qr/<em>Emphasis<\/em>/, 'Em tags are preserved' ); |
| 137 |
like( $result, qr/<big>Big<\/big>/, 'Big tags are preserved' ); |
| 138 |
like( $result, qr/<small>Small<\/small>/, 'Small tags are preserved' ); |
| 139 |
like( $result, qr/<strong>Strong<\/strong>/, 'Strong tags are preserved' ); |
| 140 |
like( $result, qr/<br>/, 'Break tags are preserved' ); |
| 141 |
like( $result, qr/<u>Underline<\/u>/, 'Underline tags are preserved' ); |
| 142 |
like( $result, qr/<hr>/, 'HR tags are preserved' ); |
| 143 |
like( $result, qr/<ol>/, 'Ordered list tags are preserved' ); |
| 144 |
like( $result, qr/<ul>/, 'Unordered list tags are preserved' ); |
| 145 |
like( $result, qr/<li>/, 'List item tags are preserved' ); |
| 146 |
like( $result, qr/<dl>/, 'Description list tags are preserved' ); |
| 147 |
like( $result, qr/<dt>Term<\/dt>/, 'Description term tags are preserved' ); |
| 148 |
like( $result, qr/<dd>Definition<\/dd>/, 'Description definition tags are preserved' ); |
| 149 |
|
| 150 |
is( $result, $comprehensive_html, 'All allowed tags in note scrubber are preserved exactly' ); |
| 151 |
|
| 152 |
my $malicious_note = '<p>Safe content</p><script>alert("XSS")</script><iframe src="evil.html"></iframe>'; |
| 153 |
my $safe_result = $scrubber->scrub($malicious_note); |
| 154 |
|
| 155 |
like( $safe_result, qr/<p>Safe content<\/p>/, 'Safe content is preserved' ); |
| 156 |
unlike( $safe_result, qr/<script>/, 'Script tags are removed from notes' ); |
| 157 |
unlike( $safe_result, qr/<iframe>/, 'Iframe tags are removed from notes' ); |
| 158 |
}; |
| 95 |
- |
|
|