@@ -295,13 +295,88 @@ public function addFile($file, $fileinfo = '')
295295 throw new ArchiveIOException ('Archive has been closed, files can no longer be added ' );
296296 }
297297
298- $ data = @file_get_contents ($ file );
299- if ($ data === false ) {
298+ $ fp = @fopen ($ file, ' rb ' );
299+ if ($ fp === false ) {
300300 throw new ArchiveIOException ('Could not open file for reading: ' .$ file );
301301 }
302302
303- // FIXME could we stream writing compressed data? gzwrite on a fopen handle?
304- $ this ->addData ($ fileinfo , $ data );
303+ $ offset = $ this ->dataOffset ();
304+ $ name = $ fileinfo ->getPath ();
305+ $ time = $ fileinfo ->getMtime ();
306+
307+ // write local file header (temporary CRC and size)
308+ $ this ->writebytes ($ this ->makeLocalFileHeader (
309+ $ time ,
310+ 0 ,
311+ 0 ,
312+ 0 ,
313+ $ name ,
314+ (bool ) $ this ->complevel
315+ ));
316+
317+ // we store no encryption header
318+
319+ // prepare info, compress and write data to archive
320+ $ deflate_context = deflate_init (ZLIB_ENCODING_DEFLATE , ['level ' => $ this ->complevel ]);
321+ $ crc_context = hash_init ('crc32b ' );
322+ $ size = $ csize = 0 ;
323+
324+ while (!feof ($ fp )) {
325+ $ block = fread ($ fp , 512 );
326+
327+ if ($ this ->complevel ) {
328+ $ is_first_block = $ size === 0 ;
329+ $ is_last_block = feof ($ fp );
330+
331+ if ($ is_last_block ) {
332+ $ c_block = deflate_add ($ deflate_context , $ block , ZLIB_FINISH );
333+ // get rid of the compression footer
334+ $ c_block = substr ($ c_block , 0 , -4 );
335+ } else {
336+ $ c_block = deflate_add ($ deflate_context , $ block , ZLIB_NO_FLUSH );
337+ }
338+
339+ // get rid of the compression header
340+ if ($ is_first_block ) {
341+ $ c_block = substr ($ c_block , 2 );
342+ }
343+
344+ $ csize += strlen ($ c_block );
345+ $ this ->writebytes ($ c_block );
346+ } else {
347+ $ this ->writebytes ($ block );
348+ }
349+
350+ $ size += strlen ($ block );
351+ hash_update ($ crc_context , $ block );
352+ }
353+ fclose ($ fp );
354+
355+ // update the local file header with the computed CRC and size
356+ $ crc = hexdec (hash_final ($ crc_context ));
357+ $ csize = $ this ->complevel ? $ csize : $ size ;
358+ $ this ->writebytesAt ($ this ->makeCrcAndSize (
359+ $ crc ,
360+ $ size ,
361+ $ csize ,
362+ ), $ offset + self ::LOCAL_FILE_HEADER_CRC_OFFSET );
363+
364+ // we store no data descriptor
365+
366+ // add info to central file directory
367+ $ this ->ctrl_dir [] = $ this ->makeCentralFileRecord (
368+ $ offset ,
369+ $ time ,
370+ $ crc ,
371+ $ size ,
372+ $ csize ,
373+ $ name ,
374+ (bool ) $ this ->complevel
375+ );
376+
377+ if (is_callable ($ this ->callback )) {
378+ call_user_func ($ this ->callback , $ fileinfo );
379+ }
305380 }
306381
307382 /**
@@ -709,6 +784,29 @@ protected function writebytes($data)
709784 return $ written ;
710785 }
711786
787+ /**
788+ * Write to the open filepointer or memory at the specified offset
789+ *
790+ * @param string $data
791+ * @param int $offset
792+ * @throws ArchiveIOException
793+ * @return int number of bytes written
794+ */
795+ protected function writebytesAt ($ data , $ offset ) {
796+ if (!$ this ->file ) {
797+ $ this ->memory .= substr_replace ($ this ->memory , $ data , $ offset );
798+ $ written = strlen ($ data );
799+ } else {
800+ @fseek ($ this ->fh , $ offset );
801+ $ written = @fwrite ($ this ->fh , $ data );
802+ @fseek ($ this ->fh , 0 , SEEK_END );
803+ }
804+ if ($ written === false ) {
805+ throw new ArchiveIOException ('Failed to write to archive stream ' );
806+ }
807+ return $ written ;
808+ }
809+
712810 /**
713811 * Current data pointer position
714812 *
@@ -825,6 +923,8 @@ protected function makeCentralFileRecord($offset, $ts, $crc, $len, $clen, $name,
825923 return $ header ;
826924 }
827925
926+ const LOCAL_FILE_HEADER_CRC_OFFSET = 14 ;
927+
828928 /**
829929 * Returns a local file header for the given data
830930 *
@@ -865,6 +965,22 @@ protected function makeLocalFileHeader($ts, $crc, $len, $clen, $name, $comp = nu
865965 return $ header ;
866966 }
867967
968+ /**
969+ * Returns only a part of the local file header containing the CRC, size and compressed size.
970+ * Used to update these fields for an already written header.
971+ *
972+ * @param int $crc CRC32 checksum of the uncompressed data
973+ * @param int $len length of the uncompressed data
974+ * @param int $clen length of the compressed data
975+ * @return string
976+ */
977+ protected function makeCrcAndSize ($ crc , $ len , $ clen ) {
978+ $ header = pack ('V ' , $ crc ); // crc-32
979+ $ header .= pack ('V ' , $ clen ); // compressed size
980+ $ header .= pack ('V ' , $ len ); // uncompressed size
981+ return $ header ;
982+ }
983+
868984 /**
869985 * Returns an allowed filename and an extra field header
870986 *
0 commit comments