Test Failed
Push — master ( 19e7e7...e85ccb )
by
unknown
07:48
created

KendoxModule::getEmlStream()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
use Kendox\Client;
4
5
require_once __DIR__ . '/vendor/autoload.php';
6
require_once __DIR__ . "/kendox-client/class.kendox-client.php";
7
require_once __DIR__ . "/class.attachment-info.php";
8
require_once __DIR__ . "/class.uploadfile.php";
9
10
class KendoxModule extends Module {
11
	// Certificate prod environment
12
	public $pfxFile = __DIR__ . '/../resources/KendoxCertificate.pfx';
13
	public $pfxPw = 'KxTest';
14
15
	// Certificate test environment
16
	public $pfxFileTest = __DIR__ . '/../resources/KendoxCertificate.pfx';
17
	public $pfxPwTest = 'KxTest';
18
19
	public $dialogUrl;
20
	public $mapiMessage;
21
22
	public $kendoxClient;
23
24
	/**
25
	 * @constructor
26
	 */
27
	public function __construct($id, $data) {
28
		parent::__construct($id, $data);
29
		$this->store = $GLOBALS['mapisession']->getDefaultMessageStore();
0 ignored issues
show
Bug Best Practice introduced by
The property store does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
30
	}
31
32
	/**
33
	 * Executes all the actions in the $data variable.
34
	 * Exception part is used for authentication errors also.
35
	 *
36
	 * @return bool true on success or false on failure
37
	 */
38
	public function execute() {
39
		foreach ($this->data as $actionType => $actionData) {
40
			if (isset($actionType)) {
41
				try {
42
					switch ($actionType) {
43
						case "attachmentinfo":
44
							$response = $this->getAttachmentInfo($actionData["storeId"], $actionData["mailEntryId"]);
45
							$this->addActionData($actionType, $response);
46
							$GLOBALS['bus']->addData($this->getResponseData());
47
							break;
48
49
						case "upload":
50
							$response = $this->upload($actionData["storeId"], $actionData["mailEntryId"], $actionData["uploadType"], $actionData["selectedAttachments"], $actionData["environment"], $actionData["apiUrl"], $actionData["userEMail"]);
51
							$this->addActionData($actionType, $response);
52
							$GLOBALS['bus']->addData($this->getResponseData());
53
							break;
54
					}
55
				}
56
				catch (Exception $e) {
57
					$response = [];
58
					$response["Successful"] = false;
59
					$response["errorMessage"] = $e->getMessage();
60
					$this->addActionData($actionType, $response);
61
					$GLOBALS['bus']->addData($this->getResponseData());
62
				}
63
			}
64
		}
65
	}
66
67
	/**
68
	 * Get attachment information (meta data) of mail.
69
	 *
70
	 * @param mixed $storeId
71
	 * @param mixed $mailEntryId
72
	 */
73
	private function getAttachmentInfo($storeId, $mailEntryId) {
74
		$this->loadMapiMessage($storeId, $mailEntryId);
75
		$items = [];
76
		$attachmentTable = mapi_message_getattachmenttable($this->mapiMessage);
77
		$messageAttachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME]);
78
		foreach ($messageAttachments as $att) {
79
			$item = new AttachmentInfo();
80
			$item->id = $att[PR_ATTACH_NUM];
81
			$item->name = $att[PR_ATTACH_LONG_FILENAME];
82
			$item->size = $att[PR_ATTACH_SIZE];
83
			$items[] = $item;
84
		}
85
		$response = [];
86
		$response["Successful"] = true;
87
		$response["attachments"] = $items;
88
89
		return $response;
90
	}
91
92
	/**
93
	 * Upload of e-mail to InfoShare.
94
	 *
95
	 * @param string $mailEntryId         Mail Entry ID
96
	 * @param mixed  $storeId
97
	 * @param mixed  $uploadType
98
	 * @param mixed  $selectedAttachments
99
	 * @param mixed  $environment
100
	 * @param mixed  $apiUrl
101
	 * @param mixed  $userEMail
102
	 *
103
	 * @return object
104
	 */
