1<?php
2/**
3 * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
4 *
5 * @category   Horde
6 * @copyright  2010-2016 Horde LLC
7 * @license    http://www.horde.org/licenses/lgpl21 LGPL 2.1
8 * @package    Mime
9 * @subpackage UnitTests
10 */
11
12/**
13 * Tests for the Horde_Mime_Headers class.
14 *
15 * @author     Michael Slusarz <slusarz@horde.org>
16 * @category   Horde
17 * @copyright  2010-2016 Horde LLC
18 * @internal
19 * @license    http://www.horde.org/licenses/lgpl21 LGPL 2.1
20 * @package    Mime
21 * @subpackage UnitTests
22 */
23class Horde_Mime_HeadersTest extends PHPUnit_Framework_TestCase
24{
25    public function testClone()
26    {
27        $hdrs = new Horde_Mime_Headers();
28        $hdrs->addHeader('To', 'foo@example.com');
29        $hdrs->addHeader('Resent-To', 'foo2@example.com');
30        $hdrs->addHeader('Resent-To', 'foo3@example.com');
31
32        $ct = new Horde_Mime_Headers_ContentParam_ContentType(
33            null,
34            'text/plain; charset="iso-8859-1"'
35        );
36        $hdrs->addHeaderOb($ct);
37
38        $cd = new Horde_Mime_Headers_ContentParam_ContentDisposition(
39            null,
40            'attachment; filename="foo"'
41        );
42        $hdrs->addHeaderOb($cd);
43
44        $hdrs2 = clone $hdrs;
45
46        $hdrs->addHeader('To', 'bar@example.com');
47        $hdrs->addHeader('Resent-To', 'bar2@example.com');
48        $ct['charset'] = 'utf-8';
49        $cd['filename'] = 'bar';
50
51        $this->assertEquals(
52            'foo@example.com',
53            strval($hdrs2['To'])
54        );
55        $this->assertEquals(
56            array('foo2@example.com', 'foo3@example.com'),
57            $hdrs2['Resent-To']->value
58        );
59        $this->assertEquals(
60            array('charset' => 'iso-8859-1'),
61            $hdrs2['Content-Type']->params
62        );
63        $this->assertEquals(
64            array('filename' => 'foo'),
65            $hdrs2['Content-Disposition']->params
66        );
67    }
68
69    /**
70     * @dataProvider serializeProvider
71     */
72    public function testSerialize($header, $value)
73    {
74        $hdrs = new Horde_Mime_Headers();
75        $hdrs->addHeader($header, $value);
76
77        $hdrs2 = unserialize(serialize($hdrs));
78
79        /* @deprecated */
80        $this->assertEquals(
81            $value,
82            $hdrs2->getValue($header)
83        );
84
85        $this->assertequals(
86            $value,
87            strval($hdrs2[$header])
88        );
89    }
90
91    public function serializeProvider()
92    {
93        return array(
94            array(
95                'Subject', 'My Subject'
96            ),
97            array(
98                'To', 'recipient@example.com'
99            ),
100            array(
101                'Cc', 'null@example.com'
102            ),
103            array(
104                'Bcc', 'invisible@example.com'
105            ),
106            array(
107                'From', 'sender@example.com'
108            )
109        );
110    }
111
112    /**
113     * @dataProvider normalHeaderDecodeProvider
114     */
115    public function testNormalHeaderDecode($header, $value, $decoded)
116    {
117        $hdrs = new Horde_Mime_Headers();
118        $hdrs->addHeader($header, $value);
119
120        /* @deprecated */
121        $this->assertEquals(
122            $decoded,
123            $hdrs->getValue($header)
124        );
125
126        $this->assertEquals(
127            $decoded,
128            $hdrs[$header]->value_single
129        );
130    }
131
132    public function normalHeaderDecodeProvider()
133    {
134        return array(
135            array(
136                'Test',
137                '=?iso-8859-15?b?VmVyc2nzbg==?=',
138                'Versión'
139            ),
140            array(
141                'To',
142                '=?utf-8?B?IklsZ2EginVwbGluc2thIg==?= <foo@example.com>',
143                'Ilga Šuplinska <foo@example.com>'
144            ),
145            array(
146                'From',
147                'Firstname =?utf-8?b?U2Vjb25kw5Es?= Third <correct.email@host.com>',
148                '"Firstname SecondÑ, Third" <correct.email@host.com>'
149            ),
150            array(
151                'From',
152                '=?utf-8?B?TGFuZXNza29nLCBKw7hyZ2Vu?= <Jorgen.Lanesskog@marinit.no>',
153                '"Lanesskog, Jørgen" <Jorgen.Lanesskog@marinit.no>'
154            )
155        );
156    }
157
158    /**
159     * @dataProvider contentParamHeaderDecodeProvider
160     */
161    public function testContentParamHeaderDecode(
162        $header, $value, $decode_value, $decode_params
163    )
164    {
165        $hdrs = new Horde_Mime_Headers();
166        $hdrs->addHeader($header, $value);
167
168        $this->assertEquals(
169            $decode_value,
170            $hdrs[$header]->value
171        );
172
173        ksort($decode_params);
174        $params = $hdrs[$header]->params;
175        ksort($params);
176
177        $this->assertEquals(
178            $decode_params,
179            $params
180        );
181    }
182
183    public function contentParamHeaderDecodeProvider()
184    {
185        return array(
186            array(
187                'Content-Type',
188                'text/plain; name="=?iso-8859-15?b?VmVyc2nzbg==?="',
189                'text/plain',
190                array(
191                    'name' => 'Versión'
192                )
193            ),
194            array(
195                'Content-Disposition',
196                "attachment; size=147502;\n filename*=utf-8''Factura%20n%C2%BA%2010.pdf",
197                'attachment',
198                array(
199                    'size' => '147502',
200                    'filename' => 'Factura nº 10.pdf'
201                )
202            )
203        );
204    }
205
206    /**
207     * @dataProvider headerAutoDetectCharsetProvider
208     */
209    public function testHeaderAutoDetectCharset($header, $value, $decoded)
210    {
211        $hdrs = Horde_Mime_Headers::parseHeaders($value);
212
213        /* @deprecated */
214        $this->assertEquals(
215            $decoded,
216            $hdrs->getValue($header)
217        );
218
219        $this->assertEquals(
220            $decoded,
221            $hdrs[$header]->value_single
222        );
223    }
224
225    public function headerAutoDetectCharsetProvider()
226    {
227        return array(
228            array(
229                'Test',
230                // This string is in Windows-1252
231                'Test: ' . base64_decode('UnVubmVyc5IgQWxlcnQh='),
232                'Runners’ Alert!'
233            )
234        );
235    }
236
237    /**
238     * @dataProvider headerEncodeProvider
239     */
240    public function testHeaderEncode(
241        $header, $values, $charset, $encoded
242    )
243    {
244        $hdrs = new Horde_Mime_Headers();
245        foreach ($values as $val) {
246            $hdrs->addHeader($header, $val);
247        }
248
249        $hdr_encode = $hdrs[$header]->sendEncode(array(
250            'charset' => $charset
251        ));
252
253        $this->assertEquals(
254            $encoded,
255            $hdr_encode
256        );
257
258        $hdr_array = $hdrs->toArray(array(
259            'charset' => $charset
260        ));
261
262        $this->assertEquals(
263            (count($encoded) > 1) ? $encoded : reset($encoded),
264            $hdr_array[$header]
265        );
266    }
267
268    public function headerEncodeProvider()
269    {
270        return array(
271            /* Single address header */
272            array(
273                'To',
274                array(
275                    'Empfänger <recipient@example.com>'
276                ),
277                'iso-8859-1',
278                array(
279                    '=?iso-8859-1?b?RW1wZuRuZ2Vy?= <recipient@example.com>'
280                )
281            ),
282            /* Multiple address header */
283            array(
284                'Resent-To',
285                array(
286                    'Empfänger <recipient@example.com>',
287                    'Foo <foo@example.com>'
288                ),
289                'iso-8859-1',
290                array(
291                    '=?iso-8859-1?b?RW1wZuRuZ2Vy?= <recipient@example.com>',
292                    'Foo <foo@example.com>'
293                )
294            ),
295            /* Bug #13814 */
296            array(
297                'Content-Description',
298                array(
299                    'AüA'
300                ),
301                'utf-8',
302                array(
303                    '=?utf-8?b?QcO8QQ==?='
304                )
305            )
306        );
307    }
308
309    public function testMultipleContentType()
310    {
311        $hdrs = Horde_Mime_Headers::parseHeaders(
312            "Content-Type: multipart/mixed\n" .
313            "Content-Type: multipart/mixed\n"
314        );
315
316        /* @deprecated */
317        $this->assertInternalType(
318            'string',
319            $hdrs->getValue('content-type', Horde_Mime_Headers::VALUE_BASE)
320        );
321
322        $this->assertInternalType(
323            'string',
324            $hdrs['content-type']->value
325        );
326    }
327
328    /**
329     * @dataProvider multivalueHeadersProvider
330     */
331    public function testMultivalueHeaders($header, $in, $expected)
332    {
333        $hdrs = Horde_Mime_Headers::parseHeaders($in);
334
335        /* @deprecated */
336        $this->assertEquals(
337            $expected,
338            $hdrs->getValue($header)
339        );
340
341        $this->assertEquals(
342            $expected,
343            $hdrs['to']->value
344        );
345    }
346
347    public function multivalueHeadersProvider()
348    {
349        $expected = 'recipient1@example.com, recipient2@example.com';
350
351        return array(
352            array(
353                'To',
354                'To: recipient1@example.com, recipient2@example.com',
355                $expected
356            ),
357            array(
358                'To',
359                "To: recipient1@example.com\nTo: recipient2@example.com",
360                $expected
361            )
362        );
363    }
364
365    /**
366     * @dataProvider addHeaderWithGroupProvider
367     */
368    public function testAddHeaderWithGroup($header, $email)
369    {
370        $rfc822 = new Horde_Mail_Rfc822();
371        $ob = $rfc822->parseAddressList($email);
372
373        $hdrs = new Horde_Mime_Headers();
374        $hdrs->addHeader($header, $ob);
375
376        /* @deprecated */
377        $this->assertEquals(
378            $email,
379            $hdrs->getValue($header)
380        );
381
382        $this->assertEquals(
383            $email,
384            $hdrs[$header]->value
385        );
386    }
387
388    public function addHeaderWithGroupProvider()
389    {
390        return array(
391            array(
392                'To',
393                'Test: foo@example.com, bar@example.com;'
394            )
395        );
396    }
397
398    /**
399     * @dataProvider unencodeMimeHeaderProvider
400     */
401    public function testUnencodedMimeHeader($header, $in, $decoded)
402    {
403        $hdrs = Horde_Mime_Headers::parseHeaders($in);
404
405        /* @deprecated */
406        $this->assertEquals(
407            $decoded,
408            $hdrs->getValue($header)
409        );
410
411        $this->assertEquals(
412            $decoded,
413            $hdrs[$header]->value
414        );
415    }
416
417    public function unencodeMimeHeaderProvider()
418    {
419        return array(
420            array(
421                'From',
422                // The header is base64 encoded to preserve charset data.
423                base64_decode('RnJvbTogwqkgVklBR1JBIMKuIE9mZmljaWFsIFNpdGUgPGZvb0BleGFtcGxlLmNvbT4='),
424                '© VIAGRA ® Official Site <foo@example.com>'
425            )
426        );
427    }
428
429    /**
430     * @dataProvider parseContentDispositionHeaderWithUtf8DataProvider
431     */
432    public function testParseContentDispositionHeaderWithUtf8Data(
433        $header, $parameter, $msg, $value
434    )
435    {
436        $hdrs = Horde_Mime_Headers::parseHeaders($msg);
437
438        /* @deprecated */
439        $cd_params = $hdrs->getValue(
440            $header,
441            $hdrs::VALUE_PARAMS
442        );
443        $this->assertEquals(
444            $value,
445            $cd_params[$parameter]
446        );
447
448        $this->assertEquals(
449            $value,
450            $hdrs[$header]->params[$parameter]
451        );
452    }
453
454    public function parseContentDispositionHeaderWithUtf8DataProvider()
455    {
456        return array(
457            array(
458                'content-disposition',
459                'filename',
460                file_get_contents(__DIR__ . '/fixtures/sample_msg_eai.txt'),
461                'blåbærsyltetøy'
462            )
463        );
464    }
465
466    public function testCaseInsensitiveContentParameters()
467    {
468        $hdr = 'Content-Type: multipart/mixed; BOUNDARY="foo"';
469        $hdrs = Horde_Mime_Headers::parseHeaders($hdr);
470
471        /* @deprecated */
472        $c_params =  $hdrs->getValue(
473            'Content-Type',
474            $hdrs::VALUE_PARAMS
475        );
476        $this->assertEquals(
477            'foo',
478            $c_params['boundary']
479        );
480
481        $this->assertEquals(
482            'foo',
483            $hdrs['content-type']['boundary']
484        );
485    }
486
487    public function testParseEaiAddresses()
488    {
489        /* Simple message. */
490        $msg = file_get_contents(__DIR__ . '/fixtures/sample_msg_eai_2.txt');
491        $hdrs = Horde_Mime_Headers::parseHeaders($msg);
492
493        /* @deprecated */
494        $this->assertEquals(
495            'Jøran Øygårdvær <jøran@example.com>',
496            $hdrs->getValue('from')
497        );
498
499        $this->assertEquals(
500            'Jøran Øygårdvær <jøran@example.com>',
501            $hdrs['from']->value
502        );
503
504        /* Message with EAI addresses in 2 fields, and another header that
505         * contains a string (that looks like an address). */
506        $msg = file_get_contents(__DIR__ . '/fixtures/sample_msg_eai_4.txt');
507        $hdrs = Horde_Mime_Headers::parseHeaders($msg);
508
509        /* @deprecated */
510        $this->assertEquals(
511            'Jøran Øygårdvær <jøran@example.com>',
512            $hdrs->getValue('from')
513        );
514        $this->assertEquals(
515            'Jøran Øygårdvær <jøran@example.com>',
516            $hdrs->getValue('cc')
517        );
518        $this->assertEquals(
519            'Jøran Øygårdvær <jøran@example.com>',
520            $hdrs->getValue('signed-off-by')
521        );
522
523        $this->assertEquals(
524            'Jøran Øygårdvær <jøran@example.com>',
525            $hdrs['from']->value
526        );
527        $this->assertEquals(
528            'Jøran Øygårdvær <jøran@example.com>',
529            $hdrs['cc']->value
530        );
531        $this->assertEquals(
532            'Jøran Øygårdvær <jøran@example.com>',
533            $hdrs['signed-off-by']->value_single
534        );
535    }
536
537    /**
538     * @dataProvider undisclosedHeaderParsingProvider
539     */
540    public function testUndisclosedHeaderParsing($header, $value)
541    {
542        $hdrs = new Horde_Mime_Headers();
543        $hdrs->addHeader($header, $value);
544
545        /* @deprecated */
546        $this->assertEquals(
547            '',
548            $hdrs->getValue($header)
549        );
550
551        $this->assertEquals(
552            '',
553            $hdrs[$header]->value
554        );
555    }
556
557    public function undisclosedHeaderParsingProvider()
558    {
559        return array(
560            array('To', 'undisclosed-recipients'),
561            array('To', 'undisclosed-recipients:'),
562            array('To', 'undisclosed-recipients:;')
563        );
564    }
565
566    public function testMultipleToAddresses()
567    {
568        $msg = file_get_contents(__DIR__ . '/fixtures/multiple_to.txt');
569        $hdrs = Horde_Mime_Headers::parseHeaders($msg);
570
571        /* @deprecated */
572        $this->assertNotEmpty($hdrs->getValue('To'));
573
574        $this->assertNotEmpty($hdrs['To']->value);
575    }
576
577    public function testBug12189()
578    {
579        $msg = file_get_contents(__DIR__ . '/fixtures/header_trailing_ws.txt');
580        $hdrs = Horde_Mime_Headers::parseHeaders($msg);
581
582        /* @deprecated */
583        $this->assertNotNull($hdrs->getValue('From'));
584
585        $this->assertNotNull($hdrs['From']->value);
586    }
587
588    public function testParseHeadersGivingStreamResource()
589    {
590        $fp = fopen(__DIR__ . '/fixtures/multiple_to.txt', 'r');
591        $hdrs = Horde_Mime_Headers::parseHeaders($fp);
592        fclose($fp);
593
594        /* @deprecated */
595        $this->assertNotEmpty($hdrs->getValue('To'));
596
597        $this->assertNotEmpty($hdrs['To']->value);
598    }
599
600    public function testParseHeadersGivingHordeStreamObject()
601    {
602        $stream = new Horde_Stream_Existing(array(
603            'stream' => fopen(__DIR__ . '/fixtures/multiple_to.txt', 'r')
604        ));
605        $hdrs = Horde_Mime_Headers::parseHeaders($stream);
606
607        /* @deprecated */
608        $this->assertNotEmpty($hdrs->getValue('To'));
609
610        $this->assertNotEmpty($hdrs['To']);
611    }
612
613    public function testParseHeadersBlankSubject()
614    {
615        $stream = new Horde_Stream_Existing(array(
616            'stream' => fopen(__DIR__ . '/fixtures/blank_subject.txt', 'r')
617        ));
618        $hdrs = Horde_Mime_Headers::parseHeaders($stream);
619
620        /* @deprecated */
621        $this->assertNotEmpty($hdrs->getValue('To'));
622
623        $this->assertNotEmpty($hdrs['To']);
624    }
625
626    /**
627     * @dataProvider multiplePriorityHeadersProvider
628     */
629    public function testMultiplePriorityHeaders($header, $data, $value)
630    {
631        $hdrs = Horde_Mime_Headers::parseHeaders($data);
632
633        /* @deprecated */
634        $this->assertInternalType(
635            'string',
636            $hdrs->getValue($header, Horde_Mime_Headers::VALUE_BASE)
637        );
638        $this->assertEquals(
639            $value,
640            $hdrs->getValue($header)
641        );
642
643        $this->assertInternalType(
644            'string',
645            $hdrs[$header]->value_single
646        );
647        $this->assertEquals(
648            $value,
649            $hdrs[$header]->value_single
650        );
651
652    }
653
654    public function multiplePriorityHeadersProvider()
655    {
656        return array(
657            array(
658                'Importance',
659                "Importance: High\nImportance: Low\n",
660                'High'
661            ),
662            array(
663                'X-priority',
664                "X-Priority: 1\nX-priority: 5\n",
665                '1'
666            )
667        );
668    }
669
670    public function testInvalidHeaderParsing()
671    {
672        $data = file_get_contents(__DIR__ . '/fixtures/invalid_hdr.txt');
673
674        $hdrs = Horde_Mime_Headers::parseHeaders($data);
675
676        $this->assertEquals(
677            'foo@example.com',
678            strval($hdrs['to'])
679        );
680
681        $this->assertNull($hdrs['From test2@example.com  Sun Apr  5 09']);
682    }
683
684    /**
685     * @dataProvider addHeaderObProvider
686     */
687    public function testAddHeaderOb($ob, $valid)
688    {
689        $hdrs = new Horde_Mime_Headers();
690
691        try {
692            $hdrs->addHeaderOb($ob, true);
693            if (!$valid) {
694                $this->fail();
695            }
696        } catch (InvalidArgumentException $e) {
697            if ($valid) {
698                $this->fail();
699            }
700            return;
701        }
702
703        $this->assertEquals(
704            $ob,
705            $hdrs[$ob->name]
706        );
707    }
708
709    public function addHeaderObProvider()
710    {
711        return array(
712            array(
713                new Horde_Mime_Headers_Addresses('To', 'foo@example.com'),
714                true
715            ),
716            array(
717                new Horde_Mime_Headers_Element_Single('To', 'foo@example.com'),
718                false
719            )
720        );
721    }
722
723    /**
724     * @dataProvider headerGenerationProvider
725     */
726    public function testHeaderGeneration($label, $data, $class)
727    {
728        $hdrs = new Horde_Mime_Headers();
729
730        $this->assertNull($hdrs[$label]);
731
732        $hdrs->addHeader($label, $data);
733
734        $ob = $hdrs[$label];
735
736        $this->assertNotNull($ob);
737        $this->assertInstanceOf($class, $ob);
738    }
739
740    public function headerGenerationProvider()
741    {
742        return array(
743            array(
744                'content-disposition',
745                'inline',
746                'Horde_Mime_Headers_ContentParam_ContentDisposition'
747            ),
748            array(
749                'content-language',
750                'en',
751                'Horde_Mime_Headers_ContentLanguage'
752            ),
753            array(
754                'content-type',
755                'text/plain',
756                'Horde_Mime_Headers_ContentParam_ContentType'
757            )
758        );
759    }
760
761    public function testBug14381()
762    {
763        $hdrs = new Horde_Mime_Headers();
764        $hdrs->addHeader('Date', 'Sat, 22 May 2016 01:41:04 0000');
765        $this->assertEquals($hdrs->getValue('Date'), 'Sat, 22 May 2016 01:41:04 +0000');
766
767        $hdrs = new Horde_Mime_Headers();
768        $hdrs->addHeader('Date', 'Sat, 22 May 2016 01:41:04 +0000');
769        $this->assertEquals($hdrs->getValue('Date'), 'Sat, 22 May 2016 01:41:04 +0000');
770    }
771
772}
773