From 3412e50b54e3daac8745234e21ab6e72be0ed165 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Wed, 04 Jun 2014 11:20:33 -0400
Subject: [PATCH] Fix attachment menu structure and aria-attributes

---
 program/lib/Crypt/GPG/Engine.php |  560 ++++++++++++++++++++++++++++++++++++++++---------------
 1 files changed, 404 insertions(+), 156 deletions(-)

diff --git a/program/lib/Crypt/GPG/Engine.php b/program/lib/Crypt/GPG/Engine.php
index 081be8e..6015414 100644
--- a/program/lib/Crypt/GPG/Engine.php
+++ b/program/lib/Crypt/GPG/Engine.php
@@ -30,9 +30,9 @@
  * @package   Crypt_GPG
  * @author    Nathan Fredrickson <nathan@silverorange.com>
  * @author    Michael Gauthier <mike@silverorange.com>
- * @copyright 2005-2010 silverorange
+ * @copyright 2005-2013 silverorange
  * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
- * @version   CVS: $Id: Engine.php 302822 2010-08-26 17:30:57Z gauthierm $
+ * @version   CVS: $Id$
  * @link      http://pear.php.net/package/Crypt_GPG
  * @link      http://www.gnupg.org/
  */
@@ -46,6 +46,16 @@
  * GPG exception classes.
  */
 require_once 'Crypt/GPG/Exceptions.php';
+
+/**
+ * Byte string operations.
+ */
+require_once 'Crypt/GPG/ByteUtils.php';
+
+/**
+ * Process control methods.
+ */
+require_once 'Crypt/GPG/ProcessControl.php';
 
 /**
  * Standard PEAR exception is used if GPG binary is not found.
@@ -70,7 +80,7 @@
  * @package   Crypt_GPG
  * @author    Nathan Fredrickson <nathan@silverorange.com>
  * @author    Michael Gauthier <mike@silverorange.com>
- * @copyright 2005-2010 silverorange
+ * @copyright 2005-2013 silverorange
  * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
  * @link      http://pear.php.net/package/Crypt_GPG
  * @link      http://www.gnupg.org/
@@ -163,6 +173,17 @@
     private $_binary = '';
 
     /**
+     * Location of GnuPG agent binary
+     *
+     * Only used for GnuPG 2.x
+     *
+     * @var string
+     * @see Crypt_GPG_Engine::__construct()
+     * @see Crypt_GPG_Engine::_getAgent()
+     */
+    private $_agent = '';
+
+    /**
      * Directory containing the GPG key files
      *
      * This property only contains the path when the <i>homedir</i> option
@@ -228,6 +249,15 @@
     private $_pipes = array();
 
     /**
+     * Array of pipes used for communication with the gpg-agent binary
+     *
+     * This is an array of file descriptor resources.
+     *
+     * @var array
+     */
+    private $_agentPipes = array();
+
+    /**
      * Array of currently opened pipes
      *
      * This array is used to keep track of remaining opened pipes so they can
@@ -246,6 +276,20 @@
      * @var resource
      */
     private $_process = null;
+
+    /**
+     * A handle for the gpg-agent process
+     *
+     * @var resource
+     */
+    private $_agentProcess = null;
+
+    /**
+     * GPG agent daemon socket and PID for running gpg-agent
+     *
+     * @var string
+     */
+    private $_agentInfo = null;
 
     /**
      * Whether or not the operating system is Darwin (OS X)
@@ -367,18 +411,6 @@
      */
     private $_version = '';
 
-    /**
-     * Cached value indicating whether or not mbstring function overloading is
-     * on for strlen
-     *
-     * This is cached for optimal performance inside the I/O loop.
-     *
-     * @var boolean
-     * @see Crypt_GPG_Engine::_byteLength()
-     * @see Crypt_GPG_Engine::_byteSubstring()
-     */
-    private static $_mbStringOverload = null;
-
     // }}}
     // {{{ __construct()
 
@@ -432,6 +464,14 @@
      *                                       operating system. The option
      *                                       <kbd>gpgBinary</kbd> is a
      *                                       deprecated alias for this option.
