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',realpath(dirname(__FILE__).'/../../').'/'); 10 require_once(DOKU_INC.'inc/init.php'); 11 require_once(DOKU_INC.'inc/common.php'); 12 require_once(DOKU_INC.'inc/pageutils.php'); 13 require_once(DOKU_INC.'inc/confutils.php'); 14 require_once(DOKU_INC.'inc/auth.php'); 15 //close sesseion 16 session_write_close(); 17 if(!defined('CHUNK_SIZE')) define('CHUNK_SIZE',16*1024); 18 19 $mimetypes = getMimeTypes(); 20 21 //get input 22 $MEDIA = getID('media',false); // no cleaning - maybe external 23 $CACHE = calc_cache($_REQUEST['cache']); 24 $WIDTH = $_REQUEST['w']; 25 $HEIGHT = $_REQUEST['h']; 26 list($EXT,$MIME) = mimetype($MEDIA); 27 if($EXT === false){ 28 $EXT = 'unknown'; 29 $MIME = 'application/octet-stream'; 30 } 31 32 //media to local file 33 if(preg_match('#^(https?|ftp)://#i',$MEDIA)){ 34 //handle external media 35 $FILE = get_from_URL($MEDIA,$EXT,$CACHE); 36 if(!$FILE){ 37 //download failed - redirect to original URL 38 header('Location: '.$MEDIA); 39 exit; 40 } 41 }else{ 42 $MEDIA = cleanID($MEDIA); 43 if(empty($MEDIA)){ 44 header("HTTP/1.0 400 Bad Request"); 45 print 'Bad request'; 46 exit; 47 } 48 49 //check permissions (namespace only) 50 if(auth_quickaclcheck(getNS($MEDIA).':X') < AUTH_READ){ 51 header("HTTP/1.0 401 Unauthorized"); 52 //fixme add some image for imagefiles 53 print 'Unauthorized'; 54 exit; 55 } 56 $FILE = mediaFN($MEDIA); 57 } 58 59 //check file existance 60 if(!@file_exists($FILE)){ 61 header("HTTP/1.0 404 Not Found"); 62 //FIXME add some default broken image 63 print 'Not Found'; 64 exit; 65 } 66 67 //handle image resizing 68 if((substr($MIME,0,5) == 'image') && $WIDTH){ 69 $FILE = get_resized($FILE,$EXT,$WIDTH,$HEIGHT); 70 } 71 72 // finally send the file to the client 73 sendFile($FILE,$MIME); 74 75/* ------------------------------------------------------------------------ */ 76 77/** 78 * Set headers and send the file to the client 79 * 80 * @author Andreas Gohr <andi@splitbrain.org> 81 */ 82function sendFile($file,$mime){ 83 // send headers 84 header("Content-Type: $mime"); 85 http_conditionalRequest(filemtime($file)); 86 list($start,$len) = http_rangeRequest(filesize($file)); 87 header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); 88 header('Pragma: public'); 89 header('Accept-Ranges: bytes'); 90 91 //application mime type is downloadable 92 if(substr($mime,0,11) == 'application'){ 93 header('Content-Disposition: attachment; filename="'.basename($file).'";'); 94 } 95 96 // send file contents 97 $fp = @fopen($file,"rb"); 98 if($fp){ 99 fseek($fp,$start); //seek to start of range 100 101 $chunk = ($len > CHUNK_SIZE) ? CHUNK_SIZE : $len; 102 while (!feof($fp) && $chunk > 0) { 103 @set_time_limit(); // large files can take a lot of time 104 print fread($fp, $chunk); 105 flush(); 106 $len -= $chunk; 107 $chunk = ($len > CHUNK_SIZE) ? CHUNK_SIZE : $len; 108 } 109 fclose($fp); 110 }else{ 111 header("HTTP/1.0 500 Internal Server Error"); 112 print "Could not read $file - bad permissions?"; 113 } 114} 115 116/** 117 * Checks and sets headers to handle range requets 118 * 119 * @author Andreas Gohr <andi@splitbrain.org> 120 * @returns array The start byte and the amount of bytes to send 121 */ 122function http_rangeRequest($size){ 123 if(!isset($_SERVER['HTTP_RANGE'])){ 124 // no range requested - send the whole file 125 header("Content-Length: $size"); 126 return array(0,$size); 127 } 128 129 $t = explode('=', $_SERVER['HTTP_RANGE']); 130 if (!$t[0]=='bytes') { 131 // we only understand byte ranges - send the whole file 132 header("Content-Length: $size"); 133 return array(0,$size); 134 } 135 136 $r = explode('-', $t[1]); 137 $start = (int)$r[0]; 138 $end = (int)$r[1]; 139 if (!$end) $end = $size - 1; 140 if ($start > $end || $start > $size || $end > $size){ 141 header('HTTP/1.1 416 Requested Range Not Satisfiable'); 142 print 'Bad Range Request!'; 143 exit; 144 } 145 146 $tot = $end - $start + 1; 147 header('HTTP/1.1 206 Partial Content'); 148 header("Content-Range: bytes {$start}-{$end}/{$size}"); 149 header("Content-Length: $tot"); 150 151 return array($start,$tot); 152} 153 154/** 155 * Checks and sets HTTP headers for conditional HTTP requests 156 * 157 * @author Simon Willison <swillison@gmail.com> 158 * @link http://simon.incutio.com/archive/2003/04/23/conditionalGet 159 */ 160function http_conditionalRequest($timestamp){ 161 // A PHP implementation of conditional get, see 162 // http://fishbowl.pastiche.org/archives/001132.html 163 $last_modified = substr(date('r', $timestamp), 0, -5).'GMT'; 164 $etag = '"'.md5($last_modified).'"'; 165 // Send the headers 166 header("Last-Modified: $last_modified"); 167 header("ETag: $etag"); 168 // See if the client has provided the required headers 169 $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? 170 stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : 171 false; 172 $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? 173 stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : 174 false; 175 if (!$if_modified_since && !$if_none_match) { 176 return; 177 } 178 // At least one of the headers is there - check them 179 if ($if_none_match && $if_none_match != $etag) { 180 return; // etag is there but doesn't match 181 } 182 if ($if_modified_since && $if_modified_since != $last_modified) { 183 return; // if-modified-since is there but doesn't match 184 } 185 // Nothing has changed since their last request - serve a 304 and exit 186 header('HTTP/1.0 304 Not Modified'); 187 exit; 188} 189 190/** 191 * Resizes the given image to the given size 192 * 193 * @author Andreas Gohr <andi@splitbrain.org> 194 */ 195function get_resized($file, $ext, $w, $h=0){ 196 global $conf; 197 198 $info = getimagesize($file); 199 if(!$h) $h = round(($w * $info[1]) / $info[0]); 200 201 202 //cache 203 $local = getCacheName($file,'.media.'.$w.'x'.$h.'.'.$ext); 204 $mtime = @filemtime($local); // 0 if not exists 205 206 if( $mtime > filemtime($file) || 207 resize_imageIM($ext,$file,$info[0],$info[1],$local,$w,$h) || 208 resize_imageGD($ext,$file,$info[0],$info[1],$local,$w,$h) ){ 209 return $local; 210 } 211 //still here? resizing failed 212 return $file; 213} 214 215/** 216 * Returns the wanted cachetime in seconds 217 * 218 * Resolves named constants 219 * 220 * @author Andreas Gohr <andi@splitbrain.org> 221 */ 222function calc_cache($cache){ 223 global $conf; 224 225 if(strtolower($cache) == 'nocache') return 0; //never cache 226 if(strtolower($cache) == 'recache') return $conf['cachetime']; //use standard cache 227 return -1; //cache endless 228} 229 230/** 231 * Download a remote file and return local filename 232 * 233 * returns false if download fails. Uses cached file if available and 234 * wanted 235 * 236 * @author Andreas Gohr <andi@splitbrain.org> 237 * @author Pavel Vitis <Pavel.Vitis@seznam.cz> 238 */ 239function get_from_URL($url,$ext,$cache){ 240 global $conf; 241 242 $local = getCacheName(strtolower($url),".media.$ext"); 243 $mtime = @filemtime($local); // 0 if not exists 244 245 //decide if download needed: 246 if( $cache == 0 || // never cache 247 ($mtime != 0 && $cache != -1) || // exists but no endless cache 248 ($mtime == 0) || // not exists 249 ($cache != -1 && $mtime < time()-$cache) // expired 250 ){ 251 if(io_download($url,$local)){ 252 return $local; 253 }else{ 254 return false; 255 } 256 } 257 258 //if cache exists use it else 259 if($mtime) return $local; 260 261 //else return false 262 return false; 263} 264 265/** 266 * resize images using external ImageMagick convert program 267 * 268 * @author Pavel Vitis <Pavel.Vitis@seznam.cz> 269 * @author Andreas Gohr <andi@splitbrain.org> 270 */ 271function resize_imageIM($ext,$from,$from_w,$from_h,$to,$to_w,$to_h){ 272 global $conf; 273 274 // check if convert is configured 275 if(!$conf['im_convert']) return false; 276 277 // prepare command 278 $cmd = $conf['im_convert']; 279 $cmd .= ' -resize '.$to_w.'x'.$to_h.'!'; 280 $cmd .= " $from $to"; 281 282 @exec($cmd,$out,$retval); 283 if ($retval == 0) return true; 284 285 return false; 286} 287 288/** 289 * resize images using PHP's libGD support 290 * 291 * @author Andreas Gohr <andi@splitbrain.org> 292 */ 293function resize_imageGD($ext,$from,$from_w,$from_h,$to,$to_w,$to_h){ 294 global $conf; 295 296 if($conf['gdlib'] < 1) return false; //no GDlib available or wanted 297 298 // check available memory 299 if(!is_mem_available(($from_w * $from_h * 4) + ($to_w * $to_h * 4))){ 300 return false; 301 } 302 303 // create an image of the given filetype 304 if ($ext == 'jpg' || $ext == 'jpeg'){ 305 if(!function_exists("imagecreatefromjpeg")) return false; 306 $image = @imagecreatefromjpeg($from); 307 }elseif($ext == 'png') { 308 if(!function_exists("imagecreatefrompng")) return false; 309 $image = @imagecreatefrompng($from); 310 311 }elseif($ext == 'gif') { 312 if(!function_exists("imagecreatefromgif")) return false; 313 $image = @imagecreatefromgif($from); 314 } 315 if(!$image) return false; 316 317 if(($conf['gdlib']>1) && function_exists("imagecreatetruecolor")){ 318 $newimg = @imagecreatetruecolor ($to_w, $to_h); 319 } 320 if(!$newimg) $newimg = @imagecreate($to_w, $to_h); 321 if(!$newimg){ 322 imagedestroy($image); 323 return false; 324 } 325 326 //keep png alpha channel if possible 327 if($ext == 'png' && $conf['gdlib']>1 && function_exists('imagesavealpha')){ 328 imagealphablending($newimg, false); 329 imagesavealpha($newimg,true); 330 } 331 332 //try resampling first 333 if(function_exists("imagecopyresampled")){ 334 if(!@imagecopyresampled($newimg, $image, 0, 0, 0, 0, $to_w, $to_h, $from_w, $from_h)) { 335 imagecopyresized($newimg, $image, 0, 0, 0, 0, $to_w, $to_h, $from_w, $from_h); 336 } 337 }else{ 338 imagecopyresized($newimg, $image, 0, 0, 0, 0, $to_w, $to_h, $from_w, $from_h); 339 } 340 341 $okay = false; 342 if ($ext == 'jpg' || $ext == 'jpeg'){ 343 if(!function_exists('imagejpeg')){ 344 $okay = false; 345 }else{ 346 $okay = imagejpeg($newimg, $to, 70); 347 } 348 }elseif($ext == 'png') { 349 if(!function_exists('imagepng')){ 350 $okay = false; 351 }else{ 352 $okay = imagepng($newimg, $to); 353 } 354 }elseif($ext == 'gif') { 355 if(!function_exists('imagegif')){ 356 $okay = false; 357 }else{ 358 $okay = imagegif($newimg, $to); 359 } 360 } 361 362 // destroy GD image ressources 363 if($image) imagedestroy($image); 364 if($newimg) imagedestroy($newimg); 365 366 return $okay; 367} 368 369/** 370 * Checks if the given amount of memory is available 371 * 372 * If the memory_get_usage() function is not available the 373 * function just assumes $used bytes of already allocated memory 374 * 375 * @param int $mem Size of memory you want to allocate in bytes 376 * @param int $used already allocated memory (see above) 377 * @author Filip Oscadal <webmaster@illusionsoftworks.cz> 378 * @author Andreas Gohr <andi@splitbrain.org> 379 */ 380function is_mem_available($mem,$bytes=1048576){ 381 $limit = trim(ini_get('memory_limit')); 382 if(empty($limit)) return true; // no limit set! 383 384 // parse limit to bytes 385 $unit = strtolower(substr($limit,-1)); 386 switch($unit){ 387 case 'g': 388 $limit = substr($limit,0,-1); 389 $limit *= 1024*1024*1024; 390 break; 391 case 'm': 392 $limit = substr($limit,0,-1); 393 $limit *= 1024*1024; 394 break; 395 case 'k': 396 $limit = substr($limit,0,-1); 397 $limit *= 1024; 398 break; 399 } 400 401 // get used memory if possible 402 if(function_exists('memory_get_usage')){ 403 $used = memory_get_usage(); 404 } 405 406 407 if($used+$mem > $limit){ 408 return false; 409 } 410 411 return true; 412} 413 414//Setup VIM: ex: et ts=2 enc=utf-8 : 415?> 416