Mercurial > hg > rc1
comparison vendor/pear/crypt_gpg/Crypt/GPG/PinEntry.php @ 0:1e000243b222
vanilla 1.3.3 distro, I hope
author | Charlie Root |
---|---|
date | Thu, 04 Jan 2018 15:50:29 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:1e000243b222 |
---|---|
1 <?php | |
2 | |
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ | |
4 | |
5 /** | |
6 * Contains a class implementing automatic pinentry for gpg-agent | |
7 * | |
8 * PHP version 5 | |
9 * | |
10 * LICENSE: | |
11 * | |
12 * This library is free software; you can redistribute it and/or modify | |
13 * it under the terms of the GNU Lesser General Public License as | |
14 * published by the Free Software Foundation; either version 2.1 of the | |
15 * License, or (at your option) any later version. | |
16 * | |
17 * This library is distributed in the hope that it will be useful, | |
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
20 * Lesser General Public License for more details. | |
21 * | |
22 * You should have received a copy of the GNU Lesser General Public | |
23 * License along with this library; if not, see | |
24 * <http://www.gnu.org/licenses/> | |
25 * | |
26 * @category Encryption | |
27 * @package Crypt_GPG | |
28 * @author Michael Gauthier <mike@silverorange.com> | |
29 * @copyright 2013 silverorange | |
30 * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 | |
31 * @link http://pear.php.net/package/Crypt_GPG | |
32 */ | |
33 | |
34 /** | |
35 * CLI user-interface and parser. | |
36 */ | |
37 require_once 'Console/CommandLine.php'; | |
38 | |
39 // {{{ class Crypt_GPG_PinEntry | |
40 | |
41 /** | |
42 * A command-line dummy pinentry program for use with gpg-agent and Crypt_GPG | |
43 * | |
44 * This pinentry receives passphrases through en environment variable and | |
45 * automatically enters the PIN in response to gpg-agent requests. No user- | |
46 * interaction required. | |
47 * | |
48 * The pinentry can be run independently for testing and debugging with the | |
49 * following syntax: | |
50 * | |
51 * <pre> | |
52 * Usage: | |
53 * crypt-gpg-pinentry [options] | |
54 * | |
55 * Options: | |
56 * -l log, --log=log Optional location to log pinentry activity. | |
57 * -v, --verbose Sets verbosity level. Use multiples for more detail | |
58 * (e.g. "-vv"). | |
59 * -h, --help show this help message and exit | |
60 * --version show the program version and exit | |
61 * </pre> | |
62 * | |
63 * @category Encryption | |
64 * @package Crypt_GPG | |
65 * @author Michael Gauthier <mike@silverorange.com> | |
66 * @copyright 2013 silverorange | |
67 * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 | |
68 * @link http://pear.php.net/package/Crypt_GPG | |
69 * @see Crypt_GPG::getKeys() | |
70 */ | |
71 class Crypt_GPG_PinEntry | |
72 { | |
73 // {{{ class constants | |
74 | |
75 /** | |
76 * Verbosity level for showing no output. | |
77 */ | |
78 const VERBOSITY_NONE = 0; | |
79 | |
80 /** | |
81 * Verbosity level for showing error output. | |
82 */ | |
83 const VERBOSITY_ERRORS = 1; | |
84 | |
85 /** | |
86 * Verbosity level for showing all output, including Assuan protocol | |
87 * messages. | |
88 */ | |
89 const VERBOSITY_ALL = 2; | |
90 | |
91 /** | |
92 * Length of buffer for reading lines from the Assuan server. | |
93 * | |
94 * PHP reads 8192 bytes. If this is set to less than 8192, PHP reads 8192 | |
95 * and buffers the rest so we might as well just read 8192. | |
96 * | |
97 * Using values other than 8192 also triggers PHP bugs. | |
98 * | |
99 * @see http://bugs.php.net/bug.php?id=35224 | |
100 */ | |
101 const CHUNK_SIZE = 8192; | |
102 | |
103 // }}} | |
104 // {{{ protected properties | |
105 | |
106 /** | |
107 * File handle for the input stream | |
108 * | |
109 * @var resource | |
110 */ | |
111 protected $stdin = null; | |
112 | |
113 /** | |
114 * File handle for the output stream | |
115 * | |
116 * @var resource | |
117 */ | |
118 protected $stdout = null; | |
119 | |
120 /** | |
121 * File handle for the log file if a log file is used | |
122 * | |
123 * @var resource | |
124 */ | |
125 protected $logFile = null; | |
126 | |
127 /** | |
128 * Whether or not this pinentry is finished and is exiting | |
129 * | |
130 * @var boolean | |
131 */ | |
132 protected $moribund = false; | |
133 | |
134 /** | |
135 * Verbosity level | |
136 * | |
137 * One of: | |
138 * - {@link Crypt_GPG_PinEntry::VERBOSITY_NONE}, | |
139 * - {@link Crypt_GPG_PinEntry::VERBOSITY_ERRORS}, or | |
140 * - {@link Crypt_GPG_PinEntry::VERBOSITY_ALL} | |
141 * | |
142 * @var integer | |
143 */ | |
144 protected $verbosity = self::VERBOSITY_NONE; | |
145 | |
146 /** | |
147 * The command-line interface parser for this pinentry | |
148 * | |
149 * @var Console_CommandLine | |
150 * | |
151 * @see Crypt_GPG_PinEntry::getParser() | |
152 */ | |
153 protected $parser = null; | |
154 | |
155 /** | |
156 * PINs to be entered by this pinentry | |
157 * | |
158 * An indexed array of associative arrays in the form: | |
159 * <code> | |
160 * <?php | |
161 * array( | |
162 * array( | |
163 * 'keyId' => $keyId, | |
164 * 'passphrase' => $passphrase | |
165 * ), | |
166 * ... | |
167 * ); | |
168 * ?> | |
169 * </code> | |
170 * | |
171 * This array is parsed from the environment variable | |
172 * <kbd>PINENTRY_USER_DATA</kbd>. | |
173 * | |
174 * @var array | |
175 * | |
176 * @see Crypt_GPG_PinEntry::initPinsFromENV() | |
177 */ | |
178 protected $pins = array(); | |
179 | |
180 /** | |
181 * The PIN currently being requested by the Assuan server | |
182 * | |
183 * If set, this is an associative array in the form: | |
184 * <code> | |
185 * <?php | |
186 * array( | |
187 * 'keyId' => $shortKeyId, | |
188 * 'userId' => $userIdString | |
189 * ); | |
190 * ?> | |
191 * </code> | |
192 * | |
193 * @var array|null | |
194 */ | |
195 protected $currentPin = null; | |
196 | |
197 // }}} | |
198 // {{{ __invoke() | |
199 | |
200 /** | |
201 * Runs this pinentry | |
202 * | |
203 * @return void | |
204 */ | |
205 public function __invoke() | |
206 { | |
207 $this->parser = $this->getCommandLineParser(); | |
208 | |
209 try { | |
210 $result = $this->parser->parse(); | |
211 | |
212 $this->setVerbosity($result->options['verbose']); | |
213 $this->setLogFilename($result->options['log']); | |
214 | |
215 $this->connect(); | |
216 $this->initPinsFromENV(); | |
217 | |
218 while (($line = fgets($this->stdin, self::CHUNK_SIZE)) !== false) { | |
219 $this->parseCommand(mb_substr($line, 0, -1, '8bit')); | |
220 if ($this->moribund) { | |
221 break; | |
222 } | |
223 } | |
224 | |
225 $this->disconnect(); | |
226 | |
227 } catch (Console_CommandLineException $e) { | |
228 $this->log($e->getMessage() . PHP_EOL, slf::VERBOSITY_ERRORS); | |
229 exit(1); | |
230 } catch (Exception $e) { | |
231 $this->log($e->getMessage() . PHP_EOL, self::VERBOSITY_ERRORS); | |
232 $this->log($e->getTraceAsString() . PHP_EOL, self::VERBOSITY_ERRORS); | |
233 exit(1); | |
234 } | |
235 } | |
236 | |
237 // }}} | |
238 // {{{ setVerbosity() | |
239 | |
240 /** | |
241 * Sets the verbosity of logging for this pinentry | |
242 * | |
243 * Verbosity levels are: | |
244 * | |
245 * - {@link Crypt_GPG_PinEntry::VERBOSITY_NONE} - no logging. | |
246 * - {@link Crypt_GPG_PinEntry::VERBOSITY_ERRORS} - log errors only. | |
247 * - {@link Crypt_GPG_PinEntry::VERBOSITY_ALL} - log everything, including | |
248 * the assuan protocol. | |
249 * | |
250 * @param integer $verbosity the level of verbosity of this pinentry. | |
251 * | |
252 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
253 */ | |
254 public function setVerbosity($verbosity) | |
255 { | |
256 $this->verbosity = (integer)$verbosity; | |
257 return $this; | |
258 } | |
259 | |
260 // }}} | |
261 // {{{ setLogFilename() | |
262 | |
263 /** | |
264 * Sets the log file location | |
265 * | |
266 * @param string $filename the new log filename to use. If an empty string | |
267 * is used, file-based logging is disabled. | |
268 * | |
269 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
270 */ | |
271 public function setLogFilename($filename) | |
272 { | |
273 if (is_resource($this->logFile)) { | |
274 fflush($this->logFile); | |
275 fclose($this->logFile); | |
276 $this->logFile = null; | |
277 } | |
278 | |
279 if ($filename != '') { | |
280 if (($this->logFile = fopen($filename, 'w')) === false) { | |
281 $this->log( | |
282 'Unable to open log file "' . $filename . '" ' | |
283 . 'for writing.' . PHP_EOL, | |
284 self::VERBOSITY_ERRORS | |
285 ); | |
286 exit(1); | |
287 } else { | |
288 stream_set_write_buffer($this->logFile, 0); | |
289 } | |
290 } | |
291 | |
292 return $this; | |
293 } | |
294 | |
295 // }}} | |
296 // {{{ getUIXML() | |
297 | |
298 /** | |
299 * Gets the CLI user-interface definition for this pinentry | |
300 * | |
301 * Detects whether or not this package is PEAR-installed and appropriately | |
302 * locates the XML UI definition. | |
303 * | |
304 * @return string the location of the CLI user-interface definition XML. | |
305 */ | |
306 protected function getUIXML() | |
307 { | |
308 // Find PinEntry config depending on the way how the package is installed | |
309 $ds = DIRECTORY_SEPARATOR; | |
310 $root = __DIR__ . $ds . '..' . $ds . '..' . $ds; | |
311 $paths = array( | |
312 '@data-dir@' . $ds . '@package-name@' . $ds . 'data', // PEAR | |
313 $root . 'data', // Git | |
314 $root . 'data' . $ds . 'Crypt_GPG' . $ds . 'data', // Composer | |
315 ); | |
316 | |
317 foreach ($paths as $path) { | |
318 if (file_exists($path . $ds . 'pinentry-cli.xml')) { | |
319 return $path . $ds . 'pinentry-cli.xml'; | |
320 } | |
321 } | |
322 } | |
323 | |
324 // }}} | |
325 // {{{ getCommandLineParser() | |
326 | |
327 /** | |
328 * Gets the CLI parser for this pinentry | |
329 * | |
330 * @return Console_CommandLine the CLI parser for this pinentry. | |
331 */ | |
332 protected function getCommandLineParser() | |
333 { | |
334 return Console_CommandLine::fromXmlFile($this->getUIXML()); | |
335 } | |
336 | |
337 // }}} | |
338 // {{{ log() | |
339 | |
340 /** | |
341 * Logs a message at the specified verbosity level | |
342 * | |
343 * If a log file is used, the message is written to the log. Otherwise, | |
344 * the message is sent to STDERR. | |
345 * | |
346 * @param string $data the message to log. | |
347 * @param integer $level the verbosity level above which the message should | |
348 * be logged. | |
349 * | |
350 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
351 */ | |
352 protected function log($data, $level) | |
353 { | |
354 if ($this->verbosity >= $level) { | |
355 if (is_resource($this->logFile)) { | |
356 fwrite($this->logFile, $data); | |
357 fflush($this->logFile); | |
358 } else { | |
359 $this->parser->outputter->stderr($data); | |
360 } | |
361 } | |
362 | |
363 return $this; | |
364 } | |
365 | |
366 // }}} | |
367 // {{{ connect() | |
368 | |
369 /** | |
370 * Connects this pinentry to the assuan server | |
371 * | |
372 * Opens I/O streams and sends initial handshake. | |
373 * | |
374 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
375 */ | |
376 protected function connect() | |
377 { | |
378 // Binary operations will not work on Windows with PHP < 5.2.6. | |
379 $rb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'r' : 'rb'; | |
380 $wb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'w' : 'wb'; | |
381 | |
382 $this->stdin = fopen('php://stdin', $rb); | |
383 $this->stdout = fopen('php://stdout', $wb); | |
384 | |
385 if (function_exists('stream_set_read_buffer')) { | |
386 stream_set_read_buffer($this->stdin, 0); | |
387 } | |
388 stream_set_write_buffer($this->stdout, 0); | |
389 | |
390 // initial handshake | |
391 $this->send($this->getOK('Crypt_GPG pinentry ready and waiting')); | |
392 | |
393 return $this; | |
394 } | |
395 | |
396 // }}} | |
397 // {{{ parseCommand() | |
398 | |
399 /** | |
400 * Parses an assuan command and performs the appropriate action | |
401 * | |
402 * Documentation of the assuan commands for pinentry is limited to | |
403 * non-existent. Most of these commands were taken from the C source code | |
404 * to gpg-agent and pinentry. | |
405 * | |
406 * Additional context was provided by using strace -f when calling the | |
407 * gpg-agent. | |
408 * | |
409 * @param string $line the assuan command line to parse | |
410 * | |
411 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
412 */ | |
413 protected function parseCommand($line) | |
414 { | |
415 $this->log('<- ' . $line . PHP_EOL, self::VERBOSITY_ALL); | |
416 | |
417 $parts = explode(' ', $line, 2); | |
418 | |
419 $command = $parts[0]; | |
420 | |
421 if (count($parts) === 2) { | |
422 $data = $parts[1]; | |
423 } else { | |
424 $data = null; | |
425 } | |
426 | |
427 switch ($command) { | |
428 case 'SETDESC': | |
429 return $this->sendSetDescription($data); | |
430 | |
431 case 'MESSAGE': | |
432 return $this->sendMessage(); | |
433 | |
434 case 'CONFIRM': | |
435 return $this->sendConfirm(); | |
436 | |
437 case 'GETINFO': | |
438 return $this->sendGetInfo($data); | |
439 | |
440 case 'GETPIN': | |
441 return $this->sendGetPin($data); | |
442 | |
443 case 'RESET': | |
444 return $this->sendReset(); | |
445 | |
446 case 'BYE': | |
447 return $this->sendBye(); | |
448 | |
449 default: | |
450 return $this->sendNotImplementedOK(); | |
451 } | |
452 } | |
453 | |
454 // }}} | |
455 // {{{ initPinsFromENV() | |
456 | |
457 /** | |
458 * Initializes the PINs to be entered by this pinentry from the environment | |
459 * variable PINENTRY_USER_DATA | |
460 * | |
461 * The PINs are parsed from a JSON-encoded string. | |
462 * | |
463 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
464 */ | |
465 protected function initPinsFromENV() | |
466 { | |
467 if (($userData = getenv('PINENTRY_USER_DATA')) !== false) { | |
468 $pins = json_decode($userData, true); | |
469 if ($pins === null) { | |
470 $this->log( | |
471 '-- failed to parse user data' . PHP_EOL, | |
472 self::VERBOSITY_ERRORS | |
473 ); | |
474 } else { | |
475 $this->pins = $pins; | |
476 $this->log( | |
477 '-- got user data [not showing passphrases]' . PHP_EOL, | |
478 self::VERBOSITY_ALL | |
479 ); | |
480 } | |
481 } | |
482 | |
483 return $this; | |
484 } | |
485 | |
486 // }}} | |
487 // {{{ disconnect() | |
488 | |
489 /** | |
490 * Disconnects this pinentry from the Assuan server | |
491 * | |
492 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
493 */ | |
494 protected function disconnect() | |
495 { | |
496 $this->log('-- disconnecting' . PHP_EOL, self::VERBOSITY_ALL); | |
497 | |
498 fflush($this->stdout); | |
499 fclose($this->stdout); | |
500 fclose($this->stdin); | |
501 | |
502 $this->stdin = null; | |
503 $this->stdout = null; | |
504 | |
505 $this->log('-- disconnected' . PHP_EOL, self::VERBOSITY_ALL); | |
506 | |
507 if (is_resource($this->logFile)) { | |
508 fflush($this->logFile); | |
509 fclose($this->logFile); | |
510 $this->logFile = null; | |
511 } | |
512 | |
513 return $this; | |
514 } | |
515 | |
516 // }}} | |
517 // {{{ sendNotImplementedOK() | |
518 | |
519 /** | |
520 * Sends an OK response for a not implemented feature | |
521 * | |
522 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
523 */ | |
524 protected function sendNotImplementedOK() | |
525 { | |
526 return $this->send($this->getOK()); | |
527 } | |
528 | |
529 // }}} | |
530 // {{{ sendSetDescription() | |
531 | |
532 /** | |
533 * Parses the currently requested key identifier and user identifier from | |
534 * the description passed to this pinentry | |
535 * | |
536 * @param string $text the raw description sent from gpg-agent. | |
537 * | |
538 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
539 */ | |
540 protected function sendSetDescription($text) | |
541 { | |
542 $text = rawurldecode($text); | |
543 $matches = array(); | |
544 // TODO: handle user id with quotation marks | |
545 $exp = '/\n"(.+)"\n.*\sID ([A-Z0-9]+),\n/mu'; | |
546 if (preg_match($exp, $text, $matches) === 1) { | |
547 $userId = $matches[1]; | |
548 $keyId = $matches[2]; | |
549 | |
550 if ($this->currentPin === null || $this->currentPin['keyId'] !== $keyId) { | |
551 $this->currentPin = array( | |
552 'userId' => $userId, | |
553 'keyId' => $keyId | |
554 ); | |
555 $this->log( | |
556 '-- looking for PIN for ' . $keyId . PHP_EOL, | |
557 self::VERBOSITY_ALL | |
558 ); | |
559 } | |
560 } | |
561 | |
562 return $this->send($this->getOK()); | |
563 } | |
564 | |
565 // }}} | |
566 // {{{ sendConfirm() | |
567 | |
568 /** | |
569 * Tells the assuan server to confirm the operation | |
570 * | |
571 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
572 */ | |
573 protected function sendConfirm() | |
574 { | |
575 return $this->send($this->getOK()); | |
576 } | |
577 | |
578 // }}} | |
579 // {{{ sendMessage() | |
580 | |
581 /** | |
582 * Tells the assuan server that any requested pop-up messages were confirmed | |
583 * by pressing the fake 'close' button | |
584 * | |
585 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
586 */ | |
587 protected function sendMessage() | |
588 { | |
589 return $this->sendButtonInfo('close'); | |
590 } | |
591 | |
592 // }}} | |
593 // {{{ sendButtonInfo() | |
594 | |
595 /** | |
596 * Sends information about pressed buttons to the assuan server | |
597 * | |
598 * This is used to fake a user-interface for this pinentry. | |
599 * | |
600 * @param string $text the button status to send. | |
601 * | |
602 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
603 */ | |
604 protected function sendButtonInfo($text) | |
605 { | |
606 return $this->send('BUTTON_INFO ' . $text . "\n"); | |
607 } | |
608 | |
609 // }}} | |
610 // {{{ sendGetPin() | |
611 | |
612 /** | |
613 * Sends the PIN value for the currently requested key | |
614 * | |
615 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
616 */ | |
617 protected function sendGetPin() | |
618 { | |
619 $foundPin = ''; | |
620 | |
621 if (is_array($this->currentPin)) { | |
622 $keyIdLength = mb_strlen($this->currentPin['keyId'], '8bit'); | |
623 | |
624 // search for the pin | |
625 foreach ($this->pins as $_keyId => $pin) { | |
626 // Warning: GnuPG 2.1 asks 3 times for passphrase if it is invalid | |
627 $keyId = $this->currentPin['keyId']; | |
628 $_keyIdLength = mb_strlen($_keyId, '8bit'); | |
629 | |
630 // Get last X characters of key identifier to compare | |
631 // Most GnuPG versions use 8 characters, but recent ones can use 16, | |
632 // We support 8 for backward compatibility | |
633 if ($keyIdLength < $_keyIdLength) { | |
634 $_keyId = mb_substr($_keyId, -$keyIdLength, $keyIdLength, '8bit'); | |
635 } else if ($keyIdLength > $_keyIdLength) { | |
636 $keyId = mb_substr($keyId, -$_keyIdLength, $_keyIdLength, '8bit'); | |
637 } | |
638 | |
639 if ($_keyId === $keyId) { | |
640 $foundPin = $pin; | |
641 break; | |
642 } | |
643 } | |
644 } | |
645 | |
646 return $this | |
647 ->send($this->getData($foundPin)) | |
648 ->send($this->getOK()); | |
649 } | |
650 | |
651 // }}} | |
652 // {{{ sendGetInfo() | |
653 | |
654 /** | |
655 * Sends information about this pinentry | |
656 * | |
657 * @param string $data the information requested by the assuan server. | |
658 * Currently only 'pid' is supported. Other requests | |
659 * return no information. | |
660 * | |
661 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
662 */ | |
663 protected function sendGetInfo($data) | |
664 { | |
665 $parts = explode(' ', $data, 2); | |
666 $command = reset($parts); | |
667 | |
668 switch ($command) { | |
669 case 'pid': | |
670 return $this->sendGetInfoPID(); | |
671 default: | |
672 return $this->send($this->getOK()); | |
673 } | |
674 | |
675 return $this; | |
676 } | |
677 // }}} | |
678 // {{{ sendGetInfoPID() | |
679 | |
680 /** | |
681 * Sends the PID of this pinentry to the assuan server | |
682 * | |
683 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
684 */ | |
685 protected function sendGetInfoPID() | |
686 { | |
687 return $this | |
688 ->send($this->getData(getmypid())) | |
689 ->send($this->getOK()); | |
690 } | |
691 | |
692 // }}} | |
693 // {{{ sendBye() | |
694 | |
695 /** | |
696 * Flags this pinentry for disconnection and sends an OK response | |
697 * | |
698 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
699 */ | |
700 protected function sendBye() | |
701 { | |
702 $return = $this->send($this->getOK('closing connection')); | |
703 $this->moribund = true; | |
704 return $return; | |
705 } | |
706 | |
707 // }}} | |
708 // {{{ sendReset() | |
709 | |
710 /** | |
711 * Resets this pinentry and sends an OK response | |
712 * | |
713 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
714 */ | |
715 protected function sendReset() | |
716 { | |
717 $this->currentPin = null; | |
718 return $this->send($this->getOK()); | |
719 } | |
720 | |
721 // }}} | |
722 // {{{ getOK() | |
723 | |
724 /** | |
725 * Gets an OK response to send to the assuan server | |
726 * | |
727 * @param string $data an optional message to include with the OK response. | |
728 * | |
729 * @return string the OK response. | |
730 */ | |
731 protected function getOK($data = null) | |
732 { | |
733 $return = 'OK'; | |
734 | |
735 if ($data) { | |
736 $return .= ' ' . $data; | |
737 } | |
738 | |
739 return $return . "\n"; | |
740 } | |
741 | |
742 // }}} | |
743 // {{{ getData() | |
744 | |
745 /** | |
746 * Gets data ready to send to the assuan server | |
747 * | |
748 * Data is appropriately escaped and long lines are wrapped. | |
749 * | |
750 * @param string $data the data to send to the assuan server. | |
751 * | |
752 * @return string the properly escaped, formatted data. | |
753 * | |
754 * @see http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html | |
755 */ | |
756 protected function getData($data) | |
757 { | |
758 // Escape data. Only %, \n and \r need to be escaped but other | |
759 // values are allowed to be escaped. See | |
760 // http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html | |
761 $data = rawurlencode($data); | |
762 $data = $this->getWordWrappedData($data, 'D'); | |
763 return $data; | |
764 } | |
765 | |
766 // }}} | |
767 // {{{ getComment() | |
768 | |
769 /** | |
770 * Gets a comment ready to send to the assuan server | |
771 * | |
772 * @param string $data the comment to send to the assuan server. | |
773 * | |
774 * @return string the properly formatted comment. | |
775 * | |
776 * @see http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html | |
777 */ | |
778 protected function getComment($data) | |
779 { | |
780 return $this->getWordWrappedData($data, '#'); | |
781 } | |
782 | |
783 // }}} | |
784 // {{{ getWordWrappedData() | |
785 | |
786 /** | |
787 * Wraps strings at 1,000 bytes without splitting UTF-8 multibyte | |
788 * characters | |
789 * | |
790 * Each line is prepended with the specified line prefix. Wrapped lines | |
791 * are automatically appended with \ characters. | |
792 * | |
793 * Protocol strings are UTF-8 but maximum line length is 1,000 bytes. | |
794 * <kbd>mb_strcut()</kbd> is used so we can limit line length by bytes | |
795 * and not split characters across multiple lines. | |
796 * | |
797 * @param string $data the data to wrap. | |
798 * @param string $prefix a single character to use as the line prefix. For | |
799 * example, 'D' or '#'. | |
800 * | |
801 * @return string the word-wrapped, prefixed string. | |
802 * | |
803 * @see http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html | |
804 */ | |
805 protected function getWordWrappedData($data, $prefix) | |
806 { | |
807 $lines = array(); | |
808 | |
809 do { | |
810 if (mb_strlen($data, '8bit') > 997) { | |
811 $line = $prefix . ' ' . mb_strcut($data, 0, 996, 'utf-8') . "\\\n"; | |
812 $lines[] = $line; | |
813 $lineLength = mb_strlen($line, '8bit') - 1; | |
814 $dataLength = mb_substr($data, '8bit'); | |
815 $data = mb_substr( | |
816 $data, | |
817 $lineLength, | |
818 $dataLength - $lineLength, | |
819 '8bit' | |
820 ); | |
821 } else { | |
822 $lines[] = $prefix . ' ' . $data . "\n"; | |
823 $data = ''; | |
824 } | |
825 } while ($data != ''); | |
826 | |
827 return implode('', $lines); | |
828 } | |
829 | |
830 // }}} | |
831 // {{{ send() | |
832 | |
833 /** | |
834 * Sends raw data to the assuan server | |
835 * | |
836 * @param string $data the data to send. | |
837 * | |
838 * @return Crypt_GPG_PinEntry the current object, for fluent interface. | |
839 */ | |
840 protected function send($data) | |
841 { | |
842 $this->log('-> ' . $data, self::VERBOSITY_ALL); | |
843 fwrite($this->stdout, $data); | |
844 fflush($this->stdout); | |
845 return $this; | |
846 } | |
847 | |
848 // }}} | |
849 } | |
850 | |
851 // }}} | |
852 | |
853 ?> |