220 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			PHP
		
	
	
		
		
			
		
	
	
			220 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			PHP
		
	
	
| 
								 | 
							
								<?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
							 |