diff --git a/htdocs/core/actions_linkedfiles.inc.php b/htdocs/core/actions_linkedfiles.inc.php index 0e0c444af33..7f938ba9b6c 100644 --- a/htdocs/core/actions_linkedfiles.inc.php +++ b/htdocs/core/actions_linkedfiles.inc.php @@ -182,37 +182,70 @@ elseif ($action == 'renamefile' && GETPOST('renamefilesave','alpha')) $filenamefrom=dol_sanitizeFileName(GETPOST('renamefilefrom','alpha'), '_', 0); // Do not remove accents $filenameto=dol_sanitizeFileName(GETPOST('renamefileto','alpha'), '_', 0); // Do not remove accents - // Security: - // Disallow file with some extensions. We rename them. - // Because if we put the documents directory into a directory inside web root (very bad), this allows to execute on demand arbitrary code. - if (preg_match('/\.htm|\.html|\.php|\.pl|\.cgi$/i',$filenameto) && empty($conf->global->MAIN_DOCUMENT_IS_OUTSIDE_WEBROOT_SO_NOEXE_NOT_REQUIRED)) + if ($filenamefrom != $filenameto) { - $filenameto.= '.noexe'; - } + // Security: + // Disallow file with some extensions. We rename them. + // Because if we put the documents directory into a directory inside web root (very bad), this allows to execute on demand arbitrary code. + if (preg_match('/\.htm|\.html|\.php|\.pl|\.cgi$/i',$filenameto) && empty($conf->global->MAIN_DOCUMENT_IS_OUTSIDE_WEBROOT_SO_NOEXE_NOT_REQUIRED)) + { + $filenameto.= '.noexe'; + } - if ($filenamefrom && $filenameto) - { - $srcpath = $upload_dir.'/'.$filenamefrom; - $destpath = $upload_dir.'/'.$filenameto; + if ($filenamefrom && $filenameto) + { + $srcpath = $upload_dir.'/'.$filenamefrom; + $destpath = $upload_dir.'/'.$filenameto; - $result = dol_move($srcpath, $destpath); - if ($result) - { - if ($object->id) - { - $object->addThumbs($destpath); - } + $result = dol_move($srcpath, $destpath); + if ($result) + { + if ($object->id) + { + $object->addThumbs($destpath); + } - // TODO Add revert function of addThumbs to remove for old name - //$object->delThumbs($srcpath); + // TODO Add revert function of addThumbs to remove for old name + //$object->delThumbs($srcpath); - setEventMessages($langs->trans("FileRenamed"), null); - } - else - { - $langs->load("errors"); // key must be loaded because we can't rely on loading during output, we need var substitution to be done now. - setEventMessages($langs->trans("ErrorFailToRenameFile", $filenamefrom, $filenameto), null, 'errors'); - } + setEventMessages($langs->trans("FileRenamed"), null); + } + else + { + $langs->load("errors"); // key must be loaded because we can't rely on loading during output, we need var substitution to be done now. + setEventMessages($langs->trans("ErrorFailToRenameFile", $filenamefrom, $filenameto), null, 'errors'); + } + } } } + + // Update properties in ECM table + if (GETPOST('ecmfileid', 'int') > 0) + { + $shareenabled = GETPOST('shareenabled', 'alpha'); + + include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php'; + $ecmfile=new EcmFiles($db); + $result = $ecmfile->fetch(GETPOST('ecmfileid', 'int')); + if ($result > 0) + { + if ($shareenabled) + { + if (empty($ecmfile->share)) + { + require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php'; + $ecmfile->share = getRandomPassword(true); + } + } + else + { + $ecmfile->share = ''; + } + $result = $ecmfile->update($user); + if ($result < 0) + { + setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings'); + } + } + } } diff --git a/htdocs/core/class/html.formfile.class.php b/htdocs/core/class/html.formfile.class.php index 4bff96700d5..ac5bfdcabb7 100644 --- a/htdocs/core/class/html.formfile.class.php +++ b/htdocs/core/class/html.formfile.class.php @@ -938,7 +938,7 @@ class FormFile * @param string $url Full url to use for click links ('' = autodetect) * @param int $showrelpart 0=Show only filename (default), 1=Show first level 1 dir * @param int $permtoeditline Permission to edit document line (You must provide a value, -1 is deprecated and must not be used any more) - * @param string $upload_dir Full path directory so we can know dir relative to MAIN_DATA_ROOT. Fill this if you want to complete file data with database indexes. + * @param string $upload_dir Full path directory so we can know dir relative to MAIN_DATA_ROOT. Fill this to complete file data with database indexes. * @param string $sortfield Sort field ('name', 'size', 'position', ...) * @param string $sortorder Sort order ('ASC' or 'DESC') * @param int $disablemove 1=Disable move button, 0=Position move is possible. @@ -951,6 +951,7 @@ class FormFile global $user, $conf, $langs, $hookmanager; global $bc,$bcdd; global $sortfield, $sortorder, $maxheightmini; + global $dolibarr_main_url_root; // Define relative path used to store the file if (empty($relativepath)) @@ -1038,6 +1039,7 @@ class FormFile print ''; if (empty($useinecm)) print ''; print ''; + print ''; if (! $disablemove) print ''; print "\n"; } @@ -1047,7 +1049,8 @@ class FormFile print_liste_field_titre('Documents2',$url,"name","",$param,'align="left"',$sortfield,$sortorder); print_liste_field_titre('Size',$url,"size","",$param,'align="right"',$sortfield,$sortorder); print_liste_field_titre('Date',$url,"date","",$param,'align="center"',$sortfield,$sortorder); - if (empty($useinecm)) print_liste_field_titre('',$url,"","",$param,'align="center"'); + if (empty($useinecm)) print_liste_field_titre('',$url,"","",$param,'align="center"'); // Preview + print_liste_field_titre(''); print_liste_field_titre(''); if (! $disablemove) print_liste_field_titre(''); print "\n"; @@ -1063,7 +1066,6 @@ class FormFile //var_dump($sortfield); $filearray=dol_sort_array($filearray, $sortfield, $sortorder); } - //var_dump($filearray); } $nboffiles=count($filearray); @@ -1146,6 +1148,48 @@ class FormFile else print ' '; print ''; } + + // Hash of file (only if we are in a mode where a scan of dir were done and we have id of file in ECM table) + print ''; + if ($relativedir && $filearray[$key]['rowid'] > 0) + { + if ($editline) + { + print $langs->trans("FileSharedViaALink").' '; + print ' '; + } + else + { + if ($file['share']) + { + // Define $urlwithroot + $urlwithouturlroot=preg_replace('/'.preg_quote(DOL_URL_ROOT,'/').'$/i','',trim($dolibarr_main_url_root)); + $urlwithroot=$urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file + //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current + + //print ''.$langs->trans("Hash").' : '.$file['share'].''; + $forcedownload=0; + $paramlink=''; + if (! empty($file['share'])) $paramlink.=($paramlink?'&':'').'hashp='.$file['share']; // Hash for public share + if ($forcedownload) $paramlink.=($paramlink?'&':'').'attachment=1'; + + $fulllink=$urlwithroot.'/document.php'.($paramlink?'?'.$paramlink:''); + //if (! empty($object->ref)) $fulllink.='&hashn='.$object->ref; // Hash of file path + //elseif (! empty($object->label)) $fulllink.='&hashc='.$object->label; // Hash of file content + + print img_picto($langs->trans("FileSharedViaALink"),'object_globe.png').' '; + print ''; + //print ' '.$langs->trans("Download").''; // No target here + } + else + { + //print ''.$langs->trans("FileNotShared").''; + } + } + } + print ''; + + // Actions buttons if (! $editline) { // Delete or view link @@ -1215,6 +1259,7 @@ class FormFile else { print ''; + print ''; print ''; print ''; print ''; @@ -1227,9 +1272,9 @@ class FormFile } if ($nboffiles == 0) { - $colspan=(empty($useinecm)?'5':'5'); - if (empty($disablemove)) $colspan++; - print ''; + $colspan=(empty($useinecm)?'6':'6'); + if (empty($disablemove)) $colspan++; // 6 columns or 7 + print ''; if (empty($textifempty)) print $langs->trans("NoFileFound"); else print $textifempty; print ''; @@ -1249,6 +1294,8 @@ class FormFile print ''; } + print ajax_autoselect('downloadlink'); + return $nboffiles; } } diff --git a/htdocs/core/lib/files.lib.php b/htdocs/core/lib/files.lib.php index 206d1bb8099..b53e588c275 100644 --- a/htdocs/core/lib/files.lib.php +++ b/htdocs/core/lib/files.lib.php @@ -225,7 +225,8 @@ function dol_dir_list_in_database($path, $filter="", $excludefilter=null, $sortc { global $conf, $db; - $sql=" SELECT rowid, label, entity, filename, filepath, fullpath_orig, keywords, cover, gen_or_uploaded, extraparams, date_c, date_m, fk_user_c, fk_user_m, acl, position"; + $sql =" SELECT rowid, label, entity, filename, filepath, fullpath_orig, keywords, cover, gen_or_uploaded, extraparams, date_c, date_m, fk_user_c, fk_user_m,"; + $sql.=" acl, position, share"; if ($mode) $sql.=", description"; $sql.=" FROM ".MAIN_DB_PREFIX."ecm_files"; $sql.=" WHERE filepath = '".$db->escape($path)."'"; @@ -258,7 +259,8 @@ function dol_dir_list_in_database($path, $filter="", $excludefilter=null, $sortc "keywords" => $obj->keywords, "cover" => $obj->cover, "position" => (int) $obj->position, - "acl" => $obj->acl + "acl" => $obj->acl, + "share" => $obj->share ); } $i++; @@ -318,6 +320,7 @@ function completeFileArrayWithDatabaseInfo(&$filearray, $relativedir) $filearray[$key]['acl']=$filearrayindatabase[$key2]['acl']; $filearray[$key]['rowid']=$filearrayindatabase[$key2]['rowid']; $filearray[$key]['label']=$filearrayindatabase[$key2]['label']; + $filearray[$key]['share']=$filearrayindatabase[$key2]['share']; $found=1; break; } diff --git a/htdocs/document.php b/htdocs/document.php index 1c722e4cb55..50f4a61186d 100644 --- a/htdocs/document.php +++ b/htdocs/document.php @@ -25,9 +25,9 @@ * \file htdocs/document.php * \brief Wrapper to download data files * \remarks Call of this wrapper is made with URL: - * document.php?modulepart=repfichierconcerne&file=relativepathoffile - * document.php?modulepart=logs&file=dolibarr.log - * document.php?modulepart=logs&hashp=sharekey + * DOL_URL_ROOT.'/document.php?modulepart=repfichierconcerne&file=relativepathoffile' + * DOL_URL_ROOT.'/document.php?modulepart=logs&file=dolibarr.log' + * DOL_URL_ROOT.'/document.php?hashp=sharekey' */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); // Not disabled cause need to load personalized language @@ -36,9 +36,9 @@ //if (! defined('NOREQUIRETRAN')) define('NOREQUIRETRAN','1'); //if (! defined('NOCSRFCHECK')) define('NOCSRFCHECK','1'); if (! defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL','1'); -//if (! defined('NOREQUIREMENU')) define('NOREQUIREMENU','1'); -//if (! defined('NOREQUIREHTML')) define('NOREQUIREHTML','1'); -//if (! defined('NOREQUIREAJAX')) define('NOREQUIREAJAX','1'); +if (! defined('NOREQUIREMENU')) define('NOREQUIREMENU','1'); +if (! defined('NOREQUIREHTML')) define('NOREQUIREHTML','1'); +if (! defined('NOREQUIREAJAX')) define('NOREQUIREAJAX','1'); //if (! defined('NOREQUIREHOOK')) define('NOREQUIREHOOK','1'); // Disable "main.inc.php" hooks // For bittorent link, we don't need to load/check we are into a login session if (isset($_GET["modulepart"]) && $_GET["modulepart"] == 'bittorrent' && ! defined("NOLOGIN")) @@ -58,9 +58,6 @@ if ((isset($_GET["modulepart"]) && $_GET["modulepart"] == 'medias') && ! defined define("NOLOGIN",1); define("NOCSRFCHECK",1); // We accept to go on this page from external web site. } -if (! defined('NOREQUIREMENU')) define('NOREQUIREMENU','1'); -if (! defined('NOREQUIREHTML')) define('NOREQUIREHTML','1'); -if (! defined('NOREQUIREAJAX')) define('NOREQUIREAJAX','1'); /** * Header empty @@ -75,7 +72,6 @@ function llxHeader() { } */ function llxFooter() { } - require 'main.inc.php'; // Load $user and permissions require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; @@ -103,21 +99,17 @@ if (in_array($modulepart, array('facture_paiement','unpaid'))) /* - * Action + * Actions */ // None + /* * View */ -// Define mime type -$type = 'application/octet-stream'; -if (GETPOST('type','alpha')) $type=GETPOST('type','alpha'); -else $type=dol_mimetype($original_file); - // Define attachment (attachment=true to force choice popup 'open'/'save as') $attachment = true; if (preg_match('/\.(html|htm)$/i',$original_file)) $attachment = false; @@ -160,6 +152,10 @@ if (! empty($hashp)) } } +// Define mime type +$type = 'application/octet-stream'; +if (GETPOST('type','alpha')) $type=GETPOST('type','alpha'); +else $type=dol_mimetype($original_file); // Security: Delete string ../ into $original_file $original_file = str_replace("../","/", $original_file); @@ -252,9 +248,6 @@ header('Content-Length: ' . dol_filesize($fullpath_original_file)); header('Cache-Control: Public, must-revalidate'); header('Pragma: public'); -//ob_clean(); -//flush(); - readfile($fullpath_original_file_osencoded); if (is_object($db)) $db->close(); diff --git a/htdocs/ecm/file_card.php b/htdocs/ecm/file_card.php index 8c62cbe18f2..e0926e0f777 100644 --- a/htdocs/ecm/file_card.php +++ b/htdocs/ecm/file_card.php @@ -341,13 +341,9 @@ if (! empty($object->share)) { if ($action != 'edit') { - $modulepart='ecm'; $forcedownload=0; $paramlink=''; - //if (! empty($modulepart)) $paramlink.=($paramlink?'&':'').'modulepart='.$modulepart; // For sharing with hash (so public files), modulepart is not required. - //if (! empty($object->entity)) $paramlink.='&entity='.$object->entity; // For sharing with hash (so public files), entity is not required. - //$paramlink.=($paramlink?'&':'').'file='.urlencode($filepath); // No need of name of file for public link, we will use the hash if (! empty($object->share)) $paramlink.=($paramlink?'&':'').'hashp='.$object->share; // Hash for public share if ($forcedownload) $paramlink.=($paramlink?'&':'').'attachment=1'; diff --git a/htdocs/viewimage.php b/htdocs/viewimage.php index 3f7ddbcd1cb..3e2b59ae404 100644 --- a/htdocs/viewimage.php +++ b/htdocs/viewimage.php @@ -20,8 +20,10 @@ /** * \file htdocs/viewimage.php - * \brief Wrapper to show images into Dolibarr screens - * \remarks Call to wrapper is '' + * \brief Wrapper to show images into Dolibarr screens. + * \remarks Call to wrapper is : + * DOL_URL_ROOT.'/viewimage.php?modulepart=diroffile&file=relativepathofofile&cache=0 + * DOL_URL_ROOT.'/viewimage.php?hashp=sharekey */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); // Not disabled cause need to load personalized language @@ -35,7 +37,16 @@ if (! defined('NOREQUIREHTML')) define('NOREQUIREHTML','1'); if (! defined('NOREQUIREAJAX')) define('NOREQUIREAJAX','1'); if (! defined('NOREQUIREHOOK')) define('NOREQUIREHOOK','1'); // Disable "main.inc.php" hooks // Some value of modulepart can be used to get resources that are public so no login are required. -if ((isset($_GET["modulepart"]) && ($_GET["modulepart"] == 'mycompany' || $_GET["modulepart"] == 'companylogo')) && ! defined("NOLOGIN")) define("NOLOGIN",'1'); +if ((isset($_GET["modulepart"]) && ($_GET["modulepart"] == 'mycompany' || $_GET["modulepart"] == 'companylogo')) && ! defined("NOLOGIN")) +{ + define("NOLOGIN",'1'); +} +// For direct external download link, we don't need to load/check we are into a login session +if (isset($_GET["hashp"]) && ! defined("NOLOGIN")) +{ + define("NOLOGIN",1); +} +// Some value of modulepart can be used to get resources that are public so no login are required. if ((isset($_GET["modulepart"]) && $_GET["modulepart"] == 'medias') && ! defined("NOLOGIN")) { define("NOLOGIN",'1'); @@ -57,18 +68,20 @@ function llxHeader() { } */ function llxFooter() { } -require 'main.inc.php'; +require 'main.inc.php'; // Load $user and permissions require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; $action=GETPOST('action','alpha'); -$original_file=GETPOST("file",'alpha'); +$original_file=GETPOST('file','alpha'); // Do not use urldecode here ($_GET are already decoded by PHP). +$hashp=GETPOST('hashp','aZ09'); $modulepart=GETPOST('modulepart','alpha'); -$urlsource=GETPOST("urlsource",'alpha'); +$urlsource=GETPOST('urlsource','alpha'); $entity=GETPOST('entity','int')?GETPOST('entity','int'):$conf->entity; // Security check -if (empty($modulepart)) accessforbidden('Bad value for parameter modulepart'); +if (empty($modulepart) && empty($hashp)) accessforbidden('Bad link. Bad value for parameter modulepart',0,0,1); +if (empty($original_file) && empty($hashp)) accessforbidden('Bad link. Missing identification to find file (original_file or hashp)',0,0,1); if ($modulepart == 'fckeditor') $modulepart='medias'; // For backward compatibility @@ -97,9 +110,45 @@ if (GETPOST("cache",'alpha')) //print $dolibarr_nocache; exit; } +// If we have a hash public (hashp), we guess the original_file. +if (! empty($hashp)) +{ + include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php'; + $ecmfile=new EcmFiles($db); + $result = $ecmfile->fetch(0, '', '', '', $hashp); + if ($result > 0) + { + $tmp = explode('/', $ecmfile->filepath, 2); // $ecmfile->filepath is relative to document directory + $moduleparttocheck = $tmp[0]; + if ($modulepart) // Not required for link using public hashp + { + if ($moduleparttocheck == $modulepart) + { + // We remove first level of directory + $original_file = (($tmp[1]?$tmp[1].'/':'').$ecmfile->filename); // this is relative to module dir + //var_dump($original_file); exit; + } + else + { + accessforbidden('Bad link. File is from another module part.',0,0,1); + } + } + else + { + $modulepart = $moduleparttocheck; + $original_file = (($tmp[1]?$tmp[1].'/':'').$ecmfile->filename); // this is relative to module dir + } + } + else + { + $langs->load("errors"); + accessforbidden($langs->trans("ErrorFileNotFoundWithSharedLink"),0,0,1); + } +} + // Define mime type $type = 'application/octet-stream'; -if (! empty($_GET["type"])) $type=$_GET["type"]; +if (GETPOST('type','alpha')) $type=GETPOST('type','alpha'); else $type=dol_mimetype($original_file); // Security: Delete string ../ into $original_file @@ -110,16 +159,49 @@ $refname=basename(dirname($original_file)."/"); // Security check if (empty($modulepart)) accessforbidden('Bad value for parameter modulepart'); -$check_access = dol_check_secure_access_document($modulepart,$original_file,$entity,$refname); + +$check_access = dol_check_secure_access_document($modulepart, $original_file, $entity, $refname); $accessallowed = $check_access['accessallowed']; $sqlprotectagainstexternals = $check_access['sqlprotectagainstexternals']; -$fullpath_original_file = $check_access['original_file']; +$fullpath_original_file = $check_access['original_file']; // $fullpath_original_file is now a full path name + +if (! empty($hashp)) +{ + $accessallowed = 1; // When using hashp, link is public so we force $accessallowed + $sqlprotectagainstexternals = ''; +} +else +{ + // Basic protection (against external users only) + if ($user->societe_id > 0) + { + if ($sqlprotectagainstexternals) + { + $resql = $db->query($sqlprotectagainstexternals); + if ($resql) + { + $num=$db->num_rows($resql); + $i=0; + while ($i < $num) + { + $obj = $db->fetch_object($resql); + if ($user->societe_id != $obj->fk_soc) + { + $accessallowed=0; + break; + } + $i++; + } + } + } + } +} // Security: // Limit access if permissions are wrong if (! $accessallowed) { - accessforbidden(); + accessforbidden(); } // Security: @@ -128,7 +210,7 @@ if (preg_match('/\.\./',$fullpath_original_file) || preg_match('/[<>|]/',$fullpa { dol_syslog("Refused to deliver file ".$fullpath_original_file); print "ErrorFileNameInvalid: ".$original_file; - exit; + exit; } @@ -174,8 +256,10 @@ else // Open and return file { clearstatcache(); + $filename = basename($fullpath_original_file); + // Output files on browser - dol_syslog("viewimage.php return file $fullpath_original_file content-type=$type"); + dol_syslog("viewimage.php return file $fullpath_original_file filename=$filename content-type=$type"); // This test is to avoid error images when image is not available (for example thumbs). if (! dol_is_file($fullpath_original_file) && empty($_GET["noalt"])) @@ -186,7 +270,7 @@ else // Open and return file exit;*/ } - // Les drois sont ok et fichier trouve + // Permissions are ok and file found, so we return it if ($type) { top_httphead($type);