Fix for namespace parsing. UWash has a namespace prefix without a delimiter, which...
[squirrelmail.git] / functions / abook_local_file.php
index a841c03562b26e601b139cc5a0bf1d1c517b80f0..4b2b9a751407b608cab107ec1fa4bbb7dc77dede 100644 (file)
@@ -1,10 +1,10 @@
 <?php
+
 /**
  * abook_local_file.php
  *
- * Copyright (c) 1999-2004 The SquirrelMail Project Team
- * Licensed under the GNU GPL. For full terms see the file COPYING.
- *
+ * @copyright &copy; 1999-2006 The SquirrelMail Project Team
+ * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  * @version $Id$
  * @package squirrelmail
  * @subpackage addressbook
  *   filename  => path to addressbook file
  * ? create    => if true: file is created if it does not exist.
  * ? umask     => umask set before opening file.
- * ? name      => name of address book
+ * ? name      => name of address book.
+ * ? detect_writeable => detect address book access permissions by
+ *                checking file permissions.
+ * ? writeable => allow writing into address book. Used only when
+ *                detect_writeable is set to false.
+ * ? listing   => enable/disable listing
  *</pre>
  * NOTE. This class should not be used directly. Use the
  *       "AddressBook" class instead.
  * @package squirrelmail
  */
 class abook_local_file extends addressbook_backend {
-    /** @var string backend type */
+    /**
+     * Backend type
+     * @var string
+     */
     var $btype = 'local';
-    /** @var string backend name */
+    /**
+     * Backend name
+     * @var string
+     */
     var $bname = 'local_file';
 
-    /** @var string file used to store data */
-    var $filename   = '';
-    /** @var object file handle */
+    /**
+     * File used to store data
+     * @var string
+     */
+    var $filename = '';
+    /**
+     * File handle
+     * @var object
+     */
     var $filehandle = 0;
-    /** @var bool create file if it is not present */
-    var $create     = false;
-    /** @var string umask of the file */
+    /**
+     * Create file, if it not present
+     * @var bool
+     */
+    var $create = false;
+    /**
+     * Detect, if address book is writeable by checking file permisions
+     * @var bool
+     */
+    var $detect_writeable   = true;
+    /**
+     * Control write access to address book
+     *
+     * Option does not have any effect, if 'detect_writeable' is 'true'
+     * @var bool
+     */
+    var $writeable = false;
+    /**
+     * controls listing of address book
+     * @var bool
+     */
+    var $listing = true;
+    /**
+     * Umask of the file
+     * @var string
+     */
     var $umask;
+    /**
+     * Sets max entry size (number of bytes used for all address book fields 
+     * (including escapes) + 4 delimiters + 1 linefeed)
+     * @var integer
+     * @since 1.5.2
+     */
+    var $line_length = 2048;
 
     /* ========================== Private ======================= */
 
@@ -70,9 +117,21 @@ class abook_local_file extends addressbook_backend {
             if(isset($param['umask'])) {
                 $this->umask = $param['umask'];
             }
-            if(!empty($param['name'])) {
+            if(isset($param['name'])) {
                 $this->sname = $param['name'];
             }
+            if(isset($param['detect_writeable'])) {
+                $this->detect_writeable = $param['detect_writeable'];
+            }
+            if(!empty($param['writeable'])) {
+                $this->writeable = $param['writeable'];
+            }
+            if(isset($param['listing'])) {
+                $this->listing = $param['listing'];
+            }
+            if(isset($param['line_length']) && ! empty($param['line_length'])) {
+                $this->line_length = (int) $param['line_length'];
+            }
 
             $this->open(true);
         } else {
@@ -92,6 +151,7 @@ class abook_local_file extends addressbook_backend {
         $this->error = '';
         $file   = $this->filename;
         $create = $this->create;
+        $fopenmode = (($this->writeable && is_writable($file)) ? 'a+' : 'r');
 
         /* Return true is file is open and $new is unset */
         if($this->filehandle && !$new) {
@@ -106,22 +166,32 @@ class abook_local_file extends addressbook_backend {
         /* Close old file, if any */
         if($this->filehandle) { $this->close(); }
 
-        /* Open file. First try to open for reading and writing,
-         * but fall back to read only. */
         umask($this->umask);
-        $fh = @fopen($file, 'a+');
-        if($fh) {
-            $this->filehandle = &$fh;
-            $this->filename   = $file;
-            $this->writeable  = true;
+        if (! $this->detect_writeable) {
+            $fh = @fopen($file,$fopenmode);
+            if ($fh) {
+                $this->filehandle = &$fh;
+                $this->filename = $file;
+            } else {
+                return $this->set_error("$file: " . _("Open failed"));
+            }
         } else {
-            $fh = @fopen($file, 'r');
+            /* Open file. First try to open for reading and writing,
+             * but fall back to read only. */
+            $fh = @fopen($file, 'a+');
             if($fh) {
                 $this->filehandle = &$fh;
                 $this->filename   = $file;
-                $this->writeable  = false;
+                $this->writeable  = true;
             } else {
-                return $this->set_error("$file: " . _("Open failed"));
+                $fh = @fopen($file, 'r');
+                if($fh) {
+                    $this->filehandle = &$fh;
+                    $this->filename   = $file;
+                    $this->writeable  = false;
+                } else {
+                    return $this->set_error("$file: " . _("Open failed"));
+                }
             }
         }
         return true;
@@ -199,6 +269,10 @@ class abook_local_file extends addressbook_backend {
         /* To be replaced by advanded search expression parsing */
         if(is_array($expr)) { return; }
 
+        // don't allow wide search when listing is disabled.
+        if ($expr=='*' && ! $this->listing)
+            return array();
+
         /* Make regexp from glob'ed expression
          * May want to quote other special characters like (, ), -, [, ], etc. */
         $expr = str_replace('?', '.', $expr);
@@ -210,17 +284,32 @@ class abook_local_file extends addressbook_backend {
         }
         @rewind($this->filehandle);
 
-        while ($row = @fgetcsv($this->filehandle, 2048, '|')) {
-            $line = join(' ', $row);
-            if(eregi($expr, $line)) {
-                array_push($res, array('nickname'  => $row[0],
-                    'name'      => $row[1] . ' ' . $row[2],
-                    'firstname' => $row[1],
-                    'lastname'  => $row[2],
-                    'email'     => $row[3],
-                    'label'     => $row[4],
-                    'backend'   => $this->bnum,
-                    'source'    => &$this->sname));
+        while ($row = @fgetcsv($this->filehandle, $this->line_length, '|')) {
+            if (count($row)<5) {
+                /**
+                 * address book is corrupted.
+                 */
+                global $oTemplate;
+                error_box(_("Address book is corrupted. Required fields are missing."));
+                $oTemplate->display('footer.tpl');
+                die();
+            } else {
+                $line = join(' ', $row);
+                /**
+                 * TODO: regexp search is supported only in local_file backend.
+                 * Do we check format of regexp or ignore errors?
+                 */
+                // errors on eregi call are suppressed in order to prevent display of regexp compilation errors
+                if(@eregi($expr, $line)) {
+                    array_push($res, array('nickname'  => $row[0],
+                        'name'      => $this->fullname($row[1], $row[2]),
+                        'firstname' => $row[1],
+                        'lastname'  => $row[2],
+                        'email'     => $row[3],
+                        'label'     => $row[4],
+                        'backend'   => $this->bnum,
+                        'source'    => &$this->sname));
+                }
             }
         }
 
@@ -242,16 +331,26 @@ class abook_local_file extends addressbook_backend {
         $this->open();
         @rewind($this->filehandle);
 
-        while ($row = @fgetcsv($this->filehandle, 2048, '|')) {
-            if(strtolower($row[0]) == $alias) {
-                return array('nickname'  => $row[0],
-                  'name'      => $row[1] . ' ' . $row[2],
-                  'firstname' => $row[1],
-                  'lastname'  => $row[2],
-                  'email'     => $row[3],
-                  'label'     => $row[4],
-                  'backend'   => $this->bnum,
-                  'source'    => &$this->sname);
+        while ($row = @fgetcsv($this->filehandle, $this->line_length, '|')) {
+            if (count($row)<5) {
+                /**
+                 * address book is corrupted.
+                 */
+                global $oTemplate;
+                error_box(_("Address book is corrupted. Required fields are missing."));
+                $oTemplate->display('footer.tpl');
+                die();
+            } else {
+                if(strtolower($row[0]) == $alias) {
+                   return array('nickname'  => $row[0],
+                      'name'      => $this->fullname($row[1], $row[2]),
+                      'firstname' => $row[1],
+                      'lastname'  => $row[2],
+                      'email'     => $row[3],
+                      'label'     => $row[4],
+                      'backend'   => $this->bnum,
+                      'source'    => &$this->sname);
+                }
             }
         }
 
@@ -264,18 +363,34 @@ class abook_local_file extends addressbook_backend {
      */
     function list_addr() {
         $res = array();
+
+        if(isset($this->listing) && !$this->listing) {
+            return array();
+        }
+
         $this->open();
         @rewind($this->filehandle);
 
-        while ($row = @fgetcsv($this->filehandle, 2048, '|')) {
-            array_push($res, array('nickname'  => $row[0],
-                'name'      => $row[1] . ' ' . $row[2],
-                'firstname' => $row[1],
-                'lastname'  => $row[2],
-                'email'     => $row[3],
-                'label'     => $row[4],
-                'backend'   => $this->bnum,
-                'source'    => &$this->sname));
+        while ($row = @fgetcsv($this->filehandle, $this->line_length, '|')) {
+            if (count($row)<5) {
+                /**
+                 * address book is corrupted. Don't be nice to people that 
+                 * violate address book formating.
+                 */
+                global $oTemplate;
+                error_box(_("Address book is corrupted. Required fields are missing."));
+                $oTemplate->display('footer.tpl');
+                die();
+            } else {
+                array_push($res, array('nickname'  => $row[0],
+                    'name'      => $this->fullname($row[1], $row[2]),
+                    'firstname' => $row[1],
+                    'lastname'  => $row[2],
+                    'email'     => $row[3],
+                    'label'     => $row[4],
+                    'backend'   => $this->bnum,
+                    'source'    => &$this->sname));
+            }
         }
         return $res;
     }
@@ -287,31 +402,40 @@ class abook_local_file extends addressbook_backend {
      */
     function add($userdata) {
         if(!$this->writeable) {
-            return $this->set_error(_("Addressbook is read-only"));
+            return $this->set_error(_("Address book is read-only"));
         }
         /* See if user exists already */
         $ret = $this->lookup($userdata['nickname']);
         if(!empty($ret)) {
-            return $this->set_error(sprintf(_("User '%s' already exist"),
-                   $ret['nickname']));
+            // i18n: don't use html formating in translation
+            return $this->set_error(sprintf(_("User \"%s\" already exists"),$ret['nickname']));
         }
 
         /* Here is the data to write */
         $data = $this->quotevalue($userdata['nickname']) . '|' .
                 $this->quotevalue($userdata['firstname']) . '|' .
-                $this->quotevalue($userdata['lastname']) . '|' .
+                $this->quotevalue((!empty($userdata['lastname'])?$userdata['lastname']:'')) . '|' .
                 $this->quotevalue($userdata['email']) . '|' .
-                $this->quotevalue($userdata['label']);
+                $this->quotevalue((!empty($userdata['label'])?$userdata['label']:''));
 
         /* Strip linefeeds */
         $data = ereg_replace("[\r\n]", ' ', $data);
+
+        /**
+         * Make sure that entry fits into allocated record space.
+         * One byte is reserved for linefeed
+         */
+        if (strlen($data) >= $this->line_length) {
+            return $this->set_error(_("Address book entry is too big"));
+        }
+
         /* Add linefeed at end */
         $data = $data . "\n";
 
         /* Reopen file, just to be sure */
         $this->open(true);
         if(!$this->writeable) {
-            return $this->set_error(_("Addressbook is read-only"));
+            return $this->set_error(_("Address book is read-only"));
         }
 
         /* Lock the file */
@@ -328,7 +452,7 @@ class abook_local_file extends addressbook_backend {
         /* Test write result */
         if($r === FALSE) {
             /* Fail */
-            $this->set_error(_("Write to addressbook failed"));
+            $this->set_error(_("Write to address book failed"));
             return FALSE;
         }
 
@@ -342,7 +466,7 @@ class abook_local_file extends addressbook_backend {
      */
     function remove($alias) {
         if(!$this->writeable) {
-            return $this->set_error(_("Addressbook is read-only"));
+            return $this->set_error(_("Address book is read-only"));
         }
 
         /* Lock the file to make sure we're the only process working
@@ -355,7 +479,7 @@ class abook_local_file extends addressbook_backend {
         @rewind($this->filehandle);
         $i = 0;
         $rows = array();
-        while($row = @fgetcsv($this->filehandle, 2048, '|')) {
+        while($row = @fgetcsv($this->filehandle, $this->line_length, '|')) {
             if(!in_array($row[0], $alias)) {
                 $rows[$i++] = $row;
             }
@@ -379,37 +503,56 @@ class abook_local_file extends addressbook_backend {
      */
     function modify($alias, $userdata) {
         if(!$this->writeable) {
-            return $this->set_error(_("Addressbook is read-only"));
+            return $this->set_error(_("Address book is read-only"));
         }
 
         /* See if user exists */
         $ret = $this->lookup($alias);
         if(empty($ret)) {
-            return $this->set_error(sprintf(_("User '%s' does not exist"),
-                $alias));
+            // i18n: don't use html formating in translation
+            return $this->set_error(sprintf(_("User \"%s\" does not exist"),$alias));
         }
-
+        
+        /* If the alias changed, see if the new alias exists */
+        if (strtolower($alias) != strtolower($userdata['nickname'])) {
+            $ret = $this->lookup($userdata['nickname']);
+            if (!empty($ret)) {
+                return $this->set_error(sprintf(_("User \"%s\" already exists"), $userdata['nickname']));
+            }
+        }
+        
         /* Lock the file to make sure we're the only process working
          * on it. */
         if(!$this->lock()) {
             return $this->set_error(_("Could not lock datafile"));
         }
 
+        /* calculate userdata size */
+        $data = $this->quotevalue($userdata['nickname']) . '|'
+            . $this->quotevalue($userdata['firstname']) . '|'
+            . $this->quotevalue((!empty($userdata['lastname'])?$userdata['lastname']:'')) . '|'
+            . $this->quotevalue($userdata['email']) . '|'
+            . $this->quotevalue((!empty($userdata['label'])?$userdata['label']:''));
+        /* make sure that it fits into allocated space */
+        if (strlen($data) >= $this->line_length) {
+            return $this->set_error(_("Address book entry is too big"));
+        }
+        
         /* Read file into memory, modifying the data for the
          * user identified by $alias */
         $this->open(true);
         @rewind($this->filehandle);
         $i = 0;
         $rows = array();
-        while($row = @fgetcsv($this->filehandle, 2048, '|')) {
+        while($row = @fgetcsv($this->filehandle, $this->line_length, '|')) {
             if(strtolower($row[0]) != strtolower($alias)) {
                 $rows[$i++] = $row;
             } else {
                 $rows[$i++] = array(0 => $userdata['nickname'],
                                     1 => $userdata['firstname'],
-                                    2 => $userdata['lastname'],
+                                    2 => (!empty($userdata['lastname'])?$userdata['lastname']:''),
                                     3 => $userdata['email'],
-                                    4 => $userdata['label']);
+                                    4 => (!empty($userdata['label'])?$userdata['label']:''));
             }
         }
 
@@ -436,6 +579,4 @@ class abook_local_file extends addressbook_backend {
         }
         return $value;
     }
-
-} /* End of class abook_local_file */
-?>
\ No newline at end of file
+}