Skip to content

Instantly share code, notes, and snippets.

@najamelan
Last active July 4, 2019 20:25
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save najamelan/e2bc8ed92911537e4475 to your computer and use it in GitHub Desktop.
Save najamelan/e2bc8ed92911537e4475 to your computer and use it in GitHub Desktop.
Escape.php
<?php
/*
Correct output escaping in php is ridiculously difficult. This class does not pretend to be perfect, but is an attempt for myself to do better than scattering htmlspecialchars all over my code.
This is basically a wrapper around basic functionality like htmlspecialchars. For better security, inspired on code from Twig, who inspired themselves on Zend who inspired themselves on ESAPI. Security notice: all of the above fail to escape the comma in javascript context. This class doesn't and it also provides you with a wrapper around HTML purifier.
This comment has some bits and bops of text to make the point. References:
http://blog.astrumfutura.com/2012/06/automatic-output-escaping-in-php-and-the-real-future-of-preventing-cross-site-scripting-xss/
http://blog.astrumfutura.com/2012/03/a-hitchhikers-guide-to-cross-site-scripting-xss-in-php-part-1-how-not-to-use-htmlspecialchars-for-output-escaping/
https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet
Context Always Determines Your Escaping Strategy
Briefly, the context of a data insertion determines how a client browser will interpret that data. For example, data output into a HTML attribute value is, surprise, in the HTML Attribute context. This means a browser’s HTML renderer will treat it as a HTML attribute. I know, this revelation is so shocking that my brain is in danger of exploding. However, let’s imagine that the output is inserted into an onmouseover attribute. This obviously means that it’s in the HTML Attribute context. However, it also means that it’s in a Javascript context – the attribute value is executable by the browser’s Javascript engine when a mouse-over action is detected for that element. Contexts can be nested – so can escaping needs.
Each such context demands a specific escaping strategy. Escaping for the HTML Attribute context is not the same as escaping for the Javascript context. Both have completely different escaping rules (i.e. different special characters and replacement strings). If you apply HTML escaping (e.g. htmlspecialchars()) to a Javascript string – you completely fail to escape properly against XSS. Worse, if the output has entered two contexts (i.e. our onmouseover attribute value), you must escape it twice – once for HTML, and once for Javascript. Oh, and you need to escape them in the correct order: Javascript first and HTML second. Why? Because attribute values are HTML unescaped before the browser will interpret the Javascript it might contain.
The main contexts to be aware of are: HTML Body (element text nodes), HTML Attribute, Javascript, CSS, Untrusted URI, GET/POST parameters (also URI related) and DOM. All have varying escaping/validation strategies that may depend on the actual content. For example, inserting strings in HTML Body contexts is quite different from inserting HTML markup into that context – the latter needs a HTML sanitiser rather than an escaper! As such, an escaping strategy may require a validation task instead of, or complimentary to, an escaping function. Determining context also relies on understanding how your output is manipulated between a HTTP request being received and having a client browser render a viewable form(s) in response to user interaction or pre-programmed events. Just because your templates look nicely escaped, it doesn’t mean that by the time Javascript has finished scrambling them that the rendered version is escaped properly.
So, contexts:
HTML Body (element text nodes) -> Escape::htmlText()
HTML Attribute -> Escape::htmlAttr()
Javascript -> Escape::js()
If you are dealing with javascript context, you should read this to understand the limitation of the escaping function:
https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet
CSS -> Not yet implemented
URI -> Depends where it goes htmlText or htmlAttr but strip off variables first if desired?
GET/POST parameters (also URI related) -> Escape::url()
DOM
*/
/*
* Wordpress also forsees similar functionality, but even though I don't understand everything they do,
* nor do I immediately see flagrant errors, I feel like the twig code is more robust and simple and prefer to write this class.
*
* Based on the Twig escaping code
*
* To use this class safely, you still must adhere to a number of principles:
*
* 1. Be consistent in encoding. Even if you don't test for malformed encodings,
* at least be consistent through input validation, output escaping and browser encoding.
* You must make sure that the html meta tag content-type sets the same encoding as the encoding of Escape
*
* 2. Always quote html attributes
*
* TODO: unit tests see esapi for a bunch
*
*/
include_once dirname( __FILE__ ) . '/HTMLPurifier/HTMLPurifier.standalone.php';
// make compatible with php < 5.4
//
if( ! defined( 'ENT_SUBSTITUTE' ) )
define( 'ENT_SUBSTITUTE', 0 );
// make gettext compatible even if there is no gettext
//
if( ! function_exists( '__' ) )
{
function __( $string ){ return $string; }
// no use of _e allowed, don't add
}
abstract
class Escape
{
// Provides a single place to disable the entire library
// for testing purposes only. Should always be false. Will completely disable the escaping
//
const DISABLED = false;
// The internal encoding. You are expected to send data in this encoding. Can be changed with setEncoding
//
static protected $encoding = 'utf-8';
// html purifier code:
//
static protected $purifierConfig = null;
static protected $purifier = null;
// initialize - you need to call this only if you need html-purifier
//
// it can take a HTMLPurifier_Config file or create a default one.
//
static
function init( $userConfig = false )
{
self::$purifierConfig = $userConfig ?
$userConfig
: HTMLPurifier_Config::createDefault()
;
self::$purifier = new HTMLPurifier( self::$purifierConfig );
return
self::$purifier
;
}
static
function setEncoding( $encoding )
{
// whenever comparing to hardcoded values in this class will be lowercase
//
self::$encoding = strtolower( $encoding );
}
// html purifier
// Send in a purifier or use Escape::init()
//
// TODO: very simple code. Might have to check if the encoding is correct etc
//
static
function purify( $dirty, $purifier = false )
{
// set default value
if( $purifier === false )
$purifier = self::$purifier;
if( self::DISABLED )
{
$purifier->purify( '' ); // creates the ErrorCollector
return $dirty;
}
return
$purifier->purify( $dirty )
;
}
// This allows getting hold of the errors corrected by htmlpurifier if you set
// Core.Errors to true in your config and if you are using the internally stored purifier
// if you keep your own purifier, call ->context->get directly on it
//
// usage: echo Escape::purifyErrorCollector()->getHTMLFormatted( Escape::purifyConfig() );
//
// or error_log( print_r( Escape::purifyErrorCollector()->getRaw(), true ) );
//
//
// TODO: Currently does not do a very elegant way of error handling...
//
static
function purifyErrorCollector()
{
if( ! self::$purifierConfig->get( 'Core.CollectErrors' ) )
trigger_error( __( "You need to set Core.Errors before running purify" ), E_USER_ERROR );
return
self::$purifier->context->get( 'ErrorCollector' )
;
}
static
function purifyConfig()
{
return
self::$purifierConfig
;
}
static
function htmlText( $dirty )
{
if( self::DISABLED )
return $dirty;
static $htmlspecialcharsCharsets = array
(
'iso-8859-1' => true, 'iso8859-1' => true
, 'iso-8859-15' => true, 'iso8859-15' => true
, 'utf-8' => true, 'cp866' => true
, 'ibm866' => true, '866' => true
, 'cp1251' => true, 'windows-1251' => true
, 'win-1251' => true, '1251' => true
, 'cp1252' => true, 'windows-1252' => true
, '1252' => true, 'koi8-r' => true
, 'koi8-ru' => true, 'koi8r' => true
, 'big5' => true, '950' => true
, 'gb2312' => true, '936' => true
, 'big5-hkscs' => true, 'shift_jis' => true
, 'sjis' => true, '932' => true
, 'euc-jp' => true, 'eucjp' => true
, 'iso8859-5' => true, 'iso-8859-5' => true
, 'macroman' => true
);
// TODO: rewrite this to avoid double code...
// not 100% sure that this is without risk, converting back and forth. Do all conversion functions behave well with malformed input???
// for best practice, use an encoding that is supported.
// htmlspecialchars does not support all encodings, so make sure we only call it with a valid one
//
if( isset( $htmlspecialcharsCharsets[ self::$encoding ] ) )
{
// avoid double encoding, it causes html entities to show up in the main text
//
$dirty = html_entity_decode( $dirty, ENT_QUOTES, 'UTF-8' );
return
htmlspecialchars( $dirty, ENT_QUOTES | ENT_SUBSTITUTE, self::$encoding )
// ENT_QUOTES > replace both single and double quotes
// ENT_SUBSTITUTE > replace invalid utf characters rather than returning an empty string
;
}
// else we need to do some conversion
//
trigger_error( __( 'Escape::htmlText: Not using utf-8 as internal encoding' ), E_USER_WARNING );
$dirty = self::convert_encoding( $dirty, 'utf-8', self::$encoding );
// avoid double encoding, it causes html entities to show up in the main text
//
$dirty = html_entity_decode( $dirty, ENT_QUOTES , 'UTF-8' );
$dirty = htmlspecialchars ( $dirty, ENT_QUOTES | ENT_SUBSTITUTE, 'utf-8' );
return
self::convert_encoding( $dirty, self::$encoding, 'utf-8' )
;
}
// use when you enter content into a HTML attribute that does not take js, nor uris, nor css.
// In class attributes it can make sense to allow spaces to have multiple classes. In this case it is very important you quote your attribute.
// This function allows letting the space pass
//
static
function htmlAttrAllowSpace( $dirty )
{
if( self::DISABLED )
return $dirty;
return
self::replaceWithCallback( '#[^a-zA-Z0-9,._ -]#Su', array( 'Escape', 'escape_html_attr_callback' ), $dirty )
;
}
static
function htmlAttr( $dirty)
{
if( self::DISABLED )
return $dirty;
return
// I allow a space, because class attributes often have multiple entries with spaces. You should always quote attributes escaped by this function
self::replaceWithCallback( '#[^a-zA-Z0-9,._-]#Su', array( 'Escape', 'escape_html_attr_callback' ), $dirty );
}
// Use this when inserting data into a javascript context (eg. A number that becomes a javascript parameter)
//
static
function js( $dirty )
{
if( self::DISABLED )
return $dirty;
// escape all non-alphanumeric characters
// into their \xHH or \uHHHH representations
return
self::replaceWithCallback( '#[^a-zA-Z0-9._]#Su', array( 'Escape', 'escape_js_callback' ), $dirty )
;
}
// TODO: does this work with utf-8?
// This should be used for adding data parameters after ? in an url. Escape each piece of data independently
//
static
function urlParam( $dirty )
{
if( self::DISABLED )
return $dirty;
// http://php.net/manual/en/function.rawurlencode.php
//
if( version_compare( PHP_VERSION, '5.3.0', '<' ) )
return str_replace( '%7E', '~', rawurlencode( $dirty ) );
return rawurlencode( $dirty );
}
// TODO: does this work with utf-8?
// This REQUIRES your query string params to be already encoded individually
// does not support 'javascript:' url's
// see: http://stackoverflow.com/questions/3235219/urlencoded-forward-slash-is-breaking-url
//
static
function uri( $dirty )
{
if( self::DISABLED )
return $dirty;
// take of the query string. We won't deal with it
// we split on the last question mark in the string
//
$components = preg_split( '/\?(?!.*\?)/u', $dirty );
$queryString = isset( $components[ 1 ] ) ? '?' . $components[ 1 ] : '';
$protocol = preg_replace( '!^([a-z]+://)?.*!ui' , '$1', $components[ 0 ] );
$path = preg_replace( "!$protocol!u" , '' , $components[ 0 ] );
$encoded = preg_replace( '/%2F/ui', '/', rawurlencode( $path ) );
// http://php.net/manual/en/function.rawurlencode.php
//
if( version_compare( PHP_VERSION, '5.3.0', '<' ) )
$encoded = str_replace( '/%7E/ui', '~', $encoded );
return $protocol . $encoded . $queryString;
}
// use this for href and img src attributes
// TODO: check if it is really necessary to run Escape::htmlAttr. Eg are there any characters left that need escaping after Escape::uri()
//
static
function uriAttr( $dirty )
{
if( self::DISABLED )
return $dirty;
return self::htmlAttr( self::uri( $dirty ) );
}
// Helper functions
// ----------------
static private
function convert_encoding( $string, $to, $from )
{
if( function_exists( 'mb_convert_encoding' ) )
return mb_convert_encoding( $string, $to, $from );
elseif( function_exists( 'iconv' ) )
return iconv( $from, $to, $string );
else
trigger_error( 'No suitable convert encoding function (use utf-8 as your encoding or install the iconv or mbstring extensions).', E_USER_ERROR );
}
static private
function replaceWithCallback( $pattern, $callback, $dirty )
{
if( strlen( $dirty ) == 0 )
return '';
if( 'utf-8' != self::$encoding )
$dirty = self::convert_encoding( $dirty, 'utf-8', self::$encoding );
// if preg_match does not find a match
//
if( 1 !== preg_match( '/^./su', $dirty ) )
trigger_error( 'The string to escape is not a valid utf-8 string.', E_USER_ERROR );
// avoid double encoding, it breaks urls with &amp; for example
//
$dirty = html_entity_decode( $dirty, ENT_QUOTES, 'UTF-8' );
$clean = preg_replace_callback( $pattern, $callback, $dirty );
if( 'utf-8' != self::$encoding )
$clean = self::convert_encoding( $clean, self::$encoding, 'utf-8' );
return $clean;
}
static private
function escape_js_callback( $matches )
{
$char = $matches[ 0 ];
// \xHH (it's only one byte)
//
if( !isset( $char[ 1 ] ) )
return '\x' . strtoupper( substr( '00'.bin2hex( $char ), -2 ) );
// \uHHHH
//
$char = self::convert_encoding( $char, 'UTF-16BE', 'UTF-8' );
return
'\u' . strtoupper( substr( '0000' . bin2hex( $char ), -4 ) )
;
}
/**
* This function is adapted from code coming from Zend Framework.
*
* @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
static private
function escape_html_attr_callback( $matches )
{
static $entityMap = array
(
34 => 'quot', /* quotation mark */
38 => 'amp', /* ampersand */
60 => 'lt', /* less-than sign */
62 => 'gt', /* greater-than sign */
160 => 'nbsp', /* no-break space */
161 => 'iexcl', /* inverted exclamation mark */
162 => 'cent', /* cent sign */
163 => 'pound', /* pound sign */
164 => 'curren', /* currency sign */
165 => 'yen', /* yen sign */
166 => 'brvbar', /* broken bar */
167 => 'sect', /* section sign */
168 => 'uml', /* diaeresis */
169 => 'copy', /* copyright sign */
170 => 'ordf', /* feminine ordinal indicator */
171 => 'laquo', /* left-pointing double angle quotation mark */
172 => 'not', /* not sign */
173 => 'shy', /* soft hyphen */
174 => 'reg', /* registered sign */
175 => 'macr', /* macron */
176 => 'deg', /* degree sign */
177 => 'plusmn', /* plus-minus sign */
178 => 'sup2', /* superscript two */
179 => 'sup3', /* superscript three */
180 => 'acute', /* acute accent */
181 => 'micro', /* micro sign */
182 => 'para', /* pilcrow sign */
183 => 'middot', /* middle dot */
184 => 'cedil', /* cedilla */
185 => 'sup1', /* superscript one */
186 => 'ordm', /* masculine ordinal indicator */
187 => 'raquo', /* right-pointing double angle quotation mark */
188 => 'frac14', /* vulgar fraction one quarter */
189 => 'frac12', /* vulgar fraction one half */
190 => 'frac34', /* vulgar fraction three quarters */
191 => 'iquest', /* inverted question mark */
192 => 'Agrave', /* Latin capital letter a with grave */
193 => 'Aacute', /* Latin capital letter a with acute */
194 => 'Acirc', /* Latin capital letter a with circumflex */
195 => 'Atilde', /* Latin capital letter a with tilde */
196 => 'Auml', /* Latin capital letter a with diaeresis */
197 => 'Aring', /* Latin capital letter a with ring above */
198 => 'AElig', /* Latin capital letter ae */
199 => 'Ccedil', /* Latin capital letter c with cedilla */
200 => 'Egrave', /* Latin capital letter e with grave */
201 => 'Eacute', /* Latin capital letter e with acute */
202 => 'Ecirc', /* Latin capital letter e with circumflex */
203 => 'Euml', /* Latin capital letter e with diaeresis */
204 => 'Igrave', /* Latin capital letter i with grave */
205 => 'Iacute', /* Latin capital letter i with acute */
206 => 'Icirc', /* Latin capital letter i with circumflex */
207 => 'Iuml', /* Latin capital letter i with diaeresis */
208 => 'ETH', /* Latin capital letter eth */
209 => 'Ntilde', /* Latin capital letter n with tilde */
210 => 'Ograve', /* Latin capital letter o with grave */
211 => 'Oacute', /* Latin capital letter o with acute */
212 => 'Ocirc', /* Latin capital letter o with circumflex */
213 => 'Otilde', /* Latin capital letter o with tilde */
214 => 'Ouml', /* Latin capital letter o with diaeresis */
215 => 'times', /* multiplication sign */
216 => 'Oslash', /* Latin capital letter o with stroke */
217 => 'Ugrave', /* Latin capital letter u with grave */
218 => 'Uacute', /* Latin capital letter u with acute */
219 => 'Ucirc', /* Latin capital letter u with circumflex */
220 => 'Uuml', /* Latin capital letter u with diaeresis */
221 => 'Yacute', /* Latin capital letter y with acute */
222 => 'THORN', /* Latin capital letter thorn */
223 => 'szlig', /* Latin small letter sharp sXCOMMAX German Eszett */
224 => 'agrave', /* Latin small letter a with grave */
225 => 'aacute', /* Latin small letter a with acute */
226 => 'acirc', /* Latin small letter a with circumflex */
227 => 'atilde', /* Latin small letter a with tilde */
228 => 'auml', /* Latin small letter a with diaeresis */
229 => 'aring', /* Latin small letter a with ring above */
230 => 'aelig', /* Latin lowercase ligature ae */
231 => 'ccedil', /* Latin small letter c with cedilla */
232 => 'egrave', /* Latin small letter e with grave */
233 => 'eacute', /* Latin small letter e with acute */
234 => 'ecirc', /* Latin small letter e with circumflex */
235 => 'euml', /* Latin small letter e with diaeresis */
236 => 'igrave', /* Latin small letter i with grave */
237 => 'iacute', /* Latin small letter i with acute */
238 => 'icirc', /* Latin small letter i with circumflex */
239 => 'iuml', /* Latin small letter i with diaeresis */
240 => 'eth', /* Latin small letter eth */
241 => 'ntilde', /* Latin small letter n with tilde */
242 => 'ograve', /* Latin small letter o with grave */
243 => 'oacute', /* Latin small letter o with acute */
244 => 'ocirc', /* Latin small letter o with circumflex */
245 => 'otilde', /* Latin small letter o with tilde */
246 => 'ouml', /* Latin small letter o with diaeresis */
247 => 'divide', /* division sign */
248 => 'oslash', /* Latin small letter o with stroke */
249 => 'ugrave', /* Latin small letter u with grave */
250 => 'uacute', /* Latin small letter u with acute */
251 => 'ucirc', /* Latin small letter u with circumflex */
252 => 'uuml', /* Latin small letter u with diaeresis */
253 => 'yacute', /* Latin small letter y with acute */
254 => 'thorn', /* Latin small letter thorn */
255 => 'yuml', /* Latin small letter y with diaeresis */
338 => 'OElig', /* Latin capital ligature oe */
339 => 'oelig', /* Latin small ligature oe */
352 => 'Scaron', /* Latin capital letter s with caron */
353 => 'scaron', /* Latin small letter s with caron */
376 => 'Yuml', /* Latin capital letter y with diaeresis */
402 => 'fnof', /* Latin small letter f with hook */
710 => 'circ', /* modifier letter circumflex accent */
732 => 'tilde', /* small tilde */
913 => 'Alpha', /* Greek capital letter alpha */
914 => 'Beta', /* Greek capital letter beta */
915 => 'Gamma', /* Greek capital letter gamma */
916 => 'Delta', /* Greek capital letter delta */
917 => 'Epsilon', /* Greek capital letter epsilon */
918 => 'Zeta', /* Greek capital letter zeta */
919 => 'Eta', /* Greek capital letter eta */
920 => 'Theta', /* Greek capital letter theta */
921 => 'Iota', /* Greek capital letter iota */
922 => 'Kappa', /* Greek capital letter kappa */
923 => 'Lambda', /* Greek capital letter lambda */
924 => 'Mu', /* Greek capital letter mu */
925 => 'Nu', /* Greek capital letter nu */
926 => 'Xi', /* Greek capital letter xi */
927 => 'Omicron', /* Greek capital letter omicron */
928 => 'Pi', /* Greek capital letter pi */
929 => 'Rho', /* Greek capital letter rho */
931 => 'Sigma', /* Greek capital letter sigma */
932 => 'Tau', /* Greek capital letter tau */
933 => 'Upsilon', /* Greek capital letter upsilon */
934 => 'Phi', /* Greek capital letter phi */
935 => 'Chi', /* Greek capital letter chi */
936 => 'Psi', /* Greek capital letter psi */
937 => 'Omega', /* Greek capital letter omega */
945 => 'alpha', /* Greek small letter alpha */
946 => 'beta', /* Greek small letter beta */
947 => 'gamma', /* Greek small letter gamma */
948 => 'delta', /* Greek small letter delta */
949 => 'epsilon', /* Greek small letter epsilon */
950 => 'zeta', /* Greek small letter zeta */
951 => 'eta', /* Greek small letter eta */
952 => 'theta', /* Greek small letter theta */
953 => 'iota', /* Greek small letter iota */
954 => 'kappa', /* Greek small letter kappa */
955 => 'lambda', /* Greek small letter lambda */
956 => 'mu', /* Greek small letter mu */
957 => 'nu', /* Greek small letter nu */
958 => 'xi', /* Greek small letter xi */
959 => 'omicron', /* Greek small letter omicron */
960 => 'pi', /* Greek small letter pi */
961 => 'rho', /* Greek small letter rho */
962 => 'sigmaf', /* Greek small letter final sigma */
963 => 'sigma', /* Greek small letter sigma */
964 => 'tau', /* Greek small letter tau */
965 => 'upsilon', /* Greek small letter upsilon */
966 => 'phi', /* Greek small letter phi */
967 => 'chi', /* Greek small letter chi */
968 => 'psi', /* Greek small letter psi */
969 => 'omega', /* Greek small letter omega */
977 => 'thetasym', /* Greek theta symbol */
978 => 'upsih', /* Greek upsilon with hook symbol */
982 => 'piv', /* Greek pi symbol */
8194 => 'ensp', /* en space */
8195 => 'emsp', /* em space */
8201 => 'thinsp', /* thin space */
8204 => 'zwnj', /* zero width non-joiner */
8205 => 'zwj', /* zero width joiner */
8206 => 'lrm', /* left-to-right mark */
8207 => 'rlm', /* right-to-left mark */
8211 => 'ndash', /* en dash */
8212 => 'mdash', /* em dash */
8216 => 'lsquo', /* left single quotation mark */
8217 => 'rsquo', /* right single quotation mark */
8218 => 'sbquo', /* single low-9 quotation mark */
8220 => 'ldquo', /* left double quotation mark */
8221 => 'rdquo', /* right double quotation mark */
8222 => 'bdquo', /* double low-9 quotation mark */
8224 => 'dagger', /* dagger */
8225 => 'Dagger', /* double dagger */
8226 => 'bull', /* bullet */
8230 => 'hellip', /* horizontal ellipsis */
8240 => 'permil', /* per mille sign */
8242 => 'prime', /* prime */
8243 => 'Prime', /* double prime */
8249 => 'lsaquo', /* single left-pointing angle quotation mark */
8250 => 'rsaquo', /* single right-pointing angle quotation mark */
8254 => 'oline', /* overline */
8260 => 'frasl', /* fraction slash */
8364 => 'euro', /* euro sign */
8465 => 'image', /* black-letter capital i */
8472 => 'weierp', /* script capital pXCOMMAX Weierstrass p */
8476 => 'real', /* black-letter capital r */
8482 => 'trade', /* trademark sign */
8501 => 'alefsym', /* alef symbol */
8592 => 'larr', /* leftwards arrow */
8593 => 'uarr', /* upwards arrow */
8594 => 'rarr', /* rightwards arrow */
8595 => 'darr', /* downwards arrow */
8596 => 'harr', /* left right arrow */
8629 => 'crarr', /* downwards arrow with corner leftwards */
8656 => 'lArr', /* leftwards double arrow */
8657 => 'uArr', /* upwards double arrow */
8658 => 'rArr', /* rightwards double arrow */
8659 => 'dArr', /* downwards double arrow */
8660 => 'hArr', /* left right double arrow */
8704 => 'forall', /* for all */
8706 => 'part', /* partial differential */
8707 => 'exist', /* there exists */
8709 => 'empty', /* empty set */
8711 => 'nabla', /* nabla */
8712 => 'isin', /* element of */
8713 => 'notin', /* not an element of */
8715 => 'ni', /* contains as member */
8719 => 'prod', /* n-ary product */
8721 => 'sum', /* n-ary summation */
8722 => 'minus', /* minus sign */
8727 => 'lowast', /* asterisk operator */
8730 => 'radic', /* square root */
8733 => 'prop', /* proportional to */
8734 => 'infin', /* infinity */
8736 => 'ang', /* angle */
8743 => 'and', /* logical and */
8744 => 'or', /* logical or */
8745 => 'cap', /* intersection */
8746 => 'cup', /* union */
8747 => 'int', /* integral */
8756 => 'there4', /* therefore */
8764 => 'sim', /* tilde operator */
8773 => 'cong', /* congruent to */
8776 => 'asymp', /* almost equal to */
8800 => 'ne', /* not equal to */
8801 => 'equiv', /* identical toXCOMMAX equivalent to */
8804 => 'le', /* less-than or equal to */
8805 => 'ge', /* greater-than or equal to */
8834 => 'sub', /* subset of */
8835 => 'sup', /* superset of */
8836 => 'nsub', /* not a subset of */
8838 => 'sube', /* subset of or equal to */
8839 => 'supe', /* superset of or equal to */
8853 => 'oplus', /* circled plus */
8855 => 'otimes', /* circled times */
8869 => 'perp', /* up tack */
8901 => 'sdot', /* dot operator */
8968 => 'lceil', /* left ceiling */
8969 => 'rceil', /* right ceiling */
8970 => 'lfloor', /* left floor */
8971 => 'rfloor', /* right floor */
9001 => 'lang', /* left-pointing angle bracket */
9002 => 'rang', /* right-pointing angle bracket */
9674 => 'loz', /* lozenge */
9824 => 'spades', /* black spade suit */
9827 => 'clubs', /* black club suit */
9829 => 'hearts', /* black heart suit */
9830 => 'diams', /* black diamond suit */
);
$chr = $matches[0];
$ord = ord($chr);
// The following replaces characters undefined in HTML with the hex entity for the Unicode replacement character.
if
(
(
$ord <= 0x1f // control characters
&& $chr != "\t"
&& $chr != "\n"
&& $chr != "\r"
)
|| (
$ord >= 0x7f
&& $ord <= 0x9f
)
)
{
return '&#xFFFD;';
}
// Check if the current character to escape has a name entity we should replace it with while grabbing the hex value of the character.
if( strlen( $chr ) == 1 )
$hex = strtoupper( substr( '00' . bin2hex( $chr ), -2 ) );
else
{
// why do we conclude that we have utf-16BE data? What about other multibyte encodings?
$chr = self::convert_encoding( $chr, 'utf-16BE', 'utf-8' );
$hex = strtoupper( substr( '0000' . bin2hex( $chr ), -4 ) );
}
$int = hexdec( $hex );
if( array_key_exists( $int, $entityMap ) )
return sprintf( '&%s;', $entityMap[ $int ] );
// Per OWASP recommendations, we'll use hex entities for any other characters where a named entity does not exist.
return sprintf( '&#x%s;', $hex );
}
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment