7becad2bb8f2f06e242bc6c2983e21aaeee3a8e1
[lhc/web/wiklou.git] / tests / phpunit / includes / libs / xmp / XMPTest.php
1 <?php
2
3 /**
4 * @group Media
5 * @covers XMPReader
6 */
7 class XMPTest extends PHPUnit_Framework_TestCase {
8
9 use MediaWikiCoversValidator;
10
11 protected function setUp() {
12 parent::setUp();
13 # Requires libxml to do XMP parsing
14 if ( !extension_loaded( 'exif' ) ) {
15 $this->markTestSkipped( "PHP extension 'exif' is not loaded, skipping." );
16 }
17 }
18
19 /**
20 * Put XMP in, compare what comes out...
21 *
22 * @param string $xmp The actual xml data.
23 * @param array $expected Expected result of parsing the xmp.
24 * @param string $info Short sentence on what's being tested.
25 *
26 * @throws Exception
27 * @dataProvider provideXMPParse
28 *
29 * @covers XMPReader::parse
30 */
31 public function testXMPParse( $xmp, $expected, $info ) {
32 if ( !is_string( $xmp ) || !is_array( $expected ) ) {
33 throw new Exception( "Invalid data provided to " . __METHOD__ );
34 }
35 $reader = new XMPReader;
36 $reader->parse( $xmp );
37 $this->assertEquals( $expected, $reader->getResults(), $info, 0.0000000001 );
38 }
39
40 public static function provideXMPParse() {
41 $xmpPath = __DIR__ . '/../../../data/xmp/';
42 $data = [];
43
44 // $xmpFiles format: array of arrays with first arg file base name,
45 // with the actual file having .xmp on the end for the xmp
46 // and .result.php on the end for a php file containing the result
47 // array. Second argument is some info on what's being tested.
48 $xmpFiles = [
49 [ '1', 'parseType=Resource test' ],
50 [ '2', 'Structure with mixed attribute and element props' ],
51 [ '3', 'Extra qualifiers (that should be ignored)' ],
52 [ '3-invalid', 'Test ignoring qualifiers that look like normal props' ],
53 [ '4', 'Flash as qualifier' ],
54 [ '5', 'Flash as qualifier 2' ],
55 [ '6', 'Multiple rdf:Description' ],
56 [ '7', 'Generic test of several property types' ],
57 [ 'flash', 'Test of Flash property' ],
58 [ 'invalid-child-not-struct', 'Test child props not in struct or ignored' ],
59 [ 'no-recognized-props', 'Test namespace and no recognized props' ],
60 [ 'no-namespace', 'Test non-namespaced attributes are ignored' ],
61 [ 'bag-for-seq', "Allow bag's instead of seq's. (T29105)" ],
62 [ 'utf16BE', 'UTF-16BE encoding' ],
63 [ 'utf16LE', 'UTF-16LE encoding' ],
64 [ 'utf32BE', 'UTF-32BE encoding' ],
65 [ 'utf32LE', 'UTF-32LE encoding' ],
66 [ 'xmpExt', 'Extended XMP missing second part' ],
67 [ 'gps', 'Handling of exif GPS parameters in XMP' ],
68 ];
69
70 $xmpFiles[] = [ 'doctype-included', 'XMP includes doctype' ];
71
72 foreach ( $xmpFiles as $file ) {
73 $xmp = file_get_contents( $xmpPath . $file[0] . '.xmp' );
74 // I'm not sure if this is the best way to handle getting the
75 // result array, but it seems kind of big to put directly in the test
76 // file.
77 $result = null;
78 include $xmpPath . $file[0] . '.result.php';
79 $data[] = [ $xmp, $result, '[' . $file[0] . '.xmp] ' . $file[1] ];
80 }
81
82 return $data;
83 }
84
85 /** Test ExtendedXMP block support. (Used when the XMP has to be split
86 * over multiple jpeg segments, due to 64k size limit on jpeg segments.
87 *
88 * @todo This is based on what the standard says. Need to find a real
89 * world example file to double check the support for this is right.
90 *
91 * @covers XMPReader::parseExtended
92 */
93 public function testExtendedXMP() {
94 $xmpPath = __DIR__ . '/../../../data/xmp/';
95 $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
96 $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
97
98 $md5sum = '28C74E0AC2D796886759006FBE2E57B7'; // of xmpExt2.xmp
99 $length = pack( 'N', strlen( $extendedXMP ) );
100 $offset = pack( 'N', 0 );
101 $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
102
103 $reader = new XMPReader();
104 $reader->parse( $standardXMP );
105 $reader->parseExtended( $extendedPacket );
106 $actual = $reader->getResults();
107
108 $expected = [
109 'xmp-exif' => [
110 'DigitalZoomRatio' => '0/10',
111 'Flash' => 9,
112 'FNumber' => '2/10',
113 ]
114 ];
115
116 $this->assertEquals( $expected, $actual );
117 }
118
119 /**
120 * This test has an extended XMP block with a wrong guid (md5sum)
121 * and thus should only return the StandardXMP, not the ExtendedXMP.
122 *
123 * @covers XMPReader::parseExtended
124 */
125 public function testExtendedXMPWithWrongGUID() {
126 $xmpPath = __DIR__ . '/../../../data/xmp/';
127 $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
128 $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
129
130 $md5sum = '28C74E0AC2D796886759006FBE2E57B9'; // Note last digit.
131 $length = pack( 'N', strlen( $extendedXMP ) );
132 $offset = pack( 'N', 0 );
133 $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
134
135 $reader = new XMPReader();
136 $reader->parse( $standardXMP );
137 $reader->parseExtended( $extendedPacket );
138 $actual = $reader->getResults();
139
140 $expected = [
141 'xmp-exif' => [
142 'DigitalZoomRatio' => '0/10',
143 'Flash' => 9,
144 ]
145 ];
146
147 $this->assertEquals( $expected, $actual );
148 }
149
150 /**
151 * Have a high offset to simulate a missing packet,
152 * which should cause it to ignore the ExtendedXMP packet.
153 *
154 * @covers XMPReader::parseExtended
155 */
156 public function testExtendedXMPMissingPacket() {
157 $xmpPath = __DIR__ . '/../../../data/xmp/';
158 $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
159 $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
160
161 $md5sum = '28C74E0AC2D796886759006FBE2E57B7'; // of xmpExt2.xmp
162 $length = pack( 'N', strlen( $extendedXMP ) );
163 $offset = pack( 'N', 2048 );
164 $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
165
166 $reader = new XMPReader();
167 $reader->parse( $standardXMP );
168 $reader->parseExtended( $extendedPacket );
169 $actual = $reader->getResults();
170
171 $expected = [
172 'xmp-exif' => [
173 'DigitalZoomRatio' => '0/10',
174 'Flash' => 9,
175 ]
176 ];
177
178 $this->assertEquals( $expected, $actual );
179 }
180
181 /**
182 * Test for multi-section, hostile XML
183 * @covers XMPReader::checkParseSafety
184 */
185 public function testCheckParseSafety() {
186 // Test for detection
187 $xmpPath = __DIR__ . '/../../../data/xmp/';
188 $file = fopen( $xmpPath . 'doctype-included.xmp', 'rb' );
189 $valid = false;
190 $reader = new XMPReader();
191 do {
192 $chunk = fread( $file, 10 );
193 $valid = $reader->parse( $chunk, feof( $file ) );
194 } while ( !feof( $file ) );
195 $this->assertFalse( $valid, 'Check that doctype is detected in fragmented XML' );
196 $this->assertEquals(
197 [],
198 $reader->getResults(),
199 'Check that doctype is detected in fragmented XML'
200 );
201 fclose( $file );
202 unset( $reader );
203
204 // Test for false positives
205 $file = fopen( $xmpPath . 'doctype-not-included.xmp', 'rb' );
206 $valid = false;
207 $reader = new XMPReader();
208 do {
209 $chunk = fread( $file, 10 );
210 $valid = $reader->parse( $chunk, feof( $file ) );
211 } while ( !feof( $file ) );
212 $this->assertTrue(
213 $valid,
214 'Check for false-positive detecting doctype in fragmented XML'
215 );
216 $this->assertEquals(
217 [
218 'xmp-exif' => [
219 'DigitalZoomRatio' => '0/10',
220 'Flash' => '9'
221 ]
222 ],
223 $reader->getResults(),
224 'Check that doctype is detected in fragmented XML'
225 );
226 }
227 }