Merge pull request #3483 from Icinga/feature/update-vendor-libs

Update vendor libs
This commit is contained in:
Eric Lippmann 2018-06-27 09:36:16 +02:00 committed by GitHub
commit 184d4afdbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
84 changed files with 1122 additions and 358 deletions

View File

@ -14,10 +14,7 @@ if (function_exists('spl_autoload_register') && function_exists('spl_autoload_un
spl_autoload_register('__autoload'); spl_autoload_register('__autoload');
} }
} elseif (!function_exists('__autoload')) { } elseif (!function_exists('__autoload')) {
function __autoload($class) require dirname(__FILE__) . '/HTMLPurifier.autoload-legacy.php';
{
return HTMLPurifier_Bootstrap::autoload($class);
}
} }
if (ini_get('zend.ze1_compatibility_mode')) { if (ini_get('zend.ze1_compatibility_mode')) {

View File

@ -19,7 +19,7 @@
*/ */
/* /*
HTML Purifier 4.8.0 - Standards Compliant HTML Filtering HTML Purifier 4.10.0 - Standards Compliant HTML Filtering
Copyright (C) 2006-2008 Edward Z. Yang Copyright (C) 2006-2008 Edward Z. Yang
This library is free software; you can redistribute it and/or This library is free software; you can redistribute it and/or
@ -58,12 +58,12 @@ class HTMLPurifier
* Version of HTML Purifier. * Version of HTML Purifier.
* @type string * @type string
*/ */
public $version = '4.8.0'; public $version = '4.10.0';
/** /**
* Constant with version of HTML Purifier. * Constant with version of HTML Purifier.
*/ */
const VERSION = '4.8.0'; const VERSION = '4.10.0';
/** /**
* Global configuration object. * Global configuration object.

View File

@ -19,8 +19,8 @@ class HTMLPurifier_Arborize
if ($token instanceof HTMLPurifier_Token_End) { if ($token instanceof HTMLPurifier_Token_End) {
$token->start = null; // [MUT] $token->start = null; // [MUT]
$r = array_pop($stack); $r = array_pop($stack);
assert($r->name === $token->name); //assert($r->name === $token->name);
assert(empty($token->attr)); //assert(empty($token->attr));
$r->endCol = $token->col; $r->endCol = $token->col;
$r->endLine = $token->line; $r->endLine = $token->line;
$r->endArmor = $token->armor; $r->endArmor = $token->armor;
@ -32,7 +32,7 @@ class HTMLPurifier_Arborize
$stack[] = $node; $stack[] = $node;
} }
} }
assert(count($stack) == 1); //assert(count($stack) == 1);
return $stack[0]; return $stack[0];
} }

View File

@ -86,7 +86,13 @@ abstract class HTMLPurifier_AttrDef
*/ */
protected function mungeRgb($string) protected function mungeRgb($string)
{ {
return preg_replace('/rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\)/', 'rgb(\1,\2,\3)', $string); $p = '\s*(\d+(\.\d+)?([%]?))\s*';
if (preg_match('/(rgba|hsla)\(/', $string)) {
return preg_replace('/(rgba|hsla)\('.$p.','.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8,\11)', $string);
}
return preg_replace('/(rgb|hsl)\('.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8)', $string);
} }
/** /**

View File

@ -27,13 +27,38 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
$definition = $config->getCSSDefinition(); $definition = $config->getCSSDefinition();
$allow_duplicates = $config->get("CSS.AllowDuplicates"); $allow_duplicates = $config->get("CSS.AllowDuplicates");
// we're going to break the spec and explode by semicolons.
// This is because semicolon rarely appears in escaped form
// Doing this is generally flaky but fast
// IT MIGHT APPEAR IN URIs, see HTMLPurifier_AttrDef_CSSURI
// for details
$declarations = explode(';', $css); // According to the CSS2.1 spec, the places where a
// non-delimiting semicolon can appear are in strings
// escape sequences. So here is some dumb hack to
// handle quotes.
$len = strlen($css);
$accum = "";
$declarations = array();
$quoted = false;
for ($i = 0; $i < $len; $i++) {
$c = strcspn($css, ";'\"", $i);
$accum .= substr($css, $i, $c);
$i += $c;
if ($i == $len) break;
$d = $css[$i];
if ($quoted) {
$accum .= $d;
if ($d == $quoted) {
$quoted = false;
}
} else {
if ($d == ";") {
$declarations[] = $accum;
$accum = "";
} else {
$accum .= $d;
$quoted = $d;
}
}
}
if ($accum != "") $declarations[] = $accum;
$propvalues = array(); $propvalues = array();
$new_declarations = ''; $new_declarations = '';

View File

@ -6,6 +6,16 @@
class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
{ {
/**
* @type HTMLPurifier_AttrDef_CSS_AlphaValue
*/
protected $alpha;
public function __construct()
{
$this->alpha = new HTMLPurifier_AttrDef_CSS_AlphaValue();
}
/** /**
* @param string $color * @param string $color
* @param HTMLPurifier_Config $config * @param HTMLPurifier_Config $config
@ -29,59 +39,104 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
return $colors[$lower]; return $colors[$lower];
} }
if (strpos($color, 'rgb(') !== false) { if (preg_match('#(rgb|rgba|hsl|hsla)\(#', $color, $matches) === 1) {
// rgb literal handling
$length = strlen($color); $length = strlen($color);
if (strpos($color, ')') !== $length - 1) { if (strpos($color, ')') !== $length - 1) {
return false; return false;
} }
$triad = substr($color, 4, $length - 4 - 1);
$parts = explode(',', $triad); // get used function : rgb, rgba, hsl or hsla
if (count($parts) !== 3) { $function = $matches[1];
$parameters_size = 3;
$alpha_channel = false;
if (substr($function, -1) === 'a') {
$parameters_size = 4;
$alpha_channel = true;
}
/*
* Allowed types for values :
* parameter_position => [type => max_value]
*/
$allowed_types = array(
1 => array('percentage' => 100, 'integer' => 255),
2 => array('percentage' => 100, 'integer' => 255),
3 => array('percentage' => 100, 'integer' => 255),
);
$allow_different_types = false;
if (strpos($function, 'hsl') !== false) {
$allowed_types = array(
1 => array('integer' => 360),
2 => array('percentage' => 100),
3 => array('percentage' => 100),
);
$allow_different_types = true;
}
$values = trim(str_replace($function, '', $color), ' ()');
$parts = explode(',', $values);
if (count($parts) !== $parameters_size) {
return false; return false;
} }
$type = false; // to ensure that they're all the same type
$type = false;
$new_parts = array(); $new_parts = array();
$i = 0;
foreach ($parts as $part) { foreach ($parts as $part) {
$i++;
$part = trim($part); $part = trim($part);
if ($part === '') { if ($part === '') {
return false; return false;
} }
$length = strlen($part);
if ($part[$length - 1] === '%') { // different check for alpha channel
// handle percents if ($alpha_channel === true && $i === count($parts)) {
if (!$type) { $result = $this->alpha->validate($part, $config, $context);
$type = 'percentage';
} elseif ($type !== 'percentage') { if ($result === false) {
return false; return false;
} }
$num = (float)substr($part, 0, $length - 1);
if ($num < 0) { $new_parts[] = (string)$result;
$num = 0; continue;
} }
if ($num > 100) {
$num = 100; if (substr($part, -1) === '%') {
} $current_type = 'percentage';
$new_parts[] = "$num%";
} else { } else {
// handle integers $current_type = 'integer';
if (!$type) { }
$type = 'integer';
} elseif ($type !== 'integer') { if (!array_key_exists($current_type, $allowed_types[$i])) {
return false; return false;
} }
$num = (int)$part;
if ($num < 0) { if (!$type) {
$num = 0; $type = $current_type;
} }
if ($num > 255) {
$num = 255; if ($allow_different_types === false && $type != $current_type) {
return false;
} }
$new_parts[] = (string)$num;
$max_value = $allowed_types[$i][$current_type];
if ($current_type == 'integer') {
// Return value between range 0 -> $max_value
$new_parts[] = (int)max(min($part, $max_value), 0);
} elseif ($current_type == 'percentage') {
$new_parts[] = (float)max(min(rtrim($part, '%'), $max_value), 0) . '%';
} }
} }
$new_triad = implode(',', $new_parts);
$color = "rgb($new_triad)"; $new_values = implode(',', $new_parts);
$color = $function . '(' . $new_values . ')';
} else { } else {
// hexadecimal handling // hexadecimal handling
if ($color[0] === '#') { if ($color[0] === '#') {
@ -100,6 +155,7 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
} }
return $color; return $color;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -97,7 +97,7 @@ class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
// PHP 5.3 and later support this functionality natively // PHP 5.3 and later support this functionality natively
if (function_exists('idn_to_ascii')) { if (function_exists('idn_to_ascii')) {
return idn_to_ascii($string); $string = idn_to_ascii($string, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
// If we have Net_IDNA2 support, we can support IRIs by // If we have Net_IDNA2 support, we can support IRIs by
// punycoding them. (This is the most portable thing to do, // punycoding them. (This is the most portable thing to do,
@ -123,13 +123,14 @@ class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
} }
} }
$string = implode('.', $new_parts); $string = implode('.', $new_parts);
if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
return $string;
}
} catch (Exception $e) { } catch (Exception $e) {
// XXX error reporting // XXX error reporting
} }
} }
// Try again
if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
return $string;
}
return false; return false;
} }
} }

View File

