diff --git a/CImage.php b/CImage.php index 40a5f0b..d01bc4e 100644 --- a/CImage.php +++ b/CImage.php @@ -416,7 +416,7 @@ class CImage public function setRemoteDownload($allow, $pattern = null) { $this->allowRemote = $allow; - $this->remotePattern = $pattern ? $pattern : $this->remotePattern; + $this->remotePattern = is_null($pattern) ? $this->remotePattern : $pattern; $this->log("Set remote download to: " . ($this->allowRemote ? "true" : "false") @@ -439,7 +439,48 @@ class CImage { $remote = preg_match($this->remotePattern, $src); $this->log("Detected remote image: " . ($remote ? "true" : "false")); - return $remote; + return !!$remote; + } + + + + /** + * Set whitelist for valid hostnames from where remote source can be + * downloaded. + * + * @param array $whitelist with regexp hostnames to allow download from. + * + * @return $this + */ + public function setRemoteHostWhitelist($whitelist = null) + { + $this->remoteHostWhitelist = $whitelist; + $this->log("Setting remote host whitelist to: " . print_r($this->remoteHostWhitelist, 1)); + return $this; + } + + + + /** + * Check if the hostname for the remote image, is on a whitelist, + * if the whitelist is defined. + * + * @param string $src the remote source. + * + * @return boolean true if hostname on $src is in the whitelist, else false. + */ + public function isRemoteSourceOnWhitelist($src) + { + if (is_null($this->remoteHostWhitelist)) { + $allow = true; + } else { + $whitelist = new CWhitelist(); + $hostname = parse_url($src, PHP_URL_HOST); + $allow = $whitelist->check($hostname, $this->remoteHostWhitelist); + } + + $this->log("Remote host is on whitelist: " . ($allow ? "true" : "false")); + return $allow; } @@ -472,6 +513,10 @@ class CImage */ public function downloadRemoteSource($src) { + if (!$this->isRemoteSourceOnWhitelist($src)) { + throw new Exception("Hostname is not on whitelist for remote sources."); + } + $remote = new CRemoteImage(); $cache = $this->saveFolder . "/remote/"; diff --git a/CWhitelist.php b/CWhitelist.php new file mode 100644 index 0000000..d0db039 --- /dev/null +++ b/CWhitelist.php @@ -0,0 +1,62 @@ +whitelist = $whitelist; + } else { + throw new Exception("Whitelist is not of a supported format."); + } + return $this; + } + + + + /** + * Check if item exists in the whitelist. + * + * @param string $item string to check. + * @param array $whitelist optional with all valid options, default is null. + * + * @return boolean true if item is in whitelist, else false. + */ + public function check($item, $whitelist = null) + { + if ($whitelist !== null) { + $this->set($whitelist); + } + + if (empty($item) or empty($this->whitelist)) { + return false; + } + + foreach ($this->whitelist as $regexp) { + if (preg_match("#$regexp#", $item)) { + return true; + } + } + + return false; + } +} diff --git a/bin/create-img-single.bash b/bin/create-img-single.bash new file mode 100755 index 0000000..5281399 --- /dev/null +++ b/bin/create-img-single.bash @@ -0,0 +1,78 @@ +#!/bin/bash + +# +# Paths and settings +# +TARGET_D="webroot/imgd.php" +TARGET_P="webroot/imgp.php" +TARGET_S="webroot/imgs.php" +NEWLINES="\n\n\n" + + + +# +# Specify the utilities used +# +ECHO="printf" + + + +# +# Main, start by checking basic usage +# +if [ $# -gt 0 ] +then + $ECHO "Usage: $0\n" + exit 1 +fi + + + +# +# Print out details on cache-directory +# +$ECHO "Creating '$TARGET_D', '$TARGET_P' and '$TARGET_S' by combining the following files:" +$ECHO "\n" +$ECHO "\n webroot/img_header.php" +$ECHO "\n CHttpGet.php" +$ECHO "\n CRemoteImage.php" +$ECHO "\n CWhitelist.php" +$ECHO "\n CImage.php" +$ECHO "\n webroot/img.php" +$ECHO "\n" +$ECHO "\n'$TARGET_D' is for development mode." +$ECHO "\n'$TARGET_P' is for production mode (default mode)." +$ECHO "\n'$TARGET_S' is for strict mode." +$ECHO "\n" + +$ECHO "\nPress enter to continue. " +read answer + + +# +# Create the $TARGET_? files +# +cat webroot/img_header.php > $TARGET_P +cat webroot/img_header.php | sed "s|//'mode' => 'production',|'mode' => 'development',|" > $TARGET_D +cat webroot/img_header.php | sed "s|//'mode' => 'production',|'mode' => 'development',|" > $TARGET_S + +$ECHO "$NEWLINES" | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null + +tail -n +2 CHttpGet.php | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null +$ECHO "$NEWLINES" | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null + +tail -n +2 CRemoteImage.php | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null +$ECHO "$NEWLINES" | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null + +tail -n +2 CImage.php | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null +$ECHO "$NEWLINES" | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null + +tail -n +2 CWhitelist.php | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null +$ECHO "$NEWLINES" | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null + +tail -n +2 webroot/img.php | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null +$ECHO "$NEWLINES" | tee -a $TARGET_D $TARGET_P $TARGET_S > /dev/null + +$ECHO "\nDone." +$ECHO "\n" +$ECHO "\n" diff --git a/webroot/img.php b/webroot/img.php index 88846b3..44bf284 100644 --- a/webroot/img.php +++ b/webroot/img.php @@ -8,7 +8,7 @@ * */ -$version = "v0.7.0 (2015-02-10)"; +$version = "v0.7.0.x (latest)"; @@ -309,6 +309,9 @@ $allowRemote = getConfig('remote_allow', false); if ($allowRemote && $passwordMatch !== false) { $pattern = getConfig('remote_pattern', null); $img->setRemoteDownload($allowRemote, $pattern); + + $whitelist = getConfig('remote_whitelist', null); + $img->setRemoteHostWhitelist($whitelist); } diff --git a/webroot/img_config.php b/webroot/img_config.php index 9d08438..754bc67 100644 --- a/webroot/img_config.php +++ b/webroot/img_config.php @@ -61,15 +61,32 @@ return array( /** - * Allow or disallow downloading of remote files, images available on - * some remote server. Default is to disallow. + * Allow or disallow downloading of remote images available on + * remote servers. Default is to disallow remote download. + * + * When enabling remote download, the default is to allow download any + * link starting with http or https. This can be changed using + * remote_pattern. + * + * When enabling remote_whitelist a check is made that the hostname of the + * source to download matches the whitelist. By default the check is + * disabled and thereby allowing download from any hosts. * * Default values. - * remote_allow: false - * remote_pattern: null // use default values from CImage + * remote_allow: false + * remote_pattern: null // use default values from CImage which is to + * // allow download from any http- and + * // https-source. + * remote_whitelist: null // use default values from CImage which is to + * // allow download from any hosts. */ - //'remote_allow' => true, - //'remote_pattern' => '#^https?://#', + //'remote_allow' => true, + //'remote_pattern' => '#^https?://#', + //'remote_whitelist' => array( + // '\.facebook\.com$', + // '^(?:images|photos-[a-z])\.ak\.instagram\.com$', + // '\.google\.com$' + //), diff --git a/webroot/imgd.php b/webroot/imgd.php index 5c787cb..80aa0d0 100644 --- a/webroot/imgd.php +++ b/webroot/imgd.php @@ -1048,7 +1048,7 @@ class CImage public function setRemoteDownload($allow, $pattern = null) { $this->allowRemote = $allow; - $this->remotePattern = $pattern ? $pattern : $this->remotePattern; + $this->remotePattern = is_null($pattern) ? $this->remotePattern : $pattern; $this->log("Set remote download to: " . ($this->allowRemote ? "true" : "false") @@ -1071,7 +1071,48 @@ class CImage { $remote = preg_match($this->remotePattern, $src); $this->log("Detected remote image: " . ($remote ? "true" : "false")); - return $remote; + return !!$remote; + } + + + + /** + * Set whitelist for valid hostnames from where remote source can be + * downloaded. + * + * @param array $whitelist with regexp hostnames to allow download from. + * + * @return $this + */ + public function setRemoteHostWhitelist($whitelist = null) + { + $this->remoteHostWhitelist = $whitelist; + $this->log("Setting remote host whitelist to: " . print_r($this->remoteHostWhitelist, 1)); + return $this; + } + + + + /** + * Check if the hostname for the remote image, is on a whitelist, + * if the whitelist is defined. + * + * @param string $src the remote source. + * + * @return boolean true if hostname on $src is in the whitelist, else false. + */ + public function isRemoteSourceOnWhitelist($src) + { + if (is_null($this->remoteHostWhitelist)) { + $allow = true; + } else { + $whitelist = new CWhitelist(); + $hostname = parse_url($src, PHP_URL_HOST); + $allow = $whitelist->check($hostname, $this->remoteHostWhitelist); + } + + $this->log("Remote host is on whitelist: " . ($allow ? "true" : "false")); + return $allow; } @@ -1104,6 +1145,10 @@ class CImage */ public function downloadRemoteSource($src) { + if (!$this->isRemoteSourceOnWhitelist($src)) { + throw new Exception("Hostname is not on whitelist for remote sources."); + } + $remote = new CRemoteImage(); $cache = $this->saveFolder . "/remote/"; @@ -3002,6 +3047,70 @@ EOD; +/** + * Act as whitelist (or blacklist). + * + */ +class CWhitelist +{ + /** + * Array to contain the whitelist options. + */ + private $whitelist = array(); + + + + /** + * Set the whitelist from an array of strings, each item in the + * whitelist should be a regexp without the surrounding / or #. + * + * @param array $whitelist with all valid options, + * default is to clear the whitelist. + * + * @return $this + */ + public function set($whitelist = array()) + { + if (is_array($whitelist)) { + $this->whitelist = $whitelist; + } else { + throw new Exception("Whitelist is not of a supported format."); + } + return $this; + } + + + + /** + * Check if item exists in the whitelist. + * + * @param string $item string to check. + * @param array $whitelist optional with all valid options, default is null. + * + * @return boolean true if item is in whitelist, else false. + */ + public function check($item, $whitelist = null) + { + if ($whitelist !== null) { + $this->set($whitelist); + } + + if (empty($item) or empty($this->whitelist)) { + return false; + } + + foreach ($this->whitelist as $regexp) { + if (preg_match("#$regexp#", $item)) { + return true; + } + } + + return false; + } +} + + + /** * Resize and crop images on the fly, store generated images in a cache. * @@ -3011,7 +3120,7 @@ EOD; * */ -$version = "v0.7.0 (2015-02-10)"; +$version = "v0.7.0.x (latest)"; @@ -3312,6 +3421,9 @@ $allowRemote = getConfig('remote_allow', false); if ($allowRemote && $passwordMatch !== false) { $pattern = getConfig('remote_pattern', null); $img->setRemoteDownload($allowRemote, $pattern); + + $whitelist = getConfig('remote_whitelist', null); + $img->setRemoteHostWhitelist($whitelist); } diff --git a/webroot/imgp.php b/webroot/imgp.php index d58754a..c4f024d 100644 --- a/webroot/imgp.php +++ b/webroot/imgp.php @@ -1048,7 +1048,7 @@ class CImage public function setRemoteDownload($allow, $pattern = null) { $this->allowRemote = $allow; - $this->remotePattern = $pattern ? $pattern : $this->remotePattern; + $this->remotePattern = is_null($pattern) ? $this->remotePattern : $pattern; $this->log("Set remote download to: " . ($this->allowRemote ? "true" : "false") @@ -1071,7 +1071,48 @@ class CImage { $remote = preg_match($this->remotePattern, $src); $this->log("Detected remote image: " . ($remote ? "true" : "false")); - return $remote; + return !!$remote; + } + + + + /** + * Set whitelist for valid hostnames from where remote source can be + * downloaded. + * + * @param array $whitelist with regexp hostnames to allow download from. + * + * @return $this + */ + public function setRemoteHostWhitelist($whitelist = null) + { + $this->remoteHostWhitelist = $whitelist; + $this->log("Setting remote host whitelist to: " . print_r($this->remoteHostWhitelist, 1)); + return $this; + } + + + + /** + * Check if the hostname for the remote image, is on a whitelist, + * if the whitelist is defined. + * + * @param string $src the remote source. + * + * @return boolean true if hostname on $src is in the whitelist, else false. + */ + public function isRemoteSourceOnWhitelist($src) + { + if (is_null($this->remoteHostWhitelist)) { + $allow = true; + } else { + $whitelist = new CWhitelist(); + $hostname = parse_url($src, PHP_URL_HOST); + $allow = $whitelist->check($hostname, $this->remoteHostWhitelist); + } + + $this->log("Remote host is on whitelist: " . ($allow ? "true" : "false")); + return $allow; } @@ -1104,6 +1145,10 @@ class CImage */ public function downloadRemoteSource($src) { + if (!$this->isRemoteSourceOnWhitelist($src)) { + throw new Exception("Hostname is not on whitelist for remote sources."); + } + $remote = new CRemoteImage(); $cache = $this->saveFolder . "/remote/"; @@ -3002,6 +3047,70 @@ EOD; +/** + * Act as whitelist (or blacklist). + * + */ +class CWhitelist +{ + /** + * Array to contain the whitelist options. + */ + private $whitelist = array(); + + + + /** + * Set the whitelist from an array of strings, each item in the + * whitelist should be a regexp without the surrounding / or #. + * + * @param array $whitelist with all valid options, + * default is to clear the whitelist. + * + * @return $this + */ + public function set($whitelist = array()) + { + if (is_array($whitelist)) { + $this->whitelist = $whitelist; + } else { + throw new Exception("Whitelist is not of a supported format."); + } + return $this; + } + + + + /** + * Check if item exists in the whitelist. + * + * @param string $item string to check. + * @param array $whitelist optional with all valid options, default is null. + * + * @return boolean true if item is in whitelist, else false. + */ + public function check($item, $whitelist = null) + { + if ($whitelist !== null) { + $this->set($whitelist); + } + + if (empty($item) or empty($this->whitelist)) { + return false; + } + + foreach ($this->whitelist as $regexp) { + if (preg_match("#$regexp#", $item)) { + return true; + } + } + + return false; + } +} + + + /** * Resize and crop images on the fly, store generated images in a cache. * @@ -3011,7 +3120,7 @@ EOD; * */ -$version = "v0.7.0 (2015-02-10)"; +$version = "v0.7.0.x (latest)"; @@ -3312,6 +3421,9 @@ $allowRemote = getConfig('remote_allow', false); if ($allowRemote && $passwordMatch !== false) { $pattern = getConfig('remote_pattern', null); $img->setRemoteDownload($allowRemote, $pattern); + + $whitelist = getConfig('remote_whitelist', null); + $img->setRemoteHostWhitelist($whitelist); } diff --git a/webroot/imgs.php b/webroot/imgs.php index 5c787cb..80aa0d0 100644 --- a/webroot/imgs.php +++ b/webroot/imgs.php @@ -1048,7 +1048,7 @@ class CImage public function setRemoteDownload($allow, $pattern = null) { $this->allowRemote = $allow; - $this->remotePattern = $pattern ? $pattern : $this->remotePattern; + $this->remotePattern = is_null($pattern) ? $this->remotePattern : $pattern; $this->log("Set remote download to: " . ($this->allowRemote ? "true" : "false") @@ -1071,7 +1071,48 @@ class CImage { $remote = preg_match($this->remotePattern, $src); $this->log("Detected remote image: " . ($remote ? "true" : "false")); - return $remote; + return !!$remote; + } + + + + /** + * Set whitelist for valid hostnames from where remote source can be + * downloaded. + * + * @param array $whitelist with regexp hostnames to allow download from. + * + * @return $this + */ + public function setRemoteHostWhitelist($whitelist = null) + { + $this->remoteHostWhitelist = $whitelist; + $this->log("Setting remote host whitelist to: " . print_r($this->remoteHostWhitelist, 1)); + return $this; + } + + + + /** + * Check if the hostname for the remote image, is on a whitelist, + * if the whitelist is defined. + * + * @param string $src the remote source. + * + * @return boolean true if hostname on $src is in the whitelist, else false. + */ + public function isRemoteSourceOnWhitelist($src) + { + if (is_null($this->remoteHostWhitelist)) { + $allow = true; + } else { + $whitelist = new CWhitelist(); + $hostname = parse_url($src, PHP_URL_HOST); + $allow = $whitelist->check($hostname, $this->remoteHostWhitelist); + } + + $this->log("Remote host is on whitelist: " . ($allow ? "true" : "false")); + return $allow; } @@ -1104,6 +1145,10 @@ class CImage */ public function downloadRemoteSource($src) { + if (!$this->isRemoteSourceOnWhitelist($src)) { + throw new Exception("Hostname is not on whitelist for remote sources."); + } + $remote = new CRemoteImage(); $cache = $this->saveFolder . "/remote/"; @@ -3002,6 +3047,70 @@ EOD; +/** + * Act as whitelist (or blacklist). + * + */ +class CWhitelist +{ + /** + * Array to contain the whitelist options. + */ + private $whitelist = array(); + + + + /** + * Set the whitelist from an array of strings, each item in the + * whitelist should be a regexp without the surrounding / or #. + * + * @param array $whitelist with all valid options, + * default is to clear the whitelist. + * + * @return $this + */ + public function set($whitelist = array()) + { + if (is_array($whitelist)) { + $this->whitelist = $whitelist; + } else { + throw new Exception("Whitelist is not of a supported format."); + } + return $this; + } + + + + /** + * Check if item exists in the whitelist. + * + * @param string $item string to check. + * @param array $whitelist optional with all valid options, default is null. + * + * @return boolean true if item is in whitelist, else false. + */ + public function check($item, $whitelist = null) + { + if ($whitelist !== null) { + $this->set($whitelist); + } + + if (empty($item) or empty($this->whitelist)) { + return false; + } + + foreach ($this->whitelist as $regexp) { + if (preg_match("#$regexp#", $item)) { + return true; + } + } + + return false; + } +} + + + /** * Resize and crop images on the fly, store generated images in a cache. * @@ -3011,7 +3120,7 @@ EOD; * */ -$version = "v0.7.0 (2015-02-10)"; +$version = "v0.7.0.x (latest)"; @@ -3312,6 +3421,9 @@ $allowRemote = getConfig('remote_allow', false); if ($allowRemote && $passwordMatch !== false) { $pattern = getConfig('remote_pattern', null); $img->setRemoteDownload($allowRemote, $pattern); + + $whitelist = getConfig('remote_whitelist', null); + $img->setRemoteHostWhitelist($whitelist); }