diff options
Diffstat (limited to 'inc')
-rw-r--r-- | inc/common.php | 4 | ||||
-rw-r--r-- | inc/fetch.functions.php | 149 | ||||
-rw-r--r-- | inc/httputils.php | 9 | ||||
-rw-r--r-- | inc/media.php | 24 |
4 files changed, 182 insertions, 4 deletions
diff --git a/inc/common.php b/inc/common.php index 471eb91b5..27f90b53b 100644 --- a/inc/common.php +++ b/inc/common.php @@ -436,6 +436,10 @@ function exportlink($id = '', $format = 'raw', $more = '', $abs = false, $sep = function ml($id = '', $more = '', $direct = true, $sep = '&', $abs = false) { global $conf; if(is_array($more)) { + // add token for resized images + if($more['w'] || $more['h']){ + $more['tok'] = media_get_token($id,$more['w'],$more['h']); + } // strip defaults for shorter URLs if(isset($more['cache']) && $more['cache'] == 'cache') unset($more['cache']); if(!$more['w']) unset($more['w']); diff --git a/inc/fetch.functions.php b/inc/fetch.functions.php new file mode 100644 index 000000000..5801e96fa --- /dev/null +++ b/inc/fetch.functions.php @@ -0,0 +1,149 @@ +<?php +/** + * Functions used by lib/exe/fetch.php + * (not included by other parts of dokuwiki) + */ + +/** + * Set headers and send the file to the client + * + * The $cache parameter influences how long files may be kept in caches, the $public parameter + * influences if this caching may happen in public proxis or in the browser cache only FS#2734 + * + * This function will abort the current script when a 304 is sent or file sending is handled + * through x-sendfile + * + * @author Andreas Gohr <andi@splitbrain.org> + * @author Ben Coburn <btcoburn@silicodon.net> + * @param string $file local file to send + * @param string $mime mime type of the file + * @param bool $dl set to true to force a browser download + * @param int $cache remaining cache time in seconds (-1 for $conf['cache'], 0 for no-cache) + * @param bool $public is this a public ressource or a private one? + */ +function sendFile($file, $mime, $dl, $cache, $public = false) { + global $conf; + // send mime headers + header("Content-Type: $mime"); + + // calculate cache times + if($cache == -1) { + $maxage = max($conf['cachetime'], 3600); // cachetime or one hour + $expires = time() + $maxage; + } else if($cache > 0) { + $maxage = $cache; // given time + $expires = time() + $maxage; + } else { // $cache == 0 + $maxage = 0; + $expires = 0; // 1970-01-01 + } + + // smart http caching headers + if($maxage) { + if($public) { + // cache publically + header('Expires: '.gmdate("D, d M Y H:i:s", $expires).' GMT'); + header('Cache-Control: public, proxy-revalidate, no-transform, max-age='.$maxage); + header('Pragma: public'); + } else { + // cache in browser + header('Expires: '.gmdate("D, d M Y H:i:s", $expires).' GMT'); + header('Cache-Control: private, no-transform, max-age='.$maxage); + header('Pragma: no-cache'); + } + } else { + // no cache at all + header('Expires: Thu, 01 Jan 1970 00:00:00 GMT'); + header('Cache-Control: no-cache, no-transform'); + header('Pragma: no-cache'); + } + + //send important headers first, script stops here if '304 Not Modified' response + $fmtime = @filemtime($file); + http_conditionalRequest($fmtime); + + //download or display? + if($dl) { + header('Content-Disposition: attachment; filename="'.utf8_basename($file).'";'); + } else { + header('Content-Disposition: inline; filename="'.utf8_basename($file).'";'); + } + + //use x-sendfile header to pass the delivery to compatible webservers + if(http_sendfile($file)) exit; + + // send file contents + $fp = @fopen($file, "rb"); + if($fp) { + http_rangeRequest($fp, filesize($file), $mime); + } else { + http_status(500); + print "Could not read $file - bad permissions?"; + } +} + +/** + * Check for media for preconditions and return correct status code + * + * READ: MEDIA, MIME, EXT, CACHE + * WRITE: MEDIA, FILE, array( STATUS, STATUSMESSAGE ) + * + * @author Gerry Weissbach <gerry.w@gammaproduction.de> + * @param $media reference to the media id + * @param $file reference to the file variable + * @returns array(STATUS, STATUSMESSAGE) + */ +function checkFileStatus(&$media, &$file, $rev = '', $width=0, $height=0) { + global $MIME, $EXT, $CACHE, $INPUT; + + //media to local file + if(preg_match('#^(https?)://#i', $media)) { + //check hash + if(substr(md5(auth_cookiesalt().$media), 0, 6) !== $INPUT->str('hash')) { + return array(412, 'Precondition Failed'); + } + //handle external images + if(strncmp($MIME, 'image/', 6) == 0) $file = media_get_from_URL($media, $EXT, $CACHE); + if(!$file) { + //download failed - redirect to original URL + return array(302, $media); + } + } else { + $media = cleanID($media); + if(empty($media)) { + return array(400, 'Bad request'); + } + // check token for resized images + if (($width || $height) && media_get_token($media, $width, $height) !== $INPUT->str('tok')) { + return array(412, 'Precondition Failed'); + } + + //check permissions (namespace only) + if(auth_quickaclcheck(getNS($media).':X') < AUTH_READ) { + return array(403, 'Forbidden'); + } + $file = mediaFN($media, $rev); + } + + //check file existance + if(!@file_exists($file)) { + return array(404, 'Not Found'); + } + + return array(200, null); +} + +/** + * Returns the wanted cachetime in seconds + * + * Resolves named constants + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +function calc_cache($cache) { + global $conf; + + if(strtolower($cache) == 'nocache') return 0; //never cache + if(strtolower($cache) == 'recache') return $conf['cachetime']; //use standard cache + return -1; //cache endless +} diff --git a/inc/httputils.php b/inc/httputils.php index d3f3cdde2..ca60ed509 100644 --- a/inc/httputils.php +++ b/inc/httputils.php @@ -61,9 +61,9 @@ function http_conditionalRequest($timestamp){ } /** - * Let the webserver send the given file vi x-sendfile method + * Let the webserver send the given file via x-sendfile method * - * @author Chris Smith <chris.eureka@jalakai.co.uk> + * @author Chris Smith <chris@jalakai.co.uk> * @returns void or exits with previously header() commands executed */ function http_sendfile($file) { @@ -177,7 +177,8 @@ function http_rangeRequest($fh,$size,$mime){ echo HTTP_HEADER_LF.'--'.HTTP_MULTIPART_BOUNDARY.'--'.HTTP_HEADER_LF; } - // everything should be done here, exit + // everything should be done here, exit (or return if testing) + if (defined('SIMPLE_TEST')) return; exit; } @@ -320,7 +321,7 @@ function http_status($code = 200, $text = '') { $server_protocol = (isset($_SERVER['SERVER_PROTOCOL'])) ? $_SERVER['SERVER_PROTOCOL'] : false; - if(substr(php_sapi_name(), 0, 3) == 'cgi') { + if(substr(php_sapi_name(), 0, 3) == 'cgi' || defined('SIMPLE_TEST')) { header("Status: {$code} {$text}", true); } elseif($server_protocol == 'HTTP/1.1' OR $server_protocol == 'HTTP/1.0') { header($server_protocol." {$code} {$text}", true, $code); diff --git a/inc/media.php b/inc/media.php index fc4ad1d67..e29a47631 100644 --- a/inc/media.php +++ b/inc/media.php @@ -1875,6 +1875,30 @@ function media_crop_image($file, $ext, $w, $h=0){ } /** + * Calculate a token to be used to verify fetch requests for resized or + * cropped images have been internally generated - and prevent external + * DDOS attacks via fetch + * + * @param string $id id of the image + * @param int $w resize/crop width + * @param int $h resize/crop height + * + * @author Christopher Smith <chris@jalakai.co.uk> + */ +function media_get_token($id,$w,$h){ + // token is only required for modified images + if ($w || $h) { + $token = auth_cookiesalt().$id; + if ($w) $token .= '.'.$w; + if ($h) $token .= '.'.$h; + + return substr(md5($token),0,6); + } + + return ''; +} + +/** * Download a remote file and return local filename * * returns false if download fails. Uses cached file if available and |