105
	private function upload($storeId, $mailEntryId, $uploadType, $selectedAttachments, $environment, $apiUrl, $userEMail) {
106
		$emlFile = null;
107
		$this->loadMapiMessage($storeId, $mailEntryId);
108
		// Write temporary message file (.EML)
109
		// Send to Kendox InfoShare
110
		$uploadFiles = [];
111
		if ($uploadType == "fullEmail") {
112
			$emlFile = $this->createTempEmlFileFromMapiMessage($mailEntryId);
113
			if (!file_exists($emlFile)) {
114
				throw new Exception("EML file " . $emlFile . " not available.");
115
			}
116
			$file = new UploadFile();
117
			$file->tempFile = $emlFile;
118
			$file->fileType = "email";
119
			$file->fileName = "email.eml";
120
			$file->fileLength = filesize($emlFile);
0 ignored issues
show
Bug introduced by
The property fileLength does not seem to exist on UploadFile.
Loading history...
121
			$file->kendoxFileId = null;
122
			$uploadFiles[] = $file;
123
		}
124
		if ($uploadType == "attachmentsOnly") {
125
			$uploadFiles = $this->getUploadFilesFromSelectedAttachments($selectedAttachments);
126
		}
127
128
		try {
129
			$this->sendFiles($environment, $uploadFiles, $apiUrl, $userEMail);
130
		}
131
		catch (Exception $ex) {
132
			throw $ex;
133
		}
134
		finally {
135
			if ($emlFile != null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $emlFile of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
136
				@unlink($emlFile);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

136
				/** @scrutinizer ignore-unhandled */ @unlink($emlFile);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
137
			}
138
			foreach ($uploadFiles as $uploadFile) {
139
				@unlink($uploadFile->tempFile);
140
			}
141
		}
142
143
		try {
144
			// Return response
145
			$response = [];
146
			$response["Successful"] = true;
147
			$response["apiUrl"] = $apiUrl;
148
			$response["userEMail"] = $userEMail;
149
			$response["messageId"] = $mailEntryId;
150
			$response["kendoxConnectionId"] = $this->kendoxClient->ConnectionId;
151
			$response["kendoxFiles"] = $uploadFiles;
152
		}
153
		catch (Exception $ex) {
154
			if ($emlFile != null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $emlFile of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
155
				@unlink($emlFile);
156
			}
157
			if ($uploadFiles != null) {
158
				foreach ($uploadFiles as $uploadFile) {
159
					@unlink($uploadFile->tempFile);
160
				}
161
			}
162
			$this->logErrorAndThrow("Error on building response message", $ex);
163
		}
164
165
		return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response returns the type array which is incompatible with the documented return type object.
Loading history...
166
	}
167
168
	private function loadMapiMessage($storeId, $mailEntryId) {
169
		try {
170
			// Read message store
171
			$store = $GLOBALS['mapisession']->openMessageStore(hex2bin($storeId));
172
		}
173
		catch (Exception $ex) {
174
			$this->logErrorAndThrow("Error on open MAPI store", $ex);
175
		}
176
177
		try {
178
			$this->mapiMessage = mapi_msgstore_openentry($store, hex2bin($mailEntryId));
179
		}
180
		catch (Exception $ex) {
181
			$this->logErrorAndThrow("Error on open MAPI message", $ex);
182
		}
183
	}