@ -0,0 +1,37 @@
<?php
// must be called POST validation
/**
* Adds rel="noopener" to any links which target a different window
* than the current one. This is used to prevent malicious websites
* from silently replacing the original window, which could be used
* to do phishing.
* This transform is controlled by %HTML.TargetNoopener.
*/
class HTMLPurifier_AttrTransform_TargetNoopener extends HTMLPurifier_AttrTransform
{
/**
* @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
if (isset($attr['rel'])) {
$rels = explode(' ', $attr['rel']);
} else {
$rels = array();
}
if (isset($attr['target']) && !in_array('noopener', $rels)) {
$rels[] = 'noopener';
}
if (!empty($rels) || isset($attr['rel'])) {
$attr['rel'] = implode(' ', $rels);
}
return $attr;
}
}

View File

@ -225,6 +225,10 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
); );
$max = $config->get('CSS.MaxImgLength'); $max = $config->get('CSS.MaxImgLength');
$this->info['min-width'] =
$this->info['max-width'] =
$this->info['min-height'] =
$this->info['max-height'] =
$this->info['width'] = $this->info['width'] =
$this->info['height'] = $this->info['height'] =
$max === null ? $max === null ?

View File

@ -50,7 +50,7 @@ class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef
// a little sanity check to make sure it's not ALL whitespace // a little sanity check to make sure it's not ALL whitespace
$all_whitespace = true; $all_whitespace = true;
$current_li = false; $current_li = null;
foreach ($children as $node) { foreach ($children as $node) {
if (!empty($node->is_whitespace)) { if (!empty($node->is_whitespace)) {
@ -71,7 +71,7 @@ class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef
// to handle non-list elements; non-list elements should // to handle non-list elements; non-list elements should
// not be appended to an existing li; only li created // not be appended to an existing li; only li created
// for non-list. This distinction is not currently made. // for non-list. This distinction is not currently made.
if ($current_li === false) { if ($current_li === null) {
$current_li = new HTMLPurifier_Node_Element('li'); $current_li = new HTMLPurifier_Node_Element('li');
$result[] = $current_li; $result[] = $current_li;
} }

View File

@ -203,7 +203,7 @@ class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef
$current_tr_tbody->children[] = $node; $current_tr_tbody->children[] = $node;
break; break;
case '#PCDATA': case '#PCDATA':
assert($node->is_whitespace); //assert($node->is_whitespace);
if ($current_tr_tbody === null) { if ($current_tr_tbody === null) {
$ret[] = $node; $ret[] = $node;
} else { } else {

View File

@ -21,7 +21,7 @@ class HTMLPurifier_Config
* HTML Purifier's version * HTML Purifier's version
* @type string * @type string
*/ */
public $version = '4.8.0'; public $version = '4.10.0';
/** /**
* Whether or not to automatically finalize * Whether or not to automatically finalize
@ -333,7 +333,7 @@ class HTMLPurifier_Config
} }
// Raw type might be negative when using the fully optimized form // Raw type might be negative when using the fully optimized form
// of stdclass, which indicates allow_null == true // of stdClass, which indicates allow_null == true
$rtype = is_int($def) ? $def : $def->type; $rtype = is_int($def) ? $def : $def->type;
if ($rtype < 0) { if ($rtype < 0) {
$type = -$rtype; $type = -$rtype;

View File

@ -24,11 +24,11 @@ class HTMLPurifier_ConfigSchema
* *
* array( * array(
* 'Namespace' => array( * 'Namespace' => array(
* 'Directive' => new stdclass(), * 'Directive' => new stdClass(),
* ) * )
* ) * )
* *
* The stdclass may have the following properties: * The stdClass may have the following properties:
* *
* - If isAlias isn't set: * - If isAlias isn't set:
* - type: Integer type of directive, see HTMLPurifier_VarParser for definitions * - type: Integer type of directive, see HTMLPurifier_VarParser for definitions
@ -39,8 +39,8 @@ class HTMLPurifier_ConfigSchema
* - namespace: Namespace this directive aliases to * - namespace: Namespace this directive aliases to
* - name: Directive name this directive aliases to * - name: Directive name this directive aliases to
* *
* In certain degenerate cases, stdclass will actually be an integer. In * In certain degenerate cases, stdClass will actually be an integer. In
* that case, the value is equivalent to an stdclass with the type * that case, the value is equivalent to an stdClass with the type
* property set to the integer. If the integer is negative, type is * property set to the integer. If the integer is negative, type is
* equal to the absolute value of integer, and allow_null is true. * equal to the absolute value of integer, and allow_null is true.
* *
@ -105,7 +105,7 @@ class HTMLPurifier_ConfigSchema
*/ */
public function add($key, $default, $type, $allow_null) public function add($key, $default, $type, $allow_null)
{ {
$obj = new stdclass(); $obj = new stdClass();
$obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type]; $obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type];
if ($allow_null) { if ($allow_null) {
$obj->allow_null = true; $obj->allow_null = true;
@ -152,14 +152,14 @@ class HTMLPurifier_ConfigSchema
*/ */
public function addAlias($key, $new_key) public function addAlias($key, $new_key)
{ {
$obj = new stdclass; $obj = new stdClass;
$obj->key = $new_key; $obj->key = $new_key;
$obj->isAlias = true; $obj->isAlias = true;
$this->info[$key] = $obj; $this->info[$key] = $obj;
} }
/** /**
* Replaces any stdclass that only has the type property with type integer. * Replaces any stdClass that only has the type property with type integer.
*/ */
public function postProcess() public function postProcess()
{ {

View File

@ -0,0 +1,16 @@
Core.AggressivelyRemoveScript
TYPE: bool
VERSION: 4.9.0
DEFAULT: true
--DESCRIPTION--
<p>
This directive enables aggressive pre-filter removal of
script tags. This is not necessary for security,
but it can help work around a bug in libxml where embedded
HTML elements inside script sections cause the parser to
choke. To revert to pre-4.9.0 behavior, set this to false.
This directive has no effect if %Core.Trusted is true,
%Core.RemoveScriptContents is false, or %Core.HiddenElements
does not contain script.
</p>
--# vim: et sw=4 sts=4

View File

@ -0,0 +1,36 @@
Core.LegacyEntityDecoder
TYPE: bool
VERSION: 4.9.0
DEFAULT: false
--DESCRIPTION--
<p>
Prior to HTML Purifier 4.9.0, entities were decoded by performing
a global search replace for all entities whose decoded versions
did not have special meanings under HTML, and replaced them with
their decoded versions. We would match all entities, even if they did
not have a trailing semicolon, but only if there weren't any trailing
alphanumeric characters.
</p>
<table>
<tr><th>Original</th><th>Text</th><th>Attribute</th></tr>
<tr><td>&amp;yen;</td><td>&yen;</td><td>&yen;</td></tr>
<tr><td>&amp;yen</td><td>&yen;</td><td>&yen;</td></tr>
<tr><td>&amp;yena</td><td>&amp;yena</td><td>&amp;yena</td></tr>
<tr><td>&amp;yen=</td><td>&yen;=</td><td>&yen;=</td></tr>
</table>
<p>
In HTML Purifier 4.9.0, we changed the behavior of entity parsing
to match entities that had missing trailing semicolons in less
cases, to more closely match HTML5 parsing behavior:
</p>
<table>
<tr><th>Original</th><th>Text</th><th>Attribute</th></tr>
<tr><td>&amp;yen;</td><td>&yen;</td><td>&yen;</td></tr>
<tr><td>&amp;yen</td><td>&yen;</td><td>&yen;</td></tr>
<tr><td>&amp;yena</td><td>&yen;a</td><td>&amp;yena</td></tr>
<tr><td>&amp;yen=</td><td>&yen;=</td><td>&amp;yen=</td></tr>
</table>
<p>
This flag reverts back to pre-HTML Purifier 4.9.0 behavior.
</p>
--# vim: et sw=4 sts=4

View File

@ -0,0 +1,10 @@
--# vim: et sw=4 sts=4
HTML.TargetNoopener
TYPE: bool
VERSION: 4.8.0
DEFAULT: TRUE
--DESCRIPTION--
If enabled, noopener rel attributes are added to links which have
a target attribute associated with them. This prevents malicious
destinations from overwriting the original window.
--# vim: et sw=4 sts=4

View File

@ -1,5 +1,5 @@
URI.DefaultScheme URI.DefaultScheme
TYPE: string TYPE: string/null
DEFAULT: 'http' DEFAULT: 'http'
--DESCRIPTION-- --DESCRIPTION--
@ -7,4 +7,9 @@ DEFAULT: 'http'
Defines through what scheme the output will be served, in order to Defines through what scheme the output will be served, in order to
select the proper object validator when no scheme information is present. select the proper object validator when no scheme information is present.
</p> </p>
<p>
Starting with HTML Purifier 4.9.0, the default scheme can be null, in
which case we reject all URIs which do not have explicit schemes.
</p>
--# vim: et sw=4 sts=4 --# vim: et sw=4 sts=4

View File

@ -112,6 +112,7 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
} }
unlink($dir . '/' . $filename); unlink($dir . '/' . $filename);
} }
closedir($dh);
return true; return true;
} }
@ -142,6 +143,7 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
unlink($dir . '/' . $filename); unlink($dir . '/' . $filename);
} }
} }
closedir($dh);
return true; return true;
} }
@ -198,11 +200,8 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
if ($result !== false) { if ($result !== false) {
// set permissions of the new file (no execute) // set permissions of the new file (no execute)
$chmod = $config->get('Cache.SerializerPermissions'); $chmod = $config->get('Cache.SerializerPermissions');
if ($chmod === null) { if ($chmod !== null) {
// don't do anything chmod($file, $chmod & 0666);
} else {
$chmod = $chmod & 0666;
chmod($file, $chmod);
} }
} }
return $result; return $result;
@ -217,6 +216,16 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
{ {
$directory = $this->generateDirectoryPath($config); $directory = $this->generateDirectoryPath($config);
$chmod = $config->get('Cache.SerializerPermissions'); $chmod = $config->get('Cache.SerializerPermissions');
if ($chmod === null) {
if (!@mkdir($directory) && !is_dir($directory)) {
trigger_error(
'Could not create directory ' . $directory . '',
E_USER_WARNING
);
return false;
}
return true;
}
if (!is_dir($directory)) { if (!is_dir($directory)) {
$base = $this->generateBaseDirectoryPath($config); $base = $this->generateBaseDirectoryPath($config);
if (!is_dir($base)) { if (!is_dir($base)) {
@ -229,25 +238,14 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
} elseif (!$this->_testPermissions($base, $chmod)) { } elseif (!$this->_testPermissions($base, $chmod)) {
return false; return false;
} }
if ($chmod === null) { if (!@mkdir($directory, $chmod) && !is_dir($directory)) {
trigger_error( trigger_error(
'Base directory ' . $base . ' does not exist, 'Could not create directory ' . $directory . '',
please create or change using %Cache.SerializerPath',
E_USER_WARNING E_USER_WARNING
); );
return false; return false;
} }
if ($chmod !== null) {
mkdir($directory, $chmod);
} else {
mkdir($directory);
}
if (!$this->_testPermissions($directory, $chmod)) { if (!$this->_testPermissions($directory, $chmod)) {
trigger_error(
'Base directory ' . $base . ' does not exist,
please create or change using %Cache.SerializerPath',
E_USER_WARNING
);
return false; return false;
} }
} elseif (!$this->_testPermissions($directory, $chmod)) { } elseif (!$this->_testPermissions($directory, $chmod)) {

0
library/vendor/HTMLPurifier/DefinitionCache/Serializer/README vendored Normal file → Executable file
View File

View File

@ -101,6 +101,14 @@ class HTMLPurifier_Encoder
* It will parse according to UTF-8 and return a valid UTF8 string, with * It will parse according to UTF-8 and return a valid UTF8 string, with
* non-SGML codepoints excluded. * non-SGML codepoints excluded.
* *
* Specifically, it will permit:
* \x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}
* Source: https://www.w3.org/TR/REC-xml/#NT-Char
* Arguably this function should be modernized to the HTML5 set
* of allowed characters:
* https://www.w3.org/TR/html5/syntax.html#preprocessing-the-input-stream
* which simultaneously expand and restrict the set of allowed characters.
*
* @param string $str The string to clean * @param string $str The string to clean
* @param bool $force_php * @param bool $force_php
* @return string * @return string
@ -122,15 +130,12 @@ class HTMLPurifier_Encoder
* function that needs to be able to understand UTF-8 characters. * function that needs to be able to understand UTF-8 characters.
* As of right now, only smart lossless character encoding converters * As of right now, only smart lossless character encoding converters
* would need that, and I'm probably not going to implement them. * would need that, and I'm probably not going to implement them.
* Once again, PHP 6 should solve all our problems.
*/ */
public static function cleanUTF8($str, $force_php = false) public static function cleanUTF8($str, $force_php = false)
{ {
// UTF-8 validity is checked since PHP 4.3.5 // UTF-8 validity is checked since PHP 4.3.5
// This is an optimization: if the string is already valid UTF-8, no // This is an optimization: if the string is already valid UTF-8, no
// need to do PHP stuff. 99% of the time, this will be the case. // need to do PHP stuff. 99% of the time, this will be the case.
// The regexp matches the XML char production, as well as well as excluding
// non-SGML codepoints U+007F to U+009F
if (preg_match( if (preg_match(
'/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du', '/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du',
$str $str
@ -255,6 +260,7 @@ class HTMLPurifier_Encoder
// 7F-9F is not strictly prohibited by XML, // 7F-9F is not strictly prohibited by XML,
// but it is non-SGML, and thus we don't allow it // but it is non-SGML, and thus we don't allow it
(0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) || (0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) ||
(0xE000 <= $mUcs4 && 0xFFFD >= $mUcs4) ||
(0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4) (0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4)
) )
) { ) {

View File

@ -16,6 +16,138 @@ class HTMLPurifier_EntityParser
*/ */
protected $_entity_lookup; protected $_entity_lookup;
/**
* Callback regex string for entities in text.
* @type string
*/
protected $_textEntitiesRegex;
/**
* Callback regex string for entities in attributes.
* @type string
*/
protected $_attrEntitiesRegex;
/**
* Tests if the beginning of a string is a semi-optional regex
*/
protected $_semiOptionalPrefixRegex;
public function __construct() {
// From
// http://stackoverflow.com/questions/15532252/why-is-reg-being-rendered-as-without-the-bounding-semicolon
$semi_optional = "quot|QUOT|lt|LT|gt|GT|amp|AMP|AElig|Aacute|Acirc|Agrave|Aring|Atilde|Auml|COPY|Ccedil|ETH|Eacute|Ecirc|Egrave|Euml|Iacute|Icirc|Igrave|Iuml|Ntilde|Oacute|Ocirc|Ograve|Oslash|Otilde|Ouml|REG|THORN|Uacute|Ucirc|Ugrave|Uuml|Yacute|aacute|acirc|acute|aelig|agrave|aring|atilde|auml|brvbar|ccedil|cedil|cent|copy|curren|deg|divide|eacute|ecirc|egrave|eth|euml|frac12|frac14|frac34|iacute|icirc|iexcl|igrave|iquest|iuml|laquo|macr|micro|middot|nbsp|not|ntilde|oacute|ocirc|ograve|ordf|ordm|oslash|otilde|ouml|para|plusmn|pound|raquo|reg|sect|shy|sup1|sup2|sup3|szlig|thorn|times|uacute|ucirc|ugrave|uml|uuml|yacute|yen|yuml";
// NB: three empty captures to put the fourth match in the right
// place
$this->_semiOptionalPrefixRegex = "/&()()()($semi_optional)/";
$this->_textEntitiesRegex =
'/&(?:'.
// hex
'[#]x([a-fA-F0-9]+);?|'.
// dec
'[#]0*(\d+);?|'.
// string (mandatory semicolon)
// NB: order matters: match semicolon preferentially
'([A-Za-z_:][A-Za-z0-9.\-_:]*);|'.
// string (optional semicolon)
"($semi_optional)".
')/';
$this->_attrEntitiesRegex =
'/&(?:'.
// hex
'[#]x([a-fA-F0-9]+);?|'.
// dec
'[#]0*(\d+);?|'.
// string (mandatory semicolon)
// NB: order matters: match semicolon preferentially
'([A-Za-z_:][A-Za-z0-9.\-_:]*);|'.
// string (optional semicolon)
// don't match if trailing is equals or alphanumeric (URL
// like)
"($semi_optional)(?![=;A-Za-z0-9])".
')/';
}
/**
* Substitute entities with the parsed equivalents. Use this on
* textual data in an HTML document (as opposed to attributes.)
*
* @param string $string String to have entities parsed.
* @return string Parsed string.
*/
public function substituteTextEntities($string)
{
return preg_replace_callback(
$this->_textEntitiesRegex,
array($this, 'entityCallback'),
$string
);
}
/**
* Substitute entities with the parsed equivalents. Use this on
* attribute contents in documents.
*
* @param string $string String to have entities parsed.
* @return string Parsed string.
*/
public function substituteAttrEntities($string)
{
return preg_replace_callback(
$this->_attrEntitiesRegex,
array($this, 'entityCallback'),
$string
);
}
/**
* Callback function for substituteNonSpecialEntities() that does the work.
*
* @param array $matches PCRE matches array, with 0 the entire match, and
* either index 1, 2 or 3 set with a hex value, dec value,
* or string (respectively).
* @return string Replacement string.
*/
protected function entityCallback($matches)
{
$entity = $matches[0];
$hex_part = @$matches[1];
$dec_part = @$matches[2];
$named_part = empty($matches[3]) ? @$matches[4] : $matches[3];
if ($hex_part !== NULL && $hex_part !== "") {
return HTMLPurifier_Encoder::unichr(hexdec($hex_part));
} elseif ($dec_part !== NULL && $dec_part !== "") {
return HTMLPurifier_Encoder::unichr((int) $dec_part);
} else {
if (!$this->_entity_lookup) {
$this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
}
if (isset($this->_entity_lookup->table[$named_part])) {
return $this->_entity_lookup->table[$named_part];
} else {
// exact match didn't match anything, so test if
// any of the semicolon optional match the prefix.
// Test that this is an EXACT match is important to
// prevent infinite loop
if (!empty($matches[3])) {
return preg_replace_callback(
$this->_semiOptionalPrefixRegex,
array($this, 'entityCallback'),
$entity
);
}
return $entity;
}
}
}
// LEGACY CODE BELOW
/** /**
* Callback regex string for parsing entities. * Callback regex string for parsing entities.
* @type string * @type string
@ -144,7 +276,7 @@ class HTMLPurifier_EntityParser
$entity; $entity;
} else { } else {
return isset($this->_special_ent2dec[$matches[3]]) ? return isset($this->_special_ent2dec[$matches[3]]) ?
$this->_special_ent2dec[$matches[3]] : $this->_special_dec2str[$this->_special_ent2dec[$matches[3]]] :
$entity; $entity;
} }
} }

View File

@ -95,7 +95,10 @@ class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter
if ($tidy !== null) { if ($tidy !== null) {
$this->_tidy = $tidy; $this->_tidy = $tidy;
} }
$html = preg_replace_callback('#<style(?:\s.*)?>(.+)</style>#isU', array($this, 'styleCallback'), $html); // NB: this must be NON-greedy because if we have
// <style>foo</style> <style>bar</style>
// we must not grab foo</style> <style>bar
$html = preg_replace_callback('#<style(?:\s.*)?>(.*)<\/style>#isU', array($this, 'styleCallback'), $html);
$style_blocks = $this->_styleMatches; $style_blocks = $this->_styleMatches;
$this->_styleMatches = array(); // reset $this->_styleMatches = array(); // reset
$context->register('StyleBlocks', $style_blocks); // $context must not be reused $context->register('StyleBlocks', $style_blocks); // $context must not be reused

View File

@ -146,7 +146,7 @@ class HTMLPurifier_Generator
$attr = $this->generateAttributes($token->attr, $token->name); $attr = $this->generateAttributes($token->attr, $token->name);
if ($this->_flashCompat) { if ($this->_flashCompat) {
if ($token->name == "object") { if ($token->name == "object") {
$flash = new stdclass(); $flash = new stdClass();
$flash->attr = $token->attr; $flash->attr = $token->attr;
$flash->param = array(); $flash->param = array();
$this->_flashStack[] = $flash; $this->_flashStack[] = $flash;

View File

@ -0,0 +1,21 @@
<?php
/**
* Module adds the target-based noopener attribute transformation to a tags. It
* is enabled by HTML.TargetNoopener
*/
class HTMLPurifier_HTMLModule_TargetNoopener extends HTMLPurifier_HTMLModule
{
/**
* @type string
*/
public $name = 'TargetNoopener';
/**
* @param HTMLPurifier_Config $config
*/
public function setup($config) {
$a = $this->addBlankElement('a');
$a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetNoopener();
}
}

View File

@ -271,11 +271,14 @@ class HTMLPurifier_HTMLModuleManager
if ($config->get('HTML.TargetBlank')) { if ($config->get('HTML.TargetBlank')) {
$modules[] = 'TargetBlank'; $modules[] = 'TargetBlank';
} }
// NB: HTML.TargetNoreferrer must be AFTER HTML.TargetBlank // NB: HTML.TargetNoreferrer and HTML.TargetNoopener must be AFTER HTML.TargetBlank
// so that its post-attr-transform gets run afterwards. // so that its post-attr-transform gets run afterwards.
if ($config->get('HTML.TargetNoreferrer')) { if ($config->get('HTML.TargetNoreferrer')) {
$modules[] = 'TargetNoreferrer'; $modules[] = 'TargetNoreferrer';
} }
if ($config->get('HTML.TargetNoopener')) {
$modules[] = 'TargetNoopener';
}
// merge in custom modules // merge in custom modules
$modules = array_merge($modules, $this->userModules); $modules = array_merge($modules, $this->userModules);

View File

@ -157,6 +157,7 @@ abstract class HTMLPurifier_Injector
return false; return false;
} }
// check for exclusion // check for exclusion
if (!empty($this->currentNesting)) {
for ($i = count($this->currentNesting) - 2; $i >= 0; $i--) { for ($i = count($this->currentNesting) - 2; $i >= 0; $i--) {
$node = $this->currentNesting[$i]; $node = $this->currentNesting[$i];
$def = $this->htmlDefinition->info[$node->name]; $def = $this->htmlDefinition->info[$node->name];
@ -164,6 +165,7 @@ abstract class HTMLPurifier_Injector
return false; return false;
} }
} }
}
return true; return true;
} }

View File

@ -26,12 +26,14 @@ class HTMLPurifier_Length
protected $isValid; protected $isValid;
/** /**
* Array Lookup array of units recognized by CSS 2.1 * Array Lookup array of units recognized by CSS 3
* @type array * @type array
*/ */
protected static $allowedUnits = array( protected static $allowedUnits = array(
'em' => true, 'ex' => true, 'px' => true, 'in' => true, 'em' => true, 'ex' => true, 'px' => true, 'in' => true,
'cm' => true, 'mm' => true, 'pt' => true, 'pc' => true 'cm' => true, 'mm' => true, 'pt' => true, 'pc' => true,
'ch' => true, 'rem' => true, 'vw' => true, 'vh' => true,
'vmin' => true, 'vmax' => true
); );
/** /**

View File

@ -96,7 +96,7 @@ class HTMLPurifier_Lexer
break; break;
} }
if (class_exists('DOMDocument') && if (class_exists('DOMDocument', false) &&
method_exists('DOMDocument', 'loadHTML') && method_exists('DOMDocument', 'loadHTML') &&
!extension_loaded('domxml') !extension_loaded('domxml')
) { ) {
@ -169,21 +169,24 @@ class HTMLPurifier_Lexer
'&#x27;' => "'" '&#x27;' => "'"
); );
public function parseText($string, $config) {
return $this->parseData($string, false, $config);
}
public function parseAttr($string, $config) {
return $this->parseData($string, true, $config);
}
/** /**
* Parses special entities into the proper characters. * Parses special entities into the proper characters.
* *
* This string will translate escaped versions of the special characters * This string will translate escaped versions of the special characters
* into the correct ones. * into the correct ones.
* *
* @warning
* You should be able to treat the output of this function as
* completely parsed, but that's only because all other entities should
* have been handled previously in substituteNonSpecialEntities()
*
* @param string $string String character data to be parsed. * @param string $string String character data to be parsed.
* @return string Parsed character data. * @return string Parsed character data.
*/ */
public function parseData($string) public function parseData($string, $is_attr, $config)
{ {
// following functions require at least one character // following functions require at least one character
if ($string === '') { if ($string === '') {
@ -209,7 +212,15 @@ class HTMLPurifier_Lexer
} }
// hmm... now we have some uncommon entities. Use the callback. // hmm... now we have some uncommon entities. Use the callback.
if ($config->get('Core.LegacyEntityDecoder')) {
$string = $this->_entity_parser->substituteSpecialEntities($string); $string = $this->_entity_parser->substituteSpecialEntities($string);
} else {
if ($is_attr) {
$string = $this->_entity_parser->substituteAttrEntities($string);
} else {
$string = $this->_entity_parser->substituteTextEntities($string);
}
}
return $string; return $string;
} }
@ -323,7 +334,9 @@ class HTMLPurifier_Lexer
} }
// expand entities that aren't the big five // expand entities that aren't the big five
if ($config->get('Core.LegacyEntityDecoder')) {
$html = $this->_entity_parser->substituteNonSpecialEntities($html); $html = $this->_entity_parser->substituteNonSpecialEntities($html);
}
// clean into wellformed UTF-8 string for an SGML context: this has // clean into wellformed UTF-8 string for an SGML context: this has
// to be done after entity expansion because the entities sometimes // to be done after entity expansion because the entities sometimes
@ -335,6 +348,13 @@ class HTMLPurifier_Lexer
$html = preg_replace('#<\?.+?\?>#s', '', $html); $html = preg_replace('#<\?.+?\?>#s', '', $html);
} }
$hidden_elements = $config->get('Core.HiddenElements');
if ($config->get('Core.AggressivelyRemoveScript') &&
!($config->get('HTML.Trusted') || !$config->get('Core.RemoveScriptContents')
|| empty($hidden_elements["script"]))) {
$html = preg_replace('#<script[^>]*>.*?</script>#i', '', $html);
}
return $html; return $html;
} }

