3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.3 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
29 * This class captures the encoding practices of CRM-5667 in a reusable
30 * fashion. In this design, all submitted values are partially HTML-encoded
31 * before saving to the database. If a DB reader needs to output in
32 * non-HTML medium, then it should undo the partial HTML encoding.
34 * This class should be short-lived -- 4.3 should introduce an alternative
35 * escaping scheme and consequently remove HTMLInputCoder.
38 * @copyright CiviCRM LLC (c) 2004-2013
43 require_once 'api/Wrapper.php';
44 class CRM_Core_HTMLInputCoder
implements API_Wrapper
{
45 private static $skipFields = NULL;
48 * @var CRM_Core_HTMLInputCoder
50 private static $_singleton = NULL;
53 * @return CRM_Core_HTMLInputCoder
55 public static function singleton() {
56 if (self
::$_singleton === NULL) {
57 self
::$_singleton = new CRM_Core_HTMLInputCoder();
59 return self
::$_singleton;
63 * @return array<string> list of field names
65 public static function getSkipFields() {
66 if (self
::$skipFields === NULL) {
67 self
::$skipFields = array(
81 'thankyou_footer_text',
88 'confirm_footer_text',
97 'premiums_intro_text',
101 'label', // This is needed for FROM Email Address configuration. dgg
102 'url', // This is needed for navigation items urls
104 'msg_text', // message templates’ text versions
105 'text_message', // (send an) email to contact’s and CiviMail’s text version
106 'data', // data i/p of persistent table
107 'sqlQuery', // CRM-6673
110 'new', // The 'new' text in word replacements
113 return self
::$skipFields;
117 * @param string $fldName
118 * @return bool TRUE if encoding should be skipped for this field
120 public static function isSkippedField($fldName) {
121 $skipFields = self
::getSkipFields();
123 // Field should be skipped
124 if (in_array($fldName, $skipFields)) {
127 // Field is multilingual and after cutting off _xx_YY should be skipped (CRM-7230)…
128 if ((preg_match('/_[a-z][a-z]_[A-Z][A-Z]$/', $fldName) && in_array(substr($fldName, 0, -6), $skipFields))) {
131 // Field can take multiple entries, eg. fieldName[1], fieldName[2], etc.
132 // We remove the index and check again if the fieldName in the list of skipped fields.
134 if (preg_match('/^(.*)\[\d+\]/', $fldName, $matches) && in_array($matches[1], $skipFields)) {
142 * This function is going to filter the
143 * submitted values across XSS vulnerability.
145 * @param array|string $values
146 * @param bool $castToString If TRUE, all scalars will be filtered (and therefore cast to strings)
147 * If FALSE, then non-string values will be preserved
149 public static function encodeInput(&$values, $castToString = TRUE) {
150 if (is_array($values)) {
151 foreach ($values as &$value) {
152 self
::encodeInput($value);
154 } elseif ($castToString ||
is_string($values)) {
155 $values = str_replace(array('<', '>'), array('<', '>'), $values);
159 public static function decodeOutput(&$values, $castToString = TRUE) {
160 if (is_array($values)) {
161 foreach ($values as &$value) {
162 self
::decodeOutput($value);
164 } elseif ($castToString ||
is_string($values)) {
165 $values = str_replace(array('<', '>'), array('<', '>'), $values);
172 public function fromApiInput($apiRequest) {
173 $lowerAction = strtolower($apiRequest['action']);
174 if ($apiRequest['version'] == 3 && in_array($lowerAction, array('get', 'create'))) {
175 // note: 'getsingle', 'replace', 'update', and chaining all build on top of 'get'/'create'
176 foreach ($apiRequest['params'] as $key => $value) {
177 // Don't apply escaping to API control parameters (e.g. 'api.foo' or 'options.foo')
178 // and don't apply to other skippable fields
179 if (!self
::isApiControlField($key) && !self
::isSkippedField($key)) {
180 self
::encodeInput($apiRequest['params'][$key], FALSE);
183 } elseif ($apiRequest['version'] == 3 && $lowerAction == 'setvalue') {
184 if (isset($apiRequest['params']['field']) && isset($apiRequest['params']['value'])) {
185 if (!self
::isSkippedField($apiRequest['params']['field'])) {
186 self
::encodeInput($apiRequest['params']['value'], FALSE);
196 public function toApiOutput($apiRequest, $result) {
197 $lowerAction = strtolower($apiRequest['action']);
198 if ($apiRequest['version'] == 3 && in_array($lowerAction, array('get', 'create', 'setvalue'))) {
199 foreach ($result as $key => $value) {
200 // Don't apply escaping to API control parameters (e.g. 'api.foo' or 'options.foo')
201 // and don't apply to other skippable fields
202 if (!self
::isApiControlField($key) && !self
::isSkippedField($key)) {
203 self
::decodeOutput($result[$key], FALSE);
214 protected function isApiControlField($key) {
215 return (FALSE !== strpos($key, '.'));