More powerfull dol_move function.

This commit is contained in:
Laurent Destailleur 2017-05-21 14:06:43 +02:00
parent 5992175b68
commit 52b5a8ece7
4 changed files with 160 additions and 91 deletions

View File

@ -65,8 +65,8 @@ class Documents extends DolibarrApi
/**
* Push a file.
* Test sample 1: { "filename": "mynewfile.txt", "modulepart": "facture", "ref": "FA1701-001", "subdir": "", "filecontent": "content text", "fileencoding": "" }.
* Test sample 2: { "filename": "mynewfile.txt", "modulepart": "medias", "ref": "", "subdir": "mysubdir1/mysubdir2", "filecontent": "content text", "fileencoding": "" }.
* Test sample 1: { "filename": "mynewfile.txt", "modulepart": "facture", "ref": "FA1701-001", "subdir": "", "filecontent": "content text", "fileencoding": "", "overwriteifexists": "0" }.
* Test sample 2: { "filename": "mynewfile.txt", "modulepart": "medias", "ref": "", "subdir": "mysubdir1/mysubdir2", "filecontent": "content text", "fileencoding": "", "overwriteifexists": "0" }.
*
* @param string $filename Name of file to create ('FA1705-0123')
* @param string $modulepart Name of module or area concerned by file upload ('facture', ...)
@ -74,10 +74,12 @@ class Documents extends DolibarrApi
* @param string $subdir Subdirectory (Only if ref not provided)
* @param string $filecontent File content (string with file content. An empty file will be created if this parameter is not provided)
* @param string $fileencoding File encoding (''=no encoding, 'base64'=Base 64)
* @param int $overwriteifexists Overwrite file if exists (1 by default)
* @return bool State of copy
* @throws RestException
*/
public function post($filename, $modulepart, $ref='', $subdir='', $filecontent='', $fileencoding='') {
public function post($filename, $modulepart, $ref='', $subdir='', $filecontent='', $fileencoding='', $overwriteifexists=0)
{
global $db, $conf;
/*var_dump($modulepart);
@ -138,30 +140,34 @@ class Documents extends DolibarrApi
$upload_dir = dol_sanitizePathName($upload_dir);
$destfile = $upload_dir . '/' . $original_file;
$destfile = $upload_dir . '/' . $original_file;
$destfiletmp = DOL_DATA_ROOT.'/admin/temp/' . $original_file;
dol_delete_file($destfiletmp);
if (!dol_is_dir($upload_dir)) {
throw new RestException(401,'Directory not exists : '.$upload_dir);
}
if (dol_is_file($destfile))
if (! $overwriteifexists && dol_is_file($destfile))
{
throw new RestException(500, "File with name '".$original_file."' already exists.");
}
$fhandle = fopen($destfile, 'w');
$fhandle = @fopen($destfiletmp, 'w');
if ($fhandle)
{
$nbofbyteswrote = fwrite($fhandle, $newfilecontent);
fclose($fhandle);
@chmod($destfile, octdec($conf->global->MAIN_UMASK));
@chmod($destfiletmp, octdec($conf->global->MAIN_UMASK));
}
else
{
throw new RestException(500, 'Failed to open file for write');
throw new RestException(500, "Failed to open file '".$destfiletmp."' for write");
}
return true;
$result = dol_move($destfiletmp, $destfile, 0, $overwriteifexists, 1);
return $result;
}
/**

View File

@ -49,6 +49,7 @@ class AntiVir
/**
* Scan a file with antivirus.
* This function runs the command defined in setup. This antivirus command must return 0 if OK.
* Return also true (virus found) if file end with '.virus' (so we can make test safely).
*
* @param string $file File to scan
* @return int <0 if KO (-98 if error, -99 if virus), 0 if OK
@ -59,6 +60,12 @@ class AntiVir
$return = 0;
if (preg_match('/\.virus$/i', $file))
{
$this->errors='File has an extension saying file is a virus';
return -97;
}
$fullcommand=$this->getCliCommand($file);
//$fullcommand='"c:\Program Files (x86)\ClamWin\bin\clamscan.exe" --database="C:\Program Files (x86)\ClamWin\lib" "c:\temp\aaa.txt"';
$fullcommand.=' 2>&1'; // This is to get error output

View File

@ -648,15 +648,19 @@ function dolCopyDir($srcfile, $destfile, $newmask, $overwriteifexists)
/**
* Move a file into another name.
* This function differs from dol_move_uploaded_file, because it can be called in any context.
* Note:
* - This function differs from dol_move_uploaded_file, because it can be called in any context.
* - Database of files is updated.
* - Test on antivirus is done only if param testvirus is provided and an antivirus was set.
*
* @param string $srcfile Source file (can't be a directory. use native php @rename() to move a directory)
* @param string $destfile Destination file (can't be a directory. use native php @rename() to move a directory)
* @param integer $newmask Mask in octal string for new file (0 by default means $conf->global->MAIN_UMASK)
* @param int $overwriteifexists Overwrite file if exists (1 by default)
* @return boolean True if OK, false if KO
* @see dol_move_uploaded_file
*/
function dol_move($srcfile, $destfile, $newmask=0, $overwriteifexists=1)
function dol_move($srcfile, $destfile, $newmask=0, $overwriteifexists=1, $testvirus=0)
{
global $user, $db, $conf;
$result=false;
@ -676,6 +680,18 @@ function dol_move($srcfile, $destfile, $newmask=0, $overwriteifexists=1)
$newpathofsrcfile=dol_osencode($srcfile);
$newpathofdestfile=dol_osencode($destfile);
// Check virus
$testvirusarray=array();
if ($testvirus)
{
$testvirusarray=dolCheckVirus($newpathofsrcfile);
if (count($testvirusarray))
{
dol_syslog("files.lib.php::dol_move canceled because a virus was found into source file. we ignore the move request.", LOG_WARNING);
return false;
}
}
$result=@rename($newpathofsrcfile, $newpathofdestfile); // To see errors, remove @
if (! $result)
{
@ -703,9 +719,17 @@ function dol_move($srcfile, $destfile, $newmask=0, $overwriteifexists=1)
dol_syslog("Try to rename also entries in database for full relative path before = ".$rel_filetorenamebefore." after = ".$rel_filetorenameafter, LOG_DEBUG);
include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
$ecmfiletarget=new EcmFiles($db);
$resultecmtarget = $ecmfiletarget->fetch(0, '', $rel_filetorenameafter);
if ($resultecmtarget > 0) // An entry for target name already exists for target, we delete it, a new one will be created.
{
$ecmfiletarget->delete($user);
}
$ecmfile=new EcmFiles($db);
$result = $ecmfile->fetch(0, '', $rel_filetorenamebefore);
if ($result > 0) // If found
$resultecm = $ecmfile->fetch(0, '', $rel_filetorenamebefore);
if ($resultecm > 0) // If an entry was found for src file, we use it to move entry
{
$filename = basename($rel_filetorenameafter);
$rel_dir = dirname($rel_filetorenameafter);
@ -714,9 +738,9 @@ function dol_move($srcfile, $destfile, $newmask=0, $overwriteifexists=1)
$ecmfile->filepath = $rel_dir;
$ecmfile->filename = $filename;
$result = $ecmfile->update($user);
$resultecm = $ecmfile->update($user);
}
elseif ($result == 0) // If not found
elseif ($resultecm == 0) // If no entry were found for src files, create/update target file
{
$filename = basename($rel_filetorenameafter);
$rel_dir = dirname($rel_filetorenameafter);
@ -730,16 +754,19 @@ function dol_move($srcfile, $destfile, $newmask=0, $overwriteifexists=1)
$ecmfile->gen_or_uploaded = 'unknown';
$ecmfile->description = ''; // indexed content
$ecmfile->keyword = ''; // keyword content
$result = $ecmfile->create($user);
if ($result < 0)
$resultecm = $ecmfile->create($user);
if ($resultecm < 0)
{
setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
}
}
}
elseif ($result < 0)
elseif ($resultecm < 0)
{
setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
}
if ($resultecm > 0) $result=true;
else $result = false;
}
}
@ -769,10 +796,41 @@ function dol_unescapefile($filename)
return trim(basename($filename), ".\x00..\x20");
}
/**
* Check virus into a file
*
* @param string $src_file Source file to check
* @return array Array of errors or empty array if not virus found
*/
function dolCheckVirus($src_file)
{
global $conf;
if (! empty($conf->global->MAIN_ANTIVIRUS_COMMAND))
{
if (! class_exists('AntiVir')) {
require_once DOL_DOCUMENT_ROOT.'/core/class/antivir.class.php';
}
$antivir=new AntiVir($db);
$result = $antivir->dol_avscan_file($src_file);
if ($result < 0) // If virus or error, we stop here
{
$reterrors=$antivir->errors;
return $reterrors;
}
}
return array();
}
/**
* Make control on an uploaded file from an GUI page and move it to final destination.
* If there is errors (virus found, antivir in error, bad filename), file is not moved.
* Note: This function can be used only into a HTML page context. Use dol_move if you are outside.
* Note:
* - This function can be used only into a HTML page context. Use dol_move if you are outside.
* - Database of files is not updated.
* - Test on antivirus is always done (if antivirus set).
*
* @param string $src_file Source full path filename ($_FILES['field']['tmp_name'])
* @param string $dest_file Target full path filename ($_FILES['field']['name'])
@ -794,80 +852,78 @@ function dol_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disable
if (empty($nohook))
{
// If an upload error has been reported
if ($uploaderrorcode)
{
switch($uploaderrorcode)
{
case UPLOAD_ERR_INI_SIZE: // 1
return 'ErrorFileSizeTooLarge';
break;
case UPLOAD_ERR_FORM_SIZE: // 2
return 'ErrorFileSizeTooLarge';
break;
case UPLOAD_ERR_PARTIAL: // 3
return 'ErrorPartialFile';
break;
case UPLOAD_ERR_NO_TMP_DIR: //
return 'ErrorNoTmpDir';
break;
case UPLOAD_ERR_CANT_WRITE:
return 'ErrorFailedToWriteInDir';
break;
case UPLOAD_ERR_EXTENSION:
return 'ErrorUploadBlockedByAddon';
break;
default:
break;
}
}
// If we need to make a virus scan
if (empty($disablevirusscan) && file_exists($src_file) && ! empty($conf->global->MAIN_ANTIVIRUS_COMMAND))
{
if (! class_exists('AntiVir')) {
require_once DOL_DOCUMENT_ROOT.'/core/class/antivir.class.php';
}
$antivir=new AntiVir($db);
$result = $antivir->dol_avscan_file($src_file);
if ($result < 0) // If virus or error, we stop here
{
$reterrors=$antivir->errors;
dol_syslog('Files.lib::dol_move_uploaded_file File "'.$src_file.'" (target name "'.$dest_file.'") KO with antivirus: result='.$result.' errors='.join(',',$antivir->errors), LOG_WARNING);
return 'ErrorFileIsInfectedWithAVirus: '.join(',',$reterrors);
}
}
// Security:
// Disallow file with some extensions. We renamed them.
// Car si on a mis le rep documents dans un rep de la racine web (pas bien), cela permet d'executer du code a la demande.
if (preg_match('/\.htm|\.html|\.php|\.pl|\.cgi$/i',$dest_file) && empty($conf->global->MAIN_DOCUMENT_IS_OUTSIDE_WEBROOT_SO_NOEXE_NOT_REQUIRED))
{
$file_name.= '.noexe';
}
// Security:
// We refuse cache files/dirs, upload using .. and pipes into filenames.
if (preg_match('/^\./',$src_file) || preg_match('/\.\./',$src_file) || preg_match('/[<>|]/',$src_file))
{
dol_syslog("Refused to deliver file ".$src_file, LOG_WARNING);
return -1;
}
// Security:
// On interdit fichiers caches, remontees de repertoire ainsi que les pipe dans les noms de fichiers.
if (preg_match('/^\./',$dest_file) || preg_match('/\.\./',$dest_file) || preg_match('/[<>|]/',$dest_file))
{
dol_syslog("Refused to deliver file ".$dest_file, LOG_WARNING);
return -2;
}
$reshook=$hookmanager->initHooks(array('fileslib'));
$parameters=array('dest_file' => $dest_file, 'src_file' => $src_file, 'file_name' => $file_name, 'varfiles' => $varfiles, 'allowoverwrite' => $allowoverwrite);
$reshook=$hookmanager->executeHooks('moveUploadedFile', $parameters, $object);
}
if (empty($reshook))
{
// If an upload error has been reported
if ($uploaderrorcode)
{
switch($uploaderrorcode)
{
case UPLOAD_ERR_INI_SIZE: // 1
return 'ErrorFileSizeTooLarge';
break;
case UPLOAD_ERR_FORM_SIZE: // 2
return 'ErrorFileSizeTooLarge';
break;
case UPLOAD_ERR_PARTIAL: // 3
return 'ErrorPartialFile';
break;
case UPLOAD_ERR_NO_TMP_DIR: //
return 'ErrorNoTmpDir';
break;
case UPLOAD_ERR_CANT_WRITE:
return 'ErrorFailedToWriteInDir';
break;
case UPLOAD_ERR_EXTENSION:
return 'ErrorUploadBlockedByAddon';
break;
default:
break;
}
}
// If we need to make a virus scan
if (empty($disablevirusscan) && file_exists($src_file))
{
$checkvirusarray=dolCheckVirus($src_file);
if (count($checkvirusarray))
{
dol_syslog('Files.lib::dol_move_uploaded_file File "'.$src_file.'" (target name "'.$dest_file.'") KO with antivirus: result='.$result.' errors='.join(',',$checkvirusarray), LOG_WARNING);
return 'ErrorFileIsInfectedWithAVirus: '.join(',',$checkvirusarray);
}
}
// Security:
// Disallow file with some extensions. We renamed them.
// Car si on a mis le rep documents dans un rep de la racine web (pas bien), cela permet d'executer du code a la demande.
if (preg_match('/\.htm|\.html|\.php|\.pl|\.cgi$/i',$dest_file) && empty($conf->global->MAIN_DOCUMENT_IS_OUTSIDE_WEBROOT_SO_NOEXE_NOT_REQUIRED))
{
$file_name.= '.noexe';
}
// Security:
// We refuse cache files/dirs, upload using .. and pipes into filenames.
if (preg_match('/^\./',$src_file) || preg_match('/\.\./',$src_file) || preg_match('/[<>|]/',$src_file))
{
dol_syslog("Refused to deliver file ".$src_file, LOG_WARNING);
return -1;
}
// Security:
// On interdit fichiers caches, remontees de repertoire ainsi que les pipe dans les noms de fichiers.
if (preg_match('/^\./',$dest_file) || preg_match('/\.\./',$dest_file) || preg_match('/[<>|]/',$dest_file))
{
dol_syslog("Refused to deliver file ".$dest_file, LOG_WARNING);
return -2;
}
}
if ($reshook < 0) // At least one blocking error returned by one hook
{
$errmsg = join(',', $hookmanager->errors);

View File

@ -172,7 +172,7 @@ class RestAPIDocumentTest extends PHPUnit_Framework_TestCase
dol_mkdir(DOL_DATA_ROOT.'/medias/tmpphpunit/tmpphpunit2');
$data = array(
'filename'=>"mynewfilethatwillfails.txt",
'filename'=>"mynewfile.txt",
'modulepart'=>"medias",
'ref'=>"",
'subdir'=>"tmpphpunit/tmpphpunit2",