cvs: pear /Crypt_GPG/GPG/Driver Php.php

[email protected] ("Michael Gauthier") Sun, 04 May 2008 04:26:23 -0000
Newsgroups php.pear.cvs
Message-ID <cvsgauthierm1209875183@cvsserver>
gauthierm		Sun May  4 04:26:23 2008 UTC

  Modified files:              
    /pear/Crypt_GPG/GPG/Driver	Php.php 
  Log:
  Refactor internal implementation of Crypt_GPG PHP driver.
   * fixes #13806 encryption of large strings
   * does the groundwork for #13586 - file based methods
   * improve speed
   * use PHP_EOL on GPG output for cross-platform portability
  These changes likely cause breakage on Windows. This will be fixed before release.
gauthierm-20080504042623.txt (text/plain, 32.1 KB)
http://cvs.php.net/viewvc.cgi/pear/Crypt_GPG/GPG/Driver/Php.php?r1=1.29&r2=1.30&diff_format=u
Index: pear/Crypt_GPG/GPG/Driver/Php.php
diff -u pear/Crypt_GPG/GPG/Driver/Php.php:1.29 pear/Crypt_GPG/GPG/Driver/Php.php:1.30
--- pear/Crypt_GPG/GPG/Driver/Php.php:1.29	Fri Apr 11 03:57:18 2008
+++ pear/Crypt_GPG/GPG/Driver/Php.php	Sun May  4 04:26:23 2008
@@ -32,7 +32,7 @@
  * @author    Michael Gauthier <[email protected]>
  * @copyright 2005-2008 silverorange
  * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
- * @version   CVS: $Id: Php.php,v 1.29 2008/04/11 03:57:18 gauthierm Exp $
+ * @version   CVS: $Id: Php.php,v 1.30 2008/05/04 04:26:23 gauthierm Exp $
  * @link      http://pear.php.net/package/Crypt_GPG
  * @link      http://www.gnupg.org/
  */
@@ -98,7 +98,17 @@
  */
 class Crypt_GPG_Driver_Php extends Crypt_GPG
 {
-    // {{{ class constants for IPC file descriptors
+    // {{{ constants
+
+    /**
+     * Size of data chunks that are sent to and retrieved from the IPC pipes.
+     *
+     * The current value was arrived at through benchmarking the encryption of
+     * a 1.9 MiB file over 30 seconds. This should correspond roughly to the
+     * system pipe buffer size.
+     *
+     */
+    const CHUNK_SIZE = 5100;
 
     /**
      * Standard input file descriptor. This is used to pass data to the GPG
@@ -224,8 +234,7 @@
      * Status output from the GPG subprocess
      *
      * Access this using {@link Crypt_GPG::_getStatus()}. If there is no status
-     * output, this will be a blank string. This gets the contents of the
-     * FD_STATUS file descriptor while the GPG subprocess is open.
+     * output, this will be a blank string.
      *
      * @var string
      * @see Crypt_GPG::_getStatus()
@@ -236,8 +245,7 @@
      * Error output from the GPG subprocess
      *
      * Access this using {@link Crypt_GPG::_getError()}. If there is no error
-     * output, this will be a blank string. This gets the contents of the
-     * FD_ERROR file descriptor while the GPG subprocess is open.
+     * output, this will be a blank string.
      *
      * @var string
      * @see Crypt_GPG::_getError()
@@ -268,6 +276,16 @@
      */
     private $_isDarwin = false;
 
+    /**
+     * Cached value indicating whether or not mbstring function overloading is
+     * on for strlen
+     *
+     * This is cached for optimal performance inside the I/O loop.
+     *
+     * @see Crypt_GPG_Driver_Php::_byteLength()
+     */
+    private static $_mbStringOverload = null;
+
     // }}}
     // {{{ __construct()
 
@@ -306,6 +324,12 @@
         $this->_isWin    = (strncmp(strtoupper(PHP_OS), 'WIN', 3) === 0);
         $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);
