Subversion Repositories Sites.obs-saisons.fr

Rev

Rev 31 | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 31 Rev 316
1
<?php
1
<?php
2
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
2
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
 
3
 
4
/**
4
/**
5
 * HTTP::Download
5
 * HTTP::Download
6
 * 
6
 * 
7
 * PHP versions 4 and 5
7
 * PHP versions 4 and 5
8
 *
8
 *
9
 * @category   HTTP
9
 * @category   HTTP
10
 * @package    HTTP_Download
10
 * @package    HTTP_Download
11
 * @author     Michael Wallner <mike@php.net>
11
 * @author     Michael Wallner <mike@php.net>
12
 * @copyright  2003-2005 Michael Wallner
12
 * @copyright  2003-2005 Michael Wallner
13
 * @license    BSD, revised
13
 * @license    BSD, revised
14
 * @version    CVS: $Id$
14
 * @version    CVS: $Id$
15
 * @link       http://pear.php.net/package/HTTP_Download
15
 * @link       http://pear.php.net/package/HTTP_Download
16
 */
16
 */
17
 
17
 
18
// {{{ includes
18
// {{{ includes
19
/**
19
/**
20
 * Requires PEAR
20
 * Requires PEAR
21
 */
21
 */
22
require_once 'PEAR.php';
22
require_once 'PEAR.php';
23
 
23
 
24
/**
24
/**
25
 * Requires HTTP_Header
25
 * Requires HTTP_Header
26
 */
26
 */
27
require_once 'HTTP/Header.php';
27
require_once 'HTTP/Header.php';
28
// }}}
28
// }}}
29
 
29
 
30
// {{{ constants
30
// {{{ constants
31
/**#@+ Use with HTTP_Download::setContentDisposition() **/
31
/**#@+ Use with HTTP_Download::setContentDisposition() **/
32
/**
32
/**
33
 * Send data as attachment
33
 * Send data as attachment
34
 */
34
 */
35
define('HTTP_DOWNLOAD_ATTACHMENT', 'attachment');
35
define('HTTP_DOWNLOAD_ATTACHMENT', 'attachment');
36
/**
36
/**
37
 * Send data inline
37
 * Send data inline
38
 */
38
 */
39
define('HTTP_DOWNLOAD_INLINE', 'inline');
39
define('HTTP_DOWNLOAD_INLINE', 'inline');
40
/**#@-**/
40
/**#@-**/
41
 
41
 
42
/**#@+ Use with HTTP_Download::sendArchive() **/
42
/**#@+ Use with HTTP_Download::sendArchive() **/
43
/**
43
/**
44
 * Send as uncompressed tar archive
44
 * Send as uncompressed tar archive
45
 */
45
 */
46
define('HTTP_DOWNLOAD_TAR', 'TAR');
46
define('HTTP_DOWNLOAD_TAR', 'TAR');
47
/**
47
/**
48
 * Send as gzipped tar archive
48
 * Send as gzipped tar archive
49
 */
49
 */
50
define('HTTP_DOWNLOAD_TGZ', 'TGZ');
50
define('HTTP_DOWNLOAD_TGZ', 'TGZ');
51
/**
51
/**
52
 * Send as bzip2 compressed tar archive
52
 * Send as bzip2 compressed tar archive
53
 */
53
 */
54
define('HTTP_DOWNLOAD_BZ2', 'BZ2');
54
define('HTTP_DOWNLOAD_BZ2', 'BZ2');
55
/**
55
/**
56
 * Send as zip archive
56
 * Send as zip archive
57
 */
57
 */
58
define('HTTP_DOWNLOAD_ZIP', 'ZIP');
58
define('HTTP_DOWNLOAD_ZIP', 'ZIP');
59
/**#@-**/
59
/**#@-**/
60
 
60
 
61
/**#@+
61
/**#@+
62
 * Error constants
62
 * Error constants
63
 */
63
 */
64
define('HTTP_DOWNLOAD_E_HEADERS_SENT',          -1);
64
define('HTTP_DOWNLOAD_E_HEADERS_SENT',          -1);
65
define('HTTP_DOWNLOAD_E_NO_EXT_ZLIB',           -2);
65
define('HTTP_DOWNLOAD_E_NO_EXT_ZLIB',           -2);
66
define('HTTP_DOWNLOAD_E_NO_EXT_MMAGIC',         -3);
66
define('HTTP_DOWNLOAD_E_NO_EXT_MMAGIC',         -3);
67
define('HTTP_DOWNLOAD_E_INVALID_FILE',          -4);
67
define('HTTP_DOWNLOAD_E_INVALID_FILE',          -4);
68
define('HTTP_DOWNLOAD_E_INVALID_PARAM',         -5);
68
define('HTTP_DOWNLOAD_E_INVALID_PARAM',         -5);
69
define('HTTP_DOWNLOAD_E_INVALID_RESOURCE',      -6);
69
define('HTTP_DOWNLOAD_E_INVALID_RESOURCE',      -6);
70
define('HTTP_DOWNLOAD_E_INVALID_REQUEST',       -7);
70
define('HTTP_DOWNLOAD_E_INVALID_REQUEST',       -7);
71
define('HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE',  -8);
71
define('HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE',  -8);
72
define('HTTP_DOWNLOAD_E_INVALID_ARCHIVE_TYPE',  -9);
72
define('HTTP_DOWNLOAD_E_INVALID_ARCHIVE_TYPE',  -9);
73
/**#@-**/
73
/**#@-**/
74
// }}}
74
// }}}
75
 
75
 
76
/** 
76
/** 
77
 * Send HTTP Downloads/Responses.
77
 * Send HTTP Downloads/Responses.
78
 *
78
 *
79
 * With this package you can handle (hidden) downloads.
79
 * With this package you can handle (hidden) downloads.
80
 * It supports partial downloads, resuming and sending 
80
 * It supports partial downloads, resuming and sending 
81
 * raw data ie. from database BLOBs.
81
 * raw data ie. from database BLOBs.
82
 * 
82
 * 
83
 * <i>ATTENTION:</i>
83
 * <i>ATTENTION:</i>
84
 * You shouldn't use this package together with ob_gzhandler or 
84
 * You shouldn't use this package together with ob_gzhandler or 
85
 * zlib.output_compression enabled in your php.ini, especially 
85
 * zlib.output_compression enabled in your php.ini, especially 
86
 * if you want to send already gzipped data!
86
 * if you want to send already gzipped data!
87
 * 
87
 * 
88
 * @access   public
88
 * @access   public
89
 * @version  $Revision$
89
 * @version  $Revision$
90
 */
90
 */
