CVE-2022-1411: Unrestricted Upload of File with Dangerous Type (CWE-434)
value: Unrestructed file upload in GitHub repository yetiforcecompany/yetiforcecrm prior to 6.4.0. Attacker can send malicious files to the victims is able to retrieve the stored data from the web application without that data being made safe to render in the browser and steals victim's cookie leads to account takeover.
CWE description: The product allows the upload or transfer of dangerous file types that are automatically processed within its environment.
CVSS v3 base score: 6.1
Published: 2022-05-05T11:15Z
Repository: https://github.com/yetiforcecompany/yetiforcecrm
Language: PHP
Files changed: app/Fields/File.php, app/Validator.php, config/version.php, modules/Vtiger/actions/Fields.php, public_html/layouts/resources/libraries/ckeditor/base64image/dialogs/dialog.js, public_html/layouts/resources/libraries/ckeditor/base64image/plugin.js
Commit date: 2022-05-05 11:49:50 +0200
Commit message: Added validation to pasted files to the wysiwyg editor

Diff: diff --git a/app/Fields/File.php b/app/Fields/File.php
index a0a36907867..1d4f662978f 100644
--- a/app/Fields/File.php
+++ b/app/Fields/File.php
@@ -164,10 +164,43 @@ class File
 		$instance->name = trim(\App\Purifier::purify(basename($path)));
 		$instance->path = $path;
 		return $instance;
 	}
 
+	/**
+	 * Load file instance from base string.
+	 *
+	 * @param string $contents
+	 * @param array  $param
+	 *
+	 * @return \self|null
+	 */
+	public static function loadFromBase(string $contents, array $param = []): ?self
+	{
+		$result = explode(',', $contents, 2);
+		$contentType = $isBase64 = false;
+		if (2 === \count($result)) {
+			[$metadata, $data] = $result;
+			foreach (explode(';', $metadata) as $cur) {
+				if ('base64' === $cur) {
+					$isBase64 = true;
+				} elseif ('data:' === substr($cur, 0, 5)) {
+					$contentType = str_replace('data:', '', $cur);
+				}
+			}
+		} else {
+			$data = $result[0];
+		}
+		$data = rawurldecode($data);
+		$rawData = $isBase64 ? base64_decode($data) : $data;
+		if (\strlen($rawData) < 12) {
+			Log::error('Incorrect content value: ' . $contents, __CLASS__);
+			return null;
+		}
+		return static::loadFromContent($rawData, false, array_merge($param, ['mimeType' => $contentType]));
+	}
+
 	/**
 	 * Load file instance from content.
 	 *
 	 * @param string   $contents
 	 * @param string   $name
@@ -879,31 +912,11 @@ class File
 	 *
 	 * @return bool|self
 	 */
 	public static function saveFromString(string $contents, array $param = [])
 	{
-		$result = explode(',', $contents, 2);
-		$contentType = $isBase64 = false;
-		if (2 === \count($result)) {
-			[$metadata, $data] = $result;
-			foreach (explode(';', $metadata) as $cur) {
-				if ('base64' === $cur) {
-					$isBase64 = true;
-				} elseif ('data:' === substr($cur, 0, 5)) {
-					$contentType = str_replace('data:', '', $cur);
-				}
-			}
-		} else {
-			$data = $result[0];
-		}
-		$data = rawurldecode($data);
-		$rawData = $isBase64 ? base64_decode($data) : $data;
-		if (\strlen($rawData) < 12) {
-			Log::error('Incorrect content value: ' . $contents, __CLASS__);
-			return false;
-		}
-		$fileInstance = static::loadFromContent($rawData, false, array_merge($param, ['mimeType' => $contentType]));
+		$fileInstance = static::loadFromBase($contents, $param);
 		if ($fileInstance->validateAndSecure()) {
 			return $fileInstance;
 		}
 		$fileInstance->delete();
 		return false;
@@ -1228,11 +1241,11 @@ class File
 			'type' => $this->getMimeType(),
 			'path' => null,
 			'createdtime' => date('Y-m-d H:i:s'),
 			'fieldname' => null,
 			'key' => null,
-			'crmid' => 0
+			'crmid' => 0,
 		];
 		foreach ($data as $key => &$value) {
 			if (isset($params[$key])) {
 				$value = $params[$key];
 			}
diff --git a/app/Validator.php b/app/Validator.php
index f17ef3a0c07..00bc2791e9b 100644
--- a/app/Validator.php
+++ b/app/Validator.php
@@ -481,6 +481,22 @@ class Validator
 		$parts = explode('/', trim(str_replace(\DIRECTORY_SEPARATOR, '/', $input), '/'));
 		return !array_filter($parts, function ($dir) {
 			return !self::dirName($dir);
 		});
 	}
+
+	/**
+	 * Check base64.
+	 *
+	 * @param string $input
+	 *
+	 * @return bool
+	 */
+	public static function base64(string $input): bool
+	{
+		if (empty($input)) {
+			return false;
+		}
+		$explode = explode(',', $input);
+		return 2 === \count($explode) && 1 === preg_match('%^[a-zA-Z0-9/+]*={0,2}$%', $explode[1]);
+	}
 }
diff --git a/config/version.php b/config/version.php
index 2c7a68e5ad4..3c9d3394511 100644
--- a/config/version.php
+++ b/config/version.php
@@ -1,7 +1,7 @@
 <?php
 
 return [
-	'appVersion' => '6.3.220',
+	'appVersion' => '6.3.221',
 	'patchVersion' => '2022.05.05',
 	'lib_roundcube' => '0.2.12',
 ];
diff --git a/modules/Vtiger/actions/Fields.php b/modules/Vtiger/actions/Fields.php
index 2d5c58c6882..1d1af8876a9 100644
--- a/modules/Vtiger/actions/Fields.php
+++ b/modules/Vtiger/actions/Fields.php
@@ -57,20 +57,21 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 		$this->exposeMethod('findAddress');
 		$this->exposeMethod('validateForField');
 		$this->exposeMethod('validateByMode');
 		$this->exposeMethod('verifyPhoneNumber');
 		$this->exposeMethod('changeFavoriteOwner');
+		$this->exposeMethod('validateFile');
 	}
 
 	/**
 	 * Get owners for ajax owners list.
 	 *
 	 * @param \App\Request $request
 	 *
 	 * @throws \App\Exceptions\NoPermitted
 	 */
