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