Completed
Push — master ( f7b062...534c81 )
by smiley
02:36
created

CLIRunner::getKeypair()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 7
rs 9.4285
cc 3
eloc 5
nc 4
nop 2
1
<?php
2
/**
3
 * Class CLIRunner
4
 *
5
 * @filesource   CLIRunner.php
6
 * @created      01.04.2016
7
 * @package      chillerlan\Threema
8
 * @author       Smiley <[email protected]>
9
 * @copyright    2016 Smiley
10
 * @license      MIT
11
 */
12
13
namespace chillerlan\Threema;
14
15
use chillerlan\Threema\Crypto\CryptoInterface;
16
use ReflectionClass;
17
use ReflectionMethod;
18
use stdClass;
19
20
/**
21
 *
22
 */
23
class CLIRunner implements CLIRunnerInterface{
24
25
	/**
26
	 * @var array
27
	 */
28
	const COMMANDS = [
29
		// local
30
		'keypair'    => 'getKeypair',
31
		'hash_email' => 'hashEmail',
32
		'hash_phone' => 'hashPhone',
33
		'encrypt'    => 'encryptFile',
34
		'decrypt'    => 'decryptFile',
35
		// network
36
		'credits'    => 'checkCredits',
37
		'check'      => 'checkCapabilities',
38
		'email2id'   => 'getIdByEmail',
39
		'phone2id'   => 'getIdByPhone',
40
		'id2pubkey'  => 'getPubkeyById',
41
		'send'       => 'sendMessage',
42
	];
43
44
	/**
45
	 * @var \chillerlan\Threema\Crypto\CryptoInterface
46
	 */
47
	protected $cryptoInterface;
48
49
	/**
50
	 * @var \chillerlan\Threema\GatewayOptions
51
	 */
52
	protected $gatewayOptions;
53
54
	/**
55
	 * @var \chillerlan\Threema\Gateway
56
	 */
57
	protected $threemaGateway;
58
59
	/**
60
	 * @var \ReflectionClass
61
	 */
62
	protected $reflection;
63
64
	/**
65
	 * @var array[\ReflectionMethod]
66
	 */
67
	private $CLIRunnerInterfaceMap;
68
69
	/**
70
	 * CLIRunner constructor.
71
	 *
72
	 * @param \chillerlan\Threema\Crypto\CryptoInterface $cryptoInterface
73
	 * @param \chillerlan\Threema\GatewayOptions         $gatewayOptions
74
	 */
75
	public function __construct(CryptoInterface $cryptoInterface, GatewayOptions $gatewayOptions){
76
		$this->cryptoInterface = $cryptoInterface;
77
		$this->gatewayOptions  = $gatewayOptions;
78
		$this->threemaGateway  = new Gateway($this->cryptoInterface, $gatewayOptions);
79
		$this->reflection      = new ReflectionClass(CLIRunnerInterface::class);
80
81
		foreach($this->reflection->getMethods() as $method){
82
			$this->CLIRunnerInterfaceMap[$method->name] = $method;
83
		}
84
	}
85
86
	/**
87
	 * @param array $arguments $_SERVER['argc']
88
	 *
89
	 * @return string
90
	 */
91
	public function run(array $arguments):string{
92
		array_shift($arguments); // shift the scriptname off the top
93
		$command = strtolower(array_shift($arguments));
94
95
		if(array_key_exists($command, self::COMMANDS) && array_key_exists(self::COMMANDS[$command], $this->CLIRunnerInterfaceMap)){
96
			try{
97
				$method = $this->CLIRunnerInterfaceMap[self::COMMANDS[$command]];
98
				$method = new ReflectionMethod($this, $method->name);
99
100
				// @todo: check method arguments
101
				return $this->log2cli($method->invokeArgs($this, $arguments));
102
			}
103
			catch(GatewayException $gatewayException){
104
				return $this->log2cli('ERROR: '.$gatewayException->getMessage());
105
			}
106
		}
107
108
		return $this->log2cli($this->help());
109
	}
110
111
	/**
112
	 * output a string, wrap at 100 chars
113
	 *
114
	 * @param string $string string to output
115
	 *
116
	 * @return string
117
	 */
118
	protected function log2cli(string $string):string{
119
		return PHP_EOL.wordwrap($string, 78, PHP_EOL).PHP_EOL;
120
	}
121
122
	/**
123
	 * @return string
124
	 * @codeCoverageIgnore
125
	 */
126
	protected function readStdIn(){
127
		$stdin = fopen('php://stdin', 'r');
128
		$lines = [];
129
130
		while($line = trim(fgets($stdin))){
131
132
			if(strlen($line) === 0 || $line === "\n"){
133
				continue;
134
			}
135
136
			$lines[] = $line;
137
		}
138
139
		return implode("\n", $lines);
140
	}
141
142
	/**
143
	 * @param array $params
144
	 *
145
	 * @return \stdClass
146
	 */
147
	protected function parseDocBlock(array $params):stdClass{
148
		$parsed             = new stdClass;
149
		$parsed->paramNames = [];
150
		$parsed->paramDoc   = [];
151
		$parsed->returnDoc  = '';
152
153
		if(empty($params)){
154
			return $parsed; // @codeCoverageIgnore
155
		}
156
157
		foreach($params as $p){
158
			$p = explode(' ', $p, 2);
159
			if(isset($p[1])){
160
				if($p[0] === 'param'){
161
					$p                    = (explode(' ', trim($p[1]), 3));
162
					$name                 = '<'.trim($p[1], ' $').'>';
163
					$parsed->paramNames[] = $name;
164
					$doc                  = isset($p[2]) ? $name.' '.trim($p[2]) : $name;
165
					$parsed->paramDoc[]   = $doc;
166
				}
167
				else if($p[0] === 'return'){
168
					$p = explode(' ', trim($p[1]), 2);
169
					$parsed->returnDoc .= isset($p[1]) ? trim($p[1]) : '';
170
				}
171
			}
172
		}
173
174
		$parsed->paramNames = implode(' ', $parsed->paramNames);
175
		$parsed->paramDoc   = implode(PHP_EOL, $parsed->paramDoc);
176
177
		return $parsed;
178
	}
179
180
	/**
181
	 * @return string
182
	 */
183
	public function help():string{
184
		// return info in case no command was found
185
		$help = 'Threema Gateway CLI tool.'.PHP_EOL;
186
		$help .= 'Crypto: '.$this->threemaGateway->cryptoVersion().PHP_EOL.PHP_EOL;
187
188
		foreach(self::COMMANDS as $command => $method){
189
			$comment = $this->reflection->getMethod($method)->getDocComment();
190
			$comment = str_replace(["\t", ' *'], '', substr($comment, 3, -2));
191
192
			$params  = explode('@', $comment);
193
			$comment = trim(array_shift($params));
194
			$parsed  = $this->parseDocBlock($params);
195
196
			$help .= PHP_EOL.'threema.php '.$command.' '.$parsed->paramNames.PHP_EOL;
197
			$help .= str_repeat('-', strlen($command) + 12).PHP_EOL;
198
			$help .= PHP_EOL.$comment.PHP_EOL;
199
			$help .= PHP_EOL.$parsed->paramDoc.PHP_EOL;
200
			$help .= PHP_EOL.'Returns: '.$parsed->returnDoc.PHP_EOL.PHP_EOL;
201
		}
202
203
		return $help;
204
	}
205
206
	/**
207
	 * @param string $path
208
	 * @param string $data
209
	 *
210
	 * @return string
211
	 */
212
	protected function writeFile(string $path, string $data):string{
213
		// @todo: check writable
214
		if(is_dir(dirname($path))){
215
			$bytes = file_put_contents($path, $data);
216
			return $bytes.' bytes written to: '.$path.PHP_EOL;
217
		}
218
		// or is not writable
219
		return $path.' does not exist'; // @codeCoverageIgnore
220
	}
221
222
	/**
223
	 * @inheritdoc
224
	 */
225
	public function getKeypair(string $privateKeyFile = null, string $publicKeyFile = null):string{
226
		$keypair  = $this->cryptoInterface->getKeypair();
227
		$message  = !empty($privateKeyFile) ? $this->writeFile($privateKeyFile, $keypair->privateKey) : '';
228
		$message .= !empty($publicKeyFile)  ? $this->writeFile($publicKeyFile, $keypair->publicKey)   : '';
229
230
		return $message.PHP_EOL.'private:'.$keypair->privateKey.PHP_EOL.'public:'.$keypair->publicKey;
231
	}
232
233
	/**
234
	 * @inheritdoc
235
	 */
236
	public function hashEmail(string $email):string{
237
		return $this->cryptoInterface->hmac_hash($email, GatewayInterface::HMAC_KEY_EMAIL_BIN);
238
	}
239
240
	/**
241
	 * @inheritdoc
242
	 */
243
	public function hashPhone(string $phoneNo):string{
244
		return $this->cryptoInterface->hmac_hash($phoneNo, GatewayInterface::HMAC_KEY_PHONE_BIN);
245
	}
246
247
	/**
248
	 * @inheritdoc
249
	 */
250
	public function checkCredits():string{
251
		return $this->threemaGateway->checkCredits();
252
	}
253
254
	/**
255
	 * @inheritdoc
256
	 */
257
	public function checkCapabilities(string $threemaID):string{
258
		return implode(',', $this->threemaGateway->checkCapabilities($threemaID));
259
	}
260
261
	/**
262
	 * @inheritdoc
263
	 */
264
	public function getIdByEmail(string $email):string{
265
		return $this->threemaGateway->getIdByEmailHash($this->hashEmail($email));
266
	}
267
268
	/**
269
	 * @inheritdoc
270
	 */
271
	public function getIdByPhone(string $phoneNo):string{
272
		return $this->threemaGateway->getIdByPhoneHash($this->hashPhone($phoneNo));
273
	}
274
275
	/**
276
	 * @inheritdoc
277
	 */
278
	public function getPubkeyById(string $threemaID):string{
279
		return $this->threemaGateway->getPublicKey($threemaID);
280
	}
281
282
	/**
283
	 * @inheritdoc
284
	 */
285
	public function encryptFile(string $privateKey, string $publicKey, string $plaintextFile, string $encryptedFile):string{
286
		$encrypted = $this->cryptoInterface->encrypt(file_get_contents($plaintextFile), $privateKey, $publicKey);
287
		return $this->writeFile($encryptedFile, $encrypted->nonce."\n".$encrypted->box);
288
	}
289
290
	/**
291
	 * @inheritdoc
292
	 */
293
	public function decryptFile(string $privateKey, string $publicKey, string $encryptedFile, string $decryptedFile):string{
294
		$data = explode("\n", file_get_contents($encryptedFile));
295
		return $this->writeFile($decryptedFile, $this->cryptoInterface->decrypt(trim($data[1]), trim($data[0]), $privateKey, $publicKey));
296
	}
297
298
	/**
299
	 * @inheritdoc
300
	 */
301
	public function sendMessage(string $toThreemaID, string $message):string{
302
		// TODO: Implement sendMessage() method.
303
	}
304
}
305