1f62ea8a1Sandi<?php 2f62ea8a1Sandi/** 3f62ea8a1Sandi * DokuWiki media passthrough file 4f62ea8a1Sandi * 5f62ea8a1Sandi * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6f62ea8a1Sandi * @author Andreas Gohr <andi@splitbrain.org> 7f62ea8a1Sandi */ 8f62ea8a1Sandi 9d0a27cb0SAndreas Gohr if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); 103138b5c7SAndreas Gohr define('DOKU_DISABLE_GZIP_OUTPUT', 1); 11f62ea8a1Sandi require_once(DOKU_INC.'inc/init.php'); 12f62ea8a1Sandi require_once(DOKU_INC.'inc/common.php'); 1313c08e2fSMichael Klier require_once(DOKU_INC.'inc/media.php'); 14f62ea8a1Sandi require_once(DOKU_INC.'inc/pageutils.php'); 15f62ea8a1Sandi require_once(DOKU_INC.'inc/confutils.php'); 16f62ea8a1Sandi require_once(DOKU_INC.'inc/auth.php'); 178746e727Sandi //close sesseion 188746e727Sandi session_write_close(); 19e935fb4aSAndreas Gohr if(!defined('CHUNK_SIZE')) define('CHUNK_SIZE',16*1024); 208746e727Sandi 21f62ea8a1Sandi $mimetypes = getMimeTypes(); 22f62ea8a1Sandi 23f62ea8a1Sandi //get input 2402b0b681SAndreas Gohr $MEDIA = stripctl(getID('media',false)); // no cleaning except control chars - maybe external 25f62ea8a1Sandi $CACHE = calc_cache($_REQUEST['cache']); 268fcc3410SAndreas Gohr $WIDTH = (int) $_REQUEST['w']; 278fcc3410SAndreas Gohr $HEIGHT = (int) $_REQUEST['h']; 28*ecebf3a8SAndreas Gohr list($EXT,$MIME,$DL) = mimetype($MEDIA); 29f62ea8a1Sandi if($EXT === false){ 30f62ea8a1Sandi $EXT = 'unknown'; 31f62ea8a1Sandi $MIME = 'application/octet-stream'; 32*ecebf3a8SAndreas Gohr $DL = true; 33f62ea8a1Sandi } 34f62ea8a1Sandi 35f62ea8a1Sandi //media to local file 36d1ed0b61SAndreas Gohr if(preg_match('#^(https?)://#i',$MEDIA)){ 37d1ed0b61SAndreas Gohr //handle external images 3813c08e2fSMichael Klier if(strncmp($MIME,'image/',6) == 0) $FILE = media_get_from_URL($MEDIA,$EXT,$CACHE); 39f62ea8a1Sandi if(!$FILE){ 40f62ea8a1Sandi //download failed - redirect to original URL 41f62ea8a1Sandi header('Location: '.$MEDIA); 42f62ea8a1Sandi exit; 43f62ea8a1Sandi } 44f62ea8a1Sandi }else{ 45f62ea8a1Sandi $MEDIA = cleanID($MEDIA); 46f62ea8a1Sandi if(empty($MEDIA)){ 47f62ea8a1Sandi header("HTTP/1.0 400 Bad Request"); 48f62ea8a1Sandi print 'Bad request'; 49f62ea8a1Sandi exit; 50f62ea8a1Sandi } 51f62ea8a1Sandi 52f62ea8a1Sandi //check permissions (namespace only) 53f62ea8a1Sandi if(auth_quickaclcheck(getNS($MEDIA).':X') < AUTH_READ){ 54f62ea8a1Sandi header("HTTP/1.0 401 Unauthorized"); 55f62ea8a1Sandi //fixme add some image for imagefiles 56f62ea8a1Sandi print 'Unauthorized'; 57f62ea8a1Sandi exit; 58f62ea8a1Sandi } 59f62ea8a1Sandi $FILE = mediaFN($MEDIA); 60f62ea8a1Sandi } 61f62ea8a1Sandi 62f62ea8a1Sandi //check file existance 63f62ea8a1Sandi if(!@file_exists($FILE)){ 64f62ea8a1Sandi header("HTTP/1.0 404 Not Found"); 65f62ea8a1Sandi //FIXME add some default broken image 66f62ea8a1Sandi print 'Not Found'; 67f62ea8a1Sandi exit; 68f62ea8a1Sandi } 69f62ea8a1Sandi 70b80bedd6SAndreas Gohr $ORIG = $FILE; 71b80bedd6SAndreas Gohr 7220bc86cfSAndreas Gohr //handle image resizing/cropping 73f62ea8a1Sandi if((substr($MIME,0,5) == 'image') && $WIDTH){ 74d52a56e1SAndreas Gohr if($HEIGHT){ 7513c08e2fSMichael Klier $FILE = media_crop_image($FILE,$EXT,$WIDTH,$HEIGHT); 7620bc86cfSAndreas Gohr }else{ 7713c08e2fSMichael Klier $FILE = media_resize_image($FILE,$EXT,$WIDTH,$HEIGHT); 78f62ea8a1Sandi } 7920bc86cfSAndreas Gohr } 80f62ea8a1Sandi 81e935fb4aSAndreas Gohr // finally send the file to the client 82b80bedd6SAndreas Gohr $data = array('file' => $FILE, 83b80bedd6SAndreas Gohr 'mime' => $MIME, 84*ecebf3a8SAndreas Gohr 'download' => $DL, 85b80bedd6SAndreas Gohr 'cache' => $CACHE, 86b80bedd6SAndreas Gohr 'orig' => $ORIG, 87b80bedd6SAndreas Gohr 'ext' => $EXT, 88b80bedd6SAndreas Gohr 'width' => $WIDTH, 89b80bedd6SAndreas Gohr 'height' => $HEIGHT); 90b80bedd6SAndreas Gohr 91b80bedd6SAndreas Gohr $evt = new Doku_Event('MEDIA_SENDFILE', $data); 92b80bedd6SAndreas Gohr if ($evt->advise_before()) { 93*ecebf3a8SAndreas Gohr sendFile($data['file'],$data['mime'],$data['download'],$data['cache']); 94b80bedd6SAndreas Gohr } 95f62ea8a1Sandi 96e935fb4aSAndreas Gohr/* ------------------------------------------------------------------------ */ 97f62ea8a1Sandi 98e935fb4aSAndreas Gohr/** 99e935fb4aSAndreas Gohr * Set headers and send the file to the client 100e935fb4aSAndreas Gohr * 101e935fb4aSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 10283730152SBen Coburn * @author Ben Coburn <btcoburn@silicodon.net> 103e935fb4aSAndreas Gohr */ 104*ecebf3a8SAndreas Gohrfunction sendFile($file,$mime,$dl,$cache){ 10583730152SBen Coburn global $conf; 10609a5b61fSgweissbach $fmtime = @filemtime($file); 107e935fb4aSAndreas Gohr // send headers 108e935fb4aSAndreas Gohr header("Content-Type: $mime"); 10983730152SBen Coburn // smart http caching headers 11083730152SBen Coburn if ($cache==-1) { 11183730152SBen Coburn // cache 11283730152SBen Coburn // cachetime or one hour 11383730152SBen Coburn header('Expires: '.gmdate("D, d M Y H:i:s", time()+max($conf['cachetime'], 3600)).' GMT'); 11483730152SBen Coburn header('Cache-Control: public, proxy-revalidate, no-transform, max-age='.max($conf['cachetime'], 3600)); 115e935fb4aSAndreas Gohr header('Pragma: public'); 11683730152SBen Coburn } else if ($cache>0) { 11783730152SBen Coburn // recache 11883730152SBen Coburn // remaining cachetime + 10 seconds so the newly recached media is used 11983730152SBen Coburn header('Expires: '.gmdate("D, d M Y H:i:s", $fmtime+$conf['cachetime']+10).' GMT'); 12083730152SBen Coburn header('Cache-Control: public, proxy-revalidate, no-transform, max-age='.max($fmtime-time()+$conf['cachetime']+10, 0)); 12183730152SBen Coburn header('Pragma: public'); 12283730152SBen Coburn } else if ($cache==0) { 12383730152SBen Coburn // nocache 12483730152SBen Coburn header('Cache-Control: must-revalidate, no-transform, post-check=0, pre-check=0'); 12583730152SBen Coburn header('Pragma: public'); 12683730152SBen Coburn } 127ff4f5ee7SBen Coburn //send important headers first, script stops here if '304 Not Modified' response 12883730152SBen Coburn http_conditionalRequest($fmtime); 1299a87c72aSAndreas Gohr 130f62ea8a1Sandi 131*ecebf3a8SAndreas Gohr //download or display? 132*ecebf3a8SAndreas Gohr if($dl){ 133e935fb4aSAndreas Gohr header('Content-Disposition: attachment; filename="'.basename($file).'";'); 134*ecebf3a8SAndreas Gohr }else{ 135*ecebf3a8SAndreas Gohr header('Content-Disposition: inline; filename="'.basename($file).'";'); 136f62ea8a1Sandi } 137f62ea8a1Sandi 1389a87c72aSAndreas Gohr //use x-sendfile header to pass the delivery to compatible webservers 1399a87c72aSAndreas Gohr if($conf['xsendfile'] == 1){ 1409a87c72aSAndreas Gohr header("X-LIGHTTPD-send-file: $file"); 1419a87c72aSAndreas Gohr exit; 1429a87c72aSAndreas Gohr }elseif($conf['xsendfile'] == 2){ 1439a87c72aSAndreas Gohr header("X-Sendfile: $file"); 1449a87c72aSAndreas Gohr exit; 145deec6eb9Spierre.pracht }elseif($conf['xsendfile'] == 3){ 146deec6eb9Spierre.pracht header("X-Accel-Redirect: $file"); 147deec6eb9Spierre.pracht exit; 1489a87c72aSAndreas Gohr } 1499a87c72aSAndreas Gohr 1509a87c72aSAndreas Gohr //support download continueing 1519a87c72aSAndreas Gohr header('Accept-Ranges: bytes'); 1529a87c72aSAndreas Gohr list($start,$len) = http_rangeRequest(filesize($file)); 1539a87c72aSAndreas Gohr 154e935fb4aSAndreas Gohr // send file contents 155e935fb4aSAndreas Gohr $fp = @fopen($file,"rb"); 156f62ea8a1Sandi if($fp){ 157e935fb4aSAndreas Gohr fseek($fp,$start); //seek to start of range 158e935fb4aSAndreas Gohr 159e935fb4aSAndreas Gohr $chunk = ($len > CHUNK_SIZE) ? CHUNK_SIZE : $len; 160e935fb4aSAndreas Gohr while (!feof($fp) && $chunk > 0) { 161d6751ba5SAndreas Gohr @set_time_limit(30); // large files can take a lot of time 162e935fb4aSAndreas Gohr print fread($fp, $chunk); 163615a21edSBrian Cowan flush(); 164e935fb4aSAndreas Gohr $len -= $chunk; 165e935fb4aSAndreas Gohr $chunk = ($len > CHUNK_SIZE) ? CHUNK_SIZE : $len; 166615a21edSBrian Cowan } 167615a21edSBrian Cowan fclose($fp); 168f62ea8a1Sandi }else{ 169f62ea8a1Sandi header("HTTP/1.0 500 Internal Server Error"); 170e935fb4aSAndreas Gohr print "Could not read $file - bad permissions?"; 171e935fb4aSAndreas Gohr } 172f62ea8a1Sandi} 173f62ea8a1Sandi 174e935fb4aSAndreas Gohr/** 175e935fb4aSAndreas Gohr * Checks and sets headers to handle range requets 176e935fb4aSAndreas Gohr * 177e935fb4aSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 178e935fb4aSAndreas Gohr * @returns array The start byte and the amount of bytes to send 179e935fb4aSAndreas Gohr */ 180e935fb4aSAndreas Gohrfunction http_rangeRequest($size){ 181e935fb4aSAndreas Gohr if(!isset($_SERVER['HTTP_RANGE'])){ 182e935fb4aSAndreas Gohr // no range requested - send the whole file 183e935fb4aSAndreas Gohr header("Content-Length: $size"); 184e935fb4aSAndreas Gohr return array(0,$size); 185e935fb4aSAndreas Gohr } 186e935fb4aSAndreas Gohr 187e935fb4aSAndreas Gohr $t = explode('=', $_SERVER['HTTP_RANGE']); 188e935fb4aSAndreas Gohr if (!$t[0]=='bytes') { 189e935fb4aSAndreas Gohr // we only understand byte ranges - send the whole file 190e935fb4aSAndreas Gohr header("Content-Length: $size"); 191e935fb4aSAndreas Gohr return array(0,$size); 192e935fb4aSAndreas Gohr } 193e935fb4aSAndreas Gohr 194e935fb4aSAndreas Gohr $r = explode('-', $t[1]); 195e935fb4aSAndreas Gohr $start = (int)$r[0]; 196e935fb4aSAndreas Gohr $end = (int)$r[1]; 197e935fb4aSAndreas Gohr if (!$end) $end = $size - 1; 198e935fb4aSAndreas Gohr if ($start > $end || $start > $size || $end > $size){ 199e935fb4aSAndreas Gohr header('HTTP/1.1 416 Requested Range Not Satisfiable'); 200e935fb4aSAndreas Gohr print 'Bad Range Request!'; 201e935fb4aSAndreas Gohr exit; 202e935fb4aSAndreas Gohr } 203e935fb4aSAndreas Gohr 204e935fb4aSAndreas Gohr $tot = $end - $start + 1; 205e935fb4aSAndreas Gohr header('HTTP/1.1 206 Partial Content'); 206e935fb4aSAndreas Gohr header("Content-Range: bytes {$start}-{$end}/{$size}"); 207e935fb4aSAndreas Gohr header("Content-Length: $tot"); 208e935fb4aSAndreas Gohr 209e935fb4aSAndreas Gohr return array($start,$tot); 210e935fb4aSAndreas Gohr} 211e935fb4aSAndreas Gohr 212e935fb4aSAndreas Gohr/** 213f62ea8a1Sandi * Returns the wanted cachetime in seconds 214f62ea8a1Sandi * 215f62ea8a1Sandi * Resolves named constants 216f62ea8a1Sandi * 217f62ea8a1Sandi * @author Andreas Gohr <andi@splitbrain.org> 218f62ea8a1Sandi */ 219f62ea8a1Sandifunction calc_cache($cache){ 220f62ea8a1Sandi global $conf; 221f62ea8a1Sandi 222f62ea8a1Sandi if(strtolower($cache) == 'nocache') return 0; //never cache 223f62ea8a1Sandi if(strtolower($cache) == 'recache') return $conf['cachetime']; //use standard cache 224f62ea8a1Sandi return -1; //cache endless 225f62ea8a1Sandi} 226f62ea8a1Sandi 227f62ea8a1Sandi//Setup VIM: ex: et ts=2 enc=utf-8 : 228f62ea8a1Sandi?> 229