1*8ce3bd73SGreg Roach<?php 2*8ce3bd73SGreg Roach 3*8ce3bd73SGreg Roach/** 4*8ce3bd73SGreg Roach * webtrees: online genealogy 5*8ce3bd73SGreg Roach * Copyright (C) 2020 webtrees development team 6*8ce3bd73SGreg Roach * This program is free software: you can redistribute it and/or modify 7*8ce3bd73SGreg Roach * it under the terms of the GNU General Public License as published by 8*8ce3bd73SGreg Roach * the Free Software Foundation, either version 3 of the License, or 9*8ce3bd73SGreg Roach * (at your option) any later version. 10*8ce3bd73SGreg Roach * This program is distributed in the hope that it will be useful, 11*8ce3bd73SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12*8ce3bd73SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13*8ce3bd73SGreg Roach * GNU General Public License for more details. 14*8ce3bd73SGreg Roach * You should have received a copy of the GNU General Public License 15*8ce3bd73SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 16*8ce3bd73SGreg Roach */ 17*8ce3bd73SGreg Roach 18*8ce3bd73SGreg Roachdeclare(strict_types=1); 19*8ce3bd73SGreg Roach 20*8ce3bd73SGreg Roachnamespace Fisharebest\Webtrees\Http\RequestHandlers; 21*8ce3bd73SGreg Roach 22*8ce3bd73SGreg Roachuse Fisharebest\Webtrees\FlashMessages; 23*8ce3bd73SGreg Roachuse Fisharebest\Webtrees\Functions\Functions; 24*8ce3bd73SGreg Roachuse Fisharebest\Webtrees\Html; 25*8ce3bd73SGreg Roachuse Fisharebest\Webtrees\I18N; 26*8ce3bd73SGreg Roachuse Fisharebest\Webtrees\Log; 27*8ce3bd73SGreg Roachuse Fisharebest\Webtrees\Registry; 28*8ce3bd73SGreg Roachuse Fisharebest\Webtrees\Services\MediaFileService; 29*8ce3bd73SGreg Roachuse Psr\Http\Message\ResponseInterface; 30*8ce3bd73SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 31*8ce3bd73SGreg Roachuse Psr\Http\Message\UploadedFileInterface; 32*8ce3bd73SGreg Roachuse Psr\Http\Server\RequestHandlerInterface; 33*8ce3bd73SGreg Roachuse Throwable; 34*8ce3bd73SGreg Roach 35*8ce3bd73SGreg Roachuse function assert; 36*8ce3bd73SGreg Roachuse function e; 37*8ce3bd73SGreg Roachuse function preg_match; 38*8ce3bd73SGreg Roachuse function redirect; 39*8ce3bd73SGreg Roachuse function route; 40*8ce3bd73SGreg Roachuse function str_replace; 41*8ce3bd73SGreg Roachuse function substr; 42*8ce3bd73SGreg Roachuse function trim; 43*8ce3bd73SGreg Roach 44*8ce3bd73SGreg Roachuse const UPLOAD_ERR_OK; 45*8ce3bd73SGreg Roach 46*8ce3bd73SGreg Roach/** 47*8ce3bd73SGreg Roach * Manage media from the control panel. 48*8ce3bd73SGreg Roach */ 49*8ce3bd73SGreg Roachclass UploadMediaAction implements RequestHandlerInterface 50*8ce3bd73SGreg Roach{ 51*8ce3bd73SGreg Roach /** @var MediaFileService */ 52*8ce3bd73SGreg Roach private $media_file_service; 53*8ce3bd73SGreg Roach 54*8ce3bd73SGreg Roach /** 55*8ce3bd73SGreg Roach * MediaController constructor. 56*8ce3bd73SGreg Roach * 57*8ce3bd73SGreg Roach * @param MediaFileService $media_file_service 58*8ce3bd73SGreg Roach */ 59*8ce3bd73SGreg Roach public function __construct(MediaFileService $media_file_service) 60*8ce3bd73SGreg Roach { 61*8ce3bd73SGreg Roach $this->media_file_service = $media_file_service; 62*8ce3bd73SGreg Roach } 63*8ce3bd73SGreg Roach 64*8ce3bd73SGreg Roach /** 65*8ce3bd73SGreg Roach * @param ServerRequestInterface $request 66*8ce3bd73SGreg Roach * 67*8ce3bd73SGreg Roach * @return ResponseInterface 68*8ce3bd73SGreg Roach */ 69*8ce3bd73SGreg Roach public function handle(ServerRequestInterface $request): ResponseInterface 70*8ce3bd73SGreg Roach { 71*8ce3bd73SGreg Roach $data_filesystem = Registry::filesystem()->data(); 72*8ce3bd73SGreg Roach 73*8ce3bd73SGreg Roach $params = (array) $request->getParsedBody(); 74*8ce3bd73SGreg Roach 75*8ce3bd73SGreg Roach $all_folders = $this->media_file_service->allMediaFolders($data_filesystem); 76*8ce3bd73SGreg Roach 77*8ce3bd73SGreg Roach foreach ($request->getUploadedFiles() as $key => $uploaded_file) { 78*8ce3bd73SGreg Roach assert($uploaded_file instanceof UploadedFileInterface); 79*8ce3bd73SGreg Roach if ($uploaded_file->getClientFilename() === '') { 80*8ce3bd73SGreg Roach continue; 81*8ce3bd73SGreg Roach } 82*8ce3bd73SGreg Roach if ($uploaded_file->getError() !== UPLOAD_ERR_OK) { 83*8ce3bd73SGreg Roach FlashMessages::addMessage(Functions::fileUploadErrorText($uploaded_file->getError()), 'danger'); 84*8ce3bd73SGreg Roach continue; 85*8ce3bd73SGreg Roach } 86*8ce3bd73SGreg Roach $key = substr($key, 9); 87*8ce3bd73SGreg Roach 88*8ce3bd73SGreg Roach $folder = $params['folder' . $key]; 89*8ce3bd73SGreg Roach $filename = $params['filename' . $key]; 90*8ce3bd73SGreg Roach 91*8ce3bd73SGreg Roach // If no filename specified, use the original filename. 92*8ce3bd73SGreg Roach if ($filename === '') { 93*8ce3bd73SGreg Roach $filename = $uploaded_file->getClientFilename(); 94*8ce3bd73SGreg Roach } 95*8ce3bd73SGreg Roach 96*8ce3bd73SGreg Roach // Validate the folder 97*8ce3bd73SGreg Roach if (!$all_folders->contains($folder)) { 98*8ce3bd73SGreg Roach break; 99*8ce3bd73SGreg Roach } 100*8ce3bd73SGreg Roach 101*8ce3bd73SGreg Roach // Validate the filename. 102*8ce3bd73SGreg Roach $filename = str_replace('\\', '/', $filename); 103*8ce3bd73SGreg Roach $filename = trim($filename, '/'); 104*8ce3bd73SGreg Roach 105*8ce3bd73SGreg Roach if (preg_match('/([:])/', $filename, $match)) { 106*8ce3bd73SGreg Roach // Local media files cannot contain certain special characters, especially on MS Windows 107*8ce3bd73SGreg Roach FlashMessages::addMessage(I18N::translate('Filenames are not allowed to contain the character “%s”.', $match[1])); 108*8ce3bd73SGreg Roach continue; 109*8ce3bd73SGreg Roach } 110*8ce3bd73SGreg Roach 111*8ce3bd73SGreg Roach if (preg_match('/(\.(php|pl|cgi|bash|sh|bat|exe|com|htm|html|shtml))$/i', $filename, $match)) { 112*8ce3bd73SGreg Roach // Do not allow obvious script files. 113*8ce3bd73SGreg Roach FlashMessages::addMessage(I18N::translate('Filenames are not allowed to have the extension “%s”.', $match[1])); 114*8ce3bd73SGreg Roach continue; 115*8ce3bd73SGreg Roach } 116*8ce3bd73SGreg Roach 117*8ce3bd73SGreg Roach $path = $folder . $filename; 118*8ce3bd73SGreg Roach 119*8ce3bd73SGreg Roach if ($data_filesystem->has($path)) { 120*8ce3bd73SGreg Roach FlashMessages::addMessage(I18N::translate('The file %s already exists. Use another filename.', $path, 'error')); 121*8ce3bd73SGreg Roach continue; 122*8ce3bd73SGreg Roach } 123*8ce3bd73SGreg Roach 124*8ce3bd73SGreg Roach // Now copy the file to the correct location. 125*8ce3bd73SGreg Roach try { 126*8ce3bd73SGreg Roach $data_filesystem->writeStream($path, $uploaded_file->getStream()->detach()); 127*8ce3bd73SGreg Roach FlashMessages::addMessage(I18N::translate('The file %s has been uploaded.', Html::filename($path)), 'success'); 128*8ce3bd73SGreg Roach Log::addMediaLog('Media file ' . $path . ' uploaded'); 129*8ce3bd73SGreg Roach } catch (Throwable $ex) { 130*8ce3bd73SGreg Roach FlashMessages::addMessage(I18N::translate('There was an error uploading your file.') . '<br>' . e($ex->getMessage()), 'danger'); 131*8ce3bd73SGreg Roach } 132*8ce3bd73SGreg Roach } 133*8ce3bd73SGreg Roach 134*8ce3bd73SGreg Roach $url = route(UploadMediaPage::class); 135*8ce3bd73SGreg Roach 136*8ce3bd73SGreg Roach return redirect($url); 137*8ce3bd73SGreg Roach } 138*8ce3bd73SGreg Roach} 139