xref: /dokuwiki/lib/exe/fetch.php (revision 4dc44bc35045455e2d2d2773e986705ff672c868)
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',dirname(__FILE__).'/../../');
10  define('DOKU_DISABLE_GZIP_OUTPUT', 1);
11  require_once(DOKU_INC.'inc/init.php');
12  require_once(DOKU_INC.'inc/common.php');
13  require_once(DOKU_INC.'inc/media.php');
14  require_once(DOKU_INC.'inc/pageutils.php');
15  require_once(DOKU_INC.'inc/confutils.php');
16  require_once(DOKU_INC.'inc/auth.php');
17  //close sesseion
18  session_write_close();
19  if(!defined('CHUNK_SIZE')) define('CHUNK_SIZE',16*1024);
20
21  $mimetypes = getMimeTypes();
22
23  //get input
24  $MEDIA  = stripctl(getID('media',false)); // no cleaning except control chars - maybe external
25  $CACHE  = calc_cache($_REQUEST['cache']);
26  $WIDTH  = (int) $_REQUEST['w'];
27  $HEIGHT = (int) $_REQUEST['h'];
28  list($EXT,$MIME,$DL) = mimetype($MEDIA);
29  if($EXT === false){
30    $EXT  = 'unknown';
31    $MIME = 'application/octet-stream';
32    $DL   = true;
33  }
34
35  //media to local file
36  if(preg_match('#^(https?)://#i',$MEDIA)){
37    //handle external images
38    if(strncmp($MIME,'image/',6) == 0) $FILE = media_get_from_URL($MEDIA,$EXT,$CACHE);
39    if(!$FILE){
40      //download failed - redirect to original URL
41      header('Location: '.$MEDIA);
42      exit;
43    }
44  }else{
45    $MEDIA = cleanID($MEDIA);
46    if(empty($MEDIA)){
47      header("HTTP/1.0 400 Bad Request");
48      print 'Bad request';
49      exit;
50    }
51
52    //check permissions (namespace only)
53    if(auth_quickaclcheck(getNS($MEDIA).':X') < AUTH_READ){
54      header("HTTP/1.0 401 Unauthorized");
55      //fixme add some image for imagefiles
56      print 'Unauthorized';
57      exit;
58    }
59    $FILE  = mediaFN($MEDIA);
60  }
61
62  //check file existance
63  if(!@file_exists($FILE)){
64    header("HTTP/1.0 404 Not Found");
65    //FIXME add some default broken image
66    print 'Not Found';
67    exit;
68  }
69
70  $ORIG = $FILE;
71
72  //handle image resizing/cropping
73  if((substr($MIME,0,5) == 'image') && $WIDTH){
74    if($HEIGHT){
75        $FILE = media_crop_image($FILE,$EXT,$WIDTH,$HEIGHT);
76    }else{
77        $FILE = media_resize_image($FILE,$EXT,$WIDTH,$HEIGHT);
78    }
79  }
80
81  // finally send the file to the client
82  $data = array('file'     => $FILE,
83                'mime'     => $MIME,
84                'download' => $DL,
85                'cache'    => $CACHE,
86                'orig'     => $ORIG,
87                'ext'      => $EXT,
88                'width'    => $WIDTH,
89                'height'   => $HEIGHT);
90
91  $evt = new Doku_Event('MEDIA_SENDFILE', $data);
92  if ($evt->advise_before()) {
93    sendFile($data['file'],$data['mime'],$data['download'],$data['cache']);
94  }
95
96/* ------------------------------------------------------------------------ */
97
98/**
99 * Set headers and send the file to the client
100 *
101 * @author Andreas Gohr <andi@splitbrain.org>
102 * @author Ben Coburn <btcoburn@silicodon.net>
103 */
104function sendFile($file,$mime,$dl,$cache){
105  global $conf;
106  $fmtime = @filemtime($file);
107  // send headers
108  header("Content-Type: $mime");
109  // smart http caching headers
110  if ($cache==-1) {
111    // cache
112    // cachetime or one hour
113    header('Expires: '.gmdate("D, d M Y H:i:s", time()+max($conf['cachetime'], 3600)).' GMT');
114    header('Cache-Control: public, proxy-revalidate, no-transform, max-age='.max($conf['cachetime'], 3600));
115    header('Pragma: public');
116  } else if ($cache>0) {
117    // recache
118    // remaining cachetime + 10 seconds so the newly recached media is used
119    header('Expires: '.gmdate("D, d M Y H:i:s", $fmtime+$conf['cachetime']+10).' GMT');
120    header('Cache-Control: public, proxy-revalidate, no-transform, max-age='.max($fmtime-time()+$conf['cachetime']+10, 0));
121    header('Pragma: public');
122  } else if ($cache==0) {
123    // nocache
124    header('Cache-Control: must-revalidate, no-transform, post-check=0, pre-check=0');
125    header('Pragma: public');
126  }
127  //send important headers first, script stops here if '304 Not Modified' response
128  http_conditionalRequest($fmtime);
129
130
131  //download or display?
132  if($dl){
133    header('Content-Disposition: attachment; filename="'.basename($file).'";');
134  }else{
135    header('Content-Disposition: inline; filename="'.basename($file).'";');
136  }
137
138  //use x-sendfile header to pass the delivery to compatible webservers
139  if($conf['xsendfile'] == 1){
140    header("X-LIGHTTPD-send-file: $file");
141    exit;
142  }elseif($conf['xsendfile'] == 2){
143    header("X-Sendfile: $file");
144    exit;
145  }elseif($conf['xsendfile'] == 3){
146    header("X-Accel-Redirect: $file");
147    exit;
148  }
149
150  //support download continueing
151  header('Accept-Ranges: bytes');
152  list($start,$len) = http_rangeRequest(filesize($file));
153
154  // send file contents
155  $fp = @fopen($file,"rb");
156  if($fp){
157    fseek($fp,$start); //seek to start of range
158
159    $chunk = ($len > CHUNK_SIZE) ? CHUNK_SIZE : $len;
160    while (!feof($fp) && $chunk > 0) {
161      @set_time_limit(30); // large files can take a lot of time
162      print fread($fp, $chunk);
163      flush();
164      $len -= $chunk;
165      $chunk = ($len > CHUNK_SIZE) ? CHUNK_SIZE : $len;
166    }
167    fclose($fp);
168  }else{
169    header("HTTP/1.0 500 Internal Server Error");
170    print "Could not read $file - bad permissions?";
171  }
172}
173
174/**
175 * Checks and sets headers to handle range requets
176 *
177 * @author  Andreas Gohr <andi@splitbrain.org>
178 * @returns array The start byte and the amount of bytes to send
179 */
180function http_rangeRequest($size){
181  if(!isset($_SERVER['HTTP_RANGE'])){
182    // no range requested - send the whole file
183    header("Content-Length: $size");
184    return array(0,$size);
185  }
186
187  $t = explode('=', $_SERVER['HTTP_RANGE']);
188  if (!$t[0]=='bytes') {
189    // we only understand byte ranges - send the whole file
190    header("Content-Length: $size");
191    return array(0,$size);
192  }
193
194  $r = explode('-', $t[1]);
195  $start = (int)$r[0];
196  $end = (int)$r[1];
197  if (!$end) $end = $size - 1;
198  if ($start > $end || $start > $size || $end > $size){
199    header('HTTP/1.1 416 Requested Range Not Satisfiable');
200    print 'Bad Range Request!';
201    exit;
202  }
203
204  $tot = $end - $start + 1;
205  header('HTTP/1.1 206 Partial Content');
206  header("Content-Range: bytes {$start}-{$end}/{$size}");
207  header("Content-Length: $tot");
208
209  return array($start,$tot);
210}
211
212/**
213 * Returns the wanted cachetime in seconds
214 *
215 * Resolves named constants
216 *
217 * @author  Andreas Gohr <andi@splitbrain.org>
218 */
219function calc_cache($cache){
220  global $conf;
221
222  if(strtolower($cache) == 'nocache') return 0; //never cache
223  if(strtolower($cache) == 'recache') return $conf['cachetime']; //use standard cache
224  return -1; //cache endless
225}
226
227//Setup VIM: ex: et ts=2 enc=utf-8 :
228?>
229