[SECURITY] t3lib_div::quoteJSvalue allows XSS
[Packages/TYPO3.CMS.git] / t3lib / codec / class.t3lib_codec_javascriptencoder.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2012 Franz G. Jahn <franzjahn@cron-it.de>
6 * (c) 2012 Helmut Hummel <helmut.hummel@typo3.org>
7 * All rights reserved
8 *
9 * This script is part of the TYPO3 project. The TYPO3 project is
10 * free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * The GNU General Public License can be found at
16 * http://www.gnu.org/copyleft/gpl.html.
17 *
18 * This script is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * This copyright notice MUST APPEAR in all copies of the script!
24 ***************************************************************/
25
26 /**
27 * Adopted from OWASP Enterprise Security API (ESAPI) reference implementation for the JavaScript Codec.
28 * Original Author: Mike Boberski
29 *
30 * This class provides encoding for user input that is intended to be used in a JavaScript context.
31 * It encodes all characters except alphanumericals and the immune characters to a hex representation.
32 *
33 * @package TYPO3
34 * @subpackage t3lib
35 *
36 * @author Mike Boberski <boberski_michael@bah.com>
37 * @copyright 2009-2010 The OWASP Foundation
38 * @link http://www.owasp.org/index.php/ESAPI
39 *
40 * @author Franz G. Jahn <franzjahn@cron-it.de>
41 * @author Helmut Hummel <helmut.hummel@typo3.org>
42 */
43 class t3lib_codec_JavaScriptEncoder implements t3lib_Singleton {
44 /**
45 * A map where the keys are ordinal values of non-alphanumeric single-byte
46 * characters and the values are hexadecimal equivalents as strings.
47 *
48 * @var array
49 */
50 protected $hexMatrix = array();
51
52 /**
53 * Characters that are immune (not dangerous) in the JavaScript context
54 *
55 * @var array
56 */
57 protected $immuneCharacters = array(',', '.', '_' );
58
59 /**
60 * Encoding that is used in the current context
61 *
62 * @var string
63 */
64 protected $encoding;
65
66 /**
67 * TYPO3 charset encoding object
68 *
69 * @var t3lib_cs
70 */
71 protected $charsetConversion = NULL;
72
73 /**
74 * Populates the $hex map of non-alphanumeric single-byte characters.
75 *
76 * Alphanumerical character are set to NULL in the matrix.
77 */
78 public function __construct() {
79 $this->charsetConversion = t3lib_div::makeInstance('t3lib_cs');
80 $this->encoding = $this->getEncoding();
81
82 for ($i = 0; $i < 256; $i++) {
83 if (($i >= ord('0') && $i <= ord('9')) || ($i >= ord('A') && $i <= ord('Z')) || ($i >= ord('a') && $i <= ord('z'))) {
84 $this->hexMatrix[$i] = NULL;
85 } else {
86 $this->hexMatrix[$i] = dechex($i);
87 }
88 }
89 }
90
91 /**
92 * Encodes a string for JavaScript.
93 *
94 * @param string $input The string to encode, may be empty.
95 * @return string The encoded string.
96 */
97 public function encode($input) {
98 $normalizedInput = $this->charsetConversion->conv($input, $this->encoding, 'utf-8');
99 $stringLength = $this->charsetConversion->strlen('utf-8', $normalizedInput);
100 $encodedString = '';
101 for ($i = 0; $i < $stringLength; $i++) {
102 $c = $this->charsetConversion->substr('utf-8', $normalizedInput, $i, 1);
103 $encodedString .= $this->encodeCharacter($c);
104 }
105
106 return $encodedString;
107 }
108
109 /**
110 * Returns backslash encoded numeric format. Does not use backslash
111 * character escapes such as, \" or \' as these may cause parsing problems.
112 * For example, if a javascript attribute, such as onmouseover, contains
113 * a \" that will close the entire attribute and allow an attacker to inject
114 * another script attribute.
115 *
116 * @param string $character utf-8 character that needs to be encoded
117 * @return string encoded character
118 */
119 protected function encodeCharacter($character) {
120 if ($this->isImmuneCharacter($character)) {
121 return $character;
122 }
123
124 $ordinalValue = $this->charsetConversion->utf8CharToUnumber($character);
125
126 // Check for alphanumeric characters
127 $hex = $this->getHexForNonAlphanumeric($ordinalValue);
128 if ($hex === NULL) {
129 return $character;
130 }
131
132 // Encode up to 256 with \\xHH
133 if ($ordinalValue < 256) {
134 $pad = substr('00', strlen($hex));
135 return '\\x' . $pad . strtoupper($hex);
136 }
137
138 // Otherwise encode with \\uHHHH
139 $pad = substr('0000', strlen($hex));
140 return '\\u' . $pad . strtoupper($hex);
141 }
142
143 /**
144 * Checks if the given character is one of the immune characters
145 *
146 * @param string $character utf-8 character to search for, must not be empty
147 * @return boolean TRUE if character is immune, FALSE otherwise
148 */
149 protected function isImmuneCharacter($character) {
150 return in_array($character, $this->immuneCharacters, TRUE);
151 }
152
153 /**
154 * Returns the ordinal value as a hex string of any character that is not a
155 * single-byte alphanumeric. The character should be supplied as a string in
156 * the utf-8 character encoding.
157 * If the character is an alphanumeric character with ordinal value below 255,
158 * then this method will return NULL.
159 *
160 * @param integer $ordinalValue Ordinal value of the character
161 * @return string hexadecimal ordinal value of non-alphanumeric characters or NULL otherwise.
162 */
163 protected function getHexForNonAlphanumeric($ordinalValue) {
164 if ($ordinalValue <= 255) {
165 return $this->hexMatrix[$ordinalValue];
166 }
167 return dechex($ordinalValue);
168 }
169
170 /**
171 * Gets the encoding depending on the current context (TYPO3_MODE)
172 *
173 * @return string
174 */
175 protected function getEncoding() {
176 if (TYPO3_MODE == 'FE') {
177 $charset = $GLOBALS['TSFE']->renderCharset;
178 } elseif (is_object($GLOBALS['LANG'])) {
179 $charset = $GLOBALS['LANG']->charSet;
180 } else if (!empty($GLOBALS['TYPO3_CONF_VARS']['BE']['forceCharset'])) {
181 $charset = $GLOBALS['TYPO3_CONF_VARS']['BE']['forceCharset'];
182 } else {
183 $charset = 'utf-8';
184 }
185
186 return $charset;
187 }
188 }
189 ?>