dolibarr 24.0.0-beta
document.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 $attachment;
43
47 public $encoding;
48
52 public $entity;
53
57 public $filename;
58
62 public $fullpath_original_file;
63
67 public $fullpath_original_file_osencoded;
68
72 public $modulepart;
73
77 public $original_file;
78
82 public $type;
83
84
90 public function init()
91 {
92 global $conf, $hookmanager;
93
94 define('MAIN_SECURITY_FORCECSP', "default-src 'none'");
95
96 if (!defined('NOTOKENRENEWAL')) {
97 define('NOTOKENRENEWAL', '1');
98 }
99 if (!defined('NOREQUIREMENU')) {
100 define('NOREQUIREMENU', '1');
101 }
102 if (!defined('NOREQUIREHTML')) {
103 define('NOREQUIREHTML', '1');
104 }
105 if (!defined('NOREQUIREAJAX')) {
106 define('NOREQUIREAJAX', '1');
107 }
108
110
111 $encoding = '';
112 $action = GETPOST('action', 'aZ09');
113 $original_file = GETPOST('file', 'alphanohtml'); // Do not use urldecode here ($_GET are already decoded by PHP).
114 $modulepart = GETPOST('modulepart', 'alpha');
115 $entity = GETPOSTINT('entity') ? GETPOSTINT('entity') : $conf->entity;
116 $socId = GETPOSTINT('soc_id');
117
118 // Security check
119 if (empty($modulepart)) {
120 httponly_accessforbidden('Bad link. Bad value for parameter modulepart', 400);
121 }
122 if (empty($original_file)) {
123 httponly_accessforbidden('Bad link. Missing identification to find file (original_file)', 400);
124 }
125
126 // get original file
127 $ecmfile = '';
128
129 // Define attachment (attachment=true to force choice popup 'open'/'save as')
130 $attachment = true;
131 if (preg_match('/\.(html|htm)$/i', $original_file)) {
132 $attachment = false;
133 }
134 if (GETPOSTISSET("attachment")) {
135 $attachment = GETPOST("attachment", 'alpha') ? true : false;
136 }
137 if (getDolGlobalString('MAIN_DISABLE_FORCE_SAVEAS')) {
138 $attachment = false;
139 }
140
141 // Define mime type
142 if (GETPOST('type', 'alpha')) {
143 $type = GETPOST('type', 'alpha');
144 } else {
145 $type = dol_mimetype($original_file);
146 }
147 // Security: Force to octet-stream if file is a dangerous file. For example when it is a .noexe file
148 // We do not force if file is a javascript to be able to get js from website module with <script src="
149 // Note: Force whatever is $modulepart seems ok.
150 if (!in_array($type, array('text/x-javascript')) && !dolIsAllowedForPreview($original_file)) {
151 $type = 'application/octet-stream';
152 }
153
154 // Security: Delete string ../ or ..\ into $original_file
155 $original_file = preg_replace('/\.\.+/', '..', $original_file); // Replace '... or more' with '..'
156 $original_file = str_replace('../', '/', $original_file);
157 $original_file = str_replace('..\\', '/', $original_file);
158
159 // Check security and set return info with full path of file
160 $accessallowed = 0; // not allowed by default
161
162 $tmparray = getElementProperties($modulepart);
163
164 $moduleName = $modulepart;
165 $moduleNameEn = $moduleName;
166
167 if ($moduleName == 'commande') {
168 $moduleNameEn = 'order';
169 } elseif ($moduleName == 'facture') {
170 $moduleNameEn = 'invoice';
171 }
172 $moduleNameUpperEn = strtoupper($moduleNameEn);
173
174 // Hooks
175 $hookmanager->initHooks(array('document'));
176 $parameters = array('ecmfile' => $ecmfile, 'modulepart' => $modulepart, 'original_file' => &$original_file, 'socId' => $socId,
177 'entity' => $entity, 'accessallowed' => &$accessallowed);
178 $object = new stdClass();
179 $reshook = $hookmanager->executeHooks('accessDownloadDocument', $parameters, $object, $action); // Note that $action and $object may have been
180 if ($reshook < 0) {
181 $errors = $hookmanager->error . (is_array($hookmanager->errors) ? (!empty($hookmanager->error) ? ', ' : '') . implode(', ', $hookmanager->errors) : '');
182 dol_syslog("document.php - Errors when executing the hook 'accessDownloadDocument' : " . $errors);
183 print "ErrorDownloadDocumentHooks: " . $errors;
184 exit;
185 }
186 if (empty($reshook)) {
187 // check config access
188 // and file mime type (only PDF)
189 // and check login access
190 if (getDolGlobalInt('WEBPORTAL_' . $moduleNameUpperEn . '_LIST_ACCESS')
191 && in_array($type, array('application/pdf'))
192 && ($context->logged_thirdparty && $context->logged_thirdparty->id > 0)
193 && $context->logged_thirdparty->id == $socId
194 ) {
195 if (isModEnabled($moduleName) && isset($conf->{$moduleName}->multidir_output[$entity])) {
196 // List of module supported in security tests (others are forbidden if not security test to check that document is owned by company is done)
197 if (in_array($moduleName, array('facture', 'invoice', 'commande', 'order', 'propal', 'ticket'))) {
198 $sql = "SELECT rowid, src_object_id, src_object_type FROM ".MAIN_DB_PREFIX.'ecm_files';
199 $sql .= " WHERE filename = '".$this->db->escape(basename($original_file))."'";
200 $sql .= " AND filepath = '".$this->db->escape(basename($tmparray['dir_output']).'/'.dirname($original_file))."'";
201 $resql = $this->db->query($sql);
202 if ($resql) {
203 $obj = $this->db->fetch_object($resql);
204
205 if ($obj->src_object_id && $obj->src_object_type) {
206 // Create the virtual user
207 $tmpuser = new User($this->db);
208 $tmpuser->socid = $socId;
209
210 include_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php';
211 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
212
213 // Use dol_check_secure_access_document(); instead or not ?
214 $ok = checkUserAccessToObject($tmpuser, array($obj->src_object_type), $obj->src_object_id, '', '', 'fk_soc');
215
216 $accessallowed = ($ok ? 1 : 0);
217 $pathdir = $tmparray['dir_output'];
218 }
219 } else {
220 dol_print_error($this->db);
221 }
222 }
223 }
224 }
225 } else {
226 $pathdir = $hookmanager->resArray['pathdir'];
227 }
228
229 // Security:
230 // Limit access if permissions are wrong
231 if (!$accessallowed) {
233 }
234
235 if (empty($pathdir)) {
236 print "ErrorDownloadDocument: No path defined to find files";
237 }
238
239 $fullpath_original_file = $pathdir . '/' . $original_file; // $fullpath_original_file is now a full path name
240
241 // Security:
242 // We refuse directory transversal change and pipes in file names
243 if (preg_match('/\.\./', $fullpath_original_file) || preg_match('/[<>|]/', $fullpath_original_file)) {
244 dol_syslog("Refused to deliver file " . $fullpath_original_file);
245 print "ErrorFileNameInvalid: " . dol_escape_htmltag($original_file);
246 exit;
247 }
248
249 // Find the subdirectory name as the reference
250 $refname = basename(dirname($original_file) . "/");
251
252 $filename = basename($fullpath_original_file);
253 $filename = preg_replace('/\.noexe$/i', '', $filename);
254
255 // Output file on browser
256 dol_syslog("document controller download $fullpath_original_file filename=$filename content-type=$type");
257 $fullpath_original_file_osencoded = dol_osencode($fullpath_original_file); // New file name encoded in OS encoding charset
258
259 // This test if file exists should be useless. We keep it to find bug more easily
260 if (!file_exists($fullpath_original_file_osencoded)) {
261 dol_syslog("ErrorFileDoesNotExists: " . $fullpath_original_file);
262 print "ErrorFileDoesNotExists: " . $original_file;
263 exit;
264 }
265
266 $fileSize = dol_filesize($fullpath_original_file);
267 $fileSizeMax = getDolGlobalInt('MAIN_SECURITY_MAXFILESIZE_DOWNLOADED');
268 if ($fileSizeMax && $fileSize > ($fileSizeMax * 1024)) {
269 // FIX: Convert limit from Ko to bytes for proper comparison
270 $fileSizeKb = round($fileSize / 1024, 2);
271 dol_syslog('ErrorFileSizeTooLarge: ' . $fileSize . ' bytes (' . $fileSizeKb . ' Kb) - max allowed: ' . $fileSizeMax . ' Kb');
272 print 'ErrorFileSizeTooLarge: ' . $fileSizeKb . ' Kb (max ' . $fileSizeMax . ' Kb)';
273 exit;
274 }
275
276 // Hooks
277 $hookmanager->initHooks(array('document'));
278 $parameters = array('ecmfile' => $ecmfile, 'modulepart' => $modulepart, 'original_file' => $original_file,
279 'entity' => $entity, 'refname' => $refname, 'fullpath_original_file' => $fullpath_original_file,
280 'filename' => $filename, 'fullpath_original_file_osencoded' => $fullpath_original_file_osencoded);
281 $object = new stdClass();
282 $reshook = $hookmanager->executeHooks('downloadDocument', $parameters, $object, $action); // Note that $action and $object may have been
283 if ($reshook < 0) {
284 $errors = $hookmanager->error . (is_array($hookmanager->errors) ? (!empty($hookmanager->error) ? ', ' : '') . implode(', ', $hookmanager->errors) : '');
285 dol_syslog("document.php - Errors when executing the hook 'downloadDocument' : " . $errors);
286 print "ErrorDownloadDocumentHooks: " . $errors;
287 exit;
288 }
289
290 $this->action = $action;
291 $this->attachment = $attachment;
292 $this->encoding = $encoding;
293 $this->entity = $entity;
294 $this->filename = $filename;
295 $this->fullpath_original_file = $fullpath_original_file;
296 $this->fullpath_original_file_osencoded = $fullpath_original_file_osencoded;
297 $this->modulepart = $modulepart;
298 $this->original_file = $original_file;
299 $this->type = $type;
300 }
301
307 public function checkAccess()
308 {
309 $this->accessRight = true;
310
311 return parent::checkAccess();
312 }
313
320 public function action()
321 {
323 if (!$context->controllerInstance->checkAccess()) {
324 return -1;
325 }
326
327 //$context = Context::getInstance();
328 //$context->title = $langs->trans('WebPortalDocumentTitle');
329 //$context->desc = $langs->trans('WebPortalDocumentDesc');
330 //$context->doNotDisplayHeaderBar=1;// hide default header
331
332 $this->init();
333
334 return 1;
335 }
336
342 public function display()
343 {
345 if (!$context->controllerInstance->checkAccess()) {
346 $this->display404();
347 return;
348 }
349
350 // initialize
351 $attachment = $this->attachment;
352 $encoding = $this->encoding;
353 $filename = $this->filename;
354 $fullpath_original_file = $this->fullpath_original_file;
355 $fullpath_original_file_osencoded = $this->fullpath_original_file_osencoded;
356 $type = $this->type;
357
358 clearstatcache();
359
360 // Permissions are ok and file found, so we return it
361 top_httphead($type);
362 header('Content-Description: File Transfer');
363 if ($encoding) {
364 header('Content-Encoding: ' . $encoding);
365 }
366 // Add MIME Content-Disposition from RFC 2183 (inline=automatically displayed, attachment=need user action to open)
367 if ($attachment) {
368 header('Content-Disposition: attachment; filename="' . $filename . '"');
369 } else {
370 header('Content-Disposition: inline; filename="' . $filename . '"');
371 }
372 header('Cache-Control: Public, must-revalidate');
373 header('Pragma: public');
374
375 // Send file now
376 header('Content-Length: ' . dol_filesize($fullpath_original_file));
377 readfileLowMemory($fullpath_original_file_osencoded);
378 }
379}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
static getInstance()
Singleton method to create one instance of this object.
Class to manage pages.
Class for DocumentController.
action()
Action method is called before html output can be used to manage security and change context.
checkAccess()
Check current access to controller.
Class to manage Dolibarr users.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_filesize($pathoffile)
Return size of a file.
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.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
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:130
httponly_accessforbidden($message='1', $http_response_code=403, $stringalreadysanitized=0)
Show a message to say access is forbidden and stop program.
checkUserAccessToObject($user, array $featuresarray, $object=0, $tableandshare='', $feature2='', $dbt_keyfield='', $dbt_select='rowid', $parenttableforentity='')
Check that access by a given user to an object is ok.
accessforbidden($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program.