aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2017-04-23 13:56:59 +0200
committerGravatar GitHub <noreply@github.com> 2017-04-23 13:56:59 +0200
commit6d5fb27f978c25be09a939d0f426a5c8962e79ec (patch)
treecba2dcd76138fa40d7a69803ddbccb3887b5111f /lib
parentd14cc25c158af2e713708beef5ce3916150f06dd (diff)
parentf483a5e95bc78cfb42e6fd90436c9f1c60e0242b (diff)
Merge pull request #1502 from Alkarex/Favicon1.2.0
Update to version 1.2 of Favicon library
Diffstat (limited to 'lib')
-rw-r--r--lib/Favicon/DataAccess.php52
-rw-r--r--lib/Favicon/Favicon.php182
-rw-r--r--lib/Favicon/FaviconDLType.php23
-rw-r--r--lib/favicons.php39
4 files changed, 198 insertions, 98 deletions
diff --git a/lib/Favicon/DataAccess.php b/lib/Favicon/DataAccess.php
index 4c1a29541..1445e9343 100644
--- a/lib/Favicon/DataAccess.php
+++ b/lib/Favicon/DataAccess.php
@@ -9,33 +9,35 @@ namespace Favicon;
**/
class DataAccess {
public function retrieveUrl($url) {
- $this->set_context();
- return @file_get_contents($url);
+ $this->set_context();
+ return @file_get_contents($url);
}
-
+
public function retrieveHeader($url) {
- $this->set_context();
+ $this->set_context();
$headers = @get_headers($url, 1);
return is_array($headers) ? array_change_key_case($headers) : array();
}
-
- public function saveCache($file, $data) {
- file_put_contents($file, $data);
- }
-
- public function readCache($file) {
- return file_get_contents($file);
- }
-
- private function set_context() {
- stream_context_set_default(
- array(
- 'http' => array(
- 'method' => 'GET',
- 'timeout' => 10,
- 'header' => "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:20.0; Favicon; +https://github.com/ArthurHoaro/favicon) Gecko/20100101 Firefox/32.0\r\n",
- )
- )
- );
- }
-}
+
+ public function saveCache($file, $data) {
+ file_put_contents($file, $data);
+ }
+
+ public function readCache($file) {
+ return file_get_contents($file);
+ }
+
+ private function set_context() {
+ stream_context_set_default(
+ array(
+ 'http' => array(
+ 'method' => 'GET',
+ 'follow_location' => 0,
+ 'max_redirects' => 1,
+ 'timeout' => 10,
+ 'header' => "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:20.0; Favicon; +https://github.com/ArthurHoaro/favicon) Gecko/20100101 Firefox/32.0\r\n",
+ )
+ )
+ );
+ }
+} \ No newline at end of file
diff --git a/lib/Favicon/Favicon.php b/lib/Favicon/Favicon.php
index 85d2ef19b..c026d8a95 100644
--- a/lib/Favicon/Favicon.php
+++ b/lib/Favicon/Favicon.php
@@ -4,6 +4,8 @@ namespace Favicon;
class Favicon
{
+ protected static $TYPE_CACHE_URL = 'url';
+ protected static $TYPE_CACHE_IMG = 'img';
protected $url = '';
protected $cacheDir;
protected $cacheTimeout;
@@ -16,18 +18,24 @@ class Favicon
}
$this->cacheDir = __DIR__ . '/../../resources/cache';
+ $this->cacheTimeout = 604800;
$this->dataAccess = new DataAccess();
}
+ /**
+ * Set cache settings:
+ * - dir: cache directory
+ * - timeout: in seconds
+ *
+ * @param array $args
+ */
public function cache($args = array()) {
if (isset($args['dir'])) {
$this->cacheDir = $args['dir'];
}
if (!empty($args['timeout'])) {
- $this->cacheTimeout = $args['timeout'];
- } else {
- $this->cacheTimeout = 0;
+ $this->cacheTimeout = $args['timeout'];
}
}
@@ -103,6 +111,9 @@ class Favicon
case '301':
case '302':
$url = isset($headers['location']) ? $headers['location'] : '';
+ if (is_array($url)) {
+ $url = end($url);
+ }
break;
default:
$loop = FALSE;
@@ -120,9 +131,16 @@ class Favicon
/**
* Find remote (or cached) favicon
- * @return favicon URL, false if nothing was found
- **/
- public function get($url = '')
+ *
+ * @param string $url to look for a favicon
+ * @param int $type type of retrieval (FaviconDLType):
+ * - HOTLINK_URL: returns remote URL
+ * - DL_FILE_PATH: returns file path of the favicon downloaded locally
+ * - RAW_IMAGE: returns the favicon image binary string
+ *
+ * @return string|bool favicon URL, false if nothing was found
+ */
+ public function get($url = '', $type = FaviconDLType::HOTLINK_URL)
{
// URLs passed to this method take precedence.
if (!empty($url)) {
@@ -130,25 +148,30 @@ class Favicon
}
// Get the base URL without the path for clearer concatenations.
- $original = rtrim($this->baseUrl($this->url, true), '/');
- $url = rtrim($this->endRedirect($this->baseUrl($this->url, false)), '/');
-
- if(($favicon = $this->checkCache($url)) || ($favicon = $this->getFavicon($url))) {
- $base = true;
- }
- elseif(($favicon = $this->checkCache($original)) || ($favicon = $this->getFavicon($original, false))) {
- $base = false;
+ $url = rtrim($this->baseUrl($this->url, true), '/');
+ $original = $url;
+ if (($favicon = $this->checkCache($original, self::$TYPE_CACHE_URL)) === false
+ && ! $favicon = $this->getFavicon($original, false)
+ ) {
+ $url = rtrim($this->endRedirect($this->baseUrl($this->url, false)), '/');
+ if (($favicon = $this->checkCache($url, self::$TYPE_CACHE_URL)) === false
+ && ! $favicon = $this->getFavicon($url)
+ ) {
+ $url = $original;
+ }
}
- else
- return false;
-
- // Save cache if necessary
- $cache = $this->cacheDir . '/' . md5($base ? $url : $original);
- if ($this->cacheTimeout && !file_exists($cache) || (is_writable($cache) && time() - filemtime($cache) > $this->cacheTimeout)) {
- $this->dataAccess->saveCache($cache, $favicon);
+
+ $this->saveCache($url, $favicon, self::$TYPE_CACHE_URL);
+
+ switch ($type) {
+ case FaviconDLType::DL_FILE_PATH:
+ return $this->getImage($url, $favicon, false);
+ case FaviconDLType::RAW_IMAGE:
+ return $this->getImage($url, $favicon, true);
+ case FaviconDLType::HOTLINK_URL:
+ default:
+ return empty($favicon) ? false : $favicon;
}
-
- return $favicon;
}
private function getFavicon($url, $checkDefault = true) {
@@ -168,7 +191,10 @@ class Favicon
// See if it's specified in a link tag in domain url.
if (!$favicon) {
- $favicon = $this->getInPage($url);
+ $favicon = trim($this->getInPage($url));
+ }
+ if (substr($favicon, 0, 2) === '//') {
+ $favicon = 'https:' . $favicon;
}
// Make sure the favicon is an absolute URL.
@@ -185,7 +211,48 @@ class Favicon
return $favicon;
}
-
+
+ /**
+ * Find remote favicon and return it as an image
+ */
+ private function getImage($url, $faviconUrl = '', $image = false)
+ {
+ if (empty($faviconUrl)) {
+ return false;
+ }
+
+ $favicon = $this->checkCache($url, self::$TYPE_CACHE_IMG);
+ // Favicon not found in the cache
+ if( $favicon === false ) {
+ $favicon = $this->dataAccess->retrieveUrl($faviconUrl);
+ // Definitely not found
+ if (!$this->checkImageMTypeContent($favicon)) {
+ return false;
+ } else {
+ $this->saveCache($url, $favicon, self::$TYPE_CACHE_IMG);
+ }
+ }
+
+ if( $image ) {
+ return $favicon;
+ }
+ else
+ return self::$TYPE_CACHE_IMG . md5($url);
+ }
+
+ /**
+ * Display data as a PNG Favicon, then exit
+ * @param $data
+ */
+ private function displayFavicon($data) {
+ header('Content-Type: image/png');
+ header('Cache-Control: private, max-age=10800, pre-check=10800');
+ header('Pragma: private');
+ header('Expires: ' . date(DATE_RFC822,strtotime('7 day')));
+ echo $data;
+ exit;
+ }
+
private function getInPage($url) {
$html = $this->dataAccess->retrieveUrl("{$url}/");
preg_match('!<head.*?>.*</head>!ims', $html, $match);
@@ -197,48 +264,77 @@ class Favicon
$head = $match[0];
$dom = new \DOMDocument();
- // Use error supression, because the HTML might be too malformed.
+ // Use error suppression, because the HTML might be too malformed.
if (@$dom->loadHTML($head)) {
$links = $dom->getElementsByTagName('link');
foreach ($links as $link) {
if ($link->hasAttribute('rel') && strtolower($link->getAttribute('rel')) == 'shortcut icon') {
return $link->getAttribute('href');
- } elseif ($link->hasAttribute('rel') && strtolower($link->getAttribute('rel')) == 'icon') {
+ }
+ }
+ foreach ($links as $link) {
+ if ($link->hasAttribute('rel') && strtolower($link->getAttribute('rel')) == 'icon') {
return $link->getAttribute('href');
- } elseif ($link->hasAttribute('href') && strpos($link->getAttribute('href'), 'favicon') !== FALSE) {
+ }
+ }
+ foreach ($links as $link) {
+ if ($link->hasAttribute('href') && strpos($link->getAttribute('href'), 'favicon') !== FALSE) {
return $link->getAttribute('href');
}
}
}
return false;
}
-
- private function checkCache($url) {
+
+ private function checkCache($url, $type) {
if ($this->cacheTimeout) {
- $cache = $this->cacheDir . '/' . md5($url);
- if (file_exists($cache) && is_readable($cache) && (time() - filemtime($cache) < $this->cacheTimeout)) {
+ $cache = $this->cacheDir . '/'. $type . md5($url);
+ if (file_exists($cache) && is_readable($cache)
+ && ($this->cacheTimeout === -1 || time() - filemtime($cache) < $this->cacheTimeout)
+ ) {
return $this->dataAccess->readCache($cache);
}
- }
+ }
return false;
}
-
+
+ /**
+ * Will save data in cacheDir if the directory writable and any previous cache is expired (cacheTimeout)
+ * @param $url
+ * @param $data
+ * @param $type
+ * @return string cache file path
+ */
+ private function saveCache($url, $data, $type) {
+ // Save cache if necessary
+ $cache = $this->cacheDir . '/'. $type . md5($url);
+ if ($this->cacheTimeout && !file_exists($cache)
+ || (is_writable($cache) && $this->cacheTimeout !== -1 && time() - filemtime($cache) > $this->cacheTimeout)
+ ) {
+ $this->dataAccess->saveCache($cache, $data);
+ }
+ return $cache;
+ }
+
private function checkImageMType($url) {
- $tmpFile = $this->cacheDir . '/tmp.ico';
$fileContent = $this->dataAccess->retrieveUrl($url);
- $this->dataAccess->saveCache($tmpFile, $fileContent);
+ return $this->checkImageMTypeContent($fileContent);
+ }
+
+ private function checkImageMTypeContent($content) {
+ if(empty($content)) return false;
+
$isImage = true;
try {
- $finfo = finfo_open(FILEINFO_MIME_TYPE);
- $isImage = strpos(finfo_file($finfo, $tmpFile), 'image') !== false;
- finfo_close($finfo);
- } catch (Exception $e) {
+ $fInfo = finfo_open(FILEINFO_MIME_TYPE);
+ $isImage = strpos(finfo_buffer($fInfo, $content), 'image') !== false;
+ finfo_close($fInfo);
+ } catch (\Exception $e) {
+ error_log('Favicon checkImageMTypeContent error: ' . $e->getMessage());
}
- unlink($tmpFile);
-
return $isImage;
}
@@ -291,7 +387,7 @@ class Favicon
}
/**
- * @param DataAccess $dataAccess
+ * @param DataAccess|\PHPUnit_Framework_MockObject_MockObject $dataAccess
*/
public function setDataAccess($dataAccess)
{
diff --git a/lib/Favicon/FaviconDLType.php b/lib/Favicon/FaviconDLType.php
new file mode 100644
index 000000000..6da525a7f
--- /dev/null
+++ b/lib/Favicon/FaviconDLType.php
@@ -0,0 +1,23 @@
+<?php
+
+
+namespace Favicon;
+
+
+interface FaviconDLType
+{
+ /**
+ * Retrieve remote favicon URL.
+ */
+ const HOTLINK_URL = 0;
+
+ /**
+ * Retrieve downloaded favicon path (requires cache).
+ */
+ const DL_FILE_PATH = 1;
+
+ /**
+ * Retrieve the image content as a binary string.
+ */
+ const RAW_IMAGE = 2;
+}
diff --git a/lib/favicons.php b/lib/favicons.php
index d8c97964e..d8baba342 100644
--- a/lib/favicons.php
+++ b/lib/favicons.php
@@ -1,43 +1,22 @@
<?php
-include(LIB_PATH . '/Favicon/Favicon.php');
+include(LIB_PATH . '/Favicon/FaviconDLType.php');
include(LIB_PATH . '/Favicon/DataAccess.php');
+include(LIB_PATH . '/Favicon/Favicon.php');
$favicons_dir = DATA_PATH . '/favicons/';
$default_favicon = PUBLIC_PATH . '/themes/icons/default_favicon.ico';
function download_favicon($website, $dest) {
- global $favicons_dir, $default_favicon;
+ global $default_favicon;
syslog(LOG_INFO, 'FreshRSS Favicon discovery GET ' . $website);
$favicon_getter = new \Favicon\Favicon();
- $favicon_getter->setCacheDir($favicons_dir);
- $favicon_url = $favicon_getter->get($website);
-
- if ($favicon_url === false) {
- return @copy($default_favicon, $dest);
- }
-
- syslog(LOG_INFO, 'FreshRSS Favicon GET ' . $favicon_url);
- $c = curl_init($favicon_url);
- curl_setopt($c, CURLOPT_HEADER, false);
- curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($c, CURLOPT_BINARYTRANSFER, true);
- curl_setopt($c, CURLOPT_USERAGENT, 'FreshRSS/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ')');
- $img_raw = curl_exec($c);
- $status_code = curl_getinfo($c, CURLINFO_HTTP_CODE);
- curl_close($c);
-
- if ($status_code === 200) {
- $file = fopen($dest, 'w');
- if ($file !== false) {
- fwrite($file, $img_raw);
- fclose($file);
- return true;
- }
- } else {
- syslog(LOG_WARNING, 'FreshRSS Favicon GET ' . $favicon_url . ' error ' . $status_code);
- }
+ $tmpPath = realpath(TMP_PATH);
+ $favicon_getter->setCacheDir($tmpPath);
+ $favicon_getter->setCacheTimeout(-1);
+ $favicon_path = $favicon_getter->get($website, \Favicon\FaviconDLType::DL_FILE_PATH);
- return false;
+ return ($favicon_path != false && @rename($tmpPath . '/' . $favicon_path, $dest)) ||
+ @copy($default_favicon, $dest);
}