Details
-
Type:
Bug
-
Status:
Closed
-
Priority:
Major
-
Resolution: Won't Fix
-
Affects Version/s: 1.8.2
-
Fix Version/s: None
-
Component/s: Libraries
-
Labels:None
-
Environment:Windows 2003R2 SP2, PHP5.2.3 SQL 5.0.45
-
Database:MySQL
-
Affected Branches:MOODLE_18_STABLE
Description
When using a UNC path for the moodledata directory, the ZIP functions do not work, this appears to be due to the function cleardoubleslashes
//Replace 1 or more slashes or backslashes to 1 slash
function cleardoubleslashes ($path) {
return preg_replace('/(\/|\\\){1,}/','/',$path);
which remove the needed // before the path to files in the zip functions.
With the current functions the path moodle creates to a zip file, or to a file to be zipped ends up like the example below
/moodleserver/moodleshare/1/resourcefolder/resourse.ext
where it needs to be
//moodleserver/moodleshare/1/resourcefolder/resourse.ext
otherwise it reports it cannot access the file
I have made the following changes in the files and functions below to get this working for us, however this will likely break an install on a *nix platform, or on a windows platform with a local moodledata directory, so for this bug to be fixed properly, some sort of error checking on the moodledata folder, or a config option for UNC based moodledata directories may be needed
I have not provided line numbers as I have added in my own comments to the php files to track where I made the changes, which has obviously move the line numbers about.
function unzip_file and function unzip_cleanfilename in moodlelib.php (to fix unzip options)
function unzip_file ($zipfile, $destination = '', $showstatus = true) {
//Unzip one zip file to a destination dir
//Both parameters must be FULL paths
//If destination isn't specified, it will be the
//SAME directory where the zip file resides.
global $CFG;
//Extract everything from zipfile
// EDIT *** CHRIS SHEARING *** Added "/" . to allow for UNC Path
$path_parts = pathinfo("/" . cleardoubleslashes($zipfile));
$zippath = $path_parts["dirname"]; //The path of the zip file
$zipfilename = $path_parts["basename"]; //The name of the zip file
$extension = $path_parts["extension"]; //The extension of the file
//If no file, error
if (empty($zipfilename)) {
return false;
}
//If no extension, error
if (empty($extension)) { return false; } }
//Clear $zipfile
// EDIT *** CHRIS SHEARING *** Added "/" . to allow for UNC Path
$zipfile = "/" . cleardoubleslashes($zipfile);
//Check zipfile exists
if (!file_exists($zipfile)) {
return false;
}
//If no destination, passed let's go with the same directory
if (empty($destination)) {
$destination = $zippath;
}
//Clear $destination
// EDIT *** CHRIS SHEARING *** Added "/" . to allow for UNC Path
$destpath = rtrim("/" . cleardoubleslashes($destination), "/");
//Check destination path exists
if (!is_dir($destpath)) { return false; } }
//Check destination path is writable. TODO!!
//Everything is ready:
// -$zippath is the path where the zip file resides (dir)
// -$zipfilename is the name of the zip file (without path)
// -$destpath is the destination path where the zip file will uncompressed (dir)
print_object($zippath); //Debug
print_object($zipfilename); //Debug
print_object($destpath); //Debug
$list = null;
if (empty($CFG->unzip)) { // Use built-in php-based unzip function
include_once("$CFG->libdir/pclzip/pclzip.lib.php");
// EDIT *** CHRIS SHEARING *** Added "/" . to allow for UNC Path
$archive = new PclZip("/" . cleardoubleslashes("$zippath/$zipfilename"));
if (!$list = $archive->extract(PCLZIP_OPT_PATH, $destpath,
PCLZIP_CB_PRE_EXTRACT, 'unzip_cleanfilename',
PCLZIP_OPT_EXTRACT_DIR_RESTRICTION, $destpath)) {
notice($archive->errorInfo(true));
return false;
}
} else { // Use external unzip program
$separator = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? ' &' : ' ;';
$redirection = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? '' : ' 2>&1';
$command = 'cd '.escapeshellarg($zippath).$separator.
escapeshellarg($CFG->unzip).' -o '.
escapeshellarg(cleardoubleslashes("$zippath/$zipfilename")).' -d '.
escapeshellarg($destpath).$redirection;
//All converted to backslashes in WIN
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$command = str_replace('/','\\',$command);
}
Exec($command,$list);
}
//Display some info about the unzip execution
if ($showstatus) {
unzip_show_status($list,$destpath);
}
return true;
}
function unzip_cleanfilename ($p_event, &$p_header) {
//This function is used as callback in unzip_file() function
//to clean illegal characters for given platform and to prevent directory traversal.
//Produces the same result as info-zip unzip.
print_object($p_header); //Debug
print_object($p_event); //Debug
$p_header['filename'] = ereg_replace('[[:cntrl:]]', '', $p_header['filename']); //strip control chars first!
$p_header['filename'] = ereg_replace('\.\.+', '', $p_header['filename']); //directory traversal protection
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$p_header['filename'] = ereg_replace('[:*"?<>|]', '_', $p_header['filename']); //replace illegal chars
$p_header['filename'] = ereg_replace('^([a-zA-Z])_', '\1:', $p_header['filename']); //repair drive letter
} else {
//Add filtering for other systems here
// BSD: none (tested)
// Linux: ??
// MacosX: ??
}
// EDIT *** CHRIS SHEARING *** Added "/" . to allow for UNC Path
$p_header['filename'] = "/" . cleardoubleslashes($p_header['filename']); //normalize the slashes/backslashes
print_object($p_header); //Debug
print_object($p_event); //Debug
return 1;
}
case "listzip" in files/index.php (to fix the listzip option)
case "listzip":
html_header($course, $wdir);
if (($file != '') and confirm_sesskey()) {
$strname = get_string("name");
$strsize = get_string("size");
$strmodified = get_string("modified");
$strok = get_string("ok");
$strlistfiles = get_string("listfiles", "", $file);
echo "<p align=\"center\">$strlistfiles:</p>";
$file = basename($file);
include_once("$CFG->libdir/pclzip/pclzip.lib.php");
// EDIT *** CHRIS SHEARING *** Added "/" . to allow for UNC Path
$archive = new PclZip("/" . cleardoubleslashes("$basedir$wdir/$file"));
if (!$list = $archive->listContent(cleardoubleslashes("$basedir$wdir"))) {
notify($archive->errorInfo(true));
} else {
echo "<table cellpadding=\"4\" cellspacing=\"2\" border=\"0\" width=\"640\" class=\"files\">";
echo "<tr class=\"file\"><th align=\"left\" class=\"header name\" scope=\"col\">$strname</th><th align=\"right\" class=\"header size\" scope=\"col\">$strsize</th><th align=\"right\" class=\"header date\" scope=\"col\">$strmodified</th></tr>";
foreach ($list as $item) {
echo "<tr>";
print_cell("left", s($item['filename']), 'name');
if (! $item['folder']) {
print_cell("right", display_size($item['size']), 'size');
} else {
echo "<td> </td>";
}
$filedate = userdate($item['mtime'], get_string("strftimedatetime"));
print_cell("right", $filedate, 'date');
echo "</tr>";
}
echo "</table>";
}
echo "<br /><center><form action=\"index.php\" method=\"get\">";
echo "<div>";
echo ' <input type="hidden" name="choose" value="'.$choose.'" />';
echo " <input type=\"hidden\" name=\"id\" value=\"$id\" />";
echo " <input type=\"hidden\" name=\"wdir\" value=\"$wdir\" />";
echo " <input type=\"hidden\" name=\"action\" value=\"cancel\" />";
echo " <input type=\"submit\" value=\"$strok\" />";
echo "</div>";
echo "</form>";
echo "</center>";
} else {
displaydir($wdir);
}
html_footer();
break;
function zip_files in moodlelib.php and function PclZipUtilPathReduction($p_dir) in pclzip.lib.php (I think this is actually an external library, and not a moodle one though, not sure if we can edit this!) (to fix zip creation)
function zip_files ($originalfiles, $destination) {
//Zip an array of files/dirs to a destination zip file
//Both parameters must be FULL paths to the files/dirs
global $CFG;
//Extract everything from destination
$path_parts = pathinfo(cleardoubleslashes($destination));
$destpath = $path_parts["dirname"]; //The path of the zip file
$destfilename = $path_parts["basename"]; //The name of the zip file
$extension = $path_parts["extension"]; //The extension of the file
//If no file, error
if (empty($destfilename)) {
return false;
}
//If no extension, add it
if (empty($extension)) {
$extension = 'zip';
$destfilename = $destfilename.'.'.$extension;
}
//Check destination path exists
if (!is_dir($destpath)) { return false; } }
//Check destination path is writable. TODO!!
//Clean destination filename
$destfilename = clean_filename($destfilename);
//Now check and prepare every file
$files = array();
$origpath = NULL;
foreach ($originalfiles as $file) { //Iterate over each file
//Check for every file
// EDIT *** CHRIS SHEARING *** Added "/" . to allow for UNC Path
$tempfile = "/" . cleardoubleslashes($file); // no doubleslashes!
//Calculate the base path for all files if it isn't set
if ($origpath === NULL) {
$origpath = rtrim(cleardoubleslashes(dirname($tempfile)), "/");
}
//See if the file is readable
if (!is_readable($tempfile)) { //Is readable
continue;
}
//See if the file/dir is in the same directory than the rest
if (rtrim(cleardoubleslashes(dirname($tempfile)), "/") != $origpath) {
continue;
}
//Add the file to the array
$files[] = $tempfile;
}
//Everything is ready:
// -$origpath is the path where ALL the files to be compressed reside (dir).
// -$destpath is the destination path where the zip file will go (dir).
// -$files is an array of files/dirs to compress (fullpath)
// -$destfilename is the name of the zip file (without path)
// print_object($files); //Debug
if (empty($CFG->zip)) { // Use built-in php-based zip function
include_once("$CFG->libdir/pclzip/pclzip.lib.php");
//rewrite filenames because the old method with PCLZIP_OPT_REMOVE_PATH does not work under win32
$zipfiles = array();
$start = strlen($origpath)+1;
foreach($files as $file) {
$tf = array();
$tf[PCLZIP_ATT_FILE_NAME] = $file;
$tf[PCLZIP_ATT_FILE_NEW_FULL_NAME] = substr($file, $start);
$zipfiles[] = $tf;
}
//create the archive
// EDIT *** CHRIS SHEARING *** Added "/" . to allow for UNC Path
$archive = new PclZip("/" . cleardoubleslashes("$destpath/$destfilename"));
if (($list = $archive->create($zipfiles) == 0)) {
notice($archive->errorInfo(true));
return false;
}
} else { // Use external zip program
$filestozip = "";
foreach ($files as $filetozip) {
$filestozip .= escapeshellarg(basename($filetozip));
$filestozip .= " ";
}
//Construct the command
$separator = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? ' &' : ' ;';
$command = 'cd '.escapeshellarg($origpath).$separator.
escapeshellarg($CFG->zip).' -r '.
escapeshellarg(cleardoubleslashes("$destpath/$destfilename")).' '.$filestozip;
//All converted to backslashes in WIN
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { $command = str_replace('/','\',$command); } }
Exec($command);
}
return true;
}
Function PclZipUtilPathReduction($p_dir) in pclzip.lib.php
function PclZipUtilPathReduction($p_dir)
{
//-(MAGIC-PclTrace)-//PclTraceFctStart(_FILE, __LINE_, "PclZipUtilPathReduction", "dir='$p_dir'");
$v_result = "";
// ----- Look for not empty path
if ($p_dir != "") {
// ----- Explode path by directory names
$v_list = explode("/", $p_dir);
// ----- Study directories from last to first
$v_skip = 0;
for ($i=sizeof($v_list)1; $i>=0; $i-) {
// ----- Look for current path
if ($v_list[$i] == ".") {
// ----- Ignore this directory
// Should be the first $i=0, but no check is done
}
else if ($v_list[$i] == "..") {
$v_skip++;
}
else if ($v_list[$i] == "") {
// ----- First '/' i.e. root slash
if ($i == 0) {
// EDIT *** CHRIS SHEARING *** Added "/" . to allow for UNC Path
$v_result = "//".$v_result;
if ($v_skip > 0) {
// ----- It is an invalid path, so the path is not modified
// TBC
$v_result = $p_dir;
//--(MAGIC-PclTrace)--//PclTraceFctMessage(__FILE__, __LINE__, 3, "Invalid path is unchanged");
$v_skip = 0;
}
}
// ----- Last '/' i.e. indicates a directory
else if ($i == (sizeof($v_list)-1)) {
$v_result = $v_list[$i];
}
// ----- Double '/' inside the path
else {
// ----- Ignore only the double '//' in path,
// but not the first and last '/'
}
}
else {
// ----- Look for item to skip
if ($v_skip > 0) {
$v_skip--;
}
else {
$v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?"/".$v_result:"");
}
}
}
// ----- Look for skip
if ($v_skip > 0) {
while ($v_skip > 0) {
$v_result = '../'.$v_result;
$v_skip--;
}
}
}
// ----- Return
//-(MAGIC-PclTrace)-//PclTraceFctEnd(_FILE, __LINE_, $v_result);
return $v_result;
}
Hi,
When i'm trying to unzip (restore) a course i get the following error:
Course restore: backup-3070_012-20080227-0937.zip
PCLZIP_ERR_DIRECTORY_RESTRICTION (-21) : Filename is outside PCLZIP_OPT_EXTRACT_DIR_RESTRICTION
When i've added your code above into moodlelib.php to fix this problem i got the following error;
Parse error: parse error in vol1:/WEB/halo19at/lib/moodlelib.php on line 7221
I'm using Moodle v1.9Beta4 version. Upgraded from Mooodle 1.65
Awaiting fix for this issue.
Many Thanks
Umar
- Creating temporary structures
- Deleting old data
- Copying zip file
- Unzipping backup
PCLZIP_ERR_DIRECTORY_RESTRICTION (-21) : Filename is outside PCLZIP_OPT_EXTRACT_DIR_RESTRICTION
When i've added your code above into moodlelib.php to fix this problem i got the following error; Parse error: parse error in vol1:/WEB/halo19at/lib/moodlelib.php on line 7221 I'm using Moodle v1.9Beta4 version. Upgraded from Mooodle 1.65 Awaiting fix for this issue. Many Thanks Umar