1
0
mirror of https://github.com/mosbth/cimage.git synced 2025-08-03 22:57:43 +02:00

remote download #43

This commit is contained in:
Mikael Roos
2015-01-10 20:17:12 +01:00
parent cae7a49d21
commit f9704a4fbc
5 changed files with 804 additions and 189 deletions

View File

@@ -5,8 +5,8 @@
*/
class CHttpGet
{
private $request = [];
private $response = [];
private $request = array();
private $response = array();
@@ -16,7 +16,7 @@ class CHttpGet
*/
public function __construct()
{
$this->request['header'] = [];
$this->request['header'] = array();
}
@@ -30,7 +30,23 @@ class CHttpGet
*/
public function setUrl($url)
{
$this->request['url'] = urlencode($url);
$this->request['url'] = $url;
return $this;
}
/**
* Set custom header field for the request.
*
* @param string $field
* @param string $value
*
* @return $this
*/
public function setHeader($field, $value)
{
$this->request['header'][] = "$field: $value";
return $this;
}
@@ -44,9 +60,22 @@ class CHttpGet
*
* @return $this
*/
public function setHeader($field, $value)
public function parseHeader()
{
$this->request['header']['field'] = $value;
$header = explode("\r\n", rtrim($this->response['headerRaw'], "\r\n"));
$output = array();
if ('HTTP' === substr($header[0], 0, 4)) {
list($output['version'], $output['status']) = explode(' ', $header[0]);
unset($header[0]);
}
foreach ($header as $entry) {
$pos = strpos($entry, ':');
$output[trim(substr($entry, 0, $pos))] = trim(substr($entry, $pos + 1));
}
$this->response['header'] = $output;
return $this;
}
@@ -55,47 +84,157 @@ class CHttpGet
/**
* Perform the request.
*
* @param boolean $debug set to true to dump headers.
*
* @return boolean
*/
public function get()
public function doGet($debug = false)
{
$status;
$options = [
$options = array(
CURLOPT_URL => $this->request['url'],
CURLOPT_HEADER => 1,
CURLOPT_HTTPHEADER => $this->request['header'],
CURLOPT_AUTOREFERER => true,
CURLOPT_RETURNTRANSFER => true,
];
CURLINFO_HEADER_OUT => $debug,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 5,
);
$ch = curl_init();
curl_setopt_array($ch, $options);
//curl_setopt($ch, CURLOPT_URL, $this->request['url']);
//curl_setopt($ch, CURLOPT_HEADER, 1);
//curl_setopt($ch, CURLOPT_VERBOSE, 1);
//curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)');
//curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
//curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
//curl_setopt($ch, CURLOPT_TIMEOUT, 5);
$response = curl_exec($ch);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (!$response) {
return false;
}
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$this->response['headerRaw'] = substr($response, 0, $headerSize);
$this->response['body'] = substr($response, $headerSize);
$this->parseHeader();
if ($debug) {
$info = curl_getinfo($ch);
echo "Request header<br><pre>", var_dump($info['request_header']), "</pre>";
echo "Response header (raw)<br><pre>", var_dump($this->response['headerRaw']), "</pre>";
echo "Response header (parsed)<br><pre>", var_dump($this->response['header']), "</pre>";
}
curl_close($ch);
$header = substr($response, 0, $header_size);
$body = substr($response, $header_size);
return true;
}
/**
* Get HTTP code of response.
*
* @return integer as HTTP status code or null if not available.
*/
public function getStatus()
{
return isset($this->response['header']['status'])
? (int) $this->response['header']['status']
: null;
}
/**
* Get file modification time of response.
*
* @return int as timestamp.
*/
public function getLastModified()
{
return isset($this->response['header']['Last-Modified'])
? strtotime($this->response['header']['Last-Modified'])
: null;
}
return $status;
/**
* Get content type.
*
* @return string as the content type or null if not existing or invalid.
*/
public function getContentType()
{
$type = isset($this->response['header']['Content-Type'])
? $this->response['header']['Content-Type']
: null;
return preg_match('#[a-z]+/[a-z]+#', $type)
? $type
: null;
}
/**
* Get file modification time of response.
*
* @param mixed $default as default value (int seconds) if date is
* missing in response header.
*
* @return int as timestamp or $default if Date is missing in
* response header.
*/
public function getDate($default = false)
{
return isset($this->response['header']['Date'])
? strtotime($this->response['header']['Date'])
: $default;
}
/**
* Get max age of cachable item.
*
* @param mixed $default as default value if date is missing in response
* header.
*
* @return int as timestamp or false if not available.
*/
public function getMaxAge($default = false)
{
$cacheControl = isset($this->response['header']['Cache-Control'])
? $this->response['header']['Cache-Control']
: null;
$maxAge = null;
if ($cacheControl) {
// max-age=2592000
$part = explode('=', $cacheControl);
$maxAge = ($part[0] == "max-age")
? (int) $part[1]
: null;
}
if ($maxAge) {
return $maxAge;
}
$expire = isset($this->response['header']['Expires'])
? strtotime($this->response['header']['Expires'])
: null;
return $expire ? $expire : $default;
}
/**
* Get body of response.
*
* @return string as body.
*/
public function getBody()
{
return $this->response['body'];
}
}

