Added draft code to handle S/MIME through GnuPG #259

This commit is contained in:
the-djmaze 2024-02-12 13:36:12 +01:00
parent 9cceafcb3e
commit 9933a0e83e
8 changed files with 1695 additions and 372 deletions

View file

@ -14,6 +14,7 @@ namespace MailSo\Mail;
use MailSo\Base\Utils;
use MailSo\Imap\Enumerations\FetchType;
use MailSo\Mime\Enumerations\Header as MimeHeader;
use SnappyMail\GPG\PGP as GPG;
/**
* @category MailSo
@ -379,8 +380,8 @@ class Message implements \JsonSerializable
if (\str_contains($sText, '-----BEGIN PGP MESSAGE-----')) {
$keyIds = [];
if (\SnappyMail\PGP\GPG::isSupported()) {
$GPG = new \SnappyMail\PGP\GPG('');
if (GPG::isSupported()) {
$GPG = new GPG('');
$keyIds = $GPG->getEncryptedMessageKeys($sText);
}
$oMessage->pgpEncrypted = [

View file

@ -3,6 +3,8 @@
namespace RainLoop\Actions;
use SnappyMail\PGP\Backup;
use SnappyMail\PGP\Keyservers;
use SnappyMail\PGP\GnuPG;
trait Pgp
{
@ -14,7 +16,7 @@ trait Pgp
{
$result = [];
$keys = \SnappyMail\PGP\Backup::getKeys();
$keys = Backup::getKeys();
foreach ($keys['public'] as $key) {
$result[] = $key['value'];
}
@ -38,7 +40,7 @@ trait Pgp
public function DoSearchPGPKey() : array
{
$result = \SnappyMail\PGP\Keyservers::get(
$result = Keyservers::get(
$this->GetActionParam('query', '')
);
return $this->DefaultResponse($result ?: false);
@ -47,7 +49,7 @@ trait Pgp
/**
* @throws \MailSo\RuntimeException
*/
public function GnuPG() : ?\SnappyMail\PGP\GnuPG
public function GnuPG() : ?GnuPG
{
$oAccount = $this->getMainAccountFromToken();
if (!$oAccount) {
@ -103,7 +105,7 @@ trait Pgp
}
}
return \SnappyMail\PGP\GnuPG::getInstance($homedir);
return GnuPG::getInstance($homedir);
}
public function DoGnupgDecrypt() : array
@ -203,14 +205,14 @@ trait Pgp
$sEmail = $aMatch[0];
}
if ($sEmail) {
$aKeys = \SnappyMail\PGP\Keyservers::index($sEmail);
$aKeys = Keyservers::index($sEmail);
if ($aKeys) {
$sKeyId = $aKeys[0]['keyid'];
}
}
}
if ($sKeyId) {
$sKey = \SnappyMail\PGP\Keyservers::get($sKeyId);
$sKey = Keyservers::get($sKeyId);
}
} catch (\Throwable $e) {
// ignore
@ -237,7 +239,7 @@ trait Pgp
*/
public function DoGetStoredPGPKeys() : array
{
return $this->DefaultResponse(\SnappyMail\PGP\Backup::getKeys());
return $this->DefaultResponse(Backup::getKeys());
}
/**

View file

@ -0,0 +1,489 @@
<?php
/**
* GnuPG
*/
namespace SnappyMail\GPG;
abstract class Base
{
const
// @see https://pear.php.net/bugs/bug.php?id=21077
CHUNK_SIZE = 65536,
// This is used to pass data to the GPG process.
FD_INPUT = 0,
// This is used to receive normal output from the GPG process.
FD_OUTPUT = 1,
// This is used to receive error output from the GPG process.
FD_ERROR = 2,
/**
* GPG status output file descriptor. The status file descriptor outputs
* detailed information for many GPG commands. See the second section of
* the file <b>doc/DETAILS</b> in the
* {@link http://www.gnupg.org/download/ GPG package} for a detailed
* description of GPG's status output.
*/
FD_STATUS = 3,
// This is used for methods requiring passphrases.
FD_COMMAND = 4,
// This is used for passing signed data when verifying a detached signature.
FD_MESSAGE = 5;
public
$strict = false;
protected
$binary,
$version = '2.0',
$passphrases = [],
$signKeys = [],
$encryptKeys = [],
$decryptKeys = [],
// Create PEM encoded output
$armor = true,
$_input,
$_output,
$proc_resource,
$_openPipes, // ProcPipes
// https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration-Options.html
$options = [
'homedir' => '',
'keyring' => '',
'digest-algo' => '',
'cipher-algo' => '',
];
function __construct(string $homedir)
{
$this->options['homedir'] = \rtrim($homedir, '/\\');
}
function __destruct()
{
$this->proc_close();
$gpgconf = static::findBinary('gpgconf');
if ($gpgconf) {
$env = ['GNUPGHOME' => $this->options['homedir']];
$pipes = [];
if ($process = \proc_open($gpgconf . ' --kill gpg-agent --homedir ' . \escapeshellarg($this->options['homedir']), [], $pipes, null, $env)) {
\proc_close($process);
}
}
}
public static function isSupported() : bool
{
return \is_callable('proc_open');
}
protected function listDecryptKeys(/*string|resource*/ $input, /*string|resource*/ $output = null)
{
return false;
}
/**
* Decrypts a given text
*/
public function decrypt(string $text) /*: string|false */
{
return false;
}
/**
* Decrypts a given file
*/
public function decryptFile(string $filename) /*: string|false */
{
return false;
}
/**
* Decrypts a given stream
*/
public function decryptStream($fp, /*string|resource*/ $output = null) /*: string|false*/
{
return false;
}
/**
* Decrypts and verifies a given text
*/
public function decryptVerify(string $text, string &$plaintext) /*: array|false*/
{
return false;
}
/**
* Decrypts and verifies a given file
*/
public function decryptVerifyFile(string $filename, string &$plaintext) /*: array|false*/
{
return false;
}
/**
* Encrypts a given text
*/
public function encrypt(string $plaintext, /*string|resource*/ $output = null) /*: string|false*/
{
return false;
}
/**
* Encrypts a given text
*/
public function encryptFile(string $filename, /*string|resource*/ $output = null) /*: string|false*/
{
return false;
}
public function encryptStream(/*resource*/ $fp, /*string|resource*/ $output = null) /*: string|false*/
{
return false;
}
/**
* Encrypts and signs a given text
*/
public function encryptSign(string $plaintext) /*: string|false*/
{
return false;
}
/**
* Encrypts and signs a given text
*/
public function encryptSignFile(string $filename) /*: string|false*/
{
return false;
}
/**
* Exports a public key
*/
public function export(string $fingerprint) /*: string|false*/
{
return false;
}
/**
* Exports a private key
*/
public function exportPrivateKey(string $fingerprint) /*: string|false*/
{
return false;
}
/**
* Imports a key
*/
public function import(string $keydata) /*: array|false*/
{
return false;
}
/**
* Imports a key
*/
public function importFile(string $filename) /*: array|false*/
{
return false;
}
public function deleteKey(string $keyId, bool $private)
{
return false;
}
/**
* Returns an array with information about all keys that matches the given pattern
*/
public function keyInfo(string $pattern, int $private = 0) : array
{
return [];
}
/**
* Sets the mode for error_reporting
* GNUPG_ERROR_WARNING, GNUPG_ERROR_EXCEPTION and GNUPG_ERROR_SILENT.
* By default GNUPG_ERROR_SILENT is used.
*/
public function setErrorMode(int $errormode) : void
{
}
/**
* Signs a given text
*/
public function sign(string $plaintext, /*string|resource*/ $output = null) /*: string|false*/
{
return false;
}
/**
* Signs a given file
*/
public function signFile(string $filename, /*string|resource*/ $output = null) /*: string|false*/
{
return false;
}
/**
* Signs a given file
*/
public function signStream($fp, /*string|resource*/ $output = null) /*: array|false*/
{
return false;
}
/**
* Verifies a signed text
*/
public function verify(string $signed_text, string $signature, string &$plaintext = null) /*: array|false*/
{
return false;
}
/**
* Verifies a signed file
*/
public function verifyFile(string $filename, string $signature, string &$plaintext = null) /*: array|false*/
{
return false;
}
/**
* Verifies a signed file
*/
public function verifyStream($fp, string $signature, string &$plaintext = null) /*: array|false*/
{
return false;
}
public function getEncryptedMessageKeys(/*string|resource*/ $data) : array
{
return [];
}
/******************************************************************
* Defined methods
******************************************************************/
/**
* Add a key for decryption
*/
public function addDecryptKey(string $fingerprint,
#[\SensitiveParameter]
string $passphrase
) : bool
{
$this->decryptKeys[$fingerprint] = $passphrase;
// $this->decryptKeys[\substr($fingerprint, -16)] = $passphrase;
return true;
}
/**
* Add a key for encryption
*/
public function addEncryptKey(string $fingerprint) : bool
{
$this->encryptKeys[$fingerprint] = 1;
return true;
}
/**
* Add a key for signing
*/
public function addSignKey(string $fingerprint,
#[\SensitiveParameter]
string $passphrase
) : bool
{
$this->signKeys[$fingerprint] = $passphrase;
// $this->signKeys[\substr($fingerprint, -16)] = $passphrase;
return false;
}
/**
* Removes all keys which were set for decryption before
*/
public function clearDecryptKeys() : bool
{
$this->decryptKeys = [];
return true;
}
/**
* Removes all keys which were set for encryption before
*/
public function clearEncryptKeys() : bool
{
$this->encryptKeys = [];
return true;
}
/**
* Removes all keys which were set for signing before
*/
public function clearSignKeys() : bool
{
$this->signKeys = [];
return true;
}
/**
* Returns the engine info
*/
public function getEngineInfo() : array
{
return [
'protocol' => null,
'file_name' => $this->binary,
'home_dir' => $this->options['homedir'],
'version' => $this->version
];
}
public function addPassphrase($keyId,
#[\SensitiveParameter]
$passphrase
)
{
$this->passphrases[$keyId] = $passphrase;
return $this;
}
/**
* Toggle the armored output
*/
public function setArmor(int $armor = 1) : bool
{
$this->armor = !!$armor;
return true;
}
protected function _debug(string $msg) : void
{
\SnappyMail\Log::debug('GPG', $msg);
}
protected function setInput(&$input) : void
{
if (\is_resource($input)) {
// https://github.com/the-djmaze/snappymail/issues/331
// $meta['stream_type'] == MEMORY or $meta['wrapper_data'] == MailSo\Base\StreamWrappers\Literal
$meta = \stream_get_meta_data($input);
if (!\in_array($meta['stream_type'], ['STDIO', 'TEMP'])) {
/*
$fp = \fopen('php://temp');
\stream_copy_to_stream($input, $fp);
$input = $fp;
*/
$input = \stream_get_contents($input);
}
}
$this->_input =& $input;
}
protected function setOutput($output)/* : resource|false*/
{
$fclose = false;
if ($output && !\is_resource($output)) {
$output = \fopen($output, 'wb');
if (!$output) {
throw new \Exception("Could not open file '{$filename}'");
}
$fclose = $output;
}
$this->_output = $output;
return $fclose;
}
public function agent()
{
// $home = \escapeshellarg($this->options['homedir']);
// echo `gpg-agent --daemon --homedir $home 2>&1`;
}
protected function getPassphrase($key)
{
$passphrase = '';
$keyIdLength = \strlen($key);
if ($keyIdLength && !empty($_ENV['PINENTRY_USER_DATA'])) {
$passphrases = \json_decode($_ENV['PINENTRY_USER_DATA'], true);
foreach ($passphrases as $keyId => $pass) {
$length = \min($keyIdLength, \strlen($keyId));
if (\substr($keyId, -$length) === \substr($key, -$length)) {
return $pass;
}
}
}
// throw new \Exception("Passphrase not found for {$key}");
return '';
}
protected function proc_close() : int
{
$exitCode = 0;
// clear PINs from environment if they were set
$_ENV['PINENTRY_USER_DATA'] = null;
if (\is_resource($this->proc_resource)) {
$this->_debug('CLOSING SUBPROCESS');
// close remaining open pipes
$this->_openPipes->closeAll();
$status = \proc_get_status($this->proc_resource);
$exitCode = \proc_close($this->proc_resource);
$this->proc_resource = null;
// proc_close() can return -1 in some cases,
// get the real exit code from the process status
if ($exitCode < 0 && $status && !$status['running']) {
$exitCode = $status['exitcode'];
}
if ($exitCode > 0) {
$this->_debug('=> subprocess returned an unexpected exit code: ' . $exitCode);
}
}
return $exitCode;
}
protected static function findBinary($name) : ?string
{
$binary = \trim((string) `which $name`);
if ($binary && \is_executable($binary)) {
return $binary;
}
$locations = \array_filter([
'/sw/bin/',
'/usr/bin/',
'/usr/local/bin/',
'/opt/local/bin/',
'/run/current-system/sw/bin/'
], '\RainLoop\Utils::inOpenBasedir');
foreach ($locations as $location) {
if (\is_executable($location . $name)) {
return $location . $name;
}
}
return null;
}
}

View file

@ -5,74 +5,29 @@
* It does not support gpg < v2.2.5 as they are from before 2018
*/
namespace SnappyMail\PGP;
namespace SnappyMail\GPG;
class GPG
class PGP extends Base
{
const
// @see https://pear.php.net/bugs/bug.php?id=21077
CHUNK_SIZE = 65536,
// This is used to pass data to the GPG process.
FD_INPUT = 0,
// This is used to receive normal output from the GPG process.
FD_OUTPUT = 1,
// This is used to receive error output from the GPG process.
FD_ERROR = 2,
/**
* GPG status output file descriptor. The status file descriptor outputs
* detailed information for many GPG commands. See the second section of
* the file <b>doc/DETAILS</b> in the
* {@link http://www.gnupg.org/download/ GPG package} for a detailed
* description of GPG's status output.
*/
FD_STATUS = 3,
// This is used for methods requiring passphrases.
FD_COMMAND = 4,
// This is used for passing signed data when verifying a detached signature.
FD_MESSAGE = 5;
public
$strict = false;
private
$_message,
$_input,
$_output,
$signKeys = [],
$encryptKeys = [],
$decryptKeys = [];
private
$binary,
$version = '2.0',
$ciphers = [],
$digests = [],
$curves = [],
$pubkey_types = [],
$compressions = [],
$passphrases = [],
$proc_resource,
$_openPipes, // GpgProcPipes
$armor = true,
$signmode = 2,
$signmode = 2;
protected
// https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration-Options.html
$options = [
'homedir' => '',
'keyring' => '',
'secret-keyring' => '',
'digest-algo' => '',
'cipher-algo' => '',
'secret-keyring' => '',
/*
2 = ZLIB (GnuPG, default)
1 = ZIP (PGP)
@ -83,7 +38,7 @@ class GPG
function __construct(string $homedir)
{
$this->options['homedir'] = \rtrim($homedir, '/\\');
parent::__construct($homedir);
// the random seed file makes subsequent actions faster so only disable it if we have to.
if ($this->options['homedir'] && !\is_writable($this->options['homedir'])) {
@ -114,85 +69,9 @@ class GPG
}
}
function __destruct()
{
$this->proc_close();
$gpgconf = static::findBinary('gpgconf');
if ($gpgconf) {
$env = ['GNUPGHOME' => $this->options['homedir']];
$pipes = [];
if ($process = \proc_open($gpgconf . ' --kill gpg-agent --homedir ' . \escapeshellarg($this->options['homedir']), [], $pipes, null, $env)) {
\proc_close($process);
}
}
}
public static function isSupported() : bool
{
return \is_callable('proc_open') && static::findBinary('gpg');
}
/**
* Add a key for decryption
*/
public function addDecryptKey(string $fingerprint,
#[\SensitiveParameter]
string $passphrase
) : bool
{
$this->decryptKeys[$fingerprint] = $passphrase;
// $this->decryptKeys[\substr($fingerprint, -16)] = $passphrase;
return true;
}
/**
* Add a key for encryption
*/
public function addEncryptKey(string $fingerprint) : bool
{
$this->encryptKeys[$fingerprint] = 1;
return true;
}
/**
* Add a key for signing
*/
public function addSignKey(string $fingerprint,
#[\SensitiveParameter]
string $passphrase
) : bool
{
$this->signKeys[$fingerprint] = $passphrase;
// $this->signKeys[\substr($fingerprint, -16)] = $passphrase;
return false;
}
/**
* Removes all keys which were set for decryption before
*/
public function clearDecryptKeys() : bool
{
$this->decryptKeys = [];
return true;
}
/**
* Removes all keys which were set for encryption before
*/
public function clearEncryptKeys() : bool
{
$this->encryptKeys = [];
return true;
}
/**
* Removes all keys which were set for signing before
*/
public function clearSignKeys() : bool
{
$this->signKeys = [];
return true;
return parent::isSupported() && static::findBinary('gpg');
}
/**
@ -388,19 +267,6 @@ class GPG
return $this->_exportKey($fingerprint, true);
}
/**
* Returns the engine info
*/
public function getEngineInfo() : array
{
return [
'protocol' => null,
'file_name' => $this->binary,
'home_dir' => $this->options['homedir'],
'version' => $this->version
];
}
/**
* Returns the errortext, if a function fails
*/
@ -414,7 +280,7 @@ class GPG
*/
public function getErrorInfo() : array
{
return false;
return [];
}
/**
@ -422,16 +288,7 @@ class GPG
*/
public function getProtocol() : int
{
return 0;
}
public function addPassphrase($keyId,
#[\SensitiveParameter]
$passphrase
)
{
$this->passphrases[$keyId] = $passphrase;
return $this;
return 0; // GPGME_PROTOCOL_OpenPGP
}
/**
@ -439,7 +296,7 @@ class GPG
* Also saves revocation certificate in {homedir}/openpgp-revocs.d/
* https://www.gnupg.org/documentation/manuals/gnupg/OpenPGP-Key-Management.html
*/
public function generateKey(GPGKeySettings $settings) /*: string|false*/
public function generateKey(PGPKeySettings $settings) /*: string|false*/
{
$arguments = [
'--batch',
@ -715,15 +572,6 @@ class GPG
return $keys;
}
/**
* Toggle the armored output
*/
public function setArmor(int $armor = 1) : bool
{
$this->armor = !!$armor;
return true;
}
/**
* Sets the mode for error_reporting
* GNUPG_ERROR_WARNING, GNUPG_ERROR_EXCEPTION and GNUPG_ERROR_SILENT.
@ -923,49 +771,6 @@ class GPG
return $this->_verify($fp, $signature);
}
private function _debug(string $msg) : void
{
\SnappyMail\Log::debug('GPG', $msg);
}
private function setInput(&$input) : void
{
if (\is_resource($input)) {
// https://github.com/the-djmaze/snappymail/issues/331
// $meta['stream_type'] == MEMORY or $meta['wrapper_data'] == MailSo\Base\StreamWrappers\Literal
$meta = \stream_get_meta_data($input);
if (!\in_array($meta['stream_type'], ['STDIO', 'TEMP'])) {
/*
$fp = \fopen('php://temp');
\stream_copy_to_stream($input, $fp);
$input = $fp;
*/
$input = \stream_get_contents($input);
}
}
$this->_input =& $input;
}
private function setOutput($output)/* : resource|false*/
{
$fclose = false;
if ($output && !\is_resource($output)) {
$output = \fopen($output, 'wb');
if (!$output) {
throw new \Exception("Could not open file '{$filename}'");
}
$fclose = $output;
}
$this->_output = $output;
return $fclose;
}
public function agent()
{
// $home = \escapeshellarg($this->options['homedir']);
// echo `gpg-agent --daemon --homedir $home 2>&1`;
}
public function getEncryptedMessageKeys(/*string|resource*/ $data) : array
{
$this->_debug('BEGIN DETECT MESSAGE KEY IDs');
@ -1068,7 +873,7 @@ class GPG
throw new \Exception('Unable to open process.');
}
$this->_openPipes = new GpgProcPipes($proc_pipes);
$this->_openPipes = new ProcPipes($proc_pipes);
$this->_debug('BEGIN PROCESSING');
@ -1373,158 +1178,4 @@ class GPG
'errors' => $errors
];
}
private function getPassphrase($key)
{
$passphrase = '';
$keyIdLength = \strlen($key);
if ($keyIdLength && !empty($_ENV['PINENTRY_USER_DATA'])) {
$passphrases = \json_decode($_ENV['PINENTRY_USER_DATA'], true);
foreach ($passphrases as $keyId => $pass) {
$length = \min($keyIdLength, \strlen($keyId));
if (\substr($keyId, -$length) === \substr($key, -$length)) {
return $pass;
}
}
}
// throw new \Exception("Passphrase not found for {$key}");
return '';
}
private function proc_close() : int
{
$exitCode = 0;
// clear PINs from environment if they were set
$_ENV['PINENTRY_USER_DATA'] = null;
if (\is_resource($this->proc_resource)) {
$this->_debug('CLOSING SUBPROCESS');
// close remaining open pipes
$this->_openPipes->closeAll();
$status = \proc_get_status($this->proc_resource);
$exitCode = \proc_close($this->proc_resource);
$this->proc_resource = null;
// proc_close() can return -1 in some cases,
// get the real exit code from the process status
if ($exitCode < 0 && $status && !$status['running']) {
$exitCode = $status['exitcode'];
}
if ($exitCode > 0) {
$this->_debug('=> subprocess returned an unexpected exit code: ' . $exitCode);
}
}
return $exitCode;
}
private static function findBinary($name) : ?string
{
$binary = \trim((string) `which $name`);
if ($binary && \is_executable($binary)) {
return $binary;
}
$locations = \array_filter([
'/sw/bin/',
'/usr/bin/',
'/usr/local/bin/',
'/opt/local/bin/',
'/run/current-system/sw/bin/'
], '\RainLoop\Utils::inOpenBasedir');
foreach ($locations as $location) {
if (\is_executable($location . $name)) {
return $location . $name;
}
}
return null;
}
}
class GpgProcPipes
{
private $pipes;
function __construct(array $pipes)
{
// Set streams as non-blocking.
foreach ($pipes as $pipe) {
\stream_set_blocking($pipe, 0);
\stream_set_write_buffer($pipe, GPG::CHUNK_SIZE);
\stream_set_chunk_size($pipe, GPG::CHUNK_SIZE);
\stream_set_read_buffer($pipe, GPG::CHUNK_SIZE);
}
$this->pipes = $pipes;
}
function __destruct()
{
$this->closeAll();
}
public function closeAll() : void
{
foreach (\array_keys($this->pipes) as $number) {
$this->close($number);
}
}
public function get(int $number)
{
if (\array_key_exists($number, $this->pipes) && \is_resource($this->pipes[$number])) {
return $this->pipes[$number];
}
}
public function close(int $number) : void
{
if (\array_key_exists($number, $this->pipes)) {
\fflush($this->pipes[$number]);
\fclose($this->pipes[$number]);
unset($this->pipes[$number]);
}
}
private $buffers = [];
public function readPipeLines(int $number) : iterable
{
$pipe = $this->get($number);
if ($pipe) {
$chunk = \fread($pipe, GPG::CHUNK_SIZE);
$length = \strlen($chunk);
$eolLength = \strlen(PHP_EOL);
if (!isset($this->buffers[$number])) {
$this->buffers[$number] = '';
}
$this->buffers[$number] .= $chunk;
while (false !== ($pos = \strpos($this->buffers[$number], PHP_EOL))) {
yield \substr($this->buffers[$number], 0, $pos);
$this->buffers[$number] = \substr($this->buffers[$number], $pos + $eolLength);
}
}
}
public function writePipe(int $number, string $data, int $length = 0) : int
{
$pipe = $this->get($number);
if ($pipe) {
$chunk = \substr($data, 0, $length ?: \strlen($data));
$length = \strlen($chunk);
$length = \fwrite($pipe, $chunk, $length);
if (!$length) {
// 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 error code in PHP. See
// PHP Bug #39598. https://bugs.php.net/bug.php?id=39598
$this->close($number);
}
return $length ?: 0;
}
return 0;
}
}

