Skip to content

Commit 780d63e

Browse files
committedNov 13, 2013
initital import
0 parents  commit 780d63e

File tree

4 files changed

+659
-0
lines changed

4 files changed

+659
-0
lines changed
 

‎COPYING

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Copyright (C) 2007-2009 Paul Duncan <pabs@pablotron.org>
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a
4+
copy of this software and associated documentation files (the
5+
"Software"), to deal in the Software without restriction, including
6+
without limitation the rights to use, copy, modify, merge, publish,
7+
distribute, sublicense, and/or sell copies of the Software, and to
8+
permit persons to whom the Software is furnished to do so, subject to
9+
the following conditions:
10+
11+
The above copyright notice and this permission notice shall be included
12+
in all copies or substantial portions of the of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20+
DEALINGS IN THE SOFTWARE.

‎README

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
ZipStream 0.2.2 README
2+
======================
3+
4+
Please see the file COPYING for licensing and warranty information. The
5+
latest version of this software is available at the following URL:
6+
7+
http://pablotron.org/software/zipstream-php/
8+
9+
Overview
10+
========
11+
A fast and simple streaming zip file downloader for PHP. Here's a
12+
simple example:
13+
14+
# create a new zipstream object
15+
$zip = new ZipStream('example.zip');
16+
17+
# create a file named 'hello.txt'
18+
$zip->add_file('some_image.jpg', 'This is the contents of hello.txt');
19+
20+
# add a file named 'image.jpg' from a local file 'path/to/image.jpg'
21+
$zip->add_file_from_path('some_image.jpg', 'path/to/image.jpg');
22+
23+
# finish the zip stream
24+
$zip->finish();
25+
26+
You can also add comments, modify file timestamps, and customize (or
27+
disable) the HTTP headers. See the class file for details. There are a
28+
couple of additional examples in the initial release announcement at the
29+
following URL:
30+
31+
http://pablotron.org/?cid=1535
32+
33+
Requirements
34+
============
35+
36+
* PHP version 5.1.2 or newer (specifically, the hash_init and
37+
hash_file functions).
38+
39+
About the Author
40+
================
41+
Paul Duncan <pabs@pablotron.org>
42+
http://pablotron.org/

‎composer.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "maennchen/zipstream-php",
3+
"description": " ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
4+
"keywords": ["zip", "stream"],
5+
"type": "library",
6+
"license": "All Rights Reserved",
7+
"authors": [
8+
{
9+
"name": "Paul Duncan",
10+
"email": "pabs@pablotron.org"
11+
}
12+
],
13+
"require": {
14+
"php": ">= 5.3"
15+
},
16+
"autoload": {
17+
"classmap": [ "zipstream.php" ]
18+
}
19+
}

‎zipstream.php