+     * - <kbd>string  agent</kbd>          - the location of the GnuPG agent
+     *                                       binary. The gpg-agent is only
+     *                                       used for GnuPG 2.x. If not
+     *                                       specified, the engine attempts
+     *                                       to auto-detect the gpg-agent
+     *                                       binary location using a list of
+     *                                       know default locations for the
+     *                                       current operating system.
      * - <kbd>boolean debug</kbd>          - whether or not to use debug mode.
      *                                       When debug mode is on, all
      *                                       communication to and from the GPG
@@ -457,24 +497,38 @@
      * @throws PEAR_Exception if the provided <kbd>binary</kbd> is invalid, or
      *         if no <kbd>binary</kbd> is provided and no suitable binary could
      *         be found.
+     *
+     * @throws PEAR_Exception if the provided <kbd>agent</kbd> is invalid, or
+     *         if no <kbd>agent</kbd> is provided and no suitable gpg-agent
+     *         cound be found.
      */
     public function __construct(array $options = array())
     {
         $this->_isDarwin = (strncmp(strtoupper(PHP_OS), 'DARWIN', 6) === 0);
 
-        // populate mbstring overloading cache if not set
-        if (self::$_mbStringOverload === null) {
-            self::$_mbStringOverload = (extension_loaded('mbstring')
-                && (ini_get('mbstring.func_overload') & 0x02) === 0x02);
-        }
-
         // get homedir
         if (array_key_exists('homedir', $options)) {
             $this->_homedir = (string)$options['homedir'];
         } else {
-            // note: this requires the package OS dep exclude 'windows'
-            $info = posix_getpwuid(posix_getuid());
-            $this->_homedir = $info['dir'].'/.gnupg';
+            if (extension_loaded('posix')) {
+                // note: this requires the package OS dep exclude 'windows'
+                $info = posix_getpwuid(posix_getuid());
+                $this->_homedir = $info['dir'].'/.gnupg';
+            } else {
+                if (isset($_SERVER['HOME'])) {
+                    $this->_homedir = $_SERVER['HOME'];
+                } else {
+                    $this->_homedir = getenv('HOME');
+                }
+            }
+
+            if ($this->_homedir === false) {
+                throw new Crypt_GPG_FileException(
+                    'Could not locate homedir. Please specify the homedir ' .
+                    'to use with the \'homedir\' option when instantiating ' .
+                    'the Crypt_GPG object.'
+                );
+            }
         }
 
         // attempt to create homedir if it does not exist
@@ -484,14 +538,38 @@
                 // with 0777, homedir is set to 0700.
                 chmod($this->_homedir, 0700);
             } else {
-                throw new Crypt_GPG_FileException('The \'homedir\' "' .
-                    $this->_homedir . '" is not readable or does not exist '.
-                    'and cannot be created. This can happen if \'homedir\' '.
-                    'is not specified in the Crypt_GPG options, Crypt_GPG is '.
-                    'run as the web user, and the web user has no home '.
-                    'directory.',
-                    0, $this->_homedir);
+                throw new Crypt_GPG_FileException(
+                    'The \'homedir\' "' . $this->_homedir . '" is not ' .
+                    'readable or does not exist and cannot be created. This ' .
+                    'can happen if \'homedir\' is not specified in the ' .
+                    'Crypt_GPG options, Crypt_GPG is run as the web user, ' .
+                    'and the web user has no home directory.',
+                    0,
+                    $this->_homedir
+                );
             }
+        }
+
+        // check homedir permissions (See Bug #19833)
+        if (!is_executable($this->_homedir)) {
+            throw new Crypt_GPG_FileException(
+                'The \'homedir\' "' . $this->_homedir . '" is not enterable ' .
+                'by the current user. Please check the permissions on your ' .
+                'homedir and make sure the current user can both enter and ' .
+                'write to the directory.',
+                0,
+                $this->_homedir
+            );
+        }
+        if (!is_writeable($this->_homedir)) {
+            throw new Crypt_GPG_FileException(
+                'The \'homedir\' "' . $this->_homedir . '" is not writable ' .
+                'by the current user. Please check the permissions on your ' .
+                'homedir and make sure the current user can both enter and ' .
+                'write to the directory.',
+                0,
+                $this->_homedir
+            );
         }
 
         // get binary
