Dotclear

source: inc/core/class.dc.update.php @ 2566:9bf417837888

Revision 2566:9bf417837888, 10.9 KB checked in by franck <carnet.franck.paul@…>, 12 years ago (diff)

Add some people in CREDITS, remove trailing spaces and tabs.

Line 
1<?php
2# -- BEGIN LICENSE BLOCK ---------------------------------------
3#
4# This file is part of Dotclear 2.
5#
6# Copyright (c) 2003-2013 Olivier Meunier & Association Dotclear
7# Licensed under the GPL version 2.0 license.
8# See LICENSE file or
9# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
10#
11# -- END LICENSE BLOCK -----------------------------------------
12if (!defined('DC_RC_PATH')) { return; }
13
14class dcUpdate
15{
16     const ERR_FILES_CHANGED = 101;
17     const ERR_FILES_UNREADABLE = 102;
18     const ERR_FILES_UNWRITALBE = 103;
19
20     protected $url;
21     protected $subject;
22     protected $version;
23     protected $cache_file;
24
25     protected $version_info = array(
26          'version' => null,
27          'href' => null,
28          'checksum' => null,
29          'info' => null,
30          'notify' => true
31     );
32
33     protected $cache_ttl = '-6 hours';
34     protected $forced_files = array();
35
36     /**
37      * Constructor
38      *
39      * @param url            string    Versions file URL
40      * @param subject        string    Subject to check
41      * @param version        string    Version type
42      * @param cache_dir      string    Directory cache path
43      */
44     public function __construct($url,$subject,$version,$cache_dir)
45     {
46          $this->url = $url;
47          $this->subject = $subject;
48          $this->version = $version;
49          $this->cache_file = $cache_dir.'/'.$subject.'-'.$version;
50     }
51
52     /**
53      * Checks for Dotclear updates.
54      * Returns latest version if available or false.
55      *
56      * @param version        string    Current version to compare
57      * @param nocache        boolean   Force checking
58      * @return string                  Latest version if available
59      */
60     public function check($version, $nocache=false)
61     {
62          $this->getVersionInfo($nocache);
63          $v = $this->getVersion();
64          if ($v && version_compare($version,$v,'<')) {
65               return $v;
66          }
67
68          return false;
69     }
70
71     public function getVersionInfo($nocache=false)
72     {
73          # Check cached file
74          if (is_readable($this->cache_file) && filemtime($this->cache_file) > strtotime($this->cache_ttl) && !$nocache)
75          {
76               $c = @file_get_contents($this->cache_file);
77               $c = @unserialize($c);
78               if (is_array($c)) {
79                    $this->version_info = $c;
80                    return;
81               }
82          }
83
84          $cache_dir = dirname($this->cache_file);
85          $can_write = (!is_dir($cache_dir) && is_writable(dirname($cache_dir)))
86          || (!file_exists($this->cache_file) && is_writable($cache_dir))
87          || is_writable($this->cache_file);
88
89          # If we can't write file, don't bug host with queries
90          if (!$can_write) {
91               return;
92          }
93
94          if (!is_dir($cache_dir)) {
95               try {
96                    files::makeDir($cache_dir);
97               } catch (Exception $e) {
98                    return;
99               }
100          }
101
102          # Try to get latest version number
103          try
104          {
105               $path = '';
106               $client = netHttp::initClient($this->url,$path);
107               if ($client !== false) {
108                    $client->setTimeout(4);
109                    $client->setUserAgent($_SERVER['HTTP_USER_AGENT']);
110                    $client->get($path);
111
112                    $this->readVersion($client->getContent());
113               }
114          }
115          catch (Exception $e) {}
116
117          # Create cache
118          file_put_contents($this->cache_file,serialize($this->version_info));
119     }
120
121     public function getVersion()
122     {
123          return $this->version_info['version'];
124     }
125
126     public function getFileURL()
127     {
128          return $this->version_info['href'];
129     }
130
131     public function getInfoURL()
132     {
133          return $this->version_info['info'];
134     }
135
136     public function getChecksum()
137     {
138          return $this->version_info['checksum'];
139     }
140
141     public function getNotify()
142     {
143          return $this->version_info['notify'];
144     }
145
146     public function getForcedFiles()
147     {
148          return $this->forced_files;
149     }
150
151     public function setForcedFiles()
152     {
153          $this->forced_files = func_get_args();
154     }
155
156     /**
157      * Sets notification flag.
158      */
159     public function setNotify($n)
160     {
161
162          if (!is_writable($this->cache_file)) {
163               return;
164          }
165
166          $this->version_info['notify'] = (boolean) $n;
167          file_put_contents($this->cache_file,serialize($this->version_info));
168     }
169
170     public function checkIntegrity($digests_file,$root)
171     {
172          if (!$digests_file) {
173               throw new Exception(__('Digests file not found.'));
174          }
175
176          $changes = $this->md5sum($root,$digests_file);
177
178          if (!empty($changes)) {
179               $e = new Exception('Some files have changed.',self::ERR_FILES_CHANGED);
180               $e->bad_files = $changes;
181               throw $e;
182          }
183
184          return true;
185     }
186
187     /**
188      * Downloads new version to destination $dest.
189      */
190     public function download($dest)
191     {
192          $url = $this->getFileURL();
193
194          if (!$url) {
195               throw new Exception(__('No file to download'));
196          }
197
198          if (!is_writable(dirname($dest))) {
199               throw new Exception(__('Root directory is not writable.'));
200          }
201
202          try
203          {
204               $client = netHttp::initClient($url,$path);
205               $client->setTimeout(4);
206               $client->setUserAgent($_SERVER['HTTP_USER_AGENT']);
207               $client->useGzip(false);
208               $client->setPersistReferers(false);
209               $client->setOutput($dest);
210               $client->get($path);
211
212               if ($client->getStatus() != 200) {
213                    @unlink($dest);
214                    throw new Exception();
215               }
216          }
217          catch (Exception $e)
218          {
219               throw new Exception(__('An error occurred while downloading archive.'));
220          }
221     }
222
223     /**
224      * Checks if archive was successfully downloaded.
225      */
226     public function checkDownload($zip)
227     {
228          $cs = $this->getChecksum();
229
230          return $cs && is_readable($zip) && md5_file($zip) == $cs;
231     }
232
233     /**
234      * Backups changed files before an update.
235      */
236     public function backup($zip_file,$zip_digests,$root,$root_digests,$dest)
237     {
238          if (!is_readable($zip_file)) {
239               throw new Exception(__('Archive not found.'));
240          }
241
242          if (!is_readable($root_digests)) {
243               @unlink($zip_file);
244               throw new Exception(__('Unable to read current digests file.'));
245          }
246
247          # Stop everything if a backup already exists and can not be overrided
248          if (!is_writable(dirname($dest)) && !file_exists($dest)) {
249               throw new Exception(__('Root directory is not writable.'));
250          }
251
252          if (file_exists($dest) && !is_writable($dest)) {
253               return false;
254          }
255
256          $b_fp = @fopen($dest,'wb');
257          if ($b_fp === false) {
258               return false;
259          }
260
261          $zip = new fileUnzip($zip_file);
262          $b_zip = new fileZip($b_fp);
263
264          if (!$zip->hasFile($zip_digests))
265          {
266               @unlink($zip_file);
267               throw new Exception(__('Downloaded file does not seem to be a valid archive.'));
268          }
269
270          $opts = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES;
271          $cur_digests = file($root_digests,$opts);
272          $new_digests = explode("\n",$zip->unzip($zip_digests));
273          $new_files = $this->getNewFiles($cur_digests,$new_digests);
274          $zip->close();
275          unset($opts,$cur_digests,$new_digests,$zip);
276
277          $not_readable = array();
278
279          if (!empty($this->forced_files)) {
280               $new_files = array_merge($new_files,$this->forced_files);
281          }
282
283          foreach ($new_files as $file)
284          {
285               if (!$file || !file_exists($root.'/'.$file)) {
286                    continue;
287               }
288
289               try {
290                    $b_zip->addFile($root.'/'.$file,$file);
291               } catch (Exception $e) {
292                    $not_readable[] = $file;
293               }
294          }
295
296          # If only one file is not readable, stop everything now
297          if (!empty($not_readable)) {
298               $e = new Exception('Some files are not readable.',self::ERR_FILES_UNREADABLE);
299               $e->bad_files = $not_readable;
300               throw $e;
301          }
302
303          $b_zip->write();
304          fclose($b_fp);
305          $b_zip->close();
306
307          return true;
308     }
309
310     /**
311      * Upgrade process.
312      */
313     public function performUpgrade($zip_file,$zip_digests,$zip_root,$root,$root_digests)
314     {
315          if (!is_readable($zip_file)) {
316               throw new Exception(__('Archive not found.'));
317          }
318
319          if (!is_readable($root_digests)) {
320               @unlink($zip_file);
321               throw new Exception(__('Unable to read current digests file.'));
322          }
323
324          $zip = new fileUnzip($zip_file);
325
326          if (!$zip->hasFile($zip_digests))
327          {
328               @unlink($zip_file);
329               throw new Exception(__('Downloaded file does not seem to be a valid archive.'));
330          }
331
332          $opts = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES;
333          $cur_digests = file($root_digests,$opts);
334          $new_digests = explode("\n",$zip->unzip($zip_digests));
335          $new_files = self::getNewFiles($cur_digests,$new_digests);
336
337          if (!empty($this->forced_files)) {
338               $new_files = array_merge($new_files,$this->forced_files);
339          }
340
341          $zip_files = array();
342          $not_writable = array();
343
344          foreach ($new_files as $file)
345          {
346               if (!$file) {
347                    continue;
348               }
349
350               if (!$zip->hasFile($zip_root.'/'.$file)) {
351                    @unlink($zip_file);
352                    throw new Exception(__('Incomplete archive.'));
353               }
354
355               $dest = $dest_dir = $root.'/'.$file;
356               while (!is_dir($dest_dir = dirname($dest_dir)));
357
358               if ((file_exists($dest) && !is_writable($dest)) ||
359               (!file_exists($dest) && !is_writable($dest_dir))) {
360                    $not_writable[] = $file;
361                    continue;
362               }
363
364               $zip_files[] = $file;
365          }
366
367          # If only one file is not writable, stop everything now
368          if (!empty($not_writable)) {
369               $e = new Exception('Some files are not writable',self::ERR_FILES_UNWRITALBE);
370               $e->bad_files = $not_writable;
371               throw $e;
372          }
373
374          # Everything's fine, we can write files, then do it now
375          $can_touch = function_exists('touch');
376          foreach ($zip_files as $file) {
377               $zip->unzip($zip_root.'/'.$file, $root.'/'.$file);
378               if ($can_touch) {
379                    @touch($root.'/'.$file);
380               }
381          }
382          @unlink($zip_file);
383     }
384
385     protected function getNewFiles($cur_digests,$new_digests)
386     {
387          $cur_md5 = $cur_path = $cur_digests;
388          $new_md5 = $new_path = $new_digests;
389
390          array_walk($cur_md5, array($this,'parseLine'),1);
391          array_walk($cur_path,array($this,'parseLine'),2);
392          array_walk($new_md5, array($this,'parseLine'),1);
393          array_walk($new_path,array($this,'parseLine'),2);
394
395          $cur = array_combine($cur_md5,$cur_path);
396          $new = array_combine($new_md5,$new_path);
397
398          return array_values(array_diff_key($new,$cur));
399     }
400
401     protected function readVersion($str)
402     {
403          try
404          {
405               $xml = new SimpleXMLElement($str,LIBXML_NOERROR);
406               $r = $xml->xpath("/versions/subject[@name='".$this->subject."']/release[@name='".$this->version."']");
407
408               if (!empty($r) && is_array($r))
409               {
410                    $r = $r[0];
411                    $this->version_info['version'] = isset($r['version']) ? (string) $r['version'] : null;
412                    $this->version_info['href'] = isset($r['href']) ? (string) $r['href'] : null;
413                    $this->version_info['checksum'] = isset($r['checksum']) ? (string) $r['checksum'] : null;
414                    $this->version_info['info'] = isset($r['info']) ? (string) $r['info'] : null;
415               }
416          }
417          catch (Exception $e)
418          {
419               throw $e;
420          }
421     }
422
423     protected function md5sum($root,$digests_file)
424     {
425          if (!is_readable($digests_file)) {
426               throw new Exception(__('Unable to read digests file.'));
427          }
428
429          $opts = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES;
430          $contents = file($digests_file,$opts);
431
432          $changes = array();
433
434          foreach ($contents as $digest)
435          {
436               if (!preg_match('#^([\da-f]{32})\s+(.+?)$#',$digest,$m)) {
437                    continue;
438               }
439
440               $md5 = $m[1];
441               $filename = $root.'/'.$m[2];
442
443               # Invalid checksum
444               if (!is_readable($filename) || !self::md5_check($filename, $md5)) {
445                    $changes[] = substr($m[2],2);
446               }
447          }
448
449          # No checksum found in digests file
450          if (empty($md5)) {
451               throw new Exception(__('Invalid digests file.'));
452          }
453
454          return $changes;
455     }
456
457     protected function parseLine(&$v,$k,$n)
458     {
459          if (!preg_match('#^([\da-f]{32})\s+(.+?)$#',$v,$m)) {
460               return;
461          }
462
463          $v = $n == 1 ? md5($m[2].$m[1]) : substr($m[2],2);
464     }
465
466     protected static function md5_check($filename,$md5)
467     {
468          if (md5_file($filename) == $md5) {
469               return true;
470          } else {
471               $filecontent = file_get_contents($filename);
472               $filecontent = str_replace ("\r\n","\n",$filecontent);
473               $filecontent = str_replace ("\r","\n",$filecontent);
474               if (md5($filecontent) == $md5) return true;
475          }
476          return false;
477     }
478}
Note: See TracBrowser for help on using the repository browser.

Sites map