Issues (15)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Gateway.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Class Gateway
4
 *
5
 * @filesource   Gateway.php
6
 * @created      10.06.2017
7
 * @package      chillerlan\Threema
8
 * @author       Smiley <[email protected]>
9
 * @copyright    2017 Smiley
10
 * @license      MIT
11
 */
12
13
namespace chillerlan\Threema;
14
15
use chillerlan\Threema\Crypto\CryptoInterface;
16
use chillerlan\Threema\HTTP\HTTPClientInterface;
17
18
class Gateway{
19
20
	const API_BASE = 'https://msgapi.threema.ch';
21
22
	const API_ERRORS = [
23
		400 => 'bad request',
24
		401 => 'unauthorized',
25
		402 => 'no credits remain',
26
		404 => 'not found',
27
		413 => 'message too large',
28
		500 => 'internal server error',
29
	];
30
31
	const HMAC_KEY_EMAIL_BIN = "\x30\xa5\x50\x0f\xed\x97\x01\xfa\x6d\xef\xdb\x61\x08\x41\x90\x0f\xeb\xb8\xe4\x30\x88\x1f\x7a\xd8\x16\x82\x62\x64\xec\x09\xba\xd7";
32
	const HMAC_KEY_PHONE_BIN = "\x85\xad\xf8\x22\x69\x53\xf3\xd9\x6c\xfd\x5d\x09\xbf\x29\x55\x5e\xb9\x55\xfc\xd8\xaa\x5e\xc4\xf9\xfc\xd8\x69\xe2\x58\x37\x07\x23";
33
34
	const FILE_NONCE           = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01";
35
	const FILE_THUMBNAIL_NONCE = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02";
36
37
	/**
38
	 * @var \chillerlan\Threema\HTTP\HTTPClientInterface
39
	 */
40
	protected $HTTP;
41
42
	/**
43
	 * @var \chillerlan\Threema\Crypto\CryptoInterface
44
	 */
45
	protected $crypto;
46
47
	/**
48
	 * @var string
49
	 */
50
	protected $gatewayID;
51
52
	/**
53
	 * @var string
54
	 */
55
	protected $gatewaySecret;
56
57
	/**
58
	 * Gateway constructor.
59
	 *
60
	 * @param \chillerlan\Threema\HTTP\HTTPClientInterface $HTTP
61
	 * @param \chillerlan\Threema\Crypto\CryptoInterface   $crypto
62
	 * @param string                                       $gatewayID
63
	 * @param string                                       $gatewaySecret
64
	 */
65
	public function __construct(HTTPClientInterface $HTTP, CryptoInterface $crypto, string $gatewayID, string $gatewaySecret){
66
		$this->HTTP          = $HTTP;
67
		$this->crypto        = $crypto;
68
		$this->gatewayID     = $gatewayID;
69
		$this->gatewaySecret = $gatewaySecret;
70
	}
71
72
	/**
73
	 * @return array
74
	 */
75
	protected function getAuthParams():array {
76
		return [
77
			'from'   => $this->gatewayID,
78
			'secret' => $this->gatewaySecret,
79
		];
80
	}
81
82
	/**
83
	 * @param       $endpoint
84
	 * @param array $params
85
	 * @param null  $body
86
	 * @param array $headers
87
	 *
88
	 * @return mixed
0 ignored issues
show
Consider making the return type a bit more specific; maybe use string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
89
	 * @throws \chillerlan\Threema\EndpointException
90
	 */
91
	protected function getResponse($endpoint, $params = [], $body = null, $headers = []){
92
		$response = $this->HTTP->getResponse(self::API_BASE.$endpoint, $params, !is_null($body) ? 'POST' : 'GET', $body, $headers);
93
94
		if($response->code !== 200){
95
96
			if(array_key_exists($response->code, self::API_ERRORS)){
97
				throw new EndpointException('gateway error: '.self::API_ERRORS[$response->code].' '.print_r($response->body, true));
98
			}
99
100
			throw new EndpointException('unknown error: "compiles on my machine." '.print_r([$response->code, $response->body], true));
101
		}
102
103
		return $response->body;
104
	}
105
106
	/**
107
	 * Get remaining credits
108
	 *
109
	 * URL: https://msgapi.threema.ch/credits?from=<gatewayID>&secret=<gatewaySecret>
110
	 *
111
	 * The API identity and secret must be passed in the corresponding GET parameters for authentication (use URL
112
	 * encoding). The number of credits left on the account that the given ID belongs to will be returned as a
113
	 * text/plain response. Note: several IDs may use the same account, and thus share the same credit balance.
114
	 *
115
	 * Possible HTTP result codes:
116
	 *
117
	 * - 200 (on success)
118
	 * - 401 (if API identity or secret are incorrect)
119
	 * - 500 (if a temporary internal server error occurs)
120
	 *
121
	 * @return int
122
	 */
123
	public function checkCredits():int{
124
		return (int)$this->getResponse('/credits', $this->getAuthParams());
125
	}
126
127
	/**
128
	 *Check file reception capability of an ID
129
	 *
130
	 * Before you send a file to a Threema ID using the blob upload (+ file message), you may want to check whether the
131
	 * recipient uses a Threema version that supports receiving files. The receiver may be using an old version, or a
132
	 * platform where file reception is not supported.
133
	 *
134
	 * URL: https://msgapi.threema.ch/capabilities/<threemaID>?from=<gatewayID>&secret=<gatewaySecret>
135
	 *
136
	 * The API identity and secret must be passed in the corresponding GET parameters for authentication (use URL
137
	 * encoding). The result is a text/plain response of supported capabilities, separated by commas. Currently defined
138
	 * capabilities:
139
	 *
140
	 * - text
141
	 * - image
142
	 * - video
143
	 * - audio
144
	 * - file
145
	 *
146
	 * More capabilities may be added in the future (separated with commas), so you should match on substrings when
147
	 * checking for file. The order in which the capabilities are returned is not defined.
148
	 *
149
	 * Example result: text,image,video,audio,file
150
	 *
151
	 * Possible HTTP result codes:
152
	 *
153
	 * - 200 (on success)
154
	 * - 401 (if API identity or secret are incorrect)
155
	 * - 404 (if no matching ID could be found)
156
	 * - 500 (if a temporary internal server error occurs)
157
	 *
158
	 * @param string $threemaID
159
	 *
160
	 * @return array
161
	 */
162
	public function checkCapabilities(string $threemaID):array{
163
		$response = explode(',', $this->getResponse('/capabilities/'.$this->checkThreemaID($threemaID), $this->getAuthParams()));
164
165
		sort($response);
166
167
		return $response;
168
	}
169
170
	/**
171
	 * Find ID by phone number
172
	 *
173
	 * URL: https://msgapi.threema.ch/lookup/phone/<phoneno>?from=<gatewayID>&secret=<gatewaySecret>
174
	 *
175
	 * The phone number must be passed in E.164 format, without the leading +. The API identity and secret must be
176
	 * passed in the corresponding GET parameters for authentication (use URL encoding).
177
	 * The Threema ID corresponding to the phone number will be returned as a text/plain response.
178
	 *
179
	 * Possible HTTP result codes:
180
	 *
181
	 * - 200 (on success)
182
	 * - 401 (if API identity or secret are incorrect)
183
	 * - 404 (if no matching ID could be found)
184
	 * - 500 (if a temporary internal server error occurs)
185
	 *
186
	 * @param string $phoneno
187
	 *
188
	 * @return string
189
	 */
190
	public function getIdByPhone(string $phoneno):string{
191
		return $this->getResponse('/lookup/phone/'.$this->checkPhoneNo($phoneno), $this->getAuthParams());
192
	}
193
194
	/**
195
	 * URL: https://msgapi.threema.ch/lookup/phone_hash/<phonenoHash>?from=<gatewayID>&secret=<gatewaySecret>
196
	 *
197
	 * The phone number must be passed as an HMAC-SHA256 hash of the E.164 number without the leading +.
198
	 * The HMAC key is 85adf8226953f3d96cfd5d09bf29555eb955fcd8aa5ec4f9fcd869e258370723 (in hexadecimal).
199
	 *
200
	 * Example: the phone number 41791234567 hashes to
201
	 * ad398f4d7ebe63c6550a486cc6e07f9baa09bd9d8b3d8cb9d9be106d35a7fdbc.
202
	 *
203
	 * The API identity and secret must be passed in the corresponding GET parameters for authentication (use URL
204
	 * encoding). The Threema ID corresponding to the phone number will be returned as a text/plain response.
205
	 *
206
	 * Possible HTTP result codes:
207
	 *
208
	 * - 200 (on success)
209
	 * - 400 (if the hash length is wrong)
210
	 * - 401 (if API identity or secret are incorrect)
211
	 * - 404 (if no matching ID could be found)
212
	 * - 500 (if a temporary internal server error occurs)
213
	 *
214
	 * @param string $phonenoHash
215
	 *
216
	 * @return string
217
	 */
218
	public function getIdByPhoneHash(string $phonenoHash):string{
219
		return $this->getResponse('/lookup/phone_hash/'.$this->checkHash($phonenoHash), $this->getAuthParams());
220
	}
221
222
	/**
223
	 * URL: https://msgapi.threema.ch/lookup/email/<email>?from=<gatewayID>&secret=<gatewaySecret>
224
	 *
225
	 * The API identity and secret must be passed in the corresponding GET parameters for authentication (use URL
226
	 * encoding). The Threema ID corresponding to the email address will be returned as a text/plain response.
227
	 *
228
	 * Possible HTTP result codes:
229
	 *
230
	 * - 200 (on success)
231
	 * - 401 (if API identity or secret are incorrect)
232
	 * - 404 (if no matching ID could be found)
233
	 * - 500 (if a temporary internal server error occurs)
234
	 *
235
	 *
236
	 * @param string $email
237
	 *
238
	 * @return string
239
	 */
240
	public function getIdByEmail(string $email):string{
241
		return $this->getResponse('/lookup/email/'.$this->checkEmail($email), $this->getAuthParams());
242
	}
243
244
	/**
245
	 * Find ID by email address hash
246
	 *
247
	 * URL: https://msgapi.threema.ch/lookup/email_hash/<emailHash>?from=<gatewayID>&secret=<gatewaySecret>
248
	 *
249
	 * The lowercased and whitespace-trimmed email address must be hashed with HMAC-SHA256.
250
	 * The HMAC key is 30a5500fed9701fa6defdb610841900febb8e430881f7ad816826264ec09bad7 (in hexadecimal).
251
	 *
252
	 * Example: the email address [email protected] hashes to
253
	 * 1ea093239cc5f0e1b6ec81b866265b921f26dc4033025410063309f4d1a8ee2c.
254
	 *
255
	 * The API identity and secret must be passed in the corresponding GET parameters for authentication (use URL
256
	 * encoding). The Threema ID corresponding to the email address will be returned as a text/plain response.
257
	 *
258
	 * Possible HTTP result codes:
259
	 *
260
	 * - 200 (on success)
261
	 * - 400 (if the hash length is wrong)
262
	 * - 401 (if API identity or secret are incorrect)
263
	 * - 404 (if no matching ID could be found)
264
	 * - 500 (if a temporary internal server error occurs)
265
	 *
266
	 * @param string $emailHash
267
	 *
268
	 * @return string
269
	 */
270
	public function getIdByEmailHash(string $emailHash):string{
271
		return $this->getResponse('/lookup/email_hash/'.$this->checkHash($emailHash), $this->getAuthParams());
272
	}
273
274
	/**
275
	 * @param array $emails
276
	 * @param array $phonenumbers
277
	 *
278
	 * @return mixed
279
	 */
280
/*
281
	public function bulkLookup($emails = [], $phonenumbers = []){
282
		$lookup = [];
283
284
		foreach($phonenumbers as $phonenumber){
285
			$lookup['phoneHashes'][] = $this->hashPhoneNo($phonenumber);
286
		}
287
288
		foreach($emails as $email){
289
			$lookup['emailHashes'][] = $this->hashEmail($email);
290
		}
291
292
		$r = $this->getResponse('/lookup/bulk', $this->getAuthParams(), json_encode($lookup));
293
294
		return json_decode($r);
295
	}
296
*/
297
298
	/**
299
	 * Key lookups
300
	 *
301
	 * For the end-to-end encrypted mode, you need the public key of the recipient in order to encrypt a message. While
302
	 * it's best to obtain this directly from the recipient (extract it from the QR code), this may not be convenient,
303
	 * and therefore you can also look up the key associated with a given ID from the server.
304
	 *
305
	 * URL: https://msgapi.threema.ch/pubkeys/<threemaID>?from=<gatewayID>&secret=<gatewaySecret>
306
	 *
307
	 * The API identity and secret must be passed in the corresponding GET parameters for authentication (use URL
308
	 * encoding). The public key corresponding to the ID will be returned as a text/plain response (hex encoded).
309
	 *
310
	 * Possible HTTP result codes:
311
	 *
312
	 * - 200 (on success)
313
	 * - 401 (if API identity or secret are incorrect)
314
	 * - 404 (if no matching ID could be found)
315
	 * - 500 (if a temporary internal server error occurs)
316
	 *
317
	 * It is strongly recommended that you cache the public keys to avoid querying the API for each message.
318
	 *
319
	 * @param string $threemaID
320
	 *
321
	 * @return string
322
	 * @throws \chillerlan\Threema\EndpointException
323
	 */
324
	public function getPublicKey(string $threemaID):string{
325
		$threemaID = $this->checkThreemaID($threemaID);
326
327
		if(!$threemaID){
328
			throw new EndpointException('invalid threema id');
329
		}
330
331
		$response = $this->getResponse('/pubkeys/'.$threemaID, $this->getAuthParams());
332
333
		if(!$response || !$this->checkHash($response)){
334
			throw new EndpointException('invalid public key');
335
		}
336
337
		return $response;
338
	}
339
340
	/**
341
	 * Send Messages, Basic mode
342
	 *
343
	 * URL: https://msgapi.threema.ch/send_simple
344
	 *
345
	 * POST parameters (application/x-www-form-urlencoded):
346
	 *
347
	 * - recipient:   choose one of the following:
348
	 *   - to           recipient identity (8 characters)
349
	 *   - phone        recipient phone number (E.164), without leading +
350
	 *   - email        recipient email address
351
	 * - text         message text, max. 3500 bytes, UTF-8 encoded
352
	 *
353
	 * - from         your API identity (8 characters, usually starts with '*')
354
	 * - secret       API authentication secret
355
	 *
356
	 * By using the phone or email recipient specifiers, one can avoid having to look up the corresponding ID
357
	 * and instead do everything in one call (may be more suitable for SMS gateway style integration).
358
	 *
359
	 * Possible HTTP result codes:
360
	 *
361
	 * - 200 (on success)
362
	 * - 400 (if the recipient identity is invalid or the account is not set up for basic mode)
363
	 * - 401 (if API identity or secret are incorrect)
364
	 * - 402 (if no credits remain)
365
	 * - 404 (if using phone or email as the recipient specifier, and the corresponding recipient could not be found)
366
	 * - 413 (if the message is too long)
367
	 * - 500 (if a temporary internal server error occurs)
368
	 *
369
	 * On success (HTTP 200), the ID of the new message is returned as text/plain.
370
	 *
371
	 * @param string $to
372
	 * @param string $message
373
	 *
374
	 * @return string
375
	 * @throws \chillerlan\Threema\EndpointException
376
	 *
377
	 */
378
/*
379
	public function sendSimple(string $to, string $message):string{
380
		$params = array_merge($this->getRecipient($to), ['text' => $message], $this->getAuthParams());
381
382
		ksort($params);
383
384
		return $this->getResponse('/send_simple', [], $params);
385
	}
386
*/
387
388
389
	/**
390
	 * @param string $to
391
	 *
392
	 * @return array
393
	 * @throws \chillerlan\Threema\EndpointException
394
	 */
395
	protected function getRecipient(string $to):array {
396
397
		switch(true){
398
			case $x = $this->checkEmail($to):
399
				return ['email' => $x];
400
			case $x = $this->checkThreemaID($to):
401
				return ['to' => $x];
402
			case $x = $this->checkPhoneNo($to):
403
				return ['phone' => $x];
404
			default:
405
				throw new EndpointException('"to" not specified: '.$to);
406
		}
407
408
	}
409
410
	/**
411
	 * End-to-end encrypted mode
412
	 *
413
	 * URL: https://msgapi.threema.ch/send_e2e
414
	 *
415
	 * POST parameters (application/x-www-form-urlencoded):
416
	 *
417
	 * - to      recipient identity (8 characters)
418
	 * - box     encrypted message data (max. 4000 bytes, hex encoded)
419
	 * - nonce   nonce used for encryption (24 bytes, hex encoded)
420
	 *
421
	 * - from    your API identity (8 characters, usually starts with '*')
422
	 * - secret  API authentication secret
423
	 *
424
	 * Possible HTTP result codes:
425
	 *
426
	 * - 200 (on success)
427
	 * - 400 (if the recipient identity is invalid or the account is not set up for end-to-end mode)
428
	 * - 401 (if API identity or secret are incorrect)
429
	 * - 402 (if no credits remain)
430
	 * - 413 (if the message is too long)
431
	 * - 500 (if a temporary internal server error occurs)
432
	 *
433
	 * On success (HTTP 200), the ID of the new message is returned as text/plain.
434
	 *
435
	 * @param string      $recipientThreemaID
436
	 * @param string      $senderPrivateKey
437
	 *
438
	 * @param string      $data
439
	 *
440
	 *
441
	 * @return string
442
	 * @throws \chillerlan\Threema\EndpointException
443
	 */
444
	protected function sendE2E(string $recipientThreemaID, string $senderPrivateKey, string $data):string{
445
		$recipientThreemaID = $this->checkThreemaID($recipientThreemaID);
446
447
		if(!$recipientThreemaID){
448
			throw new EndpointException('no threema id given');
449
		}
450
451
		$recipientPubKey = $this->getPublicKey($recipientThreemaID);
452
453
		$params = array_merge(
454
			$this->getAuthParams(),
455
			['to' => $recipientThreemaID],
456
			$this->crypto->createBox($data.$this->crypto->getPadBytes(), $senderPrivateKey, $recipientPubKey)
457
		);
458
459
		ksort($params);
460
461
		return $this->getResponse('/send_e2e', [], $params);
462
	}
463
464
	/**
465
	 * @param string $recipientThreemaID
466
	 * @param string $senderPrivateKey
467
	 * @param string $text
468
	 *
469
	 * @return string
470
	 */
471
	public function sendE2EText(string $recipientThreemaID, string $senderPrivateKey, string $text):string{
472
		return $this->sendE2E($recipientThreemaID, $senderPrivateKey, "\x01".$text);
473
	}
474
475
	/**
476
	 * @param string      $recipientThreemaID
477
	 * @param string      $senderPrivateKey
478
	 * @param string      $file
479
	 * @param string      $description
480
	 * @param string|null $thumbnail
481
	 *
482
	 * @return string
483
	 * @throws \chillerlan\Threema\EndpointException
484
	 */
485
	public function sendE2EFile(string $recipientThreemaID, string $senderPrivateKey, string $file, string $description = '', string $thumbnail = null):string{
486
487
		if(!in_array('file', $this->checkCapabilities($recipientThreemaID))){
488
			throw new EndpointException('given threema id is not capable of receiving files');
489
		}
490
491
		$fileinfo = $this->checkFile($file);
492
493
		if(!is_array($fileinfo)){
494
			throw new EndpointException('invalid file');
495
		}
496
497
		$key = $this->crypto->getRandomBytes(32);
498
499
		$content = [
500
			'b' => $this->upload($this->crypto->createSecretBox(file_get_contents($file), self::FILE_NONCE, $key)),
501
			'k' => $this->crypto->bin2hex($key),
502
			'm' => $fileinfo['mime'],
503
			'n' => $fileinfo['name'],
504
			's' => $fileinfo['size'],
505
			'i' => 0,
506
		];
507
508
		if(!empty($description)){
509
			$content['d'] = $description;
510
		}
511
512
		if($thumbnail){
513
			// @todo: autocreate thumbnail
514
			$thumbinfo = $this->checkFile($thumbnail);
515
516
			if(is_array($thumbinfo)){
517
				$content['t'] = $this->upload($this->crypto->createSecretBox(file_get_contents($thumbnail), self::FILE_THUMBNAIL_NONCE, $key));
518
			}
519
520
		}
521
522
		return $this->sendE2E($recipientThreemaID, $senderPrivateKey, "\x17".json_encode($content));
523
	}
524
525
	/**
526
	 * @param string $recipientThreemaID
527
	 * @param string $senderPrivateKey
528
	 * @param string $image
529
	 *
530
	 * @return mixed
531
	 * @throws \chillerlan\Threema\EndpointException
532
	 */
533
/*
534
	public function sendE2EImage(string $recipientThreemaID, string $senderPrivateKey, string $image){
535
536
		if(!in_array('image', $this->checkCapabilities($recipientThreemaID))){
537
			throw new EndpointException('given threema id is not capable of receiving image');
538
		}
539
540
		$fileinfo = $this->checkFile($image);
541
542
		if(!is_array($fileinfo) || !in_array($fileinfo['mime'], ['image/jpg', 'image/jpeg', 'image/png'])){
543
			throw new EndpointException('invalid image');
544
		}
545
546
		$recipientPubKey = $this->getPublicKey($recipientThreemaID);
547
548
		$box = $this->crypto->createBox(file_get_contents($image), $senderPrivateKey, $recipientPubKey);
549
550
		$message = "\x02".$this->crypto->hex2bin($this->upload($box['box']));
551
		$message .= pack('V', $fileinfo['size']);
552
		$message .= $this->crypto->hex2bin($box['nonce']);
553
554
		$params = array_merge(
555
			$this->getAuthParams(),
556
			['to' => $recipientThreemaID],
557
			$this->crypto->createBox($message.$this->crypto->getPadBytes(), $senderPrivateKey, $recipientPubKey)
558
		);
559
560
		ksort($params);
561
562
		return $this->getResponse('/send_e2e', [], $params);
563
	}
564
*/
565
566
	/**
567
	 * Upload
568
	 *
569
	 * URL: https://msgapi.threema.ch/upload_blob
570
	 *
571
	 * POST parameters (multipart/form-data):
572
	 *
573
	 * - blob    blob data (binary), max. 20 MB
574
	 *
575
	 * GET parameters:
576
	 *
577
	 * - from    your API identity (8 characters, usually starts with '*')
578
	 * - secret  API authentication secret
579
	 *
580
	 * Please note that the authentication parameters must be passed in the request URL,
581
	 * while the actual blob data needs to be sent as a multipart/form-data parameter.
582
	 *
583
	 * Possible HTTP result codes:
584
	 *
585
	 * - 200 (on success)
586
	 * - 400 (if required parameters are missing or the blob is empty)
587
	 * - 401 (if API identity or secret are incorrect)
588
	 * - 402 (if no credits remain)
589
	 * - 413 (if the blob is too big)
590
	 * - 500 (if a temporary internal server error occurs)
591
	 *
592
	 * The ID of the new blob is returned as text/plain. One credit is deducted for the upload of a blob.
593
	 *
594
	 * @param string $blob
595
	 *
596
	 * @return string
597
	 */
598
	protected function upload(string $blob):string{
599
		return $this->getResponse('/upload_blob', $this->getAuthParams(), ['blob' => $blob], ['Content-type' => 'multipart/form-data']);
600
	}
601
602
	/**
603
	 * URL: https://msgapi.threema.ch/blobs/<blobID>
604
	 *
605
	 * GET parameters:
606
	 *
607
	 * - from your API identity (8 characters, usually starts with '*')
608
	 * - secret API authentication secret
609
	 *
610
	 * Possible HTTP result codes:
611
	 *
612
	 * 200 (on success, body is the blob data as application/octet-stream)
613
	 * 401 (if API identity or secret are incorrect)
614
	 * 404 (if no blob with this ID could be found)
615
	 * 500 (if a temporary internal server error occurs)
616
	 *
617
	 * Please note: after a blob download has first been attempted, the blob may be deleted from the server within an
618
	 * hour.
619
	 *
620
	 * @param string $blobID
621
	 *
622
	 * @return mixed
0 ignored issues
show
Consider making the return type a bit more specific; maybe use string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
623
	 */
624
	public function download(string $blobID){
625
		return $this->getResponse('/blobs/'.$blobID, $this->getAuthParams());
626
	}
627
628
	/**
629
	 * Hashes an email address for identity lookup.
630
	 *
631
	 * @param string $email the email address
632
	 *
633
	 * @return string the email hash (hex)
634
	 * @throws \chillerlan\Threema\EndpointException
635
	 */
636
	public function hashEmail($email){
637
		$email = $this->checkEmail($email);
638
639
		if(!$email){
640
			throw new EndpointException('invalid email');
641
		}
642
643
		return $this->crypto->hmac_hash($email, self::HMAC_KEY_EMAIL_BIN);
644
	}
645
646
	/**
647
	 * Hashes an phone number address for identity lookup.
648
	 *
649
	 * @param string $phoneNo the phone number (in E.164 format, no leading +)
650
	 *
651
	 * @return bool|string the phone number hash (hex), false on failure
652
	 * @throws \chillerlan\Threema\EndpointException
653
	 */
654
	public function hashPhoneNo($phoneNo){
655
		$phoneNo = $this->checkPhoneNo($phoneNo);
656
657
		if(!$phoneNo){
658
			throw new EndpointException('invalid phonenumber');
659
		}
660
661
		return $this->crypto->hmac_hash($phoneNo, self::HMAC_KEY_PHONE_BIN);
662
	}
663
664
	/**
665
	 * @param string $threemaID
666
	 *
667
	 * @return null|string
668
	 */
669
	protected function checkThreemaID(string $threemaID){
670
		$threemaID = trim($threemaID);
671
672
		if(!preg_match('/^[a-z\d\*]{8}$/i', $threemaID)){
673
			return null;
674
		}
675
676
		return strtoupper($threemaID);
677
	}
678
679
	/**
680
	 * @param string $phoneNo
681
	 *
682
	 * @return null|string
683
	 */
684
	protected function checkPhoneNo(string $phoneNo){
685
		$phoneNo = trim($phoneNo);
686
687
		if(!preg_match('/^[\d]+$/', $phoneNo)){
688
			return null;
689
		}
690
691
		return $phoneNo;
692
	}
693
694
	/**
695
	 * @param $email
696
	 *
697
	 * @return null|string
698
	 */
699
	protected function checkEmail($email){
700
		$email = filter_var(trim($email), FILTER_VALIDATE_EMAIL);
701
702
		if(empty($email)){
703
			return null;
704
		}
705
706
		return strtolower($email);
707
	}
708
709
	/**
710
	 * @param string $hash
711
	 *
712
	 * @return null|string
713
	 */
714
	protected function checkHash(string $hash){
715
		$hash = trim($hash);
716
717
		if(!preg_match('/^[a-f\d]{64}$/i', $hash)){
718
			return null;
719
		}
720
721
		return $hash;
722
	}
723
724
	/**
725
	 * @param string $path
726
	 *
727
	 * @return array|null
0 ignored issues
show
Consider making the return type a bit more specific; maybe use null|array<string,string|integer>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
728
	 */
729
	protected function checkFile(string $path){
730
731
		if(!file_exists($path) || !is_file($path)){
732
			return null;
733
		}
734
735
		$mime = 'application/octet-stream';
736
737
		if(class_exists('finfo')){
738
			$mime = (new \finfo(FILEINFO_MIME_TYPE ))->file($path);
739
		}
740
		else if(function_exists('mime_content_type')) {
741
			$mime = mime_content_type($path);
742
		}
743
744
		return ['path' => $path, 'name' => basename($path), 'size' => filesize($path), 'mime' => $mime];
745
	}
746
}
747