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 'jon-doe@example.com', 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 'user@example.com?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.&subject=DOC REQUEST', 57 MailUtils::obfuscate('user@example.com?body=Hello.&subject=DOC REQUEST') 58 ); 59 $this->assertEquals( 60 'user@example.com?body=Hello.&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 = 'user@example.' 70 . 'com?body=Hello.&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.&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.&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; 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;', MailUtils::obfuscate($input), "obfuscate/$mode"); 110 $this->assertStringNotContainsString( 111 '&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 'jon-doe@example.com', 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