Skip to content

[Package Manager] Re-Download missing packages #14018

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 31 commits into
base: 3.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3fc5038
Filter user input used in phpthumb
opengeek Jul 11, 2018
606dc0f
Prevent directory traversal and limit files deleted when clearing mod…
opengeek Jul 12, 2018
4ab3571
MODX Revolution 2.6.5-pl
opengeek Jul 12, 2018
eef48c8
Fix zips from build to ensure they do not include 777/666 attributes
opengeek Jul 13, 2018
f291a83
[Package Manager] Re-Download missing packages
exside Jul 28, 2018
51f4052
Make monster oneliner multiline
exside Jul 29, 2018
bd29d3f
Split up oneliner into variables...
exside Jul 29, 2018
43a626c
Harmonize comment style + empty dir fix
exside Jul 29, 2018
6a2f944
Separate code into getMetadata() method
exside Jul 29, 2018
69c081b
Fix issue with update mode
exside Jul 29, 2018
290cee5
Optimization & Dealing with various edge cases
exside Jul 30, 2018
4195e57
Change getMetadata() to return directly
exside Aug 3, 2018
d0fd631
Otherwise a xx4 or xx5 would be detected too.
Ibochkarev Feb 17, 2019
f459f06
Merge pull request #1 from Ibochkarev/patch-2
exside Feb 20, 2019
ade661c
[Package Manager] Re-Download missing packages
exside Jul 28, 2018
160b7d6
Make monster oneliner multiline
exside Jul 29, 2018
5bbb2b1
Split up oneliner into variables...
exside Jul 29, 2018
8257b2d
Harmonize comment style + empty dir fix
exside Jul 29, 2018
35eeb5f
Separate code into getMetadata() method
exside Jul 29, 2018
a2647cc
Fix issue with update mode
exside Jul 29, 2018
317c829
Optimization & Dealing with various edge cases
exside Jul 30, 2018
1a00781
Change getMetadata() to return directly
exside Aug 3, 2018
bb8318b
Otherwise a xx4 or xx5 would be detected too.
Ibochkarev Feb 17, 2019
6353263
solve merge conflicts after rebase
exside May 22, 2019
017304e
revert unwanted file changes...
exside May 22, 2019
f4b6b9d
it's my editor trying to be smart...
exside May 22, 2019
7dfa009
Revert "revert unwanted file changes..."
exside May 22, 2019
4662482
Merge remote-tracking branch 'origin/patch-1' into patch-1
exside May 22, 2019
509acd3
re-add double whitespace at the end (yeah, still the editor)...
exside May 22, 2019
2136019
whitespace again
exside May 22, 2019
4fb2db2
Update core/model/modx/transport/modtransportpackage.class.php
Jako Sep 19, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion _build/build.sample.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ git.command = git
#project.name.fs = modx

# Override to set the version and release strings
#modx.core.version = 2.6.4
#modx.core.version = 2.6.5
#modx.core.release = pl

# these properties require a local Git clone in order to produce distributions
Expand Down
7 changes: 6 additions & 1 deletion _build/build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<tstamp />

<!-- Set some common project properties -->
<property name="project.name" value="MODX Revolution" />
<property name="project.name.fs" value="modx" />
<property name="project.root.dir" value=".." />
<property name="project.core.dir" value="${project.root.dir}/core" />
Expand All @@ -19,7 +20,7 @@
<property name="project.manager.dir" value="${project.root.dir}/manager" />

<!-- Set the project version -->
<property name="modx.core.version" value="2.6.4" />
<property name="modx.core.version" value="2.6.5" />
<property name="modx.core.release" value="pl" />

<!-- Set some common build properties -->
Expand Down Expand Up @@ -97,6 +98,7 @@
<delete file="${core.dir}/packages/core.transport.zip" failonerror="false" />
<delete dir="${core.dir}/packages/core" failonerror="false" />
<exec dir="${project.basedir}" command="${php.command} transport.core.php ${transport.image}" />
<exec dir="${project.basedir}" command="${php.command} fixzip.php ${core.dir}/packages/core.transport.zip" />
</target>

