Fix #104 and #112: Allow people to override content filter blacklist/whitelist

This commit is contained in:
Frederic Guillot 2013-07-28 17:53:17 -04:00
parent 3836018c66
commit 8dfb49b566
3 changed files with 105 additions and 44 deletions

View File

@ -161,13 +161,13 @@ To override them, create a `config.php` file at the root of the project and chan
By example, to override the default HTTP timeout value:
# file config.php
<?php
// My specific HTTP timeout (5 seconds)
define('HTTP_TIMEOUT', 5);
PS: This file must be a PHP file (nothing before the open tag `<?php`).
Actually, the following constants can be overrided:
- `HTTP_TIMEOUT` => default value is 10 seconds
@ -189,6 +189,54 @@ you can save sessions in a custom directory.
- Override the application variable like described above: `define('SESSION_SAVE_PATH', 'sessions');`
- Now, your sessions are saved in the directory `sessions`
### How to override/extends the content filtering blacklist/whitelist?
Miniflux use [PicoFeed](https://github.com/fguillot/picoFeed) to parse the content of each item.
These variables are public static arrays, extends the actual array or replace it.
**Be careful, you can break everything by doing that!!!**
Put your modifications in your custom `config.php` like described above.
By example to add a new iframe whitelist:
\PicoFeed\Filter::$iframe_whitelist[] = 'http://www.kickstarter.com';
Or to replace the entire whitelist:
\PicoFeed\Filter::$iframe_whitelist = array('http://www.kickstarter.com');
Available variables:
// Allow only specified tags and attributes
\PicoFeed\Filter::$whitelist_tags
// Strip content of these tags
\PicoFeed\Filter::$blacklist_tags
// Allow only specified URI scheme
\PicoFeed\Filter::$whitelist_scheme
// List of attributes used for external resources: src and href
\PicoFeed\Filter::$media_attributes
// Blacklist of external resources
\PicoFeed\Filter::$media_blacklist
// Required attributes for tags, if the attribute is missing the tag is dropped
\PicoFeed\Filter::$required_attributes
// Add attribute to specified tags
\PicoFeed\Filter::$add_attributes
// Attributes that must be integer
\PicoFeed\Filter::$integer_attributes
// Iframe allowed source
\PicoFeed\Filter::$iframe_whitelist
For more details, have a look to the class `vendor/PicoFeed/Filter.php`.
### How to create a theme for Miniflux?
It's very easy to write a custom theme for Miniflux.

View File

@ -36,14 +36,7 @@ Router\before(function($action) {
Response\csp(array(
'media-src' => '*',
'img-src' => '*',
'frame-src' => implode(' ', array(
'http://www.youtube.com',
'https://www.youtube.com',
'http://player.vimeo.com',
'https://player.vimeo.com',
'http://www.dailymotion.com',
'https://www.dailymotion.com',
))
'frame-src' => implode(' ', \PicoFeed\Filter::$iframe_whitelist)
));
Response\xframe();

View File

@ -10,7 +10,8 @@ class Filter
private $empty_tags = array();
private $strip_content = false;
public $allowed_tags = array(
// Allow only these tags and attributes
public static $whitelist_tags = array(
'audio' => array('controls', 'src'),
'video' => array('poster', 'controls', 'height', 'width', 'src'),
'source' => array('src', 'type'),
@ -51,12 +52,14 @@ class Filter
'q' => array('cite')
);
public $strip_tags_content = array(
// Strip content of these tags
public static $blacklist_tags = array(
'script'
);
// http://en.wikipedia.org/wiki/URI_scheme
public $allowed_protocols = array(
// Allowed URI scheme
// For a complete list go to http://en.wikipedia.org/wiki/URI_scheme
public static $scheme_whitelist = array(
'//',
'data:image/png;base64,',
'data:image/gif;base64,',
@ -92,12 +95,15 @@ class Filter
'tel:',
);
public $protocol_attributes = array(
// Attributes used for external resources
public static $media_attributes = array(
'src',
'href',
'poster',
);
public $blacklist_media = array(
// Blacklisted resources
public static $media_blacklist = array(
'feeds.feedburner.com',
'share.feedsportal.com',
'da.feedsportal.com',
@ -119,20 +125,32 @@ class Filter
'plus.google.com/share',
'www.gstatic.com/images/icons/gplus-16.png',
'www.gstatic.com/images/icons/gplus-32.png',
'www.gstatic.com/images/icons/gplus-64.png'
'www.gstatic.com/images/icons/gplus-64.png',
);
public $required_attributes = array(
// Mandatory attributes for specified tags
public static $required_attributes = array(
'a' => array('href'),
'img' => array('src'),
'iframe' => array('src')
'iframe' => array('src'),
'audio' => array('src'),
'source' => array('src'),
);
public $add_attributes = array(
// Add attributes to specified tags
public static $add_attributes = array(
'a' => 'rel="noreferrer" target="_blank"'
);
public $iframe_allowed_resources = array(
// Attributes that must be integer
public static $integer_attributes = array(
'width',
'height',
'frameborder',
);
// Iframe source whitelist, everything else is ignored
public static $iframe_whitelist = array(
'//www.youtube.com',
'http://www.youtube.com/',
'https://www.youtube.com/',
@ -218,12 +236,12 @@ class Filter
$attr_data .= ' '.$attribute.'="'.$this->getAbsoluteUrl($value, $this->url).'"';
$used_attributes[] = $attribute;
}
else if ($this->isAllowedProtocol($value) && ! $this->isBlacklistMedia($value)) {
else if ($this->isAllowedProtocol($value) && ! $this->isBlacklistedMedia($value)) {
if ($attribute == 'src' &&
isset($attributes['data-src']) &&
$this->isAllowedProtocol($attributes['data-src']) &&
! $this->isBlacklistMedia($attributes['data-src'])) {
! $this->isBlacklistedMedia($attributes['data-src'])) {
$value = $attributes['data-src'];
}
@ -232,7 +250,7 @@ class Filter
$used_attributes[] = $attribute;
}
}
else {
else if ($this->validateAttributeValue($attribute, $value)) {
$attr_data .= ' '.$attribute.'="'.$value.'"';
$used_attributes[] = $attribute;
@ -241,9 +259,9 @@ class Filter
}
// Check for required attributes
if (isset($this->required_attributes[$name])) {
if (isset(self::$required_attributes[$name])) {
foreach ($this->required_attributes[$name] as $required_attribute) {
foreach (self::$required_attributes[$name] as $required_attribute) {
if (! in_array($required_attribute, $used_attributes)) {
@ -258,9 +276,9 @@ class Filter
$this->data .= '<'.$name.$attr_data;
// Add custom attributes
if (isset($this->add_attributes[$name])) {
if (isset(self::$add_attributes[$name])) {
$this->data .= ' '.$this->add_attributes[$name].' ';
$this->data .= ' '.self::$add_attributes[$name].' ';
}
// If img or br, we don't close it here
@ -268,7 +286,7 @@ class Filter
}
}
if (in_array($name, $this->strip_tags_content)) {
if (in_array($name, self::$blacklist_tags)) {
$this->strip_content = true;
}
@ -294,8 +312,6 @@ class Filter
public function getAbsoluteUrl($path, $url)
{
//if (! filter_var($url, FILTER_VALIDATE_URL)) return '';
$components = parse_url($url);
if (! isset($components['scheme'])) $components['scheme'] = 'http';
@ -325,12 +341,10 @@ class Filter
$length = strlen($url_path);
if ($length > 1 && $url_path{$length - 1} !== '/') {
$url_path = dirname($url_path).'/';
}
if (substr($path, 0, 2) === './') {
$path = substr($path, 2);
}
@ -342,35 +356,33 @@ class Filter
public function isRelativePath($value)
{
if (strpos($value, 'data:') === 0) return false;
return strpos($value, '://') === false && strpos($value, '//') !== 0;
}
public function isAllowedTag($name)
{
return isset($this->allowed_tags[$name]);
return isset(self::$whitelist_tags[$name]);
}
public function isAllowedAttribute($tag, $attribute)
{
return in_array($attribute, $this->allowed_tags[$tag]);
return in_array($attribute, self::$whitelist_tags[$tag]);
}
public function isResource($attribute)
{
return in_array($attribute, $this->protocol_attributes);
return in_array($attribute, self::$media_attributes);
}
public function isAllowedIframeResource($value)
{
foreach ($this->iframe_allowed_resources as $url) {
foreach (self::$iframe_whitelist as $url) {
if (strpos($value, $url) === 0) {
return true;
}
}
@ -381,10 +393,9 @@ class Filter
public function isAllowedProtocol($value)
{
foreach ($this->allowed_protocols as $protocol) {
foreach (self::$scheme_whitelist as $protocol) {
if (strpos($value, $protocol) === 0) {
return true;
}
}
@ -393,12 +404,11 @@ class Filter
}
public function isBlacklistMedia($resource)
public function isBlacklistedMedia($resource)
{
foreach ($this->blacklist_media as $name) {
foreach (self::$media_blacklist as $name) {
if (strpos($resource, $name) !== false) {
return true;
}
}
@ -413,4 +423,14 @@ class Filter
isset($attributes['height']) && isset($attributes['width']) &&
$attributes['height'] == 1 && $attributes['width'] == 1;
}
public function validateAttributeValue($attribute, $value)
{
if (in_array($attribute, self::$integer_attributes)) {
return ctype_digit($value);
}
return true;
}
}