Current oav website
This commit is contained in:
514
dotclear._no/inc/core/class.dc.update.php
Normal file
514
dotclear._no/inc/core/class.dc.update.php
Normal file
@ -0,0 +1,514 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Dotclear
|
||||
* @subpackage Core
|
||||
*
|
||||
* @copyright Olivier Meunier & Association Dotclear
|
||||
* @copyright GPL-2.0-only
|
||||
*/
|
||||
|
||||
if (!defined('DC_RC_PATH')) {return;}
|
||||
|
||||
class dcUpdate
|
||||
{
|
||||
const ERR_FILES_CHANGED = 101;
|
||||
const ERR_FILES_UNREADABLE = 102;
|
||||
const ERR_FILES_UNWRITALBE = 103;
|
||||
|
||||
protected $url;
|
||||
protected $subject;
|
||||
protected $version;
|
||||
protected $cache_file;
|
||||
|
||||
protected $version_info = [
|
||||
'version' => null,
|
||||
'href' => null,
|
||||
'checksum' => null,
|
||||
'info' => null,
|
||||
'php' => '5.6',
|
||||
'notify' => true
|
||||
];
|
||||
|
||||
protected $cache_ttl = '-6 hours';
|
||||
protected $forced_files = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param url string Versions file URL
|
||||
* @param subject string Subject to check
|
||||
* @param version string Version type
|
||||
* @param cache_dir string Directory cache path
|
||||
*/
|
||||
public function __construct($url, $subject, $version, $cache_dir)
|
||||
{
|
||||
$this->url = $url;
|
||||
$this->subject = $subject;
|
||||
$this->version = $version;
|
||||
$this->cache_file = $cache_dir . '/' . $subject . '-' . $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for Dotclear updates.
|
||||
* Returns latest version if available or false.
|
||||
*
|
||||
* @param version string Current version to compare
|
||||
* @param nocache boolean Force checking
|
||||
* @return string Latest version if available
|
||||
*/
|
||||
public function check($version, $nocache = false)
|
||||
{
|
||||
$this->getVersionInfo($nocache);
|
||||
$v = $this->getVersion();
|
||||
if ($v && version_compare($version, $v, '<')) {
|
||||
return $v;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getVersionInfo($nocache = false)
|
||||
{
|
||||
# Check cached file
|
||||
if (is_readable($this->cache_file) && filemtime($this->cache_file) > strtotime($this->cache_ttl) && !$nocache) {
|
||||
$c = @file_get_contents($this->cache_file);
|
||||
$c = @unserialize($c);
|
||||
if (is_array($c)) {
|
||||
$this->version_info = $c;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$cache_dir = dirname($this->cache_file);
|
||||
$can_write = (!is_dir($cache_dir) && is_writable(dirname($cache_dir)))
|
||||
|| (!file_exists($this->cache_file) && is_writable($cache_dir))
|
||||
|| is_writable($this->cache_file);
|
||||
|
||||
# If we can't write file, don't bug host with queries
|
||||
if (!$can_write) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_dir($cache_dir)) {
|
||||
try {
|
||||
files::makeDir($cache_dir);
|
||||
} catch (Exception $e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
# Try to get latest version number
|
||||
try
|
||||
{
|
||||
$path = '';
|
||||
$status = 0;
|
||||
|
||||
$http_get = function ($http_url) use (&$status, $path) {
|
||||
$client = netHttp::initClient($http_url, $path);
|
||||
if ($client !== false) {
|
||||
$client->setTimeout(4);
|
||||
$client->setUserAgent($_SERVER['HTTP_USER_AGENT']);
|
||||
$client->get($path);
|
||||
$status = (int) $client->getStatus();
|
||||
}
|
||||
return $client;
|
||||
};
|
||||
|
||||
$client = $http_get($this->url);
|
||||
if ($status >= 400) {
|
||||
// If original URL uses HTTPS, try with HTTP
|
||||
$url_parts = parse_url($client->getRequestURL());
|
||||
if (isset($url_parts['scheme']) && $url_parts['scheme'] == 'https') {
|
||||
// Replace https by http in url
|
||||
$this->url = preg_replace('/^https(?=:\/\/)/i', 'http', $this->url);
|
||||
$client = $http_get($this->url);
|
||||
}
|
||||
}
|
||||
if (!$status || $status >= 400) {
|
||||
throw new Exception();
|
||||
}
|
||||
$this->readVersion($client->getContent());
|
||||
} catch (Exception $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
# Create cache
|
||||
file_put_contents($this->cache_file, serialize($this->version_info));
|
||||
}
|
||||
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->version_info['version'];
|
||||
}
|
||||
|
||||
public function getFileURL()
|
||||
{
|
||||
return $this->version_info['href'];
|
||||
}
|
||||
|
||||
public function getInfoURL()
|
||||
{
|
||||
return $this->version_info['info'];
|
||||
}
|
||||
|
||||
public function getChecksum()
|
||||
{
|
||||
return $this->version_info['checksum'];
|
||||
}
|
||||
|
||||
public function getPHPVersion()
|
||||
{
|
||||
return $this->version_info['php'];
|
||||
}
|
||||
|
||||
public function getNotify()
|
||||
{
|
||||
return $this->version_info['notify'];
|
||||
}
|
||||
|
||||
public function getForcedFiles()
|
||||
{
|
||||
return $this->forced_files;
|
||||
}
|
||||
|
||||
public function setForcedFiles(...$args)
|
||||
{
|
||||
$this->forced_files = $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets notification flag.
|
||||
*/
|
||||
public function setNotify($n)
|
||||
{
|
||||
|
||||
if (!is_writable($this->cache_file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->version_info['notify'] = (boolean) $n;
|
||||
file_put_contents($this->cache_file, serialize($this->version_info));
|
||||
}
|
||||
|
||||
public function checkIntegrity($digests_file, $root)
|
||||
{
|
||||
if (!$digests_file) {
|
||||
throw new Exception(__('Digests file not found.'));
|
||||
}
|
||||
|
||||
$changes = $this->md5sum($root, $digests_file);
|
||||
|
||||
if (!empty($changes)) {
|
||||
$e = new Exception('Some files have changed.', self::ERR_FILES_CHANGED);
|
||||
$e->bad_files = $changes;
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads new version to destination $dest.
|
||||
*/
|
||||
public function download($dest)
|
||||
{
|
||||
$url = $this->getFileURL();
|
||||
|
||||
if (!$url) {
|
||||
throw new Exception(__('No file to download'));
|
||||
}
|
||||
|
||||
if (!is_writable(dirname($dest))) {
|
||||
throw new Exception(__('Root directory is not writable.'));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$path = '';
|
||||
$status = 0;
|
||||
|
||||
$http_get = function ($http_url) use (&$status, $dest, $path) {
|
||||
$client = netHttp::initClient($http_url, $path);
|
||||
if ($client !== false) {
|
||||
$client->setTimeout(4);
|
||||
$client->setUserAgent($_SERVER['HTTP_USER_AGENT']);
|
||||
$client->useGzip(false);
|
||||
$client->setPersistReferers(false);
|
||||
$client->setOutput($dest);
|
||||
$client->get($path);
|
||||
$status = (int) $client->getStatus();
|
||||
}
|
||||
return $client;
|
||||
};
|
||||
|
||||
$client = $http_get($url);
|
||||
if ($status >= 400) {
|
||||
// If original URL uses HTTPS, try with HTTP
|
||||
$url_parts = parse_url($client->getRequestURL());
|
||||
if (isset($url_parts['scheme']) && $url_parts['scheme'] == 'https') {
|
||||
// Replace https by http in url
|
||||
$url = preg_replace('/^https(?=:\/\/)/i', 'http', $url);
|
||||
$client = $http_get($url);
|
||||
}
|
||||
}
|
||||
if ($status != 200) {
|
||||
@unlink($dest);
|
||||
throw new Exception();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw new Exception(__('An error occurred while downloading archive.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if archive was successfully downloaded.
|
||||
*/
|
||||
public function checkDownload($zip)
|
||||
{
|
||||
$cs = $this->getChecksum();
|
||||
|
||||
return $cs && is_readable($zip) && md5_file($zip) == $cs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Backups changed files before an update.
|
||||
*/
|
||||
public function backup($zip_file, $zip_digests, $root, $root_digests, $dest)
|
||||
{
|
||||
if (!is_readable($zip_file)) {
|
||||
throw new Exception(__('Archive not found.'));
|
||||
}
|
||||
|
||||
if (!is_readable($root_digests)) {
|
||||
@unlink($zip_file);
|
||||
throw new Exception(__('Unable to read current digests file.'));
|
||||
}
|
||||
|
||||
# Stop everything if a backup already exists and can not be overrided
|
||||
if (!is_writable(dirname($dest)) && !file_exists($dest)) {
|
||||
throw new Exception(__('Root directory is not writable.'));
|
||||
}
|
||||
|
||||
if (file_exists($dest) && !is_writable($dest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$b_fp = @fopen($dest, 'wb');
|
||||
if ($b_fp === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$zip = new fileUnzip($zip_file);
|
||||
$b_zip = new fileZip($b_fp);
|
||||
|
||||
if (!$zip->hasFile($zip_digests)) {
|
||||
@unlink($zip_file);
|
||||
throw new Exception(__('Downloaded file does not seem to be a valid archive.'));
|
||||
}
|
||||
|
||||
$opts = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES;
|
||||
$cur_digests = file($root_digests, $opts);
|
||||
$new_digests = explode("\n", $zip->unzip($zip_digests));
|
||||
$new_files = $this->getNewFiles($cur_digests, $new_digests);
|
||||
$zip->close();
|
||||
unset($opts, $cur_digests, $new_digests, $zip);
|
||||
|
||||
$not_readable = [];
|
||||
|
||||
if (!empty($this->forced_files)) {
|
||||
$new_files = array_merge($new_files, $this->forced_files);
|
||||
}
|
||||
|
||||
foreach ($new_files as $file) {
|
||||
if (!$file || !file_exists($root . '/' . $file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$b_zip->addFile($root . '/' . $file, $file);
|
||||
} catch (Exception $e) {
|
||||
$not_readable[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
# If only one file is not readable, stop everything now
|
||||
if (!empty($not_readable)) {
|
||||
$e = new Exception('Some files are not readable.', self::ERR_FILES_UNREADABLE);
|
||||
$e->bad_files = $not_readable;
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$b_zip->write();
|
||||
fclose($b_fp);
|
||||
$b_zip->close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade process.
|
||||
*/
|
||||
public function performUpgrade($zip_file, $zip_digests, $zip_root, $root, $root_digests)
|
||||
{
|
||||
if (!is_readable($zip_file)) {
|
||||
throw new Exception(__('Archive not found.'));
|
||||
}
|
||||
|
||||
if (!is_readable($root_digests)) {
|
||||
@unlink($zip_file);
|
||||
throw new Exception(__('Unable to read current digests file.'));
|
||||
}
|
||||
|
||||
$zip = new fileUnzip($zip_file);
|
||||
|
||||
if (!$zip->hasFile($zip_digests)) {
|
||||
@unlink($zip_file);
|
||||
throw new Exception(__('Downloaded file does not seem to be a valid archive.'));
|
||||
}
|
||||
|
||||
$opts = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES;
|
||||
$cur_digests = file($root_digests, $opts);
|
||||
$new_digests = explode("\n", $zip->unzip($zip_digests));
|
||||
$new_files = self::getNewFiles($cur_digests, $new_digests);
|
||||
|
||||
if (!empty($this->forced_files)) {
|
||||
$new_files = array_merge($new_files, $this->forced_files);
|
||||
}
|
||||
|
||||
$zip_files = [];
|
||||
$not_writable = [];
|
||||
|
||||
foreach ($new_files as $file) {
|
||||
if (!$file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$zip->hasFile($zip_root . '/' . $file)) {
|
||||
@unlink($zip_file);
|
||||
throw new Exception(__('Incomplete archive.'));
|
||||
}
|
||||
|
||||
$dest = $dest_dir = $root . '/' . $file;
|
||||
while (!is_dir($dest_dir = dirname($dest_dir)));
|
||||
|
||||
if ((file_exists($dest) && !is_writable($dest)) ||
|
||||
(!file_exists($dest) && !is_writable($dest_dir))) {
|
||||
$not_writable[] = $file;
|
||||
continue;
|
||||
}
|
||||
|
||||
$zip_files[] = $file;
|
||||
}
|
||||
|
||||
# If only one file is not writable, stop everything now
|
||||
if (!empty($not_writable)) {
|
||||
$e = new Exception('Some files are not writable', self::ERR_FILES_UNWRITALBE);
|
||||
$e->bad_files = $not_writable;
|
||||
throw $e;
|
||||
}
|
||||
|
||||
# Everything's fine, we can write files, then do it now
|
||||
$can_touch = function_exists('touch');
|
||||
foreach ($zip_files as $file) {
|
||||
$zip->unzip($zip_root . '/' . $file, $root . '/' . $file);
|
||||
if ($can_touch) {
|
||||
@touch($root . '/' . $file);
|
||||
}
|
||||
}
|
||||
@unlink($zip_file);
|
||||
}
|
||||
|
||||
protected function getNewFiles($cur_digests, $new_digests)
|
||||
{
|
||||
$cur_md5 = $cur_path = $cur_digests;
|
||||
$new_md5 = $new_path = $new_digests;
|
||||
|
||||
array_walk($cur_md5, [$this, 'parseLine'], 1);
|
||||
array_walk($cur_path, [$this, 'parseLine'], 2);
|
||||
array_walk($new_md5, [$this, 'parseLine'], 1);
|
||||
array_walk($new_path, [$this, 'parseLine'], 2);
|
||||
|
||||
$cur = array_combine($cur_md5, $cur_path);
|
||||
$new = array_combine($new_md5, $new_path);
|
||||
|
||||
return array_values(array_diff_key($new, $cur));
|
||||
}
|
||||
|
||||
protected function readVersion($str)
|
||||
{
|
||||
try
|
||||
{
|
||||
$xml = new SimpleXMLElement($str, LIBXML_NOERROR);
|
||||
$r = $xml->xpath("/versions/subject[@name='" . $this->subject . "']/release[@name='" . $this->version . "']");
|
||||
|
||||
if (!empty($r) && is_array($r)) {
|
||||
$r = $r[0];
|
||||
$this->version_info['version'] = isset($r['version']) ? (string) $r['version'] : null;
|
||||
$this->version_info['href'] = isset($r['href']) ? (string) $r['href'] : null;
|
||||
$this->version_info['checksum'] = isset($r['checksum']) ? (string) $r['checksum'] : null;
|
||||
$this->version_info['info'] = isset($r['info']) ? (string) $r['info'] : null;
|
||||
$this->version_info['php'] = isset($r['php']) ? (string) $r['php'] : null;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
protected function md5sum($root, $digests_file)
|
||||
{
|
||||
if (!is_readable($digests_file)) {
|
||||
throw new Exception(__('Unable to read digests file.'));
|
||||
}
|
||||
|
||||
$opts = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES;
|
||||
$contents = file($digests_file, $opts);
|
||||
|
||||
$changes = [];
|
||||
|
||||
foreach ($contents as $digest) {
|
||||
if (!preg_match('#^([\da-f]{32})\s+(.+?)$#', $digest, $m)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$md5 = $m[1];
|
||||
$filename = $root . '/' . $m[2];
|
||||
|
||||
# Invalid checksum
|
||||
if (!is_readable($filename) || !self::md5_check($filename, $md5)) {
|
||||
$changes[] = substr($m[2], 2);
|
||||
}
|
||||
}
|
||||
|
||||
# No checksum found in digests file
|
||||
if (empty($md5)) {
|
||||
throw new Exception(__('Invalid digests file.'));
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
protected function parseLine(&$v, $k, $n)
|
||||
{
|
||||
if (!preg_match('#^([\da-f]{32})\s+(.+?)$#', $v, $m)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$v = $n == 1 ? md5($m[2] . $m[1]) : substr($m[2], 2);
|
||||
}
|
||||
|
||||
protected static function md5_check($filename, $md5)
|
||||
{
|
||||
if (md5_file($filename) == $md5) {
|
||||
return true;
|
||||
} else {
|
||||
$filecontent = file_get_contents($filename);
|
||||
$filecontent = str_replace("\r\n", "\n", $filecontent);
|
||||
$filecontent = str_replace("\r", "\n", $filecontent);
|
||||
if (md5($filecontent) == $md5) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user