abook_local_file.php 12 KB

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