Fixing content type ordering when content_type is not defined.
[akelos.git] / lib / AkActionView / AkPhpCodeSanitizer.php
blobf03c4f5e2b68fb81a827e788343b3db2459f4160
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4 // +----------------------------------------------------------------------+
5 // | Akelos Framework - http://www.akelos.org |
6 // +----------------------------------------------------------------------+
7 // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez |
8 // | Released under the GNU Lesser General Public License, see LICENSE.txt|
9 // +----------------------------------------------------------------------+
11 /**
12 * @package ActionView
13 * @subpackage TemplateEngines
14 * @author Bermi Ferrer <bermi a.t akelos c.om>
15 * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
16 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
19 /**
20 * The AkPhpCodeSanitizer ensures that Action View templates do not contain illegal function/variable calls
21 * it is used by the AkPhpTemplateHander by default. If you want to stablish your own
22 * set of forbidden functionalities extend this class and set AK_PHP_CODE_SANITIZER_FOR_TEMPLATE_HANDLER
23 * with the name of your newly created class.
25 class AkPhpCodeSanitizer
27 var $Analyzer;
28 var $_invalid = array();
29 var $secure_active_record_method_calls = false;
30 var $_errors = array();
31 var $_options = array();
32 var $_protedted_types = array('constructs','variables','member variables','functions','classes','methods');
34 function clearErrors()
36 $this->_errors = array();
37 $this->_invalid = array();
40 function setOptions($options)
42 $this->_options = $options;
45 function isCodeSecure($code =null, $raise_if_insecure = true)
47 $this->clearErrors();
48 if(empty($code) && isset($this->_options['code'])){
49 $code =& $this->_options['code'];
51 $this->AnalyzeCode($code);
52 if(!defined('AK_PHP_CODE_SANITIZER_SKIP_VARIABLES') || !AK_PHP_CODE_SANITIZER_SKIP_VARIABLES){
53 $this->secureVariables($code);
55 if(!defined('AK_PHP_CODE_SANITIZER_SKIP_FUNCTIONS') || !AK_PHP_CODE_SANITIZER_SKIP_FUNCTIONS){
56 $this->secureFunctions($code);
58 if(!defined('AK_PHP_CODE_SANITIZER_SKIP_CONSTRUCTS') || !AK_PHP_CODE_SANITIZER_SKIP_CONSTRUCTS){
59 $this->secureConstructs($code);
61 if(!defined('AK_PHP_CODE_SANITIZER_SKIP_CLASSES') || !AK_PHP_CODE_SANITIZER_SKIP_CLASSES){
62 $this->secureClasses($code);
64 if(!defined('AK_PHP_CODE_SANITIZER_SKIP_PROTECTED_TYPES') || !AK_PHP_CODE_SANITIZER_SKIP_PROTECTED_TYPES){
65 $this->secureProtectedTypes($code);
68 if(!empty($this->_errors)){
69 if($raise_if_insecure){
70 $this->raiseError();
72 return false;
75 return true;
78 function raiseError($code = null)
80 $code = empty($code) ? @$this->_options['code'] : $code;
81 if(AK_DEBUG){
82 // We can't halt execution while testing and the error message is too large for trigger_error
83 if(AK_ENVIRONMENT == 'testing'){
84 trigger_error(join("\n", $this->getErrors()), E_USER_WARNING);
85 }else{
86 echo
87 '<h1>'.Ak::t('Template %template_file security error', array('%template_file'=>@$this->_options['file_path'])).':</h1>'.
88 "<ul><li>".join("</li>\n<li>",$this->getErrors())."</li></ul><hr />\n".
89 '<h2>'.Ak::t('Showing template source from %file:',array('%file'=>$this->_options['file_path'])).'</h2>'.
90 (isset($this->_options['file_path']) ? '<pre>'.htmlentities(Ak::file_get_contents($this->_options['file_path'])).'</pre><hr />':'').
91 '<h2>'.Ak::t('Showing compiled template source:').'</h2>'.highlight_string($code,true);
93 die();
95 }else{
96 trigger_error(Ak::t('Template compilation error'),E_USER_ERROR);
100 function getErrors()
102 return $this->_errors;
105 function _addDollarSymbol_(&$var)
107 if($var[0] != "$") {
108 $var = "$".$var;
112 function secureVariables($code)
114 $_forbidden['variables'] = empty($this->_options['forbidden_variables']) ?
115 array_unique(array_merge(array_keys($GLOBALS), array_keys(get_defined_vars()))) :
116 $this->_options['forbidden_variables'];
118 array_map(array(&$this,'_addDollarSymbol_'), $_forbidden['variables']);
120 $_used_vars = array_keys((array)$this->Analyzer->usedVariables);
122 if(!defined('AK_PHP_CODE_SANITIZER_SKIP_MEMBER_VARIABLES') || !AK_PHP_CODE_SANITIZER_SKIP_MEMBER_VARIABLES){
123 $this->lookForPrivateMemberVariables($this->Analyzer->usedMemberVariables);
126 $this->_invalid['variables'] = array_diff($_used_vars, array_diff($_used_vars,array_merge($_forbidden['variables'], array_filter($_used_vars, array(&$this, 'isPrivateVar')))));
129 function secureFunctions($code = null)
131 if(!empty($this->Analyzer->createdFunctions)){
132 $this->_errors[] = Ak::t('You can\'t create functions within templates');
134 $_used_functions = array_merge(array_keys((array)@$this->Analyzer->calledFunctions),array_keys((array)@$this->Analyzer->calledConstructs));
136 $_forbidden['functions'] = array_merge($this->getForbiddenFunctions(),$this->_getFuntionsAsVariables($_used_functions));
137 $this->_invalid['functions'] = array_diff($_used_functions, array_diff($_used_functions,$_forbidden['functions']));
140 function secureClasses($code = null)
142 if(!empty($this->Analyzer->createdClasses)){
143 $this->_errors[] = Ak::t('You can\'t create classes within templates');
145 if(!empty($this->Analyzer->classesInstantiated)){
146 $this->_errors[] = Ak::t('You can\'t instantiate classes within templates');
148 $this->_invalid['classes'] = array_diff(array_keys((array)$this->Analyzer->calledStaticMethods),(array)@$this->_options['classes']);
150 $_class_calls = array_merge((array)$this->Analyzer->calledStaticMethods, (array)@$this->Analyzer->calledMethods);
151 $forbidden_methods = array_merge($this->getForbiddenMethods(),(array)@$this->_options['forbidden_methods']);
152 foreach ($_class_calls as $_class_name=>$_method_calls){
153 foreach (array_keys($_method_calls) as $_method_call){
154 if(empty($_method_call)){
155 continue;
157 $_method_name = $_class_name.($_class_name[0]=='$'?'->':'::').$_method_call;
158 if($_method_call[0] === '_' || $_method_call[0] === '$' || in_array($_method_call, $forbidden_methods)){
159 $this->_invalid['methods'][] = $_method_name;
165 function secureConstructs($code = null)
167 if(!empty($this->Analyzer->filesIncluded)){
168 $this->_errors[] = Ak::t('You can\'t include files within templates using PHP include or require please use $this->render() instead');
170 $_used_constructs = array_keys((array)$this->Analyzer->calledConstructs);
171 $this->_invalid['constructs'] = array_diff($_used_constructs, array_diff($_used_constructs, empty($this->_options['forbidden_constructs']) ? array('include','include_once','require','require_once') :
172 $this->_options['forbidden_constructs']));
176 function secureProtectedTypes()
178 foreach ($this->_protedted_types as $_type){
179 if(!empty($this->_invalid[$_type])){
180 $this->_invalid[$_type] = array_diff($this->_invalid[$_type],(array)@$this->_options[$_type]);
182 if(!empty($this->_invalid[$_type])){
183 array_unique($this->_invalid[$_type]);
184 sort($this->_invalid[$_type]);
185 $this->_errors[] = Ak::t('You can\'t use the following %type within templates:',array('%type'=>Ak::t($_type))).' '.join(', ',$this->_invalid[$_type]);
190 function isPrivateVar($var)
192 return preg_match('/^["\'${\.]*_/', $var);
195 function isPrivateDynamicVar($var)
197 if(preg_match('/^["\'{\.]*\$/', $var)){
198 $var_name = trim($var, '{"\'.$');
199 if(isset($GLOBALS[$var_name])){
200 return $this->isPrivateVar($GLOBALS[$var_name]);
202 return true;
204 return false;
207 function lookForPrivateMemberVariables($var, $nested = false)
209 if(is_string($var) && $this->isPrivateVar($var)){
210 $this->_invalid['member variables'][$var] = $var;
211 return true;
212 }elseif (is_array($var)){
213 foreach (array_keys($var) as $k){
214 if($this->isPrivateVar($k) || ($nested && $this->isPrivateDynamicVar($k))){
215 $this->_invalid['member variables'][$k] = $k;
216 return true;
217 }elseif($this->lookForPrivateMemberVariables($var[$k], true)){
218 return true;
221 }elseif (is_object($var)){
222 return $this->lookForPrivateMemberVariables((array)$var, true);
224 return false;
227 function &getCodeAnalyzerInstance()
229 if(empty($this->Analyzer)){
230 require_once(AK_CONTRIB_DIR.DS.'PHPCodeAnalyzer'.DS.'PHPCodeAnalyzer.php');
231 $this->Analyzer =& new PHPCodeAnalyzer();
233 return $this->Analyzer;
236 function AnalyzeCode($code)
238 $this->Analyzer =& $this->getCodeAnalyzerInstance();
239 $this->Analyzer->source = '?>'.$code.'<?php';
240 $this->Analyzer->analyze();
241 return $this->Analyzer;
244 function _getFuntionsAsVariables($functions_array)
246 $result = array();
247 foreach ((array)$functions_array as $function){
248 if(isset($function[0]) && $function[0] == '$'){
249 $result[] = $function;
252 return $result;
255 function getForbiddenMethods()
257 return !$this->secure_active_record_method_calls ? array() : array('init','setAccessibleAttributes','setProtectedAttributes','getConnection','setConnection','create','update','updateAttribute','updateAttributes','updateAll','delete','deleteAll','destroy','destroyAll','establishConnection','freeze','isFrozen','setInheritanceColumn','getColumnsWithRegexBoundaries','instantiate','getSubclasses','setAttribute','set','setAttributes','removeAttributesProtectedFromMassAssignment','cloneRecord','decrementAttribute','decrementAndSaveAttribute','incrementAttribute','incrementAndSaveAttribute','hasAttributesDefined','reload','save','createOrUpdate','toggleAttribute','toggleAttributeAndSave','setId','getAttributesBeforeTypeCast','getAttributeBeforeTypeCast','setPrimaryKey','resetColumnInformation','setSerializeAttribute','getSerializedAttributes','getAvailableCombinedAttributes','getAvailableAttributes','loadColumnsSettings','setColumnSettings','setAttributeByLocale','setAttributeLocales','initiateAttributeToNull','initiateColumnsToNull','getAkelosDataType','getClassForDatabaseTableMapping','setTableName','setDisplayField','debug','castAttributeForDatabase','castAttributeFromDatabase','isLockingEnabled','beforeCreate','beforeValidation','beforeValidationOnCreate','beforeValidationOnUpdate','beforeSave','beforeUpdate','afterUpdate','afterValidation','afterValidationOnCreate','afterValidationOnUpdate','afterCreate','afterDestroy','beforeDestroy','afterSave','transactionStart','transactionComplete','transactionFail','transactionHasFailed','validatesConfirmationOf','validatesAcceptanceOf','validatesAssociated','validatesPresenceOf','validatesLengthOf','validatesSizeOf','validatesUniquenessOf','validatesFormatOf','validatesInclusionOf','validatesExclusionOf','validatesNumericalityOf','validate','validateOnCreate','validateOnUpdate','notifyObservers','setObservableState','getObservableState','addObserver','getObservers','addErrorToBase','addError','addErrorOnEmpty','addErrorOnBlank','addErrorOnBoundaryBreaking','addErrorOnBoundryBreaking','clearErrors','actsAs','actsLike','dbug','sqlSelectOne','sqlSelectValue','sqlSelectValues','sqlSelectAll','sqlSelect');
260 function getForbiddenFunctions()
262 return array('__halt_compiler','aggregate','aggregate_methods','aggregate_methods_by_list','aggregate_methods_by_regexp','aggregate_properties','aggregate_properties_by_list','aggregate_properties_by_regexp','aggregation_info','apache_child_terminate','apache_get_modules','apache_get_version','apache_getenv','apache_lookup_uri','apache_note','apache_request_headers','apache_reset_timeout','apache_response_headers','apache_setenv','ascii2ebcdic','basename','call_user_func','call_user_func_array','call_user_method','call_user_method_array','chdir','chgrp','chmod','chown','chroot','class_exists','clearstatcache','closedir','closelog','com_addref','com_event_sink','com_get','com_invoke','com_invoke_ex','com_isenum','com_load','com_load_typelib','com_message_pump','com_print_typeinfo','com_propget','com_propput','com_propset','com_release','com_set','compact','connection_aborted','connection_status','connection_timeout','constant','copy','crc32','create_function','deaggregate','debug_backtrace','debug_zval_dump','define','define_syslog_variables','defined','delete','dio_close','dio_fcntl','dio_open','dio_read','dio_seek','dio_stat','dio_tcsetattr','dio_truncate','dio_write','dir','dirname','disk_free_space','disk_total_space','diskfreespace','dl','ebcdic2ascii','error_log','error_reporting','escapeshellarg','escapeshellcmd','eval','exec','extension_loaded','extract','fclose','feof','fflush','fgetc','fgetcsv','fgets','fgetss','file','file_exists','file_get_contents','file_put_contents','fileatime','filectime','filegroup','fileinode','filemtime','fileowner','fileperms','filesize','filetype','flock','flush','fmod','fnmatch','fopen','fpassthru','fputcsv','fputs','fread','fscanf','fseek','fsockopen','fstat','ftell','ftruncate','func_get_arg','func_get_args','func_num_args','function_exists','fwrite','gd_info','get_browser','get_cfg_var','get_class','get_class_methods','get_class_vars','get_current_user','get_declared_classes','get_defined_constants','get_defined_functions','get_defined_vars','get_extension_funcs','get_include_path','get_included_files','get_loaded_extensions','get_magic_quotes_gpc','get_magic_quotes_runtime','get_meta_tags','get_object_vars','get_parent_class','get_required_files','get_resource_type','getallheaders','getcwd','getenv','getmygid','getmyinode','getmypid','getmyuid','glob','gzclose','gzcompress','gzdeflate','gzencode','gzeof','gzfile','gzgetc','gzgets','gzgetss','gzinflate','gzopen','gzpassthru','gzputs','gzread','gzrewind','gzseek','gztell','gzuncompress','gzwrite','header','headers_sent','highlight_file','html_doc','html_doc_file','ignore_user_abort','import_request_variables','ini_alter','ini_get','ini_get_all','ini_restore','ini_set','is_dir','is_executable','is_file','is_link','is_readable','is_uploaded_file','is_writable','is_writeable','link','linkinfo','log','log10','lstat','magic_quotes_runtime','mail','md5_file','mkdir','move_uploaded_file','ob_clean','ob_end_clean','ob_end_flush','ob_flush','ob_get_clean','ob_get_contents','ob_get_flush','ob_get_length','ob_get_level','ob_get_status','ob_gzhandler','ob_implicit_flush','ob_list_handlers','ob_start','opendir','openlog','output_add_rewrite_var','output_reset_rewrite_vars','overload','pack','parse_ini_file','parse_str','parse_url','passthru','pathinfo','pclose','pfsockopen','php_check_syntax','php_ini_scanned_files','php_logo_guid','php_sapi_name','php_strip_whitespace','php_uname','phpcredits','phpinfo','phpversion','png2wbmp','popen','preg_replace_callback','proc_close','proc_get_status','proc_nice','proc_open','proc_terminate','putenv','readdir','readfile','readgzfile','readlink','realpath','register_shutdown_function','register_tick_function','rename','restore_error_handler','restore_include_path','rewind','rewinddir','rmdir','scandir','session_cache_expire','session_cache_limiter','session_commit','session_decode','session_destroy','session_encode','session_get_cookie_params','session_id','session_is_registered','session_module_name','session_name','session_regenerate_id','session_register','session_save_path','session_set_cookie_params','session_set_save_handler','session_start','session_unregister','session_unset','session_write_close','set_error_handler','set_file_buffer','set_include_path','set_magic_quotes_runtime','set_socket_blocking','set_time_limit','setcookie','setlocale','settype','shell_exec','shmop_close','shmop_delete','shmop_open','shmop_read','shmop_size','shmop_write','show_source','similar_text','sleep','socket_accept','socket_bind','socket_clear_error','socket_close','socket_connect','socket_create','socket_create_listen','socket_create_pair','socket_get_option','socket_get_status','socket_getopt','socket_getpeername','socket_getsockname','socket_iovec_add','socket_iovec_alloc','socket_iovec_delete','socket_iovec_fetch','socket_iovec_free','socket_iovec_set','socket_last_error','socket_listen','socket_read','socket_readv','socket_recv','socket_recvfrom','socket_select','socket_send','socket_sendmsg','socket_sendto','socket_set_block','socket_set_blocking','socket_set_nonblock','socket_set_option','socket_set_timeout','socket_setopt','socket_shutdown','socket_strerror','socket_write','socket_writev','stat','stream_context_create','stream_context_get_options','stream_context_set_option','stream_context_set_params','stream_filter_append','stream_filter_prepend','stream_get_meta_data','stream_register_wrapper','stream_select','stream_set_blocking','stream_set_timeout','stream_set_write_buffer','stream_wrapper_register','symlink','syslog','system','tempnam','time_nanosleep','time_sleep_until','tmpfile','token_get_all','token_name','touch','trigger_error','umask','uniqid','unlink','unpack','unregister_tick_function','user_error','usleep','virtual','zend_get_cfg_var','zend_loader_current_file','zend_loader_enabled','zend_loader_file_encoded','zend_loader_file_licensed','zend_loader_install_license','zend_logo_guid','zend_version');