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',
100 'label', // This is needed for FROM Email Address configuration. dgg
101 'url', // This is needed for navigation items urls
103 'msg_text', // message templates’ text versions
104 'text_message', // (send an) email to contact’s and CiviMail’s text version
105 'data', // data i/p of persistent table
106 'sqlQuery', // CRM-6673
109 'new', // The 'new' text in word replacements
112 return self
::$skipFields;
116 * @param string $fldName
117 * @return bool TRUE if encoding should be skipped for this field
119 public static function isSkippedField($fldName) {
120 $skipFields = self
::getSkipFields();
122 // Field should be skipped
123 if (in_array($fldName, $skipFields)) {
126 // Field is multilingual and after cutting off _xx_YY should be skipped (CRM-7230)…
127 if ((preg_match('/_[a-z][a-z]_[A-Z][A-Z]$/', $fldName) && in_array(substr($fldName, 0, -6), $skipFields))) {
130 // Field can take multiple entries, eg. fieldName[1], fieldName[2], etc.
131 // We remove the index and check again if the fieldName in the list of skipped fields.
133 if (preg_match('/^(.*)\[\d+\]/', $fldName, $matches) && in_array($matches[1], $skipFields)) {
141 * This function is going to filter the
142 * submitted values across XSS vulnerability.
144 * @param array|string $values
145 * @param bool $castToString If TRUE, all scalars will be filtered (and therefore cast to strings)
146 * If FALSE, then non-string values will be preserved
148 public static function encodeInput(&$values, $castToString = TRUE) {
149 if (is_array($values)) {
150 foreach ($values as &$value) {
151 self
::encodeInput($value);
153 } elseif ($castToString ||
is_string($values)) {
154 $values = str_replace(array('<', '>'), array('<', '>'), $values);
158 public static function decodeOutput(&$values, $castToString = TRUE) {
159 if (is_array($values)) {
160 foreach ($values as &$value) {
161 self
::decodeOutput($value);
163 } elseif ($castToString ||
is_string($values)) {
164 $values = str_replace(array('<', '>'), array('<', '>'), $values);
171 public function fromApiInput($apiRequest) {
172 $lowerAction = strtolower($apiRequest['action']);
173 if ($apiRequest['version'] == 3 && in_array($lowerAction, array('get', 'create'))) {
174 // note: 'getsingle', 'replace', 'update', and chaining all build on top of 'get'/'create'
175 foreach ($apiRequest['params'] as $key => $value) {
176 // Don't apply escaping to API control parameters (e.g. 'api.foo' or 'options.foo')
177 // and don't apply to other skippable fields
178 if (!self
::isApiControlField($key) && !self
::isSkippedField($key)) {
179 self
::encodeInput($apiRequest['params'][$key], FALSE);
182 } elseif ($apiRequest['version'] == 3 && $lowerAction == 'setvalue') {
183 if (isset($apiRequest['params']['field']) && isset($apiRequest['params']['value'])) {
184 if (!self
::isSkippedField($apiRequest['params']['field'])) {
185 self
::encodeInput($apiRequest['params']['value'], FALSE);
195 public function toApiOutput($apiRequest, $result) {
196 $lowerAction = strtolower($apiRequest['action']);
197 if ($apiRequest['version'] == 3 && in_array($lowerAction, array('get', 'create', 'setvalue'))) {
198 foreach ($result as $key => $value) {
199 // Don't apply escaping to API control parameters (e.g. 'api.foo' or 'options.foo')
200 // and don't apply to other skippable fields
201 if (!self
::isApiControlField($key) && !self
::isSkippedField($key)) {
202 self
::decodeOutput($result[$key], FALSE);
213 protected function isApiControlField($key) {
214 return (FALSE !== strpos($key, '.'));