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

Documentation generated on Mon, 13 Jan 2020 04:21:56 +0100 by phpDocumentor 1.4.3