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