Issues (752)

server/includes/download_base.php (1 issue)

Severity
1
<?php
2
3
/**
4
 * DownloadBase.
5
 *
6
 * An abstract class which serve as base to manage downloading
7
 */
8
abstract class DownloadBase {
9
	/**
10
	 * Resource of the MAPIStore which holds the message which we need to save as file.
11
	 */
12
	protected $store;
13
14
	/**
15
	 * Entryid of the MAPIStore which holds the message which we need to save as file.
16
	 */
17
	protected $storeId;
18
19
	/**
20
	 * Entryid of the MAPIMessage which we need to save as file.
21
	 */
22
	protected $entryId;
23
24
	/**
25
	 * Array of entryids of all the MAPIMessages which we need to include in ZIP.
26
	 */
27
	protected $entryIds;
28
29
	/**
30
	 * Resource of MAPIMessage which we need to save as file.
31
	 */
32
	protected $message;
33
34
	/**
35
	 * A boolean value, set to false by default, to define if all the attachments are requested to be downloaded in a zip or not.
36
	 */
37
	protected $allAsZip;
38
39
	/**
40
	 * Array to hold file names added into the ZIP which is used to avoid similar file names to be added in ZIP.
41
	 */
42
	protected $fileNameTracker;
43
44
	/**
45
	 * Constructor.
46
	 */
47
	public function __construct() {
48
		$this->storeId = false;
49
		$this->entryId = false;
50
		$this->allAsZip = false;
51
		$this->fileNameTracker = [];
52
		$this->entryIds = [];
53
		$this->message = false;
54
	}
55
56
	/**
57
	 * Function will initialize data for this class object. it will also sanitize data
58
	 * for possible XSS attack because data is received in $_GET.
59
	 *
60
	 * @param array $data parameters received with the request
61
	 */
62
	public function init($data) {
63
		if (isset($data['storeid'])) {
64
			$this->storeId = sanitizeValue($data['storeid'], '', ID_REGEX);
65
		}
66
67
		if (isset($data['AllAsZip'])) {
68
			$this->allAsZip = sanitizeValue($data['AllAsZip'], '', STRING_REGEX);
69
		}
70
71
		if ($this->storeId) {
72
			$this->store = $GLOBALS['mapisession']->openMessageStore(hex2bin((string) $this->storeId));
73
		}
74
75
		if ($this->allAsZip) {
76
			if ($_POST && array_key_exists('entryids', $_POST) && isset($_POST['entryids'])) {
77
				$this->entryIds = $_POST['entryids'];
78
			}
79
		}
80
		else {
81
			if (isset($data['entryid'])) {
82
				$this->entryId = sanitizeValue($data['entryid'], '', ID_REGEX);
83
				$this->message = mapi_msgstore_openentry($this->store, hex2bin((string) $this->entryId));
84
			}
85
		}
86
	}
87
88
	/**
89
	 * Offers the functionality to postfixed the file name with number derived from
90
	 * the appearance of other file with same name.
91
	 * We need to keep track of the file names used so far to prevent duplicates.
92
	 *
93
	 * @param string $filename name of the file to be added in ZIP
94
	 *
95
	 * @return string $filename changed name of the file to be added in ZIP to avoid same file names
96
	 */
97
	public function handleDuplicateFileNames($filename) {
98
		// A local name is optional.
99
		if (!empty($filename)) {
100
			// Check and add if file name is not there in tracker array
101
			if (!array_key_exists($filename, $this->fileNameTracker)) {
102
				$this->fileNameTracker[$filename] = 0;
103
			}
104
105
			// We have to make sure that there aren't two of the same file names.
106
			// Otherwise, one file will override the other and we will be missing the file.
107
			while ($this->fileNameTracker[$filename] > 0) {
108
				$fileNameInfo = pathinfo($filename);
109
				$intNext = $this->fileNameTracker[$filename]++;
110
				$filename = "{$fileNameInfo['filename']} ({$intNext}).{$fileNameInfo['extension']}";
111
112
				// Check and add if newly prepared file name is not there in tracker array
113
				if (!array_key_exists($filename, $this->fileNameTracker)) {
114
					$this->fileNameTracker[$filename] = 0;
115
				}
116
			}
117
118
			// Add to the count.
119
			++$this->fileNameTracker[$filename];
120
		}
121
122
		return $filename;
123
	}
124
125
	/**
126
	 * Helper function to set necessary headers.
127
	 *
128
	 * @param string $filename      proper name for the file to be downloaded
129
	 * @param Number $contentLength total size of file content
130
	 */
131
	public function setNecessaryHeaders($filename, $contentLength) {
132
		// Set the headers
133
		header('Pragma: public');
134
		header('Expires: 0'); // set expiration time
135
		header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
136
		header('Content-Transfer-Encoding: binary');
137
138
		// Set Content Disposition header
139
		header('Content-Disposition: attachment; filename="' . addslashes(browserDependingHTTPHeaderEncode($filename)) . '"');
140
		// Set content type header
141
		header('Content-Type: application/octet-stream');
142
143
		// Set the file length
144
		header('Content-Length: ' . $contentLength);
145
	}
146
147
	/**
148
	 * Function will return message type according to the MAPI message class
149
	 * to display exception. so, user can easily understand the exception message.
150
	 *
151
	 * @param string $mapiMessageClass message type as defined in MAPI
152
	 *
153
	 * @return string $messageClass message type to prepare exception message
154
	 */
155
	public function getMessageType($mapiMessageClass) {
156
		$messageClass = '';
0 ignored issues
show
The assignment to $messageClass is dead and can be removed.
Loading history...
157
158
		// Convert message class into human readable format, so user can easily understand the display message.
159
		return match ($this->getTrimmedMessageClass($mapiMessageClass)) {
160
			'Appointment' => _('Appointment'),
161
			'StickyNote' => _('Sticky Note'),
162
			'Contact' => _('Contact'),
163
			'DistList' => _('Distribution list'),
164
			'Task' => _('Task'),
165
			'TaskRequest' => _('Task Request'),
166
			default => $mapiMessageClass,
167
		};
168
	}
169
170
	/**
171
	 * Returns message-class removing technical prefix/postfix from
172
	 * original/technical message class.
173
	 *
174
	 * @param string $mapiMessageClass message type as defined in MAPI
175
	 *
176
	 * @return string $messageClass message class without any prefix/postfix
177
	 */
178
	public function getTrimmedMessageClass($mapiMessageClass) {
179
		// Here, we have technical message class, so we need to remove technical prefix/postfix, if any.
180
		// Creates an array of strings by splitting the message class from dot(.)
181
		$explodedMessageClass = explode(".", $mapiMessageClass);
182
		$ipmIndex = array_search('IPM', $explodedMessageClass);
183
184
		return $explodedMessageClass[$ipmIndex + 1];
185
	}
186
187
	/**
188
	 * Function will encode all the necessary information about the exception
189
	 * into JSON format and send the response back to client.
190
	 *
191
	 * @param object $exception exception object
192
	 */
193
	public function handleSaveMessageException($exception) {
194
		$return = [];
195
196
		// MAPI_E_NOT_FOUND exception contains generalize exception message.
197
		// Set proper exception message as display message should be user understandable.
198
		if ($exception->getCode() == MAPI_E_NOT_FOUND) {
199
			$exception->setDisplayMessage(_('Could not find message, either it has been moved or deleted.'));
200
		}
201
202
		// Set the headers
203
		header('Expires: 0'); // set expiration time
204
		header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
205
206
		// Set Content Disposition header
207
		header('Content-Disposition: inline');
208
		// Set content type header
209
		header('Content-Type: text/plain');
210
211
		// prepare exception response according to exception class
212
		if ($exception instanceof MAPIException) {
213
			$return = [
214
				'success' => false,
215
				'zarafa' => [
216
					'error' => [
217
						'type' => ERROR_MAPI,
218
						'info' => [
219
							'hresult' => $exception->getCode(),
220
							'hresult_name' => get_mapi_error_name($exception->getCode()),
221
							'file' => $exception->getFileLine(),
222
							'display_message' => $exception->getDisplayMessage(),
223
						],
224
					],
225
				],
226
			];
227
		}
228
		elseif ($exception instanceof ZarafaException) {
229
			$return = [
230
				'success' => false,
231
				'zarafa' => [
232
					'error' => [
233
						'type' => ERROR_ZARAFA,
234
						'info' => [
235
							'file' => $exception->getFileLine(),
236
							'display_message' => $exception->getDisplayMessage(),
237
							'original_message' => $exception->getMessage(),
238
						],
239
					],
240
				],
241
			];
242
		}
243
		elseif ($exception instanceof BaseException) {
244
			$return = [
245
				'success' => false,
246
				'zarafa' => [
247
					'error' => [
248
						'type' => ERROR_GENERAL,
249
						'info' => [
250
							'file' => $exception->getFileLine(),
251
							'display_message' => $exception->getDisplayMessage(),
252
							'original_message' => $exception->getMessage(),
253
						],
254
					],
255
				],
256
			];
257
		}
258
		else {
259
			$return = [
260
				'success' => false,
261
				'zarafa' => [
262
					'error' => [
263
						'type' => ERROR_GENERAL,
264
						'info' => [
265
							'display_message' => _('Operation failed'),
266
							'original_message' => $exception->getMessage(),
267
						],
268
					],
269
				],
270
			];
271
		}
272
		echo json_encode($return);
273
	}
274
275
	/**
276
	 * Abstract Generic function to check received data and decide either the eml/vcf file or
277
	 * ZIP file is requested to be downloaded.
278
	 */
279
	abstract protected function download();
280
}
281