@@ -505,9 +583,26 @@
         }
 
         if ($this->_binary == '' || !is_executable($this->_binary)) {
-            throw new PEAR_Exception('GPG binary not found. If you are sure '.
-                'the GPG binary is installed, please specify the location of '.
-                'the GPG binary using the \'binary\' driver option.');
+            throw new PEAR_Exception(
+                'GPG binary not found. If you are sure the GPG binary is ' .
+                'installed, please specify the location of the GPG binary ' .
+                'using the \'binary\' driver option.'
+            );
+        }
+
+        // get agent 
+        if (array_key_exists('agent', $options)) {
+            $this->_agent = (string)$options['agent'];
+        } else {
+            $this->_agent = $this->_getAgent();
+        }
+
+        if ($this->_agent == '' || !is_executable($this->_agent)) {
+            throw new PEAR_Exception(
+                'gpg-agent binary not found. If you are sure the gpg-agent ' .
+                'is installed, please specify the location of the gpg-agent ' .
+                'binary using the \'agent\' driver option.'
+            );
         }
 
         /*
@@ -891,7 +986,7 @@
             }
 
             $matches    = array();
-            $expression = '/gpg \(GnuPG\) (\S+)/';
+            $expression = '#gpg \(GnuPG[A-Za-z0-9/]*?\) (\S+)#';
 
             if (preg_match($expression, $info, $matches) === 1) {
                 $this->_version = $matches[1];
@@ -1114,6 +1209,9 @@
         $fdCommand = $this->_pipes[self::FD_COMMAND];
         $fdMessage = $this->_pipes[self::FD_MESSAGE];
 
+        // select loop delay in milliseconds
+        $delay = 0;
+
         while (true) {
 
             $inputStreams     = array();
@@ -1166,15 +1264,15 @@
                 $outputStreams[] = $this->_output;
             }
 
-            if ($this->_commandBuffer != '') {
+            if ($this->_commandBuffer != '' && is_resource($fdCommand)) {
                 $outputStreams[] = $fdCommand;
             }
 
-            if ($messageBuffer != '') {
+            if ($messageBuffer != '' && is_resource($fdMessage)) {
                 $outputStreams[] = $fdMessage;
             }
 
-            if ($inputBuffer != '') {
+            if ($inputBuffer != '' && is_resource($fdInput)) {
                 $outputStreams[] = $fdInput;
             }
 
@@ -1209,33 +1307,41 @@
             }
 
             // write input (to GPG)
-            if (in_array($fdInput, $outputStreams)) {
+            if (in_array($fdInput, $outputStreams, true)) {
                 $this->_debug('GPG is ready for input');
 
-                $chunk = self::_byteSubstring(
+                $chunk = Crypt_GPG_ByteUtils::substr(
                     $inputBuffer,
                     0,
                     self::CHUNK_SIZE
                 );
 
-                $length = self::_byteLength($chunk);
+                $length = Crypt_GPG_ByteUtils::strlen($chunk);
 
                 $this->_debug(
                     '=> about to write ' . $length . ' bytes to GPG input'
                 );
 
                 $length = fwrite($fdInput, $chunk, $length);
-
-                $this->_debug('=> wrote ' . $length . ' bytes');
-
-                $inputBuffer = self::_byteSubstring(
-                    $inputBuffer,
-                    $length
-                );
+                if ($length === 0) {
+                    // If we wrote 0 bytes it was either EAGAIN or EPIPE. Since
+                    // the pipe was seleted for writing, we assume it was EPIPE.
+                    // There's no way to get the actual erorr code in PHP. See
+                    // PHP Bug #39598. https://bugs.php.net/bug.php?id=39598
+                    $this->_debug('=> broken pipe on GPG input');
+                    $this->_debug('=> closing pipe GPG input');
+                    $this->_closePipe(self::FD_INPUT);
+                } else {
+                    $this->_debug('=> wrote ' . $length . ' bytes');
+                    $inputBuffer = Crypt_GPG_ByteUtils::substr(
+                        $inputBuffer,
+                        $length
+                    );
+                }
             }
 
             // read input (from PHP stream)
-            if (in_array($this->_input, $inputStreams)) {
+            if (in_array($this->_input, $inputStreams, true)) {
                 $this->_debug('input stream is ready for reading');
                 $this->_debug(
                     '=> about to read ' . self::CHUNK_SIZE .
@@ -1243,36 +1349,48 @@
                 );
 
                 $chunk        = fread($this->_input, self::CHUNK_SIZE);
-                $length       = self::_byteLength($chunk);
+                $length       = Crypt_GPG_ByteUtils::strlen($chunk);
                 $inputBuffer .= $chunk;
 
                 $this->_debug('=> read ' . $length . ' bytes');
             }
 
             // write message (to GPG)
-            if (in_array($fdMessage, $outputStreams)) {
+            if (in_array($fdMessage, $outputStreams, true)) {
                 $this->_debug('GPG is ready for message data');
 
-                $chunk = self::_byteSubstring(
+                $chunk = Crypt_GPG_ByteUtils::substr(
                     $messageBuffer,
                     0,
                     self::CHUNK_SIZE
                 );
 
-                $length = self::_byteLength($chunk);
+                $length = Crypt_GPG_ByteUtils::strlen($chunk);
 
                 $this->_debug(
                     '=> about to write ' . $length . ' bytes to GPG message'
                 );
 
                 $length = fwrite($fdMessage, $chunk, $length);
-                $this->_debug('=> wrote ' . $length . ' bytes');
-
-                $messageBuffer = self::_byteSubstring($messageBuffer, $length);
+                if ($length === 0) {
+                    // If we wrote 0 bytes it was either EAGAIN or EPIPE. Since
+                    // the pipe was seleted for writing, we assume it was EPIPE.
+                    // There's no way to get the actual erorr code in PHP. See
+                    // PHP Bug #39598. https://bugs.php.net/bug.php?id=39598
+                    $this->_debug('=> broken pipe on GPG message');
+                    $this->_debug('=> closing pipe GPG message');
+                    $this->_closePipe(self::FD_MESSAGE);
+                } else {
+                    $this->_debug('=> wrote ' . $length . ' bytes');
+                    $messageBuffer = Crypt_GPG_ByteUtils::substr(
+                        $messageBuffer,
+                        $length
+                    );
+                }
             }
 
             // read message (from PHP stream)
-            if (in_array($this->_message, $inputStreams)) {
+            if (in_array($this->_message, $inputStreams, true)) {
                 $this->_debug('message stream is ready for reading');
                 $this->_debug(
                     '=> about to read ' . self::CHUNK_SIZE .
@@ -1280,14 +1398,14 @@
                 );
 
                 $chunk          = fread($this->_message, self::CHUNK_SIZE);
-                $length         = self::_byteLength($chunk);
+                $length         = Crypt_GPG_ByteUtils::strlen($chunk);
                 $messageBuffer .= $chunk;
 
                 $this->_debug('=> read ' . $length . ' bytes');
             }
 
             // read output (from GPG)
-            if (in_array($fdOutput, $inputStreams)) {
+            if (in_array($fdOutput, $inputStreams, true)) {
                 $this->_debug('GPG output stream ready for reading');
                 $this->_debug(
                     '=> about to read ' . self::CHUNK_SIZE .
@@ -1295,23 +1413,23 @@
                 );
 
                 $chunk         = fread($fdOutput, self::CHUNK_SIZE);
-                $length        = self::_byteLength($chunk);
+                $length        = Crypt_GPG_ByteUtils::strlen($chunk);
                 $outputBuffer .= $chunk;
 
                 $this->_debug('=> read ' . $length . ' bytes');
             }
 
             // write output (to PHP stream)
-            if (in_array($this->_output, $outputStreams)) {
+            if (in_array($this->_output, $outputStreams, true)) {
                 $this->_debug('output stream is ready for data');
 
-                $chunk = self::_byteSubstring(
+                $chunk = Crypt_GPG_ByteUtils::substr(
                     $outputBuffer,
                     0,
                     self::CHUNK_SIZE
                 );
 
-                $length = self::_byteLength($chunk);
+                $length = Crypt_GPG_ByteUtils::strlen($chunk);
 
                 $this->_debug(
                     '=> about to write ' . $length . ' bytes to output stream'
@@ -1321,11 +1439,14 @@
 
                 $this->_debug('=> wrote ' . $length . ' bytes');
 
-                $outputBuffer = self::_byteSubstring($outputBuffer, $length);
+                $outputBuffer = Crypt_GPG_ByteUtils::substr(
+                    $outputBuffer,
+                    $length
+                );
             }
 
             // read error (from GPG)
-            if (in_array($fdError, $inputStreams)) {
+            if (in_array($fdError, $inputStreams, true)) {
                 $this->_debug('GPG error stream ready for reading');
                 $this->_debug(
                     '=> about to read ' . self::CHUNK_SIZE .
@@ -1333,14 +1454,14 @@
                 );
 
                 $chunk        = fread($fdError, self::CHUNK_SIZE);
-                $length       = self::_byteLength($chunk);
+                $length       = Crypt_GPG_ByteUtils::strlen($chunk);
                 $errorBuffer .= $chunk;
 
                 $this->_debug('=> read ' . $length . ' bytes');
 
                 // pass lines to error handlers
                 while (($pos = strpos($errorBuffer, PHP_EOL)) !== false) {
-                    $line = self::_byteSubstring($errorBuffer, 0, $pos);
+                    $line = Crypt_GPG_ByteUtils::substr($errorBuffer, 0, $pos);
                     foreach ($this->_errorHandlers as $handler) {
                         array_unshift($handler['args'], $line);
                         call_user_func_array(
@@ -1350,15 +1471,15 @@
 
                         array_shift($handler['args']);
                     }
-                    $errorBuffer = self::_byteSubString(
+                    $errorBuffer = Crypt_GPG_ByteUtils::substr(
                         $errorBuffer,
-                        $pos + self::_byteLength(PHP_EOL)
+                        $pos + Crypt_GPG_ByteUtils::strlen(PHP_EOL)
                     );
                 }
             }
 
             // read status (from GPG)
-            if (in_array($fdStatus, $inputStreams)) {
+            if (in_array($fdStatus, $inputStreams, true)) {
                 $this->_debug('GPG status stream ready for reading');
                 $this->_debug(
                     '=> about to read ' . self::CHUNK_SIZE .
@@ -1366,17 +1487,17 @@
                 );
 
                 $chunk         = fread($fdStatus, self::CHUNK_SIZE);
-                $length        = self::_byteLength($chunk);
+                $length        = Crypt_GPG_ByteUtils::strlen($chunk);
                 $statusBuffer .= $chunk;
 
                 $this->_debug('=> read ' . $length . ' bytes');
 
                 // pass lines to status handlers
                 while (($pos = strpos($statusBuffer, PHP_EOL)) !== false) {
-                    $line = self::_byteSubstring($statusBuffer, 0, $pos);
+                    $line = Crypt_GPG_ByteUtils::substr($statusBuffer, 0, $pos);
                     // only pass lines beginning with magic prefix
-                    if (self::_byteSubstring($line, 0, 9) == '[GNUPG:] ') {
-                        $line = self::_byteSubstring($line, 9);
+                    if (Crypt_GPG_ByteUtils::substr($line, 0, 9) == '[GNUPG:] ') {
+                        $line = Crypt_GPG_ByteUtils::substr($line, 9);
                         foreach ($this->_statusHandlers as $handler) {
                             array_unshift($handler['args'], $line);
                             call_user_func_array(
@@ -1387,38 +1508,60 @@
                             array_shift($handler['args']);
                         }
                     }
-                    $statusBuffer = self::_byteSubString(
+                    $statusBuffer = Crypt_GPG_ByteUtils::substr(
                         $statusBuffer,
-                        $pos + self::_byteLength(PHP_EOL)
+                        $pos + Crypt_GPG_ByteUtils::strlen(PHP_EOL)
                     );
                 }
             }
 
             // write command (to GPG)
-            if (in_array($fdCommand, $outputStreams)) {
+            if (in_array($fdCommand, $outputStreams, true)) {
                 $this->_debug('GPG is ready for command data');
 
                 // send commands
-                $chunk = self::_byteSubstring(
+                $chunk = Crypt_GPG_ByteUtils::substr(
                     $this->_commandBuffer,
                     0,
                     self::CHUNK_SIZE
                 );
 
-                $length = self::_byteLength($chunk);
+                $length = Crypt_GPG_ByteUtils::strlen($chunk);
 
                 $this->_debug(
                     '=> about to write ' . $length . ' bytes to GPG command'
                 );
 
                 $length = fwrite($fdCommand, $chunk, $length);
+                if ($length === 0) {
+                    // If we wrote 0 bytes it was either EAGAIN or EPIPE. Since
+                    // the pipe was seleted for writing, we assume it was EPIPE.
+                    // There's no way to get the actual erorr code in PHP. See
+                    // PHP Bug #39598. https://bugs.php.net/bug.php?id=39598
+                    $this->_debug('=> broken pipe on GPG command');
+                    $this->_debug('=> closing pipe GPG command');
+                    $this->_closePipe(self::FD_COMMAND);
+                } else {
+                    $this->_debug('=> wrote ' . $length);
+                    $this->_commandBuffer = Crypt_GPG_ByteUtils::substr(
+                        $this->_commandBuffer,
+                        $length
+                    );
+                }
+            }
 
-                $this->_debug('=> wrote ' . $length);
+            if (count($outputStreams) === 0 || count($inputStreams) === 0) {
+                // we have an I/O imbalance, increase the select loop delay
+                // to smooth things out
+                $delay += 10;
+            } else {
+                // things are running smoothly, decrease the delay
+                $delay -= 8;
+                $delay = max(0, $delay);
+            }
 
-                $this->_commandBuffer = self::_byteSubstring(
-                    $this->_commandBuffer,
-                    $length
-                );
+            if ($delay > 0) {
+                usleep($delay);
             }
 
         } // end loop while streams are open
@@ -1449,11 +1592,82 @@
     {
         $version = $this->getVersion();
 
+        // Binary operations will not work on Windows with PHP < 5.2.6. This is
+        // in case stream_select() ever works on Windows.
+        $rb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'r' : 'rb';
+        $wb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'w' : 'wb';
+
         $env = $_ENV;
 
         // Newer versions of GnuPG return localized results. Crypt_GPG only
         // works with English, so set the locale to 'C' for the subprocess.
         $env['LC_ALL'] = 'C';
+
+        // If using GnuPG 2.x start the gpg-agent
+        if (version_compare($version, '2.0.0', 'ge')) {
+            $agentCommandLine = $this->_agent;
+
+            $agentArguments = array(
+                '--options /dev/null', // ignore any saved options
+                '--csh', // output is easier to parse
+                '--keep-display', // prevent passing --display to pinentry
+                '--no-grab',
+                '--ignore-cache-for-signing',
+                '--pinentry-touch-file /dev/null',
+                '--disable-scdaemon',
+                '--no-use-standard-socket',
+                '--pinentry-program ' . escapeshellarg($this->_getPinEntry())
+            );
+
+            if ($this->_homedir) {
+                $agentArguments[] = '--homedir ' .
+                    escapeshellarg($this->_homedir);
+            }
+
+
+            $agentCommandLine .= ' ' . implode(' ', $agentArguments)
+                . ' --daemon';
+
+            $agentDescriptorSpec = array(
+                self::FD_INPUT   => array('pipe', $rb), // stdin
+                self::FD_OUTPUT  => array('pipe', $wb), // stdout
+                self::FD_ERROR   => array('pipe', $wb)  // stderr
+            );
+
+            $this->_debug('OPENING GPG-AGENT SUBPROCESS WITH THE FOLLOWING COMMAND:');
+            $this->_debug($agentCommandLine);
+
+            $this->_agentProcess = proc_open(
+                $agentCommandLine,
+                $agentDescriptorSpec,
+                $this->_agentPipes,
+                null,
+                $env,
+                array('binary_pipes' => true)
+            );
+
+            if (!is_resource($this->_agentProcess)) {
+                throw new Crypt_GPG_OpenSubprocessException(
+                    'Unable to open gpg-agent subprocess.',
+                    0,
+                    $agentCommandLine
+                );
+            }
+
+            // Get GPG_AGENT_INFO and set environment variable for gpg process.
+            // This is a blocking read, but is only 1 line.
+            $agentInfo = fread(
+                $this->_agentPipes[self::FD_OUTPUT],
+                self::CHUNK_SIZE
+            );
+
+            $agentInfo             = explode(' ', $agentInfo, 3);
+            $this->_agentInfo      = $agentInfo[2];
+            $env['GPG_AGENT_INFO'] = $this->_agentInfo;
+
+            // gpg-agent daemon is started, we can close the launching process
+            $this->_closeAgentLaunchProcess();
+        }
 
         $commandLine = $this->_binary;
 
@@ -1511,11 +1725,6 @@
         $commandLine .= ' ' . implode(' ', $arguments) . ' ' .
             $this->_operation;
 
-        // Binary operations will not work on Windows with PHP < 5.2.6. This is
-        // in case stream_select() ever works on Windows.
-        $rb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'r' : 'rb';
-        $wb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'w' : 'wb';
-
         $descriptorSpec = array(
             self::FD_INPUT   => array('pipe', $rb), // stdin
             self::FD_OUTPUT  => array('pipe', $wb), // stdout
@@ -1525,7 +1734,7 @@
             self::FD_MESSAGE => array('pipe', $rb)  // message
         );
 
-        $this->_debug('OPENING SUBPROCESS WITH THE FOLLOWING COMMAND:');
+        $this->_debug('OPENING GPG SUBPROCESS WITH THE FOLLOWING COMMAND:');
         $this->_debug($commandLine);
 
         $this->_process = proc_open(
@@ -1540,6 +1749,11 @@
         if (!is_resource($this->_process)) {
             throw new Crypt_GPG_OpenSubprocessException(
                 'Unable to open GPG subprocess.', 0, $commandLine);
+        }
+
+        // Set streams as non-blocking. See Bug #18618.
+        foreach ($this->_pipes as $pipe) {
+            stream_set_blocking($pipe, 0);
         }
 
         $this->_openPipes = $this->_pipes;
@@ -1562,8 +1776,11 @@
      */
     private function _closeSubprocess()
     {
+        // clear PINs from environment if they were set
+        $_ENV['PINENTRY_USER_DATA'] = null;
+
         if (is_resource($this->_process)) {
-            $this->_debug('CLOSING SUBPROCESS');
+            $this->_debug('CLOSING GPG SUBPROCESS');
 
             // close remaining open pipes
             foreach (array_keys($this->_openPipes) as $pipeNumber) {
@@ -1590,9 +1807,55 @@
             $this->_process = null;
             $this->_pipes   = array();
         }
+
+        $this->_closeAgentLaunchProcess();
+
+        if ($this->_agentInfo !== null) {
+            $this->_debug('STOPPING GPG-AGENT DAEMON');
+
+            $parts   = explode(':', $this->_agentInfo, 3);
+            $pid     = $parts[1];
+            $process = new Crypt_GPG_ProcessControl($pid);
+
+            // terminate agent daemon
+            $process->terminate();
+
+            while ($process->isRunning()) {
+                usleep(10000); // 10 ms
+                $process->terminate();
+            }
+
+            $this->_agentInfo = null;
+
+            $this->_debug('GPG-AGENT DAEMON STOPPED');
+        }
     }
 
     // }}}
+    // {{ _closeAgentLaunchProcess()
+
+    private function _closeAgentLaunchProcess()
+    {
+        if (is_resource($this->_agentProcess)) {
+            $this->_debug('CLOSING GPG-AGENT LAUNCH PROCESS');
+
+            // close agent pipes
+            foreach ($this->_agentPipes as $pipe) {
+                fflush($pipe);
+                fclose($pipe);
+            }
+
+            // close agent launching process
+            proc_close($this->_agentProcess);
+
+            $this->_agentProcess = null;
+            $this->_agentPipes   = array();
+
+            $this->_debug('GPG-AGENT LAUNCH PROCESS CLOSED');
+        }
+    }
+
+    // }}
     // {{{ _closePipe()
 
     /**
@@ -1658,6 +1921,55 @@
     }
 
     // }}}
+    // {{ _getAgent()
+
+    private function _getAgent()
+    {
+        $agent = '';
+
+        if ($this->_isDarwin) {
+            $agentFiles = array(
+                '/opt/local/bin/gpg-agent', // MacPorts
+                '/usr/local/bin/gpg-agent', // Mac GPG
+                '/sw/bin/gpg-agent',        // Fink
+                '/usr/bin/gpg-agent'
+            );
+        } else {
+            $agentFiles = array(
+                '/usr/bin/gpg-agent',
+                '/usr/local/bin/gpg-agent'
+            );
+        }
+
+        foreach ($agentFiles as $agentFile) {
+            if (is_executable($agentFile)) {
+                $agent = $agentFile;
+                break;
+            }
+        }
+
+        return $agent;
+    }
+
+    // }}
+    // {{ _getPinEntry()
+
+    private function _getPinEntry()
+    {
+        // Check if we're running directly from git or if we're using a
+        // PEAR-packaged version
+        $pinEntry = '@bin-dir@' . DIRECTORY_SEPARATOR . 'crypt-gpg-pinentry';
+
+        if ($pinEntry[0] === '@') {
+            $pinEntry = dirname(__FILE__) . DIRECTORY_SEPARATOR . '..'
+                . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'scripts'
+                . DIRECTORY_SEPARATOR . 'crypt-gpg-pinentry';
+        }
+
+        return $pinEntry;
+    }
+
+    // }}
     // {{{ _debug()
 
     /**
@@ -1672,7 +1984,7 @@
     private function _debug($text)
     {
         if ($this->_debug) {
-            if (array_key_exists('SHELL', $_ENV)) {
+            if (php_sapi_name() === 'cli') {
                 foreach (explode(PHP_EOL, $text) as $line) {
                     echo "Crypt_GPG DEBUG: ", $line, PHP_EOL;
                 }
@@ -1684,70 +1996,6 @@
                 }
             }
         }
-    }
-
-    // }}}
-    // {{{ _byteLength()
-
-    /**
-     * Gets the length of a string in bytes even if mbstring function
-     * overloading is turned on
-     *
-     * This is used for stream-based communication with the GPG subprocess.
-     *
-     * @param string $string the string for which to get the length.
-     *
-     * @return integer the length of the string in bytes.
-     *
-     * @see Crypt_GPG_Engine::$_mbStringOverload
-     */
-    private static function _byteLength($string)
-    {
-        if (self::$_mbStringOverload) {
-            return mb_strlen($string, '8bit');
-        }
-
-        return strlen((binary)$string);
-    }
-
-    // }}}
-    // {{{ _byteSubstring()
-
-    /**
-     * Gets the substring of a string in bytes even if mbstring function
-     * overloading is turned on
-     *
-     * This is used for stream-based communication with the GPG subprocess.
-     *
-     * @param string  $string the input string.
-     * @param integer $start  the starting point at which to get the substring.
-     * @param integer $length optional. The length of the substring.
-     *
-     * @return string the extracted part of the string. Unlike the default PHP
-     *                <kbd>substr()</kbd> function, the returned value is
-     *                always a string and never false.
-     *
-     * @see Crypt_GPG_Engine::$_mbStringOverload
-     */
-    private static function _byteSubstring($string, $start, $length = null)
-    {
-        if (self::$_mbStringOverload) {
-            if ($length === null) {
-                return mb_substr(
-                    $string,
-                    $start,
-                    self::_byteLength($string) - $start, '8bit'
-                );
-            }
-
-            return mb_substr($string, $start, $length, '8bit');
-        }
-
-        if ($length === null) {
-            return (string)substr((binary)$string, $start);
-        }
-
-        return (string)substr((binary)$string, $start, $length);
     }
 
     // }}}

--
Gitblit v1.9.1