Source for file abook_local_file.php

Documentation is available at abook_local_file.php

  1. <?php
  2.  
  3. /**
  4.  * abook_local_file.php
  5.  *
  6.  * @copyright 1999-2020 The SquirrelMail Project Team
  7.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  8.  * @version $Id: abook_local_file.php 14840 2020-01-07 07:42:38Z pdontthink $
  9.  * @package squirrelmail
  10.  * @subpackage addressbook
  11.  */
  12.  
  13. /**
  14.  * Backend for address book as a pipe separated file
  15.  *
  16.  * Stores the address book in a local file
  17.  *
  18.  * An array with the following elements must be passed to
  19.  * the class constructor (elements marked ? are optional):
  20.  *<pre>
  21.  *   filename  => path to addressbook file
  22.  * ? create    => if true: file is created if it does not exist.
  23.  * ? umask     => umask set before opening file.
  24.  * ? name      => name of address book.
  25.  * ? detect_writeable => detect address book access permissions by
  26.  *                checking file permissions.
  27.  * ? writeable => allow writing into address book. Used only when
  28.  *                detect_writeable is set to false.
  29.  * ? listing   => enable/disable listing
  30.  * ? line_length => allowed address book record size
  31.  *</pre>
  32.  * NOTE. This class should not be used directly. Use the
  33.  *       "AddressBook" class instead.
  34.  * @package squirrelmail
  35.  */
  36.     /**
  37.      * Backend type
  38.      * @var string 
  39.      */
  40.     var $btype = 'local';
  41.     /**
  42.      * Backend name
  43.      * @var string 
  44.      */
  45.     var $bname = 'local_file';
  46.  
  47.     /**
  48.      * File used to store data
  49.      * @var string 
  50.      */
  51.     var $filename = '';
  52.     /**
  53.      * File handle
  54.      * @var object 
  55.      */
  56.     var $filehandle = 0;
  57.     /**
  58.      * Create file, if it not present
  59.      * @var bool 
  60.      */
  61.     var $create = false;
  62.     /**
  63.      * Detect, if address book is writeable by checking file permisions
  64.      * @var bool 
  65.      */
  66.     var $detect_writeable   = true;
  67.     /**
  68.      * Control write access to address book
  69.      *
  70.      * Option does not have any effect, if 'detect_writeable' is 'true'
  71.      * @var bool 
  72.      */
  73.     var $writeable = false;
  74.     /**
  75.      * controls listing of address book
  76.      * @var bool 
  77.      * @since 1.5.1 and 1.4.9
  78.      */
  79.     var $listing = true;
  80.     /**
  81.      * Umask of the file
  82.      * @var string 
  83.      */
  84.     var $umask;
  85.     /**
  86.      * Sets max entry size (number of bytes used for all address book fields
  87.      * (including escapes) + 4 delimiters + 1 linefeed)
  88.      * @var integer 
  89.      * @since 1.5.2 and 1.4.9
  90.      */
  91.     var $line_length = 2048;
  92.  
  93.     /* ========================== Private ======================= */
  94.  
  95.     /**
  96.      * Constructor (PHP5 style, required in some future version of PHP)
  97.      * @param array $param backend options
  98.      * @return bool 
  99.      */
  100.     function __construct($param{
  101.         $this->sname = _("Personal address book");
  102.         $this->umask = Umask();
  103.  
  104.         if(is_array($param)) {
  105.             if(empty($param['filename'])) {
  106.                 return $this->set_error('Invalid parameters');
  107.             }
  108.             if(!is_string($param['filename'])) {
  109.                 return $this->set_error($param['filename'': '.
  110.                      _("Not a file name"));
  111.             }
  112.  
  113.             $this->filename = $param['filename'];
  114.  
  115.             if(isset($param['create'])) {
  116.                 $this->create = $param['create'];
  117.             }
  118.             if(isset($param['umask'])) {
  119.                 $this->umask = $param['umask'];
  120.             }
  121.             if(isset($param['name'])) {
  122.                 $this->sname = $param['name'];
  123.             }
  124.             if(isset($param['detect_writeable'])) {
  125.                 $this->detect_writeable = $param['detect_writeable'];
  126.             }
  127.             if(!empty($param['writeable'])) {
  128.                 $this->writeable = $param['writeable'];
  129.             }
  130.             if(isset($param['listing'])) {
  131.                 $this->listing = $param['listing'];
  132.             }
  133.             if(isset($param['line_length']&& empty($param['line_length'])) {
  134.                 $this->line_length = (int) $param['line_length'];
  135.             }
  136.  
  137.             $this->open(true);
  138.         else {
  139.             $this->set_error('Invalid argument to constructor');
  140.         }
  141.     }
  142.  
  143.     /**
  144.      * Constructor (PHP4 style, kept for compatibility reasons)
  145.      * @param array $param backend options
  146.      * @return bool 
  147.      */
  148.     function abook_local_file($param{
  149.         return self::__construct($param);
  150.     }
  151.  
  152.     /**
  153.      * Open the addressbook file and store the file pointer.
  154.      * Use $file as the file to open, or the class' own
  155.      * filename property. If $param is empty and file is
  156.      * open, do nothing.
  157.      * @param bool $new is file already opened
  158.      * @return bool 
  159.      */
  160.     function open($new false{
  161.         $this->error = '';
  162.         $file   $this->filename;
  163.         $create $this->create;
  164.         $fopenmode (($this->writeable && is_writable($file)) 'a+' 'r');
  165.  
  166.         /* Return true is file is open and $new is unset */
  167.         if($this->filehandle && !$new{
  168.             return true;
  169.         }
  170.  
  171.         /* Check that new file exitsts */
  172.         if((!(file_exists($file&& is_readable($file))) && !$create{
  173.             return $this->set_error("$file_("No such file or directory"));
  174.         }
  175.  
  176.         /* Close old file, if any */
  177.         if($this->filehandle$this->close()}
  178.  
  179.         umask($this->umask);
  180.         if ($this->detect_writeable{
  181.             $fh @fopen($file,$fopenmode);
  182.             if ($fh{
  183.                 $this->filehandle = &$fh;
  184.                 $this->filename = $file;
  185.             else {
  186.                 return $this->set_error("$file_("Open failed"));
  187.             }
  188.         else {
  189.             /* Open file. First try to open for reading and writing,
  190.              * but fall back to read only. */
  191.             $fh @fopen($file'a+');
  192.             if($fh{
  193.                 $this->filehandle = &$fh;
  194.                 $this->filename   = $file;
  195.                 $this->writeable  = true;
  196.             else {
  197.                 $fh @fopen($file'r');
  198.                 if($fh{
  199.                     $this->filehandle = &$fh;
  200.                     $this->filename   = $file;
  201.                     $this->writeable  = false;
  202.                 else {
  203.                     return $this->set_error("$file_("Open failed"));
  204.                 }
  205.             }
  206.         }
  207.         return true;
  208.     }
  209.  
  210.     /** Close the file and forget the filehandle */
  211.     function close({
  212.         @fclose($this->filehandle);
  213.         $this->filehandle = 0;
  214.         $this->filename   = '';
  215.         $this->writable   false;
  216.     }
  217.  
  218.     /** Lock the datafile - try 20 times in 5 seconds */
  219.     function lock({
  220.         for($i $i 20 $i++{
  221.             if(flock($this->filehandle4))
  222.                 return true;
  223.             else
  224.                 usleep(250000);
  225.         }
  226.         return false;
  227.     }
  228.  
  229.     /** Unlock the datafile */
  230.     function unlock({
  231.         return flock($this->filehandle3);
  232.     }
  233.  
  234.     /**
  235.      * Overwrite the file with data from $rows
  236.      * NOTE! Previous locks are broken by this function
  237.      * @param array $rows new data
  238.      * @return bool 
  239.      */
  240.     function overwrite(&$rows{
  241.         $this->unlock();
  242.         $newfh @fopen($this->filename.'.tmp''w');
  243.  
  244.         if(!$newfh{
  245.             return $this->set_error($this->filename'.tmp:' _("Open failed"));
  246.         }
  247.  
  248.         for($i 0$cnt=sizeof($rows$i $cnt $i++{
  249.             if(is_array($rows[$i])) {
  250.                 for($j 0$cnt_part=count($rows[$i]$j $cnt_part $j++{
  251.                     $rows[$i][$j$this->quotevalue($rows[$i][$j]);
  252.                 }
  253.                 $tmpwrite sq_fwrite($newfhjoin('|'$rows[$i]"\n");
  254.                 if ($tmpwrite === FALSE{
  255.                     return $this->set_error($this->filename . '.tmp:' _("Write failed"));
  256.                 }
  257.             }
  258.         }
  259.  
  260.         fclose($newfh);
  261.         if (!@copy($this->filename . '.tmp' $this->filename)) {
  262.           return $this->set_error($this->filename . ':' _("Unable to update"));
  263.         }
  264.         @unlink($this->filename . '.tmp');
  265.         @chmod($this->filename0600);
  266.         $this->unlock();
  267.         $this->open(true);
  268.         return true;
  269.     }
  270.  
  271.     /* ========================== Public ======================== */
  272.  
  273.     /**
  274.      * Search the file
  275.      * @param string $expr search expression
  276.      * @return array search results
  277.      */
  278.     function search($expr{
  279.  
  280.         /* To be replaced by advanded search expression parsing */
  281.         if(is_array($expr)) return}
  282.  
  283.         // don't allow wide search when listing is disabled.
  284.         if ($expr=='*' && $this->listing)
  285.             return array();
  286.  
  287.         // Make regexp from glob'ed expression
  288.         $expr preg_quote($expr);
  289.         $expr str_replace(array('\\?''\\*')array('.''.*')$expr);
  290.  
  291.         $res array();
  292.         if(!$this->open()) {
  293.             return false;
  294.         }
  295.         @rewind($this->filehandle);
  296.  
  297.         while ($row @fgetcsv($this->filehandle$this->line_length'|')) {
  298.             if (count($row)<5{
  299.                 /** address book is corrupted. */
  300.                 global $color;
  301.                 error_box(_("Address book is corrupted. Required fields are missing."),$color);
  302.                 die('</body></html>');
  303.             else {
  304.                 // errors on preg_match call are suppressed in order to prevent display of regexp compilation errors
  305.                 if (@preg_match('/' $expr '/i'$row[0])    // nickname
  306.                  || @preg_match('/' $expr '/i'$row[1])    // firstname
  307.                  || @preg_match('/' $expr '/i'$row[2])    // lastname
  308.                  || @preg_match('/' $expr '/i'$row[3])) // email
  309.                     array_push($resarray('nickname'  => $row[0],
  310.                                            'name'      => $row[1' ' $row[2],
  311.                                            'firstname' => $row[1],
  312.                                            'lastname'  => $row[2],
  313.                                            'email'     => $row[3],
  314.                                            'label'     => $row[4],
  315.                                            'backend'   => $this->bnum,
  316.                                            'source'    => &$this->sname));
  317.                 }
  318.             }
  319.         }
  320.         return $res;
  321.     }
  322.  
  323.     /**
  324.      * Lookup by the indicated field
  325.      *
  326.      * @param string  $value Value to look up
  327.      * @param integer $field The field to look in, should be one
  328.      *                        of the SM_ABOOK_FIELD_* constants
  329.      *                        defined in functions/constants.php
  330.      *                        (OPTIONAL; defaults to nickname field)
  331.      *                        NOTE: uniqueness is only guaranteed
  332.      *                        when the nickname field is used here;
  333.      *                        otherwise, the first matching address
  334.      *                        is returned.
  335.      *
  336.      * @return array search results
  337.      *
  338.      */
  339.     function lookup($value$field=SM_ABOOK_FIELD_NICKNAME{
  340.         if(empty($value)) {
  341.             return array();
  342.         }
  343.  
  344.         $value strtolower($value);
  345.  
  346.         $this->open();
  347.         @rewind($this->filehandle);
  348.  
  349.         while ($row @fgetcsv($this->filehandle$this->line_length'|')) {
  350.             if (count($row)<5{
  351.                 /** address book is corrupted. */
  352.                 global $color;
  353.                 error_box(_("Address book is corrupted. Required fields are missing."),$color);
  354.                 die('</body></html>');
  355.             else {
  356.                 if(strtolower($row[$field]== $value{
  357.                     return array('nickname'  => $row[0],
  358.                                  'name'      => $row[1' ' $row[2],
  359.                                  'firstname' => $row[1],
  360.                                  'lastname'  => $row[2],
  361.                                  'email'     => $row[3],
  362.                                  'label'     => $row[4],
  363.                                  'backend'   => $this->bnum,
  364.                                  'source'    => &$this->sname);
  365.                 }
  366.             }
  367.         }
  368.  
  369.         return array();
  370.     }
  371.  
  372.     /**
  373.      * List all addresses
  374.      * @return array list of all addresses
  375.      */
  376.     function list_addr({
  377.         // check if listing is not disabled
  378.         if(isset($this->listing&& !$this->listing{
  379.             return array();
  380.         }
  381.  
  382.         $res array();
  383.         $this->open();
  384.         @rewind($this->filehandle);
  385.  
  386.         while ($row @fgetcsv($this->filehandle$this->line_length'|')) {
  387.             if (count($row)<5{
  388.                 /** address book is corrupted. */
  389.                 global $color;
  390.                 error_box(_("Address book is corrupted. Required fields are missing."),$color);
  391.                 die('</body></html>');
  392.             else {
  393.                 array_push($resarray('nickname'  => $row[0],
  394.                                        'name'      => $row[1' ' $row[2],
  395.                                        'firstname' => $row[1],
  396.                                        'lastname'  => $row[2],
  397.                                        'email'     => $row[3],
  398.                                        'label'     => $row[4],
  399.                                        'backend'   => $this->bnum,
  400.                                        'source'    => &$this->sname));
  401.             }
  402.         }
  403.         return $res;
  404.     }
  405.  
  406.     /**
  407.      * Add address
  408.      * @param array $userdata new data
  409.      * @return bool 
  410.      */
  411.     function add($userdata{
  412.         if(!$this->writeable{
  413.             return $this->set_error(_("Address book is read-only"));
  414.         }
  415.  
  416.         // NB: if you want to check for some unwanted characters
  417.         //     or other problems, do so here like this:
  418.         // TODO: Should pull all validation code out into a separate function
  419.         //if (strpos($userdata['nickname'], ' ')) {
  420.         //    return $this->set_error(_("Nickname contains illegal characters"));
  421.         //}
  422.  
  423.         /* See if user exists already */
  424.         $ret $this->lookup($userdata['nickname']);
  425.         if(!empty($ret)) {
  426.             // i18n: don't use html formating in translation
  427.             return $this->set_error(sprintf(_("User \"%s\" already exists")$ret['nickname']));
  428.         }
  429.  
  430.         /* Here is the data to write */
  431.         $data $this->quotevalue($userdata['nickname']'|' .
  432.                 $this->quotevalue($userdata['firstname']'|' .
  433.                 $this->quotevalue((!empty($userdata['lastname'])?$userdata['lastname']:'')) '|' .
  434.                 $this->quotevalue($userdata['email']'|' .
  435.                 $this->quotevalue((!empty($userdata['label'])?$userdata['label']:''));
  436.  
  437.         /* Strip linefeeds */
  438.         $nl_str array("\r","\n");
  439.         $data str_replace($nl_str' '$data);
  440.  
  441.         /**
  442.          * Make sure that entry fits into allocated record space.
  443.          * One byte is reserved for linefeed
  444.          */
  445.         if (strlen($data>= $this->line_length{
  446.             return $this->set_error(_("Address book entry is too big"));
  447.         }
  448.  
  449.         /* Add linefeed at end */
  450.         $data $data "\n";
  451.  
  452.         /* Reopen file, just to be sure */
  453.         $this->open(true);
  454.         if(!$this->writeable{
  455.             return $this->set_error(_("Address book is read-only"));
  456.         }
  457.  
  458.         /* Lock the file */
  459.         if(!$this->lock()) {
  460.             return $this->set_error(_("Could not lock datafile"));
  461.         }
  462.  
  463.         /* Write */
  464.         $r sq_fwrite($this->filehandle$data);
  465.  
  466.         /* Unlock file */
  467.         $this->unlock();
  468.  
  469.         /* Test write result */
  470.         if($r === FALSE{
  471.             /* Fail */
  472.             $this->set_error(_("Write to address book failed"));
  473.             return FALSE;
  474.         }
  475.  
  476.         return TRUE;
  477.     }
  478.  
  479.     /**
  480.      * Delete address
  481.      * @param string $alias alias that has to be deleted
  482.      * @return bool 
  483.      */
  484.     function remove($alias{
  485.         if(!$this->writeable{
  486.             return $this->set_error(_("Address book is read-only"));
  487.         }
  488.  
  489.         /* Lock the file to make sure we're the only process working
  490.          * on it. */
  491.         if(!$this->lock()) {
  492.             return $this->set_error(_("Could not lock datafile"));
  493.         }
  494.  
  495.         /* Read file into memory, ignoring nicknames to delete */
  496.         @rewind($this->filehandle);
  497.         $i 0;
  498.         $rows array();
  499.         while($row @fgetcsv($this->filehandle$this->line_length'|')) {
  500.             if(!in_array($row[0]$alias)) {
  501.                 $rows[$i++$row;
  502.             }
  503.         }
  504.  
  505.         /* Write data back */
  506.         if(!$this->overwrite($rows)) {
  507.             $this->unlock();
  508.             return false;
  509.         }
  510.  
  511.         $this->unlock();
  512.         return true;
  513.     }
  514.  
  515.     /**
  516.      * Modify address
  517.      * @param string $alias modified alias
  518.      * @param array $userdata new data
  519.      * @return bool true, if operation successful
  520.      */
  521.     function modify($alias$userdata{
  522.         if(!$this->writeable{
  523.             return $this->set_error(_("Address book is read-only"));
  524.         }
  525.  
  526.         // NB: if you want to check for some unwanted characters
  527.         //     or other problems, do so here like this:
  528.         // TODO: Should pull all validation code out into a separate function
  529.         //if (strpos($userdata['nickname'], ' ')) {
  530.         //    return $this->set_error(_("Nickname contains illegal characters"));
  531.         //}
  532.  
  533.         /* See if user exists */
  534.         $ret $this->lookup($alias);
  535.         if(empty($ret)) {
  536.             // i18n: don't use html formating in translation
  537.             return $this->set_error(sprintf(_("User \"%s\" does not exist")$alias));
  538.         }
  539.  
  540.         /* If the alias changed, see if the new alias exists */
  541.         if (strtolower($alias!= strtolower($userdata['nickname'])) {
  542.             $ret $this->lookup($userdata['nickname']);
  543.             if (!empty($ret)) {
  544.                 return $this->set_error(sprintf(_("User \"%s\" already exists")$userdata['nickname']));
  545.             }
  546.         }
  547.  
  548.         /* calculate userdata size */
  549.         $data $this->quotevalue($userdata['nickname']'|'
  550.             . $this->quotevalue($userdata['firstname']'|'
  551.             . $this->quotevalue((!empty($userdata['lastname'])?$userdata['lastname']:'')) '|'
  552.             . $this->quotevalue($userdata['email']'|'
  553.             . $this->quotevalue((!empty($userdata['label'])?$userdata['label']:''));
  554.         /* make sure that it fits into allocated space */
  555.         if (strlen($data>= $this->line_length{
  556.             return $this->set_error(_("Address book entry is too big"));
  557.         }
  558.  
  559.         /* Lock the file to make sure we're the only process working
  560.          * on it. */
  561.         if(!$this->lock()) {
  562.             return $this->set_error(_("Could not lock datafile"));
  563.         }
  564.  
  565.         /* Read file into memory, modifying the data for the
  566.          * user identified by $alias */
  567.         $this->open(true);
  568.         @rewind($this->filehandle);
  569.         $i 0;
  570.         $rows array();
  571.         while($row @fgetcsv($this->filehandle$this->line_length'|')) {
  572.             if(strtolower($row[0]!= strtolower($alias)) {
  573.                 $rows[$i++$row;
  574.             else {
  575.                 $rows[$i++array(=> $userdata['nickname'],
  576.                                     => $userdata['firstname'],
  577.                                     => (!empty($userdata['lastname'])?$userdata['lastname']:''),
  578.                                     => $userdata['email'],
  579.                                     => (!empty($userdata['label'])?$userdata['label']:''));
  580.             }
  581.         }
  582.  
  583.         /* Write data back */
  584.         if(!$this->overwrite($rows)) {
  585.             $this->unlock();
  586.             return false;
  587.         }
  588.  
  589.         $this->unlock();
  590.         return true;
  591.     }
  592.  
  593.     /**
  594.      * Function for quoting values before saving
  595.      * @param string $value string that has to be quoted
  596.      * @param string quoted string
  597.      */
  598.     function quotevalue($value{
  599.         /* Quote the field if it contains | or ". Double quotes need to
  600.          * be replaced with "" */
  601.         if(stristr($value'"'|| stristr($value'|')) {
  602.             $value '"' str_replace('"''""'$value'"';
  603.         }
  604.         return $value;
  605.     }
  606.  
  607. /* End of class abook_local_file */

Documentation generated on Mon, 13 Jan 2020 04:24:11 +0100 by phpDocumentor 1.4.3