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