From 71751ecb8c141fe5544a32e175fc0a45165206d2 Mon Sep 17 00:00:00 2001 From: Fabrixxm Date: Fri, 14 Dec 2012 10:10:33 -0500 Subject: [PATCH 1/4] template_processor: add filter support to variables --- include/template_processor.php | 485 ++++++++++++++++++--------------- 1 file changed, 265 insertions(+), 220 deletions(-) diff --git a/include/template_processor.php b/include/template_processor.php index 4088ddab60..6c5908d92e 100644 --- a/include/template_processor.php +++ b/include/template_processor.php @@ -1,250 +1,295 @@ 5.3, not certain how to code around it for unit tests -// case PREG_BAD_UTF8_OFFSET_ERROR: echo('PREG_BAD_UTF8_OFFSET_ERROR'); break; - default: - //die("Unknown preg error."); - return; - } - echo "
";
-			debug_print_backtrace();
-			die();
+define("KEY_NOT_EXISTS", '^R_key_not_Exists^');
+
+class Template {
+
+	var $r;
+	var $search;
+	var $replace;
+	var $stack = array();
+	var $nodes = array();
+	var $done = false;
+	var $d = false;
+	var $lang = null;
+	var $debug = false;
+
+	private function _preg_error() {
+
+		switch (preg_last_error()) {
+			case PREG_INTERNAL_ERROR: echo('PREG_INTERNAL_ERROR');
+				break;
+			case PREG_BACKTRACK_LIMIT_ERROR: echo('PREG_BACKTRACK_LIMIT_ERROR');
+				break;
+			case PREG_RECURSION_LIMIT_ERROR: echo('PREG_RECURSION_LIMIT_ERROR');
+				break;
+			case PREG_BAD_UTF8_ERROR: echo('PREG_BAD_UTF8_ERROR');
+				break;
+//			This is only valid for php > 5.3, not certain how to code around it for unit tests
+//			case PREG_BAD_UTF8_OFFSET_ERROR: echo('PREG_BAD_UTF8_OFFSET_ERROR'); break;
+			default:
+				//die("Unknown preg error.");
+				return;
 		}
-		
-		
-		private function _push_stack(){
-			$this->stack[] = array($this->r, $this->nodes);
+		echo "
";
+		debug_print_backtrace();
+		die();
+	}
+
+	private function _push_stack() {
+		$this->stack[] = array($this->r, $this->nodes);
+	}
+
+	private function _pop_stack() {
+		list($this->r, $this->nodes) = array_pop($this->stack);
+	}
+
+	private function _get_var($name, $retNoKey = false) {
+		$keys = array_map('trim', explode(".", $name));
+		if ($retNoKey && !array_key_exists($keys[0], $this->r))
+			return KEY_NOT_EXISTS;
+		$val = $this->r;
+		foreach ($keys as $k) {
+			$val = (isset($val[$k]) ? $val[$k] : null);
 		}
-		private function _pop_stack(){
-			list($this->r, $this->nodes) = array_pop($this->stack);
-			
+		return $val;
+	}
+
+	/**
+	 * IF node
+	 * 
+	 * {{ if <$var> }}...[{{ else }} ...] {{ endif }}
+	 * {{ if <$var>== }}...[{{ else }} ...]{{ endif }}
+	 * {{ if <$var>!= }}...[{{ else }} ...]{{ endif }}
+	 */
+	private function _replcb_if($args) {
+		if (strpos($args[2], "==") > 0) {
+			list($a, $b) = array_map("trim", explode("==", $args[2]));
+			$a = $this->_get_var($a);
+			if ($b[0] == "$")
+				$b = $this->_get_var($b);
+			$val = ($a == $b);
+		} else if (strpos($args[2], "!=") > 0) {
+			list($a, $b) = array_map("trim", explode("!=", $args[2]));
+			$a = $this->_get_var($a);
+			if ($b[0] == "$")
+				$b = $this->_get_var($b);
+			$val = ($a != $b);
+		} else {
+			$val = $this->_get_var($args[2]);
 		}
-		
-		private function _get_var($name, $retNoKey=false){
-			$keys = array_map('trim',explode(".",$name));
-			if ($retNoKey && !array_key_exists($keys[0], $this->r)) return KEY_NOT_EXISTS;
-			$val = $this->r;
-			foreach($keys as $k) {
-				$val = (isset($val[$k]) ? $val[$k] : null);
-			}
-			return $val;
+		$x = preg_split("|{{ *else *}}|", $args[3]);
+		return ( $val ? $x[0] : (isset($x[1]) ? $x[1] : ""));
+	}
+
+	/**
+	 * FOR node
+	 * 
+	 * {{ for <$var> as $name }}...{{ endfor }}
+	 * {{ for <$var> as $key=>$name }}...{{ endfor }}
+	 */
+	private function _replcb_for($args) {
+		$m = array_map('trim', explode(" as ", $args[2]));
+		$x = explode("=>", $m[1]);
+		if (count($x) == 1) {
+			$varname = $x[0];
+			$keyname = "";
+		} else {
+			list($keyname, $varname) = $x;
 		}
-		
-		/**
-		 * IF node
-		 * 
-		 * {{ if <$var> }}...[{{ else }} ...] {{ endif }}
-		 * {{ if <$var>== }}...[{{ else }} ...]{{ endif }}
-		 * {{ if <$var>!= }}...[{{ else }} ...]{{ endif }}
-		 */
-		private function _replcb_if($args){
-			if (strpos($args[2],"==")>0){
-				list($a,$b) = array_map("trim",explode("==",$args[2]));
-				$a = $this->_get_var($a);
-				if ($b[0]=="$") $b =  $this->_get_var($b);
-				$val = ($a == $b);
-			} else if (strpos($args[2],"!=")>0){
-				list($a,$b) = array_map("trim", explode("!=",$args[2]));
-				$a = $this->_get_var($a);
-				if ($b[0]=="$") $b =  $this->_get_var($b);
-				$val = ($a != $b);
-			} else {
-				$val = $this->_get_var($args[2]);
-			}
-			$x = preg_split("|{{ *else *}}|", $args[3]);
-			return ( $val ? $x[0] : (isset($x[1]) ? $x[1] : ""));
-		}
-		
-		/**
-		 * FOR node
-		 * 
-		 * {{ for <$var> as $name }}...{{ endfor }}
-		 * {{ for <$var> as $key=>$name }}...{{ endfor }}
-		 */
-		private function _replcb_for($args){
-			$m = array_map('trim', explode(" as ", $args[2]));
-			$x = explode("=>",$m[1]);
-			if (count($x) == 1) {
-				$varname = $x[0];
-				$keyname = "";
-			} else {
-				list($keyname, $varname) = $x;
-			}
-			if ($m[0]=="" || $varname=="" || is_null($varname)) die("template error: 'for ".$m[0]." as ".$varname."'") ;
-			//$vals = $this->r[$m[0]];
-			$vals = $this->_get_var($m[0]);
-			$ret="";
-			if (!is_array($vals)) return $ret; 
-			foreach ($vals as $k=>$v){
-				$this->_push_stack();
-				$r = $this->r;
-				$r[$varname] = $v;
-				if ($keyname!='') $r[$keyname] = (($k === 0) ? '0' : $k);
-				$ret .=  $this->replace($args[3], $r);
-				$this->_pop_stack();
-			}
+		if ($m[0] == "" || $varname == "" || is_null($varname))
+			die("template error: 'for " . $m[0] . " as " . $varname . "'");
+		//$vals = $this->r[$m[0]];
+		$vals = $this->_get_var($m[0]);
+		$ret = "";
+		if (!is_array($vals))
 			return $ret;
-		}
-
-		/**
-		 * INC node
-		 * 
-		 * {{ inc  [with $var1=$var2] }}{{ endinc }}
-		 */
-		private function _replcb_inc($args){
-			if (strpos($args[2],"with")) {
-				list($tplfile, $newctx) = array_map('trim', explode("with",$args[2]));
-			} else {
-				$tplfile = trim($args[2]);
-				$newctx = null;
-			}
-			
-			if ($tplfile[0]=="$") $tplfile = $this->_get_var($tplfile);
-			
+		foreach ($vals as $k => $v) {
 			$this->_push_stack();
 			$r = $this->r;
-			if (!is_null($newctx)) {
-				list($a,$b) = array_map('trim', explode("=",$newctx));
-				$r[$a] = $this->_get_var($b); 
-			}
-			$this->nodes = Array();
-			$tpl = get_markup_template($tplfile);
-			$ret = $this->replace($tpl, $r);
+			$r[$varname] = $v;
+			if ($keyname != '')
+				$r[$keyname] = (($k === 0) ? '0' : $k);
+			$ret .= $this->replace($args[3], $r);
 			$this->_pop_stack();
-			return $ret;
-			
 		}
-		
-		/**
-		 * DEBUG node
-		 * 
-		 * {{ debug $var [$var [$var [...]]] }}{{ enddebug }}
-		 * 
-		 * replace node with 
var_dump($var, $var, ...);
+ return $ret; + } + + /** + * INC node + * + * {{ inc [with $var1=$var2] }}{{ endinc }} + */ + private function _replcb_inc($args) { + if (strpos($args[2], "with")) { + list($tplfile, $newctx) = array_map('trim', explode("with", $args[2])); + } else { + $tplfile = trim($args[2]); + $newctx = null; + } + + if ($tplfile[0] == "$") + $tplfile = $this->_get_var($tplfile); + + $this->_push_stack(); + $r = $this->r; + if (!is_null($newctx)) { + list($a, $b) = array_map('trim', explode("=", $newctx)); + $r[$a] = $this->_get_var($b); + } + $this->nodes = Array(); + $tpl = get_markup_template($tplfile); + $ret = $this->replace($tpl, $r); + $this->_pop_stack(); + return $ret; + } + + /** + * DEBUG node + * + * {{ debug $var [$var [$var [...]]] }}{{ enddebug }} + * + * replace node with
var_dump($var, $var, ...);
+ */ + private function _replcb_debug($args) { + $vars = array_map('trim', explode(" ", $args[2])); + $vars[] = $args[1]; + + $ret = "
";
+		foreach ($vars as $var) {
+			$ret .= htmlspecialchars(var_export($this->_get_var($var), true));
+			$ret .= "\n";
+		}
+		$ret .= "
"; + return $ret; + } + + private function _replcb_node($m) { + $node = $this->nodes[$m[1]]; + if (method_exists($this, "_replcb_" . $node[1])) { + $s = call_user_func(array($this, "_replcb_" . $node[1]), $node); + } else { + $s = ""; + } + $s = preg_replace_callback('/\|\|([0-9]+)\|\|/', array($this, "_replcb_node"), $s); + return $s; + } + + private function _replcb($m) { + //var_dump(array_map('htmlspecialchars', $m)); + $this->done = false; + $this->nodes[] = (array) $m; + return "||" . (count($this->nodes) - 1) . "||"; + } + + private function _build_nodes($s) { + $this->done = false; + while (!$this->done) { + $this->done = true; + $s = preg_replace_callback('|{{ *([a-z]*) *([^}]*)}}([^{]*({{ *else *}}[^{]*)?){{ *end\1 *}}|', array($this, "_replcb"), $s); + if ($s == Null) + $this->_preg_error(); + } + //({{ *else *}}[^{]*)? + krsort($this->nodes); + return $s; + } + + private function var_replace($s) { + $m = array(); + /** regexp: + * \$ literal $ + * (\[)? optional open square bracket + * ([a-zA-Z0-9-_]+\.?)+ var name, followed by optional + * dot, repeated at least 1 time + * (|[a-zA-Z0-9-_:]+)* pipe followed by filter name and args, zero or many + * (?(1)\]) if there was opened square bracket + * (subgrup 1), match close bracket */ - private function _replcb_debug($args){ - $vars = array_map('trim', explode(" ",$args[2])); - $vars[] = $args[1]; + if (preg_match_all('/\$(\[)?([a-zA-Z0-9-_]+\.?)+(\|[a-zA-Z0-9-_:]+)*(?(1)\])/', $s, $m)) { + foreach ($m[0] as $var) { - $ret = "
";
-			foreach ($vars as $var){
-				$ret .= htmlspecialchars(var_export( $this->_get_var($var), true ));
-				$ret .= "\n";
-			}
-			$ret .= "
"; - return $ret; - } + $exp = str_replace(array("[", "]"), array("", ""), $var); + $exptks = explode("|", $exp); - private function _replcb_node($m) { - $node = $this->nodes[$m[1]]; - if (method_exists($this, "_replcb_".$node[1])){ - $s = call_user_func(array($this, "_replcb_".$node[1]), $node); - } else { - $s = ""; - } - $s = preg_replace_callback('/\|\|([0-9]+)\|\|/', array($this, "_replcb_node"), $s); - return $s; - } - - private function _replcb($m){ - //var_dump(array_map('htmlspecialchars', $m)); - $this->done = false; - $this->nodes[] = (array) $m; - return "||". (count($this->nodes)-1) ."||"; - } - - private function _build_nodes($s){ - $this->done = false; - while (!$this->done){ - $this->done=true; - $s = preg_replace_callback('|{{ *([a-z]*) *([^}]*)}}([^{]*({{ *else *}}[^{]*)?){{ *end\1 *}}|', array($this, "_replcb"), $s); - if ($s==Null) $this->_preg_error(); - } - //({{ *else *}}[^{]*)? - krsort($this->nodes); - return $s; - } - - - private function var_replace($s){ - $m = array(); - /** regexp: - * \$ literal $ - * (\[)? optional open square bracket - * ([a-zA-Z0-9-_]+\.?)+ var name, followed by optional - * dot, repeated at least 1 time - * (?(1)\]) if there was opened square bracket - * (subgrup 1), match close bracket - */ - if (preg_match_all('/\$(\[)?([a-zA-Z0-9-_]+\.?)+(?(1)\])/', $s,$m)){ - - foreach($m[0] as $var){ - $varn = str_replace(array("[","]"), array("",""), $var); - $val = $this->_get_var($varn, true); - if ($val!=KEY_NOT_EXISTS) - $s = str_replace($var, $val, $s); + $varn = $exptks[0]; + unset($exptks[0]); + $val = $this->_get_var($varn, true); + if ($val != KEY_NOT_EXISTS) { + /* run filters */ + /* + * Filter are in form of: + * filtername:arg:arg:arg + * + * "filtername" is function name + * "arg"s are optional, var value is appended to the end + * if one "arg"==='x' , is replaced with var value + * + * examples: + * $item.body|htmlspecialchars // escape html chars + * $item.body|htmlspecialchars|strtoupper // escape html and uppercase result + * $item.created|date:%Y %M %j // format date (created is a timestamp) + * $item.body|str_replace:cat:dog // replace all "cat" with "dog" + * $item.body|str_replace:cat:dog:x:1 // replace one "cat" with "dog" + + */ + foreach ($exptks as $filterstr) { + $filter = explode(":", $filterstr); + $filtername = $filter[0]; + unset($filter[0]); + $valkey = array_search("x", $filter); + if ($valkey === false) { + $filter[] = $val; + } else { + $filter[$valkey] = $val; + } + if (function_exists($filtername)) { + $val = call_user_func_array($filtername, $filter); + } + } + $s = str_replace($var, $val, $s); } } - - return $s; } - - public function replace($s, $r) { - $this->r = $r; - - $s = $this->_build_nodes($s); - $s = preg_replace_callback('/\|\|([0-9]+)\|\|/', array($this, "_replcb_node"), $s); - if ($s==Null) $this->_preg_error(); - - // remove comments block - $s = preg_replace('/{#[^#]*#}/', "" , $s); - - // replace strings recursively (limit to 10 loops) - $os = ""; $count=0; - while($os!=$s && $count<10){ - $os=$s; $count++; - $s = $this->var_replace($s); - } - return $s; - } + return $s; } - - $t = new Template; + public function replace($s, $r) { + $this->r = $r; + $s = $this->_build_nodes($s); + $s = preg_replace_callback('/\|\|([0-9]+)\|\|/', array($this, "_replcb_node"), $s); + if ($s == Null) + $this->_preg_error(); + + // remove comments block + $s = preg_replace('/{#[^#]*#}/', "", $s); + + // replace strings recursively (limit to 10 loops) + $os = ""; + $count = 0; + while ($os != $s && $count < 10) { + $os = $s; + $count++; + $s = $this->var_replace($s); + } + return $s; + } + +} + +$t = new Template; function template_escape($s) { - return str_replace(array('$','{{'),array('!_Doll^Ars1Az_!','!_DoubLe^BraceS4Rw_!'),$s); - - + return str_replace(array('$', '{{'), array('!_Doll^Ars1Az_!', '!_DoubLe^BraceS4Rw_!'), $s); } function template_unescape($s) { - return str_replace(array('!_Doll^Ars1Az_!','!_DoubLe^BraceS4Rw_!'),array('$','{{'),$s); - - - + return str_replace(array('!_Doll^Ars1Az_!', '!_DoubLe^BraceS4Rw_!'), array('$', '{{'), $s); } + From b38b62f759547a36cc6dc3ce8dc002ad7efc2032 Mon Sep 17 00:00:00 2001 From: Olaf Conradi Date: Sat, 15 Dec 2012 16:01:20 +0100 Subject: [PATCH 2/4] Modify nginx sample to a real config file --- mods/sample-nginx.config | 63 ++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/mods/sample-nginx.config b/mods/sample-nginx.config index 37ad5ed269..811be404dd 100644 --- a/mods/sample-nginx.config +++ b/mods/sample-nginx.config @@ -1,19 +1,31 @@ -From: Olaf Conradi -Hey @Friendica Support, +## +# Friendica Nginx configuration +# by Olaf Conradi +# +# On debian based distributions you can add this file to +# /etc/nginx/sites-available +# +# Then customize to your needs. To enable the configuration +# symlink it to /etc/nginx/sites-enabled and reload Nginx +# using /etc/init.d/nginx reload +## -Just wanted to share my #nginx configuration for #friendica with you guys. +## +# You should look at the following URL's in order to grasp a solid understanding +# of Nginx configuration files in order to fully unleash the power of Nginx. +# +# http://wiki.nginx.org/Pitfalls +# http://wiki.nginx.org/QuickStart +# http://wiki.nginx.org/Configuration +## -I noticed most of the existing configurations that are floating on the web for #nginx do not deny access to local files. Most of them use the following construct. - -location / { - try_files $uri $uri/ index.php?q=$request_uri -} - -This serves files like images statically, but also gives everyone access to the source code of your ~friendica ~friendica installation (tpl templates, sql files, etc). One should deny all locations except for images, javascript and css files. Setting these deny rules is tedious and needs maintenance when new directories are added. - -It's easier to route everything through the front controller except those known file types. - -Below is my configuration. First I forward non-SSL traffic to SSL. +## +# This configuration assumes your domain is example.net +# You have a seperate subdomain friendica.example.net +# You want all friendica traffic to be https +# You have an SSL certificate and key for your subdomain +# You have PHP FastCGI Process Manager (php5-fpm) running on localhost +## server { server_name friendica.example.net; @@ -22,7 +34,14 @@ server { rewrite ^ https://friendica.example.net$request_uri? permanent; } -Next is the SSL server part. +## +# Configure Friendica with SSL +# +# All requests are routed to the front controller +# except for certain known file types like images, css, etc. +# Those are served statically whenever possible with a +# fall back to the front controller (needed for avatars, for example) +## server { listen 443 ssl; @@ -88,17 +107,3 @@ server { } } -That's it. -#nginx #friendica @Friendica Support - - -I found one bug after posting when I noticed 404's coming in for certain image files. Avatars need a fallback to go through the front controller. -# statically serve these file types when possible -# otherwise fall back to front controller -# allow browser to cache them -# added .htm for advanced source code editor library -location ~* \.(jpg|jpeg|gif|png|css|js|ico|htm)$ { -expires 30d; -try_files $uri /index.php?q=$request_uri?; -} - From 30b5e08eb6594a8e4cc0300d875ee46fbd004f67 Mon Sep 17 00:00:00 2001 From: Olaf Conradi Date: Sat, 15 Dec 2012 16:21:50 +0100 Subject: [PATCH 3/4] Fix rewrite rules to pass arguments Fix location regex for extensions to end with $ Add more comments --- mods/sample-nginx.config | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/mods/sample-nginx.config b/mods/sample-nginx.config index 811be404dd..2383b6a447 100644 --- a/mods/sample-nginx.config +++ b/mods/sample-nginx.config @@ -25,6 +25,7 @@ # You want all friendica traffic to be https # You have an SSL certificate and key for your subdomain # You have PHP FastCGI Process Manager (php5-fpm) running on localhost +# You have Friendica installed in /mnt/friendica/www ## server { @@ -64,44 +65,45 @@ server { # rewrite to front controller as default rule location / { - rewrite ^/(.*) /index.php?q=$1 last; + rewrite ^/(.*) /index.php?q=$uri&$args last; } - # make sure webfinger isn't blocked by denying dot files - # and rewrite to front controller - location = /.well-known/host-meta { + # make sure webfinger and other well known services aren't blocked + # by denying dot files and rewrite request to the front controller + location ^~ /.well-known/ { allow all; - rewrite ^/(.*) /index.php?q=$1 last; + rewrite ^/(.*) /index.php?q=$uri&$args last; } # statically serve these file types when possible # otherwise fall back to front controller # allow browser to cache them # added .htm for advanced source code editor library - location ~* \.(jpg|jpeg|gif|png|css|js|ico|htm|html)$ { + location ~* \.(jpg|jpeg|gif|png|css|js|htm|html)$ { expires 30d; try_files $uri /index.php?q=$uri&$args; } - # block these file types - location ~* \.(tpl|md|git|tgz|log|out) { + location ~* \.(tpl|md|tgz|log|out)$ { deny all; } # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 location ~* \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; - # # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini - # # With php5-cgi alone: + # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini + + # With php5-cgi alone: # fastcgi_pass 127.0.0.1:9000; + # With php5-fpm: fastcgi_pass unix:/var/run/php5-fpm.sock; fastcgi_index index.php; include fastcgi_params; } - # deny access to all dot files (including .htaccess) + # deny access to all dot files location ~ /\. { deny all; } From c4900745899283f59b11e502026dcd9c95f21cc3 Mon Sep 17 00:00:00 2001 From: Olaf Conradi Date: Sat, 15 Dec 2012 16:43:24 +0100 Subject: [PATCH 4/4] Typos --- mods/sample-nginx.config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mods/sample-nginx.config b/mods/sample-nginx.config index 2383b6a447..0fb31efffc 100644 --- a/mods/sample-nginx.config +++ b/mods/sample-nginx.config @@ -2,7 +2,7 @@ # Friendica Nginx configuration # by Olaf Conradi # -# On debian based distributions you can add this file to +# On Debian based distributions you can add this file to # /etc/nginx/sites-available # # Then customize to your needs. To enable the configuration @@ -21,7 +21,7 @@ ## # This configuration assumes your domain is example.net -# You have a seperate subdomain friendica.example.net +# You have a separate subdomain friendica.example.net # You want all friendica traffic to be https # You have an SSL certificate and key for your subdomain # You have PHP FastCGI Process Manager (php5-fpm) running on localhost