From: Christian Weiske Date: Tue, 8 Apr 2014 05:49:02 +0000 (+0200) Subject: caching and cache time configuration X-Git-Tag: v0.1.0~29 X-Git-Url: https://git.cweiske.de/phancap.git/commitdiff_plain/d7074b4fa5043358df3aed0e3cc421aeb060517f?hp=5b85ef225aef0b9052c3797861fc79cdabd1c2f4 caching and cache time configuration --- diff --git a/.gitignore b/.gitignore index f699784..d2f14ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /www/imgcache /data/phancap.config.php +/README.html diff --git a/README.rst b/README.rst index 3b71795..78a67ec 100644 --- a/README.rst +++ b/README.rst @@ -31,6 +31,17 @@ Screenshot parameters Screenshot format (``png``, ``jpg``, ``pdf``, default: ``png``) ``smode`` Screenshot mode (``screen`` (4:3) or ``page`` (full website height)) +``smaxage`` + Maximum age of screenshot in seconds. + ISO 8601 duration specifications accepted: + + - ``P1Y`` - 1 year + - ``P2W`` - 2 weeks + - ``P1D`` - 1 day + - ``PT4H`` - 4 hours + + The configuration file defines a minimum age that the user cannot undercut + (``$screenshotMinAge``), as well as a default value (``$screenshotMaxAge``). Authentication parameters ========================= diff --git a/src/phancap/Authenticator.php b/src/phancap/Authenticator.php index e918e99..73d4ae6 100644 --- a/src/phancap/Authenticator.php +++ b/src/phancap/Authenticator.php @@ -29,7 +29,7 @@ class Authenticator } $timestamp = (int) $_GET['atimestamp']; - if ($timestamp + $config->timestampLifetime < time()) { + if ($timestamp + $config->timestampMaxAge < time()) { throw new \Exception('atimestamp too old'); } diff --git a/src/phancap/Config.php b/src/phancap/Config.php index 10aae91..4d7c7a0 100644 --- a/src/phancap/Config.php +++ b/src/phancap/Config.php @@ -17,10 +17,17 @@ class Config /** * Credentials for access - * username => secret key (used for signature) - * @var array + * + * Array of + * username => secret key + * entries (used for signature). + * + * Boolean true to allow access in every case, + * false to completely disable it. + * + * @var array|boolean */ - public $access = false; + public $access = true; /** * How long requests with an old timestamp may be used. @@ -28,13 +35,38 @@ class Config * * @var integer */ - public $timestampLifetime = 172800; + public $timestampMaxAge = 'P2D'; + + /** + * Cache time of downloaded screenshots. + * When the file is as older than this, it gets re-created. + * The user can override that using the "smaxage" parameter. + * + * Defaults to 1 week. + * + * @var integer Lifetime in seconds + */ + public $screenshotMaxAge = 'P1W'; + + /** + * Minimum age of a screeshot. + * A user cannot set the max age parameter below it. + * + * Defaults to 1 hour. + * + * @var integer Minimum lifetime in seconds + */ + public $screenshotMinAge = 'PT1H'; public function __construct() { $this->cacheDir = getcwd() . '/imgcache/'; $this->cacheDirUrl = $this->getCurrentUrlDir() . '/imgcache/'; + + $this->timestampMaxAge = Options::validateAge($this->timestampMaxAge); + $this->screenshotMaxAge = Options::validateAge($this->screenshotMaxAge); + $this->screenshotMinAge = Options::validateAge($this->screenshotMinAge); } public function load() diff --git a/src/phancap/Options.php b/src/phancap/Options.php index ddc9bdd..b51f2a7 100644 --- a/src/phancap/Options.php +++ b/src/phancap/Options.php @@ -54,6 +54,12 @@ class Options 'default' => 'screen', 'type' => array('screen', 'page'), ), + 'smaxage' => array( + 'title' => 'Maximum age for a screenshot', + 'default' => null, + 'type' => 'age', + 'min' => null, + ), /** * Authentication */ @@ -76,6 +82,11 @@ class Options public $values = array(); + /** + * @var Config + */ + protected $config; + /** * Parses an array of options, validates them and writes them into @@ -106,6 +117,12 @@ class Options $this->values[$name] = $this->validateArray( $arValues[$name], $arOption['type'] ); + } else if ($arOption['type'] == 'age') { + $this->values[$name] = $this->clamp( + static::validateAge($arValues[$name]), + $arOption['min'], null, + true + ); } else if ($arOption['type'] != 'skip') { throw new \InvalidArgumentException( 'Unsupported option type: ' . $arOption['type'] @@ -149,6 +166,63 @@ class Options } } + protected function clamp($value, $min, $max, $silent = false) + { + if ($min !== null && $value < $min) { + if ($silent) { + $value = $min; + } else { + throw new \InvalidArgumentException( + 'Value must be at least ' . $min + ); + } + } + if ($max !== null && $value > $max) { + if ($silent) { + $value = $max; + } else { + throw new \InvalidArgumentException( + 'Value may be up to ' . $min + ); + } + } + return $value; + } + + /** + * Validates an age is numeric. If it is not numeric, it's interpreted as + * a ISO 8601 duration specification. + * + * @param string $value Age in seconds + * + * @return integer Age in seconds + * + * @throws \InvalidArgumentException + * @link http://en.wikipedia.org/wiki/Iso8601#Durations + */ + public static function validateAge($value) + { + if (!is_numeric($value)) { + //convert short notation to seconds + $value = 'P' . ltrim(strtoupper($value), 'P'); + try { + $interval = new \DateInterval($value); + } catch (\Exception $e) { + throw new \InvalidArgumentException( + 'Invalid age: ' . $value + ); + } + $value = 86400 * ( + $interval->y * 365 + + $interval->m * 30 + + $interval->d + ) + $interval->h * 3600 + + $interval->m * 60 + + $interval->s; + } + return $value; + } + protected function validateArray($value, $options) { if (array_search($value, $options) === false) { @@ -168,17 +242,7 @@ class Options ); } $value = (int) $value; - if ($value < $min) { - throw new \InvalidArgumentException( - 'Value must be at least ' . $min - ); - } - if ($value > $max) { - throw new \InvalidArgumentException( - 'Value may be up to ' . $min - ); - } - return $value; + return $this->clamp($value, $min, $max); } protected function validateUrl($url) @@ -188,12 +252,19 @@ class Options throw new \InvalidArgumentException('Invalid URL'); } if (!isset($parts['scheme'])) { - throw new \InvalidArgumentException('URL scheme missing'); + throw new \InvalidArgumentException('URL scheme missing'); } if (!isset($parts['host'])) { - throw new \InvalidArgumentException('URL host missing'); + throw new \InvalidArgumentException('URL host missing'); } return $url; } + + public function setConfig(Config $config) + { + $this->config = $config; + static::$options['smaxage']['default'] = $this->config->screenshotMaxAge; + static::$options['smaxage']['min'] = $this->config->screenshotMinAge; + } } ?> diff --git a/src/phancap/Repository.php b/src/phancap/Repository.php index 2058bb6..157a062 100644 --- a/src/phancap/Repository.php +++ b/src/phancap/Repository.php @@ -13,7 +13,7 @@ class Repository $name = $this->getFilename($options); $img = new Image($name); $img->setConfig($this->config); - if (!$this->isAvailable($img)) { + if (!$this->isAvailable($img, $options)) { $this->render($img, $options); } return $img; @@ -21,18 +21,34 @@ class Repository public function getFilename(Options $options) { - return parse_url($options->values['url'], PHP_URL_HOST) - . '-' . md5(\serialize($options->values)) - . '.' . $options->values['sformat']; + $optValues = $options->values; + unset($optValues['smaxage']); + unset($optValues['atimestamp']); + unset($optValues['asignature']); + unset($optValues['atoken']); + + return parse_url($optValues['url'], PHP_URL_HOST) + . '-' . md5(\serialize($optValues)) + . '.' . $optValues['sformat']; } - public function isAvailable(Image $img) + /** + * Check if the image is available locally. + * + * @return boolean True if we have it and it's within the cache lifetime, + * false if the cache expired or the screenshot does not + * exist. + */ + public function isAvailable(Image $img, Options $options) { $path = $img->getPath(); if (!file_exists($path)) { return false; } - //FIXME: add cache lifetime check + + if (filemtime($path) < time() - $options->values['smaxage']) { + return false; + } return true; } diff --git a/www/get.php b/www/get.php index 3d7395f..1da3e8a 100644 --- a/www/get.php +++ b/www/get.php @@ -17,6 +17,7 @@ $config->load(); $options = new Options(); try { + $options->setConfig($config); $options->parse($_GET); } catch (\InvalidArgumentException $e) { header('HTTP/1.0 400 Bad Request');