MediaWiki REL1_28
GIFMetadataExtractor.php
Go to the documentation of this file.
1<?php
36 private static $gifFrameSep;
37
39 private static $gifExtensionSep;
40
42 private static $gifTerm;
43
44 const VERSION = 1;
45
46 // Each sub-block is less than or equal to 255 bytes.
47 // Most of the time its 255 bytes, except for in XMP
48 // blocks, where it's usually between 32-127 bytes each.
49 const MAX_SUBBLOCKS = 262144; // 5mb divided by 20.
50
56 static function getMetadata( $filename ) {
57 self::$gifFrameSep = pack( "C", ord( "," ) );
58 self::$gifExtensionSep = pack( "C", ord( "!" ) );
59 self::$gifTerm = pack( "C", ord( ";" ) );
60
61 $frameCount = 0;
62 $duration = 0.0;
63 $isLooped = false;
64 $xmp = "";
65 $comment = [];
66
67 if ( !$filename ) {
68 throw new Exception( "No file name specified" );
69 } elseif ( !file_exists( $filename ) || is_dir( $filename ) ) {
70 throw new Exception( "File $filename does not exist" );
71 }
72
73 $fh = fopen( $filename, 'rb' );
74
75 if ( !$fh ) {
76 throw new Exception( "Unable to open file $filename" );
77 }
78
79 // Check for the GIF header
80 $buf = fread( $fh, 6 );
81 if ( !( $buf == 'GIF87a' || $buf == 'GIF89a' ) ) {
82 throw new Exception( "Not a valid GIF file; header: $buf" );
83 }
84
85 // Skip over width and height.
86 fread( $fh, 4 );
87
88 // Read BPP
89 $buf = fread( $fh, 1 );
90 $bpp = self::decodeBPP( $buf );
91
92 // Skip over background and aspect ratio
93 fread( $fh, 2 );
94
95 // Skip over the GCT
96 self::readGCT( $fh, $bpp );
97
98 while ( !feof( $fh ) ) {
99 $buf = fread( $fh, 1 );
100
101 if ( $buf == self::$gifFrameSep ) {
102 // Found a frame
103 $frameCount++;
104
105 # # Skip bounding box
106 fread( $fh, 8 );
107
108 # # Read BPP
109 $buf = fread( $fh, 1 );
110 $bpp = self::decodeBPP( $buf );
111
112 # # Read GCT
113 self::readGCT( $fh, $bpp );
114 fread( $fh, 1 );
115 self::skipBlock( $fh );
116 } elseif ( $buf == self::$gifExtensionSep ) {
117 $buf = fread( $fh, 1 );
118 if ( strlen( $buf ) < 1 ) {
119 throw new Exception( "Ran out of input" );
120 }
121 $extension_code = unpack( 'C', $buf )[1];
122
123 if ( $extension_code == 0xF9 ) {
124 // Graphics Control Extension.
125 fread( $fh, 1 ); // Block size
126
127 fread( $fh, 1 ); // Transparency, disposal method, user input
128
129 $buf = fread( $fh, 2 ); // Delay, in hundredths of seconds.
130 if ( strlen( $buf ) < 2 ) {
131 throw new Exception( "Ran out of input" );
132 }
133 $delay = unpack( 'v', $buf )[1];
134 $duration += $delay * 0.01;
135
136 fread( $fh, 1 ); // Transparent colour index
137
138 $term = fread( $fh, 1 ); // Should be a terminator
139 if ( strlen( $term ) < 1 ) {
140 throw new Exception( "Ran out of input" );
141 }
142 $term = unpack( 'C', $term )[1];
143 if ( $term != 0 ) {
144 throw new Exception( "Malformed Graphics Control Extension block" );
145 }
146 } elseif ( $extension_code == 0xFE ) {
147 // Comment block(s).
148 $data = self::readBlock( $fh );
149 if ( $data === "" ) {
150 throw new Exception( 'Read error, zero-length comment block' );
151 }
152
153 // The standard says this should be ASCII, however its unclear if
154 // thats true in practise. Check to see if its valid utf-8, if so
155 // assume its that, otherwise assume its windows-1252 (iso-8859-1)
156 $dataCopy = $data;
157 // quickIsNFCVerify has the side effect of replacing any invalid characters
158 UtfNormal\Validator::quickIsNFCVerify( $dataCopy );
159
160 if ( $dataCopy !== $data ) {
161 MediaWiki\suppressWarnings();
162 $data = iconv( 'windows-1252', 'UTF-8', $data );
163 MediaWiki\restoreWarnings();
164 }
165
166 $commentCount = count( $comment );
167 if ( $commentCount === 0
168 || $comment[$commentCount - 1] !== $data
169 ) {
170 // Some applications repeat the same comment on each
171 // frame of an animated GIF image, so if this comment
172 // is identical to the last, only extract once.
173 $comment[] = $data;
174 }
175 } elseif ( $extension_code == 0xFF ) {
176 // Application extension (Netscape info about the animated gif)
177 // or XMP (or theoretically any other type of extension block)
178 $blockLength = fread( $fh, 1 );
179 if ( strlen( $blockLength ) < 1 ) {
180 throw new Exception( "Ran out of input" );
181 }
182 $blockLength = unpack( 'C', $blockLength )[1];
183 $data = fread( $fh, $blockLength );
184
185 if ( $blockLength != 11 ) {
186 wfDebug( __METHOD__ . " GIF application block with wrong length\n" );
187 fseek( $fh, -( $blockLength + 1 ), SEEK_CUR );
188 self::skipBlock( $fh );
189 continue;
190 }
191
192 // NETSCAPE2.0 (application name for animated gif)
193 if ( $data == 'NETSCAPE2.0' ) {
194 $data = fread( $fh, 2 ); // Block length and introduction, should be 03 01
195
196 if ( $data != "\x03\x01" ) {
197 throw new Exception( "Expected \x03\x01, got $data" );
198 }
199
200 // Unsigned little-endian integer, loop count or zero for "forever"
201 $loopData = fread( $fh, 2 );
202 if ( strlen( $loopData ) < 2 ) {
203 throw new Exception( "Ran out of input" );
204 }
205 $loopCount = unpack( 'v', $loopData )[1];
206
207 if ( $loopCount != 1 ) {
208 $isLooped = true;
209 }
210
211 // Read out terminator byte
212 fread( $fh, 1 );
213 } elseif ( $data == 'XMP DataXMP' ) {
214 // application name for XMP data.
215 // see pg 18 of XMP spec part 3.
216
217 $xmp = self::readBlock( $fh, true );
218
219 if ( substr( $xmp, -257, 3 ) !== "\x01\xFF\xFE"
220 || substr( $xmp, -4 ) !== "\x03\x02\x01\x00"
221 ) {
222 // this is just a sanity check.
223 throw new Exception( "XMP does not have magic trailer!" );
224 }
225
226 // strip out trailer.
227 $xmp = substr( $xmp, 0, -257 );
228 } else {
229 // unrecognized extension block
230 fseek( $fh, -( $blockLength + 1 ), SEEK_CUR );
231 self::skipBlock( $fh );
232 continue;
233 }
234 } else {
235 self::skipBlock( $fh );
236 }
237 } elseif ( $buf == self::$gifTerm ) {
238 break;
239 } else {
240 if ( strlen( $buf ) < 1 ) {
241 throw new Exception( "Ran out of input" );
242 }
243 $byte = unpack( 'C', $buf )[1];
244 throw new Exception( "At position: " . ftell( $fh ) . ", Unknown byte " . $byte );
245 }
246 }
247
248 return [
249 'frameCount' => $frameCount,
250 'looped' => $isLooped,
251 'duration' => $duration,
252 'xmp' => $xmp,
253 'comment' => $comment,
254 ];
255 }
256
262 static function readGCT( $fh, $bpp ) {
263 if ( $bpp > 0 ) {
264 $max = pow( 2, $bpp );
265 for ( $i = 1; $i <= $max; ++$i ) {
266 fread( $fh, 3 );
267 }
268 }
269 }
270
276 static function decodeBPP( $data ) {
277 if ( strlen( $data ) < 1 ) {
278 throw new Exception( "Ran out of input" );
279 }
280 $buf = unpack( 'C', $data )[1];
281 $bpp = ( $buf & 7 ) + 1;
282 $buf >>= 7;
283
284 $have_map = $buf & 1;
285
286 return $have_map ? $bpp : 0;
287 }
288
293 static function skipBlock( $fh ) {
294 while ( !feof( $fh ) ) {
295 $buf = fread( $fh, 1 );
296 if ( strlen( $buf ) < 1 ) {
297 throw new Exception( "Ran out of input" );
298 }
299 $block_len = unpack( 'C', $buf )[1];
300 if ( $block_len == 0 ) {
301 return;
302 }
303 fread( $fh, $block_len );
304 }
305 }
306
321 static function readBlock( $fh, $includeLengths = false ) {
322 $data = '';
323 $subLength = fread( $fh, 1 );
324 $blocks = 0;
325
326 while ( $subLength !== "\0" ) {
327 $blocks++;
328 if ( $blocks > self::MAX_SUBBLOCKS ) {
329 throw new Exception( "MAX_SUBBLOCKS exceeded (over $blocks sub-blocks)" );
330 }
331 if ( feof( $fh ) ) {
332 throw new Exception( "Read error: Unexpected EOF." );
333 }
334 if ( $includeLengths ) {
335 $data .= $subLength;
336 }
337
338 $data .= fread( $fh, ord( $subLength ) );
339 $subLength = fread( $fh, 1 );
340 }
341
342 return $data;
343 }
344}
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
static getMetadata( $filename)
static readBlock( $fh, $includeLengths=false)
Read a block.
external whereas SearchGetNearMatch runs after $term
Definition hooks.txt:2719
$comment
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37