From 0f4e009215bfa3136d334fa557335266637a7585 Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Wed, 20 Mar 2013 00:06:07 +0000 Subject: add a token to fetch urls requiring image resize/crop to prevent external DDOS via fetch --- inc/common.php | 4 ++++ inc/media.php | 24 ++++++++++++++++++++++++ lib/exe/fetch.php | 8 ++++++-- 3 files changed, 34 insertions(+), 2 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/media.php b/inc/media.php index 2268ad877..7f2fd2d1d 100644 --- a/inc/media.php +++ b/inc/media.php @@ -1864,6 +1864,30 @@ function media_crop_image($file, $ext, $w, $h=0){ return media_resize_image($file,$ext, $w, $h); } +/** + * 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 + */ +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 * diff --git a/lib/exe/fetch.php b/lib/exe/fetch.php index a558a3db8..48aa22fe2 100644 --- a/lib/exe/fetch.php +++ b/lib/exe/fetch.php @@ -32,7 +32,7 @@ if(!defined('SIMPLE_TEST')) { } // check for permissions, preconditions and cache external files - list($STATUS, $STATUSMESSAGE) = checkFileStatus($MEDIA, $FILE, $REV); + list($STATUS, $STATUSMESSAGE) = checkFileStatus($MEDIA, $FILE, $REV, $WIDTH, $HEIGHT); // prepare data for plugin events $data = array( @@ -180,7 +180,7 @@ function sendFile($file, $mime, $dl, $cache, $public = false) { * @param $file reference to the file variable * @returns array(STATUS, STATUSMESSAGE) */ -function checkFileStatus(&$media, &$file, $rev = '') { +function checkFileStatus(&$media, &$file, $rev = '', $width=0, $height=0) { global $MIME, $EXT, $CACHE, $INPUT; //media to local file @@ -200,6 +200,10 @@ function checkFileStatus(&$media, &$file, $rev = '') { 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) { -- cgit v1.2.3 From 7fb7960f92047a9bcadf9d497ae79615979e9a6d Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Fri, 22 Mar 2013 17:45:59 +0000 Subject: refactor fetch to support unittesting --- inc/fetch.functions.php | 149 ++++++++++++++++++++++++++++++++++++++++++++ lib/exe/fetch.php | 160 +++--------------------------------------------- 2 files changed, 159 insertions(+), 150 deletions(-) create mode 100644 inc/fetch.functions.php 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 @@ + + * @author Ben Coburn + * @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 + * @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 + */ +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/lib/exe/fetch.php b/lib/exe/fetch.php index 48aa22fe2..7a2250373 100644 --- a/lib/exe/fetch.php +++ b/lib/exe/fetch.php @@ -7,12 +7,17 @@ */ if(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__).'/../../'); -define('DOKU_DISABLE_GZIP_OUTPUT', 1); +if (!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT', 1); require_once(DOKU_INC.'inc/init.php'); session_write_close(); //close session -// BEGIN main (if not testing) -if(!defined('SIMPLE_TEST')) { +require_once(DOKU_INC.'inc/fetch.functions.php'); + +if (defined('SIMPLE_TEST')) { + $INPUT = new Input(); +} + +// BEGIN main $mimetypes = getMimeTypes(); //get input @@ -64,6 +69,7 @@ if(!defined('SIMPLE_TEST')) { // die on errors if($data['status'] > 203) { print $data['statusmessage']; + if (defined('SIMPLE_TEST')) return; exit; } } @@ -87,152 +93,6 @@ if(!defined('SIMPLE_TEST')) { // Do something after the download finished. $evt->advise_after(); // will not be emitted on 304 or x-sendfile -}// END DO main - -/* ------------------------------------------------------------------------ */ - -/** - * 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 - * @author Ben Coburn - * @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 - * @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 - */ -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 -} +// END DO main //Setup VIM: ex: et ts=2 : -- cgit v1.2.3 From b051e9742775994c33cb6570e27605bbe930efe4 Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Fri, 22 Mar 2013 17:55:19 +0000 Subject: code changes in the rest of the code base to support testing fetch --- inc/httputils.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 + * @author Chris Smith * @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); -- cgit v1.2.3 From 9894e7afaae16cc0699afbe839681e023afee65a Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Fri, 22 Mar 2013 17:59:23 +0000 Subject: extend TestRequest class to test fetch & detail; add a test to check it does --- _test/core/TestRequest.php | 19 ++++++++++++------- _test/tests/test/basic.test.php | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/_test/core/TestRequest.php b/_test/core/TestRequest.php index 172821576..0a54910ed 100644 --- a/_test/core/TestRequest.php +++ b/_test/core/TestRequest.php @@ -18,6 +18,9 @@ function ob_start_callback($buffer) { */ class TestRequest { + private $valid_scripts = array('/doku.php', '/lib/exe/fetch.php', '/lib/exe/detail.php'); + private $script; + private $server = array(); private $session = array(); private $get = array(); @@ -27,6 +30,7 @@ class TestRequest { public function getSession($key) { return $this->session[$key]; } public function getGet($key) { return $this->get[$key]; } public function getPost($key) { return $this->post[$key]; } + public function getScript() { return $this->script; } public function setServer($key, $value) { $this->server[$key] = $value; } public function setSession($key, $value) { $this->session[$key] = $value; } @@ -70,13 +74,13 @@ class TestRequest { // now execute dokuwiki and grep the output header_remove(); ob_start('ob_start_callback'); - include(DOKU_INC.'doku.php'); + include(DOKU_INC.$this->script); ob_end_flush(); // create the response object $response = new TestResponse( $output_buffer, - headers_list() + (function_exists('xdebug_get_headers') ? xdebug_get_headers() : headers_list()) // cli sapi doesn't do headers, prefer xdebug_get_headers() which works under cli ); // reset environment @@ -102,14 +106,15 @@ class TestRequest { * @todo make this work with other end points */ protected function setUri($uri){ - if(substr($uri,0,9) != '/doku.php'){ - throw new Exception("only '/doku.php' is supported currently"); + if(!preg_match('#^('.join('|',$this->valid_scripts).')#',$uri)){ + throw new Exception("$uri \n--- only ".join(', ',$this->valid_scripts)." are supported currently"); } $params = array(); list($uri, $query) = explode('?',$uri,2); if($query) parse_str($query, $params); + $this->script = substr($uri,1); $this->get = array_merge($params, $this->get); if(count($this->get)){ $query = '?'.http_build_query($this->get, '', '&'); @@ -129,7 +134,7 @@ class TestRequest { * Simulate a POST request with the given variables * * @param array $post all the POST parameters to use - * @param string $url end URL to simulate, needs to start with /doku.php currently + * @param string $url end URL to simulate, needs to start with /doku.php, /lib/exe/fetch.php or /lib/exe/detail.php currently * @param return TestResponse */ public function post($post=array(), $uri='/doku.php') { @@ -141,8 +146,8 @@ class TestRequest { /** * Simulate a GET request with the given variables * - * @param array $GET all the POST parameters to use - * @param string $url end URL to simulate, needs to start with /doku.php currently + * @param array $GET all the GET parameters to use + * @param string $url end URL to simulate, needs to start with /doku.php, /lib/exe/fetch.php or /lib/exe/detail.php currently * @param return TestResponse */ public function get($get=array(), $uri='/doku.php') { diff --git a/_test/tests/test/basic.test.php b/_test/tests/test/basic.test.php index a0ea48a3a..c40f2d574 100644 --- a/_test/tests/test/basic.test.php +++ b/_test/tests/test/basic.test.php @@ -101,5 +101,23 @@ class InttestsBasicTest extends DokuWikiTest { $this->assertTrue(strpos($response->getContent(), 'Andreas Gohr') >= 0); } + function testScripts() { + $request = new TestRequest(); + + // doku + $response = $request->get(); + $this->assertEquals('doku.php',$request->getScript()); + + $response = $request->get(array(),'/doku.php?id=wiki:dokuwiki&test=foo'); + $this->assertEquals('doku.php',$request->getScript()); + + // fetch + $response = $request->get(array(),'/lib/exe/fetch.php?media=wiki:dokuwiki-128.png'); + $this->assertEquals('lib/exe/fetch.php',$request->getScript()); + + // detail + $response = $request->get(array(),'/lib/exe/detail.php?id=start&media=wiki:dokuwiki-128.png'); + $this->assertEquals('lib/exe/detail.php',$request->getScript()); + } } -- cgit v1.2.3 From fb0b3fbf03223c8c304608cdb32ee5c9d755eca1 Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Fri, 22 Mar 2013 18:01:45 +0000 Subject: update TestResponse class to return specific headers & status codes (with tests) --- _test/core/TestResponse.php | 38 ++++++++++++++++++++++++++++++++++++++ _test/tests/test/basic.test.php | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/_test/core/TestResponse.php b/_test/core/TestResponse.php index 6d20afb28..7cc50ee4f 100644 --- a/_test/core/TestResponse.php +++ b/_test/core/TestResponse.php @@ -41,6 +41,44 @@ class TestResponse { return $this->headers; } + /** + * @param $name string, the name of the header without the ':', e.g. 'Content-Type', 'Pragma' + * @return mixed if exactly one header, the header (string); otherwise an array of headers, empty when no headers + */ + public function getHeader($name) { + $result = array(); + foreach ($this->headers as $header) { + if (substr($header,0,strlen($name)+1) == $name.':') { + $result[] = $header; + } + } + + return count($result) == 1 ? $result[0] : $result; + } + + /** + * @return int http status code + * + * in the test environment, only status codes explicitly set by dokuwiki are likely to be returned + * this means succcessful status codes (e.g. 200 OK) will not be present, but error codes will be + */ + public function getStatusCode() { + $headers = $this->getHeader('Status'); + $code = null; + + if ($headers) { + // if there is more than one status header, use the last one + $status = is_array($headers) ? array_pop($headers) : $headers; + $matches = array(); + preg_match('/^Status: ?(\d+)/',$status,$matches); + if ($matches){ + $code = $matches[1]; + } + } + + return $code; + } + /** * Query the response for a JQuery compatible CSS selector * diff --git a/_test/tests/test/basic.test.php b/_test/tests/test/basic.test.php index c40f2d574..1c9d6d516 100644 --- a/_test/tests/test/basic.test.php +++ b/_test/tests/test/basic.test.php @@ -4,6 +4,24 @@ * @group integration */ class InttestsBasicTest extends DokuWikiTest { + + private $some_headers = array( + 'Content-Type: image/png', + 'Date: Fri, 22 Mar 2013 16:10:01 GMT', + 'X-Powered-By: PHP/5.3.15', + 'Expires: Sat, 23 Mar 2013 17:03:46 GMT', + 'Cache-Control: public, proxy-revalidate, no-transform, max-age=86400', + 'Pragma: public', + 'Last-Modified: Fri, 22 Mar 2013 01:48:28 GMT', + 'ETag: "63daab733b38c30c337229b2e587f8fb"', + 'Content-Disposition: inline; filename="fe389b0db8c1088c336abb502d2f9ae7.media.200x200.png', + 'Accept-Ranges: bytes', + 'Content-Type: image/png', + 'Content-Length: 62315', + 'Status: 200 OK', + 'Status: 404 Not Found', + ); + /** * Execute the simplest possible request and expect * a dokuwiki page which obviously has the word "DokuWiki" @@ -120,4 +138,27 @@ class InttestsBasicTest extends DokuWikiTest { $this->assertEquals('lib/exe/detail.php',$request->getScript()); } + function testHeaders(){ + $request = new TestRequest(); + $response = $request->get(array(),'/lib/exe/fetch.php?media=wiki:dokuwiki-128.png'); + $headers = $response->getHeaders(); + $this->assertTrue(!empty($headers)); + } + + function testGetHeader(){ + $response = new TestResponse('',$this->some_headers); + + $this->assertEquals('Pragma: public', $response->getHeader('Pragma')); + $this->assertEmpty($response->getHeader('Junk')); + $this->assertEquals(array('Content-Type: image/png','Content-Type: image/png'), $response->getHeader('Content-Type')); + } + + function testGetStatus(){ + $response = new TestResponse('',$this->some_headers); + $this->assertEquals(404, $response->getStatusCode()); + + $response = new TestResponse('',array_slice($this->some_headers,0,-2)); // slide off the last two headers to leave no status header + $this->assertNull($response->getStatusCode()); + } + } -- cgit v1.2.3 From f56bb251b5bdb3bb4f2793e921cf479d2402a47c Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Fri, 22 Mar 2013 18:02:15 +0000 Subject: add test for fetch using image tokens --- _test/tests/lib/exe/fetch_imagetoken.test.php | 83 +++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 _test/tests/lib/exe/fetch_imagetoken.test.php diff --git a/_test/tests/lib/exe/fetch_imagetoken.test.php b/_test/tests/lib/exe/fetch_imagetoken.test.php new file mode 100644 index 000000000..f94e0479b --- /dev/null +++ b/_test/tests/lib/exe/fetch_imagetoken.test.php @@ -0,0 +1,83 @@ +width ? 'w='.$this->width.'&' : ''; + $h = $this->height ? 'h='.$this->height.'&' : ''; + + return '/lib/exe/fetch.php?'.$w.$h.'{%token%}media='.$this->media; + } + + function fetchResponse($token){ + $request = new TestRequest(); + return $request->get(array(),str_replace('{%token%}',$token,$this->getUri())); + } + + /** + * modified image request with valid token + * expect: header with mime-type + * expect: content + * expect: no error response + */ + function test_valid_token(){ + $valid_token = 'tok='.media_get_token($this->media, $this->width, $this->height).'&'; + $response = $this->fetchResponse($valid_token); + $this->assertTrue((bool)$response->getHeader('Content-Type')); + $this->assertTrue((bool)($response->getContent())); + + $status_code = $response->getStatusCode(); + $this->assertTrue(is_null($status_code) || (200 == $status_code)); + } + + /** + * modified image request with invalid token + * expect: 412 status code + */ + function test_invalid_token(){ + $invalid_token = 'tok='.media_get_token('junk',200,100).'&'; + $this->assertEquals(412,$this->fetchResponse($invalid_token)->getStatusCode()); + } + + /** + * modified image request with no token + * expect: 412 status code + */ + function test_missing_token(){ + $no_token = ''; + $this->assertEquals(412,$this->fetchResponse($notoken)->getStatusCode()); + } + + /** + * native image request which doesn't require a token + */ + function test_no_token_required(){ + $this->width = $this->height = 0; // no width & height, means image request at native dimensions + $any_token = 'tok='.media_get_token('junk',200,100).'&'; + $no_token = ''; + + foreach(array($any_token, $no_token) as $token) { + $response = $this->fetchResponse($token); + $this->assertTrue((bool)$response->getHeader('Content-Type')); + $this->assertTrue((bool)($response->getContent())); + + $status_code = $response->getStatusCode(); + $this->assertTrue(is_null($status_code) || (200 == $status_code)); + } + } + +} +//Setup VIM: ex: et ts=4 : -- cgit v1.2.3 From 3e8bad3a1f8b5d065e170bb12dd224bf18de6897 Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Fri, 22 Mar 2013 18:49:11 +0000 Subject: add some dependency checks with test skips to fetch tests and tests which use TestResponse::headers --- _test/tests/lib/exe/fetch_imagetoken.test.php | 20 ++++++++++++++++++-- _test/tests/test/basic.test.php | 10 +++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/_test/tests/lib/exe/fetch_imagetoken.test.php b/_test/tests/lib/exe/fetch_imagetoken.test.php index f94e0479b..9e5b6e4a2 100644 --- a/_test/tests/lib/exe/fetch_imagetoken.test.php +++ b/_test/tests/lib/exe/fetch_imagetoken.test.php @@ -7,6 +7,19 @@ class fetch_imagetoken_test extends DokuWikiTest { private $height = 0; function setUp() { + // check we can carry out these tests + if (!file_exists(mediaFN($this->media))) { + $this->markTestSkipped('Source image required for test'); + } + + header('X-Test: check headers working'); + $header_check = function_exists('xdebug_get_headers') ? xdebug_get_headers() : headers_list(); + if (empty($header_check)) { + $this->markTestSkipped('headers not returned, perhaps your sapi does not return headers, try xdebug'); + } else { + header_remove('X-Test'); + } + parent::setUp(); global $conf; @@ -35,7 +48,7 @@ class fetch_imagetoken_test extends DokuWikiTest { */ function test_valid_token(){ $valid_token = 'tok='.media_get_token($this->media, $this->width, $this->height).'&'; - $response = $this->fetchResponse($valid_token); + $response = $this->fetchResponse($valid_token); $this->assertTrue((bool)$response->getHeader('Content-Type')); $this->assertTrue((bool)($response->getContent())); @@ -63,16 +76,19 @@ class fetch_imagetoken_test extends DokuWikiTest { /** * native image request which doesn't require a token + * try: with a token & without a token + * expect: (for both) header with mime-type, content matching source image filesize & no error response */ function test_no_token_required(){ $this->width = $this->height = 0; // no width & height, means image request at native dimensions $any_token = 'tok='.media_get_token('junk',200,100).'&'; $no_token = ''; + $bytes = filesize(mediaFN($this->media)); foreach(array($any_token, $no_token) as $token) { $response = $this->fetchResponse($token); $this->assertTrue((bool)$response->getHeader('Content-Type')); - $this->assertTrue((bool)($response->getContent())); + $this->assertEquals(strlen($response->getContent()), $bytes); $status_code = $response->getStatusCode(); $this->assertTrue(is_null($status_code) || (200 == $status_code)); diff --git a/_test/tests/test/basic.test.php b/_test/tests/test/basic.test.php index 1c9d6d516..05778ccf9 100644 --- a/_test/tests/test/basic.test.php +++ b/_test/tests/test/basic.test.php @@ -139,6 +139,14 @@ class InttestsBasicTest extends DokuWikiTest { } function testHeaders(){ + header('X-Test: check headers working'); + $header_check = function_exists('xdebug_get_headers') ? xdebug_get_headers() : headers_list(); + if (empty($header_check)) { + $this->markTestSkipped('headers not returned, perhaps your sapi does not return headers, try xdebug'); + } else { + header_remove('X-Test'); + } + $request = new TestRequest(); $response = $request->get(array(),'/lib/exe/fetch.php?media=wiki:dokuwiki-128.png'); $headers = $response->getHeaders(); @@ -157,7 +165,7 @@ class InttestsBasicTest extends DokuWikiTest { $response = new TestResponse('',$this->some_headers); $this->assertEquals(404, $response->getStatusCode()); - $response = new TestResponse('',array_slice($this->some_headers,0,-2)); // slide off the last two headers to leave no status header + $response = new TestResponse('',array_slice($this->some_headers,0,-2)); // slice off the last two headers to leave no status header $this->assertNull($response->getStatusCode()); } -- cgit v1.2.3