View file

@ -1,8 +1,8 @@
<?php
namespace SnappyMail\PGP;
namespace SnappyMail\GPG;
class GPGKeySettings
class PGPKeySettings
{
public
// Primary key

View file

@ -0,0 +1,89 @@
<?php
/**
* GnuPG
*/
namespace SnappyMail\GPG;
class ProcPipes
{
private $pipes;
function __construct(array $pipes)
{
// Set streams as non-blocking.
foreach ($pipes as $pipe) {
\stream_set_blocking($pipe, 0);
\stream_set_write_buffer($pipe, Base::CHUNK_SIZE);
\stream_set_chunk_size($pipe, Base::CHUNK_SIZE);
\stream_set_read_buffer($pipe, Base::CHUNK_SIZE);
}
$this->pipes = $pipes;
}
function __destruct()
{
$this->closeAll();
}
public function closeAll() : void
{
foreach (\array_keys($this->pipes) as $number) {
$this->close($number);
}
}
public function get(int $number)
{
if (\array_key_exists($number, $this->pipes) && \is_resource($this->pipes[$number])) {
return $this->pipes[$number];
}
}
public function close(int $number) : void
{
if (\array_key_exists($number, $this->pipes)) {
\fflush($this->pipes[$number]);
\fclose($this->pipes[$number]);
unset($this->pipes[$number]);
}
}
private $buffers = [];
public function readPipeLines(int $number) : iterable
{
$pipe = $this->get($number);
if ($pipe) {
$chunk = \fread($pipe, Base::CHUNK_SIZE);
$length = \strlen($chunk);
$eolLength = \strlen(\PHP_EOL);
if (!isset($this->buffers[$number])) {
$this->buffers[$number] = '';
}
$this->buffers[$number] .= $chunk;
while (false !== ($pos = \strpos($this->buffers[$number], \PHP_EOL))) {
yield \substr($this->buffers[$number], 0, $pos);
$this->buffers[$number] = \substr($this->buffers[$number], $pos + $eolLength);
}
}
}
public function writePipe(int $number, string $data, int $length = 0) : int
{
$pipe = $this->get($number);
if ($pipe) {
$chunk = \substr($data, 0, $length ?: \strlen($data));
$length = \strlen($chunk);
$length = \fwrite($pipe, $chunk, $length);
if (!$length) {
// 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 error code in PHP. See
// PHP Bug #39598. https://bugs.php.net/bug.php?id=39598
$this->close($number);
}
return $length ?: 0;
}
return 0;
}
}

File diff suppressed because it is too large Load diff

View file

@ -6,13 +6,15 @@ defined('GNUPG_SIG_MODE_NORMAL') || define('GNUPG_SIG_MODE_NORMAL', 0);
defined('GNUPG_SIG_MODE_DETACH') || define('GNUPG_SIG_MODE_DETACH', 1);
defined('GNUPG_SIG_MODE_CLEAR') || define('GNUPG_SIG_MODE_CLEAR', 2);
use SnappyMail\GPG\PGP as GPG;
class GnuPG
{
private
$homedir,
// Instance of gnupg pecl extension https://www.php.net/gnupg
$GnuPG,
// Instance of \SnappyMail\PGP\GPG
// Instance of \SnappyMail\GPG\PGP
$GPG;
function __construct(string $homedir)