# Copyright (C) 2014, 2016, 2017 Assaf Gordon # Copyright (C) 2001-2011, 2013, 2017 Sylvain Beucler # Copyright (C) 2013, 2014, 2017-2025 Ineiev # # This file is part of Savane. # # Code written before 2008-03-30 (commit 8b757b2565ff) is distributed # under the terms of the GNU General Public license version 3 or (at your # option) any later version; further contributions are covered by # the GNU Affero General Public license version 3 or (at your option) # any later version. The license notices for the AGPL and the GPL follow. # # Savane is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # Savane is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # # Savane is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # Savane is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . $GLOBALS['skip_csp_headers'] = 1; foreach (['init', 'http', 'form-check'] as $i) require_once ("include/$i.php"); function file_exit ($func, $param) { unset ($GLOBALS['skip_csp_headers']); utils_set_csp_headers (); $func = "exit_$func"; $func ($param); } function file_exit_error ($str) { file_exit ('error', $str); } extract (sane_import ('request', [ 'preg' => [['file_id', '/^(\d+|test[.]png)$/']], 'digits' => 'file_uid' ] )); if (!$file_id) file_exit ('missing_param', ['file_id']); if ($file_id == 'test.png') { header ('Content-Type: image/png'); $fname = $GLOBALS['sys_www_topdir'] . '/images/common/floating.png'; header ('Content-Length: ' . stat ($fname)['size']); header ("Content-Disposition: attachment; filename=$file_id"); readfile ($fname); exit (0); } # Check privacy of the item this file is attached to and reject access by # non-authorized users. $result = db_execute ( "SELECT item_id, artifact FROM trackers_file WHERE file_id = ?", [$file_id] ); if (db_numrows ($result) > 0) { $item_id = db_result ($result, 0, 'item_id'); $artifact = db_result ($result, 0, 'artifact'); } else # TRANSLATORS: the argument is file id (a number). file_exit_error (sprintf (_("File #%s not found"), $file_id)); $in = [0 => $artifact]; $out = []; if ($sane_sanitizers['artifact'] ($in, $out, 0, null)) { # TRANSLATORS: the argument is artifact name ('bugs', 'task' etc.) $str = sprintf (_('Invalid artifact %s'), "$artifact"); unset ($artifact); file_exit_error ($str); } function assert_file_access ($item_fields, $file_uid) { if ($item_fields['privacy'] != '2') return; if (user_can_be_super_user ($file_uid)) # We are in the file domain and have no access to cookies, so we can't tell # if the user has become a superuser; therefore, we let site admins access # any files in any case. return; $group_id = $item_fields['group_id']; if (!member_check_private ($file_uid, $group_id)) file_exit_error ( _("Non-authorized access to file attached to private item") ); form_check_id (); } $item_fields = utils_find_item ( $artifact, $item_id, ['privacy'], 'file_exit_error' ); assert_file_access ($item_fields, $file_uid); $result = db_execute (" SELECT description, filename, filesize, filetype, date FROM trackers_file WHERE file_id = ? LIMIT 1", [$file_id] ); if (!db_numrows ($result)) file_exit_error ( sprintf (_("Couldn't find attached file #%s."), $file_id) ); $row = db_fetch_array ($result); if ($row['filesize'] < 0) file_exit_error ( sprintf (_("Attached file #%s was lost."), $file_id) . " " . sprintf ( _("File attributes: name '%s', size %s, type '%s', date %s."), $row['filename'], $row['filesize'], $row['filetype'], utils_format_date ($row['date']) ) ); $mtime = $row['date']; http_exit_if_not_modified ($mtime); header ('Last-Modified: ' . date ('r', $mtime)); # Check if the filename in database matches the one in the URL. # We do not want to allow broken URL that may make a user download # a file with a given name like "myimage.png" when actually downloading # something completely different like "mystupidvirus.scr". if ($row['filename'] != basename (rawurldecode ($_SERVER['PHP_SELF']))) file_exit_error ( _("The filename in the URL does not match the filename " . "registered in the database") ); $path = "$sys_trackers_attachments_dir/$file_id"; if (!is_readable ($path)) file_exit_error (_("No access to the file.")); $row['agpl'] = trim (git_agpl_notice ('This file is served with Savane.')); # Serve the file with respective attributes. $headers = [ 'filetype' => 'Content-Type: ', 'filesize' => 'Content-Length: ', 'filename' => 'Content-Disposition: attachment; filename=', 'description' => 'Content-Description: ', 'agpl' => 'Source-Code-Offer: ' ]; foreach ($headers as $field => $h) { if (empty ($row[$field])) continue; $val = str_replace (["\n", "\r"], ' ', $row[$field]); header ("$h$val"); } readfile ($path); exit (0); ?>