<!-- Run the phpdoc generation script -->
Expand Down Expand Up @@ -210,6 +212,7 @@
<include name="**" />
</fileset>
</zip>
<exec dir="${project.basedir}" command="${php.command} fixzip.php ${build.distrib.dir}/${distrib.name}.zip" />
<reflexive file="${build.image.dir}/setup/includes/config.core.php">
<filterchain>
<replacetokens>
Expand Down Expand Up @@ -311,6 +314,7 @@
<include name="core/packages/core/*/*.resolver" />
</fileset>
</zip>
<exec dir="${project.basedir}" command="${php.command} fixzip.php ${build.distrib.dir}/${distrib.name}.zip" />
<reflexive file="${build.image.dir}/setup/includes/config.core.php">
<filterchain>
<replacetokens>
Expand Down Expand Up @@ -374,6 +378,7 @@
<include name="core/packages/core.transport.zip" />
</fileset>
</zip>
<exec dir="${project.basedir}" command="${php.command} fixzip.php ${build.distrib.dir}/${distrib.name}.zip" />
<reflexive file="${build.image.dir}/setup/includes/config.core.php">
<filterchain>
<replacetokens>
Expand Down
26 changes: 26 additions & 0 deletions _build/fixzip.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
if (!isset($argv[1])) {
echo 'ERROR: No zip file specified.';
return 255;
}

$zipFileName = realpath($argv[1]);
if (!file_exists($zipFileName) || !is_readable($zipFileName)) {
echo 'ERROR: Specified file does not exist or is not readable.';
return 255;
}
if (!is_writable($zipFileName)) {
echo 'ERROR: Specified file is not writable.';
return 255;
}

$zip = new ZipArchive();
$zip->open($zipFileName);

for ($i = 0; $i < $zip->count(); $i++) {
$zip->setExternalAttributesIndex($i, 0, 0);
}

$zip->close();

