xref: /dokuwiki/_test/tests/MailUtilsTest.php (revision 73dc0a8919857718a3b64a4c0741b57580a34b2a)
1*73dc0a89SAndreas Gohr<?php
2*73dc0a89SAndreas Gohr
3*73dc0a89SAndreas Gohrnamespace dokuwiki\test;
4*73dc0a89SAndreas Gohr
5*73dc0a89SAndreas Gohruse dokuwiki\MailUtils;
6*73dc0a89SAndreas Gohr
7*73dc0a89SAndreas Gohrclass MailUtilsTest extends \DokuWikiTest
8*73dc0a89SAndreas Gohr{
9*73dc0a89SAndreas Gohr    // region obfuscate() — basic modes
10*73dc0a89SAndreas Gohr
11*73dc0a89SAndreas Gohr    public function testObfuscateNone(): void
12*73dc0a89SAndreas Gohr    {
13*73dc0a89SAndreas Gohr        global $conf;
14*73dc0a89SAndreas Gohr        $conf['mailguard'] = 'none';
15*73dc0a89SAndreas Gohr        $this->assertEquals('jon-doe@example.com', MailUtils::obfuscate('jon-doe@example.com'));
16*73dc0a89SAndreas Gohr    }
17*73dc0a89SAndreas Gohr
18*73dc0a89SAndreas Gohr    public function testObfuscateHex(): void
19*73dc0a89SAndreas Gohr    {
20*73dc0a89SAndreas Gohr        global $conf;
21*73dc0a89SAndreas Gohr        $conf['mailguard'] = 'hex';
22*73dc0a89SAndreas Gohr        $this->assertEquals(
23*73dc0a89SAndreas Gohr            '&#106;&#111;&#110;&#45;&#100;&#111;&#101;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;',
24*73dc0a89SAndreas Gohr            MailUtils::obfuscate('jon-doe@example.com')
25*73dc0a89SAndreas Gohr        );
26*73dc0a89SAndreas Gohr    }
27*73dc0a89SAndreas Gohr
28*73dc0a89SAndreas Gohr    public function testObfuscateVisible(): void
29*73dc0a89SAndreas Gohr    {
30*73dc0a89SAndreas Gohr        global $conf;
31*73dc0a89SAndreas Gohr        $conf['mailguard'] = 'visible';
32*73dc0a89SAndreas Gohr        $this->assertEquals('jon [dash] doe [at] example [dot] com', MailUtils::obfuscate('jon-doe@example.com'));
33*73dc0a89SAndreas Gohr    }
34*73dc0a89SAndreas Gohr
35*73dc0a89SAndreas Gohr    public function testObfuscateHexQueryPreserved(): void
36*73dc0a89SAndreas Gohr    {
37*73dc0a89SAndreas Gohr        global $conf;
38*73dc0a89SAndreas Gohr        $conf['mailguard'] = 'hex';
39*73dc0a89SAndreas Gohr        // The query string is preserved verbatim (with HTML escaping only) so
40*73dc0a89SAndreas Gohr        // mail clients can pre-fill the subject; only the address half is
41*73dc0a89SAndreas Gohr        // hex-encoded.
42*73dc0a89SAndreas Gohr        $this->assertEquals(
43*73dc0a89SAndreas Gohr            '&#117;&#115;&#101;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;?subject=Привет',
44*73dc0a89SAndreas Gohr            MailUtils::obfuscate('user@example.com?subject=Привет')
45*73dc0a89SAndreas Gohr        );
46*73dc0a89SAndreas Gohr    }
47*73dc0a89SAndreas Gohr
48*73dc0a89SAndreas Gohr    // endregion
49*73dc0a89SAndreas Gohr    // region obfuscate() / obfuscateUrl() — bug #1690 (multiple query params)
50*73dc0a89SAndreas Gohr
51*73dc0a89SAndreas Gohr    public function testNoneWithQuery(): void
52*73dc0a89SAndreas Gohr    {
53*73dc0a89SAndreas Gohr        global $conf;
54*73dc0a89SAndreas Gohr        $conf['mailguard'] = 'none';
55*73dc0a89SAndreas Gohr        $this->assertEquals(
56*73dc0a89SAndreas Gohr            'user@example.com?body=Hello.&amp;subject=DOC REQUEST',
57*73dc0a89SAndreas Gohr            MailUtils::obfuscate('user@example.com?body=Hello.&subject=DOC REQUEST')
58*73dc0a89SAndreas Gohr        );
59*73dc0a89SAndreas Gohr        $this->assertEquals(
60*73dc0a89SAndreas Gohr            'user@example.com?body=Hello.&amp;subject=DOC REQUEST',
61*73dc0a89SAndreas Gohr            MailUtils::obfuscateUrl('user@example.com?body=Hello.&subject=DOC REQUEST')
62*73dc0a89SAndreas Gohr        );
63*73dc0a89SAndreas Gohr    }
64*73dc0a89SAndreas Gohr
65*73dc0a89SAndreas Gohr    public function testHexWithQuery(): void
66*73dc0a89SAndreas Gohr    {
67*73dc0a89SAndreas Gohr        global $conf;
68*73dc0a89SAndreas Gohr        $conf['mailguard'] = 'hex';
69*73dc0a89SAndreas Gohr        $expected = '&#117;&#115;&#101;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;'
70*73dc0a89SAndreas Gohr            . '&#99;&#111;&#109;?body=Hello.&amp;subject=DOC REQUEST';
71*73dc0a89SAndreas Gohr        $this->assertEquals(
72*73dc0a89SAndreas Gohr            $expected,
73*73dc0a89SAndreas Gohr            MailUtils::obfuscate('user@example.com?body=Hello.&subject=DOC REQUEST')
74*73dc0a89SAndreas Gohr        );
75*73dc0a89SAndreas Gohr        $this->assertEquals(
76*73dc0a89SAndreas Gohr            $expected,
77*73dc0a89SAndreas Gohr            MailUtils::obfuscateUrl('user@example.com?body=Hello.&subject=DOC REQUEST')
78*73dc0a89SAndreas Gohr        );
79*73dc0a89SAndreas Gohr    }
80*73dc0a89SAndreas Gohr
81*73dc0a89SAndreas Gohr    public function testVisibleWithQuery(): void
82*73dc0a89SAndreas Gohr    {
83*73dc0a89SAndreas Gohr        global $conf;
84*73dc0a89SAndreas Gohr        $conf['mailguard'] = 'visible';
85*73dc0a89SAndreas Gohr        // Only the address half is touched: dots/dashes inside body/subject
86*73dc0a89SAndreas Gohr        // values stay intact.
87*73dc0a89SAndreas Gohr        $this->assertEquals(
88*73dc0a89SAndreas Gohr            'user [at] example [dot] com?body=Hello.&amp;subject=DOC REQUEST',
89*73dc0a89SAndreas Gohr            MailUtils::obfuscate('user@example.com?body=Hello.&subject=DOC REQUEST')
90*73dc0a89SAndreas Gohr        );
91*73dc0a89SAndreas Gohr        // For the href, the [at]/[dot] address is percent-encoded so the URL
92*73dc0a89SAndreas Gohr        // is well-formed; the query string is preserved verbatim with only
93*73dc0a89SAndreas Gohr        // HTML-attribute escaping applied.
94*73dc0a89SAndreas Gohr        $this->assertEquals(
95*73dc0a89SAndreas Gohr            'user%20%5Bat%5D%20example%20%5Bdot%5D%20com?body=Hello.&amp;subject=DOC REQUEST',
96*73dc0a89SAndreas Gohr            MailUtils::obfuscateUrl('user@example.com?body=Hello.&subject=DOC REQUEST')
97*73dc0a89SAndreas Gohr        );
98*73dc0a89SAndreas Gohr    }
99*73dc0a89SAndreas Gohr
100*73dc0a89SAndreas Gohr    /**
101*73dc0a89SAndreas Gohr     * Regression: never emit a double-escaped &amp;amp; in any mode.
102*73dc0a89SAndreas Gohr     */
103*73dc0a89SAndreas Gohr    public function testNoDoubleEscape(): void
104*73dc0a89SAndreas Gohr    {
105*73dc0a89SAndreas Gohr        global $conf;
106*73dc0a89SAndreas Gohr        $input = 'user@example.com?a=1&b=2&c=3';
107*73dc0a89SAndreas Gohr        foreach (['none', 'hex', 'visible'] as $mode) {
108*73dc0a89SAndreas Gohr            $conf['mailguard'] = $mode;
109*73dc0a89SAndreas Gohr            $this->assertStringNotContainsString('&amp;amp;', MailUtils::obfuscate($input), "obfuscate/$mode");
110*73dc0a89SAndreas Gohr            $this->assertStringNotContainsString(
111*73dc0a89SAndreas Gohr                '&amp;amp;',
112*73dc0a89SAndreas Gohr                MailUtils::obfuscateUrl($input),
113*73dc0a89SAndreas Gohr                "obfuscateUrl/$mode"
114*73dc0a89SAndreas Gohr            );
115*73dc0a89SAndreas Gohr        }
116*73dc0a89SAndreas Gohr    }
117*73dc0a89SAndreas Gohr
118*73dc0a89SAndreas Gohr    // endregion
119*73dc0a89SAndreas Gohr    // region obfuscateUrl() — URL-shape coverage without query
120*73dc0a89SAndreas Gohr
121*73dc0a89SAndreas Gohr    public function testObfuscateUrlNoneNoQuery(): void
122*73dc0a89SAndreas Gohr    {
123*73dc0a89SAndreas Gohr        global $conf;
124*73dc0a89SAndreas Gohr        $conf['mailguard'] = 'none';
125*73dc0a89SAndreas Gohr        $this->assertEquals('jon-doe@example.com', MailUtils::obfuscateUrl('jon-doe@example.com'));
126*73dc0a89SAndreas Gohr    }
127*73dc0a89SAndreas Gohr
128*73dc0a89SAndreas Gohr    public function testObfuscateUrlHexNoQuery(): void
129*73dc0a89SAndreas Gohr    {
130*73dc0a89SAndreas Gohr        global $conf;
131*73dc0a89SAndreas Gohr        $conf['mailguard'] = 'hex';
132*73dc0a89SAndreas Gohr        $this->assertEquals(
133*73dc0a89SAndreas Gohr            '&#106;&#111;&#110;&#45;&#100;&#111;&#101;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;',
134*73dc0a89SAndreas Gohr            MailUtils::obfuscateUrl('jon-doe@example.com')
135*73dc0a89SAndreas Gohr        );
136*73dc0a89SAndreas Gohr    }
137*73dc0a89SAndreas Gohr
138*73dc0a89SAndreas Gohr    public function testObfuscateUrlVisibleNoQuery(): void
139*73dc0a89SAndreas Gohr    {
140*73dc0a89SAndreas Gohr        global $conf;
141*73dc0a89SAndreas Gohr        $conf['mailguard'] = 'visible';
142*73dc0a89SAndreas Gohr        // Visible mode percent-encodes the [at]/[dot] address so the mailto
143*73dc0a89SAndreas Gohr        // URL stays well-formed.
144*73dc0a89SAndreas Gohr        $this->assertEquals(
145*73dc0a89SAndreas Gohr            'jon%20%5Bdash%5D%20doe%20%5Bat%5D%20example%20%5Bdot%5D%20com',
146*73dc0a89SAndreas Gohr            MailUtils::obfuscateUrl('jon-doe@example.com')
147*73dc0a89SAndreas Gohr        );
148*73dc0a89SAndreas Gohr    }
149*73dc0a89SAndreas Gohr
150*73dc0a89SAndreas Gohr    /**
151*73dc0a89SAndreas Gohr     * HTML-special characters in the address half must never break out of the
152*73dc0a89SAndreas Gohr     * attribute context, regardless of mode.
153*73dc0a89SAndreas Gohr     */
154*73dc0a89SAndreas Gohr    public function testObfuscateEscapesHtmlSpecials(): void
155*73dc0a89SAndreas Gohr    {
156*73dc0a89SAndreas Gohr        global $conf;
157*73dc0a89SAndreas Gohr        // Constructed so the address half contains a quote-like char that
158*73dc0a89SAndreas Gohr        // htmlspecialchars must neutralise; chosen to still pass the
159*73dc0a89SAndreas Gohr        // [at]/[dot] substitution path.
160*73dc0a89SAndreas Gohr        $input = "a'b@example.com";
161*73dc0a89SAndreas Gohr        foreach (['none', 'visible'] as $mode) {
162*73dc0a89SAndreas Gohr            $conf['mailguard'] = $mode;
163*73dc0a89SAndreas Gohr            $this->assertStringNotContainsString("'", MailUtils::obfuscate($input), "obfuscate/$mode");
164*73dc0a89SAndreas Gohr            $this->assertStringNotContainsString("'", MailUtils::obfuscateUrl($input), "obfuscateUrl/$mode");
165*73dc0a89SAndreas Gohr        }
166*73dc0a89SAndreas Gohr    }
167*73dc0a89SAndreas Gohr
168*73dc0a89SAndreas Gohr    // endregion
169*73dc0a89SAndreas Gohr    // region isValid()
170*73dc0a89SAndreas Gohr
171*73dc0a89SAndreas Gohr    public function provideAddresses(): array
172*73dc0a89SAndreas Gohr    {
173*73dc0a89SAndreas Gohr        return [
174*73dc0a89SAndreas Gohr            // our own tests
175*73dc0a89SAndreas Gohr            ['bugs@php.net', true],
176*73dc0a89SAndreas Gohr            ['~someone@somewhere.com', true],
177*73dc0a89SAndreas Gohr            ['no+body.here@somewhere.com.au', true],
178*73dc0a89SAndreas Gohr            ['username+tag@domain.com', true], // FS#1447
179*73dc0a89SAndreas Gohr            ["rfc2822+allthesechars_#*!'`/-={}are.legal@somewhere.com.au", true],
180*73dc0a89SAndreas Gohr            ['_foo@test.com', true], // FS#1049
181*73dc0a89SAndreas Gohr            ['bugs@php.net1', true], // new ICAN rulez seem to allow this
182*73dc0a89SAndreas Gohr            ['.bugs@php.net1', false],
183*73dc0a89SAndreas Gohr            ['bu..gs@php.net', false],
184*73dc0a89SAndreas Gohr            ['bugs@php..net', false],
185*73dc0a89SAndreas Gohr            ['bugs@.php.net', false],
186*73dc0a89SAndreas Gohr            ['bugs@php.net.', false],
187*73dc0a89SAndreas Gohr            ['bu(g)s@php.net1', false],
188*73dc0a89SAndreas Gohr            ['bu[g]s@php.net1', false],
189*73dc0a89SAndreas Gohr            ['somebody@somewhere.museum', true],
190*73dc0a89SAndreas Gohr            ['somebody@somewhere.travel', true],
191*73dc0a89SAndreas Gohr            ['root@[2010:fb:fdac::311:2101]', true],
192*73dc0a89SAndreas Gohr            ['test@example', true], // we allow local addresses
193*73dc0a89SAndreas Gohr
194*73dc0a89SAndreas Gohr            // tests from http://code.google.com/p/php-email-address-validation/ below
195*73dc0a89SAndreas Gohr            ['test@example.com', true],
196*73dc0a89SAndreas Gohr            ['TEST@example.com', true],
197*73dc0a89SAndreas Gohr            ['1234567890@example.com', true],
198*73dc0a89SAndreas Gohr            ['test+test@example.com', true],
199*73dc0a89SAndreas Gohr            ['test-test@example.com', true],
200*73dc0a89SAndreas Gohr            ['t*est@example.com', true],
201*73dc0a89SAndreas Gohr            ['+1~1+@example.com', true],
202*73dc0a89SAndreas Gohr            ['{_test_}@example.com', true],
203*73dc0a89SAndreas Gohr            ['"[[ test ]]"@example.com', true],
204*73dc0a89SAndreas Gohr            ['test.test@example.com', true],
205*73dc0a89SAndreas Gohr            ['test."test"@example.com', true],
206*73dc0a89SAndreas Gohr            ['"test@test"@example.com', true],
207*73dc0a89SAndreas Gohr            ['test@123.123.123.123', true],
208*73dc0a89SAndreas Gohr            ['test@[123.123.123.123]', true],
209*73dc0a89SAndreas Gohr            ['test@example.example.com', true],
210*73dc0a89SAndreas Gohr            ['test@example.example.example.com', true],
211*73dc0a89SAndreas Gohr
212*73dc0a89SAndreas Gohr            ['test.example.com', false],
213*73dc0a89SAndreas Gohr            ['test.@example.com', false],
214*73dc0a89SAndreas Gohr            ['test..test@example.com', false],
215*73dc0a89SAndreas Gohr            ['.test@example.com', false],
216*73dc0a89SAndreas Gohr            ['test@test@example.com', false],
217*73dc0a89SAndreas Gohr            ['test@@example.com', false],
218*73dc0a89SAndreas Gohr            ['-- test --@example.com', false], // No spaces allowed in local part
219*73dc0a89SAndreas Gohr            ['[test]@example.com', false], // Square brackets only allowed within quotes
220*73dc0a89SAndreas Gohr            ['"test\test"@example.com', false], // Quotes cannot contain backslash
221*73dc0a89SAndreas Gohr            ['"test"test"@example.com', false], // Quotes cannot be nested
222*73dc0a89SAndreas Gohr            ['()[]\;:,<>@example.com', false], // Disallowed Characters
223*73dc0a89SAndreas Gohr            ['test@.', false],
224*73dc0a89SAndreas Gohr            ['test@example.', false],
225*73dc0a89SAndreas Gohr            ['test@.org', false],
226*73dc0a89SAndreas Gohr            // 64 characters is maximum length for local part. This is 65.
227*73dc0a89SAndreas Gohr            ['12345678901234567890123456789012345678901234567890123456789012345@example.com', false],
228*73dc0a89SAndreas Gohr            // 255 characters is maximum length for domain. This is 256.
229*73dc0a89SAndreas Gohr            ['test@123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012.com', false],
230*73dc0a89SAndreas Gohr            ['test@[123.123.123.123', false],
231*73dc0a89SAndreas Gohr            ['test@123.123.123.123]', false],
232*73dc0a89SAndreas Gohr        ];
233*73dc0a89SAndreas Gohr    }
234*73dc0a89SAndreas Gohr
235*73dc0a89SAndreas Gohr    /**
236*73dc0a89SAndreas Gohr     * @dataProvider provideAddresses
237*73dc0a89SAndreas Gohr     */
238*73dc0a89SAndreas Gohr    public function testIsValid(string $input, bool $valid): void
239*73dc0a89SAndreas Gohr    {
240*73dc0a89SAndreas Gohr        $this->assertSame($valid, MailUtils::isValid($input));
241*73dc0a89SAndreas Gohr    }
242*73dc0a89SAndreas Gohr
243*73dc0a89SAndreas Gohr    // endregion
244*73dc0a89SAndreas Gohr    // region quotedPrintableEncode()
245*73dc0a89SAndreas Gohr
246*73dc0a89SAndreas Gohr    public function testQuotedPrintableSimple(): void
247*73dc0a89SAndreas Gohr    {
248*73dc0a89SAndreas Gohr        $this->assertEquals('hello', MailUtils::quotedPrintableEncode('hello'));
249*73dc0a89SAndreas Gohr    }
250*73dc0a89SAndreas Gohr
251*73dc0a89SAndreas Gohr    public function testQuotedPrintableSpaceEnd(): void
252*73dc0a89SAndreas Gohr    {
253*73dc0a89SAndreas Gohr        $this->assertEquals("hello=20\r\nhello", MailUtils::quotedPrintableEncode("hello \nhello"));
254*73dc0a89SAndreas Gohr    }
255*73dc0a89SAndreas Gohr
256*73dc0a89SAndreas Gohr    public function testQuotedPrintableGermanUtf8(): void
257*73dc0a89SAndreas Gohr    {
258*73dc0a89SAndreas Gohr        $this->assertEquals(
259*73dc0a89SAndreas Gohr            'hello =C3=BCberl=C3=A4nge',
260*73dc0a89SAndreas Gohr            MailUtils::quotedPrintableEncode('hello überlänge')
261*73dc0a89SAndreas Gohr        );
262*73dc0a89SAndreas Gohr    }
263*73dc0a89SAndreas Gohr
264*73dc0a89SAndreas Gohr    public function testQuotedPrintableWrap(): void
265*73dc0a89SAndreas Gohr    {
266*73dc0a89SAndreas Gohr        $in  = '123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789';
267*73dc0a89SAndreas Gohr        $out = "123456789 123456789 123456789 123456789 123456789 123456789 123456789 1234=\r\n56789 123456789";
268*73dc0a89SAndreas Gohr        $this->assertEquals($out, MailUtils::quotedPrintableEncode($in, 74));
269*73dc0a89SAndreas Gohr    }
270*73dc0a89SAndreas Gohr
271*73dc0a89SAndreas Gohr    public function testQuotedPrintableNoWrap(): void
272*73dc0a89SAndreas Gohr    {
273*73dc0a89SAndreas Gohr        $line = '123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789';
274*73dc0a89SAndreas Gohr        $this->assertEquals($line, MailUtils::quotedPrintableEncode($line, 0));
275*73dc0a89SAndreas Gohr    }
276*73dc0a89SAndreas Gohr
277*73dc0a89SAndreas Gohr    public function testQuotedPrintableRussianUtf8(): void
278*73dc0a89SAndreas Gohr    {
279*73dc0a89SAndreas Gohr        $in  = 'Ваш пароль для системы Доку Вики';
280*73dc0a89SAndreas Gohr        $out = '=D0=92=D0=B0=D1=88 =D0=BF=D0=B0=D1=80=D0=BE=D0=BB=D1=8C '
281*73dc0a89SAndreas Gohr            . '=D0=B4=D0=BB=D1=8F =D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC=D1=8B '
282*73dc0a89SAndreas Gohr            . '=D0=94=D0=BE=D0=BA=D1=83 =D0=92=D0=B8=D0=BA=D0=B8';
283*73dc0a89SAndreas Gohr        $this->assertEquals($out, MailUtils::quotedPrintableEncode($in, 0));
284*73dc0a89SAndreas Gohr    }
285*73dc0a89SAndreas Gohr
286*73dc0a89SAndreas Gohr    // endregion
287*73dc0a89SAndreas Gohr    // region PREG_PATTERN_VALID_EMAIL
288*73dc0a89SAndreas Gohr
289*73dc0a89SAndreas Gohr    public function testPatternMatchesValid(): void
290*73dc0a89SAndreas Gohr    {
291*73dc0a89SAndreas Gohr        $this->assertSame(
292*73dc0a89SAndreas Gohr            1,
293*73dc0a89SAndreas Gohr            preg_match('<' . MailUtils::PREG_PATTERN_VALID_EMAIL . '>', 'user@example.com')
294*73dc0a89SAndreas Gohr        );
295*73dc0a89SAndreas Gohr    }
296*73dc0a89SAndreas Gohr
297*73dc0a89SAndreas Gohr    public function testPatternRejectsInvalid(): void
298*73dc0a89SAndreas Gohr    {
299*73dc0a89SAndreas Gohr        // The pattern is the parser's syntax detector, not a strict validator;
300*73dc0a89SAndreas Gohr        // it must at least reject inputs with no '@'.
301*73dc0a89SAndreas Gohr        $this->assertSame(
302*73dc0a89SAndreas Gohr            0,
303*73dc0a89SAndreas Gohr            preg_match('<^' . MailUtils::PREG_PATTERN_VALID_EMAIL . '$>', 'not-an-email')
304*73dc0a89SAndreas Gohr        );
305*73dc0a89SAndreas Gohr    }
306*73dc0a89SAndreas Gohr
307*73dc0a89SAndreas Gohr    // endregion
308*73dc0a89SAndreas Gohr}
309