View | Details | Raw Unified | Return to bug 40079
Collapse All | Expand All

(-)a/C4/Scrubber.pm (+1 lines)
Lines 35-40 my %scrubbertypes = ( Link Here
35
sub new {
35
sub new {
36
    shift;            # ignore our class we are wrapper
36
    shift;            # ignore our class we are wrapper
37
    my $type = (@_) ? shift : 'default';
37
    my $type = (@_) ? shift : 'default';
38
    $type = 'default' if !defined $type;
38
    if ( !exists $scrubbertypes{$type} ) {
39
    if ( !exists $scrubbertypes{$type} ) {
39
        croak "New called with unrecognized type '$type'";
40
        croak "New called with unrecognized type '$type'";
40
    }
41
    }
(-)a/t/Scrubber.t (-78 / +141 lines)
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]]&#x5d;>
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: &lt;a href="badlink.html"&gt; link &lt;/a&gt;</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]]&#x5d;>
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
- 

Return to bug 40079