+        }
+
         if (array_key_exists('homedir', $options)) {
             $this->_homedir = (string)$options['homedir'];
         }
@@ -381,23 +405,13 @@
     public function importKey($data)
     {
         $args = array('--import');
-        $this->_openSubprocess($args);
 
-        fwrite($this->_pipes[self::FD_INPUT], $data);
-        $this->_closePipe(self::FD_INPUT);
-
-        if (!$this->_isWin) {
-            $status = $this->_getStatus();
-        }
+        $output = '';
 
+        $this->_openSubprocess($args);
+        $this->_process($data, $output);
         $code = $this->_closeSubprocess();
 
-        if ($this->_isWin) {
-            $status = $this->_getStatus();
-        }
-
-        $result = $this->_parseImportStatus($status);
-
         // ignore duplicate key import errors
         if ($code !== null && $code !== Crypt_GPG::ERROR_DUPLICATE_KEY) {
             switch ($code) {
@@ -414,6 +428,8 @@
             }
         }
 
+        $result = $this->_parseImportStatus($this->_getStatus());
+
         return $result;
     }
 
@@ -468,14 +484,13 @@
 
         $args[] = '--export ' . escapeshellarg($fingerprint);
 
-        $this->_openSubprocess($args);
-
         $keyData = '';
-        while (!feof($this->_pipes[self::FD_OUTPUT])) {
-            $keyData .= fread($this->_pipes[self::FD_OUTPUT], 1024);
-        }
+        $input = null;
 
+        $this->_openSubprocess($args);
+        $this->_process($input, $keyData);
         $code = $this->_closeSubprocess();
+
         if ($code !== null) {
             throw new Crypt_GPG_Exception(
                 'Unknown error exporting public key.', $code);
@@ -535,7 +550,9 @@
         );
 
         $this->_openSubprocess($args);
+        $this->_processNil();
         $code = $this->_closeSubprocess();
