[0004212] Work Order Entry: fixed error when voided WO refence is reused.
[fa-stable.git] / includes / archive.inc
1 <?php
2 /*--------------------------------------------------
3  | TAR/GZIP/BZIP2/ZIP ARCHIVE CLASSES 2.1
4  | By Devin Doucette
5  | Copyright (c) 2005 Devin Doucette
6  | Email: darksnoopy@shaw.ca
7  +--------------------------------------------------
8  | Email bugs/suggestions to darksnoopy@shaw.ca
9  +--------------------------------------------------
10  | This script has been created and released under
11  | the GNU GPL and is free to use and redistribute
12  | only if this copyright statement is not removed
13  +--------------------------------------------------
14  | FrontAccounting bugfixes/changes added.
15  +--------------------------------------------------*/
16
17 class archive
18 {
19         function __construct($name)
20         {
21                 $this->options = array (
22                         'basedir' => ".",
23                         'name' => $name,
24                         'prepend' => "",
25                         'inmemory' => 0,
26                         'overwrite' => 0,
27                         'recurse' => 1,
28                         'storepaths' => 1,
29                         'followlinks' => 0,
30                         'level' => 3,
31                         'method' => 1,
32                         'sfx' => "",
33                         'type' => "",
34                         'comment' => ""
35                 );
36                 $this->files = array ();
37                 $this->exclude = array ();
38                 $this->storeonly = array ();
39                 $this->error = array ();
40         }
41
42         function set_options($options)
43         {
44                 foreach ($options as $key => $value)
45                         $this->options[$key] = $value;
46                 if (!empty ($this->options['basedir']))
47                 {
48                         $this->options['basedir'] = str_replace("\\", "/", $this->options['basedir']);
49                         $this->options['basedir'] = preg_replace("/\/+/", "/", $this->options['basedir']);
50                         $this->options['basedir'] = preg_replace("/\/$/", "", $this->options['basedir']);
51                 }
52                 if (!empty ($this->options['name']))
53                 {
54                         $this->options['name'] = str_replace("\\", "/", $this->options['name']);
55 //                      $this->options['name'] = preg_replace("/\/+/", "/", $this->options['name']);
56                 }
57                 if (!empty ($this->options['prepend']))
58                 {
59                         $this->options['prepend'] = str_replace("\\", "/", $this->options['prepend']);
60                         $this->options['prepend'] = preg_replace("/^(\.*\/+)+/", "", $this->options['prepend']);
61                         $this->options['prepend'] = preg_replace("/\/+/", "/", $this->options['prepend']);
62                         $this->options['prepend'] = preg_replace("/\/$/", "", $this->options['prepend']) . "/";
63                 }
64         }
65
66         function create_archive()
67         {
68                 $this->make_list();
69
70                 if ($this->options['inmemory'] == 0)
71                 {
72                         if ($this->options['overwrite'] == 0 && 
73                                 file_exists($this->options['name'] 
74                                 . ($this->options['type'] == "pkg" || $this->options['type'] == "gzip" ||$this->options['type'] == "bzip" ? ".tmp" : "")))
75                         {
76                                 $this->error[] = "File {$this->options['name']} already exists.";
77                                 return 0;
78                         }
79                         else if (!($this->archive = @fopen($this->options['name'].".tmp", "wb+")))
80                         {
81                                 $this->error[] = "Could not open {$this->options['name']} for writing.".':'.getcwd();
82                                 return 0;
83                         }
84                 }
85                 else
86                         $this->archive = "";
87
88                 if (!$this->create_tar())
89                 {
90                         $this->error[] = "Could not create package file.";
91                         return 0;
92                 }
93                 if (!$this->create_pkg())
94                 {
95                         $this->error[] = "Could not compress package file.";
96                         return 0;
97                 }
98
99                 if ($this->options['inmemory'] == 0)
100                 {
101                         fclose($this->archive);
102                         unlink($this->options['name'] . ".tmp");
103                 }
104         }
105
106         function add_data($data)
107         {
108                 if ($this->options['inmemory'] == 0)
109                         fwrite($this->archive, $data);
110                 else
111                         $this->archive .= $data;
112         }
113
114         function make_list()
115         {
116                 if (!empty ($this->exclude)) {
117                         foreach ($this->files as $key => $value)
118                                 foreach ($this->exclude as $current)
119                                         if ($value['name'] == $current['name']) 
120                                                 unset ($this->files[$key]);
121                 }
122                 if (!empty ($this->storeonly))
123                         foreach ($this->files as $key => $value)
124                                 foreach ($this->storeonly as $current)
125                                         if ($value['name'] == $current['name'])
126                                                 $this->files[$key]['method'] = 0;
127                 unset ($this->exclude, $this->storeonly);
128         }
129
130         function add_files($list)
131         {
132                 $temp = $this->list_files($list);
133                 foreach ($temp as $current)
134                         $this->files[] = $current;
135         }
136
137         function exclude_files($list)
138         {
139                 $temp = $this->list_files($list);
140                 foreach ($temp as $current)
141                         $this->exclude[] = $current;
142         }
143
144         function store_files($list)
145         {
146                 $temp = $this->list_files($list);
147                 foreach ($temp as $current)
148                         $this->storeonly[] = $current;
149         }
150
151         function list_files($list)
152         {
153                 if (!is_array ($list))
154                 {
155                         $temp = $list;
156                         $list = array ($temp);
157                         unset ($temp);
158                 }
159
160                 $files = array ();
161
162                 $pwd = getcwd();
163                 chdir($this->options['basedir']);
164
165                 foreach ($list as $current)
166                 {
167                         $current = str_replace("\\", "/", $current);
168                         $current = preg_replace("/\/+/", "/", $current);
169                         $current = preg_replace("/\/$/", "", $current);
170                         if (strstr($current, "*"))
171                         {
172                                 $regex = preg_replace("/([\\\^\$\.\[\]\|\(\)\?\+\{\}\/])/", "\\\\\\1", $current);
173                                 $regex = str_replace("*", ".*", $regex);
174                                 $dir = strstr($current, "/") ? substr($current, 0, strrpos($current, "/")) : ".";
175                                 $temp = $this->parse_dir($dir);
176                                 foreach ($temp as $current2)
177                                         if (preg_match("/^{$regex}$/i", $current2['name']))
178                                                 $files[] = $current2;
179                                 unset ($regex, $dir, $temp, $current);
180                         }
181                         else if (@is_dir($current))
182                         {
183                                 $temp = $this->parse_dir($current);
184                                 foreach ($temp as $file)
185                                         $files[] = $file;
186                                 unset ($temp, $file);
187                         }
188                         else if (@file_exists($current))
189                                 $files[] = array ('name' => $current, 'name2' => $this->options['prepend'] .
190                                         preg_replace("/(\.+\/+)+/", "", ($this->options['storepaths'] == 0 && strstr($current, "/")) ?
191                                         substr($current, strrpos($current, "/") + 1) : $current),
192                                         'type' => @is_link($current) && $this->options['followlinks'] == 0 ? 2 : 0,
193                                         'ext' => substr($current, strrpos($current, ".")), 'stat' => stat($current));
194                 }
195
196                 chdir($pwd);
197
198                 unset ($current, $pwd);
199                 return $files;
200         }
201
202         function parse_dir($dirname)
203         {
204                 if ($this->options['storepaths'] == 1 && !preg_match("/^(\.+\/*)+$/", $dirname))
205                         $files = array (array ('name' => $dirname, 'name2' => $this->options['prepend'] .
206                                 preg_replace("/(\.+\/+)+/", "", ($this->options['storepaths'] == 0 && strstr($dirname, "/")) ?
207                                 substr($dirname, strrpos($dirname, "/") + 1) : $dirname), 'type' => 5, 'stat' => stat($dirname)));
208                 else
209                         $files = array ();
210                 $dir = @opendir($dirname);
211
212                 while ($file = @readdir($dir))
213                 {
214                         $fullname = $dirname . "/" . $file;
215                         if ($file == "." || $file == "..")
216                                 continue;
217                         else if (@is_dir($fullname))
218                         {
219                                 if (empty ($this->options['recurse']))
220                                         continue;
221                                 $temp = $this->parse_dir($fullname);
222                                 foreach ($temp as $file2)
223                                         $files[] = $file2;
224                         }
225                         else if (@file_exists($fullname))
226                                 $files[] = array ('name' => $fullname, 'name2' => $this->options['prepend'] .
227                                         preg_replace("/(\.+\/+)+/", "", ($this->options['storepaths'] == 0 && strstr($fullname, "/")) ?
228                                         substr($fullname, strrpos($fullname, "/") + 1) : $fullname),
229                                         'type' => @is_link($fullname) && $this->options['followlinks'] == 0 ? 2 : 0,
230                                         'ext' => substr($file, strrpos($file, ".")), 'stat' => stat($fullname));
231                 }
232
233                 @closedir($dir);
234
235                 return $files;
236         }
237 }
238
239 class tar_file extends archive
240 {
241         function __construct($name)
242         {
243                 parent::__construct($name);
244                 $this->options['type'] = "tar";
245         }
246
247         function create_tar()
248         {
249                 $pwd = getcwd();
250                 chdir($this->options['basedir']);
251                 $files = $this->files;
252                 foreach ($files as $current)
253                 {
254                         if ($current['name'] == $this->options['name'])
255                                 continue;
256                         if (strlen($current['name2']) > 99)
257                         {
258                                 $path = substr($current['name2'], 0, strpos($current['name2'], "/", strlen($current['name2']) - 100) + 1);
259                                 $current['name2'] = substr($current['name2'], strlen($path));
260                                 if (strlen($path) > 154 || strlen($current['name2']) > 99)
261                                 {
262                                         $this->error[] = "Could not add {$path}{$current['name2']} to archive because the filename is too long.";
263                                         continue;
264                                 }
265                         }
266                         $block = pack("a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12", $current['name2'], sprintf("%07o", 
267                                 $current['stat'][2]), sprintf("%07o", $current['stat'][4]), sprintf("%07o", $current['stat'][5]), 
268                                 sprintf("%011o", $current['type'] == 2 ? 0 : $current['stat'][7]), sprintf("%011o", $current['stat'][9]), 
269                                 "        ", $current['type'], $current['type'] == 2 ? @readlink($current['name']) : "", "ustar ", " ", 
270                                 "Unknown", "Unknown", "", "", !empty ($path) ? $path : "", "");
271
272                         $checksum = 0;
273                         for ($i = 0; $i < 512; $i++)
274                                 $checksum += ord(substr($block, $i, 1));
275                         $checksum = pack("a8", sprintf("%07o", $checksum));
276                         $block = substr_replace($block, $checksum, 148, 8);
277
278                         if ($current['type'] == 2 || $current['stat'][7] == 0)
279                                 { $this->add_data($block); 
280                                 }
281                         else if ($fp = @fopen($current['name'], "rb"))
282                         {
283                                 $this->add_data($block);
284
285                                 while ($temp = fread($fp, 1048576)) {
286                                         $this->add_data($temp);
287                                 }
288                                 if ($current['stat'][7] % 512 > 0)
289                                 {
290                                         $temp = "";
291                                         for ($i = 0; $i < 512 - $current['stat'][7] % 512; $i++)
292                                                 $temp .= "\0";
293                                         $this->add_data($temp);
294                                 }
295                                 fclose($fp);
296                         }
297                         else
298                                 $this->error[] = "Could not open file {$current['name']} for reading. It was not added.";
299                 }
300
301                 $this->add_data(pack("a1024", ""));
302
303                 chdir($pwd);
304
305                 return 1;
306         }
307         /*
308                 Extract files to base directory.
309                 When $dry_run!=0 no file is written,
310                 but list of existing files to be written is returned.
311         */
312         function extract_files($dry_run = false)
313         {
314         $flist = array();
315                 if ($fp = $this->open_archive())
316                 {
317                         $pwd = getcwd();
318                         // display_error($pwd.':'.$this->options['basedir']);
319                         chdir($this->options['basedir']);
320                         if ($this->options['inmemory'] == 1)
321                                 $this->files = array();
322
323                         while ($block = fread($fp, 512))
324                         {
325                                 $temp = unpack("a100name/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1type/a100symlink/a6magic/a2temp/a32temp/a32temp/a8temp/a8temp/a155prefix/a12temp", $block);
326                                 $file = array (
327                                         'name' => rtrim($temp['prefix']) . rtrim($temp['name']),
328                                         'stat' => array (
329                                                 2 => octdec($temp['mode']),
330                                                 4 => octdec($temp['uid']),
331                                                 5 => octdec($temp['gid']),
332                                                 7 => octdec($temp['size']),
333                                                 9 => octdec($temp['mtime']),
334                                         ),
335                                         'checksum' => octdec($temp['checksum']),
336                                         'type' => $temp['type'],
337                                         'magic' => $temp['magic'],
338                                 );
339                                 if ($file['checksum'] == 0x00000000)
340                                         break;
341                                 else if (substr($file['magic'], 0, 5) != "ustar")
342                                 {
343                                         $this->error[] = "This script does not support extracting this type of tar file.";
344                                         break;
345                                 }
346                                 $block = substr_replace($block, "        ", 148, 8);
347                                 $checksum = 0;
348                                 for ($i = 0; $i < 512; $i++)
349                                         $checksum += ord(substr($block, $i, 1));
350                                 if ($file['checksum'] != $checksum) {
351                                         $this->error[] = "Could not extract from {$this->options['name']}, it is corrupt.";
352                                         break;
353                                 }
354                                 if ($dry_run || $this->options['inmemory'] == 1)
355                                 {
356                                         if ($file['type'] == 0) {
357                                                 if($dry_run) {
358                                                         $tst = check_write($file['name']);
359                                                         if (!$tst) {
360                                                                 $this->error[] = "Could not open {$this->options['basedir']}/{$file['name']} for writing.";
361                                                                 break;
362                                                         } else {
363                                                                 $flist[] = $file['name'];
364                                                                 if ( $tst < 0 && $this->options['overwrite'] == 0)
365                                                                         $this->error[] = "{$file['name']} already exists.";
366                                                         }
367                                                         fseek($fp, ($file['stat'][7] + 511) & ~511, SEEK_CUR); // skip data
368                                                 } else {
369                                                         $dat = fread($fp, $file['stat'][7]);
370                                                         $tail = $file['stat'][7] % 512;
371                                                         if ($tail)
372                                                                 fread($fp, 512 - $tail);
373                                                         $file['data'] = $dat;
374                                                 }
375                                         }
376                                         unset ($file['checksum'], $file['magic']);
377                                         $this->files[] = $file; 
378                                 }
379                                 else if ($file['type'] == 5)
380                                 {
381                                         if (!is_dir($file['name'])) {
382                                                 mkdir($file['name'], $file['stat'][2]);
383                                         }
384                                 }
385                                 else if ($this->options['overwrite'] == 0 && file_exists($file['name']))
386                                 {
387                                         $this->error[] = "{$file['name']} already exists.";
388                                         continue;
389                                 }
390                                 else if ($file['type'] == 2)
391                                 {
392                                         symlink($temp['symlink'], $file['name']);
393                                         @chmod($file['name'], $file['stat'][2]);
394                                 }
395                                 else if ($new = @fopen($file['name'], "wb"))
396                                 {
397                                         fwrite($new, fread($fp, $file['stat'][7]));
398                                         if ($file['stat'][7] % 512)
399                                                 fread($fp, 512 - $file['stat'][7] % 512);
400                                         fclose($new);
401                                         @chmod($file['name'], $file['stat'][2]);
402                                 }
403                                 else
404                                 {
405                                         $this->error[] = "Could not open {$file['name']} for writing.";
406                                         continue;
407                                 }
408 //                              @chown($file['name'], $file['stat'][4]);
409 //                              @chgrp($file['name'], $file['stat'][5]);
410                                 if (!$dry_run) {
411                                         @touch($file['name'], $file['stat'][9]);
412                                 }
413                                 unset ($file);
414                         }
415                         chdir($pwd);
416                 }
417                 else
418                         $this->error[] = "Could not open file {$this->options['name']}";
419
420                 return $flist;
421         }
422
423         function open_archive()
424         {
425                 return @fopen($this->options['name'], "rb");
426         }
427 }
428
429 class gzip_file extends tar_file
430 {
431         function __construct($name)
432         {
433                 parent::__construct($name);
434                 $this->options['type'] = "gzip";
435         }
436
437         function create_gzip()
438         {
439                 if ($this->options['inmemory'] == 0)
440                 {
441                         if ($fp = gzopen($this->options['name'], "wb{$this->options['level']}"))
442                         {
443                                 fseek($this->archive, 0);
444                                 while ($temp = fread($this->archive, 1048576))
445                                         gzwrite($fp, $temp);
446                                 gzclose($fp);
447                         }
448                         else
449                         {
450                                 $this->error[] = "Could not open {$this->options['name']} for writing.";
451                                 return 0;
452                         }
453                 }
454                 else
455                         $this->archive = gzencode($this->archive, $this->options['level']);
456
457                 return 1;
458         }
459
460         function open_archive()
461         {
462                 return @gzopen($this->options['name'], "rb");
463         }
464 }
465