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);