diff --git a/lib/CHANGELOG.md b/lib/CHANGELOG.md index 723b8e594..b406321c4 100644 --- a/lib/CHANGELOG.md +++ b/lib/CHANGELOG.md @@ -1,6 +1,21 @@ CHANGELOG -3.7.1 (30. Dezember 2019) +3.7.2 (28 May 2020) +* CHANGED, View->sandbox: disable escaping when rendering as text/plain, bcosca/fatfree#654 +* update HTTP protocol checks, #bcosca/fatfree#1190 +* Base->clear: close vulnerability on variable compilation, bcosca/fatfree#1191 +* DB\SQL\Mapper: fix empty ID after insert, bcosca/fatfree#1175 +* DB\SQL\Mapper: fix using correct key variable for grouped sql pagination sets +* Fix return type of 'count' in Cursor->paginate() (bcosca/fatfree#1187) +* Bug fix, Web->minify: fix minification of ES6 template literals, bcosca/fatfree#1178 +* Bug fix, config: refactoring custom section parser regex, bcosca/fatfree#1149 +* Bug fix: token resolve on non-alias reroute paths, ref. 221f0c930f8664565c9825faeb9ed9af0f7a01c8 +* Websocket: Improved event handler usage +* optimized internal get calls +* only use cached lexicon when a $ttl was given +* only use money_format up until php7.4, fixes bcosca/fatfree#1174 + +3.7.1 (30. December 2019) * Base->build: Add support for brace-enclosed route tokens * Base->reroute, fix duplicate fragment issue on non-alias routes * DB\SQL\Mapper: fix empty check for pkey when reloading after insert diff --git a/lib/base.php b/lib/base.php index e8234d6ba..ea72e7c31 100644 --- a/lib/base.php +++ b/lib/base.php @@ -45,7 +45,7 @@ final class Base extends Prefab implements ArrayAccess { //@{ Framework details const PACKAGE='Fat-Free Framework', - VERSION='3.7.1-Release'; + VERSION='3.7.2-Release'; //@} //@{ HTTP status codes (RFC 2616) @@ -235,9 +235,31 @@ function cast($val) { * Convert JS-style token to PHP expression * @return string * @param $str string - **/ - function compile($str) { - return preg_replace_callback( + * @param $evaluate bool compile expressions as well or only convert variable access + **/ + function compile($str, $evaluate=TRUE) { + return (!$evaluate) + ? preg_replace_callback( + '/^@(\w+)((?:\..+|\[(?:(?:[^\[\]]*|(?R))*)\])*)/', + function($expr) { + $str='$'.$expr[1]; + if (isset($expr[2])) + $str.=preg_replace_callback( + '/\.([^.\[\]]+)|\[((?:[^\[\]\'"]*|(?R))*)\]/', + function($sub) { + $val=isset($sub[2]) ? $sub[2] : $sub[1]; + if (ctype_digit($val)) + $val=(int)$val; + $out='['.$this->export($val).']'; + return $out; + }, + $expr[2] + ); + return $str; + }, + $str + ) + : preg_replace_callback( '/(?|::)\w+)?)'. '((?:\.\w+|\[(?:(?:[^\[\]]*|(?R))*)\]|(?:\->|::)\w+|\()*)/', function($expr) { @@ -503,7 +525,9 @@ function clear($key) { // Reset global to default value $this->hive[$parts[0]]=$this->init[$parts[0]]; else { - eval('unset('.$this->compile('@this->hive.'.$key).');'); + $val=preg_replace('/^(\$hive)/','$this->hive', + $this->compile('@hive.'.$key, FALSE)); + eval('unset('.$val.');'); if ($parts[0]=='SESSION') { session_commit(); session_start(); @@ -970,7 +994,8 @@ function($expr) use($args,$conv) { $cstm=!$int=($prop=='int')) $currency_symbol=$prop; if (!$cstm && - function_exists('money_format')) + function_exists('money_format') && + version_compare(PHP_VERSION,'7.4.0')<0) return money_format( '%'.($int?'i':'n'),$args[$pos]); $fmt=[ @@ -1106,7 +1131,7 @@ function language($code) { function lexicon($path,$ttl=0) { $languages=$this->languages?:explode(',',$this->fallback); $cache=Cache::instance(); - if ($cache->exists( + if ($ttl && $cache->exists( $hash=$this->hash(implode(',',$languages).$path).'.dic',$lex)) return $lex; $lex=[]; @@ -1491,6 +1516,8 @@ function reroute($url=NULL,$permanent=FALSE,$die=TRUE) { $url=$this->build($this->hive['ALIASES'][$parts[1]], isset($parts[2])?$this->parse($parts[2]):[]). (isset($parts[3])?$parts[3]:'').(isset($parts[4])?$parts[4]:''); + else + $url=$this->build($url); if (($handler=$this->hive['ONREROUTE']) && $this->call($handler,[$url,$permanent,$die])!==FALSE) return; @@ -1856,7 +1883,8 @@ function grab($func,$args=NULL) { if ($parts[2]=='->') { if (is_subclass_of($parts[1],'Prefab')) $parts[1]=call_user_func($parts[1].'::instance'); - elseif ($container=$this->get('CONTAINER')) { + elseif (isset($this->hive['CONTAINER'])) { + $container=$this->hive['CONTAINER']; if (is_object($container) && is_callable([$container,'has']) && $container->has($parts[1])) // PSR11 $parts[1]=call_user_func([$container,'get'],$parts[1]); @@ -1991,11 +2019,11 @@ function config($source,$allow=FALSE) { $sec=$match['section']; if (preg_match( '/^(?!(?:global|config|route|map|redirect)s\b)'. - '((?:[^:])+)/i',$sec,$msec) && - !$this->exists($msec[0])) - $this->set($msec[0],NULL); + '(.*?)(?:\s*[:>])/i',$sec,$msec) && + !$this->exists($msec[1])) + $this->set($msec[1],NULL); preg_match('/^(config|route|map|redirect)s\b|'. - '^((?:[^:])+)\s*\>\s*(.*)/i',$sec,$cmd); + '^(.+?)\s*\>\s*(.*)/i',$sec,$cmd); continue; } if ($allow) @@ -2075,7 +2103,7 @@ function mutex($id,$func,$args=NULL) { mkdir($tmp,self::MODE,TRUE); // Use filesystem lock if (is_file($lock=$tmp. - $this->get('SEED').'.'.$this->hash($id).'.lock') && + $this->hive['SEED'].'.'.$this->hash($id).'.lock') && filemtime($lock)+ini_get('max_execution_time')esc($hive); if (isset($hive['ALIASES'])) $hive['ALIASES']=$fw->build($hive['ALIASES']); @@ -2963,13 +2992,11 @@ function c($val) { * @param $str string **/ function token($str) { - $fw=$this->fw; - $str=trim(preg_replace('/\{\{(.+?)\}\}/s',trim('\1'), - $fw->compile($str))); + $str=trim(preg_replace('/\{\{(.+?)\}\}/s','\1',$this->fw->compile($str))); if (preg_match('/^(.+)(?split(trim($parts[2],"\xC2\xA0")) as $func) + foreach ($this->fw->split(trim($parts[2],"\xC2\xA0")) as $func) $str=((empty($this->filter[$cmd=$func]) && function_exists($cmd)) || is_string($cmd=$this->filter($func)))? diff --git a/lib/cli/ws.php b/lib/cli/ws.php index 45af34a71..f1573c010 100644 --- a/lib/cli/ws.php +++ b/lib/cli/ws.php @@ -70,7 +70,7 @@ function alloc($socket) { $verb=NULL; $uri=NULL; foreach (explode($EOL,trim($buf)) as $line) - if (preg_match('/^(\w+)\s(.+)\sHTTP\/1\.\d$/', + if (preg_match('/^(\w+)\s(.+)\sHTTP\/[\d.]{1,3}$/', trim($line),$match)) { $verb=$match[1]; $uri=$match[2]; @@ -328,8 +328,7 @@ class Agent { $flag, $verb, $uri, - $headers, - $events; + $headers; /** * Return server instance @@ -400,8 +399,8 @@ function send($op,$data='') { if (is_bool($server->write($this->socket,$buf))) return FALSE; if (!in_array($op,[WS::Pong,WS::Close]) && - isset($this->events['send']) && - is_callable($func=$this->events['send'])) + isset($this->server->events['send']) && + is_callable($func=$this->server->events['send'])) $func($this,$op,$data); return $data; } @@ -447,8 +446,8 @@ function fetch() { case WS::Text: $data=trim($data); case WS::Binary: - if (isset($this->events['receive']) && - is_callable($func=$this->events['receive'])) + if (isset($this->server->events['receive']) && + is_callable($func=$this->server->events['receive'])) $func($this,$op,$data); break; } @@ -460,8 +459,8 @@ function fetch() { * Destroy object **/ function __destruct() { - if (isset($this->events['disconnect']) && - is_callable($func=$this->events['disconnect'])) + if (isset($this->server->events['disconnect']) && + is_callable($func=$this->server->events['disconnect'])) $func($this); } @@ -479,10 +478,9 @@ function __construct($server,$socket,$verb,$uri,array $hdrs) { $this->verb=$verb; $this->uri=$uri; $this->headers=$hdrs; - $this->events=$server->events(); - if (isset($this->events['connect']) && - is_callable($func=$this->events['connect'])) + if (isset($server->events['connect']) && + is_callable($func=$server->events['connect'])) $func($this); } diff --git a/lib/db/cursor.php b/lib/db/cursor.php index bce36bad1..2fd324a0e 100644 --- a/lib/db/cursor.php +++ b/lib/db/cursor.php @@ -148,7 +148,7 @@ function findone($filter=NULL,array $options=NULL,$ttl=0) { function paginate( $pos=0,$size=10,$filter=NULL,array $options=NULL,$ttl=0,$bounce=TRUE) { $total=$this->count($filter,$options,$ttl); - $count=ceil($total/$size); + $count=(int)ceil($total/$size); if ($bounce) $pos=max(0,min($pos,$count-1)); return [ diff --git a/lib/db/jig.php b/lib/db/jig.php index b4ab9cce4..bcc29cacc 100644 --- a/lib/db/jig.php +++ b/lib/db/jig.php @@ -90,7 +90,7 @@ function write($file,array $data=NULL) { $out=$fw->serialize($data); break; } - return $fw->write($this->dir.'/'.$file,$out); + return $fw->write($this->dir.$file,$out); } /** diff --git a/lib/db/sql/mapper.php b/lib/db/sql/mapper.php index ea041de88..1eb45c6d0 100644 --- a/lib/db/sql/mapper.php +++ b/lib/db/sql/mapper.php @@ -365,7 +365,7 @@ function count($filter=NULL,array $options=NULL,$ttl=0) { $group_fields=array_flip(array_map('trim',explode(',',$group_string))); foreach ($this->adhoc as $key=>$field) // add adhoc fields that are used for grouping - if (isset($group_fields[$field])) + if (isset($group_fields[$key])) $adhoc[]=$field['expr'].' AS '.$this->db->quotekey($key); $fields=implode(',',$adhoc); if (empty($fields)) @@ -447,7 +447,8 @@ function insert() { if ($field['pkey']) { $field['previous']=$field['value']; if (!$inc && $field['pdo_type']==\PDO::PARAM_INT && - is_null($field['value']) && !$field['nullable']) + empty($field['value']) && !$field['nullable'] && + is_null($field['default'])) $inc=$key; $filter.=($filter?' AND ':'').$this->db->quotekey($key).'=?'; $nkeys[$nctr+1]=[$field['value'],$field['pdo_type']]; diff --git a/lib/test.php b/lib/test.php index 13644bc80..d45bb18c5 100644 --- a/lib/test.php +++ b/lib/test.php @@ -34,7 +34,9 @@ class Test { //! Test results $data=[], //! Success indicator - $passed=TRUE; + $passed=TRUE, + //! Reporting level + $level; /** * Return test results diff --git a/lib/web.php b/lib/web.php index 373954bc3..05aecac72 100644 --- a/lib/web.php +++ b/lib/web.php @@ -62,7 +62,7 @@ function mime($file, $inspect=FALSE) { fclose($fhandle); } elseif (($response=$this->request($file,['method' => 'HEAD'])) - && preg_grep('/HTTP\/\d\.\d 200/',$response['headers']) + && preg_grep('/HTTP\/[\d.]{1,3} 200/',$response['headers']) && ($type = preg_grep('/^Content-Type:/i',$response['headers']))) { // get mime type directly from response header return preg_replace('/^Content-Type:\s*/i','',array_pop($type)); @@ -383,7 +383,7 @@ function($curl,$line) use(&$headers) { $body=ob_get_clean(); if (!$err && $options['follow_location'] && $open_basedir && - preg_grep('/HTTP\/1\.\d 3\d{2}/',$headers) && + preg_grep('/HTTP\/[\d.]{1,3} 3\d{2}/',$headers) && preg_match('/^Location: (.+)$/m',implode(PHP_EOL,$headers),$loc)) { $options['max_redirects']--; if($loc[1][0] == '/') { @@ -530,7 +530,7 @@ protected function _socket($url,$options) { break; } if ($options['follow_location'] && - preg_grep('/HTTP\/1\.\d 3\d{2}/',$headers) && + preg_grep('/HTTP\/[\d.]{1,3} 3\d{2}/',$headers) && preg_match('/Location: (.+?)'.preg_quote($eol).'/', $html[0],$loc)) { $options['max_redirects']--; @@ -658,7 +658,7 @@ function request($url,array $options=NULL) { } $result=$this->{'_'.$this->wrapper}($url,$options); if ($result && isset($cache)) { - if (preg_match('/HTTP\/1\.\d 304/', + if (preg_match('/HTTP\/[\d.]{1,3} 304/', implode($eol,$result['headers']))) { $result=$cache->get($hash); $result['cached']=TRUE; @@ -778,7 +778,7 @@ function minify($files,$mime=NULL,$header=TRUE,$path=NULL) { } continue; } - if (in_array($src[$ptr],['\'','"'])) { + if (in_array($src[$ptr],['\'','"','`'])) { $match=$src[$ptr]; $data.=$match; $ptr++;