Live hack: Skip some work on empty category/link sets
[lhc/web/wiklou.git] / includes / MacBinary.php
1 <?php
2 /**
3 * MacBinary signature checker and data fork extractor, for files
4 * uploaded from Internet Explorer for Mac.
5 *
6 * Copyright (C) 2005 Brion Vibber <brion@pobox.com>
7 * Portions based on Convert::BinHex by Eryq et al
8 * http://www.mediawiki.org/
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License along
21 * with this program; if not, write to the Free Software Foundation, Inc.,
22 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 * http://www.gnu.org/copyleft/gpl.html
24 *
25 * @package MediaWiki
26 * @subpackage SpecialPage
27 */
28
29 class MacBinary {
30 function MacBinary( $filename ) {
31 $this->open( $filename );
32 $this->loadHeader();
33 }
34
35 /**
36 * The file must be seekable, such as local filesystem.
37 * Remote URLs probably won't work.
38 *
39 * @param string $filename
40 */
41 function open( $filename ) {
42 $this->valid = false;
43 $this->version = 0;
44 $this->filename = '';
45 $this->dataLength = 0;
46 $this->resourceLength = 0;
47 $this->handle = fopen( $filename, 'rb' );
48 }
49
50 /**
51 * Does this appear to be a valid MacBinary archive?
52 * @return bool
53 */
54 function isValid() {
55 return $this->valid;
56 }
57
58 /**
59 * Get length of data fork
60 * @return int
61 */
62 function dataForkLength() {
63 return $this->dataLength;
64 }
65
66 /**
67 * Copy the data fork to an external file or resource.
68 * @param resource $destination
69 * @return bool
70 */
71 function extractData( $destination ) {
72 if( !$this->isValid() ) {
73 return false;
74 }
75
76 // Data fork appears immediately after header
77 fseek( $this->handle, 128 );
78 return $this->copyBytesTo( $destination, $this->dataLength );
79 }
80
81 /**
82 *
83 */
84 function close() {
85 fclose( $this->handle );
86 }
87
88 // --------------------------------------------------------------
89
90 /**
91 * Check if the given file appears to be MacBinary-encoded,
92 * as Internet Explorer on Mac OS may provide for unknown types.
93 * http://www.lazerware.com/formats/macbinary/macbinary_iii.html
94 * If ok, load header data.
95 *
96 * @return bool
97 * @access private
98 */
99 function loadHeader() {
100 $fname = 'MacBinary::loadHeader';
101
102 fseek( $this->handle, 0 );
103 $head = fread( $this->handle, 128 );
104 $this->hexdump( $head );
105
106 if( strlen( $head ) < 128 ) {
107 wfDebug( "$fname: couldn't read full MacBinary header\n" );
108 return false;
109 }
110
111 if( $head{0} != "\x00" || $head{74} != "\x00" ) {
112 wfDebug( "$fname: header bytes 0 and 74 not null\n" );
113 return false;
114 }
115
116 $signature = substr( $head, 102, 4 );
117 $a = unpack( "ncrc", substr( $head, 124, 2 ) );
118 $storedCRC = $a['crc'];
119 $calculatedCRC = $this->calcCRC( substr( $head, 0, 124 ) );
120 if( $storedCRC == $calculatedCRC ) {
121 if( $signature == 'mBIN' ) {
122 $this->version = 3;
123 } else {
124 $this->version = 2;
125 }
126 } else {
127 $crc = sprintf( "%x != %x", $storedCRC, $calculatedCRC );
128 if( $storedCRC == 0 && $head{82} == "\x00" &&
129 substr( $head, 101, 24 ) == str_repeat( "\x00", 24 ) ) {
130 wfDebug( "$fname: no CRC, looks like MacBinary I\n" );
131 $this->version = 1;
132 } elseif( $signature == 'mBIN' && $storedCRC == 0x185 ) {
133 // Mac IE 5.0 seems to insert this value in the CRC field.
134 // 5.2.3 works correctly; don't know about other versions.
135 wfDebug( "$fname: CRC doesn't match ($crc), looks like Mac IE 5.0\n" );
136 $this->version = 3;
137 } else {
138 wfDebug( "$fname: CRC doesn't match ($crc) and not MacBinary I\n" );
139 return false;
140 }
141 }
142
143 $nameLength = ord( $head{1} );
144 if( $nameLength < 1 || $nameLength > 63 ) {
145 wfDebug( "$fname: invalid filename size $nameLength\n" );
146 return false;
147 }
148 $this->filename = substr( $head, 2, $nameLength );
149
150 $forks = unpack( "Ndata/Nresource", substr( $head, 83, 8 ) );
151 $this->dataLength = $forks['data'];
152 $this->resourceLength = $forks['resource'];
153 $maxForkLength = 0x7fffff;
154
155 if( $this->dataLength < 0 || $this->dataLength > $maxForkLength ) {
156 wfDebug( "$fname: invalid data fork length $this->dataLength\n" );
157 return false;
158 }
159
160 if( $this->resourceLength < 0 || $this->resourceLength > $maxForkLength ) {
161 wfDebug( "$fname: invalid resource fork size $this->resourceLength\n" );
162 return false;
163 }
164
165 wfDebug( "$fname: appears to be MacBinary $this->version, data length $this->dataLength\n" );
166 $this->valid = true;
167 return true;
168 }
169
170 /**
171 * Calculate a 16-bit CRC value as for MacBinary headers.
172 * Adapted from perl5 Convert::BinHex by Eryq,
173 * based on the mcvert utility (Doug Moore, April '87),
174 * with magic array thingy by Jim Van Verth.
175 * http://search.cpan.org/~eryq/Convert-BinHex-1.119/lib/Convert/BinHex.pm
176 *
177 * @param string $data
178 * @param int $seed
179 * @return int
180 * @access private
181 */
182 function calcCRC( $data, $seed = 0 ) {
183 # An array useful for CRC calculations that use 0x1021 as the "seed":
184 $MAGIC = array(
185 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
186 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
187 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
188 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
189 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
190 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
191 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
192 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
193 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
194 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
195 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
196 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
197 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
198 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
199 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
200 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
201 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
202 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
203 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
204 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
205 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
206 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
207 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
208 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
209 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
210 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
211 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
212 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
213 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
214 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
215 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
216 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
217 );
218 $len = strlen( $data );
219 $crc = $seed;
220 for( $i = 0; $i < $len; $i++ ) {
221 $crc ^= ord( $data{$i} ) << 8;
222 $crc &= 0xFFFF;
223 $crc = ($crc << 8) ^ $MAGIC[$crc >> 8];
224 $crc &= 0xFFFF;
225 }
226 return $crc;
227 }
228
229 /**
230 * @param resource $destination
231 * @param int $bytesToCopy
232 * @return bool
233 * @access private
234 */
235 function copyBytesTo( $destination, $bytesToCopy ) {
236 $bufferSize = 65536;
237 for( $remaining = $bytesToCopy; $remaining > 0; $remaining -= $bufferSize ) {
238 $thisChunkSize = min( $remaining, $bufferSize );
239 $buffer = fread( $this->handle, $thisChunkSize );
240 fwrite( $destination, $buffer );
241 }
242 }
243
244 /**
245 * Hex dump of the header for debugging
246 * @access private
247 */
248 function hexdump( $data ) {
249 global $wgDebugLogFile;
250 if( !$wgDebugLogFile ) return;
251
252 $width = 16;
253 $at = 0;
254 for( $remaining = strlen( $data ); $remaining > 0; $remaining -= $width ) {
255 $line = sprintf( "%04x:", $at );
256 $printable = '';
257 for( $i = 0; $i < $width; $i++ ) {
258 $byte = ord( $data{$at++} );
259 $line .= sprintf( " %02x", $byte );
260 $printable .= ($byte >= 32 && $byte <= 126 )
261 ? chr( $byte )
262 : '.';
263 }
264 wfDebug( "MacBinary: $line $printable\n" );
265 }
266 }
267 }
268
269 ?>