Update picofeed

This commit is contained in:
Frederic Guillot 2015-02-05 21:16:34 -05:00
parent 79862f61b4
commit e1d0de8054
16 changed files with 398 additions and 97 deletions

2
vendor/autoload.php vendored
View File

@ -4,4 +4,4 @@
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInit08690efdd2fbfd1e6b9f9841af6f8e3c::getLoader();
return ComposerAutoloaderInita6340803ab9f5b44ecc7aa3f90f789c5::getLoader();

View File

@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit08690efdd2fbfd1e6b9f9841af6f8e3c
class ComposerAutoloaderInita6340803ab9f5b44ecc7aa3f90f789c5
{
private static $loader;
@ -19,9 +19,9 @@ class ComposerAutoloaderInit08690efdd2fbfd1e6b9f9841af6f8e3c
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit08690efdd2fbfd1e6b9f9841af6f8e3c', 'loadClassLoader'), true, true);
spl_autoload_register(array('ComposerAutoloaderInita6340803ab9f5b44ecc7aa3f90f789c5', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit08690efdd2fbfd1e6b9f9841af6f8e3c', 'loadClassLoader'));
spl_autoload_unregister(array('ComposerAutoloaderInita6340803ab9f5b44ecc7aa3f90f789c5', 'loadClassLoader'));
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
@ -42,14 +42,14 @@ class ComposerAutoloaderInit08690efdd2fbfd1e6b9f9841af6f8e3c
$includeFiles = require __DIR__ . '/autoload_files.php';
foreach ($includeFiles as $file) {
composerRequire08690efdd2fbfd1e6b9f9841af6f8e3c($file);
composerRequirea6340803ab9f5b44ecc7aa3f90f789c5($file);
}
return $loader;
}
}
function composerRequire08690efdd2fbfd1e6b9f9841af6f8e3c($file)
function composerRequirea6340803ab9f5b44ecc7aa3f90f789c5($file)
{
require $file;
}

View File

@ -1,43 +1,4 @@
[
{
"name": "fguillot/json-rpc",
"version": "dev-master",
"version_normalized": "9999999-dev",
"source": {
"type": "git",
"url": "https://github.com/fguillot/JsonRPC.git",
"reference": "f2d92d5a27749e1cfced841f96203f3275a5c16c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fguillot/JsonRPC/zipball/f2d92d5a27749e1cfced841f96203f3275a5c16c",
"reference": "f2d92d5a27749e1cfced841f96203f3275a5c16c",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"time": "2015-01-19 03:42:02",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-0": {
"JsonRPC": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Unlicense"
],
"authors": [
{
"name": "Frédéric Guillot",
"homepage": "http://fredericguillot.com"
}
],
"description": "A simple Json-RPC client/server library that just works",
"homepage": "https://github.com/fguillot/JsonRPC"
},
{
"name": "fguillot/simple-validator",
"version": "dev-master",
@ -155,6 +116,45 @@
"description": "Minimalist micro-framework",
"homepage": "https://github.com/fguillot/picoFarad"
},
{
"name": "fguillot/json-rpc",
"version": "dev-master",
"version_normalized": "9999999-dev",
"source": {
"type": "git",
"url": "https://github.com/fguillot/JsonRPC.git",
"reference": "514695af758060b2c89fd67c59a04ac5da91481a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fguillot/JsonRPC/zipball/514695af758060b2c89fd67c59a04ac5da91481a",
"reference": "514695af758060b2c89fd67c59a04ac5da91481a",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"time": "2015-02-02 22:12:09",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-0": {
"JsonRPC": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Unlicense"
],
"authors": [
{
"name": "Frédéric Guillot",
"homepage": "http://fredericguillot.com"
}
],
"description": "A simple Json-RPC client/server library that just works",
"homepage": "https://github.com/fguillot/JsonRPC"
},
{
"name": "fguillot/picofeed",
"version": "dev-master",
@ -162,12 +162,12 @@
"source": {
"type": "git",
"url": "https://github.com/fguillot/picoFeed.git",
"reference": "7f45ed03621778b30db0ff89a09f98d67096c253"
"reference": "c6f0742ccda390c81782f788e289cd5d8b555c3a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fguillot/picoFeed/zipball/7f45ed03621778b30db0ff89a09f98d67096c253",
"reference": "7f45ed03621778b30db0ff89a09f98d67096c253",
"url": "https://api.github.com/repos/fguillot/picoFeed/zipball/c6f0742ccda390c81782f788e289cd5d8b555c3a",
"reference": "c6f0742ccda390c81782f788e289cd5d8b555c3a",
"shasum": ""
},
"require": {
@ -178,7 +178,7 @@
"ext-xml": "*",
"php": ">=5.3.0"
},
"time": "2015-01-31 22:07:02",
"time": "2015-02-06 02:06:33",
"type": "library",
"installation-source": "dist",
"autoload": {

View File

@ -48,6 +48,15 @@ class Server
*/
private $classes = array();
/**
* List of instances
*
* @static
* @access private
* @var array
*/
private $instances = array();
/**
* Constructor
*
@ -129,6 +138,17 @@ class Server
$this->classes[$procedure] = array($class, $method);
}
/**
* Bind a class instance
*
* @access public
* @param mixed $instance Instance name
*/
public function attach($instance)
{
$this->instances[] = $instance;
}
/**
* Return the response to the client
*
@ -322,6 +342,12 @@ class Server
return $this->executeMethod($this->classes[$procedure][0], $this->classes[$procedure][1], $params);
}
foreach ($this->instances as $instance) {
if (method_exists($instance, $procedure)) {
return $this->executeMethod($instance, $procedure, $params);
}
}
throw new BadFunctionCallException('Unable to find the procedure');
}

View File

@ -283,4 +283,14 @@ $config->setFilterImageProxyCallback(function ($image_url) {
$key = hash_hmac('sha1', $image_url, 'secret');
return 'https://mypublicproxy/'.$key.'/'.urlencode($image_url);
});
```
```
### Define image proxy protocol restriction
- Method name: `setFilterImageProxyProtocol()`
- Default value: Empty (all protocols)
- Argument value: string
```php
$config->setFilterImageProxyProtocol('http');
```

View File

@ -157,6 +157,14 @@ abstract class Client
*/
protected $status_code = 0;
/**
* Enables direct passthrough to requesting client
*
* @access protected
* @var bool
*/
protected $passthrough = false;
/**
* Do the HTTP request
*
@ -462,6 +470,17 @@ abstract class Client
return $this->is_modified;
}
/**
* return true if passthrough mode is enabled
*
* @access public
* @return bool
*/
public function isPassthroughEnabled()
{
return $this->passthrough;
}
/**
* Set connection timeout
*
@ -592,6 +611,30 @@ abstract class Client
return $this;
}
/**
* Enable the passthrough mode
*
* @access public
* @return \PicoFeed\Client\Client
*/
public function enablePassthroughMode()
{
$this->passthrough = true;
return $this;
}
/**
* Disable the passthrough mode
*
* @access public
* @return \PicoFeed\Client\Client
*/
public function disablePassthroughMode()
{
$this->passthrough = false;
return $this;
}
/**
* Set config object
*

View File

@ -95,6 +95,44 @@ class Curl extends Client
return $length;
}
/**
* cURL callback to passthrough the HTTP status header to the client
*
* @access public
* @param resource $ch cURL handler
* @param string $buffer Header line
* @return integer Length of the buffer
*/
public function passthroughHeaders($ch, $buffer)
{
list($status, $headers) = HttpHeaders::parse(array($buffer));
if ($status !== 0) {
header(':', true, $status);
}
elseif (isset($headers['Content-Type'])) {
header($buffer);
}
return $this->readHeaders($ch, $buffer);
}
/**
* cURL callback to passthrough the HTTP body to the client
*
* If the function return -1, curl stop to read the HTTP response
*
* @access public
* @param resource $ch cURL handler
* @param string $buffer Chunk of data
* @return integer Length of the buffer
*/
public function passthroughBody($ch, $buffer)
{
echo $buffer;
return strlen($buffer);
}
/**
* Prepare HTTP headers
*
@ -162,6 +200,29 @@ class Curl extends Client
return $ch;
}
/**
* Set write/header functions
*
* @access private
* @return resource $ch
*/
private function prepareDownloadMode($ch)
{
$write_function = 'readBody';
$header_function = 'readHeaders';
if ($this->isPassthroughEnabled()) {
$write_function = 'passthroughBody';
$header_function = 'passthroughHeaders';
}
curl_setopt($ch, CURLOPT_WRITEFUNCTION, array($this, $write_function));
curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, $header_function));
return $ch;
}
/**
* Prepare curl context
*
@ -179,12 +240,11 @@ class Curl extends Client
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, ini_get('open_basedir') === '');
curl_setopt($ch, CURLOPT_MAXREDIRS, $this->max_redirects);
curl_setopt($ch, CURLOPT_ENCODING, '');
curl_setopt($ch, CURLOPT_WRITEFUNCTION, array($this, 'readBody'));
curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'readHeaders'));
curl_setopt($ch, CURLOPT_COOKIEJAR, 'php://memory');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'php://memory');
curl_setopt($ch, CURLOPT_SSLVERSION, 1); // Enforce TLS v1
$ch = $this->prepareDownloadMode($ch);
$ch = $this->prepareProxyContext($ch);
$ch = $this->prepareAuthContext($ch);

View File

@ -25,7 +25,9 @@ class Stream extends Client
'User-Agent: '.$this->user_agent,
);
if (function_exists('gzdecode')) {
// disable compression in passthrough mode. It could result in double
// compressed content which isn't decodeable by browsers
if (function_exists('gzdecode') && ! $this->isPassthroughEnabled()) {
$headers[] = 'Accept-Encoding: gzip';
}
@ -110,6 +112,8 @@ class Stream extends Client
*/
public function doRequest()
{
$body = '';
// Create context
$context = stream_context_create($this->prepareContext());
@ -119,27 +123,37 @@ class Stream extends Client
throw new InvalidUrlException('Unable to establish a connection');
}
// Get the entire body until the max size
$body = stream_get_contents($stream, $this->max_body_size + 1);
// If the body size is too large abort everything
if (strlen($body) > $this->max_body_size) {
throw new MaxSizeException('Content size too large');
}
// Get HTTP headers response
$metadata = stream_get_meta_data($stream);
list($status, $headers) = HttpHeaders::parse($metadata['wrapper_data']);
if ($this->isPassthroughEnabled()) {
header(':', true, $status);
if (isset($headers['Content-Type'])) {
header('Content-Type: '.$headers['Content-Type']);
}
fpassthru($stream);
}
else {
// Get the entire body until the max size
$body = stream_get_contents($stream, $this->max_body_size + 1);
// If the body size is too large abort everything
if (strlen($body) > $this->max_body_size) {
throw new MaxSizeException('Content size too large');
}
if ($metadata['timed_out']) {
throw new TimeoutException('Operation timeout');
}
}
fclose($stream);
if ($metadata['timed_out']) {
throw new TimeoutException('Operation timeout');
}
$this->setEffectiveUrl($metadata['wrapper_data']);
list($status, $headers) = HttpHeaders::parse($metadata['wrapper_data']);
return array(
'status' => $status,
'body' => $this->decodeBody($body, $headers),

View File

@ -32,6 +32,7 @@ namespace PicoFeed\Config;
* @method \PicoFeed\Config\Config setFilterBlacklistedTags(array $value)
* @method \PicoFeed\Config\Config setFilterImageProxyUrl($value)
* @method \PicoFeed\Config\Config setFilterImageProxyCallback($closure)
* @method \PicoFeed\Config\Config setFilterImageProxyProtocol($value)
*
* @method integer getClientTimeout()
* @method string getClientUserAgent()
@ -57,6 +58,7 @@ namespace PicoFeed\Config;
* @method array getFilterBlacklistedTags(array $default_value)
* @method string getFilterImageProxyUrl()
* @method \Closure getFilterImageProxyCallback()
* @method string getFilterImageProxyProtocol()
*/
class Config
{

View File

@ -28,6 +28,14 @@ class Attribute
*/
private $image_proxy_callback = null;
/**
* limits the image proxy usage to this protocol
*
* @access private
* @var string
*/
private $image_proxy_limit_protocol = '';
/**
* Tags and attribute whitelist
*
@ -273,8 +281,8 @@ class Attribute
*
* @access public
* @param string $tag Tag name
* @param string $attribute Atttribute name
* @param string $value Atttribute value
* @param string $attribute Attribute name
* @param string $value Attribute value
* @return boolean
*/
public function filterEmptyAttribute($tag, $attribute, $value)
@ -287,8 +295,8 @@ class Attribute
*
* @access public
* @param string $tag Tag name
* @param string $attribute Atttribute name
* @param string $value Atttribute value
* @param string $attribute Attribute name
* @param string $value Attribute value
* @return boolean
*/
public function filterAllowedAttribute($tag, $attribute, $value)
@ -301,8 +309,8 @@ class Attribute
*
* @access public
* @param string $tag Tag name
* @param string $attribute Atttribute name
* @param string $value Atttribute value
* @param string $attribute Attribute name
* @param string $value Attribute value
* @return boolean
*/
public function filterIntegerAttribute($tag, $attribute, $value)
@ -319,8 +327,8 @@ class Attribute
*
* @access public
* @param string $tag Tag name
* @param string $attribute Atttribute name
* @param string $value Atttribute value
* @param string $attribute Attribute name
* @param string $value Attribute value
* @return boolean
*/
public function filterIframeAttribute($tag, $attribute, $value)
@ -344,8 +352,8 @@ class Attribute
*
* @access public
* @param string $tag Tag name
* @param string $attribute Atttribute name
* @param string $value Atttribute value
* @param string $attribute Attribute name
* @param string $value Attribute value
* @return boolean
*/
public function filterBlacklistResourceAttribute($tag, $attribute, $value)
@ -362,8 +370,8 @@ class Attribute
*
* @access public
* @param string $tag Tag name
* @param string $attribute Atttribute name
* @param string $value Atttribute value
* @param string $attribute Attribute name
* @param string $value Attribute value
* @return boolean
*/
public function rewriteAbsoluteUrl($tag, $attribute, &$value)
@ -376,17 +384,18 @@ class Attribute
}
/**
* Rewrite image url to use with a proxy (HTTPS resource are ignored)
* Rewrite image url to use with a proxy
*
* @access public
* @param string $tag Tag name
* @param string $attribute Atttribute name
* @param string $value Atttribute value
* @param string $attribute Attribute name
* @param string $value Attribute value
* @return boolean
*/
public function rewriteImageProxyUrl($tag, $attribute, &$value)
{
if ($tag === 'img' && $attribute === 'src' && strpos($value, 'http:') === 0) {
if ($tag === 'img' && $attribute === 'src'
&& ! ($this->image_proxy_limit_protocol !== '' && stripos($value, $this->image_proxy_limit_protocol.':') !== 0)) {
if ($this->image_proxy_url) {
$value = sprintf($this->image_proxy_url, rawurlencode($value));
@ -404,8 +413,8 @@ class Attribute
*
* @access public
* @param string $tag Tag name
* @param string $attribute Atttribute name
* @param string $value Atttribute value
* @param string $attribute Attribute name
* @param string $value Attribute value
* @return boolean
*/
public function filterProtocolUrlAttribute($tag, $attribute, $value)
@ -422,7 +431,7 @@ class Attribute
*
* @access public
* @param string $tag Tag name
* @param array $attributes Atttributes list
* @param array $attributes Attributes list
* @return array
*/
public function addAttributes($tag, array $attributes)
@ -439,7 +448,7 @@ class Attribute
*
* @access public
* @param string $tag Tag name
* @param array $attributes Atttributes list
* @param array $attributes Attributes list
* @return boolean
*/
public function hasRequiredAttributes($tag, array $attributes)
@ -655,4 +664,17 @@ class Attribute
$this->image_proxy_callback = $callback ?: $this->image_proxy_callback;
return $this;
}
/**
* Set image proxy protocol restriction
*
* @access public
* @param string $value
* @return Attribute
*/
public function setImageProxyProtocol($value)
{
$this->image_proxy_limit_protocol = $value ?: $this->image_proxy_limit_protocol;
return $this;
}
}

View File

@ -98,6 +98,7 @@ class Html
if ($this->config !== null) {
$this->attribute->setImageProxyCallback($this->config->getFilterImageProxyCallback());
$this->attribute->setImageProxyUrl($this->config->getFilterImageProxyUrl());
$this->attribute->setImageProxyProtocol($this->config->getFilterImageProxyProtocol());
$this->attribute->setIframeWhitelist($this->config->getFilterIframeWhitelist(array()));
$this->attribute->setIntegerAttributes($this->config->getFilterIntegerAttributes(array()));
$this->attribute->setAttributeOverrides($this->config->getFilterAttributeOverrides(array()));

View File

@ -1,10 +1,10 @@
<?php
return array(
'test_url' => 'http://www.theguardian.com/law/2013/aug/31/microsoft-google-sue-us-fisa',
'test_url' => 'http://www.theguardian.com/sustainable-business/2015/feb/02/2015-hyper-transparency-global-business',
'body' => array(
'//div[@id="article-wrapper"]',
'//div[contains(@class, "content__main-column--article")]',
),
'strip' => array(
'//*[contains(@class, "promo")]',
'//div[contains(@class, "meta-container")]',
),
);
);

View File

@ -18,6 +18,20 @@ class ClientTest extends PHPUnit_Framework_TestCase
$this->assertNotEmpty($client->getLastModified());
}
// // disabled due to https://github.com/sebastianbergmann/phpunit/issues/1452
// /**
// * @runInSeparateProcess
// */
// public function testPassthrough()
// {
// $client = Client::getInstance();
// $client->setUrl('http://miniflux.net/favicon.ico');
// $client->enablePassthroughMode();
// $client->execute();
//
// $this->expectOutputString('no_to_be_defined');
// }
public function testCacheBothHaveToMatch()
{
$client = Client::getInstance();

View File

@ -18,6 +18,20 @@ class CurlTest extends PHPUnit_Framework_TestCase
$this->assertEquals('text/html; charset=utf-8', $result['headers']['Content-Type']);
}
// // disabled due to https://github.com/sebastianbergmann/phpunit/issues/1452
// /**
// * @runInSeparateProcess
// */
// public function testPassthrough()
// {
// $client = new Curl;
// $client->setUrl('http://miniflux.net/favicon.ico');
// $client->enablePassthroughMode();
// $client->doRequest();
//
// $this->expectOutputString('no_to_be_defined');
// }
public function testRedirect()
{
$client = new Curl;

View File

@ -27,6 +27,20 @@ class StreamTest extends PHPUnit_Framework_TestCase
$this->assertEquals('</html>', substr(trim($result['body']), -7));
}
// // disabled due to https://github.com/sebastianbergmann/phpunit/issues/1452
// /**
// * @runInSeparateProcess
// */
// public function testPassthrough()
// {
// $client = new Stream;
// $client->setUrl('http://miniflux.net/favicon.ico');
// $client->enablePassthroughMode();
// $client->doRequest();
//
// $this->expectOutputString('no_to_be_defined');
// }
public function testRedirect()
{
$client = new Stream;

View File

@ -90,7 +90,7 @@ class FilterTest extends PHPUnit_Framework_TestCase
$this->assertEquals('<p>Testboo</p>', $f->execute());
}
public function testImageProxy()
public function testNoImageProxySet()
{
$f = Filter::html('<p>Image <img src="/image.png" alt="My Image"/></p>', 'http://foo');
@ -98,11 +98,72 @@ class FilterTest extends PHPUnit_Framework_TestCase
'<p>Image <img src="http://foo/image.png" alt="My Image"/></p>',
$f->execute()
);
}
// Test setFilterImageProxyUrl and HTTPS
public function testImageProxyWithHTTPLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$f = Filter::html('<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="http://myproxy/?url='.rawurlencode('http://localhost/image.png').'" alt="My Image"/></p>',
$f->execute()
);
}
public function testImageProxyWithHTTPSLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$f = Filter::html('<p>Image <img src="https://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="http://myproxy/?url='.rawurlencode('https://localhost/image.png').'" alt="My Image"/></p>',
$f->execute()
);
}
public function testImageProxyLimitedToUnknownProtocol()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$config->setFilterImageProxyProtocol('tripleX');
$f = Filter::html('<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>',
$f->execute()
);
}
public function testImageProxyLimitedToHTTPwithHTTPLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$config->setFilterImageProxyProtocol('http');
$f = Filter::html('<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="http://myproxy/?url='.rawurlencode('http://localhost/image.png').'" alt="My Image"/></p>',
$f->execute()
);
}
public function testImageProxyLimitedToHTTPwithHTTPSLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$config->setFilterImageProxyProtocol('http');
$f = Filter::html('<p>Image <img src="https://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
@ -110,20 +171,40 @@ class FilterTest extends PHPUnit_Framework_TestCase
'<p>Image <img src="https://localhost/image.png" alt="My Image"/></p>',
$f->execute()
);
}
// Test setFilterImageProxyUrl
public function testImageProxyLimitedToHTTPSwithHTTPLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$config->setFilterImageProxyProtocol('https');
$f = Filter::html('<p>Image <img src="/image.png" alt="My Image"/></p>', 'http://foo');
$f = Filter::html('<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="http://myproxy/?url='.rawurlencode('http://foo/image.png').'" alt="My Image"/></p>',
'<p>Image <img src="http://localhost/image.png" alt="My Image"/></p>',
$f->execute()
);
}
// Test setFilterImageProxyCallback
public function testImageProxyLimitedToHTTPSwithHTTPSLink()
{
$config = new Config;
$config->setFilterImageProxyUrl('http://myproxy/?url=%s');
$config->setFilterImageProxyProtocol('https');
$f = Filter::html('<p>Image <img src="https://localhost/image.png" alt="My Image"/></p>', 'http://foo');
$f->setConfig($config);
$this->assertEquals(
'<p>Image <img src="http://myproxy/?url='.rawurlencode('https://localhost/image.png').'" alt="My Image"/></p>',
$f->execute()
);
}
public function testsetFilterImageProxyCallback()
{
$config = new Config;
$config->setFilterImageProxyCallback(function ($image_url) {
$key = hash_hmac('sha1', $image_url, 'secret');