xref: /dokuwiki/lib/exe/fetch.php (revision 78c7c8c9da23cd3799beecd1da873261238af12b)
1<?php
2/**
3 * DokuWiki media passthrough file
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Andreas Gohr <andi@splitbrain.org>
7 */
8
9  if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
10  require_once(DOKU_INC.'inc/init.php');
11  require_once(DOKU_INC.'inc/common.php');
12  require_once(DOKU_INC.'inc/pageutils.php');
13  require_once(DOKU_INC.'inc/confutils.php');
14  require_once(DOKU_INC.'inc/auth.php');
15  //close sesseion
16  session_write_close();
17  if(!defined('CHUNK_SIZE')) define('CHUNK_SIZE',16*1024);
18
19  $mimetypes = getMimeTypes();
20
21  //get input
22  $MEDIA  = getID('media',false); // no cleaning - maybe external
23  $CACHE  = calc_cache($_REQUEST['cache']);
24  $WIDTH  = $_REQUEST['w'];
25  $HEIGHT = $_REQUEST['h'];
26  list($EXT,$MIME) = mimetype($MEDIA);
27  if($EXT === false){
28    $EXT  = 'unknown';
29    $MIME = 'application/octet-stream';
30  }
31
32  //media to local file
33  if(preg_match('#^(https?|ftp)://#i',$MEDIA)){
34    //handle external media
35    $FILE = get_from_URL($MEDIA,$EXT,$CACHE);
36    if(!$FILE){
37      //download failed - redirect to original URL
38      header('Location: '.$MEDIA);
39      exit;
40    }
41  }else{
42    $MEDIA = cleanID($MEDIA);
43    if(empty($MEDIA)){
44      header("HTTP/1.0 400 Bad Request");
45      print 'Bad request';
46      exit;
47    }
48
49    //check permissions (namespace only)
50    if(auth_quickaclcheck(getNS($MEDIA).':X') < AUTH_READ){
51      header("HTTP/1.0 401 Unauthorized");
52      //fixme add some image for imagefiles
53      print 'Unauthorized';
54      exit;
55    }
56    $FILE  = mediaFN($MEDIA);
57  }
58
59  //check file existance
60  if(!@file_exists($FILE)){
61    header("HTTP/1.0 404 Not Found");
62    //FIXME add some default broken image
63    print 'Not Found';
64    exit;
65  }
66
67  //handle image resizing
68  if((substr($MIME,0,5) == 'image') && $WIDTH){
69    $FILE = get_resized($FILE,$EXT,$WIDTH,$HEIGHT);
70  }
71
72  // finally send the file to the client
73  sendFile($FILE,$MIME);
74
75/* ------------------------------------------------------------------------ */
76
77/**
78 * Set headers and send the file to the client
79 *
80 * @author Andreas Gohr <andi@splitbrain.org>
81 */
82function sendFile($file,$mime){
83  // send headers
84  header("Content-Type: $mime");
85  http_conditionalRequest(filemtime($file));
86  list($start,$len) = http_rangeRequest(filesize($file));
87  header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
88  header('Pragma: public');
89  header('Accept-Ranges: bytes');
90
91  //application mime type is downloadable
92  if(substr($mime,0,11) == 'application'){
93    header('Content-Disposition: attachment; filename="'.basename($file).'";');
94  }
95
96  // send file contents
97  $fp = @fopen($file,"rb");
98  if($fp){
99    fseek($fp,$start); //seek to start of range
100
101    $chunk = ($len > CHUNK_SIZE) ? CHUNK_SIZE : $len;
102    while (!feof($fp) && $chunk > 0) {
103      @set_time_limit(); // large files can take a lot of time
104      print fread($fp, $chunk);
105      flush();
106      $len -= $chunk;
107      $chunk = ($len > CHUNK_SIZE) ? CHUNK_SIZE : $len;
108    }
109    fclose($fp);
110  }else{
111    header("HTTP/1.0 500 Internal Server Error");
112    print "Could not read $file - bad permissions?";
113  }
114}
115
116/**
117 * Checks and sets headers to handle range requets
118 *
119 * @author  Andreas Gohr <andi@splitbrain.org>
120 * @returns array The start byte and the amount of bytes to send
121 */
122function http_rangeRequest($size){
123  if(!isset($_SERVER['HTTP_RANGE'])){
124    // no range requested - send the whole file
125    header("Content-Length: $size");
126    return array(0,$size);
127  }
128
129  $t = explode('=', $_SERVER['HTTP_RANGE']);
130  if (!$t[0]=='bytes') {
131    // we only understand byte ranges - send the whole file
132    header("Content-Length: $size");
133    return array(0,$size);
134  }
135
136  $r = explode('-', $t[1]);
137  $start = (int)$r[0];
138  $end = (int)$r[1];
139  if (!$end) $end = $size - 1;
140  if ($start > $end || $start > $size || $end > $size){
141    header('HTTP/1.1 416 Requested Range Not Satisfiable');
142    print 'Bad Range Request!';
143    exit;
144  }
145
146  $tot = $end - $start + 1;
147  header('HTTP/1.1 206 Partial Content');
148  header("Content-Range: bytes {$start}-{$end}/{$size}");
149  header("Content-Length: $tot");
150
151  return array($start,$tot);
152}
153
154/**
155 * Checks and sets HTTP headers for conditional HTTP requests
156 *
157 * @author Simon Willison <swillison@gmail.com>
158 * @link   http://simon.incutio.com/archive/2003/04/23/conditionalGet
159 */
160function http_conditionalRequest($timestamp){
161    // A PHP implementation of conditional get, see
162    //   http://fishbowl.pastiche.org/archives/001132.html
163    $last_modified = substr(date('r', $timestamp), 0, -5).'GMT';
164    $etag = '"'.md5($last_modified).'"';
165    // Send the headers
166    header("Last-Modified: $last_modified");
167    header("ETag: $etag");
168    // See if the client has provided the required headers
169    $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ?
170        stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) :
171        false;
172    $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ?
173        stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) :
174        false;
175    if (!$if_modified_since && !$if_none_match) {
176        return;
177    }
178    // At least one of the headers is there - check them
179    if ($if_none_match && $if_none_match != $etag) {
180        return; // etag is there but doesn't match
181    }
182    if ($if_modified_since && $if_modified_since != $last_modified) {
183        return; // if-modified-since is there but doesn't match
184    }
185    // Nothing has changed since their last request - serve a 304 and exit
186    header('HTTP/1.0 304 Not Modified');
187    exit;
188}
189
190/**
191 * Resizes the given image to the given size
192 *
193 * @author  Andreas Gohr <andi@splitbrain.org>
194 */
195function get_resized($file, $ext, $w, $h=0){
196  global $conf;
197
198  $info  = getimagesize($file);
199  if(!$h) $h = round(($w * $info[1]) / $info[0]);
200
201
202  //cache
203  $local = getCacheName($file,'.media.'.$w.'x'.$h.'.'.$ext);
204  $mtime = @filemtime($local); // 0 if not exists
205
206  if( $mtime > filemtime($file) ||
207      resize_imageIM($ext,$file,$info[0],$info[1],$local,$w,$h) ||
208      resize_imageGD($ext,$file,$info[0],$info[1],$local,$w,$h) ){
209    return $local;
210  }
211  //still here? resizing failed
212  return $file;
213}
214
215/**
216 * Returns the wanted cachetime in seconds
217 *
218 * Resolves named constants
219 *
220 * @author  Andreas Gohr <andi@splitbrain.org>
221 */
222function calc_cache($cache){
223  global $conf;
224
225  if(strtolower($cache) == 'nocache') return 0; //never cache
226  if(strtolower($cache) == 'recache') return $conf['cachetime']; //use standard cache
227  return -1; //cache endless
228}
229
230/**
231 * Download a remote file and return local filename
232 *
233 * returns false if download fails. Uses cached file if available and
234 * wanted
235 *
236 * @author  Andreas Gohr <andi@splitbrain.org>
237 * @author  Pavel Vitis <Pavel.Vitis@seznam.cz>
238 */
239function get_from_URL($url,$ext,$cache){
240  global $conf;
241
242  $local = getCacheName(strtolower($url),".media.$ext");
243  $mtime = @filemtime($local); // 0 if not exists
244
245  //decide if download needed:
246  if( $cache == 0 ||                             // never cache
247      ($mtime != 0 && $cache != -1) ||           // exists but no endless cache
248      ($mtime == 0) ||                           // not exists
249      ($cache != -1 && $mtime < time()-$cache)   // expired
250    ){
251      if(io_download($url,$local)){
252        return $local;
253      }else{
254        return false;
255      }
256  }
257
258  //if cache exists use it else
259  if($mtime) return $local;
260
261  //else return false
262  return false;
263}
264
265/**
266 * resize images using external ImageMagick convert program
267 *
268 * @author Pavel Vitis <Pavel.Vitis@seznam.cz>
269 * @author Andreas Gohr <andi@splitbrain.org>
270 */
271function resize_imageIM($ext,$from,$from_w,$from_h,$to,$to_w,$to_h){
272  global $conf;
273
274  // check if convert is configured
275  if(!$conf['im_convert']) return false;
276
277  // prepare command
278  $cmd  = $conf['im_convert'];
279  $cmd .= ' -resize '.$to_w.'x'.$to_h.'!';
280  $cmd .= " $from $to";
281
282  @exec($cmd,$out,$retval);
283  if ($retval == 0) return true;
284
285  return false;
286}
287
288/**
289 * resize images using PHP's libGD support
290 *
291 * @author Andreas Gohr <andi@splitbrain.org>
292 */
293function resize_imageGD($ext,$from,$from_w,$from_h,$to,$to_w,$to_h){
294  global $conf;
295
296  if($conf['gdlib'] < 1) return false; //no GDlib available or wanted
297
298  // check available memory
299  if(!is_mem_available(($from_w * $from_h * 4) + ($to_w * $to_h * 4))){
300    return false;
301  }
302
303  // create an image of the given filetype
304  if ($ext == 'jpg' || $ext == 'jpeg'){
305    if(!function_exists("imagecreatefromjpeg")) return false;
306    $image = @imagecreatefromjpeg($from);
307  }elseif($ext == 'png') {
308    if(!function_exists("imagecreatefrompng")) return false;
309    $image = @imagecreatefrompng($from);
310
311  }elseif($ext == 'gif') {
312    if(!function_exists("imagecreatefromgif")) return false;
313    $image = @imagecreatefromgif($from);
314  }
315  if(!$image) return false;
316
317  if(($conf['gdlib']>1) && function_exists("imagecreatetruecolor")){
318    $newimg = @imagecreatetruecolor ($to_w, $to_h);
319  }
320  if(!$newimg) $newimg = @imagecreate($to_w, $to_h);
321  if(!$newimg){
322    imagedestroy($image);
323    return false;
324  }
325
326  //keep png alpha channel if possible
327  if($ext == 'png' && $conf['gdlib']>1 && function_exists('imagesavealpha')){
328    imagealphablending($newimg, false);
329    imagesavealpha($newimg,true);
330  }
331
332  //try resampling first
333  if(function_exists("imagecopyresampled")){
334    if(!@imagecopyresampled($newimg, $image, 0, 0, 0, 0, $to_w, $to_h, $from_w, $from_h)) {
335      imagecopyresized($newimg, $image, 0, 0, 0, 0, $to_w, $to_h, $from_w, $from_h);
336    }
337  }else{
338    imagecopyresized($newimg, $image, 0, 0, 0, 0, $to_w, $to_h, $from_w, $from_h);
339  }
340
341  $okay = false;
342  if ($ext == 'jpg' || $ext == 'jpeg'){
343    if(!function_exists('imagejpeg')){
344      $okay = false;
345    }else{
346      $okay = imagejpeg($newimg, $to, 70);
347    }
348  }elseif($ext == 'png') {
349    if(!function_exists('imagepng')){
350      $okay = false;
351    }else{
352      $okay =  imagepng($newimg, $to);
353    }
354  }elseif($ext == 'gif') {
355    if(!function_exists('imagegif')){
356      $okay = false;
357    }else{
358      $okay = imagegif($newimg, $to);
359    }
360  }
361
362  // destroy GD image ressources
363  if($image) imagedestroy($image);
364  if($newimg) imagedestroy($newimg);
365
366  return $okay;
367}
368
369/**
370 * Checks if the given amount of memory is available
371 *
372 * If the memory_get_usage() function is not available the
373 * function just assumes $used bytes of already allocated memory
374 *
375 * @param  int $mem  Size of memory you want to allocate in bytes
376 * @param  int $used already allocated memory (see above)
377 * @author Filip Oscadal <webmaster@illusionsoftworks.cz>
378 * @author Andreas Gohr <andi@splitbrain.org>
379 */
380function is_mem_available($mem,$bytes=1048576){
381  $limit = trim(ini_get('memory_limit'));
382  if(empty($limit)) return true; // no limit set!
383
384  // parse limit to bytes
385  $unit = strtolower(substr($limit,-1));
386  switch($unit){
387    case 'g':
388      $limit = substr($limit,0,-1);
389      $limit *= 1024*1024*1024;
390      break;
391    case 'm':
392      $limit = substr($limit,0,-1);
393      $limit *= 1024*1024;
394      break;
395    case 'k':
396      $limit = substr($limit,0,-1);
397      $limit *= 1024;
398      break;
399  }
400
401  // get used memory if possible
402  if(function_exists('memory_get_usage')){
403    $used = memory_get_usage();
404  }
405
406
407  if($used+$mem > $limit){
408    return false;
409  }
410
411  return true;
412}
413
414//Setup VIM: ex: et ts=2 enc=utf-8 :
415?>
416