1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Allows XenForo to receive Threema messages by providing a callback. |
4
|
|
|
* |
5
|
|
|
* @package ThreemaGateway |
6
|
|
|
* @author rugk |
7
|
|
|
* @copyright Copyright (c) 2015-2016 rugk |
8
|
|
|
* @license MIT |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
class ThreemaGateway_Handler_Action_Callback extends ThreemaGateway_Handler_Action_Abstract |
12
|
|
|
{ |
13
|
|
|
/** |
14
|
|
|
* @var string used by strtotime to allow messages sent in the future |
15
|
|
|
*/ |
16
|
|
|
const ALLOW_FUTURE_MESSAGE_TIME = '+5 sec'; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* @var XenForo_Input raw parameters |
20
|
|
|
*/ |
21
|
|
|
protected $input; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @var array filtered parameters |
25
|
|
|
*/ |
26
|
|
|
protected $filtered; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var bool whether it has been checked that the message is not used in a |
30
|
|
|
* replay attack |
31
|
|
|
*/ |
32
|
|
|
protected $messageReplayChecked = false; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Initializes handling for processing a request callback. |
36
|
|
|
* |
37
|
|
|
* @param Zend_Controller_Request_Http $request |
38
|
|
|
*/ |
39
|
|
|
public function initCallbackHandling(Zend_Controller_Request_Http $request) |
40
|
|
|
{ |
41
|
|
|
$this->input = new XenForo_Input($request); |
42
|
|
|
|
43
|
|
|
$this->filtered = $this->input->filter([ |
44
|
|
|
'accesstoken' => XenForo_Input::STRING, |
45
|
|
|
'from' => XenForo_Input::STRING, |
46
|
|
|
'to' => XenForo_Input::STRING, |
47
|
|
|
'messageId' => XenForo_Input::STRING, |
48
|
|
|
'date' => XenForo_Input::DATE_TIME, |
49
|
|
|
'nonce' => XenForo_Input::STRING, |
50
|
|
|
'box' => XenForo_Input::STRING, |
51
|
|
|
'mac' => XenForo_Input::STRING |
52
|
|
|
]); |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Validates the callback request. In case of failure the Gateway server |
57
|
|
|
* should not retry here as it likely would not help anyway. |
58
|
|
|
* |
59
|
|
|
* This validation is only a basic validation and does not handle with any |
60
|
|
|
* potentially secret data to prevent any exposures. |
61
|
|
|
* It makes sure malwformed requests can be denied fastly without needing |
62
|
|
|
* to check the authentity/security of the message. |
63
|
|
|
* |
64
|
|
|
* @param string|array $errorString Output error string/array |
65
|
|
|
* |
66
|
|
|
* @return bool |
67
|
|
|
*/ |
68
|
|
|
public function validatePreConditions(&$errorString) |
69
|
|
|
{ |
70
|
|
|
/** @var XenForo_Options $options */ |
71
|
|
|
$options = XenForo_Application::getOptions(); |
72
|
|
|
|
73
|
|
|
// only allow POST requests (unless GET is allowed in ACP) |
74
|
|
|
if (!$this->settings->isDebug() || !$options->threema_gateway_allow_get_receive) { |
75
|
|
|
// as an exception we access the superglobal directly here as it is |
76
|
|
|
// difficult to get the request object |
77
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { |
78
|
|
|
$errorString = [null, 'No POST request.', '']; |
79
|
|
|
return false; |
80
|
|
|
}; |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
// special error message to let users know not to forget the access |
84
|
|
|
// token |
85
|
|
|
if (!$this->input->inRequest('accesstoken')) { |
86
|
|
|
$errorString = 'Access token missing'; |
87
|
|
|
return false; |
88
|
|
|
}; |
89
|
|
|
|
90
|
|
|
// check for other missing parameters |
91
|
|
|
if (!$this->input->inRequest('from') || |
92
|
|
|
!$this->input->inRequest('to') || |
93
|
|
|
!$this->input->inRequest('messageId') || |
94
|
|
|
!$this->input->inRequest('date') || |
95
|
|
|
!$this->input->inRequest('nonce') || |
96
|
|
|
!$this->input->inRequest('box') || |
97
|
|
|
!$this->input->inRequest('mac') |
98
|
|
|
) { |
99
|
|
|
$errorString = [null, 'Invalid request: parameter missing', 'Invalid request']; |
100
|
|
|
return false; |
101
|
|
|
}; |
102
|
|
|
|
103
|
|
|
if (!$this->settings->isEndtoEnd()) { |
104
|
|
|
$errorString = [null, 'Receiving messages is not supported, end to end mode is not configured (correctly)', 'Receiving messages is not supported']; |
105
|
|
|
return false; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
return true; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* Validates the callback request's authenticy and integrity. In case of |
113
|
|
|
* failure the Gateway server SHOULD retry. |
114
|
|
|
* |
115
|
|
|
* This validates the integrity of the request and the authentity that the |
116
|
|
|
* calling instance actually is the Threema Gateway server. |
117
|
|
|
* Retrying is allowed as the secrets, which are used to validate the |
118
|
|
|
* request may change at any time and to avoid a loss of messages the |
119
|
|
|
* Gateway server is supposed to retry the delivery. |
120
|
|
|
* |
121
|
|
|
* @param string|array $errorString Output error string/array |
122
|
|
|
* |
123
|
|
|
* @return bool |
124
|
|
|
*/ |
125
|
|
|
public function validateRequest(&$errorString) |
126
|
|
|
{ |
127
|
|
|
// access token validation (authentication of Gateway server) |
128
|
|
|
/** @var XenForo_Options $options */ |
129
|
|
|
$options = XenForo_Application::getOptions(); |
130
|
|
|
if (!$options->threema_gateway_receivecallback) { |
131
|
|
|
$errorString = [null, 'Unverified request: access token is not configured', 'Unverified request']; |
132
|
|
|
return false; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
View Code Duplication |
if (!$this->getCryptTool()->stringCompare( |
|
|
|
|
136
|
|
|
$options->threema_gateway_receivecallback, |
137
|
|
|
$this->filtered['accesstoken'] |
138
|
|
|
)) { |
139
|
|
|
$errorString = [null, 'Unverified request: access token invalid', 'Unverified request']; |
140
|
|
|
return false; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
// HMAC validation (verifies integrity of request) |
144
|
|
|
if (!$this->getE2EHelper()->checkMac( |
145
|
|
|
$this->filtered['from'], |
146
|
|
|
$this->filtered['to'], |
147
|
|
|
$this->filtered['messageId'], |
148
|
|
|
$this->filtered['date'], |
149
|
|
|
$this->filtered['nonce'], |
150
|
|
|
$this->filtered['box'], |
151
|
|
|
$this->filtered['mac'], |
152
|
|
|
$this->settings->getSecret() |
153
|
|
|
)) { |
154
|
|
|
$errorString = [null, 'Unverified request: HMAC verification failed', 'Unverified request']; |
155
|
|
|
return false; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
return true; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Validates the callback request formally. In case of failure the Gateway |
163
|
|
|
* server should NOT retry, as it likely would not help anyway. |
164
|
|
|
* |
165
|
|
|
* This validation is only a formal validation of the request data. The |
166
|
|
|
* request should already have been validated ({@see validateRequest()}). |
167
|
|
|
* In contrast to the basic validation ({@see validatePreConditions()}) this |
168
|
|
|
* validation deals with secret data and furthermore assures that the send |
169
|
|
|
* request is valid. |
170
|
|
|
* It is used to prevent malformed (but verified) bad requests to get to the |
171
|
|
|
* decryption part, which cannot decrypt them anyway. |
172
|
|
|
* |
173
|
|
|
* @param string|array $errorString Output error string/array |
174
|
|
|
* |
175
|
|
|
* @return bool |
176
|
|
|
*/ |
177
|
|
|
public function validateFormalities(&$errorString) |
178
|
|
|
{ |
179
|
|
|
// simple, formal validation of Gateway ID |
180
|
|
View Code Duplication |
if (!$this->getCryptTool()->stringCompare($this->filtered['to'], $this->settings->getId())) { |
|
|
|
|
181
|
|
|
$errorString = [null, 'Invalid request: formal verification failed', 'Invalid request']; |
182
|
|
|
return false; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** @var XenForo_Options $options */ |
186
|
|
|
$options = XenForo_Application::getOptions(); |
187
|
|
|
/** @var string $rejectOld the maximum age of a message; default/fallback: 14 days */ |
188
|
|
|
$rejectOld = '-14 days'; |
189
|
|
|
if ($options->threema_gateway_verify_receive_time && $options->threema_gateway_verify_receive_time['enabled']) { |
190
|
|
|
$rejectOld = $options->threema_gateway_verify_receive_time['time']; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
// discard too old messages |
194
|
|
|
if ($this->filtered['date'] < strtotime($rejectOld, XenForo_Application::$time)) { |
195
|
|
|
$errorString = [null, 'Message cannot be processed: Message is too old (send at ' . date('Y-m-d H:i:s', $this->filtered['date']) . ', messages older than ' . $rejectOld . ' are rejected)', 'Message cannot be processed']; |
196
|
|
|
return false; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
// discard messages sent in the future |
200
|
|
|
// (to handle leap seconds or so we allow some seconds difference) |
201
|
|
|
if ($this->filtered['date'] > strtotime(self::ALLOW_FUTURE_MESSAGE_TIME, XenForo_Application::$time)) { |
202
|
|
|
$errorString = [null, 'Message cannot be processed: Message is send in the future (send at ' . date('Y-m-d H:i:s', $this->filtered['date']) . ', please check your server clock)', 'Message cannot be processed']; |
203
|
|
|
return false; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
return true; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* Receive the message, decrypt it and save it. |
211
|
|
|
* |
212
|
|
|
* @param string $downloadPath The directory where to store received files |
213
|
|
|
* @param bool $debugMode Whether debugging information should be returned |
214
|
|
|
* (default: false) |
215
|
|
|
* |
216
|
|
|
* @throws XenForo_Exception |
217
|
|
|
* @return string|array the message, which should be shown |
218
|
|
|
*/ |
219
|
|
|
public function processMessage($downloadPath, $debugMode = false) |
220
|
|
|
{ |
221
|
|
|
/** @var string $output */ |
222
|
|
|
$output = ''; |
223
|
|
|
|
224
|
|
|
if (!ThreemaGateway_Handler_Validation::checkDir($downloadPath)) { |
225
|
|
|
throw new XenForo_Exception('Download dir ' . $downloadPath . ' cannot be accessed.'); |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
try { |
229
|
|
|
/** @var Threema\MsgApi\Helpers\ReceiveMessageResult $receiveResult */ |
230
|
|
|
$receiveResult = $this->getE2EHelper()->receiveMessage( |
231
|
|
|
$this->filtered['from'], |
232
|
|
|
$this->filtered['messageId'], |
233
|
|
|
$this->getCryptTool()->hex2bin($this->filtered['box']), |
234
|
|
|
$this->getCryptTool()->hex2bin($this->filtered['nonce']), |
235
|
|
|
$downloadPath |
236
|
|
|
); |
237
|
|
|
} catch (Exception $e) { |
238
|
|
|
// as XenForo does not allow exception chaining, we better log the exception right now |
239
|
|
|
XenForo_Error::logException($e); |
240
|
|
|
throw new XenForo_Exception('Message cannot be processed: [' . get_class($e) . '] ' . $e->getMessage()); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
if (!$receiveResult->isSuccess()) { |
244
|
|
|
throw new XenForo_Exception('Message cannot be processed: [ResultErrors] ' . implode('|', $receiveResult->getErrors())); |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** @var Threema\MsgApi\Messages\ThreemaMessage $threemaMsg */ |
248
|
|
|
$threemaMsg = $receiveResult->getThreemaMessage(); |
249
|
|
|
|
250
|
|
|
// create detailed log when debug mode is enabled |
251
|
|
|
if ($debugMode) { |
252
|
|
|
$output = $this->getLogData($receiveResult, $threemaMsg); |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** @var bool $saveMessage whether to save the message to DB. */ |
256
|
|
|
$saveMessage = true; |
257
|
|
|
|
258
|
|
|
XenForo_CodeEvent::fire('threemagw_message_callback_presave', [ |
259
|
|
|
$this, |
260
|
|
|
$receiveResult, |
261
|
|
|
$threemaMsg, |
262
|
|
|
&$output, |
263
|
|
|
&$saveMessage, |
264
|
|
|
$debugMode |
265
|
|
|
], $threemaMsg->getTypeCode()); |
266
|
|
|
|
267
|
|
|
// save message in database |
268
|
|
|
try { |
269
|
|
|
if ($saveMessage) { |
270
|
|
|
$this->saveMessage($receiveResult, $threemaMsg); |
271
|
|
|
} else { |
272
|
|
|
$this->saveMessageId($receiveResult->getMessageId()); |
273
|
|
|
} |
274
|
|
|
} catch (Exception $e) { |
275
|
|
|
// as XenForo does not allow Exception chaining, we better log the exception right now |
276
|
|
|
XenForo_Error::logException($e); |
277
|
|
|
throw new XenForo_Exception('Message could not be saved: [' . get_class($e) . '] ' . $e->getMessage()); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
XenForo_CodeEvent::fire('threemagw_message_callback_postsave', [ |
281
|
|
|
$this, |
282
|
|
|
$receiveResult, |
283
|
|
|
$threemaMsg, |
284
|
|
|
&$output, |
285
|
|
|
$saveMessage, |
286
|
|
|
$debugMode |
287
|
|
|
], $threemaMsg->getTypeCode()); |
288
|
|
|
|
289
|
|
|
return $output; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Adds a string to the current log string or array. |
294
|
|
|
* |
295
|
|
|
* @param array|string $log string or array |
296
|
|
|
* @param string $stringToAdd |
297
|
|
|
* @param string $stringToAddDetail |
298
|
|
|
*/ |
299
|
|
|
public function addLog(&$log, $stringToAdd, $stringToAddDetail = null) |
300
|
|
|
{ |
301
|
|
|
// convert to array if necessary or just add string |
302
|
|
|
if (is_string($log)) { |
303
|
|
|
if ($stringToAddDetail) { |
|
|
|
|
304
|
|
|
$log[1] = $log; |
305
|
|
|
$log[2] = $log; |
306
|
|
|
} else { |
307
|
|
|
$log .= PHP_EOL . $stringToAdd; |
308
|
|
|
return; |
309
|
|
|
} |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
// add to array |
313
|
|
|
if ($stringToAddDetail) { |
|
|
|
|
314
|
|
|
$log[1] .= PHP_EOL . $stringToAddDetail; |
315
|
|
|
} elseif ($stringToAdd) { |
316
|
|
|
$log[1] .= PHP_EOL . $stringToAdd; |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
if ($stringToAdd) { |
320
|
|
|
$log[2] .= PHP_EOL . $stringToAdd; |
321
|
|
|
} |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
/** |
325
|
|
|
* Checks whether a message is already saved. If so this may indicate a |
326
|
|
|
* replay attack. |
327
|
|
|
* |
328
|
|
|
* @param string $messageId |
329
|
|
|
* |
330
|
|
|
* @throws XenForo_Exception |
331
|
|
|
*/ |
332
|
|
|
public function assertNoReplayAttack($messageId) |
333
|
|
|
{ |
334
|
|
|
// do not check multiple times |
335
|
|
|
if ($this->messageReplayChecked) { |
336
|
|
|
return; |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
// skip all internal handling of receiver as it does a simple yes/no check only |
340
|
|
|
// also skip permissions as currently no user is logged in |
341
|
|
|
/** @var ThreemaGateway_Handler_Action_Receiver $receiver */ |
342
|
|
|
$receiver = new ThreemaGateway_Handler_Action_Receiver(true, true); |
343
|
|
|
|
344
|
|
|
// check whether message has already been saved to prevent replay attacks |
345
|
|
|
if ($receiver->messageIsReceived($messageId)) { |
346
|
|
|
throw new XenForo_Exception('Message "' . $messageId . '" has already been received and is already saved. This may indicate a replay attack.'); |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
$this->messageReplayChecked = true; |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
/** |
353
|
|
|
* Get request data. |
354
|
|
|
* |
355
|
|
|
* If you obmit the $key parameter you get an array of all request parameters. |
356
|
|
|
* If not, you'll get one specific entry. |
357
|
|
|
* In case nothing could be found, this returns "null". |
358
|
|
|
* |
359
|
|
|
* @param string $key |
360
|
|
|
* @return string|array|null |
361
|
|
|
*/ |
362
|
|
|
public function getRequest($key = null) |
363
|
|
|
{ |
364
|
|
|
if ($key === null) { |
365
|
|
|
return $this->filtered; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
if (array_key_exists($key, $this->filtered)) { |
369
|
|
|
return $this->filtered[$key]; |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
return null; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* Returns the original input. |
377
|
|
|
* |
378
|
|
|
* It is strongly discouraghed to use this and better use {@see getRequest()} |
379
|
|
|
* as this data is already filtered. |
380
|
|
|
* In general you should have few real reasons to get this RAW data. |
381
|
|
|
* |
382
|
|
|
* @return XenForo_Input |
383
|
|
|
*/ |
384
|
|
|
public function getInput() |
385
|
|
|
{ |
386
|
|
|
return $this->input; |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
/** |
390
|
|
|
* Returns an array with a not so detailed[2] and a very detailed[1] log |
391
|
|
|
* of the received message. |
392
|
|
|
* |
393
|
|
|
* @param Threema\MsgApi\Helpers\ReceiveMessageResult $receiveResult Threema MsgApi receive result |
394
|
|
|
* @param Threema\MsgApi\Messages\ThreemaMessage $threemaMsg Threema MsgApi message |
395
|
|
|
* |
396
|
|
|
* @return array[null, string, string] |
|
|
|
|
397
|
|
|
*/ |
398
|
|
|
protected function getLogData( |
399
|
|
|
Threema\MsgApi\Helpers\ReceiveMessageResult $receiveResult, |
400
|
|
|
Threema\MsgApi\Messages\ThreemaMessage $threemaMsg |
401
|
|
|
) { |
402
|
|
|
$eol = PHP_EOL; |
403
|
|
|
|
404
|
|
|
// common heading |
405
|
|
|
$publicLog = 'New message from ' . $this->filtered['from'] . $eol . $eol; |
406
|
|
|
$publicLog .= 'ID: ' . $receiveResult->getMessageId() . $eol; |
407
|
|
|
$publicLog .= 'message.type: ' . $threemaMsg->getTypeCode() . ' (' . $threemaMsg . ')' . $eol; |
408
|
|
|
$publicLog .= 'message.date: ' . date('Y-m-d H:i:s', $this->filtered['date']) . $eol; |
409
|
|
|
$debugLog = $publicLog; |
410
|
|
|
$publicLog .= '[...]' . $eol; |
411
|
|
|
|
412
|
|
|
// secret part of heading |
413
|
|
|
$debugLog .= 'files: ' . implode('|', $receiveResult->getFiles()) . $eol; |
414
|
|
|
// NOTE: File type (key of array) is not logged here! |
415
|
|
|
|
416
|
|
|
// detailed result (is secret) |
417
|
|
|
if ($threemaMsg instanceof Threema\MsgApi\Messages\TextMessage) { |
418
|
|
|
$debugLog .= 'message.getText: ' . $threemaMsg->getText() . $eol; |
419
|
|
|
} |
420
|
|
|
if ($threemaMsg instanceof Threema\MsgApi\Messages\DeliveryReceipt) { |
421
|
|
|
$debugLog .= 'message.getReceiptType: ' . $threemaMsg->getReceiptType() . $eol; |
422
|
|
|
$debugLog .= 'message.getReceiptTypeName: ' . $threemaMsg->getReceiptTypeName() . $eol; |
423
|
|
|
$debugLog .= 'message.getAckedMessageIds: ' . implode('|', $this->bin2hexArray($threemaMsg->getAckedMessageIds())) . $eol; |
424
|
|
|
} |
425
|
|
|
if ($threemaMsg instanceof Threema\MsgApi\Messages\FileMessage) { |
426
|
|
|
$debugLog .= 'message.getBlobId: ' . $threemaMsg->getBlobId() . $eol; |
427
|
|
|
$debugLog .= 'message.getEncryptionKey: ' . $threemaMsg->getEncryptionKey() . $eol; |
428
|
|
|
$debugLog .= 'message.getFilename: ' . $threemaMsg->getFilename() . $eol; |
429
|
|
|
$debugLog .= 'message.getMimeType: ' . $threemaMsg->getMimeType() . $eol; |
430
|
|
|
$debugLog .= 'message.getSize: ' . $threemaMsg->getSize() . $eol; |
431
|
|
|
$debugLog .= 'message.getThumbnailBlobId: ' . $threemaMsg->getThumbnailBlobId() . $eol; |
432
|
|
|
} |
433
|
|
|
if ($threemaMsg instanceof Threema\MsgApi\Messages\ImageMessage) { |
434
|
|
|
$debugLog .= 'message.getBlobId: ' . $threemaMsg->getBlobId() . $eol; |
435
|
|
|
$debugLog .= 'message.getLength: ' . $threemaMsg->getLength() . $eol; |
436
|
|
|
$debugLog .= 'message.getNonce: ' . $this->getCryptTool()->bin2hex($threemaMsg->getNonce()) . $eol; |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
return [null, $debugLog, $publicLog]; |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
/** |
443
|
|
|
* Saves a decrypted message in the database. |
444
|
|
|
* |
445
|
|
|
* @param Threema\MsgApi\Helpers\ReceiveMessageResult $receiveResult Threema MsgApi receive result |
446
|
|
|
* @param Threema\MsgApi\Messages\ThreemaMessage $threemaMsg Threema MsgApi message |
447
|
|
|
* |
448
|
|
|
* @throws XenForo_Exception |
449
|
|
|
*/ |
450
|
|
|
protected function saveMessage( |
451
|
|
|
Threema\MsgApi\Helpers\ReceiveMessageResult $receiveResult, |
452
|
|
|
Threema\MsgApi\Messages\ThreemaMessage $threemaMsg |
453
|
|
|
) { |
454
|
|
|
$dataWriter = XenForo_DataWriter::create('ThreemaGateway_DataWriter_Messages'); |
455
|
|
|
|
456
|
|
|
$dataWriter->set('message_id', $receiveResult->getMessageId()); // this is set for all tables |
457
|
|
|
$dataWriter->set('message_type_code', $threemaMsg->getTypeCode(), ThreemaGateway_Model_Messages::DB_TABLE_MESSAGES); |
458
|
|
|
$dataWriter->set('sender_threema_id', $this->filtered['from'], ThreemaGateway_Model_Messages::DB_TABLE_MESSAGES); |
459
|
|
|
$dataWriter->set('date_send', $this->filtered['date'], ThreemaGateway_Model_Messages::DB_TABLE_MESSAGES); |
460
|
|
|
// $dataWriter->set('date_received', XenForo_Application::$time); //= default |
|
|
|
|
461
|
|
|
|
462
|
|
|
// files |
463
|
|
|
if (count($receiveResult->getFiles()) >= 1) { |
464
|
|
|
/** @var array $fileList the files associated to the message */ |
465
|
|
|
$fileList = $receiveResult->getFiles(); |
466
|
|
|
// set current (first) type/path |
467
|
|
|
$dataWriter->set('file_type', key($fileList), ThreemaGateway_Model_Messages::DB_TABLE_FILES); |
468
|
|
|
$dataWriter->set('file_path', $dataWriter->normalizeFilePath(current($fileList)), ThreemaGateway_Model_Messages::DB_TABLE_FILES); |
469
|
|
|
// remove current value from array |
470
|
|
|
unset($fileList[key($fileList)]); |
471
|
|
|
// pass as extra data for later saving |
472
|
|
|
$dataWriter->setExtraData(ThreemaGateway_DataWriter_Messages::DATA_FILES, $fileList); |
473
|
|
|
} |
474
|
|
|
|
475
|
|
|
// set values for each message type |
476
|
|
|
if ($threemaMsg instanceof Threema\MsgApi\Messages\TextMessage) { |
477
|
|
|
$dataWriter->set('text', $threemaMsg->getText(), ThreemaGateway_Model_Messages::DB_TABLE_MESSAGES . '_text'); |
478
|
|
|
} elseif ($threemaMsg instanceof Threema\MsgApi\Messages\DeliveryReceipt) { |
479
|
|
|
$dataWriter->set('receipt_type', $threemaMsg->getReceiptType(), ThreemaGateway_Model_Messages::DB_TABLE_MESSAGES . '_delivery_receipt'); |
480
|
|
|
|
481
|
|
|
/** @var array $ackedMsgIds the acknowledged message IDs */ |
482
|
|
|
$ackedMsgIds = $this->bin2hexArray($threemaMsg->getAckedMessageIds()); |
483
|
|
|
if (count($ackedMsgIds) >= 1) { |
484
|
|
|
// set current (first) type/path |
485
|
|
|
$dataWriter->set('ack_message_id', $ackedMsgIds[0], ThreemaGateway_Model_Messages::DB_TABLE_DELIVERY_RECEIPT); |
486
|
|
|
// remove current value from array |
487
|
|
|
unset($ackedMsgIds[0]); |
488
|
|
|
// pass as extra data for later saving |
489
|
|
|
$dataWriter->setExtraData(ThreemaGateway_DataWriter_Messages::DATA_ACKED_MSG_IDS, $ackedMsgIds); |
490
|
|
|
} |
491
|
|
|
} elseif ($threemaMsg instanceof Threema\MsgApi\Messages\FileMessage) { |
492
|
|
|
$dataWriter->set('file_size', $threemaMsg->getSize(), ThreemaGateway_Model_Messages::DB_TABLE_MESSAGES . '_file'); |
493
|
|
|
$dataWriter->set('file_name', $threemaMsg->getFilename(), ThreemaGateway_Model_Messages::DB_TABLE_MESSAGES . '_file'); |
494
|
|
|
$dataWriter->set('mime_type', $threemaMsg->getMimeType(), ThreemaGateway_Model_Messages::DB_TABLE_MESSAGES . '_file'); |
495
|
|
|
} elseif ($threemaMsg instanceof Threema\MsgApi\Messages\ImageMessage) { |
496
|
|
|
$dataWriter->set('file_size', $threemaMsg->getLength(), ThreemaGateway_Model_Messages::DB_TABLE_MESSAGES . '_image'); |
497
|
|
|
} |
498
|
|
|
|
499
|
|
|
return $dataWriter->save(); |
500
|
|
|
} |
501
|
|
|
|
502
|
|
|
/** |
503
|
|
|
* Only saves a message ID to the database to prevent replay attacks. |
504
|
|
|
* |
505
|
|
|
* @param string $messageId |
506
|
|
|
* |
507
|
|
|
* @throws XenForo_Exception |
508
|
|
|
*/ |
509
|
|
|
protected function saveMessageId($messageId) |
510
|
|
|
{ |
511
|
|
|
$dataWriter = XenForo_DataWriter::create('ThreemaGateway_DataWriter_Messages'); |
512
|
|
|
|
513
|
|
|
$dataWriter->set('message_id', $messageId, ThreemaGateway_Model_Messages::DB_TABLE_MESSAGES); |
514
|
|
|
$dataWriter->roundReceiveDate(); // reduce amount of meta data stored |
515
|
|
|
|
516
|
|
|
return $dataWriter->save(); |
517
|
|
|
} |
518
|
|
|
|
519
|
|
|
/** |
520
|
|
|
* Converts binary data in an array to hex. |
521
|
|
|
* |
522
|
|
|
* @param array $bin binary array |
523
|
|
|
* |
524
|
|
|
* @return array |
525
|
|
|
*/ |
526
|
|
|
protected function bin2hexArray($bin) |
527
|
|
|
{ |
528
|
|
|
$output = []; |
529
|
|
|
foreach ($bin as $item) { |
530
|
|
|
$output[] = $this->getCryptTool()->bin2hex($item); |
531
|
|
|
} |
532
|
|
|
return $output; |
533
|
|
|
} |
534
|
|
|
} |
535
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.