summaryrefslogtreecommitdiff
path: root/tests/phpunit/includes/media/XMPTest.php
blob: 6758e94c6f56956ec9a7af52d3e8041a6dc4976e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
<?php

/**
 * @group Media
 * @covers XMPReader
 */
class XMPTest extends MediaWikiTestCase {

	protected function setUp() {
		parent::setUp();
		$this->checkPHPExtension( 'exif' ); # Requires libxml to do XMP parsing
	}

	/**
	 * Put XMP in, compare what comes out...
	 *
	 * @param string $xmp The actual xml data.
	 * @param array $expected Expected result of parsing the xmp.
	 * @param string $info Short sentence on what's being tested.
	 *
	 * @throws Exception
	 * @dataProvider provideXMPParse
	 *
	 * @covers XMPReader::parse
	 */
	public function testXMPParse( $xmp, $expected, $info ) {
		if ( !is_string( $xmp ) || !is_array( $expected ) ) {
			throw new Exception( "Invalid data provided to " . __METHOD__ );
		}
		$reader = new XMPReader;
		$reader->parse( $xmp );
		$this->assertEquals( $expected, $reader->getResults(), $info, 0.0000000001 );
	}

	public static function provideXMPParse() {
		$xmpPath = __DIR__ . '/../../data/xmp/';
		$data = array();

		// $xmpFiles format: array of arrays with first arg file base name,
		// with the actual file having .xmp on the end for the xmp
		// and .result.php on the end for a php file containing the result
		// array. Second argument is some info on what's being tested.
		$xmpFiles = array(
			array( '1', 'parseType=Resource test' ),
			array( '2', 'Structure with mixed attribute and element props' ),
			array( '3', 'Extra qualifiers (that should be ignored)' ),
			array( '3-invalid', 'Test ignoring qualifiers that look like normal props' ),
			array( '4', 'Flash as qualifier' ),
			array( '5', 'Flash as qualifier 2' ),
			array( '6', 'Multiple rdf:Description' ),
			array( '7', 'Generic test of several property types' ),
			array( 'flash', 'Test of Flash property' ),
			array( 'invalid-child-not-struct', 'Test child props not in struct or ignored' ),
			array( 'no-recognized-props', 'Test namespace and no recognized props' ),
			array( 'no-namespace', 'Test non-namespaced attributes are ignored' ),
			array( 'bag-for-seq', "Allow bag's instead of seq's. (bug 27105)" ),
			array( 'utf16BE', 'UTF-16BE encoding' ),
			array( 'utf16LE', 'UTF-16LE encoding' ),
			array( 'utf32BE', 'UTF-32BE encoding' ),
			array( 'utf32LE', 'UTF-32LE encoding' ),
			array( 'xmpExt', 'Extended XMP missing second part' ),
			array( 'gps', 'Handling of exif GPS parameters in XMP' ),
		);

		$xmpFiles[] = array( 'doctype-included', 'XMP includes doctype' );

		foreach ( $xmpFiles as $file ) {
			$xmp = file_get_contents( $xmpPath . $file[0] . '.xmp' );
			// I'm not sure if this is the best way to handle getting the
			// result array, but it seems kind of big to put directly in the test
			// file.
			$result = null;
			include $xmpPath . $file[0] . '.result.php';
			$data[] = array( $xmp, $result, '[' . $file[0] . '.xmp] ' . $file[1] );
		}

		return $data;
	}

	/** Test ExtendedXMP block support. (Used when the XMP has to be split
	 * over multiple jpeg segments, due to 64k size limit on jpeg segments.
	 *
	 * @todo This is based on what the standard says. Need to find a real
	 * world example file to double check the support for this is right.
	 *
	 * @covers XMPReader::parseExtended
	 */
	public function testExtendedXMP() {
		$xmpPath = __DIR__ . '/../../data/xmp/';
		$standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
		$extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );

		$md5sum = '28C74E0AC2D796886759006FBE2E57B7'; // of xmpExt2.xmp
		$length = pack( 'N', strlen( $extendedXMP ) );
		$offset = pack( 'N', 0 );
		$extendedPacket = $md5sum . $length . $offset . $extendedXMP;