91
class HTTP_Download
91
class HTTP_Download
92
{
92
{
93
    // {{{ protected member variables
93
    // {{{ protected member variables
94
    /**
94
    /**
95
     * Path to file for download
95
     * Path to file for download
96
     *
96
     *
97
     * @see     HTTP_Download::setFile()
97
     * @see     HTTP_Download::setFile()
98
     * @access  protected
98
     * @access  protected
99
     * @var     string
99
     * @var     string
100
     */
100
     */
101
    var $file = '';
101
    var $file = '';
102
    
102
    
103
    /**
103
    /**
104
     * Data for download
104
     * Data for download
105
     *
105
     *
106
     * @see     HTTP_Download::setData()
106
     * @see     HTTP_Download::setData()
107
     * @access  protected
107
     * @access  protected
108
     * @var     string
108
     * @var     string
109
     */
109
     */
110
    var $data = null;
110
    var $data = null;
111
    
111
    
112
    /**
112
    /**
113
     * Resource handle for download
113
     * Resource handle for download
114
     *
114
     *
115
     * @see     HTTP_Download::setResource()
115
     * @see     HTTP_Download::setResource()
116
     * @access  protected
116
     * @access  protected
117
     * @var     int
117
     * @var     int
118
     */
118
     */
119
    var $handle = null;
119
    var $handle = null;
120
    
120
    
121
    /**
121
    /**
122
     * Whether to gzip the download
122
     * Whether to gzip the download
123
     *
123
     *
124
     * @access  protected
124
     * @access  protected
125
     * @var     bool
125
     * @var     bool
126
     */
126
     */
127
    var $gzip = false;
127
    var $gzip = false;
128
    
128
    
129
    /**
129
    /**
130
     * Whether to allow caching of the download on the clients side
130
     * Whether to allow caching of the download on the clients side
131
     * 
131
     * 
132
     * @access  protected
132
     * @access  protected
133
     * @var     bool
133
     * @var     bool
134
     */
134
     */
135
    var $cache = true;
135
    var $cache = true;
136
    
136
    
137
    /**
137
    /**
138
     * Size of download
138
     * Size of download
139
     *
139
     *
140
     * @access  protected
140
     * @access  protected
141
     * @var     int
141
     * @var     int
142
     */
142
     */
143
    var $size = 0;
143
    var $size = 0;
144
    
144
    
145
    /**
145
    /**
146
     * Last modified
146
     * Last modified
147
     *
147
     *
148
     * @access  protected
148
     * @access  protected
149
     * @var     int
149
     * @var     int
150
     */
150
     */
151
    var $lastModified = 0;
151
    var $lastModified = 0;
152
    
152
    
153
    /**
153
    /**
154
     * HTTP headers
154
     * HTTP headers
155
     *
155
     *
156
     * @access  protected
156
     * @access  protected
157
     * @var     array
157
     * @var     array
158
     */
158
     */
159
    var $headers   = array(
159
    var $headers   = array(
160
        'Content-Type'  => 'application/x-octetstream',
160
        'Content-Type'  => 'application/x-octetstream',
161
        'Pragma'        => 'cache',
161
        'Pragma'        => 'cache',
162
        'Cache-Control' => 'public, must-revalidate, max-age=0',
162
        'Cache-Control' => 'public, must-revalidate, max-age=0',
163
        'Accept-Ranges' => 'bytes',
163
        'Accept-Ranges' => 'bytes',
164
        'X-Sent-By'     => 'PEAR::HTTP::Download'
164
        'X-Sent-By'     => 'PEAR::HTTP::Download'
165
    );
165
    );
166
 
166
 
167
    /**
167
    /**
168
     * HTTP_Header
168
     * HTTP_Header
169
     * 
169
     * 
170
     * @access  protected
170
     * @access  protected
171
     * @var     object
171
     * @var     object
172
     */
172
     */
173
    var $HTTP = null;
173
    var $HTTP = null;
174
    
174
    
175
    /**
175
    /**
176
     * ETag
176
     * ETag
177
     * 
177
     * 
178
     * @access  protected
178
     * @access  protected
179
     * @var     string
179
     * @var     string
180
     */
180
     */
181
    var $etag = '';
181
    var $etag = '';
182
    
182
    
183
    /**
183
    /**
184
     * Buffer Size
184
     * Buffer Size
185
     * 
185
     * 
186
     * @access  protected
186
     * @access  protected
187
     * @var     int
187
     * @var     int
188
     */
188
     */
189
    var $bufferSize = 2097152;
189
    var $bufferSize = 2097152;
190
    
190
    
191
    /**
191
    /**
192
     * Throttle Delay
192
     * Throttle Delay
193
     * 
193
     * 
194
     * @access  protected
194
     * @access  protected
195
     * @var     float
195
     * @var     float
196
     */
196
     */
197
    var $throttleDelay = 0;
197
    var $throttleDelay = 0;
198
    
198
    
199
    /**
199
    /**
200
     * Sent Bytes
200
     * Sent Bytes
201
     * 
201
     * 
202
     * @access  public
202
     * @access  public
203
     * @var     int
203
     * @var     int
204
     */
204
     */
205
    var $sentBytes = 0;
205
    var $sentBytes = 0;
206
    // }}}
206
    // }}}
207
    
207
    
208
    // {{{ constructor
208
    // {{{ constructor
209
    /**
209
    /**
210
     * Constructor
210
     * Constructor
211
     *
211
     *
212
     * Set supplied parameters.
212
     * Set supplied parameters.
213
     * 
213
     * 
214
     * @access  public
214
     * @access  public
215
     * @param   array   $params     associative array of parameters
215
     * @param   array   $params     associative array of parameters
216
     * 
216
     * 
217
     *          <b>one of:</b>
217
     *          <b>one of:</b>
218
     *                  o 'file'                => path to file for download
218
     *                  o 'file'                => path to file for download
219
     *                  o 'data'                => raw data for download
219
     *                  o 'data'                => raw data for download
220
     *                  o 'resource'            => resource handle for download
220
     *                  o 'resource'            => resource handle for download
221
     * <br/>
221
     * <br/>
222
     *          <b>and any of:</b>
222
     *          <b>and any of:</b>
223
     *                  o 'cache'               => whether to allow cs caching
223
     *                  o 'cache'               => whether to allow cs caching
224
     *                  o 'gzip'                => whether to gzip the download
224
     *                  o 'gzip'                => whether to gzip the download
225
     *                  o 'lastmodified'        => unix timestamp
225
     *                  o 'lastmodified'        => unix timestamp
226
     *                  o 'contenttype'         => content type of download
226
     *                  o 'contenttype'         => content type of download
227
     *                  o 'contentdisposition'  => content disposition
227
     *                  o 'contentdisposition'  => content disposition
228
     *                  o 'buffersize'          => amount of bytes to buffer
228
     *                  o 'buffersize'          => amount of bytes to buffer
229
     *                  o 'throttledelay'       => amount of secs to sleep
229
     *                  o 'throttledelay'       => amount of secs to sleep
230
     *                  o 'cachecontrol'        => cache privacy and validity
230
     *                  o 'cachecontrol'        => cache privacy and validity
231
     * 
231
     * 
232
     * <br />
232
     * <br />
233
     * 'Content-Disposition' is not HTTP compliant, but most browsers 
233
     * 'Content-Disposition' is not HTTP compliant, but most browsers 
234
     * follow this header, so it was borrowed from MIME standard.
234
     * follow this header, so it was borrowed from MIME standard.
235
     * 
235
     * 
236
     * It looks like this: <br />
236
     * It looks like this: <br />
237
     * "Content-Disposition: attachment; filename=example.tgz".
237
     * "Content-Disposition: attachment; filename=example.tgz".
238
     * 
238
     * 
239
     * @see HTTP_Download::setContentDisposition()
239
     * @see HTTP_Download::setContentDisposition()
240
     */
240
     */
241
    function HTTP_Download($params = array())
241
    function HTTP_Download($params = array())
242
    {
242
    {
243
        $this->HTTP = &new HTTP_Header;
243
        $this->HTTP = new HTTP_Header;
244
        $this->setParams($params);
244
        $this->setParams($params);
245
    }
245
    }
246
    // }}}
246
    // }}}
247
    
247
    
248
    // {{{ public methods
248
    // {{{ public methods
249
    /**
249
    /**
250
     * Set parameters
250
     * Set parameters
251
     * 
251
     * 
252
     * Set supplied parameters through its accessor methods.
252
     * Set supplied parameters through its accessor methods.
253
     *
253
     *
254
     * @access  public
254
     * @access  public
255
     * @return  mixed   Returns true on success or PEAR_Error on failure.
255
     * @return  mixed   Returns true on success or PEAR_Error on failure.
256
     * @param   array   $params     associative array of parameters
256
     * @param   array   $params     associative array of parameters
257
     * 
257
     * 
258
     * @see     HTTP_Download::HTTP_Download()
258
     * @see     HTTP_Download::HTTP_Download()
259
     */
259
     */
260
    function setParams($params)
260
    function setParams($params)
261
    {
261
    {
262
        foreach((array) $params as $param => $value){
262
        foreach((array) $params as $param => $value){
263
            $method = 'set'. $param;
263
            $method = 'set'. $param;
264
            
264
            
265
            if (!method_exists($this, $method)) {
265
            if (!method_exists($this, $method)) {
266
                return PEAR::raiseError(
266
                return PEAR::raiseError(
267
                    "Method '$method' doesn't exist.",
267
                    "Method '$method' doesn't exist.",
268
                    HTTP_DOWNLOAD_E_INVALID_PARAM
268
                    HTTP_DOWNLOAD_E_INVALID_PARAM
269
                );
269
                );
270
            }
270
            }
271
            
271
            
272
            $e = call_user_func_array(array(&$this, $method), (array) $value);
272
            $e = call_user_func_array(array(&$this, $method), (array) $value);
273
            
273
            
274
            if (PEAR::isError($e)) {
274
            if (PEAR::isError($e)) {
275
                return $e;
275
                return $e;
276
            }
276
            }
277
        }
277
        }
278
        return true;
278
        return true;
279
    }
279
    }
280
    
280
    
281
    /**
281
    /**
282
     * Set path to file for download
282
     * Set path to file for download
283
     *
283
     *
284
     * The Last-Modified header will be set to files filemtime(), actually.
284
     * The Last-Modified header will be set to files filemtime(), actually.
285
     * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_FILE) if file doesn't exist.
285
     * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_FILE) if file doesn't exist.
286
     * Sends HTTP 404 status if $send_404 is set to true.
286
     * Sends HTTP 404 status if $send_404 is set to true.
287
     * 
287
     * 
288
     * @access  public
288
     * @access  public
289
     * @return  mixed   Returns true on success or PEAR_Error on failure.
289
     * @return  mixed   Returns true on success or PEAR_Error on failure.
290
     * @param   string  $file       path to file for download
290
     * @param   string  $file       path to file for download
291
     * @param   bool    $send_404   whether to send HTTP/404 if
291
     * @param   bool    $send_404   whether to send HTTP/404 if
292
     *                              the file wasn't found
292
     *                              the file wasn't found
293
     */
293
     */
294
    function setFile($file, $send_404 = true)
294
    function setFile($file, $send_404 = true)
295
    {
295
    {
296
        $file = realpath($file);
296
        $file = realpath($file);
297
        if (!is_file($file)) {
297
        if (!is_file($file)) {
298
            if ($send_404) {
298
            if ($send_404) {
299
                $this->HTTP->sendStatusCode(404);
299
                $this->HTTP->sendStatusCode(404);
300
            }
300
            }
301
            return PEAR::raiseError(
301
            return PEAR::raiseError(
302
                "File '$file' not found.",
302
                "File '$file' not found.",
303
                HTTP_DOWNLOAD_E_INVALID_FILE
303
                HTTP_DOWNLOAD_E_INVALID_FILE
304
            );
304
            );
305
        }
305
        }
306
        $this->setLastModified(filemtime($file));
306
        $this->setLastModified(filemtime($file));
307
        $this->file = $file;
307
        $this->file = $file;
308
        $this->size = filesize($file);
308
        $this->size = filesize($file);
309
        return true;
309
        return true;
310
    }
310
    }
311
    
311
    
312
    /**
312
    /**
313
     * Set data for download
313
     * Set data for download
314
     *
314
     *
315
     * Set $data to null if you want to unset this.
315
     * Set $data to null if you want to unset this.
316
     * 
316
     * 
317
     * @access  public
317
     * @access  public
318
     * @return  void
318
     * @return  void
319
     * @param   $data   raw data to send
319
     * @param   $data   raw data to send
320
     */
320
     */
321
    function setData($data = null)
321
    function setData($data = null)
322
    {
322
    {
323
        $this->data = $data;
323
        $this->data = $data;
324
        $this->size = strlen($data);
324
        $this->size = strlen($data);
325
    }
325
    }
326
    
326
    
327
    /**
327
    /**
328
     * Set resource for download
328
     * Set resource for download
329
     *
329
     *
330
     * The resource handle supplied will be closed after sending the download.
330
     * The resource handle supplied will be closed after sending the download.
331
     * Returns a PEAR_Error (HTTP_DOWNLOAD_E_INVALID_RESOURCE) if $handle 
331
     * Returns a PEAR_Error (HTTP_DOWNLOAD_E_INVALID_RESOURCE) if $handle 
332
     * is no valid resource. Set $handle to null if you want to unset this.
332
     * is no valid resource. Set $handle to null if you want to unset this.
333
     * 
333
     * 
334
     * @access  public
334
     * @access  public
335
     * @return  mixed   Returns true on success or PEAR_Error on failure.
335
     * @return  mixed   Returns true on success or PEAR_Error on failure.
336
     * @param   int     $handle     resource handle
336
     * @param   int     $handle     resource handle
337
     */
337
     */
338
    function setResource($handle = null)
338
    function setResource($handle = null)
339
    {
339
    {
340
        if (!isset($handle)) {
340
        if (!isset($handle)) {
341
            $this->handle = null;
341
            $this->handle = null;
342
            $this->size = 0;
342
            $this->size = 0;
343
            return true;
343
            return true;
344
        }
344
        }
345
        
345
        
346
        if (is_resource($handle)) {
346
        if (is_resource($handle)) {
347
            $this->handle = $handle;
347
            $this->handle = $handle;
348
            $filestats    = fstat($handle);
348
            $filestats    = fstat($handle);
349
            $this->size   = $filestats['size'];
349
            $this->size   = $filestats['size'];
350
            return true;
350
            return true;
351
        }
351
        }
352
 
352
 
353
        return PEAR::raiseError(
353
        return PEAR::raiseError(
354
            "Handle '$handle' is no valid resource.",
354
            "Handle '$handle' is no valid resource.",
355
            HTTP_DOWNLOAD_E_INVALID_RESOURCE
355
            HTTP_DOWNLOAD_E_INVALID_RESOURCE
356
        );
356
        );
357
    }
357
    }
358
    
358
    
359
    /**
359
    /**
360
     * Whether to gzip the download
360
     * Whether to gzip the download
361
     *
361
     *
362
     * Returns a PEAR_Error (HTTP_DOWNLOAD_E_NO_EXT_ZLIB)
362
     * Returns a PEAR_Error (HTTP_DOWNLOAD_E_NO_EXT_ZLIB)
363
     * if ext/zlib is not available/loadable.
363
     * if ext/zlib is not available/loadable.
364
     * 
364
     * 
365
     * @access  public
365
     * @access  public
366
     * @return  mixed   Returns true on success or PEAR_Error on failure.
366
     * @return  mixed   Returns true on success or PEAR_Error on failure.
367
     * @param   bool    $gzip   whether to gzip the download
367
     * @param   bool    $gzip   whether to gzip the download
368
     */
368
     */
369
    function setGzip($gzip = false)
369
    function setGzip($gzip = false)
370
    {
370
    {
371
        if ($gzip && !PEAR::loadExtension('zlib')){
371
        if ($gzip && !PEAR::loadExtension('zlib')){
372
            return PEAR::raiseError(
372
            return PEAR::raiseError(
373
                'GZIP compression (ext/zlib) not available.',
373
                'GZIP compression (ext/zlib) not available.',
374
                HTTP_DOWNLOAD_E_NO_EXT_ZLIB
374
                HTTP_DOWNLOAD_E_NO_EXT_ZLIB
375
            );
375
            );
376
        }
376
        }
377
        $this->gzip = (bool) $gzip;
377
        $this->gzip = (bool) $gzip;
378
        return true;
378
        return true;
379
    }
379
    }
380
 
380
 
381
    /**
381
    /**
382
     * Whether to allow caching
382
     * Whether to allow caching
383
     * 
383
     * 
384
     * If set to true (default) we'll send some headers that are commonly
384
     * If set to true (default) we'll send some headers that are commonly
385
     * used for caching purposes like ETag, Cache-Control and Last-Modified.
385
     * used for caching purposes like ETag, Cache-Control and Last-Modified.
386
     * 
386
     * 
387
     * If caching is disabled, we'll send the download no matter if it
387
     * If caching is disabled, we'll send the download no matter if it
388
     * would actually be cached at the client side.
388
     * would actually be cached at the client side.
389
     *
389
     *
390
     * @access  public
390
     * @access  public
391
     * @return  void
391
     * @return  void
392
     * @param   bool    $cache  whether to allow caching
392
     * @param   bool    $cache  whether to allow caching
393
     */
393
     */
394
    function setCache($cache = true)
394
    function setCache($cache = true)
395
    {
395
    {
396
        $this->cache = (bool) $cache;
396
        $this->cache = (bool) $cache;
397
    }
397
    }
398
    
398
    
399
    /**
399
    /**
400
     * Whether to allow proxies to cache
400
     * Whether to allow proxies to cache
401
     * 
401
     * 
402
     * If set to 'private' proxies shouldn't cache the response.
402
     * If set to 'private' proxies shouldn't cache the response.
403
     * This setting defaults to 'public' and affects only cached responses.
403
     * This setting defaults to 'public' and affects only cached responses.
404
     * 
404
     * 
405
     * @access  public
405
     * @access  public
406
     * @return  bool
406
     * @return  bool
407
     * @param   string  $cache  private or public
407
     * @param   string  $cache  private or public
408
     * @param   int     $maxage maximum age of the client cache entry
408
     * @param   int     $maxage maximum age of the client cache entry
409
     */
409
     */
410
    function setCacheControl($cache = 'public', $maxage = 0)
410
    function setCacheControl($cache = 'public', $maxage = 0)
411
    {
411
    {
412
        switch ($cache = strToLower($cache))
412
        switch ($cache = strToLower($cache))
413
        {
413
        {
414
            case 'private':
414
            case 'private':
415
            case 'public':
415
            case 'public':
416
                $this->headers['Cache-Control'] = 
416
                $this->headers['Cache-Control'] = 
417
                    $cache .', must-revalidate, max-age='. abs($maxage);
417
                    $cache .', must-revalidate, max-age='. abs($maxage);
418
                return true;
418
                return true;
419
            break;
419
            break;
420
        }
420
        }
421
        return false;
421
        return false;
422
    }
422
    }
423
    
423
    
424
    /**
424
    /**
425
     * Set ETag
425
     * Set ETag
426
     * 
426
     * 
427
     * Sets a user-defined ETag for cache-validation.  The ETag is usually
427
     * Sets a user-defined ETag for cache-validation.  The ETag is usually
428
     * generated by HTTP_Download through its payload information.
428
     * generated by HTTP_Download through its payload information.
429
     * 
429
     * 
430
     * @access  public
430
     * @access  public
431
     * @return  void
431
     * @return  void
432
     * @param   string  $etag Entity tag used for strong cache validation.
432
     * @param   string  $etag Entity tag used for strong cache validation.
433
     */
433
     */
434
    function setETag($etag = null)
434
    function setETag($etag = null)
435
    {
435
    {
436
        $this->etag = (string) $etag;
436
        $this->etag = (string) $etag;
437
    }
437
    }
438
    
438
    
439
    /**
439
    /**
440
     * Set Size of Buffer
440
     * Set Size of Buffer
441
     * 
441
     * 
442
     * The amount of bytes specified as buffer size is the maximum amount
442
     * The amount of bytes specified as buffer size is the maximum amount
443
     * of data read at once from resources or files.  The default size is 2M
443
     * of data read at once from resources or files.  The default size is 2M
444
     * (2097152 bytes).  Be aware that if you enable gzip compression and
444
     * (2097152 bytes).  Be aware that if you enable gzip compression and
445
     * you set a very low buffer size that the actual file size may grow
445
     * you set a very low buffer size that the actual file size may grow
446
     * due to added gzip headers for each sent chunk of the specified size.
446
     * due to added gzip headers for each sent chunk of the specified size.
447
     * 
447
     * 
448
     * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_PARAM) if $size is not
448
     * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_PARAM) if $size is not
449
     * greater than 0 bytes.
449
     * greater than 0 bytes.
450
     * 
450
     * 
451
     * @access  public
451
     * @access  public
452
     * @return  mixed   Returns true on success or PEAR_Error on failure.
452
     * @return  mixed   Returns true on success or PEAR_Error on failure.
453
     * @param   int     $bytes Amount of bytes to use as buffer.
453
     * @param   int     $bytes Amount of bytes to use as buffer.
454
     */
454
     */
455
    function setBufferSize($bytes = 2097152)
455
    function setBufferSize($bytes = 2097152)
456
    {
456
    {
457
        if (0 >= $bytes) {
457
        if (0 >= $bytes) {
458
            return PEAR::raiseError(
458
            return PEAR::raiseError(
459
                'Buffer size must be greater than 0 bytes ('. $bytes .' given)',
459
                'Buffer size must be greater than 0 bytes ('. $bytes .' given)',
460
                HTTP_DOWNLOAD_E_INVALID_PARAM);
460
                HTTP_DOWNLOAD_E_INVALID_PARAM);
461
        }
461
        }
462
        $this->bufferSize = abs($bytes);
462
        $this->bufferSize = abs($bytes);
463
        return true;
463
        return true;
464
    }
464
    }
465
    
465
    
466
    /**
466
    /**
467
     * Set Throttle Delay
467
     * Set Throttle Delay
468
     * 
468
     * 
469
     * Set the amount of seconds to sleep after each chunck that has been
469
     * Set the amount of seconds to sleep after each chunck that has been
470
     * sent.  One can implement some sort of throttle through adjusting the
470
     * sent.  One can implement some sort of throttle through adjusting the
471
     * buffer size and the throttle delay.  With the following settings
471
     * buffer size and the throttle delay.  With the following settings
472
     * HTTP_Download will sleep a second after each 25 K of data sent.
472
     * HTTP_Download will sleep a second after each 25 K of data sent.
473
     * 
473
     * 
474
     * <code>
474
     * <code>
475
     *  Array(
475
     *  Array(
476
     *      'throttledelay' => 1,
476
     *      'throttledelay' => 1,
477
     *      'buffersize'    => 1024 * 25,
477
     *      'buffersize'    => 1024 * 25,
478
     *  )
478
     *  )
479
     * </code>
479
     * </code>
480
     * 
480
     * 
481
     * Just be aware that if gzipp'ing is enabled, decreasing the chunk size 
481
     * Just be aware that if gzipp'ing is enabled, decreasing the chunk size 
482
     * too much leads to proportionally increased network traffic due to added
482
     * too much leads to proportionally increased network traffic due to added
483
     * gzip header and bottom bytes around each chunk.
483
     * gzip header and bottom bytes around each chunk.
484
     * 
484
     * 
485
     * @access  public
485
     * @access  public
486
     * @return  void
486
     * @return  void
487
     * @param   float   $seconds    Amount of seconds to sleep after each 
487
     * @param   float   $seconds    Amount of seconds to sleep after each 
488
     *                              chunk that has been sent.
488
     *                              chunk that has been sent.
489
     */
489
     */
490
    function setThrottleDelay($seconds = 0)
490
    function setThrottleDelay($seconds = 0)
491
    {
491
    {
492
        $this->throttleDelay = abs($seconds) * 1000;
492
        $this->throttleDelay = abs($seconds) * 1000;
493
    }
493
    }
494
    
494
    
495
    /**
495
    /**
496
     * Set "Last-Modified"
496
     * Set "Last-Modified"
497
     *
497
     *
498
     * This is usually determined by filemtime() in HTTP_Download::setFile()
498
     * This is usually determined by filemtime() in HTTP_Download::setFile()
499
     * If you set raw data for download with HTTP_Download::setData() and you
499
     * If you set raw data for download with HTTP_Download::setData() and you
500
     * want do send an appropiate "Last-Modified" header, you should call this
500
     * want do send an appropiate "Last-Modified" header, you should call this
501
     * method.
501
     * method.
502
     * 
502
     * 
503
     * @access  public
503
     * @access  public
504
     * @return  void
504
     * @return  void
505
     * @param   int     unix timestamp
505
     * @param   int     unix timestamp
506
     */
506
     */
507
    function setLastModified($last_modified)
507
    function setLastModified($last_modified)
508
    {
508
    {
509
        $this->lastModified = $this->headers['Last-Modified'] = (int) $last_modified;
509
        $this->lastModified = $this->headers['Last-Modified'] = (int) $last_modified;
510
    }
510
    }
511
    
511
    
512
    /**
512
    /**
513
     * Set Content-Disposition header
513
     * Set Content-Disposition header
514
     * 
514
     * 
515
     * @see HTTP_Download::HTTP_Download
515
     * @see HTTP_Download::HTTP_Download
516
     *
516
     *
517
     * @access  public
517
     * @access  public
518
     * @return  void
518
     * @return  void
519
     * @param   string  $disposition    whether to send the download
519
     * @param   string  $disposition    whether to send the download
520
     *                                  inline or as attachment
520
     *                                  inline or as attachment
521
     * @param   string  $file_name      the filename to display in
521
     * @param   string  $file_name      the filename to display in
522
     *                                  the browser's download window
522
     *                                  the browser's download window
523
     * 
523
     * 
524
     * <b>Example:</b>
524
     * <b>Example:</b>
525
     * <code>
525
     * <code>
526
     * $HTTP_Download->setContentDisposition(
526
     * $HTTP_Download->setContentDisposition(
527
     *   HTTP_DOWNLOAD_ATTACHMENT,
527
     *   HTTP_DOWNLOAD_ATTACHMENT,
528
     *   'download.tgz'
528
     *   'download.tgz'
529
     * );
529
     * );
530
     * </code>
530
     * </code>
531
     */
531
     */
532
    function setContentDisposition( $disposition    = HTTP_DOWNLOAD_ATTACHMENT, 
532
    function setContentDisposition( $disposition    = HTTP_DOWNLOAD_ATTACHMENT, 
533
                                    $file_name      = null)
533
                                    $file_name      = null)
534
    {
534
    {
535
        $cd = $disposition;
535
        $cd = $disposition;
536
        if (isset($file_name)) {
536
        if (isset($file_name)) {
537
            $cd .= '; filename="' . $file_name . '"';
537
            $cd .= '; filename="' . $file_name . '"';
538
        } elseif ($this->file) {
538
        } elseif ($this->file) {
539
            $cd .= '; filename="' . basename($this->file) . '"';
539
            $cd .= '; filename="' . basename($this->file) . '"';
540
        }
540
        }
541
        $this->headers['Content-Disposition'] = $cd;
541
        $this->headers['Content-Disposition'] = $cd;
542
    }
542
    }
543
    
543
    
544
    /**
544
    /**
545
     * Set content type of the download
545
     * Set content type of the download
546
     *
546
     *
547
     * Default content type of the download will be 'application/x-octetstream'.
547
     * Default content type of the download will be 'application/x-octetstream'.
548
     * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE) if 
548
     * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE) if 
549
     * $content_type doesn't seem to be valid.
549
     * $content_type doesn't seem to be valid.
550
     * 
550
     * 
551
     * @access  public
551
     * @access  public
552
     * @return  mixed   Returns true on success or PEAR_Error on failure.
552
     * @return  mixed   Returns true on success or PEAR_Error on failure.
553
     * @param   string  $content_type   content type of file for download
553
     * @param   string  $content_type   content type of file for download
554
     */
554
     */
555
    function setContentType($content_type = 'application/x-octetstream')
555
    function setContentType($content_type = 'application/x-octetstream')
556
    {
556
    {
557
        if (!preg_match('/^[a-z]+\w*\/[a-z]+[\w.;= -]*$/', $content_type)) {
557
        if (!preg_match('/^[a-z]+\w*\/[a-z]+[\w.;= -]*$/', $content_type)) {
558
            return PEAR::raiseError(
558
            return PEAR::raiseError(
559
                "Invalid content type '$content_type' supplied.",
559
                "Invalid content type '$content_type' supplied.",
560
                HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE
560
                HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE
561
            );
561
            );
562
        }
562
        }
563
        $this->headers['Content-Type'] = $content_type;
563
        $this->headers['Content-Type'] = $content_type;
564
        return true;
564
        return true;
565
    }
565
    }
566
    
566
    
567
    /**
567
    /**
568
     * Guess content type of file
568
     * Guess content type of file
569
     * 
569
     * 
570
     * First we try to use PEAR::MIME_Type, if installed, to detect the content 
570
     * First we try to use PEAR::MIME_Type, if installed, to detect the content 
571
     * type, else we check if ext/mime_magic is loaded and properly configured.
571
     * type, else we check if ext/mime_magic is loaded and properly configured.
572
     *
572
     *
573
     * Returns PEAR_Error if:
573
     * Returns PEAR_Error if:
574
     *      o if PEAR::MIME_Type failed to detect a proper content type
574
     *      o if PEAR::MIME_Type failed to detect a proper content type
575
     *        (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE)
575
     *        (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE)
576
     *      o ext/magic.mime is not installed, or not properly configured
576
     *      o ext/magic.mime is not installed, or not properly configured
577
     *        (HTTP_DOWNLOAD_E_NO_EXT_MMAGIC)
577
     *        (HTTP_DOWNLOAD_E_NO_EXT_MMAGIC)
578
     *      o mime_content_type() couldn't guess content type or returned
578
     *      o mime_content_type() couldn't guess content type or returned
579
     *        a content type considered to be bogus by setContentType()
579
     *        a content type considered to be bogus by setContentType()
580
     *        (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE)
580
     *        (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE)
581
     * 
581
     * 
582
     * @access  public
582
     * @access  public
583
     * @return  mixed   Returns true on success or PEAR_Error on failure.
583
     * @return  mixed   Returns true on success or PEAR_Error on failure.
584
     */
584
     */
585
    function guessContentType()
585
    function guessContentType()
586
    {
586
    {
587
        if (class_exists('MIME_Type') || @include_once 'MIME/Type.php') {
587
        if (class_exists('MIME_Type') || @include_once 'MIME/Type.php') {
588
            if (PEAR::isError($mime_type = MIME_Type::autoDetect($this->file))) {
588
            if (PEAR::isError($mime_type = MIME_Type::autoDetect($this->file))) {
589
                return PEAR::raiseError($mime_type->getMessage(),
589
                return PEAR::raiseError($mime_type->getMessage(),
590
                    HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE);
590
                    HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE);
591
            }
591
            }
592
            return $this->setContentType($mime_type);
592
            return $this->setContentType($mime_type);
593
        }
593
        }
594
        if (!function_exists('mime_content_type')) {
594
        if (!function_exists('mime_content_type')) {
595
            return PEAR::raiseError(
595
            return PEAR::raiseError(
596
                'This feature requires ext/mime_magic!',
596
                'This feature requires ext/mime_magic!',
597
                HTTP_DOWNLOAD_E_NO_EXT_MMAGIC
597
                HTTP_DOWNLOAD_E_NO_EXT_MMAGIC
598
            );
598
            );
599
        }
599
        }
600
        if (!is_file(ini_get('mime_magic.magicfile'))) {
600
        if (!is_file(ini_get('mime_magic.magicfile'))) {
601
            return PEAR::raiseError(
601
            return PEAR::raiseError(
602
                'ext/mime_magic is loaded but not properly configured!',
602
                'ext/mime_magic is loaded but not properly configured!',
603
                HTTP_DOWNLOAD_E_NO_EXT_MMAGIC
603
                HTTP_DOWNLOAD_E_NO_EXT_MMAGIC
604
            );
604
            );
605
        }
605
        }
606
        if (!$content_type = @mime_content_type($this->file)) {
606
        if (!$content_type = @mime_content_type($this->file)) {
607
            return PEAR::raiseError(
607
            return PEAR::raiseError(
608
                'Couldn\'t guess content type with mime_content_type().',
608
                'Couldn\'t guess content type with mime_content_type().',
609
                HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE
609
                HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE
610
            );
610
            );
611
        }
611
        }
612
        return $this->setContentType($content_type);
612
        return $this->setContentType($content_type);
613
    }
613
    }
614
 
614
 
615
    /**
615
    /**
616
     * Send
616
     * Send
617
     *
617
     *
618
     * Returns PEAR_Error if:
618
     * Returns PEAR_Error if:
619
     *   o HTTP headers were already sent (HTTP_DOWNLOAD_E_HEADERS_SENT)
619
     *   o HTTP headers were already sent (HTTP_DOWNLOAD_E_HEADERS_SENT)
620
     *   o HTTP Range was invalid (HTTP_DOWNLOAD_E_INVALID_REQUEST)
620
     *   o HTTP Range was invalid (HTTP_DOWNLOAD_E_INVALID_REQUEST)
621
     * 
621
     * 
622
     * @access  public
622
     * @access  public
623
     * @return  mixed   Returns true on success or PEAR_Error on failure.
623
     * @return  mixed   Returns true on success or PEAR_Error on failure.
624
     * @param   bool    $autoSetContentDisposition Whether to set the
624
     * @param   bool    $autoSetContentDisposition Whether to set the
625
     *                  Content-Disposition header if it isn't already.
625
     *                  Content-Disposition header if it isn't already.
626
     */
626
     */
627
    function send($autoSetContentDisposition = true)
627
    function send($autoSetContentDisposition = true)
628
    {
628
    {
629
        if (headers_sent()) {
629
        if (headers_sent()) {
630
            return PEAR::raiseError(
630
            return PEAR::raiseError(
631
                'Headers already sent.',
631
                'Headers already sent.',
632
                HTTP_DOWNLOAD_E_HEADERS_SENT
632
                HTTP_DOWNLOAD_E_HEADERS_SENT
633
            );
633
            );
634
        }
634
        }
635
        
635
        
636
        if (!ini_get('safe_mode')) {
636
        if (!ini_get('safe_mode')) {
637
            @set_time_limit(0);
637
            @set_time_limit(0);
638
        }
638
        }
639
        
639
        
640
        if ($autoSetContentDisposition && 
640
        if ($autoSetContentDisposition && 
641
            !isset($this->headers['Content-Disposition'])) {
641
            !isset($this->headers['Content-Disposition'])) {
642
            $this->setContentDisposition();
642
            $this->setContentDisposition();
643
        }
643
        }
644
        
644
        
645
        if ($this->cache) {
645
        if ($this->cache) {
646
            $this->headers['ETag'] = $this->generateETag();
646
            $this->headers['ETag'] = $this->generateETag();
647
            if ($this->isCached()) {
647
            if ($this->isCached()) {
648
                $this->HTTP->sendStatusCode(304);
648
                $this->HTTP->sendStatusCode(304);
649
                $this->sendHeaders();
649
                $this->sendHeaders();
650
                return true;
650
                return true;
651
            }
651
            }
652
        } else {
652
        } else {
653
            unset($this->headers['Last-Modified']);
653
            unset($this->headers['Last-Modified']);
654
        }
654
        }
655
        
655
        
656
        if (ob_get_level()) {
656
        if (ob_get_level()) {
657
        	while (@ob_end_clean());
657
        	while (@ob_end_clean());
658
        }
658
        }
659
        
659
        
660
        if ($this->gzip) {
660
        if ($this->gzip) {
661
            @ob_start('ob_gzhandler');
661
            @ob_start('ob_gzhandler');
662
        } else {
662
        } else {
663
            ob_start();
663
            ob_start();
664
        }
664
        }
665
        
665
        
666
        $this->sentBytes = 0;
666
        $this->sentBytes = 0;
667
        
667
        
668
        if ($this->isRangeRequest()) {
668
        if ($this->isRangeRequest()) {
669
            $this->HTTP->sendStatusCode(206);
669
            $this->HTTP->sendStatusCode(206);
670
            $chunks = $this->getChunks();
670
            $chunks = $this->getChunks();
671
        } else {
671
        } else {
672
            $this->HTTP->sendStatusCode(200);
672
            $this->HTTP->sendStatusCode(200);
673
            $chunks = array(array(0, $this->size));
673
            $chunks = array(array(0, $this->size));
674
            if (!$this->gzip && count(ob_list_handlers()) < 2) {
674
            if (!$this->gzip && count(ob_list_handlers()) < 2) {
675
                $this->headers['Content-Length'] = $this->size;
675
                $this->headers['Content-Length'] = $this->size;
676
            }
676
            }
677
        }
677
        }
678
 
678
 
679
        if (PEAR::isError($e = $this->sendChunks($chunks))) {
679
        if (PEAR::isError($e = $this->sendChunks($chunks))) {
680
            ob_end_clean();
680
            ob_end_clean();
681
            $this->HTTP->sendStatusCode(416);
681
            $this->HTTP->sendStatusCode(416);
682
            return $e;
682
            return $e;
683
        }
683
        }
684
        
684
        
685
        ob_end_flush();
685
        ob_end_flush();
686
        flush();
686
        flush();
687
        return true;
687
        return true;
688
    }    
688
    }    
689
 
689
 
690
    /**
690
    /**
691
     * Static send
691
     * Static send
692
     *
692
     *
693
     * @see     HTTP_Download::HTTP_Download()
693
     * @see     HTTP_Download::HTTP_Download()
694
     * @see     HTTP_Download::send()
694
     * @see     HTTP_Download::send()
695
     * 
695
     * 
696
     * @static
696
     * @static
697
     * @access  public
697
     * @access  public
698
     * @return  mixed   Returns true on success or PEAR_Error on failure.
698
     * @return  mixed   Returns true on success or PEAR_Error on failure.
699
     * @param   array   $params     associative array of parameters
699
     * @param   array   $params     associative array of parameters
700
     * @param   bool    $guess      whether HTTP_Download::guessContentType()
700
     * @param   bool    $guess      whether HTTP_Download::guessContentType()
701
     *                               should be called
701
     *                               should be called
702
     */
702
     */
703
    function staticSend($params, $guess = false)
703
    function staticSend($params, $guess = false)
704
    {
704
    {
705
        $d = &new HTTP_Download();
705
        $d = new HTTP_Download();
706
        $e = $d->setParams($params);
706
        $e = $d->setParams($params);
707
        if (PEAR::isError($e)) {
707
        if (PEAR::isError($e)) {
708
            return $e;
708
            return $e;
709
        }
709
        }
710
        if ($guess) {
710
        if ($guess) {
711
            $e = $d->guessContentType();
711
            $e = $d->guessContentType();
712
            if (PEAR::isError($e)) {
712
            if (PEAR::isError($e)) {
713
                return $e;
713
                return $e;
714
            }
714
            }
715
        }
715
        }
716
        return $d->send();
716
        return $d->send();
717
    }
717
    }
718
    
718
    
719
    /**
719
    /**
720
     * Send a bunch of files or directories as an archive
720
     * Send a bunch of files or directories as an archive
721
     * 
721
     * 
722
     * Example:
722
     * Example:
723
     * <code>
723
     * <code>
724
     *  require_once 'HTTP/Download.php';
724
     *  require_once 'HTTP/Download.php';
725
     *  HTTP_Download::sendArchive(
725
     *  HTTP_Download::sendArchive(
726
     *      'myArchive.tgz',
726
     *      'myArchive.tgz',
727
     *      '/var/ftp/pub/mike',
727
     *      '/var/ftp/pub/mike',
728
     *      HTTP_DOWNLOAD_TGZ,
728
     *      HTTP_DOWNLOAD_TGZ,
729
     *      '',
729
     *      '',
730
     *      '/var/ftp/pub'
730
     *      '/var/ftp/pub'
731
     *  );
731
     *  );
732
     * </code>
732
     * </code>
733
     *
733
     *
734
     * @see         Archive_Tar::createModify()
734
     * @see         Archive_Tar::createModify()
735
     * @deprecated  use HTTP_Download_Archive::send()
735
     * @deprecated  use HTTP_Download_Archive::send()
736
     * @static
736
     * @static
737
     * @access  public
737
     * @access  public
738
     * @return  mixed   Returns true on success or PEAR_Error on failure.
738
     * @return  mixed   Returns true on success or PEAR_Error on failure.
739
     * @param   string  $name       name the sent archive should have
739
     * @param   string  $name       name the sent archive should have
740
     * @param   mixed   $files      files/directories
740
     * @param   mixed   $files      files/directories
741
     * @param   string  $type       archive type
741
     * @param   string  $type       archive type
742
     * @param   string  $add_path   path that should be prepended to the files
742
     * @param   string  $add_path   path that should be prepended to the files
743
     * @param   string  $strip_path path that should be stripped from the files
743
     * @param   string  $strip_path path that should be stripped from the files
744
     */
744
     */
745
    function sendArchive(   $name, 
745
    function sendArchive(   $name, 
746
                            $files, 
746
                            $files, 
747
                            $type       = HTTP_DOWNLOAD_TGZ, 
747
                            $type       = HTTP_DOWNLOAD_TGZ, 
748
                            $add_path   = '', 
748
                            $add_path   = '', 
749
                            $strip_path = '')
749
                            $strip_path = '')
750
    {
750
    {
751
        require_once 'HTTP/Download/Archive.php';
751
        require_once 'HTTP/Download/Archive.php';
752
        return HTTP_Download_Archive::send($name, $files, $type, 
752
        return HTTP_Download_Archive::send($name, $files, $type, 
753
            $add_path, $strip_path);
753
            $add_path, $strip_path);
754
    }
754
    }
755
    // }}}
755
    // }}}
756
    
756
    
757
    // {{{ protected methods
757
    // {{{ protected methods
758
    /** 
758
    /** 
759
     * Generate ETag
759
     * Generate ETag
760
     * 
760
     * 
761
     * @access  protected
761
     * @access  protected
762
     * @return  string
762
     * @return  string
763
     */
763
     */
764
    function generateETag()
764
    function generateETag()
765
    {
765
    {
766
        if (!$this->etag) {
766
        if (!$this->etag) {
767
            if ($this->data) {
767
            if ($this->data) {
768
                $md5 = md5($this->data);
768
                $md5 = md5($this->data);
769
            } else {
769
            } else {
770
                $fst = is_resource($this->handle) ? 
770
                $fst = is_resource($this->handle) ? 
771
                    fstat($this->handle) : stat($this->file);
771
                    fstat($this->handle) : stat($this->file);
772
                $md5 = md5($fst['mtime'] .'='. $fst['ino'] .'='. $fst['size']);
772
                $md5 = md5($fst['mtime'] .'='. $fst['ino'] .'='. $fst['size']);
773
            }
773
            }
774
            $this->etag = '"' . $md5 . '-' . crc32($md5) . '"';
774
            $this->etag = '"' . $md5 . '-' . crc32($md5) . '"';
775
        }
775
        }
776
        return $this->etag;
776
        return $this->etag;
777
    }
777
    }
778
    
778
    
779
    /** 
779
    /** 
780
     * Send multiple chunks
780
     * Send multiple chunks
781
     * 
781
     * 
782
     * @access  protected
782
     * @access  protected
783
     * @return  mixed   Returns true on success or PEAR_Error on failure.
783
     * @return  mixed   Returns true on success or PEAR_Error on failure.
784
     * @param   array   $chunks
784
     * @param   array   $chunks
785
     */
785
     */
786
    function sendChunks($chunks)
786
    function sendChunks($chunks)
787
    {
787
    {
788
        if (count($chunks) == 1) {
788
        if (count($chunks) == 1) {
789
            return $this->sendChunk(current($chunks));
789
            return $this->sendChunk(current($chunks));
790
        }
790
        }
791
 
791
 
792
        $bound = uniqid('HTTP_DOWNLOAD-', true);
792
        $bound = uniqid('HTTP_DOWNLOAD-', true);
793
        $cType = $this->headers['Content-Type'];
793
        $cType = $this->headers['Content-Type'];
794
        $this->headers['Content-Type'] =
794
        $this->headers['Content-Type'] =
795
            'multipart/byteranges; boundary=' . $bound;
795
            'multipart/byteranges; boundary=' . $bound;
796
        $this->sendHeaders();
796
        $this->sendHeaders();
797
        foreach ($chunks as $chunk){
797
        foreach ($chunks as $chunk){
798
            if (PEAR::isError($e = $this->sendChunk($chunk, $cType, $bound))) {
798
            if (PEAR::isError($e = $this->sendChunk($chunk, $cType, $bound))) {
799
                return $e;
799
                return $e;
800
            }
800
            }
801
        }
801
        }
802
        #echo "\r\n--$bound--\r\n";
802
        #echo "\r\n--$bound--\r\n";
803
        return true;
803
        return true;
804
    }
804
    }
805
    
805
    
806
    /**
806
    /**
807
     * Send chunk of data
807
     * Send chunk of data
808
     * 
808
     * 
809
     * @access  protected
809
     * @access  protected
810
     * @return  mixed   Returns true on success or PEAR_Error on failure.
810
     * @return  mixed   Returns true on success or PEAR_Error on failure.
811
     * @param   array   $chunk  start and end offset of the chunk to send
811
     * @param   array   $chunk  start and end offset of the chunk to send
812
     * @param   string  $cType  actual content type
812
     * @param   string  $cType  actual content type
813
     * @param   string  $bound  boundary for multipart/byteranges
813
     * @param   string  $bound  boundary for multipart/byteranges
814
     */
814
     */
815
    function sendChunk($chunk, $cType = null, $bound = null)
815
    function sendChunk($chunk, $cType = null, $bound = null)
816
    {
816
    {
817
        list($offset, $lastbyte) = $chunk;
817
        list($offset, $lastbyte) = $chunk;
818
        $length = ($lastbyte - $offset) + 1;
818
        $length = ($lastbyte - $offset) + 1;
819
        
819
        
820
        if ($length < 1) {
820
        if ($length < 1) {
821
            return PEAR::raiseError(
821
            return PEAR::raiseError(
822
                "Error processing range request: $offset-$lastbyte/$length",
822
                "Error processing range request: $offset-$lastbyte/$length",
823
                HTTP_DOWNLOAD_E_INVALID_REQUEST
823
                HTTP_DOWNLOAD_E_INVALID_REQUEST
824
            );
824
            );
825
        }
825
        }
826
        
826
        
827
        $range = $offset . '-' . $lastbyte . '/' . $this->size;
827
        $range = $offset . '-' . $lastbyte . '/' . $this->size;
828
        
828
        
829
        if (isset($cType, $bound)) {
829
        if (isset($cType, $bound)) {
830
            echo    "\r\n--$bound\r\n",
830
            echo    "\r\n--$bound\r\n",
831
                    "Content-Type: $cType\r\n",
831
                    "Content-Type: $cType\r\n",
832
                    "Content-Range: bytes $range\r\n\r\n";
832
                    "Content-Range: bytes $range\r\n\r\n";
833
        } else {
833
        } else {
834
            if ($this->isRangeRequest()) {
834
            if ($this->isRangeRequest()) {
835
                $this->headers['Content-Length'] = $length;
835
                $this->headers['Content-Length'] = $length;
836
                $this->headers['Content-Range'] = 'bytes '. $range;
836
                $this->headers['Content-Range'] = 'bytes '. $range;
837
            }
837
            }
838
            $this->sendHeaders();
838
            $this->sendHeaders();
839
        }
839
        }
840
 
840
 
841
        if ($this->data) {
841
        if ($this->data) {
842
            while (($length -= $this->bufferSize) > 0) {
842
            while (($length -= $this->bufferSize) > 0) {
843
                $this->flush(substr($this->data, $offset, $this->bufferSize));
843
                $this->flush(substr($this->data, $offset, $this->bufferSize));
844
                $this->throttleDelay and $this->sleep();
844
                $this->throttleDelay and $this->sleep();
845
                $offset += $this->bufferSize;
845
                $offset += $this->bufferSize;
846
            }
846
            }
847
            if ($length) {
847
            if ($length) {
848
                $this->flush(substr($this->data, $offset, $this->bufferSize + $length));
848
                $this->flush(substr($this->data, $offset, $this->bufferSize + $length));
849
            }
849
            }
850
        } else {
850
        } else {
851
            if (!is_resource($this->handle)) {
851
            if (!is_resource($this->handle)) {
852
                $this->handle = fopen($this->file, 'rb');
852
                $this->handle = fopen($this->file, 'rb');
853
            }
853
            }
854
            fseek($this->handle, $offset);
854
            fseek($this->handle, $offset);
855
            while (($length -= $this->bufferSize) > 0) {
855
            while (($length -= $this->bufferSize) > 0) {
856
                $this->flush(fread($this->handle, $this->bufferSize));
856
                $this->flush(fread($this->handle, $this->bufferSize));
857
                $this->throttleDelay and $this->sleep();
857
                $this->throttleDelay and $this->sleep();
858
            }
858
            }
859
            if ($length) {
859
            if ($length) {
860
                $this->flush(fread($this->handle, $this->bufferSize + $length));
860
                $this->flush(fread($this->handle, $this->bufferSize + $length));
861
            }
861
            }
862
        }
862
        }
863
        return true;
863
        return true;
864
    }
864
    }
865
    
865
    
866
    /** 
866
    /** 
867
     * Get chunks to send
867
     * Get chunks to send
868
     * 
868
     * 
869
     * @access  protected
869
     * @access  protected
870
     * @return  array
870
     * @return  array
871
     */
871
     */
872
    function getChunks()
872
    function getChunks()
873
    {
873
    {
874
        $parts = array();
874
        $parts = array();
875
        foreach (explode(',', $this->getRanges()) as $chunk){
875
        foreach (explode(',', $this->getRanges()) as $chunk){
876
            list($o, $e) = explode('-', $chunk);
876
            list($o, $e) = explode('-', $chunk);
877
            if ($e >= $this->size || (empty($e) && $e !== 0 && $e !== '0')) {
877
            if ($e >= $this->size || (empty($e) && $e !== 0 && $e !== '0')) {
878
                $e = $this->size - 1;
878
                $e = $this->size - 1;
879
            }
879
            }
880
            if (empty($o) && $o !== 0 && $o !== '0') {
880
            if (empty($o) && $o !== 0 && $o !== '0') {
881
                $o = $this->size - $e;
881
                $o = $this->size - $e;
882
                $e = $this->size - 1;
882
                $e = $this->size - 1;
883
            }
883
            }
884
            $parts[] = array($o, $e);
884
            $parts[] = array($o, $e);
885
        }
885
        }
886
        return $parts;
886
        return $parts;
887
    }
887
    }
888
    
888
    
889
    /** 
889
    /** 
890
     * Check if range is requested
890
     * Check if range is requested
891
     * 
891
     * 
892
     * @access  protected
892
     * @access  protected
893
     * @return  bool
893
     * @return  bool
894
     */
894
     */
895
    function isRangeRequest()
895
    function isRangeRequest()
896
    {
896
    {
897
        if (!isset($_SERVER['HTTP_RANGE'])) {
897
        if (!isset($_SERVER['HTTP_RANGE'])) {
898
            return false;
898
            return false;
899
        }
899
        }
900
        return $this->isValidRange();
900
        return $this->isValidRange();
901
    }
901
    }
902
    
902
    
903
    /** 
903
    /** 
904
     * Get range request
904
     * Get range request
905
     * 
905
     * 
906
     * @access  protected
906
     * @access  protected
907
     * @return  array
907
     * @return  array
908
     */
908
     */
909
    function getRanges()
909
    function getRanges()
910
    {
910
    {
911
        return preg_match('/^bytes=((\d*-\d*,? ?)+)$/', 
911
        return preg_match('/^bytes=((\d*-\d*,? ?)+)$/', 
912
            @$_SERVER['HTTP_RANGE'], $matches) ? $matches[1] : array();
912
            @$_SERVER['HTTP_RANGE'], $matches) ? $matches[1] : array();
913
    }
913
    }
914
    
914
    
915
    /** 
915
    /** 
916
     * Check if entity is cached
916
     * Check if entity is cached
917
     * 
917
     * 
918
     * @access  protected
918
     * @access  protected
919
     * @return  bool
919
     * @return  bool
920
     */
920
     */
921
    function isCached()
921
    function isCached()
922
    {
922
    {
923
        return (
923
        return (
924
            (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
924
            (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
925
            $this->lastModified == strtotime(current($a = explode(
925
            $this->lastModified == strtotime(current($a = explode(
926
                ';', $_SERVER['HTTP_IF_MODIFIED_SINCE'])))) ||
926
                ';', $_SERVER['HTTP_IF_MODIFIED_SINCE'])))) ||
927
            (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
927
            (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
928
            $this->compareAsterisk('HTTP_IF_NONE_MATCH', $this->etag))
928
            $this->compareAsterisk('HTTP_IF_NONE_MATCH', $this->etag))
929
        );
929
        );
930
    }
930
    }
931
    
931
    
932
    /** 
932
    /** 
933
     * Check if entity hasn't changed
933
     * Check if entity hasn't changed
934
     * 
934
     * 
935
     * @access  protected
935
     * @access  protected
936
     * @return  bool
936
     * @return  bool
937
     */
937
     */
938
    function isValidRange()
938
    function isValidRange()
939
    {
939
    {
940
        if (isset($_SERVER['HTTP_IF_MATCH']) &&
940
        if (isset($_SERVER['HTTP_IF_MATCH']) &&
941
            !$this->compareAsterisk('HTTP_IF_MATCH', $this->etag)) {
941
            !$this->compareAsterisk('HTTP_IF_MATCH', $this->etag)) {
942
            return false;
942
            return false;
943
        }
943
        }
944
        if (isset($_SERVER['HTTP_IF_RANGE']) &&
944
        if (isset($_SERVER['HTTP_IF_RANGE']) &&
945
                  $_SERVER['HTTP_IF_RANGE'] !== $this->etag &&
945
                  $_SERVER['HTTP_IF_RANGE'] !== $this->etag &&
946
                  strtotime($_SERVER['HTTP_IF_RANGE']) !== $this->lastModified) {
946
                  strtotime($_SERVER['HTTP_IF_RANGE']) !== $this->lastModified) {
947
            return false;
947
            return false;
948
        }
948
        }
949
        if (isset($_SERVER['HTTP_IF_UNMODIFIED_SINCE'])) {
949
        if (isset($_SERVER['HTTP_IF_UNMODIFIED_SINCE'])) {
950
            $lm = current($a = explode(';', $_SERVER['HTTP_IF_UNMODIFIED_SINCE']));
950
            $lm = current($a = explode(';', $_SERVER['HTTP_IF_UNMODIFIED_SINCE']));
951
            if (strtotime($lm) !== $this->lastModified) {
951
            if (strtotime($lm) !== $this->lastModified) {
952
                return false;
952
                return false;
953
            }
953
            }
954
        }
954
        }
955
        if (isset($_SERVER['HTTP_UNLESS_MODIFIED_SINCE'])) {
955
        if (isset($_SERVER['HTTP_UNLESS_MODIFIED_SINCE'])) {
956
            $lm = current($a = explode(';', $_SERVER['HTTP_UNLESS_MODIFIED_SINCE']));
956
            $lm = current($a = explode(';', $_SERVER['HTTP_UNLESS_MODIFIED_SINCE']));
957
            if (strtotime($lm) !== $this->lastModified) {
957
            if (strtotime($lm) !== $this->lastModified) {
958
                return false;
958
                return false;
959
            }
959
            }
960
        }
960
        }
961
        return true;
961
        return true;
962
    }
962
    }
963
    
963
    
964
    /** 
964
    /** 
965
     * Compare against an asterisk or check for equality
965
     * Compare against an asterisk or check for equality
966
     * 
966
     * 
967
     * @access  protected
967
     * @access  protected
968
     * @return  bool
968
     * @return  bool
969
     * @param   string  key for the $_SERVER array
969
     * @param   string  key for the $_SERVER array
970
     * @param   string  string to compare
970
     * @param   string  string to compare
971
     */
971
     */
972
    function compareAsterisk($svar, $compare)
972
    function compareAsterisk($svar, $compare)
973
    {
973
    {
974
        foreach (array_map('trim', explode(',', $_SERVER[$svar])) as $request) {
974
        foreach (array_map('trim', explode(',', $_SERVER[$svar])) as $request) {
975
            if ($request === '*' || $request === $compare) {
975
            if ($request === '*' || $request === $compare) {
976
                return true;
976
                return true;
977
            }
977
            }
978
        }
978
        }
979
        return false;
979
        return false;
980
    }
980
    }
981
    
981
    
982
    /**
982
    /**
983
     * Send HTTP headers
983
     * Send HTTP headers
984
     *
984
     *
985
     * @access  protected
985
     * @access  protected
986
     * @return  void
986
     * @return  void
987
     */
987
     */
988
    function sendHeaders()
988
    function sendHeaders()
989
    {
989
    {
990
        foreach ($this->headers as $header => $value) {
990
        foreach ($this->headers as $header => $value) {
991
            $this->HTTP->setHeader($header, $value);
991
            $this->HTTP->setHeader($header, $value);
992
        }
992
        }
993
        $this->HTTP->sendHeaders();
993
        $this->HTTP->sendHeaders();
994
        /* NSAPI won't output anything if we did this */
994
        /* NSAPI won't output anything if we did this */
995
        if (strncasecmp(PHP_SAPI, 'nsapi', 5)) {
995
        if (strncasecmp(PHP_SAPI, 'nsapi', 5)) {
996
            ob_flush();
996
            ob_flush();
997
            flush();
997
            flush();
998
        }
998
        }
999
    }
999
    }
1000
    
1000
    
1001
    /**
1001
    /**
1002
     * Flush
1002
     * Flush
1003
     * 
1003
     * 
1004
     * @access  protected
1004
     * @access  protected
1005
     * @return  void
1005
     * @return  void
1006
     * @param   string  $data
1006
     * @param   string  $data
1007
     */
1007
     */
1008
    function flush($data = '')
1008
    function flush($data = '')
1009
    {
1009
    {
1010
        if ($dlen = strlen($data)) {
1010
        if ($dlen = strlen($data)) {
1011
            $this->sentBytes += $dlen;
1011
            $this->sentBytes += $dlen;
1012
            echo $data;
1012
            echo $data;
1013
        }
1013
        }
1014
        ob_flush();
1014
        ob_flush();
1015
        flush();
1015
        flush();
1016
    }
1016
    }
1017
    
1017
    
1018
    /**
1018
    /**
1019
     * Sleep
1019
     * Sleep
1020
     * 
1020
     * 
1021
     * @access  protected
1021
     * @access  protected
1022
     * @return  void
1022
     * @return  void
1023
     */
1023
     */
1024
    function sleep()
1024
    function sleep()
1025
    {
1025
    {
1026
        if (OS_WINDOWS) {
1026
        if (OS_WINDOWS) {
1027
            com_message_pump($this->throttleDelay);
1027
            com_message_pump($this->throttleDelay);
1028
        } else {
1028
        } else {
1029
            usleep($this->throttleDelay * 1000);
1029
            usleep($this->throttleDelay * 1000);
1030
        }
1030
        }
1031
    }
1031
    }
1032
    // }}}
1032
    // }}}
1033
}
1033
}
1034
?>
1034
?>