xref: /dokuwiki/lib/exe/fetch.php (revision 254e5c84cc0ef99f63ad829ef4abf3240d05ada6)
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 * Resizes the given image to the given size
156 *
157 * @author  Andreas Gohr <andi@splitbrain.org>
158 */
159function get_resized($file, $ext, $w, $h=0){
160  global $conf;
161
162  $info  = getimagesize($file);
163  if(!$h) $h = round(($w * $info[1]) / $info[0]);
164
165
166  //cache
167  $local = getCacheName($file,'.media.'.$w.'x'.$h.'.'.$ext);
168  $mtime = @filemtime($local); // 0 if not exists
169
170  if( $mtime > filemtime($file) ||
171      resize_imageIM($ext,$file,$info[0],$info[1],$local,$w,$h) ||
172      resize_imageGD($ext,$file,$info[0],$info[1],$local,$w,$h) ){
173    return $local;
174  }
175  //still here? resizing failed
176  return $file;
177}
178
179/**
180 * Returns the wanted cachetime in seconds
181 *
182 * Resolves named constants
183 *
184 * @author  Andreas Gohr <andi@splitbrain.org>
185 */
186function calc_cache($cache){
187  global $conf;
188
189  if(strtolower($cache) == 'nocache') return 0; //never cache
190  if(strtolower($cache) == 'recache') return $conf['cachetime']; //use standard cache
191  return -1; //cache endless
192}
193
194/**
195 * Download a remote file and return local filename
196 *
197 * returns false if download fails. Uses cached file if available and
198 * wanted
199 *
200 * @author  Andreas Gohr <andi@splitbrain.org>
201 * @author  Pavel Vitis <Pavel.Vitis@seznam.cz>
202 */
203function get_from_URL($url,$ext,$cache){
204  global $conf;
205
206  $local = getCacheName(strtolower($url),".media.$ext");
207  $mtime = @filemtime($local); // 0 if not exists
208
209  //decide if download needed:
210  if( $cache == 0 ||                             // never cache
211      ($mtime != 0 && $cache != -1) ||           // exists but no endless cache
212      ($mtime == 0) ||                           // not exists
213      ($cache != -1 && $mtime < time()-$cache)   // expired
214    ){
215      if(io_download($url,$local)){
216        return $local;
217      }else{
218        return false;
219      }
220  }
221
222  //if cache exists use it else
223  if($mtime) return $local;
224
225  //else return false
226  return false;
227}
228
229/**
230 * resize images using external ImageMagick convert program
231 *
232 * @author Pavel Vitis <Pavel.Vitis@seznam.cz>
233 * @author Andreas Gohr <andi@splitbrain.org>
234 */
235function resize_imageIM($ext,$from,$from_w,$from_h,$to,$to_w,$to_h){
236  global $conf;
237
238  // check if convert is configured
239  if(!$conf['im_convert']) return false;
240
241  // prepare command
242  $cmd  = $conf['im_convert'];
243  $cmd .= ' -resize '.$to_w.'x'.$to_h.'!';
244  $cmd .= " $from $to";
245
246  @exec($cmd,$out,$retval);
247  if ($retval == 0) return true;
248
249  return false;
250}
251
252/**
253 * resize images using PHP's libGD support
254 *
255 * @author Andreas Gohr <andi@splitbrain.org>
256 */
257function resize_imageGD($ext,$from,$from_w,$from_h,$to,$to_w,$to_h){
258  global $conf;
259
260  if($conf['gdlib'] < 1) return false; //no GDlib available or wanted
261
262  // check available memory
263  if(!is_mem_available(($from_w * $from_h * 4) + ($to_w * $to_h * 4))){
264    return false;
265  }
266
267  // create an image of the given filetype
268  if ($ext == 'jpg' || $ext == 'jpeg'){
269    if(!function_exists("imagecreatefromjpeg")) return false;
270    $image = @imagecreatefromjpeg($from);
271  }elseif($ext == 'png') {
272    if(!function_exists("imagecreatefrompng")) return false;
273    $image = @imagecreatefrompng($from);
274
275  }elseif($ext == 'gif') {
276    if(!function_exists("imagecreatefromgif")) return false;
277    $image = @imagecreatefromgif($from);
278  }
279  if(!$image) return false;
280
281  if(($conf['gdlib']>1) && function_exists("imagecreatetruecolor")){
282    $newimg = @imagecreatetruecolor ($to_w, $to_h);
283  }
284  if(!$newimg) $newimg = @imagecreate($to_w, $to_h);
285  if(!$newimg){
286    imagedestroy($image);
287    return false;
288  }
289
290  //keep png alpha channel if possible
291  if($ext == 'png' && $conf['gdlib']>1 && function_exists('imagesavealpha')){
292    imagealphablending($newimg, false);
293    imagesavealpha($newimg,true);
294  }
295
296  //try resampling first
297  if(function_exists("imagecopyresampled")){
298    if(!@imagecopyresampled($newimg, $image, 0, 0, 0, 0, $to_w, $to_h, $from_w, $from_h)) {
299      imagecopyresized($newimg, $image, 0, 0, 0, 0, $to_w, $to_h, $from_w, $from_h);
300    }
301  }else{
302    imagecopyresized($newimg, $image, 0, 0, 0, 0, $to_w, $to_h, $from_w, $from_h);
303  }
304
305  $okay = false;
306  if ($ext == 'jpg' || $ext == 'jpeg'){
307    if(!function_exists('imagejpeg')){
308      $okay = false;
309    }else{
310      $okay = imagejpeg($newimg, $to, 70);
311    }
312  }elseif($ext == 'png') {
313    if(!function_exists('imagepng')){
314      $okay = false;
315    }else{
316      $okay =  imagepng($newimg, $to);
317    }
318  }elseif($ext == 'gif') {
319    if(!function_exists('imagegif')){
320      $okay = false;
321    }else{
322      $okay = imagegif($newimg, $to);
323    }
324  }
325
326  // destroy GD image ressources
327  if($image) imagedestroy($image);
328  if($newimg) imagedestroy($newimg);
329
330  return $okay;
331}
332
333/**
334 * Checks if the given amount of memory is available
335 *
336 * If the memory_get_usage() function is not available the
337 * function just assumes $used bytes of already allocated memory
338 *
339 * @param  int $mem  Size of memory you want to allocate in bytes
340 * @param  int $used already allocated memory (see above)
341 * @author Filip Oscadal <webmaster@illusionsoftworks.cz>
342 * @author Andreas Gohr <andi@splitbrain.org>
343 */
344function is_mem_available($mem,$bytes=1048576){
345  $limit = trim(ini_get('memory_limit'));
346  if(empty($limit)) return true; // no limit set!
347
348  // parse limit to bytes
349  $unit = strtolower(substr($limit,-1));
350  switch($unit){
351    case 'g':
352      $limit = substr($limit,0,-1);
353      $limit *= 1024*1024*1024;
354      break;
355    case 'm':
356      $limit = substr($limit,0,-1);
357      $limit *= 1024*1024;
358      break;
359    case 'k':
360      $limit = substr($limit,0,-1);
361      $limit *= 1024;
362      break;
363  }
364
365  // get used memory if possible
366  if(function_exists('memory_get_usage')){
367    $used = memory_get_usage();
368  }
369
370
371  if($used+$mem > $limit){
372    return false;
373  }
374
375  return true;
376}
377
378//Setup VIM: ex: et ts=2 enc=utf-8 :
379?>
380