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