View File

@ -72,12 +72,20 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
$doc->loadHTML($html); $doc->loadHTML($html);
restore_error_handler(); restore_error_handler();
$body = $doc->getElementsByTagName('html')->item(0)-> // <html>
getElementsByTagName('body')->item(0); // <body>
$div = $body->getElementsByTagName('div')->item(0); // <div>
$tokens = array(); $tokens = array();
$this->tokenizeDOM( $this->tokenizeDOM($div, $tokens, $config);
$doc->getElementsByTagName('html')->item(0)-> // <html> // If the div has a sibling, that means we tripped across
getElementsByTagName('body')->item(0), // <body> // a premature </div> tag. So remove the div we parsed,
$tokens // and then tokenize the rest of body. We can't tokenize
); // the sibling directly as we'll lose the tags in that case.
if ($div->nextSibling) {
$body->removeChild($div);
$this->tokenizeDOM($body, $tokens, $config);
}
return $tokens; return $tokens;
} }
@ -88,7 +96,7 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
* @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens. * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens.
* @return HTMLPurifier_Token of node appended to previously passed tokens. * @return HTMLPurifier_Token of node appended to previously passed tokens.
*/ */
protected function tokenizeDOM($node, &$tokens) protected function tokenizeDOM($node, &$tokens, $config)
{ {
$level = 0; $level = 0;
$nodes = array($level => new HTMLPurifier_Queue(array($node))); $nodes = array($level => new HTMLPurifier_Queue(array($node)));
@ -97,7 +105,7 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
while (!$nodes[$level]->isEmpty()) { while (!$nodes[$level]->isEmpty()) {
$node = $nodes[$level]->shift(); // FIFO $node = $nodes[$level]->shift(); // FIFO
$collect = $level > 0 ? true : false; $collect = $level > 0 ? true : false;
$needEndingTag = $this->createStartNode($node, $tokens, $collect); $needEndingTag = $this->createStartNode($node, $tokens, $collect, $config);
if ($needEndingTag) { if ($needEndingTag) {
$closingNodes[$level][] = $node; $closingNodes[$level][] = $node;
} }
@ -118,6 +126,41 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
} while ($level > 0); } while ($level > 0);
} }
/**
* Portably retrieve the tag name of a node; deals with older versions
* of libxml like 2.7.6
* @param DOMNode $node
*/
protected function getTagName($node)
{
if (property_exists($node, 'tagName')) {
return $node->tagName;
} else if (property_exists($node, 'nodeName')) {
return $node->nodeName;
} else if (property_exists($node, 'localName')) {
return $node->localName;
}
return null;
}
/**
* Portably retrieve the data of a node; deals with older versions
* of libxml like 2.7.6
* @param DOMNode $node
*/
protected function getData($node)
{
if (property_exists($node, 'data')) {
return $node->data;
} else if (property_exists($node, 'nodeValue')) {
return $node->nodeValue;
} else if (property_exists($node, 'textContent')) {
return $node->textContent;
}
return null;
}
/** /**
* @param DOMNode $node DOMNode to be tokenized. * @param DOMNode $node DOMNode to be tokenized.
* @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens. * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens.
@ -127,13 +170,16 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
* @return bool if the token needs an endtoken * @return bool if the token needs an endtoken
* @todo data and tagName properties don't seem to exist in DOMNode? * @todo data and tagName properties don't seem to exist in DOMNode?
*/ */
protected function createStartNode($node, &$tokens, $collect) protected function createStartNode($node, &$tokens, $collect, $config)
{ {
// intercept non element nodes. WE MUST catch all of them, // intercept non element nodes. WE MUST catch all of them,
// but we're not getting the character reference nodes because // but we're not getting the character reference nodes because
// those should have been preprocessed // those should have been preprocessed
if ($node->nodeType === XML_TEXT_NODE) { if ($node->nodeType === XML_TEXT_NODE) {
$tokens[] = $this->factory->createText($node->data); $data = $this->getData($node); // Handle variable data property
if ($data !== null) {
$tokens[] = $this->factory->createText($data);
}
return false; return false;
} elseif ($node->nodeType === XML_CDATA_SECTION_NODE) { } elseif ($node->nodeType === XML_CDATA_SECTION_NODE) {
// undo libxml's special treatment of <script> and <style> tags // undo libxml's special treatment of <script> and <style> tags
@ -151,7 +197,7 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
} }
} }
} }
$tokens[] = $this->factory->createText($this->parseData($data)); $tokens[] = $this->factory->createText($this->parseText($data, $config));
return false; return false;
} elseif ($node->nodeType === XML_COMMENT_NODE) { } elseif ($node->nodeType === XML_COMMENT_NODE) {
// this is code is only invoked for comments in script/style in versions // this is code is only invoked for comments in script/style in versions
@ -163,21 +209,20 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
// not-well tested: there may be other nodes we have to grab // not-well tested: there may be other nodes we have to grab
return false; return false;
} }
$attr = $node->hasAttributes() ? $this->transformAttrToAssoc($node->attributes) : array(); $attr = $node->hasAttributes() ? $this->transformAttrToAssoc($node->attributes) : array();
$tag_name = $this->getTagName($node); // Handle variable tagName property
if (empty($tag_name)) {
return (bool) $node->childNodes->length;
}
// We still have to make sure that the element actually IS empty // We still have to make sure that the element actually IS empty
if (!$node->childNodes->length) { if (!$node->childNodes->length) {
if ($collect) { if ($collect) {
$tokens[] = $this->factory->createEmpty($node->tagName, $attr); $tokens[] = $this->factory->createEmpty($tag_name, $attr);
} }
return false; return false;
} else { } else {
if ($collect) { if ($collect) {
$tokens[] = $this->factory->createStart( $tokens[] = $this->factory->createStart($tag_name, $attr);
$tag_name = $node->tagName, // somehow, it get's dropped
$attr
);
} }
return true; return true;
} }
@ -189,10 +234,10 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
*/ */
protected function createEndNode($node, &$tokens) protected function createEndNode($node, &$tokens)
{ {
$tokens[] = $this->factory->createEnd($node->tagName); $tag_name = $this->getTagName($node); // Handle variable tagName property
$tokens[] = $this->factory->createEnd($tag_name);
} }
/** /**
* Converts a DOMNamedNodeMap of DOMAttr objects into an assoc array. * Converts a DOMNamedNodeMap of DOMAttr objects into an assoc array.
* *
@ -252,7 +297,7 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
* @param HTMLPurifier_Context $context * @param HTMLPurifier_Context $context
* @return string * @return string
*/ */
protected function wrapHTML($html, $config, $context) protected function wrapHTML($html, $config, $context, $use_div = true)
{ {
$def = $config->getDefinition('HTML'); $def = $config->getDefinition('HTML');
$ret = ''; $ret = '';
@ -271,7 +316,11 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
$ret .= '<html><head>'; $ret .= '<html><head>';
$ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />'; $ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
// No protection if $html contains a stray </div>! // No protection if $html contains a stray </div>!
$ret .= '</head><body>' . $html . '</body></html>'; $ret .= '</head><body>';
if ($use_div) $ret .= '<div>';
$ret .= $html;
if ($use_div) $ret .= '</div>';
$ret .= '</body></html>';
return $ret; return $ret;
} }
} }

