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