View File

@@ -297,6 +297,26 @@ class CImage
/**
* Allow remote file download, default is to disallow remote file download.
*/
private $allowRemote = false;
/**
* Pattern to recognize a remote file.
*/
private $remotePattern = '#^[http|https]://#';
/**
* Use the cache if true, set to false to ignore the cached file.
*/
private $useCache = true;
/**
* Properties, the class is mutable and the method setOptions()
* decides (partly) what properties are created.
@@ -313,7 +333,6 @@ class CImage
public $filters;
private $type; // Calculated from source image
private $attr; // Calculated from source image
private $useCache; // Use the cache if true, set to false to ignore the cached file.
private $useOriginal; // Use original image if possible
@@ -338,7 +357,8 @@ class CImage
/**
* Set verbose mode.
*
* @param boolean $mode true or false to enable and disable versbose mode, default is true.
* @param boolean $mode true or false to enable and disable verbose mode,
* default is true.
*
* @return $this
*/
@@ -350,6 +370,82 @@ class CImage
/**
* Set save folder, base folder for saving cache files.
*
* @todo clean up how $this->saveFolder is used in other methods.
*
* @param string $path where to store cached files.
*
* @return $this
*/
public function setSaveFolder($path)
{
$this->saveFolder = $path;
return $this;
}
/**
* Use cache or not.
*
* @todo clean up how $this->noCache is used in other methods.
*
* @param string $use true or false to use cache.
*
* @return $this
*/
public function useCache($use = true)
{
$this->useCache = $use;
return $this;
}
/**
* Allow or disallow remote image download.
*
* @param boolean $allow true or false to enable and disable.
* @param string $pattern to use to detect if its a remote file.
*
* @return $this
*/
public function setRemoteDownload($allow, $pattern = null)
{
$this->allowRemote = $allow;
$this->remotePattern = $pattern ? $pattern : $this->remotePattern;
$this->log("Set remote download to: "
. ($this->allowRemote ? "true" : "false")
. " using pattern "
. $this->remotePattern);
return $this;
}
/**
* Check if the image resource is a remote file or not.
*
* @param string $src check if src is remote.
*
* @return boolean true if $src is a remote file, else false.
*/
public function isRemoteSource($src)
{
$remote = $this->allowRemote
&& preg_match($this->remotePattern, $src);
$this->log("Detected remote image: " . ($remote ? "true" : "false"));
return $remote;
}
/**
* Check if file extension is valid as a file extension.
*
@@ -381,7 +477,26 @@ class CImage
{
if (!isset($src)) {
return $this;
} else if (!isset($dir)) {
}
if ($this->isRemoteSource($src)) {
$remote = new CRemoteImage();
$cache = $this->saveFolder . "/remote/";
if (!is_writable($cache)) {
$this->log("The remote cache is not writable.");
}
$remote->setCache($cache);
$remote->useCache($this->useCache);
$src = $remote->download($src);
$this->log("Remote HTTP status: " . $remote->getStatus());
$this->log("Remote item has local cached file: $src");
$this->log("Remote details on cache:" . print_r($remote->getDetails(), true));
$dir = null;
}
if (!isset($dir)) {
$dir = dirname($src);
$src = basename($src);
}
@@ -1942,10 +2057,8 @@ class CImage
break;
case 'gif':
if ($this->saveFolder) {
$this->Log("Saving image as GIF to cache.");
imagegif($this->image, $this->cacheFileName);
}
break;
case 'png':

330
CRemoteImage.php Normal file
View File

@@ -0,0 +1,330 @@
<?php
/**
* Get a image from a remote server using HTTP GET and If-Modified-Since.
*
*/
class CRemoteImage
{
/**
* Path to cache files.
*/
private $saveFolder = null;
/**
* Use cache or not.
*/
private $useCache = true;
/**
* HTTP object to aid in download file.
*/
private $http;
/**
* Status of the HTTP request.
*/
private $status;
/**
* Defalt age for cached items 60*60*24*7.
*/
private $defaultMaxAge = 604800;
/**
* Url of downloaded item.
*/
private $url;
/**
* Base name of cache file for downloaded item.
*/
private $fileName;
/**
* Filename for json-file with details of cached item.
*/
private $fileJson;
/**
* Filename for image-file.
*/
private $fileImage;
/**
* Cache details loaded from file.
*/
private $cache;
/**
* Constructor
*
*/
public function __construct()
{
;
}
/**
* Get status of last HTTP request.
*
* @return int as status
*/
public function getStatus()
{
return $this->status;
}
/**
* Get JSON details for cache item.
*
* @return array with json details on cache.
*/
public function getDetails()
{
return $this->cache;
}
/**
* Set the path to the cache directory.
*
* @param boolean $use true to use the cache and false to ignore cache.
*
* @return $this
*/
public function setCache($path)
{
$this->saveFolder = $path;
return $this;
}
/**
* Decide if the cache should be used or not before trying to download
* a remote file.
*
* @param boolean $use true to use the cache and false to ignore cache.
*
* @return $this
*/
public function useCache($use = true)
{
$this->useCache = $use;
return $this;
}
/**
* Translate a content type to a file extension.
*
* @param string $type a valid content type.
*
* @return string as file extension or false if no match.
*/
function contentTypeToFileExtension($type) {
$extension = array(
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif',
);
return isset($extension[$type])
? $extension[$type]
: false;
}
/**
* Set header fields.
*
* @return $this
*/
function setHeaderFields() {
$this->http->setHeader("User-Agent", "CImage/0.6 (PHP/". phpversion() . " cURL)");
$this->http->setHeader("Accept", "image/jpeg,image/png,image/gif");
if ($this->useCache) {
$this->http->setHeader("Cache-Control", "max-age=0");
} else {
$this->http->setHeader("Cache-Control", "no-cache");
$this->http->setHeader("Pragma", "no-cache");
}
}
/**
* Save downloaded resource to cache.
*
* @return string as path to saved file or false if not saved.
*/
function save() {
$this->cache = array();
$date = $this->http->getDate(time());
$maxAge = $this->http->getMaxAge($this->defaultMaxAge);
$lastModified = $this->http->getLastModified();
$type = $this->http->getContentType();
$extension = $this->contentTypeToFileExtension($type);
$this->cache['Date'] = gmdate("D, d M Y H:i:s T", $date);
$this->cache['Max-Age'] = $maxAge;
$this->cache['Content-Type'] = $type;
$this->cache['File-Extension'] = $extension;
if ($lastModified) {
$this->cache['Last-Modified'] = gmdate("D, d M Y H:i:s T", $lastModified);
}
if ($extension) {
$this->fileImage = $this->fileName . "." . $extension;
// Save only if body is a valid image
$body = $this->http->getBody();
$img = imagecreatefromstring($body);
if ($img !== false) {
file_put_contents($this->fileImage, $body);
file_put_contents($this->fileJson, json_encode($this->cache));
return $this->fileImage;
}
}
return false;
}
/**
* Got a 304 and updates cache with new age.
*
* @return string as path to cached file.
*/
function updateCacheDetails() {
$date = $this->http->getDate(time());
$maxAge = $this->http->getMaxAge($this->defaultMaxAge);
$lastModified = $this->http->getLastModified();
$this->cache['Date'] = gmdate("D, d M Y H:i:s T", $date);
$this->cache['Max-Age'] = $maxAge;
if ($lastModified) {
$this->cache['Last-Modified'] = gmdate("D, d M Y H:i:s T", $lastModified);
}
file_put_contents($this->fileJson, json_encode($this->cache));
return $this->fileImage;
}
/**
* Download a remote file and keep a cache of downloaded files.
*
* @param string $url a remote url.
*
* @return string as path to downloaded file or false if failed.
*/
function download($url) {
$this->http = new CHttpGet();
$this->url = $url;
// First check if the cache is valid and can be used
$this->loadCacheDetails();
if ($this->useCache) {
$src = $this->getCachedSource();
if ($src) {
$this->status = 1;
return $src;
}
}
// Do a HTTP request to download item
$this->setHeaderFields();
$this->http->setUrl($this->url);
$this->http->doGet();
$this->status = $this->http->getStatus();
if ($this->status === 200) {
return $this->save();
} else if ($this->status === 304) {
return $this->updateCacheDetails();
}
return false;
}
/**
* Get the path to the cached image file if the cache is valid.
*
* @return $this
*/
public function loadCacheDetails()
{
$cacheFile = str_replace(["/", ":", "#", ".", "?"], "-", $this->url);
$this->fileName = $this->saveFolder . $cacheFile;
$this->fileJson = $this->fileName . ".json";
$this->cache = json_decode(file_get_contents($this->fileJson), true);
}
/**
* Get the path to the cached image file if the cache is valid.
*
* @return string as the path ot the image file or false if no cache.
*/
public function getCachedSource()
{
$this->fileImage = $this->fileName . "." . $this->cache['File-Extension'];
$imageExists = is_readable($this->fileImage);
// Is cache valid?
$date = strtotime($this->cache['Date']);
$maxAge = $this->cache['Max-Age'];
$now = time();
if ($imageExists && $date + $maxAge > $now) {
return $this->fileImage;
}
// Prepare for a 304 if available
if ($imageExists && isset($this->cache['Last-Modified'])) {
$this->http->setHeader("If-Modified-Since", $this->cache['Last-Modified']);
}
return false;
}
}