View File

@ -129,12 +129,12 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
// We are not inside tag and there still is another tag to parse // We are not inside tag and there still is another tag to parse
$token = new $token = new
HTMLPurifier_Token_Text( HTMLPurifier_Token_Text(
$this->parseData( $this->parseText(
substr( substr(
$html, $html,
$cursor, $cursor,
$position_next_lt - $cursor $position_next_lt - $cursor
) ), $config
) )
); );
if ($maintain_line_numbers) { if ($maintain_line_numbers) {
@ -154,11 +154,11 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
// Create Text of rest of string // Create Text of rest of string
$token = new $token = new
HTMLPurifier_Token_Text( HTMLPurifier_Token_Text(
$this->parseData( $this->parseText(
substr( substr(
$html, $html,
$cursor $cursor
) ), $config
) )
); );
if ($maintain_line_numbers) { if ($maintain_line_numbers) {
@ -324,8 +324,8 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
$token = new $token = new
HTMLPurifier_Token_Text( HTMLPurifier_Token_Text(
'<' . '<' .
$this->parseData( $this->parseText(
substr($html, $cursor) substr($html, $cursor), $config
) )
); );
if ($maintain_line_numbers) { if ($maintain_line_numbers) {
@ -429,7 +429,7 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
if ($value === false) { if ($value === false) {
$value = ''; $value = '';
} }
return array($key => $this->parseData($value)); return array($key => $this->parseAttr($value, $config));
} }
// setup loop environment // setup loop environment
@ -518,7 +518,7 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
if ($value === false) { if ($value === false) {
$value = ''; $value = '';
} }
$array[$key] = $this->parseData($value); $array[$key] = $this->parseAttr($value, $config);
$cursor++; $cursor++;
} else { } else {
// boolattr // boolattr

View File

@ -21,7 +21,7 @@ class HTMLPurifier_Lexer_PH5P extends HTMLPurifier_Lexer_DOMLex
public function tokenizeHTML($html, $config, $context) public function tokenizeHTML($html, $config, $context)
{ {
$new_html = $this->normalize($html, $config, $context); $new_html = $this->normalize($html, $config, $context);
$new_html = $this->wrapHTML($new_html, $config, $context); $new_html = $this->wrapHTML($new_html, $config, $context, false /* no div */);
try { try {
$parser = new HTML5($new_html); $parser = new HTML5($new_html);
$doc = $parser->save(); $doc = $parser->save();
@ -36,7 +36,7 @@ class HTMLPurifier_Lexer_PH5P extends HTMLPurifier_Lexer_DOMLex
$doc->getElementsByTagName('html')->item(0)-> // <html> $doc->getElementsByTagName('html')->item(0)-> // <html>
getElementsByTagName('body')->item(0) // <body> getElementsByTagName('body')->item(0) // <body>
, ,
$tokens $tokens, $config
); );
return $tokens; return $tokens;
} }
@ -1507,7 +1507,7 @@ class HTML5
$entity = $this->character($start, $this->char); $entity = $this->character($start, $this->char);
$cond = strlen($e_name) > 0; $cond = strlen($e_name) > 0;
// The rest of the parsing happens bellow. // The rest of the parsing happens below.
break; break;
// Anything else // Anything else
@ -1515,6 +1515,7 @@ class HTML5
// Consume the maximum number of characters possible, with the // Consume the maximum number of characters possible, with the
// consumed characters case-sensitively matching one of the // consumed characters case-sensitively matching one of the
// identifiers in the first column of the entities table. // identifiers in the first column of the entities table.
$e_name = $this->characters('0-9A-Za-z;', $this->char + 1); $e_name = $this->characters('0-9A-Za-z;', $this->char + 1);
$len = strlen($e_name); $len = strlen($e_name);
@ -1534,7 +1535,7 @@ class HTML5
} }
$cond = isset($entity); $cond = isset($entity);
// The rest of the parsing happens bellow. // The rest of the parsing happens below.
break; break;
} }
@ -1547,7 +1548,7 @@ class HTML5
// Return a character token for the character corresponding to the // Return a character token for the character corresponding to the
// entity name (as given by the second column of the entities table). // entity name (as given by the second column of the entities table).
return html_entity_decode('&' . $entity . ';', ENT_QUOTES, 'UTF-8'); return html_entity_decode('&' . rtrim($entity, ';') . ';', ENT_QUOTES, 'UTF-8');
} }
private function emitToken($token) private function emitToken($token)

View File