		$reader = new XMPReader();
		$reader->parse( $standardXMP );
		$reader->parseExtended( $extendedPacket );
		$actual = $reader->getResults();

		$expected = array(
			'xmp-exif' => array(
				'DigitalZoomRatio' => '0/10',
				'Flash' => 9,
				'FNumber' => '2/10',
			)
		);

		$this->assertEquals( $expected, $actual );
	}

	/**
	 * This test has an extended XMP block with a wrong guid (md5sum)
	 * and thus should only return the StandardXMP, not the ExtendedXMP.
	 *
	 * @covers XMPReader::parseExtended
	 */
	public function testExtendedXMPWithWrongGUID() {
		$xmpPath = __DIR__ . '/../../data/xmp/';
		$standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
		$extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );

		$md5sum = '28C74E0AC2D796886759006FBE2E57B9'; // Note last digit.
		$length = pack( 'N', strlen( $extendedXMP ) );
		$offset = pack( 'N', 0 );
		$extendedPacket = $md5sum . $length . $offset . $extendedXMP;

		$reader = new XMPReader();
		$reader->parse( $standardXMP );
		$reader->parseExtended( $extendedPacket );
		$actual = $reader->getResults();

		$expected = array(
			'xmp-exif' => array(
				'DigitalZoomRatio' => '0/10',
				'Flash' => 9,
			)
		);

		$this->assertEquals( $expected, $actual );
	}

	/**
	 * Have a high offset to simulate a missing packet,
	 * which should cause it to ignore the ExtendedXMP packet.
	 *
	 * @covers XMPReader::parseExtended
	 */
	public function testExtendedXMPMissingPacket() {
		$xmpPath = __DIR__ . '/../../data/xmp/';
		$standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
		$extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );

		$md5sum = '28C74E0AC2D796886759006FBE2E57B7'; // of xmpExt2.xmp
		$length = pack( 'N', strlen( $extendedXMP ) );
		$offset = pack( 'N', 2048 );
		$extendedPacket = $md5sum . $length . $offset . $extendedXMP;

		$reader = new XMPReader();
		$reader->parse( $standardXMP );
		$reader->parseExtended( $extendedPacket );
		$actual = $reader->getResults();

		$expected = array(
			'xmp-exif' => array(
				'DigitalZoomRatio' => '0/10',
				'Flash' => 9,
			)
		);

		$this->assertEquals( $expected, $actual );
	}

	/**
	 * Test for multi-section, hostile XML
	 * @covers checkParseSafety
	 */
	public function testCheckParseSafety() {

		// Test for detection
		$xmpPath = __DIR__ . '/../../data/xmp/';
		$file = fopen( $xmpPath . 'doctype-included.xmp', 'rb' );
		$valid = false;
		$reader = new XMPReader();
		do {
			$chunk = fread( $file, 10 );
			$valid = $reader->parse( $chunk, feof( $file ) );
		} while ( !feof( $file ) );
		$this->assertFalse( $valid, 'Check that doctype is detected in fragmented XML' );
		$this->assertEquals(
			array(),
			$reader->getResults(),
			'Check that doctype is detected in fragmented XML'
		);
		fclose( $file );
		unset( $reader );

		// Test for false positives
		$file = fopen( $xmpPath . 'doctype-not-included.xmp', 'rb' );
		$valid = false;
		$reader = new XMPReader();
		do {
			$chunk = fread( $file, 10 );
			$valid = $reader->parse( $chunk, feof( $file ) );
		} while ( !feof( $file ) );
		$this->assertTrue(
			$valid,
			'Check for false-positive detecting doctype in fragmented XML'
		);
		$this->assertEquals(
			array(
				'xmp-exif' => array(
					'DigitalZoomRatio' => '0/10',
					'Flash' => '9'
				)
			),
			$reader->getResults(),
			'Check that doctype is detected in fragmented XML'
		);
	}
}