dolibarr 23.0.3
viewimage.controller.class.php
Go to the documentation of this file.
1<?php
2/*
3 * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
4 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
26require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
27
28
33{
37 public $action;
38
42 public $entity;
43
47 public $filename;
48
52 public $fullpath_original_file;
53
57 public $fullpath_original_file_osencoded;
58
62 public $modulepart;
63
67 public $original_file;
68
72 public $type;
73
74
80 public function init()
81 {
82 global $conf, $hookmanager, $dolibarr_nocache, $user;
83
84 define('MAIN_SECURITY_FORCECSP', "default-src 'none'");
85
86 if (!defined('NOREQUIRESOC')) {
87 define('NOREQUIRESOC', '1');
88 }
89 if (!defined('NOREQUIRETRAN')) {
90 define('NOREQUIRETRAN', '1');
91 }
92 if (!defined('NOCSRFCHECK')) {
93 define('NOCSRFCHECK', '1');
94 }
95 if (!defined('NOTOKENRENEWAL')) {
96 define('NOTOKENRENEWAL', '1');
97 }
98 if (!defined('NOREQUIREMENU')) {
99 define('NOREQUIREMENU', '1');
100 }
101 if (!defined('NOREQUIREHTML')) {
102 define('NOREQUIREHTML', '1');
103 }
104 if (!defined('NOREQUIREAJAX')) {
105 define('NOREQUIREAJAX', '1');
106 }
107
108 // For MultiCompany module.
109 // Do not use GETPOST here, function is not defined and define must be done before including main.inc.php
110 // Because 2 entities can have the same ref.
111 $entity = (!empty($_GET['entity']) ? (int) $_GET['entity'] : (!empty($_POST['entity']) ? (int) $_POST['entity'] : 1));
112 define("DOLENTITY", $entity);
113
115
116 $action = GETPOST('action', 'aZ09');
117 $original_file = GETPOST('file', 'alphanohtml');
118 $hashp = GETPOST('hashp', 'aZ09', 1);
119 $extname = GETPOST('extname', 'alpha', 1);
120 $modulepart = GETPOST('modulepart', 'alpha', 1);
121 $urlsource = GETPOST('urlsource', 'alpha');
122 $entity = (GETPOSTINT('entity') ? GETPOSTINT('entity') : $conf->entity);
123
124 // Security check
125 if (empty($modulepart) && empty($hashp)) {
126 httponly_accessforbidden('Bad link. Bad value for parameter modulepart', 400);
127 }
128 if (empty($original_file) && empty($hashp) && $modulepart != 'barcode') {
129 httponly_accessforbidden('Bad link. Missing identification to find file (param file or hashp)', 400);
130 }
131 if ($modulepart == 'fckeditor') {
132 $modulepart = 'medias'; // For backward compatibility
133 }
134
135 $cachestring = GETPOST("cache", 'aZ09'); // May be 1, or an int, or a hash
136 if ($cachestring) {
137 // Important: The following code is to avoid a page request by the browser and PHP CPU at each Dolibarr page access.
138 // We are here when param cache=xxx to force a cache policy:
139 // xxx=1 means cache of 3600s
140 // xxx=abcdef or 123456789 means a cache of 1 week (the key will be modified to get break cache use)
141 if (empty($dolibarr_nocache)) {
142 $delaycache = ((is_numeric($cachestring) && (int) $cachestring > 1 && (int) $cachestring < 999999) ? $cachestring : '3600');
143 header('Cache-Control: max-age=' . $delaycache . ', public, must-revalidate');
144 header('Pragma: cache'); // This is to avoid to have Pragma: no-cache set by proxy or web server
145 header('Expires: ' . gmdate('D, d M Y H:i:s', time() + (int) $delaycache) . ' GMT'); // This is to avoid to have Expires set by proxy or web server
146 } else {
147 // If any cache on files were disable by config file (for test purpose)
148 header('Cache-Control: no-cache');
149 }
150 //print $dolibarr_nocache; exit;
151 }
152
153 // Define mime type
154 $type = 'application/octet-stream';
155 if (GETPOST('type', 'alpha')) {
156 $type = GETPOST('type', 'alpha');
157 } else {
158 $type = dol_mimetype($original_file);
159 }
160
161 // Security: This wrapper is for images. We do not allow type/html
162 if (preg_match('/html/i', $type)) {
163 httponly_accessforbidden('Error: Using the image wrapper to output a file with a mime type HTML is not possible.');
164 }
165 // Security: This wrapper is for images. We do not allow files ending with .noexe
166 if (preg_match('/\.noexe$/i', $original_file)) {
167 httponly_accessforbidden('Error: Using the image wrapper to output a file ending with .noexe is not allowed.');
168 }
169
170 // Security: Delete string ../ or ..\ into $original_file
171 $original_file = preg_replace('/\.\.+/', '..', $original_file); // Replace '... or more' with '..'
172 $original_file = str_replace('../', '/', $original_file);
173 $original_file = str_replace('..\\', '/', $original_file);
174
175 // Find the subdirectory name as the reference
176 $refname = basename(dirname($original_file) . "/");
177 if ($refname == 'thumbs') {
178 // If we get the thumbs directory, we must go one step higher. For example original_file='10/thumbs/myfile_small.jpg' -> refname='10'
179 $refname = basename(dirname(dirname($original_file)) . "/");
180 }
181
182 // Check that file is allowed for view with viewimage.php
183 if (!empty($original_file) && !dolIsAllowedForPreview($original_file)) {
184 httponly_accessforbidden('This file extension is not qualified for preview', 403);
185 }
186
187 // Security check
188 if (empty($modulepart)) {
189 httponly_accessforbidden('Bad value for parameter modulepart', 400);
190 }
191
192 // When logged in a different entity, medias cannot be accessed because $conf->$module->multidir_output
193 // is not set on the requested entity, but they are public documents, so reset entity
194 if ($modulepart === 'medias' && $entity != $conf->entity) {
195 $conf->entity = $entity;
196 $conf->setValues($this->db);
197 }
198
199 $sqlprotectagainstexternals = '';
200 $fullpath_original_file = '';
201 $accessallowed = 0;
202
203 // Hooks
204 $hookmanager->initHooks(array('viewimage'));
205 $parameters = array('modulepart' => $modulepart, 'original_file' => &$original_file,
206 'sqlprotectagainstexternals' => &$sqlprotectagainstexternals, 'fullpath_original_file' => &$fullpath_original_file,
207 'entity' => $entity, 'accessallowed' => &$accessallowed);
208 $object = new stdClass();
209 $reshook = $hookmanager->executeHooks('accessViewImage', $parameters, $object, $action); // Note that $action and $object may have been
210 if ($reshook < 0) {
211 $errors = $hookmanager->error . (is_array($hookmanager->errors) ? (!empty($hookmanager->error) ? ', ' : '') . implode(', ', $hookmanager->errors) : '');
212 dol_syslog("document.php - Errors when executing the hook 'accessViewImage' : " . $errors);
213 print "ErrorViewImageHooks: " . $errors;
214 exit;
215 } elseif (empty($reshook)) {
216 $check_access = dol_check_secure_access_document($modulepart, $original_file, $entity, $user, $refname);
217 $accessallowed = $check_access['accessallowed'];
218 $sqlprotectagainstexternals = $check_access['sqlprotectagainstexternals'];
219 $fullpath_original_file = $check_access['original_file']; // $fullpath_original_file is now a full path name
220 }
221
222 if (!empty($hashp)) {
223 $accessallowed = 1; // When using hashp, link is public so we force $accessallowed
224 $sqlprotectagainstexternals = '';
225 } else {
226 // Basic protection (against external users only)
227 if ($user->socid > 0) {
228 if ($sqlprotectagainstexternals) {
229 $resql = $this->db->query($sqlprotectagainstexternals);
230 if ($resql) {
231 $num = $this->db->num_rows($resql);
232 $i = 0;
233 while ($i < $num) {
234 $obj = $this->db->fetch_object($resql);
235 if ($user->socid != $obj->fk_soc) {
236 $accessallowed = 0;
237 break;
238 }
239 $i++;
240 }
241 }
242 }
243 }
244 }
245
246 // Security:
247 // Limit access if permissions are wrong
248 if (!$accessallowed) {
250 }
251
252 // Security:
253 // On interdit les remontees de repertoire ainsi que les pipe dans les noms de fichiers.
254 if (preg_match('/\.\./', $fullpath_original_file) || preg_match('/[<>|]/', $fullpath_original_file)) {
255 dol_syslog("Refused to deliver file " . $fullpath_original_file);
256 print "ErrorFileNameInvalid: " . dol_escape_htmltag($original_file);
257 exit;
258 }
259
260 // Find the subdirectory name as the reference
261 $refname = basename(dirname($original_file) . "/");
262
263 $filename = basename($fullpath_original_file);
264 $filename = preg_replace('/\.noexe$/i', '', $filename);
265
266 // Output file on browser
267 dol_syslog("document controller download $fullpath_original_file filename=$filename content-type=$type");
268 $fullpath_original_file_osencoded = dol_osencode($fullpath_original_file); // New file name encoded in OS encoding charset
269
270 // This test if file exists should be useless. We keep it to find bug more easily
271 if (!file_exists($fullpath_original_file_osencoded)) {
272 dol_syslog("ErrorFileDoesNotExists: " . $fullpath_original_file);
273 print "ErrorFileDoesNotExists: " . $original_file;
274 exit;
275 }
276
277 $fileSize = dol_filesize($fullpath_original_file);
278 $fileSizeMax = getDolGlobalInt('MAIN_SECURITY_MAXFILESIZE_DOWNLOADED');
279 if ($fileSizeMax && $fileSize > $fileSizeMax) {
280 dol_syslog('ErrorFileSizeTooLarge: ' . $fileSize);
281 print 'ErrorFileSizeTooLarge: ' . $fileSize . ' (max ' . $fileSizeMax . ' Kb)';
282 exit;
283 }
284
285 // Hooks
286 $hookmanager->initHooks(array('document'));
287 $parameters = array('modulepart' => $modulepart, 'original_file' => $original_file,
288 'entity' => $entity, 'refname' => $refname, 'fullpath_original_file' => $fullpath_original_file,
289 'filename' => $filename, 'fullpath_original_file_osencoded' => $fullpath_original_file_osencoded);
290 $object = new stdClass();
291 $reshook = $hookmanager->executeHooks('viewImage', $parameters, $object, $action); // Note that $action and $object may have been
292 if ($reshook < 0) {
293 $errors = $hookmanager->error . (is_array($hookmanager->errors) ? (!empty($hookmanager->error) ? ', ' : '') . implode(', ', $hookmanager->errors) : '');
294 dol_syslog("document.php - Errors when executing the hook 'viewImage' : " . $errors);
295 print "ErrorViewImageHooks: " . $errors;
296 exit;
297 }
298
299 $this->action = $action;
300 $this->entity = $entity;
301 $this->filename = $filename;
302 $this->fullpath_original_file = $fullpath_original_file;
303 $this->fullpath_original_file_osencoded = $fullpath_original_file_osencoded;
304 $this->modulepart = $modulepart;
305 $this->original_file = $original_file;
306 $this->type = $type;
307 }
308
314 public function checkAccess()
315 {
316 $this->accessRight = true;
317
318 return parent::checkAccess();
319 }
320
327 public function action()
328 {
330 if (!$context->controllerInstance->checkAccess()) {
331 return -1;
332 }
333
334 //$context = Context::getInstance();
335 //$context->title = $langs->trans('WebPortalDocumentTitle');
336 //$context->desc = $langs->trans('WebPortalDocumentDesc');
337 //$context->doNotDisplayHeaderBar=1;// hide default header
338
339 $this->init();
340
341 return 1;
342 }
343
349 public function display()
350 {
351 global $conf;
352
354 if (!$context->controllerInstance->checkAccess()) {
355 $this->display404();
356 return;
357 }
358
359 // initialize
360 $modulepart = $this->modulepart;
361 $fullpath_original_file = $this->fullpath_original_file;
362 $fullpath_original_file_osencoded = $this->fullpath_original_file_osencoded;
363 $type = $this->type;
364
365 if ($modulepart == 'barcode') {
366 $generator = GETPOST("generator", "aZ09");
367 $encoding = GETPOST("encoding", "aZ09");
368 $readable = GETPOST("readable", 'aZ09') ? GETPOST("readable", "aZ09") : "Y";
369 if (in_array($encoding, array('EAN8', 'EAN13'))) {
370 $code = GETPOST("code", 'alphanohtml');
371 } else {
372 $code = GETPOST("code", 'restricthtml'); // This can be rich content (qrcode, datamatrix, ...)
373 }
374
375 // If $code is virtualcard_xxx_999.vcf, it is a file to read to get code
376 $reg = array();
377 if (preg_match('/^virtualcard_([^_]+)_(\d+)\.vcf$/', $code, $reg)) {
378 $vcffile = '';
379 $id = 0;
380 $login = '';
381 if ($reg[1] == 'user' && (int) $reg[2] > 0) {
382 $vcffile = $conf->user->dir_temp . '/' . $code;
383 $id = (int) $reg[2];
384 $tmpuser = new User($this->db);
385 $tmpuser->fetch($id);
386 $login = $tmpuser->login;
387 } elseif ($reg[1] == 'contact' && (int) $reg[2] > 0) {
388 $vcffile = $conf->contact->dir_temp . '/' . $code;
389 $id = (int) $reg[2];
390 }
391
392 $code = '';
393 if ($vcffile && $id) {
394 // Case of use of viewimage to get the barcode for user pubic profile,
395 // we must check the securekey that protet against forging url
396 if ($reg[1] == 'user' && (int) $reg[2] > 0) {
397 $encodedsecurekey = dol_hash($conf->file->instance_unique_id . 'uservirtualcard' . $id . '-' . $login, 'md5');
398 if ($encodedsecurekey != GETPOST('securekey')) {
399 $code = 'badvalueforsecurekey';
400 }
401 }
402 if (empty($code)) {
403 $code = file_get_contents($vcffile);
404 }
405 }
406 }
407
408
409 if (empty($generator) || empty($encoding)) {
410 print 'Error: Parameter "generator" or "encoding" not defined';
411 exit;
412 }
413
414 $dirbarcode = array_merge(array("/core/modules/barcode/doc/"), $conf->modules_parts['barcode']);
415
416 $result = 0;
417
418 foreach ($dirbarcode as $reldir) {
419 $dir = dol_buildpath($reldir, 0);
420 $newdir = dol_osencode($dir);
421
422 // Check if directory exists (we do not use dol_is_dir to avoid loading files.lib.php)
423 if (!is_dir($newdir)) {
424 continue;
425 }
426
427 $result = @include_once $newdir . $generator . '.modules.php';
428 if ($result) {
429 break;
430 }
431 }
432
433 // Load barcode class
434 $classname = "mod" . ucfirst($generator);
435
436 $module = new $classname($this->db);
437 '@phan-var-force ModeleBarCode $module';
439 if ($module->encodingIsSupported($encoding)) {
440 $result = $module->buildBarCode($code, $encoding, $readable);
441 }
442 } else {
443 // Open and return file
444 clearstatcache();
445
446 $filename = basename($fullpath_original_file);
447
448 // Output files on browser
449 dol_syslog("viewimage.php return file $fullpath_original_file filename=$filename content-type=$type");
450
451 if (!dol_is_file($fullpath_original_file) && !GETPOSTINT("noalt", 1)) {
452 // This test is to replace error images with a nice "notfound image" when image is not available (for example when thumbs not yet generated).
453 $fullpath_original_file = Context::getRootConfigUrl() . 'img/nophoto.png';
454 /*$error='Error: File '.$_GET["file"].' does not exists or filesystems permissions are not allowed';
455 print $error;
456 exit;*/
457 }
458
459 // Permissions are ok and file found, so we return it
460 if ($type) {
461 top_httphead($type);
462 header('Content-Disposition: inline; filename="' . basename($fullpath_original_file) . '"');
463 } else {
464 top_httphead('image/png');
465 header('Content-Disposition: inline; filename="' . basename($fullpath_original_file) . '"');
466 }
467
468 $fullpath_original_file_osencoded = dol_osencode($fullpath_original_file);
469
470 readfile($fullpath_original_file_osencoded);
471 }
472 }
473}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
static getInstance()
Singleton method to create one instance of this object.
static getRootConfigUrl()
Get WebPortal root url.
Class to manage pages.
display()
Display.
display404()
Display error template.
Class to manage Dolibarr users.
Class for ViewImageController.
checkAccess()
Check current access to controller.
action()
Action method is called before html output can be used to manage security and change context.
dol_filesize($pathoffile)
Return size of a file.
dol_check_secure_access_document($modulepart, $original_file, $entity, $fuser=null, $refname='', $mode='read')
Security check when accessing to a document (used by document.php, viewimage.php and webservices to g...
dol_is_file($pathoffile)
Return if path is a file.
dol_mimetype($file, $default='application/octet-stream', $mode=0)
Return MIME type of a file from its name with extension.
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
dolIsAllowedForPreview($file)
Return if a file is qualified for preview.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
if(!defined( 'NOREQUIREMENU')) if(!empty(GETPOST('seteventmessages', 'alpha'))) if(!function_exists("llxHeader")) top_httphead($contenttype='text/html', $forcenocache=0)
Show HTTP header.
$context
@method int call_trigger(string $triggerName, ?User $user)
Definition logout.php:42
if(preg_match('/(crypted|dolcrypt):/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition repair.php:125
httponly_accessforbidden($message='1', $http_response_code=403, $stringalreadysanitized=0)
Show a message to say access is forbidden and stop program.
dol_hash($chain, $type='0', $nosalt=0, $mode=0)
Returns a hash (non reversible encryption) of a string.
accessforbidden($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program.