@ -1,10 +1,10 @@
GLOBIGNORE=$0; rm -rf * GLOBIGNORE=$0; rm -rf *
rm ../HTMLPurifier*.php rm ../HTMLPurifier*.php
curl https://codeload.github.com/ezyang/htmlpurifier/tar.gz/v4.8.0 -o htmlpurifier-4.8.0.tar.gz curl https://codeload.github.com/ezyang/htmlpurifier/tar.gz/v4.10.0 -o htmlpurifier-4.10.0.tar.gz
tar xzf htmlpurifier-4.8.0.tar.gz --strip-components 1 htmlpurifier-4.8.0/LICENSE tar xzf htmlpurifier-4.10.0.tar.gz --strip-components 1 htmlpurifier-4.10.0/LICENSE
tar xzf htmlpurifier-4.8.0.tar.gz --strip-components 1 htmlpurifier-4.8.0/VERSION tar xzf htmlpurifier-4.10.0.tar.gz --strip-components 1 htmlpurifier-4.10.0/VERSION
tar xzf htmlpurifier-4.8.0.tar.gz -C ../ --strip-components 2 htmlpurifier-4.8.0/library/HTMLPurifier.php tar xzf htmlpurifier-4.10.0.tar.gz -C ../ --strip-components 2 htmlpurifier-4.10.0/library/HTMLPurifier.php
tar xzf htmlpurifier-4.8.0.tar.gz -C ../ --strip-components 2 htmlpurifier-4.8.0/library/HTMLPurifier.autoload.php tar xzf htmlpurifier-4.10.0.tar.gz -C ../ --strip-components 2 htmlpurifier-4.10.0/library/HTMLPurifier.autoload.php
tar xzf htmlpurifier-4.8.0.tar.gz --strip-components 3 htmlpurifier-4.8.0/library/HTMLPurifier/* tar xzf htmlpurifier-4.10.0.tar.gz --strip-components 3 htmlpurifier-4.10.0/library/HTMLPurifier/*
rm htmlpurifier-4.8.0.tar.gz rm htmlpurifier-4.10.0.tar.gz

View File

@ -165,7 +165,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
if (empty($zipper->front)) break; if (empty($zipper->front)) break;
$token = $zipper->prev($token); $token = $zipper->prev($token);
// indicate that other injectors should not process this token, // indicate that other injectors should not process this token,
// but we need to reprocess it // but we need to reprocess it. See Note [Injector skips]
unset($token->skip[$i]); unset($token->skip[$i]);
$token->rewind = $i; $token->rewind = $i;
if ($token instanceof HTMLPurifier_Token_Start) { if ($token instanceof HTMLPurifier_Token_Start) {
@ -210,6 +210,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
if ($token instanceof HTMLPurifier_Token_Text) { if ($token instanceof HTMLPurifier_Token_Text) {
foreach ($this->injectors as $i => $injector) { foreach ($this->injectors as $i => $injector) {
if (isset($token->skip[$i])) { if (isset($token->skip[$i])) {
// See Note [Injector skips]
continue; continue;
} }
if ($token->rewind !== null && $token->rewind !== $i) { if ($token->rewind !== null && $token->rewind !== $i) {
@ -367,6 +368,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
if ($ok) { if ($ok) {
foreach ($this->injectors as $i => $injector) { foreach ($this->injectors as $i => $injector) {
if (isset($token->skip[$i])) { if (isset($token->skip[$i])) {
// See Note [Injector skips]
continue; continue;
} }
if ($token->rewind !== null && $token->rewind !== $i) { if ($token->rewind !== null && $token->rewind !== $i) {
@ -422,6 +424,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
$token->start = $current_parent; $token->start = $current_parent;
foreach ($this->injectors as $i => $injector) { foreach ($this->injectors as $i => $injector) {
if (isset($token->skip[$i])) { if (isset($token->skip[$i])) {
// See Note [Injector skips]
continue; continue;
} }
if ($token->rewind !== null && $token->rewind !== $i) { if ($token->rewind !== null && $token->rewind !== $i) {
@ -534,12 +537,17 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
*/ */
protected function processToken($token, $injector = -1) protected function processToken($token, $injector = -1)
{ {
// Zend OpCache miscompiles $token = array($token), so
// avoid this pattern. See: https://github.com/ezyang/htmlpurifier/issues/108
// normalize forms of token // normalize forms of token
if (is_object($token)) { if (is_object($token)) {
$token = array(1, $token); $tmp = $token;
$token = array(1, $tmp);
} }
if (is_int($token)) { if (is_int($token)) {
$token = array($token); $tmp = $token;
$token = array($tmp);
} }
if ($token === false) { if ($token === false) {
$token = array(1); $token = array(1);
@ -561,7 +569,12 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
list($old, $r) = $this->zipper->splice($this->token, $delete, $token); list($old, $r) = $this->zipper->splice($this->token, $delete, $token);
if ($injector > -1) { if ($injector > -1) {
// determine appropriate skips // See Note [Injector skips]
// Determine appropriate skips. Here's what the code does:
// *If* we deleted one or more tokens, copy the skips
// of those tokens into the skips of the new tokens (in $token).
// Also, mark the newly inserted tokens as having come from
// $injector.
$oldskip = isset($old[0]) ? $old[0]->skip : array(); $oldskip = isset($old[0]) ? $old[0]->skip : array();
foreach ($token as $object) { foreach ($token as $object) {
$object->skip = $oldskip; $object->skip = $oldskip;
@ -597,4 +610,50 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
} }
} }
// Note [Injector skips]
// ~~~~~~~~~~~~~~~~~~~~~
// When I originally designed this class, the idea behind the 'skip'
// property of HTMLPurifier_Token was to help avoid infinite loops
// in injector processing. For example, suppose you wrote an injector
// that bolded swear words. Naively, you might write it so that
// whenever you saw ****, you replaced it with <strong>****</strong>.
//
// When this happens, we will reprocess all of the tokens with the
// other injectors. Now there is an opportunity for infinite loop:
// if we rerun the swear-word injector on these tokens, we might
// see **** and then reprocess again to get
// <strong><strong>****</strong></strong> ad infinitum.
//
// Thus, the idea of a skip is that once we process a token with
// an injector, we mark all of those tokens as having "come from"
// the injector, and we never run the injector again on these
// tokens.
//
// There were two more complications, however:
//
// - With HTMLPurifier_Injector_RemoveEmpty, we noticed that if
// you had <b><i></i></b>, after you removed the <i></i>, you
// really would like this injector to go back and reprocess
// the <b> tag, discovering that it is now empty and can be
// removed. So we reintroduced the possibility of infinite looping
// by adding a "rewind" function, which let you go back to an
// earlier point in the token stream and reprocess it with injectors.
// Needless to say, we need to UN-skip the token so it gets
// reprocessed.
//
// - Suppose that you successfuly process a token, replace it with
// one with your skip mark, but now another injector wants to
// process the skipped token with another token. Should you continue
// to skip that new token, or reprocess it? If you reprocess,
// you can end up with an infinite loop where one injector converts
// <a> to <b>, and then another injector converts it back. So
// we inherit the skips, but for some reason, I thought that we
// should inherit the skip from the first token of the token
// that we deleted. Why? Well, it seems to work OK.
//
// If I were to redesign this functionality, I would absolutely not
// go about doing it this way: the semantics are just not very well
// defined, and in any case you probably wanted to operate on trees,
// not token streams.
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -26,7 +26,7 @@ abstract class HTMLPurifier_Token
public $armor = array(); public $armor = array();
/** /**
* Used during MakeWellFormed. * Used during MakeWellFormed. See Note [Injector skips]
* @type * @type
*/ */
public $skip; public $skip;

View File

@ -85,11 +85,13 @@ class HTMLPurifier_URI
$def = $config->getDefinition('URI'); $def = $config->getDefinition('URI');
$scheme_obj = $def->getDefaultScheme($config, $context); $scheme_obj = $def->getDefaultScheme($config, $context);
if (!$scheme_obj) { if (!$scheme_obj) {
if ($def->defaultScheme !== null) {
// something funky happened to the default scheme object // something funky happened to the default scheme object
trigger_error( trigger_error(
'Default scheme object "' . $def->defaultScheme . '" was not readable', 'Default scheme object "' . $def->defaultScheme . '" was not readable',
E_USER_WARNING E_USER_WARNING
); );
} // suppress error if it's null
return false; return false;
} }
} }

View File

@ -1 +1 @@
4.8.0 4.10.0

View File

@ -74,6 +74,11 @@ class Minifier
*/ */
protected $options; protected $options;
/**
* These characters are used to define strings.
*/
protected $stringDelimiters = ['\'', '"', '`'];
/** /**
* Contains the default options for minification. This array is merged with * Contains the default options for minification. This array is merged with
* the one passed in by the user to create the request specific set of * the one passed in by the user to create the request specific set of
@ -115,9 +120,7 @@ class Minifier
unset($jshrink); unset($jshrink);
return $js; return $js;
} catch (\Exception $e) { } catch (\Exception $e) {
if (isset($jshrink)) { if (isset($jshrink)) {
// Since the breakdownScript function probably wasn't finished // Since the breakdownScript function probably wasn't finished
// we clean it out before discarding it. // we clean it out before discarding it.
@ -176,12 +179,11 @@ class Minifier
protected function loop() protected function loop()
{ {
while ($this->a !== false && !is_null($this->a) && $this->a !== '') { while ($this->a !== false && !is_null($this->a) && $this->a !== '') {
switch ($this->a) { switch ($this->a) {
// new lines // new lines
case "\n": case "\n":
// if the next line is something that can't stand alone preserve the newline // if the next line is something that can't stand alone preserve the newline
if (strpos('(-+{[@', $this->b) !== false) { if (strpos('(-+[@', $this->b) !== false) {
echo $this->a; echo $this->a;
$this->saveString(); $this->saveString();
break; break;
@ -189,14 +191,17 @@ class Minifier
// if B is a space we skip the rest of the switch block and go down to the // if B is a space we skip the rest of the switch block and go down to the
// string/regex check below, resetting $this->b with getReal // string/regex check below, resetting $this->b with getReal
if($this->b === ' ') if ($this->b === ' ') {
break; break;
}
// otherwise we treat the newline like a space // otherwise we treat the newline like a space
// no break
case ' ': case ' ':
if(static::isAlphaNumeric($this->b)) if (static::isAlphaNumeric($this->b)) {
echo $this->a; echo $this->a;
}
$this->saveString(); $this->saveString();
break; break;
@ -217,9 +222,11 @@ class Minifier
break; break;
case ' ': case ' ':
if(!static::isAlphaNumeric($this->a)) if (!static::isAlphaNumeric($this->a)) {
break; break;
}
// no break
default: default:
// check for some regex that breaks stuff // check for some regex that breaks stuff
if ($this->a === '/' && ($this->b === '\'' || $this->b === '"')) { if ($this->a === '/' && ($this->b === '\'' || $this->b === '"')) {
@ -236,10 +243,11 @@ class Minifier
// do reg check of doom // do reg check of doom
$this->b = $this->getReal(); $this->b = $this->getReal();
if(($this->b == '/' && strpos('(,=:[!&|?', $this->a) !== false)) if (($this->b == '/' && strpos('(,=:[!&|?', $this->a) !== false)) {
$this->saveRegex(); $this->saveRegex();
} }
} }
}
/** /**
* Resets attributes that do not need to be stored between requests so that * Resets attributes that do not need to be stored between requests so that
@ -282,9 +290,9 @@ class Minifier
// Normalize all whitespace except for the newline character into a // Normalize all whitespace except for the newline character into a
// standard space. // standard space.
if($char !== "\n" && ord($char) < 32) if ($char !== "\n" && ord($char) < 32) {
return ' '; return ' ';
}
return $char; return $char;
} }
@ -312,10 +320,13 @@ class Minifier
$this->c = $this->getChar(); $this->c = $this->getChar();
if ($this->c === '/') { if ($this->c === '/') {
return $this->processOneLineComments($startIndex); $this->processOneLineComments($startIndex);
return $this->getReal();
} elseif ($this->c === '*') { } elseif ($this->c === '*') {
return $this->processMultiLineComments($startIndex); $this->processMultiLineComments($startIndex);
return $this->getReal();
} }
return $char; return $char;
@ -326,7 +337,7 @@ class Minifier
* conditional comments. * conditional comments.
* *
* @param int $startIndex The index point where "getReal" function started * @param int $startIndex The index point where "getReal" function started
* @return string * @return void
*/ */
protected function processOneLineComments($startIndex) protected function processOneLineComments($startIndex)
{ {
@ -335,17 +346,12 @@ class Minifier
// kill rest of line // kill rest of line
$this->getNext("\n"); $this->getNext("\n");
unset($this->c);
if ($thirdCommentString == '@') { if ($thirdCommentString == '@') {
$endPoint = $this->index - $startIndex; $endPoint = $this->index - $startIndex;
unset($this->c); $this->c = "\n" . substr($this->input, $startIndex, $endPoint);
$char = "\n" . substr($this->input, $startIndex, $endPoint);
} else {
// first one is contents of $this->c
$this->getChar();
$char = $this->getChar();
} }
return $char;
} }
/** /**
@ -353,7 +359,7 @@ class Minifier
* Conditional comments and "license" style blocks are preserved. * Conditional comments and "license" style blocks are preserved.
* *
* @param int $startIndex The index point where "getReal" function started * @param int $startIndex The index point where "getReal" function started
* @return bool|string False if there's no character * @return void
* @throws \RuntimeException Unclosed comments will throw an error * @throws \RuntimeException Unclosed comments will throw an error
*/ */
protected function processMultiLineComments($startIndex) protected function processMultiLineComments($startIndex)
@ -363,7 +369,6 @@ class Minifier
// kill everything up to the next */ if it's there // kill everything up to the next */ if it's there
if ($this->getNext('*/')) { if ($this->getNext('*/')) {
$this->getChar(); // get * $this->getChar(); // get *
$this->getChar(); // get / $this->getChar(); // get /
$char = $this->getChar(); // get next real character $char = $this->getChar(); // get next real character
@ -387,21 +392,20 @@ class Minifier
$endPoint = ($this->index - 1) - $startIndex; $endPoint = ($this->index - 1) - $startIndex;
echo substr($this->input, $startIndex, $endPoint); echo substr($this->input, $startIndex, $endPoint);
return $char; $this->c = $char;
}
return;
}
} else { } else {
$char = false; $char = false;
} }
if($char === false) if ($char === false) {
throw new \RuntimeException('Unclosed multiline comment at position: ' . ($this->index - 2)); throw new \RuntimeException('Unclosed multiline comment at position: ' . ($this->index - 2));
}
// if we're here c is part of the comment and therefore tossed // if we're here c is part of the comment and therefore tossed
if(isset($this->c)) $this->c = $char;
unset($this->c);
return $char;
} }
/** /**
@ -418,9 +422,9 @@ class Minifier
$pos = strpos($this->input, $string, $this->index); $pos = strpos($this->input, $string, $this->index);
// If it's not there return false. // If it's not there return false.
if($pos === false) if ($pos === false) {
return false; return false;
}
// Adjust position of index to jump ahead to the asked for string // Adjust position of index to jump ahead to the asked for string
$this->index = $pos; $this->index = $pos;
@ -444,7 +448,7 @@ class Minifier
$this->a = $this->b; $this->a = $this->b;
// If this isn't a string we don't need to do anything. // If this isn't a string we don't need to do anything.
if ($this->a !== "'" && $this->a !== '"') { if (!in_array($this->a, $this->stringDelimiters)) {
return; return;
} }
@ -473,7 +477,11 @@ class Minifier
// character, so those will be treated just fine using the switch // character, so those will be treated just fine using the switch
// block below. // block below.
case "\n": case "\n":
if ($stringType === '`') {
echo $this->a;
} else {
throw new \RuntimeException('Unclosed string at position: ' . $startpos); throw new \RuntimeException('Unclosed string at position: ' . $startpos);
}
break; break;
// Escaped characters get picked up here. If it's an escaped new line it's not really needed // Escaped characters get picked up here. If it's an escaped new line it's not really needed
@ -513,16 +521,18 @@ class Minifier
echo $this->a . $this->b; echo $this->a . $this->b;
while (($this->a = $this->getChar()) !== false) { while (($this->a = $this->getChar()) !== false) {
if($this->a === '/') if ($this->a === '/') {
break; break;
}
if ($this->a === '\\') { if ($this->a === '\\') {
echo $this->a; echo $this->a;
$this->a = $this->getChar(); $this->a = $this->getChar();
} }
if($this->a === "\n") if ($this->a === "\n") {
throw new \RuntimeException('Unclosed regex pattern at position: ' . $this->index); throw new \RuntimeException('Unclosed regex pattern at position: ' . $this->index);
}
echo $this->a; echo $this->a;
} }
@ -583,5 +593,4 @@ class Minifier
return $js; return $js;
} }
} }

View File

@ -1,4 +1,4 @@
curl https://codeload.github.com/tedious/JShrink/tar.gz/v1.1.0 -o JShrink-1.1.0.tar.gz curl https://codeload.github.com/tedious/JShrink/tar.gz/v1.3.0 -o JShrink-1.3.0.tar.gz
tar xzf JShrink-1.1.0.tar.gz --strip-components 1 JShrink-1.1.0/LICENSE tar xzf JShrink-1.3.0.tar.gz --strip-components 1 JShrink-1.3.0/LICENSE
tar xzf JShrink-1.1.0.tar.gz --strip-components 3 JShrink-1.1.0/src/JShrink/Minifier.php tar xzf JShrink-1.3.0.tar.gz --strip-components 3 JShrink-1.3.0/src/JShrink/Minifier.php
rm JShrink-1.1.0.tar.gz rm JShrink-1.3.0.tar.gz

View File

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2013 Emanuil Rusev, erusev.com Copyright (c) 2013-2018 Emanuil Rusev, erusev.com
Permission is hereby granted, free of charge, to any person obtaining a copy of Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in this software and associated documentation files (the "Software"), to deal in

View File

@ -17,7 +17,7 @@ class Parsedown
{ {
# ~ # ~
const version = '1.6.0'; const version = '1.7.1';
# ~ # ~
@ -75,6 +75,32 @@ class Parsedown
protected $urlsLinked = true; protected $urlsLinked = true;
function setSafeMode($safeMode)
{
$this->safeMode = (bool) $safeMode;
return $this;
}
protected $safeMode;
protected $safeLinksWhitelist = array(
'http://',
'https://',
'ftp://',
'ftps://',
'mailto:',
'data:image/png;base64,',
'data:image/gif;base64,',
'data:image/jpeg;base64,',
'irc:',
'ircs:',
'git:',
'ssh:',
'news:',
'steam:',
);
# #
# Lines # Lines
# #
@ -115,7 +141,7 @@ class Parsedown
# Blocks # Blocks
# #
private function lines(array $lines) protected function lines(array $lines)
{ {
$CurrentBlock = null; $CurrentBlock = null;
@ -175,7 +201,7 @@ class Parsedown
} }
else else
{ {
if (method_exists($this, 'block'.$CurrentBlock['type'].'Complete')) if ($this->isBlockCompletable($CurrentBlock['type']))
{ {
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
} }
@ -216,7 +242,7 @@ class Parsedown
$Block['identified'] = true; $Block['identified'] = true;
} }
if (method_exists($this, 'block'.$blockType.'Continue')) if ($this->isBlockContinuable($blockType))
{ {
$Block['continuable'] = true; $Block['continuable'] = true;
} }
@ -245,7 +271,7 @@ class Parsedown
# ~ # ~
if (isset($CurrentBlock['continuable']) and method_exists($this, 'block'.$CurrentBlock['type'].'Complete')) if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
{ {
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
} }
@ -278,6 +304,16 @@ class Parsedown
return $markup; return $markup;
} }
protected function isBlockContinuable($Type)
{
return method_exists($this, 'block'.$Type.'Continue');
}
protected function isBlockCompletable($Type)
{
return method_exists($this, 'block'.$Type.'Complete');
}
# #
# Code # Code
@ -332,8 +368,6 @@ class Parsedown
{ {
$text = $Block['element']['text']['text']; $text = $Block['element']['text']['text'];
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
$Block['element']['text']['text'] = $text; $Block['element']['text']['text'] = $text;
return $Block; return $Block;
@ -344,7 +378,7 @@ class Parsedown
protected function blockComment($Line) protected function blockComment($Line)
{ {
if ($this->markupEscaped) if ($this->markupEscaped or $this->safeMode)
{ {
return; return;
} }
@ -386,7 +420,7 @@ class Parsedown
protected function blockFencedCode($Line) protected function blockFencedCode($Line)
{ {
if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches)) if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches))
{ {
$Element = array( $Element = array(
'name' => 'code', 'name' => 'code',
@ -438,7 +472,7 @@ class Parsedown
return $Block; return $Block;
} }
$Block['element']['text']['text'] .= "\n".$Line['body'];; $Block['element']['text']['text'] .= "\n".$Line['body'];
return $Block; return $Block;
} }
@ -447,8 +481,6 @@ class Parsedown
{ {
$text = $Block['element']['text']['text']; $text = $Block['element']['text']['text'];
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
$Block['element']['text']['text'] = $text; $Block['element']['text']['text'] = $text;
return $Block; return $Block;
@ -505,6 +537,16 @@ class Parsedown
), ),
); );
if($name === 'ol')
{
$listStart = stristr($matches[0], '.', true);
if($listStart !== '1')
{
$Block['element']['attributes'] = array('start' => $listStart);
}
}
$Block['li'] = array( $Block['li'] = array(
'name' => 'li', 'name' => 'li',
'handler' => 'li', 'handler' => 'li',
@ -527,6 +569,8 @@ class Parsedown
{ {
$Block['li']['text'] []= ''; $Block['li']['text'] []= '';
$Block['loose'] = true;
unset($Block['interrupted']); unset($Block['interrupted']);
} }
@ -575,6 +619,22 @@ class Parsedown
} }
} }
protected function blockListComplete(array $Block)
{
if (isset($Block['loose']))
{
foreach ($Block['element']['text'] as &$li)
{
if (end($li['text']) !== '')
{
$li['text'] []= '';
}
}
}
return $Block;
}
# #
# Quote # Quote
@ -658,12 +718,12 @@ class Parsedown
protected function blockMarkup($Line) protected function blockMarkup($Line)
{ {
if ($this->markupEscaped) if ($this->markupEscaped or $this->safeMode)
{ {
return; return;
} }
if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
{ {
$element = strtolower($matches[1]); $element = strtolower($matches[1]);
@ -977,7 +1037,7 @@ class Parsedown
# ~ # ~
# #
public function line($text) public function line($text, $nonNestables=array())
{ {
$markup = ''; $markup = '';
@ -993,6 +1053,13 @@ class Parsedown
foreach ($this->InlineTypes[$marker] as $inlineType) foreach ($this->InlineTypes[$marker] as $inlineType)
{ {
# check to see if the current inline type is nestable in the current context
if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables))
{
continue;
}
$Inline = $this->{'inline'.$inlineType}($Excerpt); $Inline = $this->{'inline'.$inlineType}($Excerpt);
if ( ! isset($Inline)) if ( ! isset($Inline))
@ -1014,6 +1081,13 @@ class Parsedown
$Inline['position'] = $markerPosition; $Inline['position'] = $markerPosition;
} }
# cause the new element to 'inherit' our non nestables
foreach ($nonNestables as $non_nestable)
{
$Inline['element']['nonNestables'][] = $non_nestable;
}
# the text that comes before the inline # the text that comes before the inline
$unmarkedText = substr($text, 0, $Inline['position']); $unmarkedText = substr($text, 0, $Inline['position']);
@ -1054,7 +1128,6 @@ class Parsedown
if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches)) if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
{ {
$text = $matches[2]; $text = $matches[2];
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
$text = preg_replace("/[ ]*\n/", ' ', $text); $text = preg_replace("/[ ]*\n/", ' ', $text);
return array( return array(
@ -1173,6 +1246,7 @@ class Parsedown
$Element = array( $Element = array(
'name' => 'a', 'name' => 'a',
'handler' => 'line', 'handler' => 'line',
'nonNestables' => array('Url', 'Link'),
'text' => null, 'text' => null,
'attributes' => array( 'attributes' => array(
'href' => null, 'href' => null,
@ -1184,7 +1258,7 @@ class Parsedown
$remainder = $Excerpt['text']; $remainder = $Excerpt['text'];
if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches)) if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
{ {
$Element['text'] = $matches[1]; $Element['text'] = $matches[1];
@ -1197,7 +1271,7 @@ class Parsedown
return; return;
} }
if (preg_match('/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches)) if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
{ {
$Element['attributes']['href'] = $matches[1]; $Element['attributes']['href'] = $matches[1];
@ -1233,8 +1307,6 @@ class Parsedown
$Element['attributes']['title'] = $Definition['title']; $Element['attributes']['title'] = $Definition['title'];
} }
$Element['attributes']['href'] = str_replace(array('&', '<'), array('&amp;', '&lt;'), $Element['attributes']['href']);
return array( return array(
'extent' => $extent, 'extent' => $extent,
'element' => $Element, 'element' => $Element,
@ -1243,12 +1315,12 @@ class Parsedown
protected function inlineMarkup($Excerpt) protected function inlineMarkup($Excerpt)
{ {
if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false) if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
{ {
return; return;
} }
if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches)) if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches))
{ {
return array( return array(
'markup' => $matches[0], 'markup' => $matches[0],
@ -1264,7 +1336,7 @@ class Parsedown
); );
} }
if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches)) if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
{ {
return array( return array(
'markup' => $matches[0], 'markup' => $matches[0],
@ -1323,14 +1395,16 @@ class Parsedown
if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
{ {
$url = $matches[0][0];
$Inline = array( $Inline = array(
'extent' => strlen($matches[0][0]), 'extent' => strlen($matches[0][0]),
'position' => $matches[0][1], 'position' => $matches[0][1],
'element' => array( 'element' => array(
'name' => 'a', 'name' => 'a',
'text' => $matches[0][0], 'text' => $url,
'attributes' => array( 'attributes' => array(
'href' => $matches[0][0], 'href' => $url,
), ),
), ),
); );
@ -1343,7 +1417,7 @@ class Parsedown
{ {
if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
{ {
$url = str_replace(array('&', '<'), array('&amp;', '&lt;'), $matches[1]); $url = $matches[1];
return array( return array(
'extent' => strlen($matches[0]), 'extent' => strlen($matches[0]),
@ -1381,6 +1455,11 @@ class Parsedown
protected function element(array $Element) protected function element(array $Element)
{ {
if ($this->safeMode)
{
$Element = $this->sanitiseElement($Element);
}
$markup = '<'.$Element['name']; $markup = '<'.$Element['name'];
if (isset($Element['attributes'])) if (isset($Element['attributes']))
@ -1392,7 +1471,7 @@ class Parsedown
continue; continue;
} }
$markup .= ' '.$name.'="'.$value.'"'; $markup .= ' '.$name.'="'.self::escape($value).'"';
} }
} }
@ -1400,13 +1479,18 @@ class Parsedown
{ {
$markup .= '>'; $markup .= '>';
if (!isset($Element['nonNestables']))
{
$Element['nonNestables'] = array();
}
if (isset($Element['handler'])) if (isset($Element['handler']))
{ {
$markup .= $this->{$Element['handler']}($Element['text']); $markup .= $this->{$Element['handler']}($Element['text'], $Element['nonNestables']);
} }
else else
{ {
$markup .= $Element['text']; $markup .= self::escape($Element['text'], true);
} }
$markup .= '</'.$Element['name'].'>'; $markup .= '</'.$Element['name'].'>';
@ -1465,10 +1549,77 @@ class Parsedown
return $markup; return $markup;
} }
protected function sanitiseElement(array $Element)
{
static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
static $safeUrlNameToAtt = array(
'a' => 'href',
'img' => 'src',
);
if (isset($safeUrlNameToAtt[$Element['name']]))
{
$Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
}
if ( ! empty($Element['attributes']))
{
foreach ($Element['attributes'] as $att => $val)
{
# filter out badly parsed attribute
if ( ! preg_match($goodAttribute, $att))
{
unset($Element['attributes'][$att]);
}
# dump onevent attribute
elseif (self::striAtStart($att, 'on'))
{
unset($Element['attributes'][$att]);
}
}
}
return $Element;
}
protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
{
foreach ($this->safeLinksWhitelist as $scheme)
{
if (self::striAtStart($Element['attributes'][$attribute], $scheme))
{
return $Element;
}
}
$Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
return $Element;
}
# #
# Static Methods # Static Methods
# #
protected static function escape($text, $allowQuotes = false)
{
return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
}
protected static function striAtStart($string, $needle)
{
$len = strlen($needle);
if ($len > strlen($string))
{
return false;
}
else
{
return strtolower(substr($string, 0, $len)) === strtolower($needle);
}
}
static function instance($name = 'default') static function instance($name = 'default')
{ {
if (isset(self::$instances[$name])) if (isset(self::$instances[$name]))
@ -1519,10 +1670,10 @@ class Parsedown
'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
'i', 'rp', 'del', 'code', 'strike', 'marquee', 'i', 'rp', 'del', 'code', 'strike', 'marquee',
'q', 'rt', 'ins', 'font', 'strong', 'q', 'rt', 'ins', 'font', 'strong',
's', 'tt', 'sub', 'mark', 's', 'tt', 'kbd', 'mark',
'u', 'xm', 'sup', 'nobr', 'u', 'xm', 'sub', 'nobr',
'var', 'ruby', 'sup', 'ruby',
'wbr', 'span', 'var', 'span',
'time', 'wbr', 'time',
); );
} }

View File

@ -1,4 +1,4 @@
RELEASE=1.6.0 RELEASE=1.7.1
PARSEDOWN=parsedown-$RELEASE PARSEDOWN=parsedown-$RELEASE
curl https://codeload.github.com/erusev/parsedown/tar.gz/${RELEASE} -o ${PARSEDOWN}.tar.gz curl https://codeload.github.com/erusev/parsedown/tar.gz/${RELEASE} -o ${PARSEDOWN}.tar.gz
tar xfz ${PARSEDOWN}.tar.gz --strip-components 1 ${PARSEDOWN}/Parsedown.php ${PARSEDOWN}/LICENSE.txt tar xfz ${PARSEDOWN}.tar.gz --strip-components 1 ${PARSEDOWN}/Parsedown.php ${PARSEDOWN}/LICENSE.txt

View File

@ -5,12 +5,12 @@ tar xzf dompdf-0.8.2.tar.gz --strip-components 1 dompdf-0.8.2/{lib,src,LICENSE.L
rm dompdf-0.8.2.tar.gz rm dompdf-0.8.2.tar.gz
mv LICENSE.LGPL LICENSE mv LICENSE.LGPL LICENSE
curl https://codeload.github.com/PhenX/php-font-lib/tar.gz/0.4 -o php-font-lib-0.4.tar.gz curl https://codeload.github.com/PhenX/php-font-lib/tar.gz/0.5.1 -o php-font-lib-0.5.1.tar.gz
mkdir -p lib/php-font-lib mkdir -p lib/php-font-lib
tar xzf php-font-lib-0.4.tar.gz --strip-components 1 -C lib/php-font-lib php-font-lib-0.4/{src,LICENSE} tar xzf php-font-lib-0.5.1.tar.gz --strip-components 1 -C lib/php-font-lib php-font-lib-0.5.1/{src,LICENSE}
rm php-font-lib-0.4.tar.gz rm php-font-lib-0.5.1.tar.gz
curl https://codeload.github.com/PhenX/php-svg-lib/tar.gz/v0.3 -o php-svg-lib-0.3.tar.gz curl https://codeload.github.com/PhenX/php-svg-lib/tar.gz/v0.3.2 -o php-svg-lib-0.3.2.tar.gz
mkdir -p lib/php-svg-lib mkdir -p lib/php-svg-lib
tar xzf php-svg-lib-0.3.tar.gz --strip-components 1 -C lib/php-svg-lib php-svg-lib-0.3/src tar xzf php-svg-lib-0.3.2.tar.gz --strip-components 1 -C lib/php-svg-lib php-svg-lib-0.3.2/src
rm php-svg-lib-0.3.tar.gz rm php-svg-lib-0.3.2.tar.gz

View File

@ -157,6 +157,10 @@ class BinaryStream {
return ord($this->read(1)); return ord($this->read(1));
} }
public function readUInt8Many($count) {
return array_values(unpack("C*", $this->read($count)));
}
public function writeUInt8($data) { public function writeUInt8($data) {
return $this->write(chr($data), 1); return $this->write(chr($data), 1);
} }
@ -171,6 +175,10 @@ class BinaryStream {
return $v; return $v;
} }
public function readInt8Many($count) {
return array_values(unpack("c*", $this->read($count)));
}
public function writeInt8($data) { public function writeInt8($data) {
if ($data < 0) { if ($data < 0) {
$data += 0x100; $data += 0x100;
@ -185,6 +193,10 @@ class BinaryStream {
return $a["n"]; return $a["n"];
} }
public function readUInt16Many($count) {
return array_values(unpack("n*", $this->read($count * 2)));
}
public function readUFWord() { public function readUFWord() {
return $this->readUInt16(); return $this->readUInt16();
} }
@ -198,7 +210,8 @@ class BinaryStream {
} }
public function readInt16() { public function readInt16() {
$v = $this->readUInt16(); $a = unpack("nn", $this->read(2));
$v = $a["n"];
if ($v >= 0x8000) { if ($v >= 0x8000) {
$v -= 0x10000; $v -= 0x10000;
@ -207,6 +220,17 @@ class BinaryStream {
return $v; return $v;
} }
public function readInt16Many($count) {
$vals = array_values(unpack("n*", $this->read($count * 2)));
foreach ($vals as &$v) {
if ($v >= 0x8000) {
$v -= 0x10000;
}
}
return $vals;
}
public function readFWord() { public function readFWord() {
return $this->readInt16(); return $this->readInt16();
} }
@ -251,6 +275,13 @@ class BinaryStream {
$this->readUInt32(); // ignored $this->readUInt32(); // ignored
$date = $this->readUInt32() - 2082844800; $date = $this->readUInt32() - 2082844800;
# PHP_INT_MIN isn't defined in PHP < 7.0
$php_int_min = defined("PHP_INT_MIN") ? PHP_INT_MIN : ~PHP_INT_MAX;
if (is_string($date) || $date > PHP_INT_MAX || $date < $php_int_min) {
$date = 0;
}
return strftime("%Y-%m-%d %H:%M:%S", $date); return strftime("%Y-%m-%d %H:%M:%S", $date);
} }
@ -319,6 +350,18 @@ class BinaryStream {
if ($type[0] == self::char) { if ($type[0] == self::char) {
return $this->read($type[1]); return $this->read($type[1]);
} }
if ($type[0] == self::uint16) {
return $this->readUInt16Many($type[1]);
}
if ($type[0] == self::int16) {
return $this->readInt16Many($type[1]);
}
if ($type[0] == self::uint8) {
return $this->readUInt8Many($type[1]);
}
if ($type[0] == self::int8) {
return $this->readInt8Many($type[1]);
}
$ret = array(); $ret = array();
for ($i = 0; $i < $type[1]; $i++) { for ($i = 0; $i < $type[1]; $i++) {
@ -376,8 +419,10 @@ class BinaryStream {
$ret = 0; $ret = 0;
for ($i = 0; $i < $type[1]; $i++) { for ($i = 0; $i < $type[1]; $i++) {
if (isset($data[$i])) {
$ret += $this->w($type[0], $data[$i]); $ret += $this->w($type[0], $data[$i]);
} }
}
return $ret; return $ret;
} }

View File

@ -0,0 +1,11 @@
<?php
namespace FontLib\Exception;
class FontNotFoundException extends \Exception
{
public function __construct($fontPath)
{
$this->message = 'Font not found in: ' . $fontPath;
}
}

View File

@ -8,6 +8,8 @@
namespace FontLib; namespace FontLib;
use FontLib\Exception\FontNotFoundException;
/** /**
* Generic font file. * Generic font file.
* *
@ -22,6 +24,10 @@ class Font {
* @return TrueType\File|null $file * @return TrueType\File|null $file
*/ */
public static function load($file) { public static function load($file) {
if(!file_exists($file)){
throw new FontNotFoundException($file);
}
$header = file_get_contents($file, false, null, null, 4); $header = file_get_contents($file, false, null, null, 4);
$class = null; $class = null;

View File

@ -42,8 +42,7 @@ class Outline extends BinaryStream {
* *
* @return Outline * @return Outline
*/ */
static function init(glyf $table, $offset, $size) { static function init(glyf $table, $offset, $size, BinaryStream $font) {
$font = $table->getFont();
$font->seek($offset); $font->seek($offset);
if ($font->readInt16() > -1) { if ($font->readInt16() > -1) {
@ -55,7 +54,7 @@ class Outline extends BinaryStream {
$glyph = new OutlineComposite($table, $offset, $size); $glyph = new OutlineComposite($table, $offset, $size);
} }
$glyph->parse(); $glyph->parse($font);
return $glyph; return $glyph;
} }
@ -73,8 +72,7 @@ class Outline extends BinaryStream {
$this->size = $size; $this->size = $size;
} }
function parse() { function parse(BinaryStream $font) {
$font = $this->getFont();
$font->seek($this->offset); $font->seek($this->offset);
if (!$this->size) { if (!$this->size) {

View File

@ -41,8 +41,11 @@ class OutlineComposite extends Outline {
$glyphIDs[] = $_component->glyphIndex; $glyphIDs[] = $_component->glyphIndex;
$_glyph = $this->table->data[$_component->glyphIndex]; $_glyph = $this->table->data[$_component->glyphIndex];
if ($_glyph !== $this) {
$glyphIDs = array_merge($glyphIDs, $_glyph->getGlyphIDs()); $glyphIDs = array_merge($glyphIDs, $_glyph->getGlyphIDs());
} }
}
return $glyphIDs; return $glyphIDs;
} }
@ -224,11 +227,15 @@ class OutlineComposite extends Outline {
$glyphs = $glyph_data->data; $glyphs = $glyph_data->data;
foreach ($this->components as $component) { foreach ($this->components as $component) {
$_glyph = $glyphs[$component->glyphIndex];
if ($_glyph !== $this) {
$contours[] = array( $contours[] = array(
"contours" => $glyphs[$component->glyphIndex]->getSVGContours(), "contours" => $_glyph->getSVGContours(),
"transform" => $component->getMatrix(), "transform" => $component->getMatrix(),
); );
} }
}
return $contours; return $contours;
} }

View File

@ -35,6 +35,12 @@ class cmap extends Table {
"rangeShift" => self::uint16, "rangeShift" => self::uint16,
); );
private static $subtable_v12_format = array(
"length" => self::uint32,
"language" => self::uint32,
"ngroups" => self::uint32
);
protected function _parse() { protected function _parse() {
$font = $this->getFont(); $font = $this->getFont();
@ -46,6 +52,7 @@ class cmap extends Table {
for ($i = 0; $i < $data["numberSubtables"]; $i++) { for ($i = 0; $i < $data["numberSubtables"]; $i++) {
$subtables[] = $font->unpack(self::$subtable_header_format); $subtables[] = $font->unpack(self::$subtable_header_format);
} }
$data["subtables"] = $subtables; $data["subtables"] = $subtables;
foreach ($data["subtables"] as $i => &$subtable) { foreach ($data["subtables"] as $i => &$subtable) {
@ -53,26 +60,58 @@ class cmap extends Table {
$subtable["format"] = $font->readUInt16(); $subtable["format"] = $font->readUInt16();
// @todo Only CMAP version 4 // @todo Only CMAP version 4 and 12
if ($subtable["format"] != 4) { if (($subtable["format"] != 4) && ($subtable["format"] != 12)) {
unset($data["subtables"][$i]); unset($data["subtables"][$i]);
$data["numberSubtables"]--; $data["numberSubtables"]--;
continue; continue;
} }
if ($subtable["format"] == 12) {
$font->readUInt16();
$subtable += $font->unpack(self::$subtable_v12_format);
$glyphIndexArray = array();
$endCodes = array();
$startCodes = array();
for ($p = 0; $p < $subtable['ngroups']; $p++) {
$startCode = $startCodes[] = $font->readUInt32();
$endCode = $endCodes[] = $font->readUInt32();
$startGlyphCode = $font->readUInt32();
for ($c = $startCode; $c <= $endCode; $c++) {
$glyphIndexArray[$c] = $startGlyphCode;
$startGlyphCode++;
}
}
$subtable += array(
"startCode" => $startCodes,
"endCode" => $endCodes,
"glyphIndexArray" => $glyphIndexArray,
);
}
else if ($subtable["format"] == 4) {
$subtable += $font->unpack(self::$subtable_v4_format); $subtable += $font->unpack(self::$subtable_v4_format);
$segCount = $subtable["segCountX2"] / 2; $segCount = $subtable["segCountX2"] / 2;
$subtable["segCount"] = $segCount; $subtable["segCount"] = $segCount;
$endCode = $font->r(array(self::uint16, $segCount)); $endCode = $font->readUInt16Many($segCount);
$font->readUInt16(); // reservedPad $font->readUInt16(); // reservedPad
$startCode = $font->r(array(self::uint16, $segCount)); $startCode = $font->readUInt16Many($segCount);
$idDelta = $font->r(array(self::int16, $segCount)); $idDelta = $font->readInt16Many($segCount);
$ro_start = $font->pos(); $ro_start = $font->pos();
$idRangeOffset = $font->r(array(self::uint16, $segCount)); $idRangeOffset = $font->readUInt16Many($segCount);
$glyphIndexArray = array(); $glyphIndexArray = array();
for ($i = 0; $i < $segCount; $i++) { for ($i = 0; $i < $segCount; $i++) {
@ -115,6 +154,7 @@ class cmap extends Table {
"glyphIndexArray" => $glyphIndexArray, "glyphIndexArray" => $glyphIndexArray,
); );
} }
}
$this->data = $data; $this->data = $data;
} }

View File

@ -31,7 +31,7 @@ class glyf extends Table {
foreach ($real_loca as $gid => $location) { foreach ($real_loca as $gid => $location) {
$_offset = $offset + $loca[$gid]; $_offset = $offset + $loca[$gid];
$_size = $loca[$gid + 1] - $loca[$gid]; $_size = $loca[$gid + 1] - $loca[$gid];
$data[$gid] = Outline::init($this, $_offset, $_size); $data[$gid] = Outline::init($this, $_offset, $_size, $font);
} }
$this->data = $data; $this->data = $data;

View File

@ -25,9 +25,12 @@ class hmtx extends Table {
$font->seek($offset); $font->seek($offset);
$data = array(); $data = array();
for ($gid = 0; $gid < $numOfLongHorMetrics; $gid++) { $metrics = $font->readUInt16Many($numOfLongHorMetrics * 2);
$advanceWidth = $font->readUInt16(); for ($gid = 0, $mid = 0; $gid < $numOfLongHorMetrics; $gid++) {
$leftSideBearing = $font->readUInt16(); $advanceWidth = isset($metrics[$mid]) ? $metrics[$mid] : 0;
$mid += 1;
$leftSideBearing = isset($metrics[$mid]) ? $metrics[$mid] : 0;
$mid += 1;
$data[$gid] = array($advanceWidth, $leftSideBearing); $data[$gid] = array($advanceWidth, $leftSideBearing);
} }

View File

@ -44,10 +44,15 @@ class kern extends Table {
$pairs = array(); $pairs = array();
$tree = array(); $tree = array();
for ($i = 0; $i < $subtable["nPairs"]; $i++) { $values = $font->readUInt16Many($subtable["nPairs"] * 3);
$left = $font->readUInt16(); for ($i = 0, $idx = 0; $i < $subtable["nPairs"]; $i++) {
$right = $font->readUInt16(); $left = $values[$idx++];
$value = $font->readInt16(); $right = $values[$idx++];
$value = $values[$idx++];
if ($value >= 0x8000) {
$value -= 0x10000;
}
$pairs[] = array( $pairs[] = array(
"left" => $left, "left" => $left,

View File

@ -32,7 +32,7 @@ class loca extends Table {
$loc = unpack("n*", $d); $loc = unpack("n*", $d);
for ($i = 0; $i <= $numGlyphs; $i++) { for ($i = 0; $i <= $numGlyphs; $i++) {
$data[] = $loc[$i + 1] * 2; $data[] = isset($loc[$i + 1]) ? $loc[$i + 1] * 2 : 0;
} }
} }
@ -43,7 +43,7 @@ class loca extends Table {
$loc = unpack("N*", $d); $loc = unpack("N*", $d);
for ($i = 0; $i <= $numGlyphs; $i++) { for ($i = 0; $i <= $numGlyphs; $i++) {
$data[] = $loc[$i + 1]; $data[] = isset($loc[$i + 1]) ? $loc[$i + 1] : 0;
} }
} }
} }

View File

@ -73,7 +73,7 @@ class name extends Table {
3 => "Microsoft", 3 => "Microsoft",
); );
static $plaformSpecific = array( static $platformSpecific = array(
// Unicode // Unicode
0 => array( 0 => array(
0 => "Default semantics", 0 => "Default semantics",

View File

@ -42,10 +42,7 @@ class post extends Table {
case 2: case 2:
$data["numberOfGlyphs"] = $font->readUInt16(); $data["numberOfGlyphs"] = $font->readUInt16();
$glyphNameIndex = array(); $glyphNameIndex = $font->readUInt16Many($data["numberOfGlyphs"]);
for ($i = 0; $i < $data["numberOfGlyphs"]; $i++) {
$glyphNameIndex[] = $font->readUInt16();
}
$data["glyphNameIndex"] = $glyphNameIndex; $data["glyphNameIndex"] = $glyphNameIndex;

View File

@ -305,7 +305,7 @@ class File extends BinaryStream {
$class = "FontLib\\Table\\Type\\$name_canon"; $class = "FontLib\\Table\\Type\\$name_canon";
if (!isset($this->directory[$tag]) || !class_exists($class)) { if (!isset($this->directory[$tag]) || !@class_exists($class)) {
return; return;
} }
} }

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg; namespace Svg;

View File

@ -2,8 +2,8 @@
/** /**
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg; namespace Svg;
@ -333,6 +333,9 @@ class Document extends AbstractTag
case 'text': case 'text':
$tag = new Text($this, $name); $tag = new Text($this, $name);
break; break;
case 'desc':
return;
} }
if ($tag) { if ($tag) {
@ -350,8 +353,6 @@ class Document extends AbstractTag
$this->stack[] = $tag; $this->stack[] = $tag;
$tag->handle($attributes); $tag->handle($attributes);
} else {
echo "Unknown: '$name'\n";
} }
} }

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Gradient; namespace Svg\Gradient;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien M<EFBFBD>nager <fabien.menager@gmail.com> * @author Fabien M<EFBFBD>nager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg; namespace Svg;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien M<EFBFBD>nager <fabien.menager@gmail.com> * @author Fabien M<EFBFBD>nager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Surface; namespace Svg\Surface;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien M<EFBFBD>nager <fabien.menager@gmail.com> * @author Fabien M<EFBFBD>nager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Surface; namespace Svg\Surface;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien M<EFBFBD>nager <fabien.menager@gmail.com> * @author Fabien M<EFBFBD>nager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Surface; namespace Svg\Surface;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien M<EFBFBD>nager <fabien.menager@gmail.com> * @author Fabien M<EFBFBD>nager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Surface; namespace Svg\Surface;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;
@ -21,7 +21,7 @@ abstract class AbstractTag
/** @var Style */ /** @var Style */
protected $style; protected $style;
protected $attributes; protected $attributes = array();
protected $hasShape = true; protected $hasShape = true;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;

View File

@ -3,13 +3,13 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;
use Svg\Gradient\Stop; use Svg\Gradient;
use Svg\Style; use Svg\Style;
class LinearGradient extends AbstractTag class LinearGradient extends AbstractTag
@ -19,7 +19,7 @@ class LinearGradient extends AbstractTag
protected $x2; protected $x2;
protected $y2; protected $y2;
/** @var Stop[] */ /** @var Gradient\Stop[] */
protected $stops = array(); protected $stops = array();
public function start($attributes) public function start($attributes)
@ -47,7 +47,7 @@ class LinearGradient extends AbstractTag
continue; continue;
} }
$_stop = new Stop(); $_stop = new Gradient\Stop();
$_attributes = $_child->attributes; $_attributes = $_child->attributes;
// Style // Style

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien M<EFBFBD>nager <fabien.menager@gmail.com> * @author Fabien M<EFBFBD>nager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
namespace Svg\Tag; namespace Svg\Tag;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib * @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib * @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/ */
spl_autoload_register(function($class) { spl_autoload_register(function($class) {