xref: /dokuwiki/lib/exe/fetch.php (revision 0e99f6d7194833f1a152c286a984baca203b25ef)
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  header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
86  header('Pragma: public');
87  header('Accept-Ranges: bytes');
88  //send important headers first, script stops here if '304 Not Modified' response
89  http_conditionalRequest(filemtime($file));
90  list($start,$len) = http_rangeRequest(filesize($file));
91
92  //application mime type is downloadable
93  if(substr($mime,0,11) == 'application'){
94    header('Content-Disposition: attachment; filename="'.basename($file).'";');
95  }
96
97  // send file contents
98  $fp = @fopen($file,"rb");
99  if($fp){
100    fseek($fp,$start); //seek to start of range
101
102    $chunk = ($len > CHUNK_SIZE) ? CHUNK_SIZE : $len;
103    while (!feof($fp) && $chunk > 0) {
104      @set_time_limit(); // large files can take a lot of time
105      print fread($fp, $chunk);
106      flush();
107      $len -= $chunk;
108      $chunk = ($len > CHUNK_SIZE) ? CHUNK_SIZE : $len;
109    }
110    fclose($fp);
111  }else{
112    header("HTTP/1.0 500 Internal Server Error");
113    print "Could not read $file - bad permissions?";
114  }
115}
116
117/**
118 * Checks and sets headers to handle range requets
119 *
120 * @author  Andreas Gohr <andi@splitbrain.org>
121 * @returns array The start byte and the amount of bytes to send
122 */
123function http_rangeRequest($size){
124  if(!isset($_SERVER['HTTP_RANGE'])){
125    // no range requested - send the whole file
126    header("Content-Length: $size");
127    return array(0,$size);
128  }
129
130  $t = explode('=', $_SERVER['HTTP_RANGE']);
131  if (!$t[0]=='bytes') {
132    // we only understand byte ranges - send the whole file
133    header("Content-Length: $size");
134    return array(0,$size);
135  }
136
137  $r = explode('-', $t[1]);
138  $start = (int)$r[0];
139  $end = (int)$r[1];
140  if (!$end) $end = $size - 1;
141  if ($start > $end || $start > $size || $end > $size){
142    header('HTTP/1.1 416 Requested Range Not Satisfiable');
143    print 'Bad Range Request!';
144    exit;
145  }
146
147  $tot = $end - $start + 1;
148  header('HTTP/1.1 206 Partial Content');
149  header("Content-Range: bytes {$start}-{$end}/{$size}");
150  header("Content-Length: $tot");
151
152  return array($start,$tot);
153}
154
155/**
156 * Resizes the given image to the given size
157 *
158 * @author  Andreas Gohr <andi@splitbrain.org>
159 */
160function get_resized($file, $ext, $w, $h=0){
161  global $conf;
162
163  $info  = getimagesize($file);
164  if(!$h) $h = round(($w * $info[1]) / $info[0]);
165
166
167  //cache
168  $local = getCacheName($file,'.media.'.$w.'x'.$h.'.'.$ext);
169  $mtime = @filemtime($local); // 0 if not exists
170
171  if( $mtime > filemtime($file) ||
172      resize_imageIM($ext,$file,$info[0],$info[1],$local,$w,$h) ||
173      resize_imageGD($ext,$file,$info[0],$info[1],$local,$w,$h) ){
174    return $local;
175  }
176  //still here? resizing failed
177  return $file;
178}
179
180/**
181 * Returns the wanted cachetime in seconds
182 *
183 * Resolves named constants
184 *
185 * @author  Andreas Gohr <andi@splitbrain.org>
186 */
187function calc_cache($cache){
188  global $conf;
189
190  if(strtolower($cache) == 'nocache') return 0; //never cache
191  if(strtolower($cache) == 'recache') return $conf['cachetime']; //use standard cache
192  return -1; //cache endless
193}
194
195/**
196 * Download a remote file and return local filename
197 *
198 * returns false if download fails. Uses cached file if available and
199 * wanted
200 *
201 * @author  Andreas Gohr <andi@splitbrain.org>
202 * @author  Pavel Vitis <Pavel.Vitis@seznam.cz>
203 */
204function get_from_URL($url,$ext,$cache){
205  global $conf;
206
207  $local = getCacheName(strtolower($url),".media.$ext");
208  $mtime = @filemtime($local); // 0 if not exists
209
210  //decide if download needed:
211  if( $cache == 0 ||                             // never cache
212      ($mtime != 0 && $cache != -1) ||           // exists but no endless cache
213      ($mtime == 0) ||                           // not exists
214      ($cache != -1 && $mtime < time()-$cache)   // expired
215    ){
216      if(io_download($url,$local)){
217        return $local;
218      }else{
219        return false;
220      }
221  }
222
223  //if cache exists use it else
224  if($mtime) return $local;
225
226  //else return false
227  return false;
228}
229
230/**
231 * resize images using external ImageMagick convert program
232 *
233 * @author Pavel Vitis <Pavel.Vitis@seznam.cz>
234 * @author Andreas Gohr <andi@splitbrain.org>
235 */
236function resize_imageIM($ext,$from,$from_w,$from_h,$to,$to_w,$to_h){
237  global $conf;
238
239  // check if convert is configured
240  if(!$conf['im_convert']) return false;
241
242  // prepare command
243  $cmd  = $conf['im_convert'];
244  $cmd .= ' -resize '.$to_w.'x'.$to_h.'!';
245  $cmd .= " $from $to";
246
247  @exec($cmd,$out,$retval);
248  if ($retval == 0) return true;
249
250  return false;
251}
252
253/**
254 * resize images using PHP's libGD support
255 *
256 * @author Andreas Gohr <andi@splitbrain.org>
257 */
258function resize_imageGD($ext,$from,$from_w,$from_h,$to,$to_w,$to_h){
259  global $conf;
260
261  if($conf['gdlib'] < 1) return false; //no GDlib available or wanted
262
263  // check available memory
264  if(!is_mem_available(($from_w * $from_h * 4) + ($to_w * $to_h * 4))){
265    return false;
266  }
267
268  // create an image of the given filetype
269  if ($ext == 'jpg' || $ext == 'jpeg'){
270    if(!function_exists("imagecreatefromjpeg")) return false;
271    $image = @imagecreatefromjpeg($from);
272  }elseif($ext == 'png') {
273    if(!function_exists("imagecreatefrompng")) return false;
274    $image = @imagecreatefrompng($from);
275
276  }elseif($ext == 'gif') {
277    if(!function_exists("imagecreatefromgif")) return false;
278    $image = @imagecreatefromgif($from);
279  }
280  if(!$image) return false;
281
282  if(($conf['gdlib']>1) && function_exists("imagecreatetruecolor")){
283    $newimg = @imagecreatetruecolor ($to_w, $to_h);
284  }
285  if(!$newimg) $newimg = @imagecreate($to_w, $to_h);
286  if(!$newimg){
287    imagedestroy($image);
288    return false;
289  }
290
291  //keep png alpha channel if possible
292  if($ext == 'png' && $conf['gdlib']>1 && function_exists('imagesavealpha')){
293    imagealphablending($newimg, false);
294    imagesavealpha($newimg,true);
295  }
296
297  //try resampling first
298  if(function_exists("imagecopyresampled")){
299    if(!@imagecopyresampled($newimg, $image, 0, 0, 0, 0, $to_w, $to_h, $from_w, $from_h)) {
300      imagecopyresized($newimg, $image, 0, 0, 0, 0, $to_w, $to_h, $from_w, $from_h);
301    }
302  }else{
303    imagecopyresized($newimg, $image, 0, 0, 0, 0, $to_w, $to_h, $from_w, $from_h);
304  }
305
306  $okay = false;
307  if ($ext == 'jpg' || $ext == 'jpeg'){
308    if(!function_exists('imagejpeg')){
309      $okay = false;
310    }else{
311      $okay = imagejpeg($newimg, $to, 70);
312    }
313  }elseif($ext == 'png') {
314    if(!function_exists('imagepng')){
315      $okay = false;
316    }else{
317      $okay =  imagepng($newimg, $to);
318    }
319  }elseif($ext == 'gif') {
320    if(!function_exists('imagegif')){
321      $okay = false;
322    }else{
323      $okay = imagegif($newimg, $to);
324    }
325  }
326
327  // destroy GD image ressources
328  if($image) imagedestroy($image);
329  if($newimg) imagedestroy($newimg);
330
331  return $okay;
332}
333
334/**
335 * Checks if the given amount of memory is available
336 *
337 * If the memory_get_usage() function is not available the
338 * function just assumes $used bytes of already allocated memory
339 *
340 * @param  int $mem  Size of memory you want to allocate in bytes
341 * @param  int $used already allocated memory (see above)
342 * @author Filip Oscadal <webmaster@illusionsoftworks.cz>
343 * @author Andreas Gohr <andi@splitbrain.org>
344 */
345function is_mem_available($mem,$bytes=1048576){
346  $limit = trim(ini_get('memory_limit'));
347  if(empty($limit)) return true; // no limit set!
348
349  // parse limit to bytes
350  $unit = strtolower(substr($limit,-1));
351  switch($unit){
352    case 'g':
353      $limit = substr($limit,0,-1);
354      $limit *= 1024*1024*1024;
355      break;
356    case 'm':
357      $limit = substr($limit,0,-1);
358      $limit *= 1024*1024;
359      break;
360    case 'k':
361      $limit = substr($limit,0,-1);
362      $limit *= 1024;
363      break;
364  }
365
366  // get used memory if possible
367  if(function_exists('memory_get_usage')){
368    $used = memory_get_usage();
369  }
370
371
372  if($used+$mem > $limit){
373    return false;
374  }
375
376  return true;
377}
378
379//Setup VIM: ex: et ts=2 enc=utf-8 :
380?>
381