+578
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,578 @@
1+
<?php
2+
3+
##########################################################################
4+
# ZipStream - Streamed, dynamically generated zip archives. #
5+
# by Paul Duncan <pabs@pablotron.org> #
6+
# #
7+
# Copyright (C) 2007-2009 Paul Duncan <pabs@pablotron.org> #
8+
# #
9+
# Permission is hereby granted, free of charge, to any person obtaining #
10+
# a copy of this software and associated documentation files (the #
11+
# "Software"), to deal in the Software without restriction, including #
12+
# without limitation the rights to use, copy, modify, merge, publish, #
13+
# distribute, sublicense, and/or sell copies of the Software, and to #
14+
# permit persons to whom the Software is furnished to do so, subject to #
15+
# the following conditions: #
16+
# #
17+
# The above copyright notice and this permission notice shall be #
18+
# included in all copies or substantial portions of the of the Software. #
19+
# #
20+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, #
21+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF #
22+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. #
23+
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR #
24+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, #
25+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR #
26+
# OTHER DEALINGS IN THE SOFTWARE. #
27+
##########################################################################
28+
29+
#
30+
# ZipStream - Streamed, dynamically generated zip archives.
31+
# by Paul Duncan <pabs@pablotron.org>
32+
#
33+
# Requirements:
34+
#
35+
# * PHP version 5.1.2 or newer.
36+
#
37+
# Usage:
38+
#
39+
# Streaming zip archives is a simple, three-step process:
40+
#
41+
# 1. Create the zip stream:
42+
#
43+
# $zip = new ZipStream('example.zip');
44+
#
45+
# 2. Add one or more files to the archive:
46+
#
47+
# # add first file
48+
# $data = file_get_contents('some_file.gif');
49+
# $zip->add_file('some_file.gif', $data);
50+
#
51+
# # add second file
52+
# $data = file_get_contents('some_file.gif');
53+
# $zip->add_file('another_file.png', $data);
54+
#
55+
# 3. Finish the zip stream:
56+
#
57+
# $zip->finish();
58+
#
59+
# You can also add an archive comment, add comments to individual files,
60+
# and adjust the timestamp of files. See the API documentation for each
61+
# method below for additional information.
62+
#
63+
# Example:
64+
#
65+
# # create a new zip stream object
66+
# $zip = new ZipStream('some_files.zip');
67+
#
68+
# # list of local files
69+
# $files = array('foo.txt', 'bar.jpg');
70+
#
71+
# # read and add each file to the archive
72+
# foreach ($files as $path)
73+
# $zip->add_file($path, file_get_contents($path));
74+
#
75+
# # write archive footer to stream
76+
# $zip->finish();
77+
#
78+
class ZipStream {
79+
const VERSION = '0.2.2';
80+
81+
var $opt = array(),
82+
$files = array(),
83+
$cdr_ofs = 0,
84+
$ofs = 0;
85+
86+
#
87+
# Create a new ZipStream object.
88+
#
89+
# Parameters:
90+
#
91+
# $name - Name of output file (optional).
92+
# $opt - Hash of archive options (optional, see "Archive Options"
93+
# below).
94+
#
95+
# Archive Options:
96+
#
97+
# comment - Comment for this archive.
98+
# content_type - HTTP Content-Type. Defaults to 'application/x-zip'.
99+
# content_disposition - HTTP Content-Disposition. Defaults to
100+
# 'attachment; filename=\"FILENAME\"', where
101+
# FILENAME is the specified filename.
102+
# large_file_size - Size, in bytes, of the largest file to try
103+
# and load into memory (used by
104+
# add_file_from_path()). Large files may also
105+
# be compressed differently; see the
106+
# 'large_file_method' option.
107+
# large_file_method - How to handle large files. Legal values are
108+
# 'store' (the default), or 'deflate'. Store
109+
# sends the file raw and is significantly
110+
# faster, while 'deflate' compresses the file
111+
# and is much, much slower. Note that deflate
112+
# must compress the file twice and extremely
113+
# slow.
114+
# send_http_headers - Boolean indicating whether or not to send
115+
# the HTTP headers for this file.
116+
#
117+
# Note that content_type and content_disposition do nothing if you are
118+
# not sending HTTP headers.
119+
#
120+
# Large File Support:
121+
#
122+
# By default, the method add_file_from_path() will send send files
123+
# larger than 20 megabytes along raw rather than attempting to
124+
# compress them. You can change both the maximum size and the
125+
# compression behavior using the large_file_* options above, with the
126+
# following caveats:
127+
#
128+
# * For "small" files (e.g. files smaller than large_file_size), the
129+
# memory use can be up to twice that of the actual file. In other
130+
# words, adding a 10 megabyte file to the archive could potentially
131+
# occupty 20 megabytes of memory.
132+
#
133+
# * Enabling compression on large files (e.g. files larger than
134+
# large_file_size) is extremely slow, because ZipStream has to pass
135+
# over the large file once to calculate header information, and then
136+
# again to compress and send the actual data.
137+
#
138+
# Examples:
139+
#
140+
# # create a new zip file named 'foo.zip'
141+
# $zip = new ZipStream('foo.zip');
142+
#
143+
# # create a new zip file named 'bar.zip' with a comment
144+
# $zip = new ZipStream('bar.zip', array(
145+
# 'comment' => 'this is a comment for the zip file.',
146+
# ));
147+
#
148+
# Notes:
149+
#
150+
# If you do not set a filename, then this library _DOES NOT_ send HTTP
151+
# headers by default. This behavior is to allow software to send its
152+
# own headers (including the filename), and still use this library.
153+
#
154+
function __construct($name = null, $opt = array()) {
155+
# save options
156+
$this->opt = $opt;
157+
158+
# set large file defaults: size = 20 megabytes, method = store
159+
if (!$this->opt['large_file_size'])
160+
$this->opt['large_file_size'] = 20 * 1024 * 1024;
161+
if (!$this->opt['large_file_method'])
162+
$this->opt['large_file_method'] = 'store';
163+
164+
$this->output_name = $name;
165+
if ($name || $opt['send_http_headers'])
166+
$this->need_headers = true;
167+
}
168+
169+
#
170+
# add_file - add a file to the archive
171+
#
172+
# Parameters:
173+
#
174+
# $name - path of file in archive (including directory).
175+
# $data - contents of file
176+
# $opt - Hash of options for file (optional, see "File Options"
177+
# below).
178+
#
179+
# File Options:
180+
# time - Last-modified timestamp (seconds since the epoch) of
181+
# this file. Defaults to the current time.
182+
# comment - Comment related to this file.
183+
#
184+
# Examples:
185+
#
186+
# # add a file named 'foo.txt'
187+
# $data = file_get_contents('foo.txt');
188+
# $zip->add_file('foo.txt', $data);
189+
#
190+
# # add a file named 'bar.jpg' with a comment and a last-modified
191+
# # time of two hours ago
192+
# $data = file_get_contents('bar.jpg');
193+
# $zip->add_file('bar.jpg', $data, array(
194+
# 'time' => time() - 2 * 3600,
195+
# 'comment' => 'this is a comment about bar.jpg',
196+
# ));
197+
#
198+
function add_file($name, $data, $opt = array()) {
199+
# compress data
200+
$zdata = gzdeflate($data);
201+
202+
# calculate header attributes
203+
$crc = crc32($data);
204+
$zlen = strlen($zdata);
205+
$len = strlen($data);
206+
$meth = 0x08;
207+
208+
# send file header
209+
$this->add_file_header($name, $opt, $meth, $crc, $zlen, $len);
210+
211+
# print data
212+
$this->send($zdata);
213+
}
214+
215+
#
216+
# add_file_from_path - add a file at path to the archive.
217+
#
218+
# Note that large files may be compresed differently than smaller
219+
# files; see the "Large File Support" section above for more
220+
# information.
221+
#
222+
# Parameters:
223+
#
224+
# $name - name of file in archive (including directory path).
225+
# $path - path to file on disk (note: paths should be encoded using
226+
# UNIX-style forward slashes -- e.g '/path/to/some/file').
227+
# $opt - Hash of options for file (optional, see "File Options"
228+
# below).
229+
#
230+
# File Options:
231+
# time - Last-modified timestamp (seconds since the epoch) of
232+
# this file. Defaults to the current time.
233+
# comment - Comment related to this file.
234+
#
235+
# Examples:
236+
#
237+
# # add a file named 'foo.txt' from the local file '/tmp/foo.txt'
238+
# $zip->add_file_from_path('foo.txt', '/tmp/foo.txt');
239+
#
240+
# # add a file named 'bigfile.rar' from the local file
241+
# # '/usr/share/bigfile.rar' with a comment and a last-modified
242+
# # time of two hours ago
243+
# $path = '/usr/share/bigfile.rar';
244+
# $zip->add_file_from_path('bigfile.rar', $path, array(
245+
# 'time' => time() - 2 * 3600,
246+
# 'comment' => 'this is a comment about bar.jpg',
247+
# ));
248+
#
249+
function add_file_from_path($name, $path, $opt = array()) {
250+
if ($this->is_large_file($path)) {
251+
# file is too large to be read into memory; add progressively
252+
$this->add_large_file($name, $path, $opt);
253+
} else {
254+
# file is small enough to read into memory; read file contents and
255+
# handle with add_file()
256+
$data = file_get_contents($path);
257+
$this->add_file($name, $data, $opt);
258+
}
259+
}
260+
261+
#
262+
# finish - Write zip footer to stream.
263+
#
264+
# Example:
265+
#
266+
# # add a list of files to the archive
267+
# $files = array('foo.txt', 'bar.jpg');
268+
# foreach ($files as $path)
269+
# $zip->add_file($path, file_get_contents($path));
270+
#
271+
# # write footer to stream
272+
# $zip->finish();
273+
#
274+
function finish() {
275+
# add trailing cdr record
276+
$this->add_cdr($this->opt);
277+
$this->clear();
278+
}
279+
280+
###################
281+
# PRIVATE METHODS #
282+
###################
283+
284+
#
285+
# Create and send zip header for this file.
286+
#
287+
private function add_file_header($name, $opt, $meth, $crc, $zlen, $len) {
288+
# strip leading slashes from file name
289+
# (fixes bug in windows archive viewer)
290+
$name = preg_replace('/^\\/+/', '', $name);
291+
292+
# calculate name length
293+
$nlen = strlen($name);
294+
295+
# create dos timestamp
296+
$opt['time'] = $opt['time'] ? $opt['time'] : time();
297+
$dts = $this->dostime($opt['time']);
298+
299+
# build file header
300+
$fields = array( # (from V.A of APPNOTE.TXT)
301+
array('V', 0x04034b50), # local file header signature
302+
array('v', (6 << 8) + 3), # version needed to extract
303+
array('v', 0x00), # general purpose bit flag
304+
array('v', $meth), # compresion method (deflate or store)
305+
array('V', $dts), # dos timestamp
306+
array('V', $crc), # crc32 of data
307+
array('V', $zlen), # compressed data length
308+
array('V', $len), # uncompressed data length
309+
array('v', $nlen), # filename length
310+
array('v', 0), # extra data len
311+
);
312+
313+
# pack fields and calculate "total" length
314+
$ret = $this->pack_fields($fields);
315+
$cdr_len = strlen($ret) + $nlen + $zlen;
316+
317+
# print header and filename
318+
$this->send($ret . $name);
319+
320+
# add to central directory record and increment offset
321+
$this->add_to_cdr($name, $opt, $meth, $crc, $zlen, $len, $cdr_len);
322+
}
323+
324+
#
325+
# Add a large file from the given path.
326+
#
327+
private function add_large_file($name, $path, $opt = array()) {
328+
$st = stat($path);
329+
$block_size = 1048576; # process in 1 megabyte chunks
330+
$algo = 'crc32b';
331+
332+
# calculate header attributes
333+
$zlen = $len = $st['size'];
334+
335+
$meth_str = $this->opt['large_file_method'];
336+
if ($meth_str == 'store') {
337+
# store method
338+
$meth = 0x00;
339+
$crc = unpack('V', hash_file($algo, $path, true));
340+
$crc = $crc[1];
341+
} elseif ($meth_str == 'deflate') {
342+
# deflate method
343+
$meth = 0x08;
344+
345+
# open file, calculate crc and compressed file length
346+
$fh = fopen($path, 'rb');
347+
$hash_ctx = hash_init($algo);
348+
$zlen = 0;
349+
350+
# read each block, update crc and zlen
351+
while ($data = fgets($fh, $block_size)) {
352+
hash_update($hash_ctx, $data);
353+
$data = gzdeflate($data);
354+
$zlen += strlen($data);
355+
}
356+
357+
# close file and finalize crc
358+
fclose($fh);
359+
$crc = unpack('V', hash_final($hash_ctx, true));
360+
$crc = $crc[1];
361+
} else {
362+
die("unknown large_file_method: $meth_str");
363+
}
364+
365+
# send file header
366+
$this->add_file_header($name, $opt, $meth, $crc, $zlen, $len);
367+
368+
# open input file
369+
$fh = fopen($path, 'rb');
370+
371+
# send file blocks
372+
while ($data = fgets($fh, $block_size)) {
373+
if ($meth_str == 'deflate')
374+
$data = gzdeflate($data);
375+
376+
# send data
377+
$this->send($data);
378+
}
379+
380+
# close input file
381+
fclose($fh);
382+
}
383+
384+
#
385+
# Is this file larger than large_file_size?
386+
#
387+
function is_large_file($path) {
388+
$st = stat($path);
389+
return ($this->opt['large_file_size'] > 0) &&
390+
($st['size'] > $this->opt['large_file_size']);
391+
}
392+
393+
#
394+
# Save file attributes for trailing CDR record.
395+
#
396+
private function add_to_cdr($name, $opt, $meth, $crc, $zlen, $len, $rec_len) {
397+
$this->files[] = array($name, $opt, $meth, $crc, $zlen, $len, $this->ofs);
398+
$this->ofs += $rec_len;
399+
}
400+
401+
#
402+
# Send CDR record for specified file.
403+
#
404+
private function add_cdr_file($args) {
405+
list ($name, $opt, $meth, $crc, $zlen, $len, $ofs) = $args;
406+
407+
# get attributes
408+
$comment = $opt['comment'] ? $opt['comment'] : '';
409+
410+
# get dos timestamp
411+
$dts = $this->dostime($opt['time']);
412+
413+
$fields = array( # (from V,F of APPNOTE.TXT)
414+
array('V', 0x02014b50), # central file header signature
415+
array('v', (6 << 8) + 3), # version made by
416+
array('v', (6 << 8) + 3), # version needed to extract
417+
array('v', 0x00), # general purpose bit flag
418+
array('v', $meth), # compresion method (deflate or store)
419+
array('V', $dts), # dos timestamp
420+
array('V', $crc), # crc32 of data
421+
array('V', $zlen), # compressed data length
422+
array('V', $len), # uncompressed data length
423+
array('v', strlen($name)), # filename length
424+
array('v', 0), # extra data len
425+
array('v', strlen($comment)), # file comment length
426+
array('v', 0), # disk number start
427+
array('v', 0), # internal file attributes
428+
array('V', 32), # external file attributes
429+
array('V', $ofs), # relative offset of local header
430+
);
431+
432+
# pack fields, then append name and comment
433+
$ret = $this->pack_fields($fields) . $name . $comment;
434+
435+
$this->send($ret);
436+
437+
# increment cdr offset
438+
$this->cdr_ofs += strlen($ret);
439+
}
440+
441+
#
442+
# Send CDR EOF (Central Directory Record End-of-File) record.
443+
#
444+
private function add_cdr_eof($opt = null) {
445+
$num = count($this->files);
446+
$cdr_len = $this->cdr_ofs;
447+
$cdr_ofs = $this->ofs;
448+
449+
# grab comment (if specified)
450+
$comment = '';
451+
if ($opt && $opt['comment'])
452+
$comment = $opt['comment'];
453+
454+
$fields = array( # (from V,F of APPNOTE.TXT)
455+
array('V', 0x06054b50), # end of central file header signature
456+
array('v', 0x00), # this disk number
457+
array('v', 0x00), # number of disk with cdr
458+
array('v', $num), # number of entries in the cdr on this disk
459+
array('v', $num), # number of entries in the cdr
460+
array('V', $cdr_len), # cdr size
461+
array('V', $cdr_ofs), # cdr ofs
462+
array('v', strlen($comment)), # zip file comment length
463+
);
464+
465+
$ret = $this->pack_fields($fields) . $comment;
466+
$this->send($ret);
467+
}
468+
469+
#
470+
# Add CDR (Central Directory Record) footer.
471+
#
472+
private function add_cdr($opt = null) {
473+
foreach ($this->files as $file)
474+
$this->add_cdr_file($file);
475+
$this->add_cdr_eof($opt);
476+
}
477+
478+
#
479+
# Clear all internal variables. Note that the stream object is not
480+
# usable after this.
481+
#
482+
function clear() {
483+
$this->files = array();
484+
$this->ofs = 0;
485+
$this->cdr_ofs = 0;
486+
$this->opt = array();
487+
}
488+
489+
###########################
490+
# PRIVATE UTILITY METHODS #
491+
###########################
492+
493+
#
494+
# Send HTTP headers for this stream.
495+
#
496+
private function send_http_headers() {
497+
# grab options
498+
$opt = $this->opt;
499+
500+
# grab content type from options
501+
$content_type = 'application/x-zip';
502+
if ($opt['content_type'])
503+
$content_type = $this->opt['content_type'];
504+
505+
# grab content disposition
506+
$disposition = 'attachment';
507+
if ($opt['content_disposition'])
508+
$disposition = $opt['content_disposition'];
509+
510+
if ($this->output_name)
511+
$disposition .= "; filename=\"{$this->output_name}\"";
512+
513+
$headers = array(
514+
'Content-Type' => $content_type,
515+
'Content-Disposition' => $disposition,
516+
'Pragma' => 'public',
517+
'Cache-Control' => 'public, must-revalidate',
518+
'Content-Transfer-Encoding' => 'binary',
519+
);
520+
521+
foreach ($headers as $key => $val)
522+
header("$key: $val");
523+
}
524+
525+
#
526+
# Send string, sending HTTP headers if necessary.
527+
#
528+
private function send($str) {
529+
if ($this->need_headers)
530+
$this->send_http_headers();
531+
$this->need_headers = false;
532+
533+
echo $str;
534+
}
535+
536+
#
537+
# Convert a UNIX timestamp to a DOS timestamp.
538+
#
539+
function dostime($when = 0) {
540+
# get date array for timestamp
541+
$d = getdate($when);
542+
543+
# set lower-bound on dates
544+
if ($d['year'] < 1980) {
545+
$d = array('year' => 1980, 'mon' => 1, 'mday' => 1,
546+
'hours' => 0, 'minutes' => 0, 'seconds' => 0);
547+
}
548+
549+
# remove extra years from 1980
550+
$d['year'] -= 1980;
551+
552+
# return date string
553+
return ($d['year'] << 25) | ($d['mon'] << 21) | ($d['mday'] << 16) |
554+
($d['hours'] << 11) | ($d['minutes'] << 5) | ($d['seconds'] >> 1);
555+
}
556+
557+
#
558+
# Create a format string and argument list for pack(), then call
559+
# pack() and return the result.
560+
#
561+
function pack_fields($fields) {
562+
list ($fmt, $args) = array('', array());
563+
564+
# populate format string and argument list
565+
foreach ($fields as $field) {
566+
$fmt .= $field[0];
567+
$args[] = $field[1];
568+
}
569+
570+
# prepend format string to argument list
571+
array_unshift($args, $fmt);
572+
573+
# build output string from header and compressed data
574+
return call_user_func_array('pack', $args);
575+
}
576+
};
577+
578+
?>

0 commit comments

Comments
 (0)
Please sign in to comment.