X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=functions%2Fabook_local_file.php;h=762377c3a09ee6835bd679ebead5ffc07ce140c6;hb=517f50995c634e600e73cf4208e90c464fe798b6;hp=b91af2bc1b5a29ac9fc730d28593c5d5e5cf7c66;hpb=01265fbab60e51c5d97f55cfefe5888abb383ae9;p=squirrelmail.git diff --git a/functions/abook_local_file.php b/functions/abook_local_file.php index b91af2bc..762377c3 100644 --- a/functions/abook_local_file.php +++ b/functions/abook_local_file.php @@ -3,36 +3,92 @@ /** * abook_local_file.php * - * Copyright (c) 1999-2002 The SquirrelMail Project Team - * Licensed under the GNU GPL. For full terms see the file COPYING. + * @copyright © 1999-2005 The SquirrelMail Project Team + * @license http://opensource.org/licenses/gpl-license.php GNU Public License + * @version $Id$ + * @package squirrelmail + * @subpackage addressbook + */ + +/** + * Backend for address book as a pipe separated file * - * Backend for addressbook as a pipe separated file + * Stores the address book in a local file * * An array with the following elements must be passed to * the class constructor (elements marked ? are optional): - * - * filename => path to addressbook file - * ? create => if true: file is created if it does not exist. - * ? umask => umask set before opening file. - * + *
+ *   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.
+ * ? 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
+ *
* NOTE. This class should not be used directly. Use the * "AddressBook" class instead. - * - * $Id$ + * @package squirrelmail */ - class abook_local_file extends addressbook_backend { + /** + * Backend type + * @var string + */ var $btype = 'local'; + /** + * Backend name + * @var string + */ var $bname = 'local_file'; - var $filename = ''; + /** + * File used to store data + * @var string + */ + var $filename = ''; + /** + * File handle + * @var object + */ var $filehandle = 0; - var $create = false; + /** + * 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; /* ========================== Private ======================= */ - /* Constructor */ + /** + * Constructor + * @param array $param backend options + * @return bool + */ function abook_local_file($param) { $this->sname = _("Personal address book"); $this->umask = Umask(); @@ -48,66 +104,90 @@ class abook_local_file extends addressbook_backend { $this->filename = $param['filename']; - if($param['create']) { - $this->create = true; + if(isset($param['create'])) { + $this->create = $param['create']; } 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']; + } + $this->open(true); } else { $this->set_error('Invalid argument to constructor'); } } - /* Open the addressbook file and store the file pointer. - * Use $file as the file to open, or the class' own - * filename property. If $param is empty and file is - * open, do nothing. */ + /** + * Open the addressbook file and store the file pointer. + * Use $file as the file to open, or the class' own + * filename property. If $param is empty and file is + * open, do nothing. + * @param bool $new is file already opened + * @return bool + */ function open($new = false) { $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) { return true; } - + /* Check that new file exitsts */ if((!(file_exists($file) && is_readable($file))) && !$create) { return $this->set_error("$file: " . _("No such file or directory")); } - + /* 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; } - /* Close the file and forget the filehandle */ + /** Close the file and forget the filehandle */ function close() { @fclose($this->filehandle); $this->filehandle = 0; @@ -115,10 +195,10 @@ class abook_local_file extends addressbook_backend { $this->writable = false; } - /* Lock the datafile - try 20 times in 5 seconds */ + /** Lock the datafile - try 20 times in 5 seconds */ function lock() { for($i = 0 ; $i < 20 ; $i++) { - if(flock($this->filehandle, 2 + 4)) + if(flock($this->filehandle, 2 + 4)) return true; else usleep(250000); @@ -126,51 +206,74 @@ class abook_local_file extends addressbook_backend { return false; } - /* Lock the datafile */ + /** Unlock the datafile */ function unlock() { return flock($this->filehandle, 3); } - /* Overwrite the file with data from $rows - * NOTE! Previous locks are broken by this function */ + /** + * Overwrite the file with data from $rows + * NOTE! Previous locks are broken by this function + * @param array $rows new data + * @return bool + */ function overwrite(&$rows) { $this->unlock(); - $newfh = @fopen($this->filename, 'w'); + $newfh = @fopen($this->filename.'.tmp', 'w'); + if(!$newfh) { - return $this->set_error("$file: " . _("Open failed")); + return $this->set_error($this->filename. '.tmp:' . _("Open failed")); } - - for($i = 0 ; $i < sizeof($rows) ; $i++) { + + for($i = 0, $cnt=sizeof($rows) ; $i < $cnt ; $i++) { if(is_array($rows[$i])) { - fwrite($newfh, join('|', $rows[$i]) . "\n"); + for($j = 0, $cnt_part=count($rows[$i]) ; $j < $cnt_part ; $j++) { + $rows[$i][$j] = $this->quotevalue($rows[$i][$j]); + } + $tmpwrite = sq_fwrite($newfh, join('|', $rows[$i]) . "\n"); + if ($tmpwrite === FALSE) { + return $this->set_error($this->filename . '.tmp:' . _("Write failed")); + } } - } + } fclose($newfh); + if (!@copy($this->filename . '.tmp' , $this->filename)) { + return $this->set_error($this->filename . ':' . _("Unable to update")); + } + @unlink($this->filename . '.tmp'); $this->unlock(); $this->open(true); return true; } - + /* ========================== Public ======================== */ - - /* Search the file */ + + /** + * Search the file + * @param string $expr search expression + * @return array search results + */ function search($expr) { /* 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); $expr = str_replace('*', '.*', $expr); - + $res = array(); if(!$this->open()) { return false; } @rewind($this->filehandle); - + while ($row = @fgetcsv($this->filehandle, 2048, '|')) { $line = join(' ', $row); if(eregi($expr, $line)) { @@ -184,21 +287,25 @@ class abook_local_file extends addressbook_backend { 'source' => &$this->sname)); } } - + return $res; } - - /* Lookup alias */ + + /** + * Lookup alias + * @param string $alias alias + * @return array search results + */ function lookup($alias) { if(empty($alias)) { return array(); } $alias = strtolower($alias); - + $this->open(); @rewind($this->filehandle); - + while ($row = @fgetcsv($this->filehandle, 2048, '|')) { if(strtolower($row[0]) == $alias) { return array('nickname' => $row[0], @@ -211,16 +318,24 @@ class abook_local_file extends addressbook_backend { 'source' => &$this->sname); } } - + return array(); } - /* List all addresses */ + /** + * List all addresses + * @return array list of all addresses + */ 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], @@ -234,7 +349,11 @@ class abook_local_file extends addressbook_backend { return $res; } - /* Add address */ + /** + * Add address + * @param array $userdata new data + * @return bool + */ function add($userdata) { if(!$this->writeable) { return $this->set_error(_("Addressbook is read-only")); @@ -242,56 +361,65 @@ class abook_local_file extends addressbook_backend { /* 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 = $userdata['nickname'] . '|' . $userdata['firstname'] . '|' . - $userdata['lastname'] . '|' . $userdata['email'] . '|' . - $userdata['label']; + $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']:'')); + /* Strip linefeeds */ $data = ereg_replace("[\r\n]", ' ', $data); /* 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")); } - + /* Lock the file */ if(!$this->lock()) { return $this->set_error(_("Could not lock datafile")); } - + /* Write */ - $r = fwrite($this->filehandle, $data); - + $r = sq_fwrite($this->filehandle, $data); + /* Unlock file */ $this->unlock(); - - /* Test write result and exit if OK */ - if($r > 0) return true; - - /* Fail */ - $this->set_error(_("Write to addressbook failed")); - return false; + + /* Test write result */ + if($r === FALSE) { + /* Fail */ + $this->set_error(_("Write to addressbook failed")); + return FALSE; + } + + return TRUE; } - /* Delete address */ + /** + * Delete address + * @param string $alias alias that has to be deleted + * @return bool + */ function remove($alias) { if(!$this->writeable) { return $this->set_error(_("Addressbook is read-only")); } - + /* 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")); } - + /* Read file into memory, ignoring nicknames to delete */ @rewind($this->filehandle); $i = 0; @@ -301,37 +429,42 @@ class abook_local_file extends addressbook_backend { $rows[$i++] = $row; } } - + /* Write data back */ if(!$this->overwrite($rows)) { $this->unlock(); return false; } - + $this->unlock(); return true; } - /* Modify address */ + /** + * Modify address + * @param string $alias modified alias + * @param array $userdata new data + * @return bool true, if operation successful + */ function modify($alias, $userdata) { if(!$this->writeable) { return $this->set_error(_("Addressbook 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)); } - + /* 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")); } - - /* Read file into memory, modifying the data for the + + /* Read file into memory, modifying the data for the * user identified by $alias */ $this->open(true); @rewind($this->filehandle); @@ -343,21 +476,35 @@ class abook_local_file extends addressbook_backend { } else { $rows[$i++] = array(0 => $userdata['nickname'], 1 => $userdata['firstname'], - 2 => $userdata['lastname'], - 3 => $userdata['email'], - 4 => $userdata['label']); + 2 => (!empty($userdata['lastname'])?$userdata['lastname']:''), + 3 => $userdata['email'], + 4 => (!empty($userdata['label'])?$userdata['label']:'')); } } - + /* Write data back */ if(!$this->overwrite($rows)) { $this->unlock(); return false; } - + $this->unlock(); return true; } - + + /** + * Function for quoting values before saving + * @param string $value string that has to be quoted + * @param string quoted string + */ + function quotevalue($value) { + /* Quote the field if it contains | or ". Double quotes need to + * be replaced with "" */ + if(ereg("[|\"]", $value)) { + $value = '"' . str_replace('"', '""', $value) . '"'; + } + return $value; + } + } /* End of class abook_local_file */ -?> \ No newline at end of file +?>