return 0;
2 changes: 1 addition & 1 deletion _build/transport.core.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
$cacheManager->deleteTree($packageDirectory . 'core',array(
'deleteTop' => true,
'skipDirs' => false,
'extensions' => '*',
'extensions' => array(),
));
}
if (!file_exists($packageDirectory . 'core') && !file_exists($packageDirectory . 'core.transport.zip')) {
Expand Down
5 changes: 5 additions & 0 deletions core/docs/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
This file shows the changes in recent releases of MODX. The most current release is usually the
development release, and is only shown to give an idea of what's currently in the pipeline.

MODX Revolution 2.6.5-pl (July 11, 2018)
====================================
- Prevent directory traversal and limit files deleted when clearing modFileRegister [#13980]
- Filter user input used in phpthumb [#13979]

MODX Revolution 2.6.4-pl (June 7, 2018)
====================================
- Fix sorting by access column in Template Access tab of Template Variable edit view [#13893]
Expand Down
2 changes: 1 addition & 1 deletion core/docs/version.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
$v= array ();
$v['version']= '2'; // Current version.
$v['major_version']= '6'; // Current major version.
$v['minor_version']= '4'; // Current minor version.
$v['minor_version']= '5'; // Current minor version.
$v['patch_level']= 'pl'; // Current patch level.
$v['code_name']= 'Revolution'; // Current codename.
$v['distro']= '@git@';
Expand Down
2 changes: 1 addition & 1 deletion core/model/modx/modfilehandler.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ public function remove($options = array()) {
$options = array_merge(array(
'deleteTop' => true,
'skipDirs' => false,
'extensions' => '',
'extensions' => array(),
), $options);

$this->fileHandler->modx->getCacheManager();
Expand Down
43 changes: 31 additions & 12 deletions core/model/modx/registry/modfileregister.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,21 @@ class modFileRegister extends modRegister {
/**
* Construct a new modFileRegister instance.
*
* {@inheritdoc}
* @param modX &$modx A reference to a modX instance.
* @param string $key A valid PHP variable which will be set on the modRegistry instance.
* @param array $options Optional array of registry options.
*/
function __construct(& $modx, $key, $options = array()) {
parent :: __construct($modx, $key, $options);
function __construct(& $modx, $key, $options = array())
{
parent::__construct($modx, $key, $options);

$modx->getCacheManager();
$this->directory = $modx->getCachePath() . 'registry/';
$this->directory .= isset($options['directory'])
? $options['directory']
: $key;
if ($this->directory[strlen($this->directory)-1] != '/') $this->directory .= '/';
? $options['directory']
: $key;

$this->directory = rtrim($this->directory, '/') . '/';
}

/**
Expand All @@ -57,12 +62,16 @@ public function connect(array $attributes = array()) {
*
* {@inheritdoc}
*/
public function clear($topic) {
$topicDirectory = $this->directory;
$topicDirectory.= $topic[0] == '/' ? substr($topic, 1) : $topic ;
return $this->modx->cacheManager->deleteTree($topicDirectory, array(
'extensions' => '.msg.php'
));
public function clear($topic)
{
$topicDirectory = $this->directory . ltrim($this->sanitizePath($topic), '/');

return $this->modx->cacheManager->deleteTree(
realpath($topicDirectory),
array(
'extensions' => array('.msg.php')
)
);
}

/**
Expand Down Expand Up @@ -263,4 +272,14 @@ public function send($topic, $message, array $options = array()) {
public function close() {
return true;
}

/**
* Sanitize the specified path
*
* @param string $path The path to clean
* @return string The sanitized path
*/
protected function sanitizePath($path) {
return preg_replace(array("/\.*[\/|\\\]/i", "/[\/|\\\]+/i"), array('/', '/'), $path);
}
}
131 changes: 102 additions & 29 deletions core/model/modx/transport/modtransportpackage.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,9 @@ public function getTransport($state = -1) {
$packageDir = $workspace->get('path') . 'packages/';
$sourceFile = $this->get('source');
if ($sourceFile) {
$transferred= file_exists($packageDir . $sourceFile);
$transferred = file_exists($packageDir . $sourceFile);
if (!$transferred) { /* if no transport zip, attempt to get it */
if (!$transferred= $this->transferPackage($sourceFile, $packageDir)) {
if (!$transferred = $this->transferPackage($sourceFile, $packageDir)) {
$this->xpdo->log(xPDO::LOG_LEVEL_ERROR,$this->xpdo->lexicon('package_err_transfer',array(
'sourceFile' => $sourceFile,
'packageDir' => $packageDir,
Expand All @@ -212,9 +212,9 @@ public function getTransport($state = -1) {
}
if ($transferred) {
if ($state < 0) {
/* if directory is missing but zip exists, and DB state value is incorrect, fix here */
/* if directory is missing or empty but zip exists, and DB state value is incorrect, fix here */
$targetDir = basename($sourceFile, '.transport.zip');
$state = is_dir($packageDir.$targetDir) ? $this->get('state') : xPDOTransport::STATE_PACKED;
$state = (is_dir($packageDir.$targetDir) && count(glob($packageDir.$targetDir.'/*')) !== 0) ? $this->get('state') : xPDOTransport::STATE_PACKED;
}
/* retrieve the package */
$this->package = xPDOTransport :: retrieve($this->xpdo, $packageDir . $sourceFile, $packageDir, $state);
Expand All @@ -236,6 +236,40 @@ public function getTransport($state = -1) {
return $this->package;
}

/**
* Get metadata for a package in a more usable format
*
* @access public
* @param array $metadata optional input array to be processed, defaults to package metadata
* @param string $keyfield the inner array element pointing to the value for key substitution
* @return array an array of metadata accessible by $keyfield
*/
public function getMetadata($metadata = array(), $keyfield = 'name') {
if (empty($metadata)) {
$metadata = $this->get('metadata');
}

if (is_array($metadata[0])) {
return array_reduce($metadata, function ($result, $item) use ($keyfield) {
$key = $item[$keyfield];
unset($item[$keyfield]);

/* recurisvely handle nested arrays, attributes and children */
foreach ($item as $k => $v) {
if (is_array($v) && !empty($v)) {
$item[$k] = $this->getMetadata($v);
}
}

$result[$key] = $item;

return $result;
}, array());
} else {
return $metadata;
}
}

/**
* Removes and uninstalls the package.
*
Expand Down Expand Up @@ -357,13 +391,35 @@ public function uninstall(array $options = array()) {
* @return boolean True if successful.
*/
public function transferPackage($sourceFile, $targetDir) {
$transferred= false;
$content= '';
$transferred = false;
$content = '';
if (is_dir($targetDir) && is_writable($targetDir)) {
if (!is_array($this->xpdo->version)) { $this->xpdo->getVersionData(); }
$productVersion = $this->xpdo->version['code_name'].'-'.$this->xpdo->version['full_version'];

$source= $this->get('service_url') . $sourceFile.(strpos($sourceFile,'?') !== false ? '&' : '?').'revolution_version='.$productVersion;
$productVersion = join('-', array(
$this->xpdo->version['code_name'],
$this->xpdo->version['full_version']
));

/* make sure the package is downloaded, if not attempt re-download */
if (strpos($sourceFile, '//') === false && !file_exists($targetDir . $sourceFile)) {
/* get the package metadata */
$metadata = $this->getMetadata();

if (!empty($metadata) && ($metadata['location'] || $metadata['file'])) {
/* assign remote download URL */
if ($metadata['location']) {
if (is_array($metadata['location'])) {
$source = $metadata['location']['text'];
} else {
$source = $metadata['location'];
}
} else {
$source = $metadata['file']['children']['location']['text'];
}
}
} else {
$source = $this->get('service_url') . $sourceFile.(strpos($sourceFile,'?') !== false ? '&' : '?').'revolution_version='.$productVersion;
}

/* see if user has allow_url_fopen on and is not behind a proxy */
$proxyHost = $this->xpdo->getOption('proxy_host',null,'');
Expand Down Expand Up @@ -417,6 +473,11 @@ public function transferPackage($sourceFile, $targetDir) {
}
}
$content = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if ($status >= 400) {
$content = '';
}
curl_close($ch);
}

Expand All @@ -432,7 +493,7 @@ public function transferPackage($sourceFile, $targetDir) {
$transferred= $cacheManager->writeFile($target, $content);
}
} else {
$this->xpdo->log(xPDO::LOG_LEVEL_ERROR,'MODX could not download the file. You must enable allow_url_fopen, cURL or fsockopen to use remote transport packaging.');
$this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'MODX could not download the file. ' . (isset($status) ? 'Server responded with status code ' . $status . '.' : 'You must enable allow_url_fopen, cURL or fsockopen to use remote transport packaging.'));
}
} else {
$this->xpdo->log(xPDO::LOG_LEVEL_ERROR,$this->xpdo->lexicon('package_err_target_write',array(
Expand Down Expand Up @@ -661,33 +722,45 @@ protected function _getByFsockopen($url) {
$purl = parse_url($url);
$host = $purl['host'];
$path = !empty($purl['path']) ? $purl['path'] : '/';
if (!empty($purl['query'])) { $path .= '?'.$purl['query']; }
if (!empty($purl['query'])) {
$path .= '?' . $purl['query'];
}
$port = !empty($purl['port']) ? $purl['port'] : '80';

$timeout = 10;
$response = '';
$fp = @fsockopen($host,$port,$errno,$errstr,$timeout);

if( !$fp ) {
$this->xpdo->log(xPDO::LOG_LEVEL_ERROR,'Could not retrieve from '.$url);
$this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Could not retrieve from ' . $url);
} else {
fwrite($fp, "GET $path ".$_SERVER['SERVER_PROTOCOL']."\r\n" .
"Host: $host\r\n" .
"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3\r\n" .
"Accept: */*\r\n" .
"Accept-Language: en-us,en;q=0.5\r\n" .
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" .
"Keep-Alive: 300\r\n" .
"Connection: keep-alive\r\n" .
"Referer: http://$host\r\n\r\n");

while ($line = fread($fp, 4096)) {
$response .= $line;
}
fclose($fp);

$pos = strpos($response, "\r\n\r\n");
$response = substr($response, $pos + 4);
$out = implode("\r\n", array(
"GET $path " . $_SERVER['SERVER_PROTOCOL'],
"Host: $host",
"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3",
"Accept: */*",
"Accept-Language: en-us,en;q=0.5",
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7",
"Keep-Alive: 300",
"Connection: keep-alive",
"Referer: http://$host",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it has any influence on how the code works but just to be consistent it would be nice if the referer would use the same protocol (http or https) as the "host".

"\r\n"
));
fwrite($fp, $out);

$status = explode(' ', fgets($fp, 13))[1];

if (strpos($status, '4') !== false || strpos($status, '5') !== false) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(strpos($status, '4') === 0 || strpos($status, '5') === 0)

Otherwise a xx4 or xx5 would be detected too.

$response = '';
} else {
while ($line = fread($fp, 4096)) {
$response .= $line;
}
$pos = strpos($response, "\r\n\r\n");
$response = substr($response, $pos + 4);
}

fclose($fp);
}
return $response;
}
Expand Down
Loading