CVE-2022-1411 (published 2022-05-05T11:15Z)

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.

Severity: MEDIUM
CVSS v2 base score: 4.3 (AV:N/AC:M/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 6.1, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N)

Weakness information:
cwe_id: CWE-434
cwe_name: Unrestricted Upload of File with Dangerous Type
description: The product allows the upload or transfer of dangerous file types that are automatically processed within its environment.
extended_description: 
            
            
            
            
            
         
         

Fix information:
repo_url: https://github.com/yetiforcecompany/yetiforcecrm
commit_details: repo_url: https://github.com/yetiforcecompany/yetiforcecrm
author: Mariusz Krzaczkowski
author_date: 2022-05-05 11:49:50+02:00
msg: Added validation to pasted files to the wysiwyg editor
repository: repo_url: https://github.com/yetiforcecompany/yetiforcecrm
repo_name: YetiForceCompany/YetiForceCRM
description: Our team created for you one of the most innovative CRM systems that supports mainly business processes and allows for customization according to your needs. Be ahead of your competition and implement YetiForce!
repo_language: PHP
owner: yetiforcecompany
file_changes: filename: File.php
change_type: ModificationType.MODIFY
diff: @@ -166,6 +166,39 @@ class File
 		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.
 	 *
@@ -881,27 +914,7 @@ class File
 	 */
 	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;
 		}
@@ -1230,7 +1243,7 @@ class File
 			'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])) {

programming_language: PHP
filename: Validator.php
change_type: ModificationType.MODIFY
diff: @@ -483,4 +483,20 @@ class Validator
 			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]);
+	}
 }

programming_language: PHP
filename: version.php
change_type: ModificationType.MODIFY
diff: @@ -1,7 +1,7 @@
 <?php
 
 return [
-	'appVersion' => '6.3.220',
+	'appVersion' => '6.3.221',
 	'patchVersion' => '2022.05.05',
 	'lib_roundcube' => '0.2.12',
 ];

programming_language: PHP
filename: Fields.php
change_type: ModificationType.MODIFY
diff: @@ -59,6 +59,7 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 		$this->exposeMethod('validateByMode');
 		$this->exposeMethod('verifyPhoneNumber');
 		$this->exposeMethod('changeFavoriteOwner');
+		$this->exposeMethod('validateFile');
 	}
 
 	/**
@@ -68,7 +69,7 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 	 *
 	 * @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);
@@ -121,7 +122,7 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 	 *
 	 * @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);
@@ -148,7 +149,7 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 	 *
 	 * @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));
@@ -195,7 +196,7 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 	 *
 	 * @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');
@@ -224,7 +225,7 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 	 *
 	 * @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();
@@ -243,7 +244,7 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 	 * @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);
@@ -265,7 +266,7 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 	 *
 	 * @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()) {
@@ -288,7 +289,7 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 	 *
 	 * @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);
@@ -299,4 +300,30 @@ class Vtiger_Fields_Action extends \App\Controller\Action
 		]);
 		$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();
+	}
 }

programming_language: PHP
filename: dialog.js
change_type: ModificationType.MODIFY
diff: @@ -1,21 +1,24 @@
-/* {[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) {
@@ -24,7 +27,7 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
 		n = null;
 		return r;
 	}
-	var isFReaderSupported = fileSupport();
+	let isFReaderSupported = fileSupport();
 
 	/* Load preview image */
 	function imagePreviewLoad(s) {
@@ -35,10 +38,10 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
 		}
 
 		/* 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 () {
@@ -47,11 +50,11 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
 
 			/* 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;
@@ -62,7 +65,7 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
 
 			/* Insert preview image */
 			try {
-				var p = imgPreview.getElement().$;
+				let p = imgPreview.getElement().$;
 				if (p) p.appendChild(this);
 			} catch (e) {}
 		};
@@ -82,39 +85,61 @@ 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'
 		};
@@ -128,25 +153,25 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
 	}
 
 	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);
@@ -154,8 +179,55 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
 		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'],
@@ -165,8 +237,16 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
 						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('');
+								});
 						}
 					}
 				]
@@ -240,10 +320,13 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
 				);
 		},
 		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();
@@ -251,11 +334,11 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
 			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');
@@ -265,13 +348,13 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
 					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') {
@@ -281,38 +364,38 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
 					}
 				}
 				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) {
@@ -320,77 +403,85 @@ CKEDITOR.dialog.add('base64image-dialog', function (editor) {
 			}
 			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 */

programming_language: JavaScript
filename: plugin.js
change_type: ModificationType.MODIFY
diff: @@ -1,72 +1,28 @@
-/* {[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:
@@ -78,29 +34,119 @@ CKEDITOR.plugins.add('base64image', {
 				]
 			})
 		);
-		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();
 	}
 });

programming_language: JavaScript