+
         if ($code !== null) {
             switch ($code) {
             case Crypt_GPG::ERROR_DELETE_PRIVATE_KEY:
@@ -595,7 +612,9 @@
         );
 
         $this->_openSubprocess($args);
+        $this->_processNil();
         $code = $this->_closeSubprocess();
+
         if ($code !== null) {
             switch ($code) {
             case Crypt_GPG::ERROR_KEY_NOT_FOUND:
@@ -651,25 +670,29 @@
             $args[] = '--list-secret-keys ' . escapeshellarg($keyId);
         }
 
+        $input  = null;
+        $output = '';
+
         $this->_openSubprocess($args);
+        $this->_process($input, $output);
+        $code = $this->_closeSubprocess();
+
+        // ignore not found key errors
+        if ($code !== null && $code !== Crypt_GPG::ERROR_KEY_NOT_FOUND) {
+            throw new Crypt_GPG_Exception(
+                'Unknown error getting keys.', $code);
+        }
 
         $privateKeyFingerprints = array();
-        while (!feof($this->_pipes[self::FD_OUTPUT])) {
-            $line    = fgets($this->_pipes[self::FD_OUTPUT]);
-            $lineExp = explode(':', $line);
 
+        $lines = explode(PHP_EOL, $output);
+        foreach ($lines as $line) {
+            $lineExp = explode(':', $line);
             if ($lineExp[0] == 'fpr') {
                 $privateKeyFingerprints[] = $lineExp[9];
             }
         }
 
-        $code = $this->_closeSubprocess();
-        // ignore not found key errors
-        if ($code !== null && $code !== Crypt_GPG::ERROR_KEY_NOT_FOUND) {
-            throw new Crypt_GPG_Exception(
-                'Unknown error getting keys.', $code);
-        }
-
         // get public keys
         array_pop($args);
 
@@ -679,15 +702,26 @@
             $args[] = '--list-public-keys ' . escapeshellarg($keyId);
         }
 
+        $input  = null;
+        $output = '';
+
         $this->_openSubprocess($args);
+        $this->_process($input, $output);
+        $code = $this->_closeSubprocess();
+
+        // ignore not found key errors
+        if ($code !== null && $code !== Crypt_GPG::ERROR_KEY_NOT_FOUND) {
+            throw new Crypt_GPG_Exception(
+                'Unknown error getting keys.', $code);
+        }
 
         $keys = array();
 
         $key    = null; // current key
         $subKey = null; // current sub-key
 
-        while (!feof($this->_pipes[self::FD_OUTPUT])) {
-            $line    = fgets($this->_pipes[self::FD_OUTPUT]);
+        $lines = explode(PHP_EOL, $output);
+        foreach ($lines as $line) {
             $lineExp = explode(':', $line);
 
             if ($lineExp[0] == 'pub') {
@@ -732,13 +766,6 @@
             $keys[] = $key;
         }
 
-        $code = $this->_closeSubprocess();
-        // ignore not found key errors
-        if ($code !== null && $code !== Crypt_GPG::ERROR_KEY_NOT_FOUND) {
-            throw new Crypt_GPG_Exception(
-                'Unknown error getting keys.', $code);
-        }
-
         return $keys;
     }
 
@@ -782,12 +809,22 @@
             '--list-keys ' . escapeshellarg($keyId)
         );
 
+        $fingerprint = null;
+        $input       = null;
+        $output      = '';
+
         $this->_openSubprocess($args);
+        $this->_process($input, $output);
+        $code = $this->_closeSubprocess();
 
-        $fingerprint = null;
+        // ignore not found key errors
+        if ($code !== null && $code !== Crypt_GPG::ERROR_KEY_NOT_FOUND) {
+            throw new Crypt_GPG_Exception(
+                'Unknown error getting key fingerprint.', $code);
+        }
 
-        while (!feof($this->_pipes[self::FD_OUTPUT])) {
-            $line = fgets($this->_pipes[self::FD_OUTPUT]);
+        $lines = explode(PHP_EOL, $output);
+        foreach ($lines as $line) {
             if (substr($line, 0, 3) == 'fpr') {
                 $lineExp     = explode(':', $line);
                 $fingerprint = $lineExp[9];
@@ -809,13 +846,6 @@
             }
         }
 
-        $code = $this->_closeSubprocess();
-        // ignore not found key errors
-        if ($code !== null && $code !== Crypt_GPG::ERROR_KEY_NOT_FOUND) {
-            throw new Crypt_GPG_Exception(
-                'Unknown error getting key fingerprint.', $code);
-        }
-
         return $fingerprint;
     }
 
@@ -852,7 +882,8 @@
     public function encrypt($keyId, $data, $armor = true)
     {
         $data = (string)$data;
-        $encryptedData = null;
+
+        $encryptedData = '';
 
         $args = array('--recipient ' . escapeshellarg($keyId));
 
@@ -863,16 +894,7 @@
         $args[] = '--encrypt';
 
         $this->_openSubprocess($args);
-
-        fwrite($this->_pipes[self::FD_INPUT], $data);
-        $this->_closePipe(self::FD_INPUT);
-
-        $encryptedData = '';
-
-        while (!feof($this->_pipes[self::FD_OUTPUT])) {
-            $encryptedData .= fread($this->_pipes[self::FD_OUTPUT], 1024);
-        }
-
+        $this->_process($data, $encryptedData);
         $code = $this->_closeSubprocess();
 
         if ($code !== null) {
@@ -948,11 +970,12 @@
 
         $args[] = '--decrypt';
 
-        $this->_openSubprocess($args);
-
-        $data = $this->_processWithPassphrase($encryptedData, $passphrase);
+        $data = '';
 
+        $this->_openSubprocess($args);
+        $this->_processWithMessage($encryptedData, $data, $passphrase);
         $code = $this->_closeSubprocess();
+
         if ($code !== null) {
             switch ($code) {
             case Crypt_GPG::ERROR_KEY_NOT_FOUND:
@@ -1074,11 +1097,12 @@
             break;
         }
 
-        $this->_openSubprocess($args);
-
-        $signedData = $this->_processWithPassphrase($data, $passphrase);
+        $signedData = '';
 
+        $this->_openSubprocess($args);
+        $this->_processWithMessage($data, $signedData, $passphrase);
         $code = $this->_closeSubprocess();
+
         if ($code !== null) {
             switch ($code) {
             case Crypt_GPG::ERROR_KEY_NOT_FOUND:
@@ -1165,29 +1189,16 @@
 
         if ($signature == '') {
             // signed or clearsigned data
-
-            // write the signed data to the GPG subprocess in stdin
-            fwrite($this->_pipes[self::FD_INPUT], $signedData);
-            $this->_closePipe(self::FD_INPUT);
+            $output = '';
+            $this->_process($signedData, $output);
         } else {
             // detached signature
-
-            // write signature data to stdin
-            fwrite($this->_pipes[self::FD_INPUT], $signature);
-            $this->_closePipe(self::FD_INPUT);
-
-            if (!$this->_isWin) {
-                // write signed data to fd 4
-                fwrite($this->_pipes[self::FD_MESSAGE], $signedData);
-                $this->_closePipe(self::FD_MESSAGE);
-            }
-        }
-
-        if (!$this->_isWin) {
-            $status = $this->_getStatus();
+            $output = '';
+            $this->_processWithMessage($signature, $output, $signedData);
         }
 
         $code = $this->_closeSubprocess();
+
         if ($code !== null) {
             switch ($code) {
             case Crypt_GPG::ERROR_NO_DATA:
@@ -1203,12 +1214,8 @@
             }
         }
 
-        if ($this->_isWin) {
-            $status = $this->_getStatus();
-        }
-
         // get the response information
-        $resp = $this->_parseVerifyStatus($status);
+        $resp = $this->_parseVerifyStatus($this->_getStatus());
 
         // create an object to return, and fill it with data
         $sig = new Crypt_GPG_Signature();
@@ -1344,42 +1351,384 @@
     }
 
     // }}}
-    // {{{ _processWithPassphrase()
+    // {{{ _processNil()
 
     /**
-     * Performs internal operations requiring a passphrase
+     * Helper method for processing GPG operations that do not require any
+     * input or output
      *
-     * Performs operations that require a passphrase. For example,
-     * decryption, signigning and clearsigning.
+     * @return void
      *
-     * @param string $data       the data to process. If there is no data to
-     *                           process, use null.
-     * @param string $passphrase the passphrase of the user's private key.
+     * @see Crypt_GPG_Driver_Php::_process()
+     */
+    private function _processNil()
+    {
+        $input  = null;
+        $output = '';
+        $this->_process($input, $output);
+    }
+
+    // }}}
+    // {{{ _process()
+
+    /**
+     * This is a helper method used for processing the majority of GPG
+     * operations that require input or output but do not require a passphrase
+     * or detached signature data
      *
-     * @return string the processed data.
+     * @param string|resource $input   the input source. This is data to send
+     *                                 to GPG. If there is no data to send to
+     *                                 GPG, specify null.
+     * @param string|resource $output  the output location. This is where the
+     *                                 output of GPG is sent.
      *
-     * @sensitive $passphrase
+     * @return void
+     *
+     * @see Crypt_GPG_Driver_Php::_processWithMessage()
      */
-    private function _processWithPassphrase($data, $passphrase)
+    private function _process(&$input, &$output)
     {
-        $result = null;
+        $message = null;
+        $this->_processWithMessage($input, $output, $message);
+    }
 
-        if ($data !== null) {
-            fwrite($this->_pipes[self::FD_INPUT], $data);
-            $this->_closePipe(self::FD_INPUT);
+    // }}}
+    // {{{ _processWithMessage()
+
+    /**
+     * This is a helper method for processing GPG operations that require a
+     * passphrase or detached signature data in addition to standard input and
+     * output
+     *
+     * @param string|resource $input   the input source. This is data to send
+     *                                 to GPG. If there is no data to send to
+     *                                 GPG, specify null.
+     * @param string|resource $output  the output location. This is where the
+     *                                 output of GPG is sent.
+     * @param string|resource $message the message input source. Things like
+     *                                 passphrases and detached signature data
+     *                                 should be specified here. If there is no
+     *                                 message input, specify null.
+     *
+     * @return void
+     *
+     * @sensitive $message
+     *
+     * @see Crypt_GPG_Driver_Php::_processCore()
+     */
+    private function _processWithMessage(&$input, &$output, &$message)
+    {
+        $this->_status = '';
+        $this->_error  = '';
+        $this->_processCore($input, $output, $this->_error, $this->_status,
+            $message);
+    }
+
+    // }}}
+    // {{{ _processCore()
+
+    /**
+     * Performs internal streaming operations for the subprocess using either
+     * strings or streams as input/output points
+     *
+     * All arguments may either be strings or streams. Output from this method
+     * is stored in in the strings or streams passed by reference in the
+     * appropriate parameters. All parameters are passed by reference
+     *
+     * The implementation of this method is verbose mainly for performance
+     * reasons. Adding streams to a lookup array and looping the array inside
+     * the main I/O loop would be siginficantly slower for large streams.
+     *
+     * @param string|resource $input   the input source. This is data to send
+     *                                 to GPG. If there is no data to send to
+     *                                 GPG, specify null.
+     * @param string|resource $output  the output location. This is where the
+     *                                 output of GPG is sent.
+     * @param string|resource $error   the error output location. This is where
+     *                                 the error output of GPG is sent.
+     * @param string|resource $status  the status output location. This is
+     *                                 where the status output of GPG is sent.
+     * @param string|resource $message the message input source. Things like
+     *                                 passphrases and detached signature data
+     *                                 should be specified here. If there is no
+     *                                 message input, specify null.
+     *
+     * @return void
+     *
+     * @sensitive $message
+     */
+    private function _processCore(&$input, &$output, &$error, &$status,
+        &$message)
+    {
+        // buffer positions for string input
+        if (is_string($input)) {
+            $inputPosition   = 0;
+            $inputByteLength = self::_byteLength($input);
+        }
+
+        if (is_string($message)) {
+            $messagePosition   = 0;
+            $messageByteLength = self::_byteLength($message);
+        }
+
+        $pipeInput   = $this->_pipes[self::FD_INPUT];
+        $pipeOutput  = $this->_pipes[self::FD_OUTPUT];
+        $pipeError   = $this->_pipes[self::FD_ERROR];
+        $pipeStatus  = $this->_pipes[self::FD_STATUS];
+        $pipeMessage = $this->_pipes[self::FD_MESSAGE];
+
+        stream_set_blocking($pipeInput, 0);
+        stream_set_blocking($pipeMessage, 0);
+        stream_set_blocking($pipeError, 0);
+        stream_set_blocking($pipeMessage, 0);
+        stream_set_blocking($pipeOutput, 0);
+
+        // set up input stream array
+        $openInputStreams = array(
+            'pipe' . self::FD_OUTPUT => $pipeOutput,
+            'pipe' . self::FD_ERROR  => $pipeError,
+            'pipe' . self::FD_STATUS => $pipeStatus
+        );
+
+        if (is_resource($input)) {
+            $openInputStreams['input'] = $input;
         }
 
-        if (!$this->_isWin && $passphrase !== null) {
-            fwrite($this->_pipes[self::FD_MESSAGE], $passphrase);
-            $this->_closePipe(self::FD_MESSAGE);
+        if (is_resource($message)) {
+            $openInputStreams['message'] = $message;
         }
 
-        $result = '';
-        while (!feof($this->_pipes[self::FD_OUTPUT])) {
-            $result .= fread($this->_pipes[self::FD_OUTPUT], 1024);
+        // set up output stream array
+        $openOutputStreams = array();
+
+        if (isset($input)) {
+            $openOutputStreams['pipe' . self::FD_INPUT] = $pipeInput;
         }
 
-        return $result;
+        if (isset($message)) {
+            $openOutputStreams['pipe' . self::FD_MESSAGE] = $pipeMessage;
+        }
+
+        if (is_resource($output)) {
+            $openOutputStreams['output'] = $output;
+        }
+
+        if (is_resource($error)) {
+            $openOutputStreams['error'] = $error;
+        }
+
+        if (is_resource($status)) {
+            $openOutputStreams['status'] = $status;
+        }
+
+        // loop until all streams are closed
+        while (count($openInputStreams) > 0 || count($openOutputStreams) > 0) {
+
+            $inputStreams     = $openInputStreams;
+            $outputStreams    = $openOutputStreams;
+            $exceptionStreams = null;
+
+            $ready = stream_select($inputStreams, $outputStreams,
+                $exceptionStreams, null);
+
+            if ($ready === false) {
+                throw new Crypt_GPG_Exception(
+                    'Error selecting stream for communication with GPG ' .
+                        'subprocess.');
+            }
+
+            if ($ready > 0) {
+
+                $idle = true;
+
+                // input
+                if (in_array($pipeInput, $outputStreams)) {
+                    if (is_string($input)) {
+                        $idle = false;
+                        $chunk = substr($input, $inputPosition,
+                            self::CHUNK_SIZE);
+
+                        $length = self::_byteLength($chunk);
+                        fwrite($pipeInput, $chunk, $length);
+                        $inputPosition += $length;
+                    } else {
+                        if (in_array($input, $inputStreams)) {
+                            $idle = false;
+                            $chunk = fread($input, self::CHUNK_SIZE);
+                            fwrite($pipeInput, $chunk,
+                                self::_byteLength($chunk));
+                        }
+                    }
+                }
+
+                // message
+                if (in_array($pipeMessage, $outputStreams)) {
+                    if (is_string($message)) {
+                        $idle = false;
+                        $chunk = substr($message, $messagePosition,
+                            self::CHUNK_SIZE);
+
+                        $length = self::_byteLength($chunk);
+                        fwrite($pipeMessage, $chunk, $length);
+                        $messagePosition += $length;
+                    } else {
+                        if (in_array($message, $inputStreams)) {
+                            $idle = false;
+                            $chunk = fread($message, self::CHUNK_SIZE);
+                            fwrite($pipeMessage, $chunk,
+                                self::_byteLength($chunk));
+                        }
+                    }
+                }
+
+                // output
+                if (in_array($pipeOutput, $inputStreams)) {
+                    if (is_string($output)) {
+                        $idle = false;
+                        $chunk = fread($pipeOutput, self::CHUNK_SIZE);
+                        $output .= $chunk;
+                    } else {
+                        if (in_array($output, $outputStreams)) {
+                            $idle = false;
+                            $chunk = fread($pipeOutput, self::CHUNK_SIZE);
+                            fwrite($output, $chunk, self::_byteLength($chunk));
+                        }
+                    }
+                }
+
+                // error
+                if (in_array($pipeError, $inputStreams)) {
+                    if (is_string($error)) {
+                        $idle = false;
+                        $chunk = fread($pipeError, self::CHUNK_SIZE);
+                        $error .= $chunk;
+                    } else {
+                        if (in_array($error, $errorStreams)) {
+                            $idle = false;
+                            $chunk = fread($pipeError, self::CHUNK_SIZE);
+                            fwrite($error, $chunk, self::_byteLength($chunk));
+                        }
+                    }
+                }
+
+                // status
+                if (in_array($pipeStatus, $inputStreams)) {
+                    if (is_string($status)) {
+                        $idle = false;
+                        $chunk = fread($pipeStatus, self::CHUNK_SIZE);
+                        $status .= $chunk;
+                    } else {
+                        if (in_array($status, $statusStreams)) {
+                            $idle = false;
+                            $chunk = fread($pipeStatus, self::CHUNK_SIZE);
+                            fwrite($status, $chunk, self::_byteSize($chunk));
+                        }
+                    }
+                }
+
+            } // end if ready
+
+            if ($idle) {
+                // prevent 100% CPU usage if we're waiting on data. Note,
+                // usleep is much faster than sleep here.
+                usleep(0);
+            } else {
+                // input
+                if (is_string($input)) {
+                    if ($inputPosition >= $inputByteLength) {
+                        $this->_closePipe(self::FD_INPUT);
+                        unset($openOutputStreams['pipe' . self::FD_INPUT]);
+                    }
+                } else {
+                    if (in_array($input, $openInputStreams) && feof($input)) {
+                        fclose($input);
+                        unset($openInputStreams['input']);
+                        $this->_closePipe(self::FD_INPUT);
+                        unset($openOutputStreams['pipe' . self::FD_INPUT]);
+                    }
+                }
+
+                // message
+                if (is_string($message)) {
+                    if ($messagePosition >= $messageByteLength) {
+                        $this->_closePipe(self::FD_MESSAGE);
+                        unset($openOutputStreams['pipe' . self::FD_MESSAGE]);
+                    }
+                } else {
+                    if (   in_array($message, $openInputStreams)
+                        && feof($message)
+                    ) {
+                        fclose($message);
+                        unset($openInputStreams['message']);
+                        $this->_closePipe(self::FD_MESSAGE);
+                        unset($openOutputStreams['pipe' . self::FD_MESSAGE]);
+                    }
+                }
+
+                // output
+                if (   in_array($pipeOutput, $openInputStreams)
+                    && feof($pipeOutput)
+                ) {
+                    $this->_closePipe(self::FD_OUTPUT);
+                    unset($openInputStreams['pipe' . self::FD_OUTPUT]);
+                    if (is_resource($output)) {
+                        fflush($output);
+                        fclose($output);
+                        unset($openOutputStreams['output']);
+                    }
+                }
+
+                // error
+                if (   in_array($pipeError, $openInputStreams)
+                    && feof($pipeError)
+                ) {
+                    $this->_closePipe(self::FD_ERROR);
+                    unset($openInputStreams['pipe' . self::FD_ERROR]);
+                    if (is_resource($error)) {
+                        fflush($error);
+                        fclose($error);
+                        unset($openOutputStreams['error']);
+                    }
+                }
+
+                // status
+                if (   in_array($pipeStatus, $openInputStreams)
+                    && feof($pipeStatus)
+                ) {
+                    $this->_closePipe(self::FD_STATUS);
+                    unset($openInputStreams['pipe' . self::FD_STATUS]);
+                    if (is_resource($status)) {
+                        fflush($status);
+                        fclose($status);
+                        unset($openOutputStreams['status']);
+                    }
+                }
+            } // end if not idle
+        } // end loop while streams are open
+    }
+
+    // }}}
+    // {{{ _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_Driver_Php::$_mbStringOverload
+     */
+    private static function _byteLength($string)
+    {
+        if (self::$_mbStringOverload) {
+            return mb_strlen($string, '8bit');
+        }
+
+        return strlen((binary)$string);
     }
 
     // }}}
@@ -1411,10 +1760,12 @@
             $env = $_ENV;
         }
 
+        $this->_error  = '';
+        $this->_status = '';
+
         $command = $this->_gpgBinary;
 
         if ($this->_isWin) {
-            $this->_status = '';
             $this->_createTempFile(self::FD_STATUS);
             array_unshift($args, '--status-file ' .
                 escapeshellarg($this->_tempFiles[self::FD_STATUS]));
@@ -1454,7 +1805,7 @@
         $this->_debug($command);
 
         $this->_process = proc_open($command, $descriptorSpec, $this->_pipes,
-            null, $env);
+            null, $env, array('binary_pipes' => true));
 
         if (!is_resource($this->_process)) {
             throw new Crypt_GPG_OpenSubprocessException(
@@ -1485,11 +1836,6 @@
 
         if (is_resource($this->_process)) {
 
-            $error = $this->_getError();
-            if (!$this->_isWin) {
-                $status = $this->_getStatus();
-            }
-
             // close remaining open pipes
             foreach (array_keys($this->_openPipes) as $pipeNumber) {
                 $this->_closePipe($pipeNumber);
@@ -1497,15 +1843,14 @@
 
             $exitCode = proc_close($this->_process);
 
-            if ($this->_isWin) {
-                $status = $this->_getStatus();
-            }
-
             // delete any remaining temp files
             foreach (array_keys($this->_tempFiles) as $fileNumber) {
                 $this->_deleteTempFile($fileNumber);
             }
 
+            $error  = $this->_getError();
+            $status = $this->_getStatus();
+
             if ($exitCode != 0) {
                 $this->_debug('Subprocess returned an unexpected exit code: ' .
                     $exitCode);
@@ -1519,11 +1864,6 @@
             $this->_process   = null;
             $this->_pipes     = array();
             $this->_tempFiles = array();
-            $this->_error     = '';
-
-            if (!$this->_isWin) {
-                $this->_status = '';
-            }
         }
 
         return $return;
@@ -1573,7 +1913,7 @@
     {
         $resp = array();
 
-        foreach (explode("\n", $status) as $line) {
+        foreach (explode(PHP_EOL, $status) as $line) {
             $line = rtrim($line);
             if (substr($line, 0, 9) == '[GNUPG:] ') {
                 $line    = substr($line, 9);
@@ -1616,7 +1956,7 @@
     {
         $result = array();
 
-        foreach (explode("\n", $status) as $line) {
+        foreach (explode(PHP_EOL, $status) as $line) {
             $line = rtrim($line);
             if (substr($line, 0, 9) == '[GNUPG:] ') {
                 $line    = substr($line, 9);
@@ -1655,10 +1995,12 @@
      * @return string the status output from the open GPG subprocess. If there
      *                is no status output or there is no open GPG subprocess,
      *                a blank string is returned.
+     *
+     * @see Crypt_GPG_Driver_Php::_processWithMessage()
      */
     private function _getStatus()
     {
-        if ($this->_status == '') {
+/*        if ($this->_status == '') {
             if ($this->_isWin) {
                 $this->_status = $this->_readStatusFile();
             } else {
@@ -1669,7 +2011,7 @@
                     }
                 }
             }
-        }
+        }*/
 
         return $this->_status;
     }
@@ -1680,22 +2022,14 @@
     /**
      * Gets the error output from the GPG subprocess
      *
-     * This helper method caches the content of the error file descriptor while
-     * the GPG subprocess is open.
-     *
      * @return string the error output from the open GPG subprocess. If there
      *                is no error output or there is no open GPG subprocess,
      *                a blank string is returned.
+     *
+     * @see Crypt_GPG_Driver_Php::_processWithMessage()
      */
     private function _getError()
     {
-        if ($this->_error == '' &&
-            array_key_exists(self::FD_ERROR, $this->_openPipes)) {
-            while (!feof($this->_pipes[self::FD_ERROR])) {
-                $this->_error .= fread($this->_pipes[self::FD_ERROR], 8192);
-            }
-        }
-
         return $this->_error;
     }
 
@@ -1725,7 +2059,7 @@
     {
         $errorCode = Crypt_GPG::ERROR_UNKNOWN;
 
-        $status = explode("\n", $status);
+        $status = explode(PHP_EOL, $status);
         $needPassphrase = false;
         foreach ($status as $line) {
             $tokens = explode(' ', trim($line));
@@ -1964,8 +2298,8 @@
     private function _debug($text)
     {
         if ($this->debug) {
-            foreach (explode("\n", $text) as $line) {
-                echo "Crypt_GPG DEBUG: ", $line, "\n";
+            foreach (explode(PHP_EOL, $text) as $line) {
+                echo "Crypt_GPG DEBUG: ", $line, PHP_EOL;
             }
         }
     }