composer.json config/config.ini.sample lib/Kolab web/index.php

Thomas Brüderli bruederli at kolabsys.com
Wed May 1 15:00:09 CEST 2013


 composer.json                        |    2 
 config/config.ini.sample             |   12 -
 lib/Kolab/Config.php                 |  284 +++++++++++++++++++++++++++++++++++
 lib/Kolab/FreeBusy/Config.php        |  173 ---------------------
 lib/Kolab/FreeBusy/Directory.php     |    2 
 lib/Kolab/FreeBusy/DirectoryLDAP.php |    2 
 web/index.php                        |   12 -
 7 files changed, 299 insertions(+), 188 deletions(-)

New commits:
commit 5b2bbaa400888eb9cdfe1cd512971036f2d494ba
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Wed May 1 14:59:58 2013 +0200

    Use the generic Kolab Config class for .ini file parsing. The user of the class can specify the extected type of a config option Fixes #1825

diff --git a/composer.json b/composer.json
index 910564b..74048b4 100644
--- a/composer.json
+++ b/composer.json
@@ -2,7 +2,7 @@
 	"name": "kolab/free-busy",
 	"description": "Kolab Free/Busy Service",
 	"license": "AGPL-3.0",
-	"version": "0.1.1",
+	"version": "0.1.2",
 	"repositories": [
 		{
 			"type": "pear",
diff --git a/config/config.ini.sample b/config/config.ini.sample
index 62437d3..778f15a 100644
--- a/config/config.ini.sample
+++ b/config/config.ini.sample
@@ -19,10 +19,10 @@
 
 ;; Allow privileged access from these IPs
 [trustednetworks]
-allow[] = 127.0.0.1
-allow[] = 192.168.0.0/16
-allow[] = 10.10.*
-allow[] = ::1
+allow = 127.0.0.1,
+  192.168.0.0/16,
+  10.10.*,
+   ::1
 
 ;; Logging configuration
 [log]
@@ -47,8 +47,8 @@ bind_dn = "uid=kolab-service,ou=Special Users,dc=yourdomain,dc=com"
 bind_pw = "<service-bind-pw>"
 base_dn = "dc=yourdomain,dc=com"
 filter = "(&(objectClass=kolabInetOrgPerson)(|(uid=%s)(mail=%s)(alias=%s)))"
-attributes[] = mail
-lc_attributes[] = mail
+attributes = mail, sn
+lc_attributes = sn
 fbsource = file:/www/kolab-freebusy/data/%mail.ifb
 loglevel = 100  ; Debug
 
diff --git a/lib/Kolab/Config.php b/lib/Kolab/Config.php
new file mode 100644
index 0000000..30cebbf
--- /dev/null
+++ b/lib/Kolab/Config.php
@@ -0,0 +1,284 @@
+<?php
+
+/**
+ * Model class to give access to service configuration
+ *
+ * This file is part of the Kolab PHP Utilities library
+ *
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact at kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace Kolab;
+
+/**
+ * Wrapper class for service configuration
+ */
+class Config
+{
+    const STRING = 0;
+    const BOOL   = 1;
+    const INT    = 2;
+    const FLOAT  = 3;
+    const ARR    = 4;
+
+    protected static $instance;
+
+    protected $env = '';
+    protected $basedir = '../config';
+    protected $data = array();
+    protected $valid = false;
+
+    /**
+     * Singelton getter
+     *
+     * @param string Path to load config from
+     */
+    public static function get_instance($env = '', $dir = null)
+    {
+        if (!isset(self::$instance)) {
+            self::$instance = new Config($env);
+            if ($dir) self::$instance->basedir = $dif;
+        }
+
+        if (!self::$instance->valid) {
+            self::$instance->load('config.ini');
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * Default constructor
+     */
+    function __construct($env = '')
+    {
+        $this->env = $env;
+        $this->load($this->basedir . '/config.ini');
+    }
+
+    /**
+     * Load config from the given .ini file
+     */
+    private function load($file, $use_env = true)
+    {
+        // check for relative path
+        if (!is_readable($inifile) && is_readable($this->basedir . '/' . $inifile)) {
+            $inifile = $this->basedir . '/' . $inifile;
+        }
+
+        $inifile = $this->resolve_path($file, $use_env);
+        if ($raw = self::parse_ini_file($inifile, true)) {
+            foreach ($raw as $section => $values) {
+                $sub = null;
+                if (preg_match('/^(\w+)\s+"?(.+)"?$/Ui', $section, $m)) {
+                    $section = $m[1];
+                    $sub = trim($m[2], '"');
+                }
+                if (!empty($sub) && !empty($values)) {
+                    $config[$section][$sub] = $values;
+                }
+                else if (!empty($values) && is_array($values)) {
+                    $config[$section] = $values;
+                }
+            }
+
+            $this->register($config);
+            $this->valid = !empty($this->data);
+        }
+        else {
+            trigger_error("Failed to parse configuration from $inifile", E_USER_ERROR);
+        }
+    }
+
+    /**
+     * Helper method to resolve the absolute path to the given config file.
+     * This also takes the 'env' property into account.
+     */
+    private function resolve_path($file, $use_env)
+    {
+        if ($file[0] != '/') {
+            $file = realpath($this->basedir . '/' . $file);
+        }
+
+        // check if <file>-env.ini exists
+        if ($file && $use_env && !empty($this->env)) {
+            $envfile = preg_replace('/\.(ini|conf)$/', '-' . $this->env . '.\\1', $file);
+            if (is_file($envfile))
+                return $envfile;
+        }
+        
+        return $file;
+    }
+
+    /**
+     * Replacement for PHP's parse_ini_file()
+     */
+    private static function parse_ini_file($filename)
+    {
+        $raw = array();
+        foreach (file($filename) as $_line) {
+            if ($_line[0] == ';')  // skip comments
+                continue;
+
+            if (preg_match('/^\[([a-z0-9-_\.]+[^\]]*)\]/', $_line, $matches)) {
+                $_cur_section = $matches[1];
+                $raw[$_cur_section] = array();
+                unset($_cur_key);
+            }
+
+            if (preg_match('/^([a-z0-9\.-_]+)\s*=\s*(.*)/', $_line, $matches)) {
+                if (isset($_cur_section) && !empty($_cur_section)) {
+                    $_cur_key = $matches[1];
+                    $raw[$_cur_section][$matches[1]] = isset($matches[2]) ? trim($matches[2], ' "') : '';
+                }
+            }
+            else if (preg_match('/^\s+(.*)$/', $_line, $matches)) {
+                if (isset($_cur_key) && !empty($_cur_key)) {
+                    $raw[$_cur_section][$_cur_key] .=  $matches[1];
+                }
+            }
+        }
+
+        return $raw;
+    }
+
+    /**
+     * Dump the hierarchical structure of config options into a flat list with keys delimited by dots
+     */
+    private function register($config, $prefix = '')
+    {
+        // merge the new config values over existing data
+        if (empty($prefix)) {
+            $this->data = array_replace_recursive($this->data, $config);
+        }
+        else if (is_array($config)) {
+            $pkey = rtrim($prefix, '.');
+            $this->data[$pkey] = is_array($this->data[$pkey]) ? array_replace_recursive((array)$this->data[$pkey], $config) : $config;
+        }
+
+        foreach ((array)$config as $key => $val) {
+            if (is_array($val)) {
+                $this->register($val, "$prefix$key.");
+            }
+            else {
+                $this->data[$prefix.$key] = $val;
+            }
+        }
+
+        // resolve references in config options (e.g. %(foo.bar))
+        if (empty($prefix)) {
+            array_walk_recursive($this->data, array($this, 'resolve_reference'));
+        }
+    }
+
+    /**
+     * Callback to resolve references in the given config option value
+     */
+    private function resolve_reference(&$value, $key)
+    {
+        if (is_string($value)) {
+            $value = preg_replace_callback('/%[({]([\w.]+)[})]/i', array($this, 'replace_reference'), $value);
+        }
+    }
+
+    /**
+     * Callback function to replace the given reference with the read config value
+     */
+    private function replace_reference($m)
+    {
+        return $this->data[$m[1]];
+    }
+
+    /**
+     * Magic getter for direct read-only access to config options
+     */
+    public function __get($name)
+    {
+        return $this->data[$name];
+    }
+
+    /**
+     * Magic isset check
+     */
+    public function __isset($name)
+    {
+        return array_key_exists($name, $this->data);
+    }
+
+    /**
+     * Common getter for config options with fallback to default values
+     *
+     * @param string  Config option name
+     * @param mixed   Default value if option isn't set in config
+     * @param integer Expected variable type
+     * @return mixed Config option value
+     */
+    public function get($name, $default = null, $type = null)
+    {
+        switch ($name) {
+            case 'output.tempdir':
+            case 'session.savepath':
+                // return an absolute path for relative directory properties
+                if (isset($this->data[$name]) && $this->data[$name][0] != '/') {
+                    $value = realpath(INSTALL_PATH . '/' . $this->data[$name]);
+                    break;
+                }
+
+            default:
+                $value = array_key_exists($name, $this->data) ? $this->data[$name] : $default;
+        }
+
+        // convert value to the requested type
+        return $type ? self::convert($value, $type) : value;
+    }
+
+    /**
+     * Determines whether we have a valid configuration loaded
+     *
+     * @return boolean True if valid, False otherwise
+     */
+    public function valid()
+    {
+        return !empty($this->data);
+    }
+
+    /**
+     * Convert the given (string) value to the requested type
+     *
+     * @param string  Config value
+     * @param int     Output type (one of this class constants)
+     * @return mixed  The converted value
+     */
+    public static function convert($value, $type)
+    {
+        // convert value to the requested type
+        switch ($type) {
+            case self::INT:
+                return intval($value);
+            case self::FLOAT:
+                return floatval($value);
+            case self::BOOL:
+                return (bool)preg_match('/^(true|1|on|enabled|yes)$/i', $value);
+            case self::ARR:
+                return preg_split('/,\s*/', strval($value));
+        }
+
+        return $value;
+    }
+}
+
diff --git a/lib/Kolab/FreeBusy/Config.php b/lib/Kolab/FreeBusy/Config.php
deleted file mode 100644
index ab82a1a..0000000
--- a/lib/Kolab/FreeBusy/Config.php
+++ /dev/null
@@ -1,173 +0,0 @@
-<?php
-
-/**
- * This file is part of the Kolab Server Free/Busy Service
- *
- * @author Thomas Bruederli <bruederli at kolabsys.com>
- *
- * Copyright (C) 2013, Kolab Systems AG <contact at kolabsys.com>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-namespace Kolab\FreeBusy;
-
-/**
- * Wrapper class for service configuration
- */
-class Config
-{
-	private static $instance;
-
-	private $basedir = '.';
-	private $data = array();
-	private $valid = false;
-
-	/**
-	 * Singelton getter
-	 *
-	 * @param string Path to load config from
-	 */
-	public static function getInstance($dir = null)
-	{
-		if (!isset(self::$instance)) {
-			self::$instance = new Config($dir);
-		}
-
-		if ($dir && !self::$instance->valid) {
-			self::$instance->load($configdir . '/config.ini');
-		}
-
-		return self::$instance;
-	}
-
-	/**
-	 * Default constructor
-	 */
-	function __construct($configdir = null)
-	{
-		if ($configdir) {
-			$this->basedir = $configdir;
-			$this->load($configdir . '/config.ini');
-		}
-	}
-
-	/**
-	 * Load config from the given .ini file
-	 */
-	private function load($inifile)
-	{
-		if ($raw = parse_ini_file($inifile, true)) {
-			$config['directories'] = array();
-			foreach ($raw as $section => $values) {
-				// check for known sections
-				if (in_array($section, array('httpauth','trustednetworks','log'))) {
-					$config[$section] = $values;
-				}
-				else if (strpos($section, 'directory') === 0 || isset($values['fbsource'])) {
-					$sect = preg_replace('/^directory\s*/', '', $section);
-					$key = strlen($sect) ? $sect : count($config['directories']);
-					$config['directories'][$key] = $values;
-				}
-				else if (!empty($values) && is_array($values)) {
-					$config[$section] = $values;
-				}
-			}
-
-			$this->register($config);
-			$this->valid = !empty($this->data['directories']);
-		}
-		else {
-			trigger_error("Failed to parse configuration from $inifile", E_USER_ERROR);
-		}
-	}
-
-	/**
-	 * Dump the hierarchical structure of config options into a flat list with keys delimited by dots
-	 */
-	private function register($config, $prefix = '')
-	{
-		// merge the new config values over existing data
-		if (empty($prefix)) {
-			$this->data = array_replace_recursive($this->data, $config);
-		}
-		else if (is_array($config)) {
-			$pkey = rtrim($prefix, '.');
-			$this->data[$pkey] = is_array($this->data[$pkey]) ? array_replace_recursive((array)$this->data[$pkey], $config) : $config;
-		}
-
-		foreach ((array)$config as $key => $val) {
-			if (is_array($val)) {
-				$this->register($val, "$prefix$key.");
-			}
-			else {
-				$this->data[$prefix.$key] = $val;
-			}
-		}
-
-		// resolve references in config options (e.g. %(foo.bar))
-		if (empty($prefix)) {
-			array_walk_recursive($this->data, array($this, 'resolve_reference'));
-		}
-	}
-
-	/**
-	 * Callback to resolve references in the given config option value
-	 */
-	private function resolve_reference(&$value, $key)
-	{
-		if (is_string($value)) {
-			$value = preg_replace_callback('/%[({]([\w.]+)[})]/i', array($this, 'replace_reference'), $value);
-		}
-	}
-
-	/**
-	 * Callback function to replace the given reference with the read config value
-	 */
-	private function replace_reference($m)
-	{
-		return $this->data[$m[1]];
-	}
-
-	/**
-	 * Magic getter for direct read-only access to config options
-	 */
-	public function __get($name)
-	{
-		return $this->data[$name];
-	}
-
-	/** 
-	 * Common getter for config options with fallback in default values
-	 *
-	 * @param string Config option name
-	 * @param mixed  Default value if option isn't set in config
-	 * @return mixed Config option value
-	 */
-	public function get($name, $default = null)
-	{
-		return array_key_exists($name, $this->data) ? $this->data[$name] : $default;
-	}
-
-	/**
-	 * Determines whether we have a valid configuration loaded
-	 *
-	 * @return boolean True if valid, False otherwise
-	 */
-	public function isValid()
-	{
-		return !empty($this->data);
-	}
-}
-
diff --git a/lib/Kolab/FreeBusy/Directory.php b/lib/Kolab/FreeBusy/Directory.php
index 1c70f75..6478e85 100644
--- a/lib/Kolab/FreeBusy/Directory.php
+++ b/lib/Kolab/FreeBusy/Directory.php
@@ -93,7 +93,7 @@ abstract class Directory
 	protected function postprocessAttrib($attrib)
 	{
 		if (!empty($this->config['lc_attributes'])) {
-			foreach ((array)$this->config['lc_attributes'] as $key) {
+			foreach (Config::convert($this->config['lc_attributes'], Config::ARR) as $key) {
 				if (!empty($attrib[$key]))
 					$attrib[$key] = strtolower($attrib[$key]);
 			}
diff --git a/lib/Kolab/FreeBusy/DirectoryLDAP.php b/lib/Kolab/FreeBusy/DirectoryLDAP.php
index 5e31e24..f462f79 100644
--- a/lib/Kolab/FreeBusy/DirectoryLDAP.php
+++ b/lib/Kolab/FreeBusy/DirectoryLDAP.php
@@ -49,7 +49,7 @@ class DirectoryLDAP extends Directory
 			'port'      => $host['port'] ?: 389,
 			'use_tls'   => $host['scheme'] == 'tls' || $host['scheme'] == 'ldaps',
 			'root_dn'   => $config['base_dn'],
-			'return_attributes' => (array)$config['attributes'],
+			'return_attributes' => Config::convert($config['attributes'], Config::ARR),
 			'log_hook'  => array($this, 'log'),
 		) + $config;
 
diff --git a/web/index.php b/web/index.php
index 93a998a..fcbce69 100644
--- a/web/index.php
+++ b/web/index.php
@@ -5,7 +5,7 @@
  *
  * This is the public API to provide Free/Busy information for Kolab users.
  *
- * @version 0.1.1
+ * @version 0.1.2
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  *
  * Copyright (C) 2013, Kolab Systems AG <contact at kolabsys.com>
@@ -33,19 +33,19 @@ ini_set('error_reporting', E_ALL &~ E_NOTICE);
 // use composer's autoloader for both dependencies and local lib
 require_once KOLAB_FREEBUSY_ROOT . '/vendor/autoload.php';
 
+use Kolab\Config;
 use Kolab\FreeBusy\Utils;
-use Kolab\FreeBusy\Config;
 use Kolab\FreeBusy\Logger;
 use Kolab\FreeBusy\Directory;
 use Kolab\FreeBusy\HTTPAuth;
 
 
 // load config
-$config = Config::getInstance(KOLAB_FREEBUSY_ROOT . '/config');
-if ($config->isValid()) {
+$config = Config::get_instance(KOLAB_FREEBUSY_ROOT . '/config');
+if ($config->valid()) {
 	// check for trusted IP first
 	$remote_ip = Utils::remoteIP();
-	$trusted_ip = $config->trustednetworks ? Utils::checkIPRange($remote_ip, $config->trustednetworks['allow']) : false;
+	$trusted_ip = $config->trustednetworks ? Utils::checkIPRange($remote_ip, $config->get('trustednetworks.allow', array(), Config::ARR)) : false;
 
 	$log = Logger::get('web');
 
@@ -85,7 +85,7 @@ if ($config->isValid()) {
 	}
 
 	// iterate over directories
-	foreach ($config->directories as $key => $dirconfig) {
+	foreach ($config->directory as $key => $dirconfig) {
 		$log->addDebug("Trying directory $key", $dirconfig);
 
 		$directory = Directory::factory($dirconfig);





More information about the commits mailing list