diff --git a/composer.json b/composer.json index 97c718f73..ded240958 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,8 @@ "scripts": { "test": "phpunit --do-not-cache-result --stderr tests", "post-install-cmd": [ - "@composer install --working-dir ./tools/autoupdate/" + "@composer install --working-dir ./tools/autoupdate/", + "@php -r \"array_map('unlink', glob('vendor/enshrined/svg-sanitize/tests/data/*.svg'));\"" ], "post-update-cmd": [ "@composer update --working-dir ./tools/autoupdate/" @@ -16,11 +17,12 @@ }, "require": { "php": "^7.3 || ^8.0", + "ext-json": "*", + "ext-mysqli": "*", "caxy/php-htmldiff": "^0.1.13", "doctrine/annotations": "^1.11", "doctrine/cache": "^1.10", - "ext-json": "*", - "ext-mysqli": "*", + "enshrined/svg-sanitize": "^0.14.1", "oomphinc/composer-installers-extender": "^2.0", "phpmailer/phpmailer": "^6.2", "symfony/config": "^5.1", diff --git a/composer.lock b/composer.lock index f271d0b02..03164085f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a106897404dfaebd331e5f54b89ccf18", + "content-hash": "215040317134baa2df64f1754c381c80", "packages": [ { "name": "caxy/php-htmldiff", @@ -465,6 +465,52 @@ ], "time": "2022-01-12T08:27:12+00:00" }, + { + "name": "enshrined/svg-sanitize", + "version": "0.14.1", + "source": { + "type": "git", + "url": "https://github.com/darylldoyle/svg-sanitizer.git", + "reference": "307b42066fb0b76b5119f5e1f0826e18fefabe95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/darylldoyle/svg-sanitizer/zipball/307b42066fb0b76b5119f5e1f0826e18fefabe95", + "reference": "307b42066fb0b76b5119f5e1f0826e18fefabe95", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^7.0 || ^8.0" + }, + "require-dev": { + "codeclimate/php-test-reporter": "^0.1.2", + "phpunit/phpunit": "^6.5 || ^8.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "enshrined\\svgSanitize\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Daryll Doyle", + "email": "daryll@enshrined.co.uk" + } + ], + "description": "An SVG sanitizer for PHP", + "support": { + "issues": "https://github.com/darylldoyle/svg-sanitizer/issues", + "source": "https://github.com/darylldoyle/svg-sanitizer/tree/0.14.1" + }, + "time": "2021-08-09T23:46:54+00:00" + }, { "name": "ezyang/htmlpurifier", "version": "v4.14.0", @@ -2725,16 +2771,16 @@ }, { "name": "twig/twig", - "version": "v3.3.7", + "version": "v3.3.8", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "8f168c6ffa3ce76d1786b3cd52275424a3fc675b" + "reference": "972d8604a92b7054828b539f2febb0211dd5945c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/8f168c6ffa3ce76d1786b3cd52275424a3fc675b", - "reference": "8f168c6ffa3ce76d1786b3cd52275424a3fc675b", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/972d8604a92b7054828b539f2febb0211dd5945c", + "reference": "972d8604a92b7054828b539f2febb0211dd5945c", "shasum": "" }, "require": { @@ -2785,7 +2831,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.3.7" + "source": "https://github.com/twigphp/Twig/tree/v3.3.8" }, "funding": [ { @@ -2797,7 +2843,7 @@ "type": "tidelift" } ], - "time": "2022-01-03T21:15:37+00:00" + "time": "2022-02-04T06:59:48+00:00" }, { "name": "yeswiki/theme-margot", @@ -2926,12 +2972,12 @@ }, "type": "library", "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, "files": [ "src/DeepCopy/deep_copy.php" - ] + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ diff --git a/tools/attach/config.yaml b/tools/attach/config.yaml index 98207032e..d81c965c7 100644 --- a/tools/attach/config.yaml +++ b/tools/attach/config.yaml @@ -12,6 +12,7 @@ parameters: png: 'PNG' gif: 'GIF' jpeg: 'JPEG' + webp: 'WEBP' # Autres images (peuvent utiliser le tag ) bmp: 'BMP' tif: 'TIFF' @@ -66,6 +67,7 @@ parameters: h: 'C header' kml: 'Keyhole Markup Language' kmz: 'Google Earth Placemark File' + md: 'Markdown' mm: 'Mindmap' pas: 'Pascal' pdf: 'PDF' @@ -93,7 +95,9 @@ parameters: xspf: 'XSPF' xls: 'Excel' xlsx: 'Excel' + xlsm: 'Excel' xml: 'XML' + yaml: 'YAML' zip: 'Zip' # Open Document odt: 'opendocument text' @@ -109,6 +113,20 @@ parameters: ots: 'opendocument spreadsheet-template' otp: 'opendocument presentation-template' otg: 'opendocument graphics-template' + attach_config: + ext_images: "gif|jpeg|png|jpg|svg|webp" + ext_audio: "mp3|aac" + ext_video: "mp4|webm|ogg" + ext_wma: "wma" + ext_pdf: "pdf" + ext_freemind: "mm" + ext_flashvideo: "flv" + ext_script: "php|php3|asp|asx|vb|vbs|js" + upload_path: "files" + update_symbole: "" + fmDelete_symbole: "Supr" + fmRestore_symbole: "Rest" + fmTrash_symbole: "Corbeille" attach-video-config: default_video_service: 'peertube' default_peertube_instance: 'https://framatube.org/' diff --git a/tools/attach/handlers/AjaxUploadHandler.php b/tools/attach/handlers/AjaxUploadHandler.php new file mode 100644 index 000000000..045c019cb --- /dev/null +++ b/tools/attach/handlers/AjaxUploadHandler.php @@ -0,0 +1,89 @@ +getService(SecurityController::class)->isWikiHibernated()) { + throw new \Exception(_t('WIKI_IN_HIBERNATION')); + }; + + if (!$this->hasAccesUpload($_GET)) { + return $this->formatOuput(['error' => _t('NO_RIGHT_TO_WRITE_IN_THIS_PAGE')]); + } + + // load classes + require_once 'tools/attach/libs/qq.lib.php'; + + if (!class_exists('attach')) { + include_once 'tools/attach/libs/attach.lib.php'; + } + $errorsMessage = ''; + ob_start(); + try { + $att = new attach($this->wiki); + + // list of valid extensions, ex. array("jpeg", "xml", "bmp") + $allowedExtensions = array_keys($this->params->get('authorized-extensions')); + + // max file size in bytes + $sizeLimit = $att->attachConfig['max_file_size']; + + $uploader = new qqFileUploader($allowedExtensions, $sizeLimit, $this->hasTempTag); + $result = $uploader->handleUpload($att->attachConfig['upload_path']); + } catch (\Throwable $th) { + $errorsMessage .= "{$th->getMessage()} in {$th->getFile()}, line {$th->getLine()}"; + } + $errorsMessage .= ob_get_contents(); + ob_end_clean(); + if (!empty($errorsMessage)) { + $result['error'] = ($result['error'] ?? '').$errorsMessage; + } + return $this->formatOuput($result); + } + + private function hasAccesUpload(array $get): bool + { + $tag = $this->wiki->getPageTag(); + if (empty(trim($tag))) { + return false; + } + + $this->hasTempTag = ( + isset($get['tempTag']) + && preg_match("/^{$this->params->get('temp_tag_for_entry_creation')}_[A-Fa-f0-9]+$/m", $get['tempTag']) + ); + $page = $this->getService(PageManager::class)->getOne($tag); + $aclService = $this->getService(AclService::class); + + return (( + empty($page) // new page + && $aclService->hasAccess('write', $tag) // default rights to write + ) || ( + !empty($page) // existing page + && $aclService->hasAccess('write', $tag) + ) || ( + !empty($page) // existing page + && $aclService->hasAccess('read', $tag) // page with cration of entries + && $this->hasTempTag + ) + ); + } + + private function formatOuput(array $ouput): string + { + return htmlspecialchars(json_encode($ouput), ENT_NOQUOTES, YW_CHARSET); + } +} diff --git a/tools/attach/handlers/page/__edit.php b/tools/attach/handlers/page/__edit.php index 5725a07c2..6acd05092 100644 --- a/tools/attach/handlers/page/__edit.php +++ b/tools/attach/handlers/page/__edit.php @@ -1,2 +1,11 @@ AddJavascriptFile('tools/attach/libs/fileuploader.js'); + +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use YesWiki\Core\Service\AssetsManager; + +$this->services->get(AssetsManager::class)->AddJavascript( + "var fileUploaderConfig = {attach_config:{ext_images:" + .json_encode(explode("|", $this->services->get(ParameterBagInterface::class)->get("attach_config")["ext_images"])) + ."}};" +); +$this->services->get(AssetsManager::class)->AddJavascriptFile('tools/attach/libs/fileuploader.js'); diff --git a/tools/attach/libs/attach.lib.php b/tools/attach/libs/attach.lib.php index f2eccc426..f123206cb 100644 --- a/tools/attach/libs/attach.lib.php +++ b/tools/attach/libs/attach.lib.php @@ -34,6 +34,8 @@ # voir actions/attach.php ppour la documentation # copyrigth Eric Feldstein 2003-2004 +use enshrined\svgSanitize\Sanitizer; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use YesWiki\Core\Service\LinkTracker; if (!defined("WIKINI_VERSION")) { @@ -62,74 +64,24 @@ class attach public $pageId = 0; //identifiant de la page public $isSafeMode = true; //indicateur du safe mode de PHP public $data = ''; //indicateur du safe mode de PHP + private $params; /** * Constructeur. Met les valeurs par defaut aux parametres de configuration */ public function __construct(&$wiki) { $this->wiki = $wiki; - $this->attachConfig = $this->wiki->GetConfigValue("attach_config"); + $this->params = $this->wiki->services->get(ParameterBagInterface::class); + $this->attachConfig = $this->params->get("attach_config"); if (!is_array($this->attachConfig)) { - $this->attachConfig = array(); - } - - if (empty($this->attachConfig["ext_images"])) { - $this->attachConfig["ext_images"] = "gif|jpeg|png|jpg|svg|webp"; - } - - if (empty($this->attachConfig["ext_audio"])) { - $this->attachConfig["ext_audio"] = "mp3|aac"; - } - - if (empty($this->attachConfig["ext_video"])) { - $this->attachConfig["ext_video"] = "mp4|webm|ogg"; - } - - if (empty($this->attachConfig["ext_wma"])) { - $this->attachConfig["ext_wma"] = "wma"; - } - - if (empty($this->attachConfig["ext_pdf"])) { - $this->attachConfig["ext_pdf"] = "pdf"; - } - - if (empty($this->attachConfig["ext_freemind"])) { - $this->attachConfig["ext_freemind"] = "mm"; - } - - if (empty($this->attachConfig["ext_flashvideo"])) { - $this->attachConfig["ext_flashvideo"] = "flv"; - } - - if (empty($this->attachConfig["ext_script"])) { - $this->attachConfig["ext_script"] = "php|php3|asp|asx|vb|vbs|js"; - } - - if (empty($this->attachConfig['upload_path'])) { - $this->attachConfig['upload_path'] = 'files'; - } - - if (empty($this->attachConfig['update_symbole'])) { - $this->attachConfig['update_symbole'] = ''; + throw new Exception("attach_config should be an array in wakka.config.php"); } if (empty($this->attachConfig['max_file_size'])) { $this->attachConfig['max_file_size'] = $this->wiki->GetConfigValue("max_file_size") ? $this->wiki->GetConfigValue("max_file_size") : $this->file_upload_max_size(); } - if (empty($this->attachConfig['fmDelete_symbole'])) { - $this->attachConfig['fmDelete_symbole'] = 'Supr'; - } - - if (empty($this->attachConfig['fmRestore_symbole'])) { - $this->attachConfig['fmRestore_symbole'] = 'Rest'; - } - - if (empty($this->attachConfig['fmTrash_symbole'])) { - $this->attachConfig['fmTrash_symbole'] = 'Corbeille'; - } - $safemode = $this->wiki->GetConfigValue("no_safe_mode"); if (empty($safemode)) { if (version_compare(phpversion(), '5.3', '<')) { @@ -783,6 +735,9 @@ public function performUpload() $srcFile = $_FILES['upFile']['tmp_name']; if (move_uploaded_file($srcFile, $destFile)) { chmod($destFile, 0644); + if ($ext === "svg") { + $this->sanitizeSVGfile($destFile); + } header("Location: " . $this->wiki->href("", $this->wiki->GetPageTag(), "")); } else { echo "
" . _t('ERROR_MOVING_TEMPORARY_FILE') . "
\n"; @@ -1222,5 +1177,24 @@ public function redimensionner_image($image_src, $image_dest, $largeur, $hauteur return $imgTrans->targetFile; } } + + /** + * @param string $content of svg + * @return string $content + */ + public function sanitizeSVG(string $content): string + { + $sanitizer = new Sanitizer(); + return $sanitizer->sanitize($content); + } + + /** + * @param string $filePath svg + */ + public function sanitizeSVGfile(string $filePath) + { + $content = file_get_contents($filePath); + file_put_contents($filePath, $this->sanitizeSVG($content)); + } } } diff --git a/tools/attach/libs/fileuploader.js b/tools/attach/libs/fileuploader.js index 8da7f19b4..e3d125a01 100755 --- a/tools/attach/libs/fileuploader.js +++ b/tools/attach/libs/fileuploader.js @@ -1416,7 +1416,7 @@ qq.extend(qq.UploadHandlerXhr.prototype, { UploadModal.find('.modal-title').append(fileuploaded).append(filesize); // If it's an image - if ((responseJSON.extension === 'jpg') || (responseJSON.extension === 'jpeg') || (responseJSON.extension === 'gif') || (responseJSON.extension === 'png')) { + if (typeof fileUploaderConfig !== "undefined" && fileUploaderConfig.attach_config.ext_images.indexOf(responseJSON.extension) > -1) { imageinput.show(); hiddenfilenameinput.val(responseJSON.simplefilename); UploadModal.find('.attach_alt').val('image ' + responseJSON.simplefilename + ' (' + filesize.text() + ')'); diff --git a/tools/attach/handlers/page/ajaxupload.php b/tools/attach/libs/qq.lib.php old mode 100755 new mode 100644 similarity index 84% rename from tools/attach/handlers/page/ajaxupload.php rename to tools/attach/libs/qq.lib.php index 674855738..c738e456f --- a/tools/attach/handlers/page/ajaxupload.php +++ b/tools/attach/libs/qq.lib.php @@ -1,20 +1,8 @@ config['temp_tag_for_entry_creation']}_[A-Fa-f0-9]+$/m", $_GET['tempTag'])); - -if ( - ($this->LoadPage($this->getPageTag()) && $this->HasAccess('write')) - || ($this->HasAccess('read') && $hasTempTag) -) { - /** - * Handle file uploads via XMLHttpRequest - */ +if (!class_exists('qqUploadedFileXhr')) { class qqUploadedFileXhr { /** @@ -52,7 +40,9 @@ public function getSize() } } } +} +if (!class_exists('qqUploadedFileForm')) { /** * Handle file uploads via regular form post (uses the $_FILES array) */ @@ -78,7 +68,9 @@ public function getSize() return $_FILES['qqfile']['size']; } } +} +if (!class_exists('qqFileUploader')) { class qqFileUploader { private $allowedExtensions = array(); @@ -195,7 +187,7 @@ public function handleUpload($uploadDirectory, $replaceOldFile = false) $GLOBALS['wiki']->setParameter("file", $filename . '.' . $ext); // dans le cas d'une nouvelle page, on donne une valeur a la date de création dans le fuseau horaire du serveur (heure SQL) - if ($this->hasTempTag || $GLOBALS['wiki']->page['time'] == '') { + if ($this->hasTempTag || !isset($GLOBALS['wiki']->page['time']) || $GLOBALS['wiki']->page['time'] == '') { $dbTz = $GLOBALS['wiki']->services->get(DbService::class)->getDbTimeZone(); $sqlTimeFormat = 'Y-m-d H:i:s'; $GLOBALS['wiki']->page['time'] = !empty($dbTz) ? (new DateTime())->setTimezone(new DateTimeZone($dbTz))->format($sqlTimeFormat) : date($sqlTimeFormat); @@ -212,6 +204,9 @@ public function handleUpload($uploadDirectory, $replaceOldFile = false) ob_end_clean(); if ($this->file->save($fullfilename)) { + if ($ext === "svg") { + $attach->sanitizeSVGfile($fullfilename); + } return array_map('utf8_encode', array('success'=>true, 'filename'=>$fullfilename, 'simplefilename'=>$filename . '.' . $ext, 'extension'=>$ext)); } else { return array_map( @@ -223,32 +218,4 @@ public function handleUpload($uploadDirectory, $replaceOldFile = false) } } } - - if (!class_exists('attach')) { - include_once 'tools/attach/libs/attach.lib.php'; - } - ob_start(); - $att = new attach($this); - - // list of valid extensions, ex. array("jpeg", "xml", "bmp") - $allowedExtensions = array_keys($this->config['authorized-extensions']); - - // max file size in bytes - $sizeLimit = $att->attachConfig['max_file_size']; - - $uploader = new qqFileUploader($allowedExtensions, $sizeLimit, $hasTempTag); - $result = $uploader->handleUpload($att->attachConfig['upload_path']); - - - unset($att); - $errorsMessage = ob_get_contents(); - ob_end_clean(); - if (!empty($errorsMessage)) { - $result['errorMessage'] = $errorsMessage; - } - unset($errorsMessage); -} else { - $result = array('error' => _t('NO_RIGHT_TO_WRITE_IN_THIS_PAGE')); } -// to pass data through iframe you will need to encode all html tags -echo htmlspecialchars(json_encode($result), ENT_NOQUOTES, YW_CHARSET); diff --git a/tools/bazar/fields/TextareaField.php b/tools/bazar/fields/TextareaField.php index 6d56e0b29..5741e7986 100644 --- a/tools/bazar/fields/TextareaField.php +++ b/tools/bazar/fields/TextareaField.php @@ -5,6 +5,7 @@ use DateTime; use DateTimeZone; use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use YesWiki\Core\Service\DbService; /** @@ -101,7 +102,7 @@ protected function renderInput($entry) ob_start(); include_once 'tools/aceditor/actions/actions_builder.php'; $output = ob_get_contents(); - ob_end_clean(); + ob_end_clean(); } } @@ -113,6 +114,7 @@ protected function renderInput($entry) 'value' => $this->getValue($entry), 'entryId' => $entry['id_fiche'] ?? null, 'tempTag' => $tempTag, + 'attachConfigExtImages' => explode("|", $this->getService(ParameterBagInterface::class)->get("attach_config")["ext_images"]) ]); } diff --git a/tools/bazar/templates/inputs/textarea.twig b/tools/bazar/templates/inputs/textarea.twig index 8a85ac46f..3d1f27121 100644 --- a/tools/bazar/templates/inputs/textarea.twig +++ b/tools/bazar/templates/inputs/textarea.twig @@ -29,6 +29,9 @@
{{ include('@attach/attach-file-uploader-button.twig', {fileUploaderAnchor:'#' ~ field.name ~ 'Container .aceditor-toolbar', fileUploaderSelector:'#' ~ field.name }) }}
+ {{ include_javascript('tools/attach/libs/fileuploader.js') }} {% endif %}