<?php
+/**
+ * Part of phancap
+ *
+ * PHP version 5
+ *
+ * @category Tools
+ * @package Options
+ * @author Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @link http://cweiske.de/phancap.htm
+ */
namespace phancap;
+/**
+ * Options a user can give to the API
+ *
+ * @category Tools
+ * @package Options
+ * @author Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @version Release: @package_version@
+ * @link http://cweiske.de/phancap.htm
+ */
class Options
{
- public static $options = array(
+ /**
+ * Available options and their configuration
+ *
+ * @var array
+ */
+ public $options = array(
/**
* Browser settings
*/
'default' => 'screen',
'type' => array('screen', 'page'),
),
+ 'smaxage' => array(
+ 'title' => 'Maximum age for a screenshot',
+ 'default' => null,
+ 'type' => 'age',
+ 'min' => null,
+ ),
+ /**
+ * Authentication
+ */
+ 'atimestamp' => array(
+ 'title' => 'Timestamp the request has been generated',
+ 'default' => null,
+ 'type' => 'skip',
+ ),
+ 'atoken' => array(
+ 'title' => 'Access token (user name)',
+ 'default' => null,
+ 'type' => 'skip',
+ ),
+ 'asignature' => array(
+ 'title' => 'Access signature',
+ 'default' => null,
+ 'type' => 'skip',
+ ),
);
+ /**
+ * Actual values we use after parsing the GET parameters
+ *
+ * @var array
+ */
public $values = array();
+ /**
+ * Configuration object
+ *
+ * @var Config
+ */
+ protected $config;
+
/**
* Parses an array of options, validates them and writes them into
* $this->values.
*
* @param array $arValues Array of options, e.g. $_GET
+ *
+ * @return void
+ * @throws \InvalidArgumentException When required parameters are missing
+ * or parameter values are invalid.
*/
public function parse($arValues)
{
- foreach (static::$options as $name => $arOption) {
+ foreach ($this->options as $name => $arOption) {
$this->values[$name] = $arOption['default'];
if (!isset($arValues[$name])) {
if (isset($arOption['required'])) {
continue;
}
- if ($arOption['type'] == 'url') {
+ if ($arValues[$name] === ''
+ && !isset($arOption['required'])
+ ) {
+ //allow empty value; default value will be used
+ } else if ($arOption['type'] == 'url') {
$this->values[$name] = $this->validateUrl($arValues[$name]);
} else if ($arOption['type'] == 'int') {
$this->values[$name] = $this->validateInt(
- $arValues[$name], $arOption['min'], $arOption['max']
+ $name, $arValues[$name],
+ $arOption['min'], $arOption['max']
);
} else if (gettype($arOption['type']) == 'array') {
$this->values[$name] = $this->validateArray(
- $arValues[$name], $arOption['type']
+ $name, $arValues[$name], $arOption['type']
);
- } else {
+ } 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']
);
$this->calcPageSize();
}
+ /**
+ * Calculate the browser size and screenshot size from the given options
+ *
+ * @return void
+ */
protected function calcPageSize()
{
if ($this->values['swidth'] === null) {
}
}
- protected function validateArray($value, $options)
+ /**
+ * Makes sure a value is between $min and $max (inclusive)
+ *
+ * @param integer $value Value to check
+ * @param integer $min Minimum allowed value
+ * @param integer $max Maximum allowed value
+ * @param boolean $silent When silent, invalid values are corrected.
+ * An exception is thrown otherwise.
+ *
+ * @return integer Corrected value
+ * @throws \InvalidArgumentException When not silent and value outside range
+ */
+ 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;
+ }
+
+ /**
+ * Check that a given value exists in an array
+ *
+ * @param string $name Variable name
+ * @param string $value Value to check
+ * @param array $options Array of allowed values
+ *
+ * @return string Value
+ * @throws \InvalidArgumentException If the value does not exist in $options
+ */
+ protected function validateArray($name, $value, $options)
{
if (array_search($value, $options) === false) {
throw new \InvalidArgumentException(
- 'Invalid value ' . $value . '.'
+ 'Invalid value ' . $value . ' for ' . $name . '.'
. ' Allowed: ' . implode(', ', $options)
);
}
return $value;
}
- protected function validateInt($value, $min, $max)
+ /**
+ * Validate that a value is numeric and between $min and $max (inclusive)
+ *
+ * @param string $name Variable name
+ * @param string $value Value to check
+ * @param integer $min Minimum allowed value
+ * @param integer $max Maximum allowed value
+ *
+ * @return integer Value as integer
+ * @throws \InvalidArgumentException When outside range or not numeric
+ */
+ protected function validateInt($name, $value, $min, $max)
{
if (!is_numeric($value)) {
throw new \InvalidArgumentException(
- 'Value must be a number'
+ $name . ' value must be a number'
);
}
$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);
}
+ /**
+ * Validate (and fix) an URL
+ *
+ * @param string $url URL
+ *
+ * @return string Fixed URL
+ * @throws \InvalidArgumentException
+ */
protected function validateUrl($url)
{
+ if ($url === '') {
+ throw new \InvalidArgumentException('URL is empty');
+ }
$parts = parse_url($url);
if ($parts === false) {
throw new \InvalidArgumentException('Invalid URL');
}
if (!isset($parts['scheme'])) {
- throw new \InvalidArgumentException('URL scheme missing');
+ $url = 'http://' . $url;
+ $parts = parse_url($url);
+ } else if ($parts['scheme'] != 'http' && $parts['scheme'] != 'https') {
+ throw new \InvalidArgumentException('Unsupported protocol');
}
if (!isset($parts['host'])) {
- throw new \InvalidArgumentException('URL host missing');
+ throw new \InvalidArgumentException('URL host missing');
}
+
+ $rebuild = false;
+ if (strlen(preg_replace('#[[:ascii:]]#', '', $parts['host']))) {
+ //non-ascii characters in the host name
+ $host = idn_to_ascii($parts['host']);
+ if ($host === false) {
+ //incoming URL was not UTF-8 but some ISO dialect
+ $host = idn_to_ascii(utf8_encode($parts['host']));
+ if ($host === false) {
+ throw new \InvalidArgumentException(
+ 'Strange characters in host name'
+ );
+ }
+ }
+ $parts['host'] = $host;
+ $rebuild = true;
+ }
+ if (strlen(preg_replace('#[[:ascii:]]#', '', $parts['path']))) {
+ //non-ascii characters in the path
+ $parts['path'] = str_replace('%2F', '/', urlencode($parts['path']));
+ $rebuild = true;
+ }
+
+ if ($rebuild) {
+ $url = static::http_build_url($parts);
+ }
+
return $url;
}
+
+ /**
+ * Set phancap configuration
+ *
+ * @param Config $config Phancap configuration
+ *
+ * @return void
+ */
+ public function setConfig(Config $config)
+ {
+ $this->config = $config;
+ $this->options['smaxage']['default'] = $this->config->screenshotMaxAge;
+ $this->options['smaxage']['min'] = $this->config->screenshotMinAge;
+ }
+
+ /**
+ * Re-build an URL parts array generated by parse_url()
+ *
+ * @param string $parts Array of URL parts
+ *
+ * @return string URL
+ */
+ protected static function http_build_url($parts)
+ {
+ $scheme = isset($parts['scheme'])
+ ? $parts['scheme'] . '://' : '';
+ $host = isset($parts['host'])
+ ? $parts['host'] : '';
+ $port = isset($parts['port'])
+ ? ':' . $parts['port'] : '';
+ $user = isset($parts['user'])
+ ? $parts['user'] : '';
+ $pass = isset($parts['pass'])
+
+ ? ':' . $parts['pass'] : '';
+ $pass = ($user || $pass)
+ ? "$pass@" : '';
+ $path = isset($parts['path'])
+ ? $parts['path'] : '';
+ $query = isset($parts['query'])
+ ? '?' . $parts['query'] : '';
+ $fragment = isset($parts['fragment'])
+ ? '#' . $parts['fragment'] : '';
+ return "$scheme$user$pass$host$port$path$query$fragment";
+ }
}
?>