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 'jon-doe@example.com', 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 'user@example.com?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.&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.&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 = 'user@example.' 70*73dc0a89SAndreas Gohr . 'com?body=Hello.&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.&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.&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; 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;', MailUtils::obfuscate($input), "obfuscate/$mode"); 110*73dc0a89SAndreas Gohr $this->assertStringNotContainsString( 111*73dc0a89SAndreas Gohr '&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 'jon-doe@example.com', 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