From e17d8f4f0e4f0d2044fec7dec061374686f9ad8d Mon Sep 17 00:00:00 2001 From: Roly Rudy Gutierrez Pinto Date: Wed, 27 Feb 2019 19:48:45 -0400 Subject: [PATCH] PMC-313 --- .../Extension/Restler/UploadFormat.php | 14 +++ .../translations/english/processmaker.en.po | 12 +++ workflow/engine/controllers/designer.php | 11 ++- workflow/engine/data/mysql/insert.sql | 2 + .../engine/methods/cases/cases_SaveData.php | 55 ++---------- .../BusinessModel/InputDocument.php | 87 ++++++++++++++++++- .../src/ProcessMaker/BusinessModel/Light.php | 1 + .../ProcessMaker/Util/PhpShorthandByte.php | 82 +++++++++++++++++ .../Validation/ValidationUploadedFiles.php | 70 +++++++++++++++ workflow/engine/templates/designer/index.html | 2 + 10 files changed, 282 insertions(+), 54 deletions(-) create mode 100644 workflow/engine/src/ProcessMaker/Util/PhpShorthandByte.php diff --git a/framework/src/Maveriks/Extension/Restler/UploadFormat.php b/framework/src/Maveriks/Extension/Restler/UploadFormat.php index be0e01655..5f85d8ebd 100644 --- a/framework/src/Maveriks/Extension/Restler/UploadFormat.php +++ b/framework/src/Maveriks/Extension/Restler/UploadFormat.php @@ -2,6 +2,7 @@ namespace Luracast\Restler\Format; use Luracast\Restler\RestException; +use ProcessMaker\Validation\ValidationUploadedFiles; /** * Extending UploadFormat Support for Multi Part Form Data and File Uploads @@ -84,8 +85,21 @@ class UploadFormat extends Format throw new RestException(500, 'UploadFormat is read only'); } + /** + * Decode request. + * + * @param mixed $data + * @return array + * @throws RestException + * + * @see Luracast\Restler\CommentParser->parseEmbeddedData() + */ public function decode($data) { + $runRulesForFileEmpty = ValidationUploadedFiles::getValidationUploadedFiles()->runRulesForFileEmpty(); + if ($runRulesForFileEmpty->fails()) { + throw new RestException($runRulesForFileEmpty->getStatus(), $runRulesForFileEmpty->getMessage()); + } $doMimeCheck = !empty(self::$allowedMimeTypes); $doSizeCheck = self::$maximumFileSize ? TRUE : FALSE; //validate diff --git a/workflow/engine/content/translations/english/processmaker.en.po b/workflow/engine/content/translations/english/processmaker.en.po index da8a87367..74b5c8081 100644 --- a/workflow/engine/content/translations/english/processmaker.en.po +++ b/workflow/engine/content/translations/english/processmaker.en.po @@ -24857,6 +24857,18 @@ msgstr "Error: The application {0} is not canceled." msgid "The default configuration was not defined" msgstr "The default configuration was not defined" +# TRANSLATION +# LABEL/ID_THE_FILE_SIZE_IS_BIGGER_THAN_THE_MAXIMUM_ALLOWED +#: LABEL/ID_THE_FILE_SIZE_IS_BIGGER_THAN_THE_MAXIMUM_ALLOWED +msgid "The file size is bigger than the maximum allowed, the maximum size allowed is {0} Mbytes." +msgstr "The file size is bigger than the maximum allowed, the maximum size allowed is {0} Mbytes." + +# TRANSLATION +# LABEL/ID_THE_MAXIMUM_VALUE_OF_THIS_FIELD_IS +#: LABEL/ID_THE_MAXIMUM_VALUE_OF_THIS_FIELD_IS +msgid "The maximum value of this field is {0}." +msgstr "The maximum value of this field is {0}." + # TRANSLATION # LABEL/ID_THE_MIMETYPE_EXTENSION_ERROR #: LABEL/ID_THE_MIMETYPE_EXTENSION_ERROR diff --git a/workflow/engine/controllers/designer.php b/workflow/engine/controllers/designer.php index 44772077b..8f5efd29f 100644 --- a/workflow/engine/controllers/designer.php +++ b/workflow/engine/controllers/designer.php @@ -11,9 +11,10 @@ use ProcessMaker\Plugins\PluginRegistry; */ use Maveriks\Util\ClassLoader; -use \OAuth2\Request; -use \ProcessMaker\BusinessModel\Light\Tracker; -use \ProcessMaker\Services\OAuth2\Server; +use OAuth2\Request; +use ProcessMaker\BusinessModel\InputDocument; +use ProcessMaker\BusinessModel\Light\Tracker; +use ProcessMaker\Services\OAuth2\Server; class Designer extends Controller { @@ -27,6 +28,8 @@ class Designer extends Controller * Index Action * * @param string $httpData (opional) + * + * @see Controller->call() */ public function index($httpData) { @@ -65,6 +68,8 @@ class Designer extends Controller $this->setVar("SYS_LANG", SYS_LANG); $this->setVar("SYS_SKIN", SYS_SKIN); $this->setVar('HTTP_SERVER_HOSTNAME', System::getHttpServerHostnameRequestsFrontEnd()); + $inpuDocument = new InputDocument(); + $this->setVar('maxFileSizeInformation', G::json_encode($inpuDocument->getMaxFileSize())); if ($debug) { if (!file_exists(PATH_HTML . "lib-dev/pmUI/build.cache")) { diff --git a/workflow/engine/data/mysql/insert.sql b/workflow/engine/data/mysql/insert.sql index 512c573c1..338525eed 100644 --- a/workflow/engine/data/mysql/insert.sql +++ b/workflow/engine/data/mysql/insert.sql @@ -61036,6 +61036,8 @@ INSERT INTO TRANSLATION (TRN_CATEGORY,TRN_ID,TRN_LANG,TRN_VALUE,TRN_UPDATE_DATE ( 'LABEL','ID_THERE_PROBLEM_SENDING_EMAIL','en','There was a problem sending the email to','2016-04-08') , ( 'LABEL','ID_THE_APPLICATION_IS_NOT_CANCELED','en','Error: The application {0} is not canceled.','2016-06-15') , ( 'LABEL','ID_THE_DEFAULT_CONFIGURATION','en','The default configuration was not defined','2016-11-16') , +( 'LABEL','ID_THE_FILE_SIZE_IS_BIGGER_THAN_THE_MAXIMUM_ALLOWED','en','The file size is bigger than the maximum allowed, the maximum size allowed is {0} Mbytes.','2019-02-26') , +( 'LABEL','ID_THE_MAXIMUM_VALUE_OF_THIS_FIELD_IS','en','The maximum value of this field is {0}.','2019-02-26') , ( 'LABEL','ID_THE_MIMETYPE_EXTENSION_ERROR','en','The mime type does not correspond to the permitted extension, please verify your file.','2018-10-2') , ( 'LABEL','ID_THE_NAME_CHANGE_MAY_CAUSE_DATA_LOSS','en','The change might cause data loss in the PM table. Do you want to continue?','2017-03-30') , ( 'LABEL','ID_THE_PHP_FILES_EXECUTION_WAS_DISABLED','en','The PHP files execution was disabled please contact the system administrator.','2018-04-20') , diff --git a/workflow/engine/methods/cases/cases_SaveData.php b/workflow/engine/methods/cases/cases_SaveData.php index d37f30f47..a218bd604 100644 --- a/workflow/engine/methods/cases/cases_SaveData.php +++ b/workflow/engine/methods/cases/cases_SaveData.php @@ -1,26 +1,7 @@ . - * - * For more information, contact Colosa Inc, 2566 Le Jeune Rd., - * Coral Gables, FL, 33134, USA, or email info@colosa.com. - */ + +use ProcessMaker\Validation\ValidationUploadedFiles; + //validate the data post if (!isset($_SESSION['USER_LOGGED'])) { if(!strpos($_SERVER['REQUEST_URI'], 'gmail')) { @@ -69,32 +50,12 @@ if (!isset($_SESSION['USER_LOGGED'])) { } } -/** - * If you can, you may want to set post_max_size to a low value (say 1M) to make - * testing easier. First test to see how your script behaves. Try uploading a file - * that is larger than post_max_size. If you do you will get a message like this - * in your error log: - * - * [09-Jun-2010 19:28:01] PHP Warning: POST Content-Length of 30980857 bytes exceeds - * the limit of 2097152 bytes in Unknown on line 0 - * - * This makes the script is not completed. - * - * Solving the problem: - * The PHP documentation http://php.net/manual/en/ini.core.php#ini.post-max-size - * provides a hack to solve this problem: - * - * If the size of post data is greater than post_max_size, the $_POST and $_FILES - * superglobals are empty. - */ -if ($_SERVER['REQUEST_METHOD'] == 'POST' && empty($_POST) && empty($_FILES) && $_SERVER['CONTENT_LENGTH'] > 0) { - $aMessage = array(); - $aMessage['MESSAGE'] = G::loadTranslation('ID_UPLOAD_ERR_INI_SIZE'); - $G_PUBLISH = new Publisher(); - $G_PUBLISH->AddContent('xmlform', 'xmlform', 'login/showMessage', '', $aMessage); - G::RenderPage('publish', 'blank'); +ValidationUploadedFiles::getValidationUploadedFiles()->dispatch(function($validator) { + G::SendMessageText($validator->getMessage(), "ERROR"); + $url = explode("sys" . config("system.workspace"), $_SERVER['HTTP_REFERER']); + G::header("location: " . "/sys" . config("system.workspace") . $url[1]); die(); -} +}); try { if ($_GET['APP_UID'] !== $_SESSION['APPLICATION']) { diff --git a/workflow/engine/src/ProcessMaker/BusinessModel/InputDocument.php b/workflow/engine/src/ProcessMaker/BusinessModel/InputDocument.php index 62122e104..3fc84d723 100644 --- a/workflow/engine/src/ProcessMaker/BusinessModel/InputDocument.php +++ b/workflow/engine/src/ProcessMaker/BusinessModel/InputDocument.php @@ -1,7 +1,10 @@ doPostInputDocument() + * @link https://wiki.processmaker.com/3.0/Input_Documents#Creating_Input_Documents */ public function create($processUid, $arrayData) { @@ -310,6 +316,8 @@ class InputDocument $flagDataDestinationPath = (isset($arrayData["INP_DOC_DESTINATION_PATH"]))? 1 : 0; $flagDataTags = (isset($arrayData["INP_DOC_TAGS"]))? 1 : 0; + $this->throwExceptionIfMaximumFileSizeExceed(intval($arrayData["INP_DOC_MAX_FILESIZE"]), $arrayData["INP_DOC_MAX_FILESIZE_UNIT"]); + //Create $inputDocument = new \InputDocument(); @@ -348,8 +356,11 @@ class InputDocument * * @param string $inputDocumentUid Unique id of InputDocument * @param array $arrayData Data - * - * return array Return data of the InputDocument updated + * + * @return array Return data of the InputDocument updated + * + * @see \ProcessMaker\Services\Api\Project\InputDocument->doPutInputDocument() + * @link https://wiki.processmaker.com/3.0/Input_Documents#Creating_Input_Documents */ public function update($inputDocumentUid, $arrayData) { @@ -374,6 +385,8 @@ class InputDocument if (isset($arrayData["INP_DOC_TITLE"])) { $this->throwExceptionIfExistsTitle($processUid, $arrayData["INP_DOC_TITLE"], $this->arrayFieldNameForException["inputDocumentTitle"], $inputDocumentUid); } + + $this->throwExceptionIfMaximumFileSizeExceed(intval($arrayData["INP_DOC_MAX_FILESIZE"]), $arrayData["INP_DOC_MAX_FILESIZE_UNIT"]); //Update $arrayData["INP_DOC_UID"] = $inputDocumentUid; @@ -519,7 +532,7 @@ class InputDocument * * @param string $inputDocumentUid Unique id of InputDocument * - * return array Return an array with data of an InputDocument + * @return array Return an array with data of an InputDocument */ public function getInputDocument($inputDocumentUid) { @@ -544,5 +557,71 @@ class InputDocument throw $e; } } + + /** + * Throw exception if maximum file size exceed to php directives. + * + * @param int $value + * @param string $unit + * @throws Exception + * + * @see ProcessMaker\BusinessModel\InputDocument->create() + * @see ProcessMaker\BusinessModel\InputDocument->update() + * @link https://wiki.processmaker.com/3.2/Input_Documents + */ + public function throwExceptionIfMaximumFileSizeExceed($value, $unit) + { + //The value of 'INP_DOC_MAX_FILESIZE_UNIT' can only take two values: 'KB'and 'MB'. + if ($unit === "MB") { + $value = $value * (1024 ** 2); + } + if ($unit === "KB") { + $value = $value * (1024 ** 1); + } + $object = $this->getMaxFileSize(); + if ($object->uploadMaxFileSizeBytes < $value) { + throw new Exception(G::LoadTranslation("ID_THE_MAXIMUM_VALUE_OF_THIS_FIELD_IS", [$object->uploadMaxFileSize])); + } + } + + /** + * To upload large files, post_max_size value must be larger than upload_max_filesize. + * Generally speaking, memory_limit should be larger than post_max_size. When an integer + * is used, the value is measured in bytes. The shorthand notation may also be used. + * If the size of post data is greater than post_max_size, the $_POST and $_FILES + * superglobals are empty. + * + * @return object + * + * @see ProcessMaker\BusinessModel\InputDocument->throwExceptionIfMaximumFileSizeExceed() + * @link https://wiki.processmaker.com/3.2/Input_Documents + * @link http://php.net/manual/en/faq.using.php#faq.using.shorthandbytes + */ + public function getMaxFileSize() + { + $phpShorthandByte = new PhpShorthandByte(); + $postMaxSize = ini_get("post_max_size"); + $postMaxSizeBytes = $phpShorthandByte->valueToBytes($postMaxSize); + $uploadMaxFileSize = ini_get("upload_max_filesize"); + $uploadMaxFileSizeBytes = $phpShorthandByte->valueToBytes($uploadMaxFileSize); + + if ($postMaxSizeBytes < $uploadMaxFileSizeBytes) { + $uploadMaxFileSize = $postMaxSize; + $uploadMaxFileSizeBytes = $postMaxSizeBytes; + } + + //according to the acceptance criteria the information is always shown in MBytes + $uploadMaxFileSizeMBytes = $uploadMaxFileSizeBytes / (1024 ** 2); //conversion constant + $uploadMaxFileSizeUnit = "MB"; //short processmaker notation, https://wiki.processmaker.com/3.0/File_control#Size_Unity + $uploadMaxFileSizePhpUnit = "M"; //short php notation, http://php.net/manual/en/faq.using.php#faq.using.shorthandbytes + + $result = [ + "uploadMaxFileSize" => $phpShorthandByte->getFormatBytes($uploadMaxFileSizeMBytes . $uploadMaxFileSizePhpUnit), + "uploadMaxFileSizeBytes" => $uploadMaxFileSizeBytes, + "uploadMaxFileSizeMBytes" => $uploadMaxFileSizeMBytes, + "uploadMaxFileSizeUnit" => $uploadMaxFileSizeUnit + ]; + return (object) $result; + } } diff --git a/workflow/engine/src/ProcessMaker/BusinessModel/Light.php b/workflow/engine/src/ProcessMaker/BusinessModel/Light.php index 16ba133e6..4cbdb2c48 100644 --- a/workflow/engine/src/ProcessMaker/BusinessModel/Light.php +++ b/workflow/engine/src/ProcessMaker/BusinessModel/Light.php @@ -1490,6 +1490,7 @@ class Light } $response['listLanguage'] = $languagesList; if (isset($params['fileLimit']) && $params['fileLimit']) { + //to do: ProcessMaker\BusinessModel\InputDocument->getMaxFileSize() $postMaxSize = $this->return_bytes(ini_get('post_max_size')); $uploadMaxFileSize = $this->return_bytes(ini_get('upload_max_filesize')); if ($postMaxSize < $uploadMaxFileSize) { diff --git a/workflow/engine/src/ProcessMaker/Util/PhpShorthandByte.php b/workflow/engine/src/ProcessMaker/Util/PhpShorthandByte.php new file mode 100644 index 000000000..6207298be --- /dev/null +++ b/workflow/engine/src/ProcessMaker/Util/PhpShorthandByte.php @@ -0,0 +1,82 @@ +units = ['K', 'M', 'G']; + $this->terminal = "bytes"; + } + + /** + * Convert value string to bytes, for directives php.ini + * + * @param string $value + * @return integer + * + * @see ProcessMaker\BusinessModel\InputDocument->getMaxFileSize() + * @link http://php.net/manual/en/faq.using.php#faq.using.shorthandbytes + */ + public function valueToBytes($value) + { + foreach ($this->units as $i => $unit) { + $number = $this->getNumberValue($value, $unit); + if ($number !== null) { + $result = $number * (1024 ** ($i + 1)); + return $result; + } + } + return intval($value); + } + + /** + * Get number value and validate expresion. + * Valid expresion is: [number][unit] + * + * @param string $string + * @param string $unit + * @return integer|null + * + * @see ProcessMaker\Util\PhpShorthandByte->valueToBytes() + * @link http://php.net/manual/en/faq.using.php#faq.using.shorthandbytes + */ + public function getNumberValue($string, $unit) + { + $string = preg_replace('/\s+/', '', $string); + $isCorrect = preg_match("/\d+{$unit}/", $string); + if ($isCorrect === 1) { + $result = rtrim($string, $unit); + return intval($result); + } + return null; + } + + /** + * Get format bytes. + * + * @param string $value + * @return string + */ + public function getFormatBytes($value) + { + foreach ($this->units as $i => $unit) { + $number = $this->getNumberValue($value, $unit); + if ($number !== null) { + return $number . " " . $unit . $this->terminal; + } + } + return $value; + } +} diff --git a/workflow/engine/src/ProcessMaker/Validation/ValidationUploadedFiles.php b/workflow/engine/src/ProcessMaker/Validation/ValidationUploadedFiles.php index ccd5cc6f1..d354f21fa 100644 --- a/workflow/engine/src/ProcessMaker/Validation/ValidationUploadedFiles.php +++ b/workflow/engine/src/ProcessMaker/Validation/ValidationUploadedFiles.php @@ -8,6 +8,7 @@ use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Facades\Cache; use ProcessMaker\Core\System; use ProcessMaker\Services\OAuth2\Server; +use ProcessMaker\Util\PhpShorthandByte; use Symfony\Component\HttpFoundation\File\File; class ValidationUploadedFiles @@ -169,6 +170,8 @@ class ValidationUploadedFiles * File upload validation. * * @return $this + * + * @see workflow/public_html/sysGeneric.php */ public function runRulesToAllUploadedFiles() { @@ -177,6 +180,12 @@ class ValidationUploadedFiles return; } $this->fails = []; + + $validator = $this->runRulesForFileEmpty(); + if ($validator->fails()) { + $this->fails[] = $validator; + } + foreach ($files as $file) { $data = (object) $file; if (!is_array($data->name) || !is_array($data->tmp_name)) { @@ -207,9 +216,70 @@ class ValidationUploadedFiles } } } + return $this; } + /** + * Run rules if files is empty. + * + * @see ProcessMaker\Validation\ValidationUploadedFiles->runRulesToAllUploadedFiles() + * @see Luracast\Restler\Format\UploadFormat->decode() + */ + public function runRulesForFileEmpty() + { + $validator = new Validator(); + + //rule: validate $_SERVER['CONTENT_LENGTH'] + $rule = $validator->addRule(); + $rule->validate(null, function($file) use ($rule) { + //according to the acceptance criteria the information is always shown in MBytes + $phpShorthandByte = new PhpShorthandByte(); + $postMaxSize = ini_get("post_max_size"); + $postMaxSizeBytes = $phpShorthandByte->valueToBytes($postMaxSize); + $uploadMaxFileSize = ini_get("upload_max_filesize"); + $uploadMaxFileSizeBytes = $phpShorthandByte->valueToBytes($uploadMaxFileSize); + + if ($postMaxSizeBytes < $uploadMaxFileSizeBytes) { + $uploadMaxFileSize = $postMaxSize; + $uploadMaxFileSizeBytes = $postMaxSizeBytes; + } + //according to the acceptance criteria the information is always shown in MBytes + $uploadMaxFileSizeMBytes = $uploadMaxFileSizeBytes / (1024 ** 2); //conversion constant + + $message = G::LoadTranslation('ID_THE_FILE_SIZE_IS_BIGGER_THAN_THE_MAXIMUM_ALLOWED', [$uploadMaxFileSizeMBytes]); + $rule->message($message); + /** + * If you can, you may want to set post_max_size to a low value (say 1M) to make + * testing easier. First test to see how your script behaves. Try uploading a file + * that is larger than post_max_size. If you do you will get a message like this + * in your error log: + * + * [09-Jun-2010 19:28:01] PHP Warning: POST Content-Length of 30980857 bytes exceeds + * the limit of 2097152 bytes in Unknown on line 0 + * + * This makes the script is not completed. + * + * Solving the problem: + * The PHP documentation http://php.net/manual/en/ini.core.php#ini.post-max-size + * provides a hack to solve this problem: + * + * If the size of post data is greater than post_max_size, the $_POST and $_FILES + * superglobals are empty. + */ + if ($_SERVER['REQUEST_METHOD'] === 'POST' && empty($_POST) && empty($_FILES) && $_SERVER['CONTENT_LENGTH'] > 0) { + return true; + } + return false; + }) + ->status(400) + ->log(function($rule) { + Bootstrap::registerMonologPhpUploadExecution('phpUpload', 400, $rule->getMessage(), ""); + }); + + return $validator->validate(); + } + /** * Get the first error and call the argument function. * diff --git a/workflow/engine/templates/designer/index.html b/workflow/engine/templates/designer/index.html index a40df5785..da5100749 100644 --- a/workflow/engine/templates/designer/index.html +++ b/workflow/engine/templates/designer/index.html @@ -29,6 +29,7 @@ var SYS_LANG = "{$SYS_LANG}"; var SYS_SKIN = "{$SYS_SKIN}"; var HTTP_SERVER_HOSTNAME = "{$HTTP_SERVER_HOSTNAME}"; + var maxFileSizeInformation = {$maxFileSizeInformation}; @@ -86,6 +87,7 @@ var SYS_LANG = "{$SYS_LANG}"; var SYS_SKIN = "{$SYS_SKIN}"; var HTTP_SERVER_HOSTNAME = "{$HTTP_SERVER_HOSTNAME}"; + var maxFileSizeInformation = {$maxFileSizeInformation}; {foreach from=$sourceJs item=pathFile}