ThreemaGateway_Handler_Action_Callback   C
last analyzed

Complexity

Total Complexity 59

Size/Duplication

Total Lines 524
Duplicated Lines 2.1 %

Coupling/Cohesion

Components 2
Dependencies 4

Importance

Changes 0
Metric Value
wmc 59
lcom 2
cbo 4
dl 11
loc 524
rs 6.1904
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
B addLog() 0 24 6
A assertNoReplayAttack() 0 19 3
A getRequest() 0 12 3
A getInput() 0 4 1
B getLogData() 0 43 5
C saveMessage() 0 51 7
A saveMessageId() 0 9 1
A bin2hexArray() 0 8 2
A initCallbackHandling() 0 15 1
C validatePreConditions() 0 42 13
B validateRequest() 7 35 4
B validateFormalities() 4 31 6
C processMessage() 0 72 7

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ThreemaGateway_Handler_Action_Callback often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ThreemaGateway_Handler_Action_Callback, and based on these observations, apply Extract Interface, too.

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(
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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())) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stringToAddDetail of type string|null is loosely compared to true; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stringToAddDetail of type string|null is loosely compared to true; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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]
0 ignored issues
show
Documentation introduced by
The doc-type array[null, could not be parsed: Expected "]" at position 2, but found "null". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
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
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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