184
185
	private function createTempEmlFileFromMapiMessage($mailEntryId) {
186
		// Read message properties
187
		try {
188
			$messageProps = mapi_getprops($this->mapiMessage, [PR_SUBJECT, PR_MESSAGE_CLASS]);
189
		} catch (Exception $ex) {
190
			$this->logErrorAndThrow("Error on getting MAPI message properties", $ex);
191
		}
192
193
		// Get EML-Stream
194
		try {
195
			$fileName = $this->sanitizeValue($mailEntryId, '', ID_REGEX) . '.eml';
0 ignored issues
show
Unused Code introduced by
The assignment to $fileName is dead and can be removed.
Loading history...
196
			$stream = $this->getEmlStream($messageProps);
197
			$stat = mapi_stream_stat($stream);
198
		}
199
		catch (Exception $ex) {
200
			$this->logErrorAndThrow("Error on reading EML stream from MAPI Message", $ex);
201
		}
202
203
		// Create temporary file
204
		try {
205
			// Set the file length
206
			$fileLength = $stat['cb'];
207
			$tempFile = $this->createTempFilename();
208
			// Read stream for whole message
209
			for ($i = 0; $i < $fileLength; $i += BLOCK_SIZE) {
210
				$appendData = mapi_stream_read($stream, BLOCK_SIZE);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $stream does not seem to be defined for all execution paths leading up to this point.
Loading history...
211
				file_put_contents($tempFile, $appendData, FILE_APPEND);
212
			}
213
214
			return $tempFile;
215
		}
216
		catch (Exception $ex) {
217
			$this->logErrorAndThrow("Error on writing temporary EML file", $ex);
218
		}
219
	}
220
221
	private function createTempFilename() {
222
		$tempPath = TMP_PATH;
223
		return $tempPath . $this->getGUID() . ".tmp";
224
	}
225
226
	public function getGUID() {
227
		if (function_exists('com_create_guid')) {
228
			return com_create_guid();
229
		}
230
		mt_srand((float) microtime() * 10000); // optional for php 4.2.0 and up.
0 ignored issues
show
Bug introduced by
(double)microtime() * 10000 of type double is incompatible with the type integer expected by parameter $seed of mt_srand(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

230
		mt_srand(/** @scrutinizer ignore-type */ (float) microtime() * 10000); // optional for php 4.2.0 and up.
Loading history...
231
		$charid = strtoupper(md5(uniqid(random_int(0, mt_getrandmax()), true)));
232
		$hyphen = chr(45); // "-"
233
234
		return chr(123) . // "{"
235
			substr($charid, 0, 8) . $hyphen .
236
			substr($charid, 8, 4) . $hyphen .
237
			substr($charid, 12, 4) . $hyphen .
238
			substr($charid, 16, 4) . $hyphen .
239
			substr($charid, 20, 12) .
240
			chr(125); // "}"
241
	}
242
243
	private function getUploadFilesFromSelectedAttachments($selectedAttachments) {
244
		try {
245
			$uploadFiles = [];
246
			$attachmentTable = mapi_message_getattachmenttable($this->mapiMessage);
247
			$messageAttachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME]);
248
			foreach ($selectedAttachments as $att) {
249
				$tmpFile = $this->createTempFilename();
250
251
				try {
252
					$this->saveAttachmentToTempFile($tmpFile, $att["attachmentNumber"]);
253
					$file = new UploadFile();
254
					$file->tempFile = $tmpFile;
255
					$file->fileType = "attachment";
256
					$file->fileName = $messageAttachments[$att["attachmentNumber"]][PR_ATTACH_LONG_FILENAME];
257
					$file->fileLength = filesize($tmpFile);
0 ignored issues
show
Bug introduced by
The property fileLength does not seem to exist on UploadFile.
Loading history...
258
					$file->kendoxFileId = null;
259
					$uploadFiles[] = $file;
260
				}
261
				catch (Exception $exFile) {
262
					@unlink($tmpFile);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

262
					/** @scrutinizer ignore-unhandled */ @unlink($tmpFile);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
263
264
					throw $exFile;
265
				}
266
			}
267
			if (count($uploadFiles) == 0) {
268
				throw new Exception("No attachments selected.");
269
			}
270
271
			return $uploadFiles;
272
		}
273
		catch (Exception $ex) {
274
			$this->logErrorAndThrow("Error on creating upload files from attachment.", $ex);
275
		}
276
	}
277
278
	/**
279
	 * Sending file to Kendox InfoShare.
280
	 *
281
	 * @param UploadFile[] $uploadFiles Files to upload
282
	 * @param mixed        $environment
283
	 * @param mixed        $apiUrl
284
	 * @param mixed        $userEMail
285
	 */
