xref: /dokuwiki/lib/exe/fetch.php (revision 3b399a1bd4eba22429d676bddd759ed762c9e80e)
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
9if(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__).'/../../');
10define('DOKU_DISABLE_GZIP_OUTPUT', 1);
11require_once(DOKU_INC.'inc/init.php');
12//close session
13session_write_close();
14
15$mimetypes = getMimeTypes();
16
17//get input
18$MEDIA  = stripctl(getID('media', false)); // no cleaning except control chars - maybe external
19$CACHE  = calc_cache($INPUT->str('cache'));
20$WIDTH  = $INPUT->int('w');
21$HEIGHT = $INPUT->int('h');
22$REV    = & $INPUT->ref('rev');
23//sanitize revision
24$REV = preg_replace('/[^0-9]/', '', $REV);
25
26list($EXT, $MIME, $DL) = mimetype($MEDIA, false);
27if($EXT === false) {
28    $EXT  = 'unknown';
29    $MIME = 'application/octet-stream';
30    $DL   = true;
31}
32
33// check for permissions, preconditions and cache external files
34list($STATUS, $STATUSMESSAGE) = checkFileStatus($MEDIA, $FILE, $REV);
35
36// prepare data for plugin events
37$data = array(
38    'media'         => $MEDIA,
39    'file'          => $FILE,
40    'orig'          => $FILE,
41    'mime'          => $MIME,
42    'download'      => $DL,
43    'cache'         => $CACHE,
44    'ext'           => $EXT,
45    'width'         => $WIDTH,
46    'height'        => $HEIGHT,
47    'status'        => $STATUS,
48    'statusmessage' => $STATUSMESSAGE,
49);
50
51// handle the file status
52$evt = new Doku_Event('FETCH_MEDIA_STATUS', $data);
53if($evt->advise_before()) {
54    // redirects
55    if($data['status'] > 300 && $data['status'] <= 304) {
56        send_redirect($data['statusmessage']);
57    }
58    // send any non 200 status
59    if($data['status'] != 200) {
60        header('HTTP/1.0 '.$data['status'].' '.$data['statusmessage']);
61    }
62    // die on errors
63    if($data['status'] > 203) {
64        print $data['statusmessage'];
65        exit;
66    }
67}
68$evt->advise_after();
69unset($evt);
70
71//handle image resizing/cropping
72if((substr($MIME, 0, 5) == 'image') && $WIDTH) {
73    if($HEIGHT) {
74        $data['file'] = $FILE = media_crop_image($data['file'], $EXT, $WIDTH, $HEIGHT);
75    } else {
76        $data['file'] = $FILE = media_resize_image($data['file'], $EXT, $WIDTH, $HEIGHT);
77    }
78}
79
80// finally send the file to the client
81$evt = new Doku_Event('MEDIA_SENDFILE', $data);
82if($evt->advise_before()) {
83    sendFile($data['file'], $data['mime'], $data['download'], $data['cache']);
84}
85// Do something after the download finished.
86$evt->advise_after();
87
88/* ------------------------------------------------------------------------ */
89
90/**
91 * Set headers and send the file to the client
92 *
93 * @author Andreas Gohr <andi@splitbrain.org>
94 * @author Ben Coburn <btcoburn@silicodon.net>
95 */
96function sendFile($file, $mime, $dl, $cache) {
97    global $conf;
98    $fmtime = @filemtime($file);
99    // send headers
100    header("Content-Type: $mime");
101    // smart http caching headers
102    if($cache == -1) {
103        // cache
104        // cachetime or one hour
105        header('Expires: '.gmdate("D, d M Y H:i:s", time() + max($conf['cachetime'], 3600)).' GMT');
106        header('Cache-Control: public, proxy-revalidate, no-transform, max-age='.max($conf['cachetime'], 3600));
107        header('Pragma: public');
108    } else if($cache > 0) {
109        // recache
110        // remaining cachetime + 10 seconds so the newly recached media is used
111        header('Expires: '.gmdate("D, d M Y H:i:s", $fmtime + $conf['cachetime'] + 10).' GMT');
112        header('Cache-Control: public, proxy-revalidate, no-transform, max-age='.max($fmtime - time() + $conf['cachetime'] + 10, 0));
113        header('Pragma: public');
114    } else if($cache == 0) {
115        // nocache
116        header('Cache-Control: must-revalidate, no-transform, post-check=0, pre-check=0');
117        header('Pragma: public');
118    }
119    //send important headers first, script stops here if '304 Not Modified' response
120    http_conditionalRequest($fmtime);
121
122    //download or display?
123    if($dl) {
124        header('Content-Disposition: attachment; filename="'.utf8_basename($file).'";');
125    } else {
126        header('Content-Disposition: inline; filename="'.utf8_basename($file).'";');
127    }
128
129    //use x-sendfile header to pass the delivery to compatible webservers
130    if(http_sendfile($file)) exit;
131
132    // send file contents
133    $fp = @fopen($file, "rb");
134    if($fp) {
135        http_rangeRequest($fp, filesize($file), $mime);
136    } else {
137        header("HTTP/1.0 500 Internal Server Error");
138        print "Could not read $file - bad permissions?";
139    }
140}
141
142/**
143 * Check for media for preconditions and return correct status code
144 *
145 * READ: MEDIA, MIME, EXT, CACHE
146 * WRITE: MEDIA, FILE, array( STATUS, STATUSMESSAGE )
147 *
148 * @author Gerry Weissbach <gerry.w@gammaproduction.de>
149 * @param $media reference to the media id
150 * @param $file  reference to the file variable
151 * @returns array(STATUS, STATUSMESSAGE)
152 */
153function checkFileStatus(&$media, &$file, $rev = '') {
154    global $MIME, $EXT, $CACHE, $INPUT;
155
156    //media to local file
157    if(preg_match('#^(https?)://#i', $media)) {
158        //check hash
159        if(substr(md5(auth_cookiesalt().$media), 0, 6) !== $INPUT->str('hash')) {
160            return array(412, 'Precondition Failed');
161        }
162        //handle external images
163        if(strncmp($MIME, 'image/', 6) == 0) $file = media_get_from_URL($media, $EXT, $CACHE);
164        if(!$file) {
165            //download failed - redirect to original URL
166            return array(302, $media);
167        }
168    } else {
169        $media = cleanID($media);
170        if(empty($media)) {
171            return array(400, 'Bad request');
172        }
173
174        //check permissions (namespace only)
175        if(auth_quickaclcheck(getNS($media).':X') < AUTH_READ) {
176            return array(403, 'Forbidden');
177        }
178        $file = mediaFN($media, $rev);
179    }
180
181    //check file existance
182    if(!@file_exists($file)) {
183        return array(404, 'Not Found');
184    }
185
186    return array(200, null);
187}
188
189/**
190 * Returns the wanted cachetime in seconds
191 *
192 * Resolves named constants
193 *
194 * @author  Andreas Gohr <andi@splitbrain.org>
195 */
196function calc_cache($cache) {
197    global $conf;
198
199    if(strtolower($cache) == 'nocache') return 0; //never cache
200    if(strtolower($cache) == 'recache') return $conf['cachetime']; //use standard cache
201    return -1; //cache endless
202}
203
204//Setup VIM: ex: et ts=2 :
205