-	public function getOwners(App\Request $request)
+	public function getOwners(App\Request $request): void
 	{
 		if (!App\Config::performance('SEARCH_OWNERS_BY_AJAX')) {
 			throw new \App\Exceptions\NoPermitted('LBL_PERMISSION_DENIED', 406);
 		}
 		if ('owner' !== $this->fieldModel->getFieldDataType() && 'sharedOwner' !== $this->fieldModel->getFieldDataType()) {
@@ -119,11 +120,11 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 	 *
 	 * @param \App\Request $request
 	 *
 	 * @throws \App\Exceptions\NoPermitted
 	 */
-	public function getUserRole(App\Request $request)
+	public function getUserRole(App\Request $request): void
 	{
 		if (!App\Config::performance('SEARCH_ROLES_BY_AJAX')) {
 			throw new \App\Exceptions\NoPermitted('LBL_PERMISSION_DENIED', 406);
 		}
 		if ('userRole' !== $this->fieldModel->getFieldDataType()) {
@@ -146,11 +147,11 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 	/**
 	 * @param \App\Request $request
 	 *
 	 * @throws \App\Exceptions\NoPermitted
 	 */
-	public function getReference(App\Request $request)
+	public function getReference(App\Request $request): void
 	{
 		if ($request->has('fieldName')) {
 			$fieldModel = Vtiger_Module_Model::getInstance($request->getModule())->getFieldByName($request->getByType('fieldName', 2));
 			if (empty($fieldModel) || !$fieldModel->isActiveField() || !$fieldModel->isViewEnabled()) {
 				throw new \App\Exceptions\NoPermitted('ERR_NO_PERMISSIONS_TO_FIELD', 406);
@@ -193,11 +194,11 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 	 *
 	 * @param \App\Request $request
 	 *
 	 * @throws \App\Exceptions\NoPermitted
 	 */
-	public function verifyPhoneNumber(App\Request $request)
+	public function verifyPhoneNumber(App\Request $request): void
 	{
 		if ('phone' !== $this->fieldModel->getFieldDataType()) {
 			throw new \App\Exceptions\NoPermitted('ERR_NO_PERMISSIONS_TO_FIELD');
 		}
 		$response = new Vtiger_Response();
@@ -222,11 +223,11 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 	/**
 	 * Find address.
 	 *
 	 * @param \App\Request $request
 	 */
-	public function findAddress(App\Request $request)
+	public function findAddress(App\Request $request): void
 	{
 		$instance = \App\Map\Address::getInstance($request->getByType('type'));
 		$response = new Vtiger_Response();
 		if ($instance) {
 			$response->setResult($instance->find($request->getByType('value', 'Text')));
@@ -241,11 +242,11 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 	 *
 	 * @throws \App\Exceptions\IllegalValue
 	 * @throws \App\Exceptions\NoPermitted
 	 * @throws \yii\db\Exception
 	 */
-	public function changeFavoriteOwner(App\Request $request)
+	public function changeFavoriteOwner(App\Request $request): void
 	{
 		if (!App\Config::module('Users', 'FAVORITE_OWNERS') || (\App\User::getCurrentUserRealId() !== \App\User::getCurrentUserId())) {
 			throw new \App\Exceptions\NoPermitted('LBL_PERMISSION_DENIED', 406);
 		}
 		$moduleName = $request->getModule();
@@ -263,11 +264,11 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 	 *
 	 * @param \App\Request $request
 	 *
 	 * @throws \App\Exceptions\NoPermitted
 	 */
-	public function validateForField(App\Request $request)
+	public function validateForField(App\Request $request): void
 	{
 		$fieldModel = Vtiger_Module_Model::getInstance($request->getModule())->getFieldByName($request->getByType('fieldName', 2));
 		if (!$fieldModel || !$fieldModel->isActiveField() || !$fieldModel->isViewEnabled()) {
 			throw new \App\Exceptions\NoPermitted('ERR_NO_PERMISSIONS_TO_FIELD', 406);
 		}
@@ -286,17 +287,43 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 	 *
 	 * @param \App\Request $request
 	 *
 	 * @throws \App\Exceptions\NoPermitted
 	 */
-	public function validateByMode(App\Request $request)
+	public function validateByMode(App\Request $request): void
 	{
 		if ($request->isEmpty('purifyMode') || !$request->has('value')) {
 			throw new \App\Exceptions\NoPermitted('ERR_ILLEGAL_VALUE', 406);
 		}
 		$response = new Vtiger_Response();
 		$response->setResult([
 			'raw' => $request->getByType('value', $request->getByType('purifyMode')),
 		]);
 		$response->emit();
 	}
+
+	/**
+	 * Validate file.
+	 *
+	 * @param App\Request $request
+	 *
+	 * @return void
+	 */
+	public function validateFile(App\Request $request): void
+	{
+		$validate = false;
+		if ($request->has('base64')) {
+			$fileInstance = \App\Fields\File::loadFromBase($request->getByType('base64', 'base64'), ['validateAllowedFormat' => 'image']);
+			if ($fileInstance && $fileInstance->validate()) {
+				$validate = true;
+			} else {
+				$validateError = $fileInstance->validateError;
+			}
+		}
+		$response = new Vtiger_Response();
+		$response->setResult([
+			'validate' => $validate,
+			'validateError' => $validateError ?? null,
+		]);
+		$response->emit();
+	}
 }
diff --git a/public_html/layouts/resources/libraries/ckeditor/base64image/dialogs/dialog.js b/public_html/layouts/resources/libraries/ckeditor/base64image/dialogs/dialog.js
index faec7711b86..524fbaf205a 100644
--- a/public_html/layouts/resources/libraries/ckeditor/base64image/dialogs/dialog.js
+++ b/public_html/layouts/resources/libraries/ckeditor/base64image/dialogs/dialog.js
@@ -1,70 +1,73 @@
-/* {[The file is published on the basis of YetiForce Public License 3.0 that can be found in the following directory: licenses/LicenseEN.txt or yetiforce.com]} */
+/* {[The file is published on the basis of YetiForce Public License 5.0 that can be found in the following directory: licenses/LicenseEN.txt or yetiforce.com]} */
+'use strict';
 
 CKEDITOR.dialog.add('base64image-dialog', function (editor) {
-	var t = null,
+	let self = null,
 		selectedImg = null,
 		orgWidth = null,
 		orgHeight = null,
 		imgPreview = null,
-		imgScal = 1,
-		lock = true;
+		sourceElements = [],
+		imgScale = 1,
+		lock = true,
+		maxUploadSize = CONFIG['maxUploadLimit'];
 
 	/* Check File Reader Support */
 	function fileSupport() {
-		var r = false,
+		let r = false,
 			n = null;
 		try {
 			if (FileReader) {
-				var n = document.createElement('input');
+				let n = document.createElement('input');
 				if (n && 'files' in n) r = true;
 			}
 		} catch (e) {
 			r = false;
 		}
 		n = null;
 		return r;
 	}
-	var isFReaderSupported = fileSupport();
+	let isFReaderSupported = fileSupport();
 
 	/* Load preview image */
 	function imagePreviewLoad(s) {
 		/* no preview */
 		if (typeof s != 'string' || !s) {
 			imgPreview.getElement().setHtml('');
 			return;
 		}
 
 		/* Create image */
-		var i = new Image();
+		let i = new Image();
 
 		/* Display loading text in preview element */
-		imgPreview.getElement().setHtml('Loading...');
+		$(imgPreview.getElement().$).progressIndicator({ position: 'html' });
 
 		/* When image is loaded */
 		i.onload = function () {
 			/* Remove preview */
 			imgPreview.getElement().setHtml('');
 
 			/* Set attributes */
 			if (orgWidth == null || orgHeight == null) {
-				t.setValueOf('tab-properties', 'width', this.width);
-				t.setValueOf('tab-properties', 'height', this.height);
-				imgScal = 1;
-				if (this.height > 0 && this.width > 0) imgScal = this.width / this.height;
-				if (imgScal <= 0) imgScal = 1;
+				self.setValueOf('tab-properties', 'width', this.width);
+				self.setValueOf('tab-properties', 'height', this.height);
+				imgScale = 1;
+				if (this.height > 0 && this.width > 0) imgScale = this.width / this.height;
+				if (imgScale <= 0) imgScale = 1;
 			} else {
 				orgWidth = null;
 				orgHeight = null;
 			}
 			this.id = editor.id + 'previewimage';
 			this.setAttribute('style', 'max-width:400px;max-height:100px;');
 			this.setAttribute('alt', '');
 
 			/* Insert preview image */
 			try {
-				var p = imgPreview.getElement().$;
+				let p = imgPreview.getElement().$;
 				if (p) p.appendChild(this);
 			} catch (e) {}
 		};
 
 		/* Error Function */
@@ -80,43 +83,65 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
 	}
 
 	function imagePreview(src) {
 		imgPreview.getElement().setHtml('');
 		if (isFReaderSupported) {
-			var fileI = t.getContentElement('tab-source', 'file');
-			var n = null;
-			try {
-				n = fileI.getInputElement().$;
-			} catch (e) {
-				n = null;
-			}
-			if (n && 'files' in n && n.files && n.files.length > 0 && n.files[0]) {
-				if ('type' in n.files[0] && !n.files[0].type.match('image.*')) return;
-				if (!FileReader) return;
-				imgPreview.getElement().setHtml('Loading...');
-				var fr = new FileReader();
-				fr.onload = (function (f) {
-					return function (e) {
-						imgPreview.getElement().setHtml('');
-						imagePreviewLoad(e.target.result);
-					};
-				})(n.files[0]);
-				fr.onerror = function () {
+			$(imgPreview.getElement().$).progressIndicator({ position: 'html' });
+			readImageAsBase64()
+				.done(function (base) {
 					imgPreview.getElement().setHtml('');
-				};
-				fr.onabort = function () {
+					imagePreviewLoad(base);
+				})
+				.fail(function () {
 					imgPreview.getElement().setHtml('');
-				};
-				fr.readAsDataURL(n.files[0]);
+				});
+		}
+	}
+
+	function readImageAsBase64() {
+		const aDeferred = jQuery.Deferred();
+		let fileI = self.getContentElement('tab-source', 'file'),
+			n = null;
+		try {
+			n = fileI.getInputElement().$;
+		} catch (e) {
+			n = null;
+		}
+		if (n && 'files' in n && n.files && n.files.length > 0 && n.files[0]) {
+			if (('type' in n.files[0] && !n.files[0].type.match('image.*')) || !FileReader) {
+				aDeferred.reject();
+				return aDeferred.promise();
+			}
+			if (n.files[0].size > maxUploadSize) {
+				app.showNotify({
+					text: app.vtranslate('JS_UPLOADED_FILE_SIZE_EXCEEDS'),
+					type: 'error'
+				});
+				aDeferred.reject();
+				return aDeferred.promise();
 			}
+			let fr = new FileReader();
+			fr.onload = (function (f) {
+				return function (e) {
+					aDeferred.resolve(e.target.result);
+				};
+			})(n.files[0]);
+			fr.onerror = function () {
+				aDeferred.reject();
+			};
+			fr.onabort = function () {
+				aDeferred.reject();
+			};
+			fr.readAsDataURL(n.files[0]);
 		}
+		return aDeferred.promise();
 	}
 
 	function getImageDimensions() {
-		var o = {
-			w: t.getContentElement('tab-properties', 'width').getValue(),
-			h: t.getContentElement('tab-properties', 'height').getValue(),
+		let o = {
+			w: self.getContentElement('tab-properties', 'width').getValue(),
+			h: self.getContentElement('tab-properties', 'height').getValue(),
 			uw: 'px',
 			uh: 'px'
 		};
 		if (o.w.indexOf('%') >= 0) o.uw = '%';
 		if (o.h.indexOf('%') >= 0) o.uh = '%';
@@ -126,49 +151,104 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
 		if (isNaN(o.h)) o.h = 0;
 		return o;
 	}
 
 	function imageDimensions(src) {
-		var o = getImageDimensions();
-		var u = 'px';
+		let o = getImageDimensions();
+		let u = 'px';
 		if (src == 'width') {
 			if (o.uw == '%') u = '%';
-			o.h = Math.round(o.w / imgScal);
+			o.h = Math.round(o.w / imgScale);
 		} else {
 			if (o.uh == '%') u = '%';
-			o.w = Math.round(o.h * imgScal);
+			o.w = Math.round(o.h * imgScale);
 		}
 		if (u == '%') {
 			o.w += '%';
 			o.h += '%';
 		}
-		t.getContentElement('tab-properties', 'width').setValue(o.w),
-			t.getContentElement('tab-properties', 'height').setValue(o.h);
+		self.getContentElement('tab-properties', 'width').setValue(o.w),
+			self.getContentElement('tab-properties', 'height').setValue(o.h);
 	}
 
 	function integerValue(elem) {
-		var v = elem.getValue(),
+		let v = elem.getValue(),
 			u = '';
 		if (v.indexOf('%') >= 0) u = '%';
 		v = parseInt(v, 10);
 		if (isNaN(v)) v = 0;
 		elem.setValue(v + u);
 	}
 
+	function validateFile() {
+		const fieldInfo = $(editor.element.$).data('fieldinfo');
+		let length = editor.getData().length,
+			selectedImg = editor.getSelection();
+		if (selectedImg) selectedImg = selectedImg.getSelectedElement();
+		if (!selectedImg || selectedImg.getName() !== 'img') selectedImg = null;
+		if (selectedImg) {
+			length = length - selectedImg.getOuterHtml().length;
+		}
+		const aDeferred = jQuery.Deferred();
+		readImageAsBase64()
+			.done((base) => {
+				length += base.length;
+				if (length > fieldInfo['maximumlength']) {
+					app.showNotify({
+						text: app.vtranslate('JS_MAXIMUM_TEXT_SIZE_IN_BYTES') + ' ' + fieldInfo['maximumlength'],
+						type: 'error'
+					});
+				}
+				AppConnector.request({
+					module: app.getModuleName(),
+					action: 'Fields',
+					mode: 'validateFile',
+					fieldName: fieldInfo['name'],
+					base64: base
+				})
+					.done((data) => {
+						if (data.result.validate) {
+							aDeferred.resolve();
+						} else {
+							app.showNotify({
+								text: data.result.validateError,
+								type: 'error'
+							});
+							aDeferred.reject();
+						}
+					})
+					.fail(function () {
+						aDeferred.resolve();
+					});
+			})
+			.fail(() => {
+				aDeferred.reject();
+			});
+		return aDeferred.promise();
+	}
+
 	if (isFReaderSupported) {
-		var sourceElements = [
+		sourceElements = [
 			{
 				type: 'hbox',
 				widths: ['70px'],
 				style: 'margin-top:40px;',
 				children: [
 					{
 						type: 'file',
 						id: 'file',
 						label: '',
-						onChange: function () {
-							imagePreview('file');
+						size: maxUploadSize,
+						onChange: function (a) {
+							validateFile()
+								.done(() => {
+									imagePreview('file');
+								})
+								.fail(function () {
+									self.getContentElement('tab-source', 'file').getInputElement().$.value = null;
+									imgPreview.getElement().setHtml('');
+								});
 						}
 					}
 				]
 			},
 			{
@@ -238,161 +318,172 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
 					},
 					this.getContentElement('tab-properties', 'border')
 				);
 		},
 		onShow: function () {
+			this.getContentElement('tab-source', 'file')
+				.getInputElement()
+				.$.setAttribute('accept', 'image/jpeg, image/png, image/gif');
 			/* Remove preview */
 			imgPreview.getElement().setHtml('');
 
-			(t = this), (orgWidth = null), (orgHeight = null), (imgScal = 1), (lock = true);
+			(self = this), (orgWidth = null), (orgHeight = null), (imgScale = 1), (lock = true);
 
 			/* selected image or null */
 			selectedImg = editor.getSelection();
 			if (selectedImg) selectedImg = selectedImg.getSelectedElement();
 			if (!selectedImg || selectedImg.getName() !== 'img') selectedImg = null;
 
 			/* Set input values */
-			t.setValueOf('tab-properties', 'lock', lock);
-			t.setValueOf('tab-properties', 'vmargin', '0');
-			t.setValueOf('tab-properties', 'hmargin', '0');
-			t.setValueOf('tab-properties', 'border', '0');
-			t.setValueOf('tab-properties', 'align', 'none');
+			self.setValueOf('tab-properties', 'lock', lock);
+			self.setValueOf('tab-properties', 'vmargin', '0');
+			self.setValueOf('tab-properties', 'hmargin', '0');
+			self.setValueOf('tab-properties', 'border', '0');
+			self.setValueOf('tab-properties', 'align', 'none');
 			if (selectedImg) {
 				/* Set input values from selected image */
 				if (typeof selectedImg.getAttribute('width') == 'string') orgWidth = selectedImg.getAttribute('width');
 				if (typeof selectedImg.getAttribute('height') == 'string') orgHeight = selectedImg.getAttribute('height');
 				if ((orgWidth == null || orgHeight == null) && selectedImg.$) {
 					orgWidth = selectedImg.$.width;
 					orgHeight = selectedImg.$.height;
 				}
 				if (orgWidth != null && orgHeight != null) {
-					t.setValueOf('tab-properties', 'width', orgWidth);
-					t.setValueOf('tab-properties', 'height', orgHeight);
+					self.setValueOf('tab-properties', 'width', orgWidth);
+					self.setValueOf('tab-properties', 'height', orgHeight);
 					orgWidth = parseInt(orgWidth, 10);
 					orgHeight = parseInt(orgHeight, 10);
-					imgScal = 1;
-					if (!isNaN(orgWidth) && !isNaN(orgHeight) && orgHeight > 0 && orgWidth > 0) imgScal = orgWidth / orgHeight;
-					if (imgScal <= 0) imgScal = 1;
+					imgScale = 1;
+					if (!isNaN(orgWidth) && !isNaN(orgHeight) && orgHeight > 0 && orgWidth > 0) imgScale = orgWidth / orgHeight;
+					if (imgScale <= 0) imgScale = 1;
 				}
 
 				if (typeof selectedImg.getAttribute('src') == 'string') {
 					if (selectedImg.getAttribute('src').indexOf('data:') === 0) {
 						imagePreview('base64');
 						imagePreviewLoad(selectedImg.getAttribute('src'));
 					}
 				}
 				if (typeof selectedImg.getAttribute('alt') == 'string')
-					t.setValueOf('tab-properties', 'alt', selectedImg.getAttribute('alt'));
+					self.setValueOf('tab-properties', 'alt', selectedImg.getAttribute('alt'));
 				if (typeof selectedImg.getAttribute('hspace') == 'string')
-					t.setValueOf('tab-properties', 'hmargin', selectedImg.getAttribute('hspace'));
+					self.setValueOf('tab-properties', 'hmargin', selectedImg.getAttribute('hspace'));
 				if (typeof selectedImg.getAttribute('vspace') == 'string')
-					t.setValueOf('tab-properties', 'vmargin', selectedImg.getAttribute('vspace'));
+					self.setValueOf('tab-properties', 'vmargin', selectedImg.getAttribute('vspace'));
 				if (typeof selectedImg.getAttribute('border') == 'string')
-					t.setValueOf('tab-properties', 'border', selectedImg.getAttribute('border'));
+					self.setValueOf('tab-properties', 'border', selectedImg.getAttribute('border'));
 				if (typeof selectedImg.getAttribute('align') == 'string') {
 					switch (selectedImg.getAttribute('align')) {
 						case 'top':
 						case 'text-top':
-							t.setValueOf('tab-properties', 'align', 'top');
+							self.setValueOf('tab-properties', 'align', 'top');
 							break;
 						case 'baseline':
 						case 'bottom':
 						case 'text-bottom':
-							t.setValueOf('tab-properties', 'align', 'bottom');
+							self.setValueOf('tab-properties', 'align', 'bottom');
 							break;
 						case 'left':
-							t.setValueOf('tab-properties', 'align', 'left');
+							self.setValueOf('tab-properties', 'align', 'left');
 							break;
 						case 'right':
-							t.setValueOf('tab-properties', 'align', 'right');
+							self.setValueOf('tab-properties', 'align', 'right');
 							break;
 					}
 				}
-				t.selectPage('tab-properties');
+				self.selectPage('tab-properties');
 			}
 		},
 		onOk: function () {
 			/* Get image source */
-			var src = '';
+			let src = '';
 			try {
 				src = CKEDITOR.document.getById(editor.id + 'previewimage').$.src;
 			} catch (e) {
 				src = '';
 			}
 			if (typeof src != 'string' || src == null || src === '') return;
 
-			/* selected image or new image */
-			if (selectedImg) var newImg = selectedImg;
-			else var newImg = editor.document.createElement('img');
-			newImg.setAttribute('src', src);
-			src = null;
+			validateFile().always(() => {
+				/* selected image or new image */
+				if (selectedImg) {
+					var newImg = selectedImg;
+				} else {
+					var newImg = editor.document.createElement('img');
+				}
+				newImg.setAttribute('src', src);
+				src = null;
 
-			/* Set attributes */
-			newImg.setAttribute('alt', t.getValueOf('tab-properties', 'alt').replace(/^\s+/, '').replace(/\s+$/, ''));
-			var attr = {
-					width: ['width', 'width:#;', 'integer', 1],
-					height: ['height', 'height:#;', 'integer', 1],
-					vmargin: ['vspace', 'margin-top:#;margin-bottom:#;', 'integer', 0],
-					hmargin: ['hspace', 'margin-left:#;margin-right:#;', 'integer', 0],
-					align: ['align', ''],
-					border: ['border', 'border:# solid black;', 'integer', 0]
-				},
-				css = [],
-				value,
-				cssvalue,
-				attrvalue,
-				k;
-			for (k in attr) {
-				value = t.getValueOf('tab-properties', k);
-				attrvalue = value;
-				cssvalue = value;
-				unit = 'px';
-
-				if (k == 'align') {
-					switch (value) {
-						case 'top':
-						case 'bottom':
-							attr[k][1] = 'vertical-align:#;';
-							break;
-						case 'left':
-						case 'right':
-							attr[k][1] = 'float:#;';
-							break;
-						default:
-							value = null;
-							break;
+				/* Set attributes */
+				newImg.setAttribute('alt', self.getValueOf('tab-properties', 'alt').replace(/^\s+/, '').replace(/\s+$/, ''));
+				let attr = {
+						width: ['width', 'width:#;', 'integer', 1],
+						height: ['height', 'height:#;', 'integer', 1],
+						vmargin: ['vspace', 'margin-top:#;margin-bottom:#;', 'integer', 0],
+						hmargin: ['hspace', 'margin-left:#;margin-right:#;', 'integer', 0],
+						align: ['align', ''],
+						border: ['border', 'border:# solid black;', 'integer', 0]
+					},
+					css = [],
+					value,
+					cssValue,
+					attrValue,
+					unit,
+					k;
+				for (k in attr) {
+					value = self.getValueOf('tab-properties', k);
+					attrValue = value;
+					cssValue = value;
+					unit = 'px';
+
+					if (k == 'align') {
+						switch (value) {
+							case 'top':
+							case 'bottom':
+								attr[k][1] = 'vertical-align:#;';
+								break;
+							case 'left':
+							case 'right':
+								attr[k][1] = 'float:#;';
+								break;
+							default:
+								value = null;
+								break;
+						}
 					}
-				}
 
-				if (attr[k][2] == 'integer') {
-					if (value.indexOf('%') >= 0) unit = '%';
-					value = parseInt(value, 10);
-					if (isNaN(value)) value = null;
-					else if (value < attr[k][3]) value = null;
-					if (value != null) {
-						if (unit == '%') {
-							attrvalue = value + '%';
-							cssvalue = value + '%';
-						} else {
-							attrvalue = value;
-							cssvalue = value + 'px';
+					if (attr[k][2] == 'integer') {
+						if (value.indexOf('%') >= 0) unit = '%';
+						value = parseInt(value, 10);
+						if (isNaN(value)) value = null;
+						else if (value < attr[k][3]) value = null;
+						if (value != null) {
+							if (unit == '%') {
+								attrValue = value + '%';
+								cssValue = value + '%';
+							} else {
+								attrValue = value;
+								cssValue = value + 'px';
+							}
 						}
 					}
-				}
 
-				if (value != null) {
-					newImg.setAttribute(attr[k][0], attrvalue);
-					css.push(attr[k][1].replace(/#/g, cssvalue));
+					if (value != null) {
+						newImg.setAttribute(attr[k][0], attrValue);
+						css.push(attr[k][1].replace(/#/g, cssValue));
+					}
 				}
-			}
-			if (css.length > 0) newImg.setAttribute('style', css.join(''));
+				if (css.length > 0) newImg.setAttribute('style', css.join(''));
+
+				/* Insert new image */
+				if (!selectedImg) editor.insertElement(newImg);
 
-			/* Insert new image */
-			if (!selectedImg) editor.insertElement(newImg);
+				/* Resize image */
+				if (editor.plugins.imageresize) editor.plugins.imageresize.resize(editor, newImg, 800, 800);
 
-			/* Resize image */
-			if (editor.plugins.imageresize) editor.plugins.imageresize.resize(editor, newImg, 800, 800);
+				editor.updateElement();
+			});
 		},
 
 		/* Dialog form */
 		contents: [
 			{
diff --git a/public_html/layouts/resources/libraries/ckeditor/base64image/plugin.js b/public_html/layouts/resources/libraries/ckeditor/base64image/plugin.js
index fd8d13d6bff..9e47c40407d 100644
--- a/public_html/layouts/resources/libraries/ckeditor/base64image/plugin.js
+++ b/public_html/layouts/resources/libraries/ckeditor/base64image/plugin.js
@@ -1,74 +1,30 @@
-/* {[The file is published on the basis of YetiForce Public License 3.0 that can be found in the following directory: licenses/LicenseEN.txt or yetiforce.com]} */
-
-function initPasteEvent(editorInstance) {
-	if (editorInstance.addFeature) {
-		editorInstance.addFeature({
-			allowedContent: 'img[alt,id,!src]{width,height};'
-		});
-	}
-
-	editorInstance.on('contentDom', function () {
-		var editableElement = editorInstance.editable ? editorInstance.editable() : editorInstance.document;
-		editableElement.on('paste', onPaste, null, { editor: editorInstance });
-	});
-}
-function onPaste(event) {
-	var editor = event.listenerData && event.listenerData.editor;
-	var $event = event.data.$;
-	var clipboardData = $event.clipboardData;
-	var found = false;
-	var imageType = /^image/;
-
-	if (!clipboardData) {
-		return;
-	}
-	return Array.prototype.forEach.call(clipboardData.types, function (type, i) {
-		if (found) {
-			return;
-		}
-		if (type.match(imageType) || clipboardData.items[i].type.match(imageType)) {
-			readImageAsBase64(clipboardData.items[i], editor);
-			return (found = true);
-		}
-	});
-}
-
-function readImageAsBase64(item, editor) {
-	if (!item || typeof item.getAsFile !== 'function') {
-		return;
-	}
-	var file = item.getAsFile();
-	var reader = new FileReader();
-	reader.onload = function (evt) {
-		var element = editor.document.createElement('img', {
-			attributes: {
-				src: evt.target.result
-			}
-		});
-		setTimeout(function () {
-			editor.insertElement(element);
-		}, 10);
-	};
-	reader.readAsDataURL(file);
-}
+/* {[The file is published on the basis of YetiForce Public License 5.0 that can be found in the following directory: licenses/LicenseEN.txt or yetiforce.com]} */
+'use strict';
 
 CKEDITOR.plugins.add('base64image', {
 	requires: 'dialog',
 	icons: 'base64image',
 	hidpi: true,
-	init: function (editorInstance) {
-		initPasteEvent(editorInstance);
-		var pluginName = 'base64image-dialog';
-		editorInstance.ui.addToolbarGroup('base64image', 'insert');
-		editorInstance.ui.addButton('base64image', {
-			label: editorInstance.lang.common.image,
+	init: function (editor) {
+		if (editor.addFeature) {
+			editor.addFeature({
+				allowedContent: 'img[alt,id,!src]{width,height};'
+			});
+		}
+		editor.on('paste', (event, a, b) => {
+			this.onPaste(event);
+		});
+		const pluginName = 'base64image-dialog';
+		editor.ui.addToolbarGroup('base64image', 'insert');
+		editor.ui.addButton('base64image', {
+			label: editor.lang.common.image,
 			command: pluginName,
 			toolbar: 'insert'
 		});
 		CKEDITOR.dialog.add(pluginName, this.path + 'dialogs/dialog.js');
-		editorInstance.addCommand(
+		editor.addCommand(
 			pluginName,
 			new CKEDITOR.dialogCommand(pluginName, {
 				allowedContent:
 					'img[alt,!src]{border-style,border-width,float,height,margin,margin-bottom,margin-left,margin-right,margin-top,width}',
 				requiredContent: 'img[alt,src]',
@@ -76,31 +32,121 @@ CKEDITOR.plugins.add('base64image', {
 					['img{width}: sizeToStyle', 'img[width]: sizeToAttribute'],
 					['img{float}: alignmentToStyle', 'img[align]: alignmentToAttribute']
 				]
 			})
 		);
-		editorInstance.on('doubleclick', function (evt) {
+		editor.on('doubleclick', function (evt) {
 			if (evt.data.element && !evt.data.element.isReadOnly() && evt.data.element.getName() === 'img') {
 				evt.data.dialog = pluginName;
-				editorInstance.getSelection().selectElement(evt.data.element);
+				editor.getSelection().selectElement(evt.data.element);
 			}
 		});
-		if (editorInstance.addMenuItem) {
-			editorInstance.addMenuGroup('imageToBase64Group');
-			editorInstance.addMenuItem('imageToBase64Item', {
-				label: editorInstance.lang.common.image,
+		if (editor.addMenuItem) {
+			editor.addMenuGroup('imageToBase64Group');
+			editor.addMenuItem('imageToBase64Item', {
+				label: editor.lang.common.image,
 				icon: this.path + 'icons/base64image.png',
 				command: pluginName,
 				group: 'imageToBase64Group'
 			});
 		}
-		if (editorInstance.contextMenu) {
-			editorInstance.contextMenu.addListener(function (element, selection) {
+		if (editor.contextMenu) {
+			editor.contextMenu.addListener(function (element) {
 				if (element && element.getName() === 'img') {
-					editorInstance.getSelection().selectElement(element);
+					editor.getSelection().selectElement(element);
 					return { imageToBase64Item: CKEDITOR.TRISTATE_ON };
 				}
 				return null;
 			});
 		}
+	},
+	onPaste: function (event) {
+		const self = this,
+			allowedTypes = 'image/jpeg|image/png|image/gif',
+			dataTransfer = event.data.dataTransfer,
+			editor = event.editor,
+			count = dataTransfer.getFilesCount();
+		for (let index = 0; index < count; index++) {
+			let file = dataTransfer.getFile(index);
+			if (file.type.match(allowedTypes)) {
+				self
+					.validateFile(file, editor)
+					.done(function (base) {
+						let image,
+							selectedImg = editor.getSelection();
+						if (selectedImg) selectedImg = selectedImg.getSelectedElement();
+						if (!selectedImg || selectedImg.getName() !== 'img') selectedImg = null;
+						if (selectedImg) {
+							image = selectedImg;
+						} else {
+							image = editor.document.createElement('img');
+						}
+						image.setAttribute('src', base);
+						if (!selectedImg) editor.insertElement(image);
+					})
+					.fail(function (error) {
+						editor.showNotification(error, 'warning');
+					});
+			} else {
+				editor.showNotification(
+					app.vtranslate('JS_INVALID_FILE_TYPE') +
+						'<br>' +
+						app.vtranslate('JS_AVAILABLE_FILE_TYPES') +
+						': ' +
+						allowedTypes.replace(/\|/g, ', '),
+					'warning'
+				);
+			}
+		}
+	},
+	validateFile: function (file, editor) {
+		const aDeferred = jQuery.Deferred();
+		if (file.size > CONFIG['maxUploadLimit']) {
+			aDeferred.reject(app.vtranslate('JS_UPLOADED_FILE_SIZE_EXCEEDS'));
+		}
+		this.readAndValidate(file, editor)
+			.done(function (base) {
+				aDeferred.resolve(base);
+			})
+			.fail(function (error) {
+				aDeferred.reject(error);
+			});
+		return aDeferred.promise();
+	},
+	readAndValidate: function (file, editor) {
+		const aDeferred = jQuery.Deferred(),
+			fieldInfo = $(editor.element.$).data('fieldinfo');
+		let length = editor.getData().length,
+			selectedImg = editor.getSelection();
+		if (selectedImg) selectedImg = selectedImg.getSelectedElement();
+		if (!selectedImg || selectedImg.getName() !== 'img') selectedImg = null;
+		if (selectedImg) {
+			length = length - selectedImg.getOuterHtml().length;
+		}
+		const fileReader = new FileReader();
+		fileReader.onload = function (evt) {
+			length += evt.target.result.length;
+			if (length > fieldInfo['maximumlength']) {
+				return aDeferred.reject(app.vtranslate('JS_MAXIMUM_TEXT_SIZE_IN_BYTES') + ' ' + fieldInfo['maximumlength']);
+			}
+			AppConnector.request({
+				module: app.getModuleName(),
+				action: 'Fields',
+				mode: 'validateFile',
+				fieldName: fieldInfo['name'],
+				base64: evt.target.result
+			})
+				.done((data) => {
+					if (data.result.validate) {
+						aDeferred.resolve(evt.target.result);
+					} else {
+						aDeferred.reject(data.result.validateError);
+					}
+				})
+				.fail(function () {
+					aDeferred.reject();
+				});
+		};
+		fileReader.readAsDataURL(file);
+		return aDeferred.promise();
 	}
 });

Vulnerable code: 		$result = explode(',', $contents, 2);
		$contentType = $isBase64 = false;
		if (2 === \count($result)) {
			[$metadata, $data] = $result;
			foreach (explode(';', $metadata) as $cur) {
				if ('base64' === $cur) {
					$isBase64 = true;
				} elseif ('data:' === substr($cur, 0, 5)) {
					$contentType = str_replace('data:', '', $cur);
				}
			}
		} else {
			$data = $result[0];
		}
		$data = rawurldecode($data);
		$rawData = $isBase64 ? base64_decode($data) : $data;
		if (\strlen($rawData) < 12) {
			Log::error('Incorrect content value: ' . $contents, __CLASS__);
			return false;
		}
		$fileInstance = static::loadFromContent($rawData, false, array_merge($param, ['mimeType' => $contentType]));
			'crmid' => 0
	'appVersion' => '6.3.220',
	public function getOwners(App\Request $request)
	public function getUserRole(App\Request $request)
	public function getReference(App\Request $request)
	public function verifyPhoneNumber(App\Request $request)
	public function findAddress(App\Request $request)
	public function changeFavoriteOwner(App\Request $request)
	public function validateForField(App\Request $request)
	public function validateByMode(App\Request $request)
/* {[The file is published on the basis of YetiForce Public License 3.0 that can be found in the following directory: licenses/LicenseEN.txt or yetiforce.com]} */
	var t = null,
		imgScal = 1,
		lock = true;
		var r = false,
				var n = document.createElement('input');
	var isFReaderSupported = fileSupport();
		var i = new Image();
		imgPreview.getElement().setHtml('Loading...');
				t.setValueOf('tab-properties', 'width', this.width);
				t.setValueOf('tab-properties', 'height', this.height);
				imgScal = 1;
				if (this.height > 0 && this.width > 0) imgScal = this.width / this.height;
				if (imgScal <= 0) imgScal = 1;
				var p = imgPreview.getElement().$;
			var fileI = t.getContentElement('tab-source', 'file');
			var n = null;
			try {
				n = fileI.getInputElement().$;
			} catch (e) {
				n = null;
			}
			if (n && 'files' in n && n.files && n.files.length > 0 && n.files[0]) {
				if ('type' in n.files[0] && !n.files[0].type.match('image.*')) return;
				if (!FileReader) return;
				imgPreview.getElement().setHtml('Loading...');
				var fr = new FileReader();
				fr.onload = (function (f) {
					return function (e) {
						imgPreview.getElement().setHtml('');
						imagePreviewLoad(e.target.result);
					};
				})(n.files[0]);
				fr.onerror = function () {
				};
				fr.onabort = function () {
				};
				fr.readAsDataURL(n.files[0]);
		var o = {
			w: t.getContentElement('tab-properties', 'width').getValue(),
			h: t.getContentElement('tab-properties', 'height').getValue(),
		var o = getImageDimensions();
		var u = 'px';
			o.h = Math.round(o.w / imgScal);
			o.w = Math.round(o.h * imgScal);
		t.getContentElement('tab-properties', 'width').setValue(o.w),
			t.getContentElement('tab-properties', 'height').setValue(o.h);
		var v = elem.getValue(),
		var sourceElements = [
						onChange: function () {
							imagePreview('file');
			(t = this), (orgWidth = null), (orgHeight = null), (imgScal = 1), (lock = true);
			t.setValueOf('tab-properties', 'lock', lock);
			t.setValueOf('tab-properties', 'vmargin', '0');
			t.setValueOf('tab-properties', 'hmargin', '0');
			t.setValueOf('tab-properties', 'border', '0');
			t.setValueOf('tab-properties', 'align', 'none');
					t.setValueOf('tab-properties', 'width', orgWidth);
					t.setValueOf('tab-properties', 'height', orgHeight);
					imgScal = 1;
					if (!isNaN(orgWidth) && !isNaN(orgHeight) && orgHeight > 0 && orgWidth > 0) imgScal = orgWidth / orgHeight;
					if (imgScal <= 0) imgScal = 1;
					t.setValueOf('tab-properties', 'alt', selectedImg.getAttribute('alt'));
					t.setValueOf('tab-properties', 'hmargin', selectedImg.getAttribute('hspace'));
					t.setValueOf('tab-properties', 'vmargin', selectedImg.getAttribute('vspace'));
					t.setValueOf('tab-properties', 'border', selectedImg.getAttribute('border'));
							t.setValueOf('tab-properties', 'align', 'top');
							t.setValueOf('tab-properties', 'align', 'bottom');
							t.setValueOf('tab-properties', 'align', 'left');
							t.setValueOf('tab-properties', 'align', 'right');
				t.selectPage('tab-properties');
			var src = '';
			/* selected image or new image */
			if (selectedImg) var newImg = selectedImg;
			else var newImg = editor.document.createElement('img');
			newImg.setAttribute('src', src);
			src = null;
			/* Set attributes */
			newImg.setAttribute('alt', t.getValueOf('tab-properties', 'alt').replace(/^\s+/, '').replace(/\s+$/, ''));
			var attr = {
					width: ['width', 'width:#;', 'integer', 1],
					height: ['height', 'height:#;', 'integer', 1],
					vmargin: ['vspace', 'margin-top:#;margin-bottom:#;', 'integer', 0],
					hmargin: ['hspace', 'margin-left:#;margin-right:#;', 'integer', 0],
					align: ['align', ''],
					border: ['border', 'border:# solid black;', 'integer', 0]
				},
				css = [],
				value,
				cssvalue,
				attrvalue,
				k;
			for (k in attr) {
				value = t.getValueOf('tab-properties', k);
				attrvalue = value;
				cssvalue = value;
				unit = 'px';

				if (k == 'align') {
					switch (value) {
						case 'top':
						case 'bottom':
							attr[k][1] = 'vertical-align:#;';
							break;
						case 'left':
						case 'right':
							attr[k][1] = 'float:#;';
							break;
						default:
							value = null;
							break;
				}
				if (attr[k][2] == 'integer') {
					if (value.indexOf('%') >= 0) unit = '%';
					value = parseInt(value, 10);
					if (isNaN(value)) value = null;
					else if (value < attr[k][3]) value = null;
					if (value != null) {
						if (unit == '%') {
							attrvalue = value + '%';
							cssvalue = value + '%';
						} else {
							attrvalue = value;
							cssvalue = value + 'px';
				}
				if (value != null) {
					newImg.setAttribute(attr[k][0], attrvalue);
					css.push(attr[k][1].replace(/#/g, cssvalue));
			}
			if (css.length > 0) newImg.setAttribute('style', css.join(''));
			/* Insert new image */
			if (!selectedImg) editor.insertElement(newImg);
			/* Resize image */
			if (editor.plugins.imageresize) editor.plugins.imageresize.resize(editor, newImg, 800, 800);
/* {[The file is published on the basis of YetiForce Public License 3.0 that can be found in the following directory: licenses/LicenseEN.txt or yetiforce.com]} */

function initPasteEvent(editorInstance) {
	if (editorInstance.addFeature) {
		editorInstance.addFeature({
			allowedContent: 'img[alt,id,!src]{width,height};'
		});
	}

	editorInstance.on('contentDom', function () {
		var editableElement = editorInstance.editable ? editorInstance.editable() : editorInstance.document;
		editableElement.on('paste', onPaste, null, { editor: editorInstance });
	});
}
function onPaste(event) {
	var editor = event.listenerData && event.listenerData.editor;
	var $event = event.data.$;
	var clipboardData = $event.clipboardData;
	var found = false;
	var imageType = /^image/;

	if (!clipboardData) {
		return;
	}
	return Array.prototype.forEach.call(clipboardData.types, function (type, i) {
		if (found) {
			return;
		}
		if (type.match(imageType) || clipboardData.items[i].type.match(imageType)) {
			readImageAsBase64(clipboardData.items[i], editor);
			return (found = true);
		}
	});
}

function readImageAsBase64(item, editor) {
	if (!item || typeof item.getAsFile !== 'function') {
		return;
	}
	var file = item.getAsFile();
	var reader = new FileReader();
	reader.onload = function (evt) {
		var element = editor.document.createElement('img', {
			attributes: {
				src: evt.target.result
			}
		});
		setTimeout(function () {
			editor.insertElement(element);
		}, 10);
	};
	reader.readAsDataURL(file);
}
	init: function (editorInstance) {
		initPasteEvent(editorInstance);
		var pluginName = 'base64image-dialog';
		editorInstance.ui.addToolbarGroup('base64image', 'insert');
		editorInstance.ui.addButton('base64image', {
			label: editorInstance.lang.common.image,
		editorInstance.addCommand(
		editorInstance.on('doubleclick', function (evt) {
				editorInstance.getSelection().selectElement(evt.data.element);
		if (editorInstance.addMenuItem) {
			editorInstance.addMenuGroup('imageToBase64Group');
			editorInstance.addMenuItem('imageToBase64Item', {
				label: editorInstance.lang.common.image,
		if (editorInstance.contextMenu) {
			editorInstance.contextMenu.addListener(function (element, selection) {
					editorInstance.getSelection().selectElement(element);
Fixed code: 	/**
	 * Load file instance from base string.
	 *
	 * @param string $contents
	 * @param array  $param
	 *
	 * @return \self|null
	 */
	public static function loadFromBase(string $contents, array $param = []): ?self
	{
		$result = explode(',', $contents, 2);
		$contentType = $isBase64 = false;
		if (2 === \count($result)) {
			[$metadata, $data] = $result;
			foreach (explode(';', $metadata) as $cur) {
				if ('base64' === $cur) {
					$isBase64 = true;
				} elseif ('data:' === substr($cur, 0, 5)) {
					$contentType = str_replace('data:', '', $cur);
				}
			}
		} else {
			$data = $result[0];
		}
		$data = rawurldecode($data);
		$rawData = $isBase64 ? base64_decode($data) : $data;
		if (\strlen($rawData) < 12) {
			Log::error('Incorrect content value: ' . $contents, __CLASS__);
			return null;
		}
		return static::loadFromContent($rawData, false, array_merge($param, ['mimeType' => $contentType]));
	}

		$fileInstance = static::loadFromBase($contents, $param);
			'crmid' => 0,

	/**
	 * Check base64.
	 *
	 * @param string $input
	 *
	 * @return bool
	 */
	public static function base64(string $input): bool
	{
		if (empty($input)) {
			return false;
		}
		$explode = explode(',', $input);
		return 2 === \count($explode) && 1 === preg_match('%^[a-zA-Z0-9/+]*={0,2}$%', $explode[1]);
	}
	'appVersion' => '6.3.221',
		$this->exposeMethod('validateFile');
	public function getOwners(App\Request $request): void
	public function getUserRole(App\Request $request): void
	public function getReference(App\Request $request): void
	public function verifyPhoneNumber(App\Request $request): void
	public function findAddress(App\Request $request): void
	public function changeFavoriteOwner(App\Request $request): void
	public function validateForField(App\Request $request): void
	public function validateByMode(App\Request $request): void

	/**
	 * Validate file.
	 *
	 * @param App\Request $request
	 *
	 * @return void
	 */
	public function validateFile(App\Request $request): void
	{
		$validate = false;
		if ($request->has('base64')) {
			$fileInstance = \App\Fields\File::loadFromBase($request->getByType('base64', 'base64'), ['validateAllowedFormat' => 'image']);
			if ($fileInstance && $fileInstance->validate()) {
				$validate = true;
			} else {
				$validateError = $fileInstance->validateError;
			}
		}
		$response = new Vtiger_Response();
		$response->setResult([
			'validate' => $validate,
			'validateError' => $validateError ?? null,
		]);
		$response->emit();
	}
/* {[The file is published on the basis of YetiForce Public License 5.0 that can be found in the following directory: licenses/LicenseEN.txt or yetiforce.com]} */
'use strict';
	let self = null,
		sourceElements = [],
		imgScale = 1,
		lock = true,
		maxUploadSize = CONFIG['maxUploadLimit'];
		let r = false,
				let n = document.createElement('input');
	let isFReaderSupported = fileSupport();
		let i = new Image();
		$(imgPreview.getElement().$).progressIndicator({ position: 'html' });
				self.setValueOf('tab-properties', 'width', this.width);
				self.setValueOf('tab-properties', 'height', this.height);
				imgScale = 1;
				if (this.height > 0 && this.width > 0) imgScale = this.width / this.height;
				if (imgScale <= 0) imgScale = 1;
				let p = imgPreview.getElement().$;
			$(imgPreview.getElement().$).progressIndicator({ position: 'html' });
			readImageAsBase64()
				.done(function (base) {
					imagePreviewLoad(base);
				})
				.fail(function () {
				});
		}
	}

	function readImageAsBase64() {
		const aDeferred = jQuery.Deferred();
		let fileI = self.getContentElement('tab-source', 'file'),
			n = null;
		try {
			n = fileI.getInputElement().$;
		} catch (e) {
			n = null;
		}
		if (n && 'files' in n && n.files && n.files.length > 0 && n.files[0]) {
			if (('type' in n.files[0] && !n.files[0].type.match('image.*')) || !FileReader) {
				aDeferred.reject();
				return aDeferred.promise();
			}
			if (n.files[0].size > maxUploadSize) {
				app.showNotify({
					text: app.vtranslate('JS_UPLOADED_FILE_SIZE_EXCEEDS'),
					type: 'error'
				});
				aDeferred.reject();
				return aDeferred.promise();
			let fr = new FileReader();
			fr.onload = (function (f) {
				return function (e) {
					aDeferred.resolve(e.target.result);
				};
			})(n.files[0]);
			fr.onerror = function () {
				aDeferred.reject();
			};
			fr.onabort = function () {
				aDeferred.reject();
			};
			fr.readAsDataURL(n.files[0]);
		return aDeferred.promise();
		let o = {
			w: self.getContentElement('tab-properties', 'width').getValue(),
			h: self.getContentElement('tab-properties', 'height').getValue(),
		let o = getImageDimensions();
		let u = 'px';
			o.h = Math.round(o.w / imgScale);
			o.w = Math.round(o.h * imgScale);
		self.getContentElement('tab-properties', 'width').setValue(o.w),
			self.getContentElement('tab-properties', 'height').setValue(o.h);
		let v = elem.getValue(),
	function validateFile() {
		const fieldInfo = $(editor.element.$).data('fieldinfo');
		let length = editor.getData().length,
			selectedImg = editor.getSelection();
		if (selectedImg) selectedImg = selectedImg.getSelectedElement();
		if (!selectedImg || selectedImg.getName() !== 'img') selectedImg = null;
		if (selectedImg) {
			length = length - selectedImg.getOuterHtml().length;
		}
		const aDeferred = jQuery.Deferred();
		readImageAsBase64()
			.done((base) => {
				length += base.length;
				if (length > fieldInfo['maximumlength']) {
					app.showNotify({
						text: app.vtranslate('JS_MAXIMUM_TEXT_SIZE_IN_BYTES') + ' ' + fieldInfo['maximumlength'],
						type: 'error'
					});
				}
				AppConnector.request({
					module: app.getModuleName(),
					action: 'Fields',
					mode: 'validateFile',
					fieldName: fieldInfo['name'],
					base64: base
				})
					.done((data) => {
						if (data.result.validate) {
							aDeferred.resolve();
						} else {
							app.showNotify({
								text: data.result.validateError,
								type: 'error'
							});
							aDeferred.reject();
						}
					})
					.fail(function () {
						aDeferred.resolve();
					});
			})
			.fail(() => {
				aDeferred.reject();
			});
		return aDeferred.promise();
	}

		sourceElements = [
						size: maxUploadSize,
						onChange: function (a) {
							validateFile()
								.done(() => {
									imagePreview('file');
								})
								.fail(function () {
									self.getContentElement('tab-source', 'file').getInputElement().$.value = null;
									imgPreview.getElement().setHtml('');
								});
			this.getContentElement('tab-source', 'file')
				.getInputElement()
				.$.setAttribute('accept', 'image/jpeg, image/png, image/gif');
			(self = this), (orgWidth = null), (orgHeight = null), (imgScale = 1), (lock = true);
			self.setValueOf('tab-properties', 'lock', lock);
			self.setValueOf('tab-properties', 'vmargin', '0');
			self.setValueOf('tab-properties', 'hmargin', '0');
			self.setValueOf('tab-properties', 'border', '0');
			self.setValueOf('tab-properties', 'align', 'none');
					self.setValueOf('tab-properties', 'width', orgWidth);
					self.setValueOf('tab-properties', 'height', orgHeight);
					imgScale = 1;
					if (!isNaN(orgWidth) && !isNaN(orgHeight) && orgHeight > 0 && orgWidth > 0) imgScale = orgWidth / orgHeight;
					if (imgScale <= 0) imgScale = 1;
					self.setValueOf('tab-properties', 'alt', selectedImg.getAttribute('alt'));
					self.setValueOf('tab-properties', 'hmargin', selectedImg.getAttribute('hspace'));
					self.setValueOf('tab-properties', 'vmargin', selectedImg.getAttribute('vspace'));
					self.setValueOf('tab-properties', 'border', selectedImg.getAttribute('border'));
							self.setValueOf('tab-properties', 'align', 'top');
							self.setValueOf('tab-properties', 'align', 'bottom');
							self.setValueOf('tab-properties', 'align', 'left');
							self.setValueOf('tab-properties', 'align', 'right');
				self.selectPage('tab-properties');
			let src = '';
			validateFile().always(() => {
				/* selected image or new image */
				if (selectedImg) {
					var newImg = selectedImg;
				} else {
					var newImg = editor.document.createElement('img');
				}
				newImg.setAttribute('src', src);
				src = null;
				/* Set attributes */
				newImg.setAttribute('alt', self.getValueOf('tab-properties', 'alt').replace(/^\s+/, '').replace(/\s+$/, ''));
				let attr = {
						width: ['width', 'width:#;', 'integer', 1],
						height: ['height', 'height:#;', 'integer', 1],
						vmargin: ['vspace', 'margin-top:#;margin-bottom:#;', 'integer', 0],
						hmargin: ['hspace', 'margin-left:#;margin-right:#;', 'integer', 0],
						align: ['align', ''],
						border: ['border', 'border:# solid black;', 'integer', 0]
					},
					css = [],
					value,
					cssValue,
					attrValue,
					unit,
					k;
				for (k in attr) {
					value = self.getValueOf('tab-properties', k);
					attrValue = value;
					cssValue = value;
					unit = 'px';

					if (k == 'align') {
						switch (value) {
							case 'top':
							case 'bottom':
								attr[k][1] = 'vertical-align:#;';
								break;
							case 'left':
							case 'right':
								attr[k][1] = 'float:#;';
								break;
							default:
								value = null;
								break;
						}
					if (attr[k][2] == 'integer') {
						if (value.indexOf('%') >= 0) unit = '%';
						value = parseInt(value, 10);
						if (isNaN(value)) value = null;
						else if (value < attr[k][3]) value = null;
						if (value != null) {
							if (unit == '%') {
								attrValue = value + '%';
								cssValue = value + '%';
							} else {
								attrValue = value;
								cssValue = value + 'px';
							}
					if (value != null) {
						newImg.setAttribute(attr[k][0], attrValue);
						css.push(attr[k][1].replace(/#/g, cssValue));
					}
				if (css.length > 0) newImg.setAttribute('style', css.join(''));

				/* Insert new image */
				if (!selectedImg) editor.insertElement(newImg);
				/* Resize image */
				if (editor.plugins.imageresize) editor.plugins.imageresize.resize(editor, newImg, 800, 800);
				editor.updateElement();
			});
/* {[The file is published on the basis of YetiForce Public License 5.0 that can be found in the following directory: licenses/LicenseEN.txt or yetiforce.com]} */
'use strict';
	init: function (editor) {
		if (editor.addFeature) {
			editor.addFeature({
				allowedContent: 'img[alt,id,!src]{width,height};'
			});
		}
		editor.on('paste', (event, a, b) => {
			this.onPaste(event);
		});
		const pluginName = 'base64image-dialog';
		editor.ui.addToolbarGroup('base64image', 'insert');
		editor.ui.addButton('base64image', {
			label: editor.lang.common.image,
		editor.addCommand(
		editor.on('doubleclick', function (evt) {
				editor.getSelection().selectElement(evt.data.element);
		if (editor.addMenuItem) {
			editor.addMenuGroup('imageToBase64Group');
			editor.addMenuItem('imageToBase64Item', {
				label: editor.lang.common.image,
		if (editor.contextMenu) {
			editor.contextMenu.addListener(function (element) {
					editor.getSelection().selectElement(element);
	},
	onPaste: function (event) {
		const self = this,
			allowedTypes = 'image/jpeg|image/png|image/gif',
			dataTransfer = event.data.dataTransfer,
			editor = event.editor,
			count = dataTransfer.getFilesCount();
		for (let index = 0; index < count; index++) {
			let file = dataTransfer.getFile(index);
			if (file.type.match(allowedTypes)) {
				self
					.validateFile(file, editor)
					.done(function (base) {
						let image,
							selectedImg = editor.getSelection();
						if (selectedImg) selectedImg = selectedImg.getSelectedElement();
						if (!selectedImg || selectedImg.getName() !== 'img') selectedImg = null;
						if (selectedImg) {
							image = selectedImg;
						} else {
							image = editor.document.createElement('img');
						}
						image.setAttribute('src', base);
						if (!selectedImg) editor.insertElement(image);
					})
					.fail(function (error) {
						editor.showNotification(error, 'warning');
					});
			} else {
				editor.showNotification(
					app.vtranslate('JS_INVALID_FILE_TYPE') +
						'<br>' +
						app.vtranslate('JS_AVAILABLE_FILE_TYPES') +
						': ' +
						allowedTypes.replace(/\|/g, ', '),
					'warning'
				);
			}
		}
	},
	validateFile: function (file, editor) {
		const aDeferred = jQuery.Deferred();
		if (file.size > CONFIG['maxUploadLimit']) {
			aDeferred.reject(app.vtranslate('JS_UPLOADED_FILE_SIZE_EXCEEDS'));
		}
		this.readAndValidate(file, editor)
			.done(function (base) {
				aDeferred.resolve(base);
			})
			.fail(function (error) {
				aDeferred.reject(error);
			});
		return aDeferred.promise();
	},
	readAndValidate: function (file, editor) {
		const aDeferred = jQuery.Deferred(),
			fieldInfo = $(editor.element.$).data('fieldinfo');
		let length = editor.getData().length,
			selectedImg = editor.getSelection();
		if (selectedImg) selectedImg = selectedImg.getSelectedElement();
		if (!selectedImg || selectedImg.getName() !== 'img') selectedImg = null;
		if (selectedImg) {
			length = length - selectedImg.getOuterHtml().length;
		}
		const fileReader = new FileReader();
		fileReader.onload = function (evt) {
			length += evt.target.result.length;
			if (length > fieldInfo['maximumlength']) {
				return aDeferred.reject(app.vtranslate('JS_MAXIMUM_TEXT_SIZE_IN_BYTES') + ' ' + fieldInfo['maximumlength']);
			}
			AppConnector.request({
				module: app.getModuleName(),
				action: 'Fields',
				mode: 'validateFile',
				fieldName: fieldInfo['name'],
				base64: evt.target.result
			})
				.done((data) => {
					if (data.result.validate) {
						aDeferred.resolve(evt.target.result);
					} else {
						aDeferred.reject(data.result.validateError);
					}
				})
				.fail(function () {
					aDeferred.reject();
				});
		};
		fileReader.readAsDataURL(file);
		return aDeferred.promise();