Merge pull request 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');
}
} elseif (!function_exists('__autoload')) {
function __autoload($class)
{
return HTMLPurifier_Bootstrap::autoload($class);
}
require dirname(__FILE__) . '/HTMLPurifier.autoload-legacy.php';
}
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
This library is free software; you can redistribute it and/or
@ -58,12 +58,12 @@ class HTMLPurifier
* Version of HTML Purifier.
* @type string
*/
public $version = '4.8.0';
public $version = '4.10.0';
/**
* Constant with version of HTML Purifier.
*/
const VERSION = '4.8.0';
const VERSION = '4.10.0';
/**
* Global configuration object.

View File

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

View File

@ -86,7 +86,13 @@ abstract class HTMLPurifier_AttrDef
*/
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();
$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();
$new_declarations = '';

View File

@ -6,6 +6,16 @@
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 HTMLPurifier_Config $config
@ -29,59 +39,104 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
return $colors[$lower];
}
if (strpos($color, 'rgb(') !== false) {
// rgb literal handling
if (preg_match('#(rgb|rgba|hsl|hsla)\(#', $color, $matches) === 1) {
$length = strlen($color);
if (strpos($color, ')') !== $length - 1) {
return false;
}
$triad = substr($color, 4, $length - 4 - 1);
$parts = explode(',', $triad);
if (count($parts) !== 3) {
// get used function : rgb, rgba, hsl or hsla
$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;
}
$type = false; // to ensure that they're all the same type
$type = false;
$new_parts = array();
$i = 0;
foreach ($parts as $part) {
$i++;
$part = trim($part);
if ($part === '') {
return false;
}
$length = strlen($part);
if ($part[$length - 1] === '%') {
// handle percents
if (!$type) {
$type = 'percentage';
} elseif ($type !== 'percentage') {
// different check for alpha channel
if ($alpha_channel === true && $i === count($parts)) {
$result = $this->alpha->validate($part, $config, $context);
if ($result === false) {
return false;
}
$num = (float)substr($part, 0, $length - 1);
if ($num < 0) {
$num = 0;
}
if ($num > 100) {
$num = 100;
}
$new_parts[] = "$num%";
$new_parts[] = (string)$result;
continue;
}
if (substr($part, -1) === '%') {
$current_type = 'percentage';
} else {
// handle integers
if (!$type) {
$type = 'integer';
} elseif ($type !== 'integer') {
return false;
}
$num = (int)$part;
if ($num < 0) {
$num = 0;
}
if ($num > 255) {
$num = 255;
}
$new_parts[] = (string)$num;
$current_type = 'integer';
}
if (!array_key_exists($current_type, $allowed_types[$i])) {
return false;
}
if (!$type) {
$type = $current_type;
}
if ($allow_different_types === false && $type != $current_type) {
return false;
}
$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 {
// hexadecimal handling
if ($color[0] === '#') {
@ -100,6 +155,7 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
}
return $color;
}
}
// 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
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
// 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);
if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
return $string;
}
} catch (Exception $e) {
// XXX error reporting
}
}
// Try again
if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
return $string;
}
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');
$this->info['min-width'] =
$this->info['max-width'] =
$this->info['min-height'] =
$this->info['max-height'] =
$this->info['width'] =
$this->info['height'] =
$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
$all_whitespace = true;
$current_li = false;
$current_li = null;
foreach ($children as $node) {
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
// not be appended to an existing li; only li created
// for non-list. This distinction is not currently made.
if ($current_li === false) {
if ($current_li === null) {
$current_li = new HTMLPurifier_Node_Element('li');
$result[] = $current_li;
}

View File

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

View File

@ -21,7 +21,7 @@ class HTMLPurifier_Config
* HTML Purifier's version
* @type string
*/
public $version = '4.8.0';
public $version = '4.10.0';
/**
* Whether or not to automatically finalize
@ -333,7 +333,7 @@ class HTMLPurifier_Config
}
// 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;
if ($rtype < 0) {
$type = -$rtype;

View File

@ -24,11 +24,11 @@ class HTMLPurifier_ConfigSchema
*
* 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:
* - type: Integer type of directive, see HTMLPurifier_VarParser for definitions
@ -39,8 +39,8 @@ class HTMLPurifier_ConfigSchema
* - namespace: Namespace this directive aliases to
* - name: Directive name this directive aliases to
*
* In certain degenerate cases, stdclass will actually be an integer. In
* that case, the value is equivalent to an stdclass with the type
* In certain degenerate cases, stdClass will actually be an integer. In
* that case, the value is equivalent to an stdClass with the type
* property set to the integer. If the integer is negative, type is
* 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)
{
$obj = new stdclass();
$obj = new stdClass();
$obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type];
if ($allow_null) {
$obj->allow_null = true;
@ -152,14 +152,14 @@ class HTMLPurifier_ConfigSchema
*/
public function addAlias($key, $new_key)
{
$obj = new stdclass;
$obj = new stdClass;
$obj->key = $new_key;
$obj->isAlias = true;
$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()
{

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
TYPE: string
TYPE: string/null
DEFAULT: 'http'
--DESCRIPTION--
@ -7,4 +7,9 @@ DEFAULT: 'http'
Defines through what scheme the output will be served, in order to
select the proper object validator when no scheme information is present.
</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

View File

@ -112,6 +112,7 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
}
unlink($dir . '/' . $filename);
}
closedir($dh);
return true;
}
@ -142,6 +143,7 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
unlink($dir . '/' . $filename);
}
}
closedir($dh);
return true;
}
@ -198,11 +200,8 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
if ($result !== false) {
// set permissions of the new file (no execute)
$chmod = $config->get('Cache.SerializerPermissions');
if ($chmod === null) {
// don't do anything
} else {
$chmod = $chmod & 0666;
chmod($file, $chmod);
if ($chmod !== null) {
chmod($file, $chmod & 0666);
}
}
return $result;
@ -217,6 +216,16 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
{
$directory = $this->generateDirectoryPath($config);
$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)) {
$base = $this->generateBaseDirectoryPath($config);
if (!is_dir($base)) {
@ -229,25 +238,14 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
} elseif (!$this->_testPermissions($base, $chmod)) {
return false;
}
if ($chmod === null) {
if (!@mkdir($directory, $chmod) && !is_dir($directory)) {
trigger_error(
'Base directory ' . $base . ' does not exist,
please create or change using %Cache.SerializerPath',
'Could not create directory ' . $directory . '',
E_USER_WARNING
);
return false;
}
if ($chmod !== null) {
mkdir($directory, $chmod);
} else {
mkdir($directory);
}
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;
}
} 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
* 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 bool $force_php
* @return string
@ -122,15 +130,12 @@ class HTMLPurifier_Encoder
* function that needs to be able to understand UTF-8 characters.
* As of right now, only smart lossless character encoding converters
* 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)
{
// UTF-8 validity is checked since PHP 4.3.5
// 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.
// The regexp matches the XML char production, as well as well as excluding
// non-SGML codepoints U+007F to U+009F
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',
$str
@ -255,6 +260,7 @@ class HTMLPurifier_Encoder
// 7F-9F is not strictly prohibited by XML,
// but it is non-SGML, and thus we don't allow it
(0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) ||
(0xE000 <= $mUcs4 && 0xFFFD >= $mUcs4) ||
(0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4)
)
) {

View File

@ -16,6 +16,138 @@ class HTMLPurifier_EntityParser
*/
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.
* @type string
@ -144,7 +276,7 @@ class HTMLPurifier_EntityParser
$entity;
} else {
return isset($this->_special_ent2dec[$matches[3]]) ?
$this->_special_ent2dec[$matches[3]] :
$this->_special_dec2str[$this->_special_ent2dec[$matches[3]]] :
$entity;
}
}

