You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
219 lines
9.2 KiB
219 lines
9.2 KiB
<?php |
|
|
|
/** |
|
* Validates a font family list according to CSS spec |
|
*/ |
|
class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef |
|
{ |
|
|
|
protected $mask = null; |
|
|
|
public function __construct() |
|
{ |
|
$this->mask = '_- '; |
|
for ($c = 'a'; $c <= 'z'; $c++) { |
|
$this->mask .= $c; |
|
} |
|
for ($c = 'A'; $c <= 'Z'; $c++) { |
|
$this->mask .= $c; |
|
} |
|
for ($c = '0'; $c <= '9'; $c++) { |
|
$this->mask .= $c; |
|
} // cast-y, but should be fine |
|
// special bytes used by UTF-8 |
|
for ($i = 0x80; $i <= 0xFF; $i++) { |
|
// We don't bother excluding invalid bytes in this range, |
|
// because the our restriction of well-formed UTF-8 will |
|
// prevent these from ever occurring. |
|
$this->mask .= chr($i); |
|
} |
|
|
|
/* |
|
PHP's internal strcspn implementation is |
|
O(length of string * length of mask), making it inefficient |
|
for large masks. However, it's still faster than |
|
preg_match 8) |
|
for (p = s1;;) { |
|
spanp = s2; |
|
do { |
|
if (*spanp == c || p == s1_end) { |
|
return p - s1; |
|
} |
|
} while (spanp++ < (s2_end - 1)); |
|
c = *++p; |
|
} |
|
*/ |
|
// possible optimization: invert the mask. |
|
} |
|
|
|
/** |
|
* @param string $string |
|
* @param HTMLPurifier_Config $config |
|
* @param HTMLPurifier_Context $context |
|
* @return bool|string |
|
*/ |
|
public function validate($string, $config, $context) |
|
{ |
|
static $generic_names = array( |
|
'serif' => true, |
|
'sans-serif' => true, |
|
'monospace' => true, |
|
'fantasy' => true, |
|
'cursive' => true |
|
); |
|
$allowed_fonts = $config->get('CSS.AllowedFonts'); |
|
|
|
// assume that no font names contain commas in them |
|
$fonts = explode(',', $string); |
|
$final = ''; |
|
foreach ($fonts as $font) { |
|
$font = trim($font); |
|
if ($font === '') { |
|
continue; |
|
} |
|
// match a generic name |
|
if (isset($generic_names[$font])) { |
|
if ($allowed_fonts === null || isset($allowed_fonts[$font])) { |
|
$final .= $font . ', '; |
|
} |
|
continue; |
|
} |
|
// match a quoted name |
|
if ($font[0] === '"' || $font[0] === "'") { |
|
$length = strlen($font); |
|
if ($length <= 2) { |
|
continue; |
|
} |
|
$quote = $font[0]; |
|
if ($font[$length - 1] !== $quote) { |
|
continue; |
|
} |
|
$font = substr($font, 1, $length - 2); |
|
} |
|
|
|
$font = $this->expandCSSEscape($font); |
|
|
|
// $font is a pure representation of the font name |
|
|
|
if ($allowed_fonts !== null && !isset($allowed_fonts[$font])) { |
|
continue; |
|
} |
|
|
|
if (ctype_alnum($font) && $font !== '') { |
|
// very simple font, allow it in unharmed |
|
$final .= $font . ', '; |
|
continue; |
|
} |
|
|
|
// bugger out on whitespace. form feed (0C) really |
|
// shouldn't show up regardless |
|
$font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font); |
|
|
|
// Here, there are various classes of characters which need |
|
// to be treated differently: |
|
// - Alphanumeric characters are essentially safe. We |
|
// handled these above. |
|
// - Spaces require quoting, though most parsers will do |
|
// the right thing if there aren't any characters that |
|
// can be misinterpreted |
|
// - Dashes rarely occur, but they fairly unproblematic |
|
// for parsing/rendering purposes. |
|
// The above characters cover the majority of Western font |
|
// names. |
|
// - Arbitrary Unicode characters not in ASCII. Because |
|
// most parsers give little thought to Unicode, treatment |
|
// of these codepoints is basically uniform, even for |
|
// punctuation-like codepoints. These characters can |
|
// show up in non-Western pages and are supported by most |
|
// major browsers, for example: "MS 明朝" is a |
|
// legitimate font-name |
|
// <http://ja.wikipedia.org/wiki/MS_明朝>. See |
|
// the CSS3 spec for more examples: |
|
// <http://www.w3.org/TR/2011/WD-css3-fonts-20110324/localizedfamilynames.png> |
|
// You can see live samples of these on the Internet: |
|
// <http://www.google.co.jp/search?q=font-family+MS+明朝|ゴシック> |
|
// However, most of these fonts have ASCII equivalents: |
|
// for example, 'MS Mincho', and it's considered |
|
// professional to use ASCII font names instead of |
|
// Unicode font names. Thanks Takeshi Terada for |
|
// providing this information. |
|
// The following characters, to my knowledge, have not been |
|
// used to name font names. |
|
// - Single quote. While theoretically you might find a |
|
// font name that has a single quote in its name (serving |
|
// as an apostrophe, e.g. Dave's Scribble), I haven't |
|
// been able to find any actual examples of this. |
|
// Internet Explorer's cssText translation (which I |
|
// believe is invoked by innerHTML) normalizes any |
|
// quoting to single quotes, and fails to escape single |
|
// quotes. (Note that this is not IE's behavior for all |
|
// CSS properties, just some sort of special casing for |
|
// font-family). So a single quote *cannot* be used |
|
// safely in the font-family context if there will be an |
|
// innerHTML/cssText translation. Note that Firefox 3.x |
|
// does this too. |
|
// - Double quote. In IE, these get normalized to |
|
// single-quotes, no matter what the encoding. (Fun |
|
// fact, in IE8, the 'content' CSS property gained |
|
// support, where they special cased to preserve encoded |
|
// double quotes, but still translate unadorned double |
|
// quotes into single quotes.) So, because their |
|
// fixpoint behavior is identical to single quotes, they |
|
// cannot be allowed either. Firefox 3.x displays |
|
// single-quote style behavior. |
|
// - Backslashes are reduced by one (so \\ -> \) every |
|
// iteration, so they cannot be used safely. This shows |
|
// up in IE7, IE8 and FF3 |
|
// - Semicolons, commas and backticks are handled properly. |
|
// - The rest of the ASCII punctuation is handled properly. |
|
// We haven't checked what browsers do to unadorned |
|
// versions, but this is not important as long as the |
|
// browser doesn't /remove/ surrounding quotes (as IE does |
|
// for HTML). |
|
// |
|
// With these results in hand, we conclude that there are |
|
// various levels of safety: |
|
// - Paranoid: alphanumeric, spaces and dashes(?) |
|
// - International: Paranoid + non-ASCII Unicode |
|
// - Edgy: Everything except quotes, backslashes |
|
// - NoJS: Standards compliance, e.g. sod IE. Note that |
|
// with some judicious character escaping (since certain |
|
// types of escaping doesn't work) this is theoretically |
|
// OK as long as innerHTML/cssText is not called. |
|
// We believe that international is a reasonable default |
|
// (that we will implement now), and once we do more |
|
// extensive research, we may feel comfortable with dropping |
|
// it down to edgy. |
|
|
|
// Edgy: alphanumeric, spaces, dashes, underscores and Unicode. Use of |
|
// str(c)spn assumes that the string was already well formed |
|
// Unicode (which of course it is). |
|
if (strspn($font, $this->mask) !== strlen($font)) { |
|
continue; |
|
} |
|
|
|
// Historical: |
|
// In the absence of innerHTML/cssText, these ugly |
|
// transforms don't pose a security risk (as \\ and \" |
|
// might--these escapes are not supported by most browsers). |
|
// We could try to be clever and use single-quote wrapping |
|
// when there is a double quote present, but I have choosen |
|
// not to implement that. (NOTE: you can reduce the amount |
|
// of escapes by one depending on what quoting style you use) |
|
// $font = str_replace('\\', '\\5C ', $font); |
|
// $font = str_replace('"', '\\22 ', $font); |
|
// $font = str_replace("'", '\\27 ', $font); |
|
|
|
// font possibly with spaces, requires quoting |
|
$final .= "'$font', "; |
|
} |
|
$final = rtrim($final, ', '); |
|
if ($final === '') { |
|
return false; |
|
} |
|
return $final; |
|
} |
|
|
|
} |
|
|
|
// vim: et sw=4 sts=4
|
|
|