Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 49 additions & 73 deletions ClamavPlugin.inc.php → ClamavPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,23 @@
*
* @brief ClamAV plugin class
*/
import('lib.pkp.classes.plugins.GenericPlugin');

namespace APP\plugins\generic\clamav;

use PKP\plugins\GenericPlugin;
use PKP\config\Config;
use PKP\plugins\Hook;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\AjaxModal;
use PKP\core\JSONMessage;
use PKP\core\PKPApplication;
use PKP\file\TemporaryFileManager;
use Exception;
use APP\core\Application;
use APP\plugins\generic\clamav\ClamavSettingsForm;
use APP\plugins\generic\clamav\ClamavVersionHandler;
use APP\template\TemplateManager;

class ClamScanFailureException extends Exception {};

class ClamavPlugin extends GenericPlugin {
Expand All @@ -34,9 +50,9 @@ function register($category, $path, $mainContextId = NULL) {
return true;
if ($success && $this->getEnabled()) {
// Enable Clam AV's preprocessing of uploaded files
HookRegistry::register('SubmissionFile::validate', array($this, 'clamscanHandleUpload'));
Hook::add('submissionfilesuploadform::validate', array($this, 'clamscanHandleUpload'));
// Create handler for AJAX call
HookRegistry::register('LoadHandler', array($this, 'setPageHandler'));
Hook::add('LoadHandler', array($this, 'setPageHandler'));
}
return $success;
}
Expand Down Expand Up @@ -65,30 +81,12 @@ function getDescription() {
function isSitePlugin() {
return true;
}

/**
* Backwards-compatible method to retrieve the current context across
* multiple versions of PKP applicatiosn
* @return
*/
function _getBackwardsCompatibleContext() {
if(method_exists('Application', 'get')) {
// OJS 3.2 and later
$request = Application::get()->getRequest();
$context = $request->getContext();
} else {
// OJS 3.1.2 and earlier
$context = Request::getContext();
}
return $context;
}

/**
* @copydoc Plugin::getActions()
*/
function getActions($request, $verb) {
$router = $request->getRouter();
import('lib.pkp.classes.linkAction.request.AjaxModal');
return array_merge(
$this->getEnabled() ? array(
new LinkAction(
Expand All @@ -106,14 +104,13 @@ function getActions($request, $verb) {
function manage($args, $request) {
switch ($request->getUserVar('verb')) {
case 'settings':
$contextID = (!is_null($this->_getBackwardsCompatibleContext()) ? $this->_getBackwardsCompatibleContext()->getId() : CONTEXT_SITE);
$context = Application::get()->getRequest()->getContext();
$contextID = $context->getId();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is some risk to this change.

Previously, we checked to see if getContext() returned null, and if so, substituted PKPApplication::CONTEXT_SITE for the context id. The current code presumes that $context must not be null.

This can be checked by trying to manage the plugin from the Site level, rather than from the journal level. Or, we could re-introduce the if statement presumptively.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#23


AppLocale::requireComponents(LOCALE_COMPONENT_APP_COMMON, LOCALE_COMPONENT_PKP_MANAGER);
$templateMgr = TemplateManager::getManager($request);

$templateMgr->register_function('plugin_url', array($this, 'smartyPluginUrl'));
$templateMgr->registerPlugin('function','plugin_url', array($this, 'smartyPluginUrl'));

$this->import('ClamavSettingsForm');
$form = new ClamavSettingsForm($this, $contextID);

if ($request->getUserVar('save')) {
Expand All @@ -138,13 +135,7 @@ function manage($args, $request) {
* @copydoc PKPPlugin::getTemplatePath
*/
function getTemplatePath($inCore = false) {
if(method_exists($this, 'getTemplateResource')) {
// OJS 3.1.2 and later
return parent::getTemplatePath($inCore);
} else {
// OJS 3.1.1 and earlier 3.x releases
return parent::getTemplatePath($inCore) . 'templates' . DIRECTORY_SEPARATOR;
}
return parent::getTemplatePath($inCore);
}

/**
Expand All @@ -155,7 +146,7 @@ function getTemplatePath($inCore = false) {
* */
function getClamVersion($path = '', $type=self::TYPE_EXECUTABLE) {
if ($path === '') {
$path = $this->getSetting(CONTEXT_SITE, 'clamavPath');
$path = $this->getSetting(PKPApplication::CONTEXT_SITE, 'clamavPath');
}
if ($type === self::TYPE_EXECUTABLE && !empty($path) && is_executable($path)) {
$version = exec($path . ' --version');
Expand Down Expand Up @@ -194,7 +185,7 @@ function _clamscanFile($uploadedFile) {
if ($this->getClamVersion() && !empty($uploadedFile)) {
$output = "";
$exitCode = "";
$clamAVPath = $this->getSetting(CONTEXT_SITE, 'clamavPath');
$clamAVPath = $this->getSetting(PKPApplication::CONTEXT_SITE, 'clamavPath');
$scan = exec($clamAVPath . ' --no-summary -i ' . $uploadedFile, $output, $exitCode);
//process the result
preg_match('/:(.*)/',$output[0],$matches);
Expand All @@ -220,7 +211,7 @@ function _clamscanFile($uploadedFile) {
* @return boolean|string
*/
function _clamDaemonFile($uploadedFile) {
$clamavSocketPath = $this->getSetting(CONTEXT_SITE, 'clamavSocketPath');
$clamavSocketPath = $this->getSetting(PKPApplication::CONTEXT_SITE, 'clamavSocketPath');
$maxInstreamSize = 1024; // TODO: need to universalize this based on a plugin setting
$output = "";
$errno = 0;
Expand Down Expand Up @@ -292,8 +283,8 @@ function _clamDaemonFile($uploadedFile) {
function _clamDaemonShortPolling($socket, $delay = 100000000, $intervals = 10) {
// author's note: cludge cludge cludge cludge. TODO: look into long polling for unix sockets?
$output = "";
$intervals = $this->getSetting(CONTEXT_SITE, 'clamavSocketTimeout')*10;
$failover = $this->getSetting(CONTEXT_SITE, 'allowUnscannedFiles');
$intervals = $this->getSetting(PKPApplication::CONTEXT_SITE, 'clamavSocketTimeout')*10;
$failover = $this->getSetting(PKPApplication::CONTEXT_SITE, 'allowUnscannedFiles');

for ($i = 0; $i < $intervals; $i++) {
// sleep() does not accept fractional seconds and usleep uses a surprising amount of CPU, leaving us with time_nanosleep().
Expand All @@ -315,66 +306,51 @@ function _clamDaemonShortPolling($socket, $delay = 100000000, $intervals = 10) {
* @see submissionfilesuploadform::validate()
*/
function clamscanHandleUpload($hookName, $args) {
$fileId = $args[2]['fileId'];
$props = $args[2];
$allowedLocales = $args[3];
$file = Services::get('file')->get($fileId);
$path = $file->path;
$uploadedFile = Config::getVar('files', 'files_dir') . '/' . $path;
$uploadedFile = $_FILES['uploadedFile']['tmp_name'];
//scan for viruses if file exists
if (null !== $file) {
$useSocket = $this->getSetting(CONTEXT_SITE, 'clamavUseSocket');
import('lib.pkp.classes.validation.ValidatorFactory');
$schemaService = Services::get('schema');
if (null !== $uploadedFile) {
$useSocket = $this->getSetting(PKPApplication::CONTEXT_SITE, 'clamavUseSocket');
$rejectionMessage = array();
$validator = \ValidatorFactory::make(
$props,
$schemaService->getValidationRules(SCHEMA_SUBMISSION_FILE, $allowedLocales),
$rejectionMessage
);
try {
if ($useSocket === true) {
$message = $this->_clamDaemonFile($uploadedFile);
} else {
$message = $this->_clamscanFile($uploadedFile);
$tempFileManager = new TemporaryFileManager();
$request = Application::get()->getRequest();
$user = $request->getUser();
$userId = $user->getId();
$fileId = $tempFileManager->createTempFileFromExisting($uploadedFile, $userId);
$file = $tempFileManager->getFile($fileId, $userId);
$tempFilePath = $file->getFilePath();
$message = $this->_clamscanFile($tempFilePath);
$tempFileManager->deleteById($fileId, $userId);
}
//No viruses found! Continue with submission
if ($message === false) {
return false;
} else {
if ($message !== false) {
//ClamAV reported a virus or failed to complete the scan
//Prepare to notify the user and halt the upload process
$rejectionMessage = ["threatname"=>$message];
//If Clam found a virus, it will return the signature name as a string
if ($message == true) {
//create a user notification
$validator->errors()->add('clamAV::virusDetected', __('plugins.generic.clamav.uploadBlocked',$rejectionMessage));
$args[0]->addError('clamAV::virusDetected', __('plugins.generic.clamav.uploadBlocked',$rejectionMessage));
}
}
}
//Scanning errors will generate a custom exception
catch (ClamScanFailureException $e){
//Couldn't scan, but ClamAV plugin settings are permissive, continue with submission anyway
$setting=$this->getSetting(CONTEXT_SITE, 'allowUnscannedFiles');
if ( $this->getSetting(CONTEXT_SITE, 'allowUnscannedFiles')===self::UNSCANNED_ALLOW) {
return false;
} else {
$setting=$this->getSetting(PKPApplication::CONTEXT_SITE, 'allowUnscannedFiles');
if ( $this->getSetting(PKPApplication::CONTEXT_SITE, 'allowUnscannedFiles')!==self::UNSCANNED_ALLOW) {
//Otherwise notify the user that there was an error
//the user will see the general error message from the locale file
$validator->errors()->add('clamAV::failedToScan', __('plugins.generic.clamav.error'));
$args[0]->addError('clamAV::failedToSCan', __('plugins.generic.clamav.error'));
}
}

//The file is considered unsafe. Interrupt submission process and clean up the working copy/metadata
$errors = $schemaService->formatValidationErrors($validator->errors(), $schemaService->get(SCHEMA_SUBMISSION_FILE), $allowedLocales);

if ($args[0]===null){
$args[0]=$errors;
} elseif (is_array($args[0])) {
array_push($args[0],$errors);
}
// returning true aborts processing
return true;
$args[1] = false;
return false;

}
}
Expand All @@ -389,9 +365,9 @@ function clamscanHandleUpload($hookName, $args) {
public function setPageHandler($hookName, $params) {
$page = $params[0];
$op = $params[1];
$handler = $params[3];
if ($this->getEnabled() && $page === 'clamav' && $op === 'clamavVersion') {
$this->import('ClamavVersionHandler');
define('HANDLER_CLASS', 'ClamavVersionHandler');
$params[3] = new ClamavVersionHandler();
return true;
}
return false;
Expand Down
47 changes: 25 additions & 22 deletions ClamavSettingsForm.inc.php → ClamavSettingsForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@
*
* @brief Form for the site admin to modify Clam AV plugin settings
*/
import('lib.pkp.classes.form.Form');

namespace APP\plugins\generic\clamav;

use PKP\form\Form;
use PKP\form\validation\FormValidator;
use PKP\form\validation\FormValidatorPost;
use PKP\form\validation\FormValidatorCSRF;
use PKP\core\PKPApplication;
use APP\core\Application;
use APP\template\TemplateManager;

class ClamavSettingsForm extends Form {

Expand All @@ -29,16 +38,10 @@ class ClamavSettingsForm extends Form {
function __construct($plugin, $contextId) {
$this->_contextId = $contextId;
$this->_plugin = $plugin;

if (method_exists($plugin, 'getTemplateResource')) {
// OJS 3.1.2 and later
parent::__construct($plugin->getTemplateResource('settingsForm.tpl'));
} else {
// OJS 3.1.1 and earlier
parent::__construct($plugin->getTemplatePath() . 'settingsForm.tpl');
}

$this->addCheck(new FormValidator($this, 'clamavPath', FORM_VALIDATOR_OPTIONAL_VALUE, 'plugins.generic.clamav.manager.settings.clamavPathRequired'));

parent::__construct($plugin->getTemplateResource('settingsForm.tpl'));

$this->addCheck(new FormValidator($this, 'clamavPath', FormValidator::FORM_VALIDATOR_OPTIONAL_VALUE, 'plugins.generic.clamav.manager.settings.clamavPathRequired'));
$this->addCheck(new FormValidatorPost($this));
$this->addCheck(new FormValidatorCSRF($this));
}
Expand All @@ -51,18 +54,18 @@ function initData() {
$request = Application::get()->getRequest();
$basePluginUrl = $request->getBaseUrl() . DIRECTORY_SEPARATOR . $plugin->getPluginPath() . DIRECTORY_SEPARATOR;

$this->setData('clamavPath', $plugin->getSetting(CONTEXT_SITE, 'clamavPath'));
$this->setData('clamavUseSocket', $plugin->getSetting(CONTEXT_SITE, 'clamavUseSocket'));
$this->setData('clamavSocketPath', $plugin->getSetting(CONTEXT_SITE, 'clamavSocketPath'));
$this->setData('unscannedFileOption', $plugin->getSetting(CONTEXT_SITE, 'allowUnscannedFiles'));
$this->setData('clamavSocketTimeout', $plugin->getSetting(CONTEXT_SITE, 'clamavSocketTimeout'));
$this->setData('clamavPath', $plugin->getSetting(PKPApplication::CONTEXT_SITE, 'clamavPath'));
$this->setData('clamavUseSocket', $plugin->getSetting(PKPApplication::CONTEXT_SITE, 'clamavUseSocket'));
$this->setData('clamavSocketPath', $plugin->getSetting(PKPApplication::CONTEXT_SITE, 'clamavSocketPath'));
$this->setData('unscannedFileOption', $plugin->getSetting(PKPApplication::CONTEXT_SITE, 'allowUnscannedFiles'));
$this->setData('clamavSocketTimeout', $plugin->getSetting(PKPApplication::CONTEXT_SITE, 'clamavSocketTimeout'));


$this->setData('pluginJavascriptURL', $basePluginUrl . 'js' . DIRECTORY_SEPARATOR);
$this->setData('pluginStylesheetURL', $basePluginUrl . 'css' . DIRECTORY_SEPARATOR);
$this->setData('pluginLoadingImageURL', $basePluginUrl . 'images' . DIRECTORY_SEPARATOR . "spinner.gif");
$this->setData('pluginLoadingImageURL', $basePluginUrl . 'images' . DIRECTORY_SEPARATOR . "spinner.gif");
$this->setData('pluginAjaxUrl', $request->getDispatcher()->url($request, ROUTE_PAGE, null, 'clamav', 'clamavVersion'));
$this->setData('pluginAjaxUrl', $request->getDispatcher()->url($request, PKPApplication::ROUTE_PAGE, null, 'clamav', 'clamavVersion'));

$this->setData('baseUrl', $request->getBaseUrl());
}
Expand Down Expand Up @@ -106,11 +109,11 @@ function execute(...$functionArgs) {
$allowUnscannedFiles = $this->_plugin::UNSCANNED_DEFAULT;
}

$this->_plugin->updateSetting(CONTEXT_SITE, 'clamavPath', $this->getData('clamavPath'), 'string');
$this->_plugin->updateSetting(CONTEXT_SITE, 'clamavUseSocket', $this->getData('clamavUseSocket'), 'bool');
$this->_plugin->updateSetting(CONTEXT_SITE, 'clamavSocketPath', $this->getData('clamavSocketPath'), 'string');
$this->_plugin->updateSetting(CONTEXT_SITE, 'clamavSocketTimeout', $clamavSocketTimeout, 'int');
$this->_plugin->updateSetting(CONTEXT_SITE, 'allowUnscannedFiles', $allowUnscannedFiles, 'string');
$this->_plugin->updateSetting(PKPApplication::CONTEXT_SITE, 'clamavPath', $this->getData('clamavPath'), 'string');
$this->_plugin->updateSetting(PKPApplication::CONTEXT_SITE, 'clamavUseSocket', $this->getData('clamavUseSocket'), 'bool');
$this->_plugin->updateSetting(PKPApplication::CONTEXT_SITE, 'clamavSocketPath', $this->getData('clamavSocketPath'), 'string');
$this->_plugin->updateSetting(PKPApplication::CONTEXT_SITE, 'clamavSocketTimeout', $clamavSocketTimeout, 'int');
$this->_plugin->updateSetting(PKPApplication::CONTEXT_SITE, 'allowUnscannedFiles', $allowUnscannedFiles, 'string');
}

/**
Expand Down
9 changes: 8 additions & 1 deletion ClamavVersionHandler.inc.php → ClamavVersionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@
* @brief Handle router requests for the clam AV version for the clam AV plugin.
*/

import('classes.handler.Handler');

namespace APP\plugins\generic\clamav;

use PKP\core\JSONMessage;
use PKP\security\Validation;
use PKP\plugins\PluginRegistry;
use APP\template\TemplateManager;
use APP\handler\Handler;

class ClamavVersionHandler extends Handler {
/**
Expand Down
4 changes: 0 additions & 4 deletions css/clamav.css
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,6 @@
content: "";
}

#clamdSocketAdvanced div.full_description {

}

#clamdSocketAdvancedSettings > div.pkp_helpers_quarter.inline {
/*
* sets this as a containing block, against which other absolutely-
Expand Down
22 changes: 0 additions & 22 deletions index.php

This file was deleted.

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion version.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<version>
<application>clamav</application>
<type>plugins.generic</type>
<release>4.0.0.3</release>
<release>4.1.0.0</release>
<date>2024-06-10</date>
<lazy-load>1</lazy-load>
<class>ClamavPlugin</class>
Expand Down