View File

@ -95,7 +95,10 @@ class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter
if ($tidy !== null) {
$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;
$this->_styleMatches = array(); // reset
$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);
if ($this->_flashCompat) {
if ($token->name == "object") {
$flash = new stdclass();
$flash = new stdClass();
$flash->attr = $token->attr;
$flash->param = array();
$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')) {
$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.
if ($config->get('HTML.TargetNoreferrer')) {
$modules[] = 'TargetNoreferrer';
}
if ($config->get('HTML.TargetNoopener')) {
$modules[] = 'TargetNoopener';
}
// merge in custom modules
$modules = array_merge($modules, $this->userModules);

View File

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

View File

@ -26,12 +26,14 @@ class HTMLPurifier_Length
protected $isValid;
/**
* Array Lookup array of units recognized by CSS 2.1
* Array Lookup array of units recognized by CSS 3
* @type array
*/
protected static $allowedUnits = array(
'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;
}
if (class_exists('DOMDocument') &&
if (class_exists('DOMDocument', false) &&
method_exists('DOMDocument', 'loadHTML') &&
!extension_loaded('domxml')
) {
@ -169,21 +169,24 @@ class HTMLPurifier_Lexer
'&#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.
*
* This string will translate escaped versions of the special characters
* 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.
* @return string Parsed character data.
*/
public function parseData($string)
public function parseData($string, $is_attr, $config)
{
// following functions require at least one character
if ($string === '') {
@ -209,7 +212,15 @@ class HTMLPurifier_Lexer
}
// hmm... now we have some uncommon entities. Use the callback.
$string = $this->_entity_parser->substituteSpecialEntities($string);
if ($config->get('Core.LegacyEntityDecoder')) {
$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;
}
@ -323,7 +334,9 @@ class HTMLPurifier_Lexer
}
// expand entities that aren't the big five
$html = $this->_entity_parser->substituteNonSpecialEntities($html);
if ($config->get('Core.LegacyEntityDecoder')) {
$html = $this->_entity_parser->substituteNonSpecialEntities($html);
}
// clean into wellformed UTF-8 string for an SGML context: this has
// to be done after entity expansion because the entities sometimes
@ -335,6 +348,13 @@ class HTMLPurifier_Lexer
$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;
}

View File

@ -72,12 +72,20 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
$doc->loadHTML($html);
restore_error_handler();
$body = $doc->getElementsByTagName('html')->item(0)-> // <html>
getElementsByTagName('body')->item(0); // <body>
$div = $body->getElementsByTagName('div')->item(0); // <div>
$tokens = array();
$this->tokenizeDOM(
$doc->getElementsByTagName('html')->item(0)-> // <html>
getElementsByTagName('body')->item(0), // <body>
$tokens
);
$this->tokenizeDOM($div, $tokens, $config);
// If the div has a sibling, that means we tripped across
// a premature </div> tag. So remove the div we parsed,
// 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;
}
@ -88,7 +96,7 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
* @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens.
* @return HTMLPurifier_Token of node appended to previously passed tokens.
*/
protected function tokenizeDOM($node, &$tokens)
protected function tokenizeDOM($node, &$tokens, $config)
{
$level = 0;
$nodes = array($level => new HTMLPurifier_Queue(array($node)));
@ -97,7 +105,7 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
while (!$nodes[$level]->isEmpty()) {
$node = $nodes[$level]->shift(); // FIFO
$collect = $level > 0 ? true : false;
$needEndingTag = $this->createStartNode($node, $tokens, $collect);
$needEndingTag = $this->createStartNode($node, $tokens, $collect, $config);
if ($needEndingTag) {
$closingNodes[$level][] = $node;
}
@ -118,6 +126,41 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
} 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 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
* @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,
// but we're not getting the character reference nodes because
// those should have been preprocessed
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;
} elseif ($node->nodeType === XML_CDATA_SECTION_NODE) {
// 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;
} elseif ($node->nodeType === XML_COMMENT_NODE) {
// 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
return false;
}
$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
if (!$node->childNodes->length) {
if ($collect) {
$tokens[] = $this->factory->createEmpty($node->tagName, $attr);
$tokens[] = $this->factory->createEmpty($tag_name, $attr);
}
return false;
} else {
if ($collect) {
$tokens[] = $this->factory->createStart(
$tag_name = $node->tagName, // somehow, it get's dropped
$attr
);
$tokens[] = $this->factory->createStart($tag_name, $attr);
}
return true;
}
@ -189,10 +234,10 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
*/
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.
*
@ -252,7 +297,7 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
* @param HTMLPurifier_Context $context
* @return string
*/
protected function wrapHTML($html, $config, $context)
protected function wrapHTML($html, $config, $context, $use_div = true)
{
$def = $config->getDefinition('HTML');
$ret = '';
@ -271,7 +316,11 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
$ret .= '<html><head>';
$ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
// 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;
}
}

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
$token = new
HTMLPurifier_Token_Text(
$this->parseData(
$this->parseText(
substr(
$html,
$cursor,
$position_next_lt - $cursor
)
), $config
)
);
if ($maintain_line_numbers) {
@ -154,11 +154,11 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
// Create Text of rest of string
$token = new
HTMLPurifier_Token_Text(
$this->parseData(
$this->parseText(
substr(
$html,
$cursor
)
), $config
)
);
if ($maintain_line_numbers) {
@ -324,8 +324,8 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
$token = new
HTMLPurifier_Token_Text(
'<' .
$this->parseData(
substr($html, $cursor)
$this->parseText(
substr($html, $cursor), $config
)
);
if ($maintain_line_numbers) {
@ -429,7 +429,7 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
if ($value === false) {
$value = '';
}
return array($key => $this->parseData($value));
return array($key => $this->parseAttr($value, $config));
}
// setup loop environment
@ -518,7 +518,7 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
if ($value === false) {
$value = '';
}
$array[$key] = $this->parseData($value);
$array[$key] = $this->parseAttr($value, $config);
$cursor++;
} else {
// boolattr

View File

@ -21,7 +21,7 @@ class HTMLPurifier_Lexer_PH5P extends HTMLPurifier_Lexer_DOMLex
public function tokenizeHTML($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 {
$parser = new HTML5($new_html);
$doc = $parser->save();
@ -34,9 +34,9 @@ class HTMLPurifier_Lexer_PH5P extends HTMLPurifier_Lexer_DOMLex
$tokens = array();
$this->tokenizeDOM(
$doc->getElementsByTagName('html')->item(0)-> // <html>
getElementsByTagName('body')->item(0) // <body>
getElementsByTagName('body')->item(0) // <body>
,
$tokens
$tokens, $config
);
return $tokens;
}
@ -1507,7 +1507,7 @@ class HTML5
$entity = $this->character($start, $this->char);
$cond = strlen($e_name) > 0;
// The rest of the parsing happens bellow.
// The rest of the parsing happens below.
break;
// Anything else
@ -1515,6 +1515,7 @@ class HTML5
// Consume the maximum number of characters possible, with the
// consumed characters case-sensitively matching one of the
// identifiers in the first column of the entities table.
$e_name = $this->characters('0-9A-Za-z;', $this->char + 1);
$len = strlen($e_name);
@ -1534,7 +1535,7 @@ class HTML5
}
$cond = isset($entity);
// The rest of the parsing happens bellow.
// The rest of the parsing happens below.
break;
}
@ -1547,7 +1548,7 @@ class HTML5
// Return a character token for the character corresponding to the
// 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)

View File

@ -1,10 +1,10 @@
GLOBIGNORE=$0; rm -rf *
rm ../HTMLPurifier*.php
curl https://codeload.github.com/ezyang/htmlpurifier/tar.gz/v4.8.0 -o htmlpurifier-4.8.0.tar.gz
tar xzf htmlpurifier-4.8.0.tar.gz --strip-components 1 htmlpurifier-4.8.0/LICENSE
tar xzf htmlpurifier-4.8.0.tar.gz --strip-components 1 htmlpurifier-4.8.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.8.0.tar.gz -C ../ --strip-components 2 htmlpurifier-4.8.0/library/HTMLPurifier.autoload.php
tar xzf htmlpurifier-4.8.0.tar.gz --strip-components 3 htmlpurifier-4.8.0/library/HTMLPurifier/*
rm 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.10.0.tar.gz --strip-components 1 htmlpurifier-4.10.0/LICENSE
tar xzf htmlpurifier-4.10.0.tar.gz --strip-components 1 htmlpurifier-4.10.0/VERSION
tar xzf htmlpurifier-4.10.0.tar.gz -C ../ --strip-components 2 htmlpurifier-4.10.0/library/HTMLPurifier.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.10.0.tar.gz --strip-components 3 htmlpurifier-4.10.0/library/HTMLPurifier/*
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;
$token = $zipper->prev($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]);
$token->rewind = $i;
if ($token instanceof HTMLPurifier_Token_Start) {
@ -210,6 +210,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
if ($token instanceof HTMLPurifier_Token_Text) {
foreach ($this->injectors as $i => $injector) {
if (isset($token->skip[$i])) {
// See Note [Injector skips]
continue;
}
if ($token->rewind !== null && $token->rewind !== $i) {
@ -367,6 +368,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
if ($ok) {
foreach ($this->injectors as $i => $injector) {
if (isset($token->skip[$i])) {
// See Note [Injector skips]
continue;
}
if ($token->rewind !== null && $token->rewind !== $i) {
@ -422,6 +424,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
$token->start = $current_parent;
foreach ($this->injectors as $i => $injector) {
if (isset($token->skip[$i])) {
// See Note [Injector skips]
continue;
}
if ($token->rewind !== null && $token->rewind !== $i) {
@ -534,12 +537,17 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
*/
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
if (is_object($token)) {
$token = array(1, $token);
$tmp = $token;
$token = array(1, $tmp);
}
if (is_int($token)) {
$token = array($token);
$tmp = $token;
$token = array($tmp);
}
if ($token === false) {
$token = array(1);
@ -561,7 +569,12 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
list($old, $r) = $this->zipper->splice($this->token, $delete, $token);
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();
foreach ($token as $object) {
$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

View File

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

View File

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

View File

@ -1 +1 @@
4.8.0
4.10.0

View File

@ -74,6 +74,11 @@ class Minifier
*/
protected $options;
/**
* These characters are used to define strings.
*/
protected $stringDelimiters = ['\'', '"', '`'];
/**
* 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
@ -115,9 +120,7 @@ class Minifier
unset($jshrink);
return $js;
} catch (\Exception $e) {
if (isset($jshrink)) {
// Since the breakdownScript function probably wasn't finished
// we clean it out before discarding it.
@ -176,12 +179,11 @@ class Minifier
protected function loop()
{
while ($this->a !== false && !is_null($this->a) && $this->a !== '') {
switch ($this->a) {
// new lines
case "\n":
// 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;
$this->saveString();
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
// string/regex check below, resetting $this->b with getReal
if($this->b === ' ')
if ($this->b === ' ') {
break;
}
// otherwise we treat the newline like a space
// no break
case ' ':
if(static::isAlphaNumeric($this->b))
if (static::isAlphaNumeric($this->b)) {
echo $this->a;
}
$this->saveString();
break;
@ -217,9 +222,11 @@ class Minifier
break;
case ' ':
if(!static::isAlphaNumeric($this->a))
if (!static::isAlphaNumeric($this->a)) {
break;
}
// no break
default:
// check for some regex that breaks stuff
if ($this->a === '/' && ($this->b === '\'' || $this->b === '"')) {
@ -236,8 +243,9 @@ class Minifier
// do reg check of doom
$this->b = $this->getReal();
if(($this->b == '/' && strpos('(,=:[!&|?', $this->a) !== false))
if (($this->b == '/' && strpos('(,=:[!&|?', $this->a) !== false)) {
$this->saveRegex();
}
}
}
@ -267,7 +275,7 @@ class Minifier
$char = $this->c;
unset($this->c);
// Otherwise we start pulling from the input.
// Otherwise we start pulling from the input.
} else {
$char = substr($this->input, $this->index, 1);
@ -282,9 +290,9 @@ class Minifier
// Normalize all whitespace except for the newline character into a
// standard space.
if($char !== "\n" && ord($char) < 32)
if ($char !== "\n" && ord($char) < 32) {
return ' ';
}
return $char;
}
@ -312,10 +320,13 @@ class Minifier
$this->c = $this->getChar();
if ($this->c === '/') {
return $this->processOneLineComments($startIndex);
$this->processOneLineComments($startIndex);
return $this->getReal();
} elseif ($this->c === '*') {
return $this->processMultiLineComments($startIndex);
$this->processMultiLineComments($startIndex);
return $this->getReal();
}
return $char;
@ -325,8 +336,8 @@ class Minifier
* Removed one line comments, with the exception of some very specific types of
* conditional comments.
*
* @param int $startIndex The index point where "getReal" function started
* @return string
* @param int $startIndex The index point where "getReal" function started
* @return void
*/
protected function processOneLineComments($startIndex)
{
@ -335,17 +346,12 @@ class Minifier
// kill rest of line
$this->getNext("\n");
unset($this->c);
if ($thirdCommentString == '@') {
$endPoint = $this->index - $startIndex;
unset($this->c);
$char = "\n" . substr($this->input, $startIndex, $endPoint);
} else {
// first one is contents of $this->c
$this->getChar();
$char = $this->getChar();
$this->c = "\n" . substr($this->input, $startIndex, $endPoint);
}
return $char;
}
/**
@ -353,7 +359,7 @@ class Minifier
* Conditional comments and "license" style blocks are preserved.
*
* @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
*/
protected function processMultiLineComments($startIndex)
@ -363,14 +369,13 @@ class Minifier
// kill everything up to the next */ if it's there
if ($this->getNext('*/')) {
$this->getChar(); // get *
$this->getChar(); // get /
$char = $this->getChar(); // get next real character
// Now we reinsert conditional comments and YUI-style licensing comments
if (($this->options['flaggedComments'] && $thirdCommentString === '!')
|| ($thirdCommentString === '@') ) {
|| ($thirdCommentString === '@')) {
// If conditional comments or flagged comments are not the first thing in the script
// we need to echo a and fill it with a space before moving on.
@ -387,21 +392,20 @@ class Minifier
$endPoint = ($this->index - 1) - $startIndex;
echo substr($this->input, $startIndex, $endPoint);
return $char;
}
$this->c = $char;
return;
}
} else {
$char = false;
}
if($char === false)
if ($char === false) {
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(isset($this->c))
unset($this->c);
return $char;
$this->c = $char;
}
/**
@ -418,9 +422,9 @@ class Minifier
$pos = strpos($this->input, $string, $this->index);
// If it's not there return false.
if($pos === false)
if ($pos === false) {
return false;
}
// Adjust position of index to jump ahead to the asked for string
$this->index = $pos;
@ -444,7 +448,7 @@ class Minifier
$this->a = $this->b;
// 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;
}
@ -473,7 +477,11 @@ class Minifier
// character, so those will be treated just fine using the switch
// block below.
case "\n":
throw new \RuntimeException('Unclosed string at position: ' . $startpos );
if ($stringType === '`') {
echo $this->a;
} else {
throw new \RuntimeException('Unclosed string at position: ' . $startpos);
}
break;
// 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;
while (($this->a = $this->getChar()) !== false) {
if($this->a === '/')
if ($this->a === '/') {
break;
}
if ($this->a === '\\') {
echo $this->a;
$this->a = $this->getChar();
}
if($this->a === "\n")
if ($this->a === "\n") {
throw new \RuntimeException('Unclosed regex pattern at position: ' . $this->index);
}
echo $this->a;
}
@ -583,5 +593,4 @@ class Minifier
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
tar xzf JShrink-1.1.0.tar.gz --strip-components 1 JShrink-1.1.0/LICENSE
tar xzf JShrink-1.1.0.tar.gz --strip-components 3 JShrink-1.1.0/src/JShrink/Minifier.php
rm 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.3.0.tar.gz --strip-components 1 JShrink-1.3.0/LICENSE
tar xzf JShrink-1.3.0.tar.gz --strip-components 3 JShrink-1.3.0/src/JShrink/Minifier.php
rm JShrink-1.3.0.tar.gz

View File

@ -1,6 +1,6 @@
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
this software and associated documentation files (the "Software"), to deal in
@ -17,4 +17,4 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

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;
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
#
@ -115,7 +141,7 @@ class Parsedown
# Blocks
#
private function lines(array $lines)
protected function lines(array $lines)
{
$CurrentBlock = null;
@ -175,7 +201,7 @@ class Parsedown
}
else
{
if (method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
if ($this->isBlockCompletable($CurrentBlock['type']))
{
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
}
@ -216,7 +242,7 @@ class Parsedown
$Block['identified'] = true;
}
if (method_exists($this, 'block'.$blockType.'Continue'))
if ($this->isBlockContinuable($blockType))
{
$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);
}
@ -278,6 +304,16 @@ class Parsedown
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
@ -332,8 +368,6 @@ class Parsedown
{
$text = $Block['element']['text']['text'];
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
$Block['element']['text']['text'] = $text;
return $Block;
@ -344,7 +378,7 @@ class Parsedown
protected function blockComment($Line)
{
if ($this->markupEscaped)
if ($this->markupEscaped or $this->safeMode)
{
return;
}
@ -386,7 +420,7 @@ class Parsedown
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(
'name' => 'code',
@ -438,7 +472,7 @@ class Parsedown
return $Block;
}
$Block['element']['text']['text'] .= "\n".$Line['body'];;
$Block['element']['text']['text'] .= "\n".$Line['body'];
return $Block;
}
@ -447,8 +481,6 @@ class Parsedown
{
$text = $Block['element']['text']['text'];
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
$Block['element']['text']['text'] = $text;
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(
'name' => 'li',
'handler' => 'li',
@ -527,6 +569,8 @@ class Parsedown
{
$Block['li']['text'] []= '';
$Block['loose'] = true;
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
@ -658,12 +718,12 @@ class Parsedown
protected function blockMarkup($Line)
{
if ($this->markupEscaped)
if ($this->markupEscaped or $this->safeMode)
{
return;
}
if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
{
$element = strtolower($matches[1]);
@ -977,7 +1037,7 @@ class Parsedown
# ~
#
public function line($text)
public function line($text, $nonNestables=array())
{
$markup = '';
@ -993,6 +1053,13 @@ class Parsedown
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);
if ( ! isset($Inline))
@ -1014,6 +1081,13 @@ class Parsedown
$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
$unmarkedText = substr($text, 0, $Inline['position']);
@ -1054,7 +1128,6 @@ class Parsedown
if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
{
$text = $matches[2];
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
$text = preg_replace("/[ ]*\n/", ' ', $text);
return array(
@ -1173,6 +1246,7 @@ class Parsedown
$Element = array(
'name' => 'a',
'handler' => 'line',
'nonNestables' => array('Url', 'Link'),
'text' => null,
'attributes' => array(
'href' => null,
@ -1184,7 +1258,7 @@ class Parsedown
$remainder = $Excerpt['text'];
if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches))
if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
{
$Element['text'] = $matches[1];
@ -1197,7 +1271,7 @@ class Parsedown
return;
}
if (preg_match('/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches))
if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
{
$Element['attributes']['href'] = $matches[1];
@ -1233,8 +1307,6 @@ class Parsedown
$Element['attributes']['title'] = $Definition['title'];
}
$Element['attributes']['href'] = str_replace(array('&', '<'), array('&amp;', '&lt;'), $Element['attributes']['href']);
return array(
'extent' => $extent,
'element' => $Element,
@ -1243,12 +1315,12 @@ class Parsedown
protected function inlineMarkup($Excerpt)
{
if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false)
if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
{
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(
'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(
'markup' => $matches[0],
@ -1323,14 +1395,16 @@ class Parsedown
if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
{
$url = $matches[0][0];
$Inline = array(
'extent' => strlen($matches[0][0]),
'position' => $matches[0][1],
'element' => array(
'name' => 'a',
'text' => $matches[0][0],
'text' => $url,
'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))
{
$url = str_replace(array('&', '<'), array('&amp;', '&lt;'), $matches[1]);
$url = $matches[1];
return array(
'extent' => strlen($matches[0]),
@ -1381,6 +1455,11 @@ class Parsedown
protected function element(array $Element)
{
if ($this->safeMode)
{
$Element = $this->sanitiseElement($Element);
}
$markup = '<'.$Element['name'];
if (isset($Element['attributes']))
@ -1392,7 +1471,7 @@ class Parsedown
continue;
}
$markup .= ' '.$name.'="'.$value.'"';
$markup .= ' '.$name.'="'.self::escape($value).'"';
}
}
@ -1400,13 +1479,18 @@ class Parsedown
{
$markup .= '>';
if (!isset($Element['nonNestables']))
{
$Element['nonNestables'] = array();
}
if (isset($Element['handler']))
{
$markup .= $this->{$Element['handler']}($Element['text']);
$markup .= $this->{$Element['handler']}($Element['text'], $Element['nonNestables']);
}
else
{
$markup .= $Element['text'];
$markup .= self::escape($Element['text'], true);
}
$markup .= '</'.$Element['name'].'>';
@ -1465,10 +1549,77 @@ class Parsedown
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
#
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')
{
if (isset(self::$instances[$name]))
@ -1519,10 +1670,10 @@ class Parsedown
'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
'i', 'rp', 'del', 'code', 'strike', 'marquee',
'q', 'rt', 'ins', 'font', 'strong',
's', 'tt', 'sub', 'mark',
'u', 'xm', 'sup', 'nobr',
'var', 'ruby',
'wbr', 'span',
'time',
's', 'tt', 'kbd', 'mark',
'u', 'xm', 'sub', 'nobr',
'sup', 'ruby',
'var', 'span',
'wbr', 'time',
);
}

View File

@ -1,4 +1,4 @@
RELEASE=1.6.0
RELEASE=1.7.1
PARSEDOWN=parsedown-$RELEASE
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

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
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
tar xzf php-font-lib-0.4.tar.gz --strip-components 1 -C lib/php-font-lib php-font-lib-0.4/{src,LICENSE}
rm php-font-lib-0.4.tar.gz
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.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
tar xzf php-svg-lib-0.3.tar.gz --strip-components 1 -C lib/php-svg-lib php-svg-lib-0.3/src
rm php-svg-lib-0.3.tar.gz
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.2.tar.gz

View File

@ -157,6 +157,10 @@ class BinaryStream {
return ord($this->read(1));
}
public function readUInt8Many($count) {
return array_values(unpack("C*", $this->read($count)));
}
public function writeUInt8($data) {
return $this->write(chr($data), 1);
}
@ -171,6 +175,10 @@ class BinaryStream {
return $v;
}
public function readInt8Many($count) {
return array_values(unpack("c*", $this->read($count)));
}
public function writeInt8($data) {
if ($data < 0) {
$data += 0x100;
@ -185,6 +193,10 @@ class BinaryStream {
return $a["n"];
}
public function readUInt16Many($count) {
return array_values(unpack("n*", $this->read($count * 2)));
}
public function readUFWord() {
return $this->readUInt16();
}
@ -198,7 +210,8 @@ class BinaryStream {
}
public function readInt16() {
$v = $this->readUInt16();
$a = unpack("nn", $this->read(2));
$v = $a["n"];
if ($v >= 0x8000) {
$v -= 0x10000;
@ -207,6 +220,17 @@ class BinaryStream {
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() {
return $this->readInt16();
}
@ -250,6 +274,13 @@ class BinaryStream {
public function readLongDateTime() {
$this->readUInt32(); // ignored
$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);
}
@ -319,6 +350,18 @@ class BinaryStream {
if ($type[0] == self::char) {
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();
for ($i = 0; $i < $type[1]; $i++) {
@ -376,7 +419,9 @@ class BinaryStream {
$ret = 0;
for ($i = 0; $i < $type[1]; $i++) {
$ret += $this->w($type[0], $data[$i]);
if (isset($data[$i])) {
$ret += $this->w($type[0], $data[$i]);
}
}
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;
use FontLib\Exception\FontNotFoundException;
/**
* Generic font file.
*
@ -22,6 +24,10 @@ class Font {
* @return TrueType\File|null $file
*/
public static function load($file) {
if(!file_exists($file)){
throw new FontNotFoundException($file);
}
$header = file_get_contents($file, false, null, null, 4);
$class = null;

View File

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

View File

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

View File

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

View File

@ -31,7 +31,7 @@ class glyf extends Table {
foreach ($real_loca as $gid => $location) {
$_offset = $offset + $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;

View File

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

View File

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

View File

@ -32,7 +32,7 @@ class loca extends Table {
$loc = unpack("n*", $d);
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);
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",
);
static $plaformSpecific = array(
static $platformSpecific = array(
// Unicode
0 => array(
0 => "Default semantics",
@ -190,4 +190,4 @@ class name extends Table {
return $length;
}
}
}

View File

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

View File

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

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

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

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;
@ -21,7 +21,7 @@ abstract class AbstractTag
/** @var Style */
protected $style;
protected $attributes;
protected $attributes = array();
protected $hasShape = true;
@ -180,4 +180,4 @@ abstract class AbstractTag
}
}
}
}
}

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,13 +3,13 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;
use Svg\Gradient\Stop;
use Svg\Gradient;
use Svg\Style;
class LinearGradient extends AbstractTag
@ -19,7 +19,7 @@ class LinearGradient extends AbstractTag
protected $x2;
protected $y2;
/** @var Stop[] */
/** @var Gradient\Stop[] */
protected $stops = array();
public function start($attributes)
@ -47,7 +47,7 @@ class LinearGradient extends AbstractTag
continue;
}
$_stop = new Stop();
$_stop = new Gradient\Stop();
$_attributes = $_child->attributes;
// Style

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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;

View File

@ -3,7 +3,7 @@
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @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) {