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