* API: All pages list
[mediawiki.git] / includes / MacBinary.php
blob05c3ce5cfd992b12357bcb0da4453ca1c9882489
1 <?php
2 /**
3 * MacBinary signature checker and data fork extractor, for files
4 * uploaded from Internet Explorer for Mac.
6 * Copyright (C) 2005 Brion Vibber <brion@pobox.com>
7 * Portions based on Convert::BinHex by Eryq et al
8 * http://www.mediawiki.org/
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.
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.
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 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 * http://www.gnu.org/copyleft/gpl.html
25 * @package MediaWiki
26 * @subpackage SpecialPage
29 class MacBinary {
30 function MacBinary( $filename ) {
31 $this->open( $filename );
32 $this->loadHeader();
35 /**
36 * The file must be seekable, such as local filesystem.
37 * Remote URLs probably won't work.
39 * @param string $filename
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' );
50 /**
51 * Does this appear to be a valid MacBinary archive?
52 * @return bool
54 function isValid() {
55 return $this->valid;
58 /**
59 * Get length of data fork
60 * @return int
62 function dataForkLength() {
63 return $this->dataLength;
66 /**
67 * Copy the data fork to an external file or resource.
68 * @param resource $destination
69 * @return bool
71 function extractData( $destination ) {
72 if( !$this->isValid() ) {
73 return false;
76 // Data fork appears immediately after header
77 fseek( $this->handle, 128 );
78 return $this->copyBytesTo( $destination, $this->dataLength );
81 /**
84 function close() {
85 fclose( $this->handle );
88 // --------------------------------------------------------------
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.
96 * @return bool
97 * @access private
99 function loadHeader() {
100 $fname = 'MacBinary::loadHeader';
102 fseek( $this->handle, 0 );
103 $head = fread( $this->handle, 128 );
104 $this->hexdump( $head );
106 if( strlen( $head ) < 128 ) {
107 wfDebug( "$fname: couldn't read full MacBinary header\n" );
108 return false;
111 if( $head{0} != "\x00" || $head{74} != "\x00" ) {
112 wfDebug( "$fname: header bytes 0 and 74 not null\n" );
113 return false;
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;
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;
143 $nameLength = ord( $head{1} );
144 if( $nameLength < 1 || $nameLength > 63 ) {
145 wfDebug( "$fname: invalid filename size $nameLength\n" );
146 return false;
148 $this->filename = substr( $head, 2, $nameLength );
150 $forks = unpack( "Ndata/Nresource", substr( $head, 83, 8 ) );
151 $this->dataLength = $forks['data'];
152 $this->resourceLength = $forks['resource'];
153 $maxForkLength = 0x7fffff;
155 if( $this->dataLength < 0 || $this->dataLength > $maxForkLength ) {
156 wfDebug( "$fname: invalid data fork length $this->dataLength\n" );
157 return false;
160 if( $this->resourceLength < 0 || $this->resourceLength > $maxForkLength ) {
161 wfDebug( "$fname: invalid resource fork size $this->resourceLength\n" );
162 return false;
165 wfDebug( "$fname: appears to be MacBinary $this->version, data length $this->dataLength\n" );
166 $this->valid = true;
167 return true;
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
177 * @param string $data
178 * @param int $seed
179 * @return int
180 * @access private
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
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;
226 return $crc;
230 * @param resource $destination
231 * @param int $bytesToCopy
232 * @return bool
233 * @access private
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 );
245 * Hex dump of the header for debugging
246 * @access private
248 function hexdump( $data ) {
249 global $wgDebugLogFile;
250 if( !$wgDebugLogFile ) return;
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 && $remaining - $i > 0; $i++ ) {
258 $byte = ord( $data{$at++} );
259 $line .= sprintf( " %02x", $byte );
260 $printable .= ($byte >= 32 && $byte <= 126 )
261 ? chr( $byte )
262 : '.';
264 if( $i < $width ) {
265 $line .= str_repeat( ' ', $width - $i );
267 wfDebug( "MacBinary: $line $printable\n" );