abook_local_file.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. <?php
  2. /**
  3. * abook_local_file.php
  4. *
  5. * Copyright (c) 1999-2004 The SquirrelMail Project Team
  6. * Licensed under the GNU GPL. For full terms see the file COPYING.
  7. *
  8. * Backend for addressbook as a pipe separated file
  9. *
  10. * An array with the following elements must be passed to
  11. * the class constructor (elements marked ? are optional):
  12. *
  13. * filename => path to addressbook file
  14. * ? create => if true: file is created if it does not exist.
  15. * ? umask => umask set before opening file.
  16. *
  17. * NOTE. This class should not be used directly. Use the
  18. * "AddressBook" class instead.
  19. *
  20. * @version $Id$
  21. * @package squirrelmail
  22. * @subpackage addressbook
  23. */
  24. /**
  25. * Store the addressbook in a local file
  26. * @package squirrelmail
  27. */
  28. class abook_local_file extends addressbook_backend {
  29. var $btype = 'local';
  30. var $bname = 'local_file';
  31. var $filename = '';
  32. var $filehandle = 0;
  33. var $create = false;
  34. var $umask;
  35. /* ========================== Private ======================= */
  36. /* Constructor */
  37. function abook_local_file($param) {
  38. $this->sname = _("Personal address book");
  39. $this->umask = Umask();
  40. if(is_array($param)) {
  41. if(empty($param['filename'])) {
  42. return $this->set_error('Invalid parameters');
  43. }
  44. if(!is_string($param['filename'])) {
  45. return $this->set_error($param['filename'] . ': '.
  46. _("Not a file name"));
  47. }
  48. $this->filename = $param['filename'];
  49. if($param['create']) {
  50. $this->create = true;
  51. }
  52. if(isset($param['umask'])) {
  53. $this->umask = $param['umask'];
  54. }
  55. if(!empty($param['name'])) {
  56. $this->sname = $param['name'];
  57. }
  58. $this->open(true);
  59. } else {
  60. $this->set_error('Invalid argument to constructor');
  61. }
  62. }
  63. /* Open the addressbook file and store the file pointer.
  64. * Use $file as the file to open, or the class' own
  65. * filename property. If $param is empty and file is
  66. * open, do nothing. */
  67. function open($new = false) {
  68. $this->error = '';
  69. $file = $this->filename;
  70. $create = $this->create;
  71. /* Return true is file is open and $new is unset */
  72. if($this->filehandle && !$new) {
  73. return true;
  74. }
  75. /* Check that new file exitsts */
  76. if((!(file_exists($file) && is_readable($file))) && !$create) {
  77. return $this->set_error("$file: " . _("No such file or directory"));
  78. }
  79. /* Close old file, if any */
  80. if($this->filehandle) { $this->close(); }
  81. /* Open file. First try to open for reading and writing,
  82. * but fall back to read only. */
  83. umask($this->umask);
  84. $fh = @fopen($file, 'a+');
  85. if($fh) {
  86. $this->filehandle = &$fh;
  87. $this->filename = $file;
  88. $this->writeable = true;
  89. } else {
  90. $fh = @fopen($file, 'r');
  91. if($fh) {
  92. $this->filehandle = &$fh;
  93. $this->filename = $file;
  94. $this->writeable = false;
  95. } else {
  96. return $this->set_error("$file: " . _("Open failed"));
  97. }
  98. }
  99. return true;
  100. }
  101. /* Close the file and forget the filehandle */
  102. function close() {
  103. @fclose($this->filehandle);
  104. $this->filehandle = 0;
  105. $this->filename = '';
  106. $this->writable = false;
  107. }
  108. /* Lock the datafile - try 20 times in 5 seconds */
  109. function lock() {
  110. for($i = 0 ; $i < 20 ; $i++) {
  111. if(flock($this->filehandle, 2 + 4))
  112. return true;
  113. else
  114. usleep(250000);
  115. }
  116. return false;
  117. }
  118. /* Lock the datafile */
  119. function unlock() {
  120. return flock($this->filehandle, 3);
  121. }
  122. /* Overwrite the file with data from $rows
  123. * NOTE! Previous locks are broken by this function */
  124. function overwrite(&$rows) {
  125. $this->unlock();
  126. $newfh = @fopen($this->filename.'.tmp', 'w');
  127. if(!$newfh) {
  128. return $this->set_error($this->filename. '.tmp:' . _("Open failed"));
  129. }
  130. for($i = 0, $cnt=sizeof($rows) ; $i < $cnt ; $i++) {
  131. if(is_array($rows[$i])) {
  132. for($j = 0, $cnt_part=count($rows[$i]) ; $j < $cnt_part ; $j++) {
  133. $rows[$i][$j] = $this->quotevalue($rows[$i][$j]);
  134. }
  135. $tmpwrite = sq_fwrite($newfh, join('|', $rows[$i]) . "\n");
  136. if ($tmpwrite === FALSE) {
  137. return $this->set_error($this->filename . '.tmp:' . _("Write failed"));
  138. }
  139. }
  140. }
  141. fclose($newfh);
  142. if (!@copy($this->filename . '.tmp' , $this->filename)) {
  143. return $this->set_error($this->filename . ':' . _("Unable to update"));
  144. }
  145. @unlink($this->filename . '.tmp');
  146. $this->unlock();
  147. $this->open(true);
  148. return true;
  149. }
  150. /* ========================== Public ======================== */
  151. /* Search the file */
  152. function search($expr) {
  153. /* To be replaced by advanded search expression parsing */
  154. if(is_array($expr)) { return; }
  155. /* Make regexp from glob'ed expression
  156. * May want to quote other special characters like (, ), -, [, ], etc. */
  157. $expr = str_replace('?', '.', $expr);
  158. $expr = str_replace('*', '.*', $expr);
  159. $res = array();
  160. if(!$this->open()) {
  161. return false;
  162. }
  163. @rewind($this->filehandle);
  164. while ($row = @fgetcsv($this->filehandle, 2048, '|')) {
  165. $line = join(' ', $row);
  166. if(eregi($expr, $line)) {
  167. array_push($res, array('nickname' => $row[0],
  168. 'name' => $row[1] . ' ' . $row[2],
  169. 'firstname' => $row[1],
  170. 'lastname' => $row[2],
  171. 'email' => $row[3],
  172. 'label' => $row[4],
  173. 'backend' => $this->bnum,
  174. 'source' => &$this->sname));
  175. }
  176. }
  177. return $res;
  178. }
  179. /* Lookup alias */
  180. function lookup($alias) {
  181. if(empty($alias)) {
  182. return array();
  183. }
  184. $alias = strtolower($alias);
  185. $this->open();
  186. @rewind($this->filehandle);
  187. while ($row = @fgetcsv($this->filehandle, 2048, '|')) {
  188. if(strtolower($row[0]) == $alias) {
  189. return array('nickname' => $row[0],
  190. 'name' => $row[1] . ' ' . $row[2],
  191. 'firstname' => $row[1],
  192. 'lastname' => $row[2],
  193. 'email' => $row[3],
  194. 'label' => $row[4],
  195. 'backend' => $this->bnum,
  196. 'source' => &$this->sname);
  197. }
  198. }
  199. return array();
  200. }
  201. /* List all addresses */
  202. function list_addr() {
  203. $res = array();
  204. $this->open();
  205. @rewind($this->filehandle);
  206. while ($row = @fgetcsv($this->filehandle, 2048, '|')) {
  207. array_push($res, array('nickname' => $row[0],
  208. 'name' => $row[1] . ' ' . $row[2],
  209. 'firstname' => $row[1],
  210. 'lastname' => $row[2],
  211. 'email' => $row[3],
  212. 'label' => $row[4],
  213. 'backend' => $this->bnum,
  214. 'source' => &$this->sname));
  215. }
  216. return $res;
  217. }
  218. /* Add address */
  219. function add($userdata) {
  220. if(!$this->writeable) {
  221. return $this->set_error(_("Addressbook is read-only"));
  222. }
  223. /* See if user exists already */
  224. $ret = $this->lookup($userdata['nickname']);
  225. if(!empty($ret)) {
  226. return $this->set_error(sprintf(_("User '%s' already exist"),
  227. $ret['nickname']));
  228. }
  229. /* Here is the data to write */
  230. $data = $this->quotevalue($userdata['nickname']) . '|' .
  231. $this->quotevalue($userdata['firstname']) . '|' .
  232. $this->quotevalue($userdata['lastname']) . '|' .
  233. $this->quotevalue($userdata['email']) . '|' .
  234. $this->quotevalue($userdata['label']);
  235. /* Strip linefeeds */
  236. $data = ereg_replace("[\r\n]", ' ', $data);
  237. /* Add linefeed at end */
  238. $data = $data . "\n";
  239. /* Reopen file, just to be sure */
  240. $this->open(true);
  241. if(!$this->writeable) {
  242. return $this->set_error(_("Addressbook is read-only"));
  243. }
  244. /* Lock the file */
  245. if(!$this->lock()) {
  246. return $this->set_error(_("Could not lock datafile"));
  247. }
  248. /* Write */
  249. $r = sq_fwrite($this->filehandle, $data);
  250. /* Unlock file */
  251. $this->unlock();
  252. /* Test write result */
  253. if($r === FALSE) {
  254. /* Fail */
  255. $this->set_error(_("Write to addressbook failed"));
  256. return FALSE;
  257. }
  258. return TRUE;
  259. }
  260. /* Delete address */
  261. function remove($alias) {
  262. if(!$this->writeable) {
  263. return $this->set_error(_("Addressbook is read-only"));
  264. }
  265. /* Lock the file to make sure we're the only process working
  266. * on it. */
  267. if(!$this->lock()) {
  268. return $this->set_error(_("Could not lock datafile"));
  269. }
  270. /* Read file into memory, ignoring nicknames to delete */
  271. @rewind($this->filehandle);
  272. $i = 0;
  273. $rows = array();
  274. while($row = @fgetcsv($this->filehandle, 2048, '|')) {
  275. if(!in_array($row[0], $alias)) {
  276. $rows[$i++] = $row;
  277. }
  278. }
  279. /* Write data back */
  280. if(!$this->overwrite($rows)) {
  281. $this->unlock();
  282. return false;
  283. }
  284. $this->unlock();
  285. return true;
  286. }
  287. /* Modify address */
  288. function modify($alias, $userdata) {
  289. if(!$this->writeable) {
  290. return $this->set_error(_("Addressbook is read-only"));
  291. }
  292. /* See if user exists */
  293. $ret = $this->lookup($alias);
  294. if(empty($ret)) {
  295. return $this->set_error(sprintf(_("User '%s' does not exist"),
  296. $alias));
  297. }
  298. /* Lock the file to make sure we're the only process working
  299. * on it. */
  300. if(!$this->lock()) {
  301. return $this->set_error(_("Could not lock datafile"));
  302. }
  303. /* Read file into memory, modifying the data for the
  304. * user identified by $alias */
  305. $this->open(true);
  306. @rewind($this->filehandle);
  307. $i = 0;
  308. $rows = array();
  309. while($row = @fgetcsv($this->filehandle, 2048, '|')) {
  310. if(strtolower($row[0]) != strtolower($alias)) {
  311. $rows[$i++] = $row;
  312. } else {
  313. $rows[$i++] = array(0 => $userdata['nickname'],
  314. 1 => $userdata['firstname'],
  315. 2 => $userdata['lastname'],
  316. 3 => $userdata['email'],
  317. 4 => $userdata['label']);
  318. }
  319. }
  320. /* Write data back */
  321. if(!$this->overwrite($rows)) {
  322. $this->unlock();
  323. return false;
  324. }
  325. $this->unlock();
  326. return true;
  327. }
  328. /* Function for quoting values before saving */
  329. function quotevalue($value) {
  330. /* Quote the field if it contains | or ". Double quotes need to
  331. * be replaced with "" */
  332. if(ereg("[|\"]", $value)) {
  333. $value = '"' . str_replace('"', '""', $value) . '"';
  334. }
  335. return $value;
  336. }
  337. } /* End of class abook_local_file */
  338. ?>