Dotclear

source: inc/core/class.dc.update.php @ 1231:415e762385aa

Revision 1231:415e762385aa, 11.3 KB checked in by Denis Jean-Christian <contact@…>, 12 years ago (diff)

Forcer le test d'existance de mise à jour, closes #1412

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

Sites map