/www/imgcache
/data/phancap.config.php
+/README.html
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
=========================
}
$timestamp = (int) $_GET['atimestamp'];
- if ($timestamp + $config->timestampLifetime < time()) {
+ if ($timestamp + $config->timestampMaxAge < time()) {
throw new \Exception('atimestamp too old');
}
/**
* 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.
*
* @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()
'default' => 'screen',
'type' => array('screen', 'page'),
),
+ 'smaxage' => array(
+ 'title' => 'Maximum age for a screenshot',
+ 'default' => null,
+ 'type' => 'age',
+ 'min' => null,
+ ),
/**
* Authentication
*/
public $values = array();
+ /**
+ * @var Config
+ */
+ protected $config;
+
/**
* Parses an array of options, validates them and writes them into
$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']
}
}
+ 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) {
);
}
$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)
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;
+ }
}
?>
$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;
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;
}
$options = new Options();
try {
+ $options->setConfig($config);
$options->parse($_GET);
} catch (\InvalidArgumentException $e) {
header('HTTP/1.0 400 Bad Request');