286
	private function sendFiles($environment, $uploadFiles, $apiUrl, $userEMail) {
287
		try {
288
			$pfx = $this->pfxFile;
289
			$pfxPw = $this->pfxPw;
290
			if ($environment != "prod") {
291
				$pfx = $this->pfxFileTest;
292
				$pfxPw = $this->pfxPwTest;
293
			}
294
			if (!file_exists($pfx)) {
295
				throw new Exception(_("Kendox certificate is not available."));
296
			}
297
			$this->kendoxClient = new Client($apiUrl);
298
			$uid = $this->kendoxClient->loginWithToken($pfx, $pfxPw, "svc_grommunio");
0 ignored issues
show
Unused Code introduced by
The assignment to $uid is dead and can be removed.
Loading history...
299
			$query = [
300
				[
301
					"ColumnName" => "email",
302
					"RelationalOperator" => "Equals",
303
					"Value" => $userEMail],
304
			];
305
			$result = $this->kendoxClient->userTableQuery("grommunio", $query, false);
306
			if (count($result) == 0) {
307
				throw new Exception("User with e-mail address " . $userEMail . " not found in Kendox user table grommunio.");
308
			}
309
			$uid = $result[0][1];
310
			$this->kendoxClient->logout();
311
			// Login and upload
312
			$this->kendoxClient->loginWithToken($pfx, $pfxPw, $uid);
313
			foreach ($uploadFiles as $uploadFile) {
314
				try {
315
					$uploadFile->kendoxFileId = $this->kendoxClient->uploadFile($uploadFile->tempFile);
316
				}
317
				catch (Exception $exUpload) {
318
					$this->logErrorAndThrow("Upload of file " . $uploadFile->fileName . " failed.", $exUpload);
319
				}
320
			}
321
		}
322
		catch (Exception $ex) {
323
			$this->logErrorAndThrow("Sending files failed", $ex);
324
		}
325
	}
326
327
	/**
328
	 * Logging an error and throw the exception.
329
	 *
330
	 * @param string    $displayMessage
331
	 * @param Exception $ex
332
	 */
333
	public function logErrorAndThrow($displayMessage, $ex) {
334
		$errMsg = $displayMessage . ": " . $ex->getMessage();
335
		error_log($errMsg);
336
		error_log($ex->getTraceAsString());
337
338
		throw new Exception($errMsg);
339
	}
340
341
	/**
342
	 * Function will obtain stream from the message, For email messages it will open email as
343
	 * inet object and get the stream content as eml format
344
	 *
345
	 * @param array $messageProps properties of this particular message
346
	 *
347
	 * @return Stream $stream the eml stream obtained from message
0 ignored issues
show
Bug introduced by
The type Stream was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
348
	 */
349
	public function getEmlStream($messageProps) {
350
		$addrBook = $GLOBALS['mapisession']->getAddressbook();
351
		return mapi_inetmapi_imtoinet($GLOBALS['mapisession']->getSession(), $addrBook, $this->mapiMessage, []);
352
	}
353
354
	/**
355
	 * Funtion to get binary content of an a ttachment
356
	 * PR_ATTACH_DATA_BIN.
357
	 *
358
	 * @param string $tempFile         Path and file of temporary attachment file
359
	 * @param int    $attachmentNumber Number of attachment
360
	 *
361
	 * @return string
362
	 */
363
	public function saveAttachmentToTempFile($tempFile, $attachmentNumber) {
364
		$attach = mapi_message_openattach($this->mapiMessage, $attachmentNumber);
365
		file_put_contents($tempFile, mapi_attach_openbin($attach, PR_ATTACH_DATA_BIN));
366
	}
367
368
	/**
369
	 * Function to sanitize user input values to prevent XSS attacks.
370
	 *
371
	 * @param mixed  $value   value that should be sanitized
372
	 * @param mixed  $default default value to return when value is not safe
373
	 * @param string $regex   regex to validate values based on type of value passed
374
	 */
375
	public function sanitizeValue($value, $default = '', $regex = false) {
376
		$result = addslashes($value);
377
		if ($regex) {
378
			$match = preg_match_all($regex, $result);
379
			if (!$match) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $match of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
380
				$result = $default;
381
			}
382
		}
383
384
		return $result;
385
	}
386
}
387