View File

@@ -133,6 +133,21 @@ $img->setVerbose($verbose);
/**
* Allow or disallow remote download of images from other servers.
*
*/
$allowRemote = $config['remote_allow'];
if ($allowRemote) {
$pattern = isset($config['remote_pattern'])
? $config['remote_pattern']
: null;
$img->setRemoteDownload($allowRemote, $pattern);
}
/**
* shortcut, sc - extend arguments with a constant value, defined
* in config-file.
@@ -164,9 +179,13 @@ preg_match($config['valid_filename'], $srcImage)
or errorPage('Filename contains invalid characters.');
// Check that the image is a file below the directory 'image_path'.
if ($config['image_path_constraint']) {
if ($allowRemote && $img->isRemoteSource($srcImage)) {
// If source is a remote file, ignore local file checks.
} else if ($config['image_path_constraint']) {
// Check that the image is a file below the directory 'image_path'.
$pathToImage = realpath($config['image_path'] . $srcImage);
$imageDir = realpath($config['image_path']);
@@ -574,6 +593,8 @@ EOD;
* Load, process and output the image
*/
$img->log("Incoming arguments: " . print_r(verbose(), 1))
->setSaveFolder($config['cache_path'])
->useCache($useCache)
->setSource($srcImage, $config['image_path'])
->setOptions(
array(

View File

@@ -5,6 +5,9 @@
* config-file imgtest_config.php.
*
*/
include __DIR__ . "/../CHttpGet.php";
include __DIR__ . "/../CRemoteImage.php";
return array(
/**
@@ -29,7 +32,16 @@ return array(
/**
* A regexp for validating characters in the image filename.
*/
'valid_filename' => '#^[a-z0-9A-Z-/_\.]+$#',
'valid_filename' => '#^[a-z0-9A-Z-/_\.:]+$#',
/**
* Allow or disallow downloading of remote files, images available on
* some remote server. Default is to disallow.
*/
'remote_allow' => true,
'remote_pattern' => '#^http#',