diff --git a/Controller/AS2Controller.php b/Controller/AS2Controller.php index 548565c..8c044bf 100644 --- a/Controller/AS2Controller.php +++ b/Controller/AS2Controller.php @@ -4,6 +4,8 @@ use Symfony\Component\HttpFoundation\Request; +use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\HttpFoundation\Response; use TechData\AS2SecureBundle\Services\AS2; /** @@ -11,7 +13,7 @@ * * @author wpigott */ -class AS2Controller +class AS2Controller extends Controller { /** @@ -27,6 +29,12 @@ function __construct(AS2 $as2Service) public function inboundAction(Request $request) { - $this->as2Service->handleRequest($request); + try { + $this->as2Service->handleRequest($request); + return new Response('',200); + } + catch (\Exception $exception ) { + return new Response('',500); + } } } diff --git a/DependencyInjection/CompilerPass/PartnerProviderCompilerPass.php b/DependencyInjection/CompilerPass/PartnerProviderCompilerPass.php index 8bd939b..9eab482 100644 --- a/DependencyInjection/CompilerPass/PartnerProviderCompilerPass.php +++ b/DependencyInjection/CompilerPass/PartnerProviderCompilerPass.php @@ -1,4 +1,5 @@ getParameter('tech_data_as2_secure.partner_provider.service_id')); - - // Add to the partner factory - $container->getDefinition('tech_data_as2_secure.factory.partner')->setArguments(array($reference)); + $partnerProviderResolver = $container->getDefinition('tech_data_as2_secure.partner_provider.resolver'); + foreach ($container->findTaggedServiceIds('tech_data_as2_partner') as $serviceId => $tags) { + $partnerProviderResolver->addMethodCall( + 'add', [new Reference($serviceId)] + ); + } } -} \ No newline at end of file +} diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 95531f3..c0b31ee 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -8,7 +8,8 @@ /** * This is the class that validates and merges configuration from your app/config files * - * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class} + * To learn more see + * {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class} */ class Configuration implements ConfigurationInterface { @@ -18,12 +19,12 @@ class Configuration implements ConfigurationInterface public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('tech_data_as2_secure'); - + $rootNode = $treeBuilder->root('tech_data_as2_secure'); + // Here you should define the parameters that are allowed to // configure your bundle. See the documentation linked above for // more information on that topic. - + return $treeBuilder; } } diff --git a/DependencyInjection/TechDataAS2SecureExtension.php b/DependencyInjection/TechDataAS2SecureExtension.php index 87590de..ea1d91a 100644 --- a/DependencyInjection/TechDataAS2SecureExtension.php +++ b/DependencyInjection/TechDataAS2SecureExtension.php @@ -20,9 +20,9 @@ class TechDataAS2SecureExtension extends Extension public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); - $config = $this->processConfiguration($configuration, $configs); - - $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $config = $this->processConfiguration($configuration, $configs); + + $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); $loader->load('services.xml'); } } diff --git a/Events/Error.php b/Events/Error.php index 81dff76..9b3e115 100644 --- a/Events/Error.php +++ b/Events/Error.php @@ -11,5 +11,5 @@ */ class Error extends Event { - const EVENT = 'ERROR'; + const EVENT = 'as2.error'; } diff --git a/Events/Log.php b/Events/Log.php index 9debfef..6939582 100644 --- a/Events/Log.php +++ b/Events/Log.php @@ -11,32 +11,62 @@ */ class Log extends Event { - - const EVENT = 'LOG'; + + /** + * + */ + const EVENT = 'as2.log'; + /** + * + */ const TYPE_INFO = 'INFO'; + /** + * + */ const TYPE_WARN = 'WARN'; + /** + * + */ const TYPE_ERROR = 'ERROR'; - + + /** + * @var + */ private $message; + /** + * @var + */ private $type; - + + /** + * Log constructor. + * + * @param $type + * @param $message + */ function __construct($type, $message) { - $this->type = $type; + $this->type = $type; $this->message = $message; } - - + + + /** + * @return mixed + */ public function getMessage() { return $this->message; } - + + /** + * @param $message + */ public function setMessage($message) { $this->message = $message; } - + /** * @return mixed */ @@ -44,7 +74,7 @@ public function getType() { return $this->type; } - + /** * @param mixed $type */ @@ -52,6 +82,6 @@ public function setType($type) { $this->type = $type; } - - + + } diff --git a/Events/MessageReceived.php b/Events/MessageReceived.php index a1afa6a..61095b7 100644 --- a/Events/MessageReceived.php +++ b/Events/MessageReceived.php @@ -3,42 +3,171 @@ namespace TechData\AS2SecureBundle\Events; use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * Description of MessageReceived * * @author wpigott */ -class MessageReceived extends Event { - - const EVENT = 'MESSAGE_RECEIVED'; - +class MessageReceived extends Event +{ + + /** + * + */ + const EVENT = 'as2.messageReceived'; + /** + * + */ + const TYPE_MESSAGE = 'message'; + /** + * + */ + const TYPE_MDN = 'mdn'; + + /** + * @var string + */ private $message; - private $messageType; - private $headers = array(); - - public function getMessage() { - return $this->message; + + /** + * @var string + */ + private $messageId; + + /** + * @var + */ + private $originalMessageId; + /** + * @var + */ + private $type; + + /** + * @var string + */ + private $sendingPartnerId; + + /** + * @var string + */ + private $receivingPartnerId; + + /** + * @return string + */ + public function getSendingPartnerId() + { + return $this->sendingPartnerId; } - - public function setMessage($message) { - $this->message = $message; + + /** + * @return string + */ + public function getMessageId() + { + return $this->messageId; } - - public function getMessageType() { - return $this->messageType; + + /** + * @return mixed + */ + public function getType() + { + return $this->type; } - - public function getHeaders() { - return $this->headers; + + /** + * @param mixed $type + * + * @return MessageReceived + */ + public function setType($type) + { + $this->type = $type; + + return $this; } - - public function setMessageType($messageType) { - $this->messageType = $messageType; + + /** + * @return mixed + */ + public function getOriginalMessageId() + { + return $this->originalMessageId; } - - public function setHeaders($headers) { - $this->headers = $headers; + + /** + * @param mixed $originalMessageId + */ + public function setOriginalMessageId($originalMessageId) + { + $this->originalMessageId = $originalMessageId; + + return $this; + } + + /** + * @param string $messageId + */ + public function setMessageId($messageId) + { + $this->messageId = $messageId; + + return $this; + } + + /** + * @param string $sendingPartnerId + * + * @return MessageReceived + */ + public function setSendingPartnerId($sendingPartnerId) + { + $this->sendingPartnerId = $sendingPartnerId; + + return $this; + } + + /** + * @return string + */ + public function getReceivingPartnerId() + { + return $this->receivingPartnerId; + } + + /** + * @param string $receivingPartnerId + * + * @return MessageReceived + */ + public function setReceivingPartnerId($receivingPartnerId) + { + $this->receivingPartnerId = $receivingPartnerId; + + return $this; + } + + /** + * @param $message + * + * @return $this + */ + public function setMessage($message) + { + $this->message = $message; + + return $this; + } + + /** + * @return string + */ + public function getMessage() + { + return $this->message; } - } diff --git a/Events/MessageSent.php b/Events/MessageSent.php index 22d2895..af7a658 100644 --- a/Events/MessageSent.php +++ b/Events/MessageSent.php @@ -10,12 +10,85 @@ * @author wpigott */ class MessageSent extends Event { - const EVENT = 'MESSAGE_SENT'; + const EVENT = 'as2.messageSent'; + const TYPE_MESSAGE = 'message'; + const TYPE_MDN = 'mdn'; private $message; + private $messageId; private $messageType; + private $type; + private $code; + private $originalMessageId; private $headers = array(); + + /** + * @return mixed + */ + public function getOriginalMessageId() + { + return $this->originalMessageId; + } + + /** + * @param mixed $originalMessageId + */ + public function setOriginalMessageId($originalMessageId) + { + $this->originalMessageId = $originalMessageId; + } + + + /** + * @return mixed + */ + public function getCode() + { + return $this->code; + } + + /** + * @param mixed $code + */ + public function setCode($code) + { + $this->code = $code; + } + + + /** + * @return mixed + */ + public function getType() + { + return $this->type; + } + + /** + * @param mixed $type + */ + public function setType($type) + { + $this->type = $type; + } + + /** + * @return mixed + */ + public function getMessageId() + { + return $this->messageId; + } + + /** + * @param mixed $messageId + */ + public function setMessageId($messageId) + { + $this->messageId = $messageId; + } + public function getMessage() { return $this->message; } diff --git a/Factories/AbstractFactory.php b/Factories/AbstractFactory.php index 330e602..cf4138a 100644 --- a/Factories/AbstractFactory.php +++ b/Factories/AbstractFactory.php @@ -12,6 +12,11 @@ use TechData\AS2SecureBundle\Factories\Partner as PartnerFactory; use TechData\AS2SecureBundle\Factories\Adapter as AdapterFactory; +/** + * Class AbstractFactory + * + * @package TechData\AS2SecureBundle\Factories + */ abstract class AbstractFactory { /** @@ -28,7 +33,7 @@ abstract class AbstractFactory * @var AdapterFactory */ private $adapterFactory; - + /** * @return EventDispatcherInterface */ @@ -36,7 +41,7 @@ protected function getEventDispatcher() { return $this->eventDispatcher; } - + /** * @param EventDispatcherInterface $EventDispatcher */ @@ -44,33 +49,39 @@ public function setEventDispatcher(EventDispatcherInterface $EventDispatcher) { $this->eventDispatcher = $EventDispatcher; } - + /** - * + * * @return PartnerFactory */ - public function getPartnerFactory() + public function getPartnerFactory() { return $this->partnerFactory; } - - public function setPartnerFactory(PartnerFactory $partnerFactory) + + /** + * @param Partner $partnerFactory + */ + public function setPartnerFactory(PartnerFactory $partnerFactory) { $this->partnerFactory = $partnerFactory; } - + /** * @return AdapterFactory */ - public function getAdapterFactory() { + public function getAdapterFactory() + { return $this->adapterFactory; } - - public function setAdapterFactory(AdapterFactory $adapterFactory) { + + /** + * @param Adapter $adapterFactory + */ + public function setAdapterFactory(AdapterFactory $adapterFactory) + { $this->adapterFactory = $adapterFactory; } - - - - + + } \ No newline at end of file diff --git a/Factories/Adapter.php b/Factories/Adapter.php index dbe52b9..6fd1685 100644 --- a/Factories/Adapter.php +++ b/Factories/Adapter.php @@ -11,24 +11,39 @@ use TechData\AS2SecureBundle\Factories\Partner as PartnerFactory; use TechData\AS2SecureBundle\Models\Adapter as AdapterModel; +/** + * Class Adapter + * + * @package TechData\AS2SecureBundle\Factories + */ class Adapter { /** * @var PartnerFactory */ private $partnerFactory; + /** + * @var string + */ private $AS2_DIR_BIN; - + + /** + * Adapter constructor. + * + * @param Partner $partnerFactory + * @param $AS2_DIR_BIN + */ function __construct(PartnerFactory $partnerFactory, $AS2_DIR_BIN) { $this->partnerFactory = $partnerFactory; - $this->AS2_DIR_BIN = $AS2_DIR_BIN; + $this->AS2_DIR_BIN = $AS2_DIR_BIN; } - - + + /** * @param $partner_from * @param $partner_to + * * @return AdapterModel * @throws \TechData\AS2SecureBundle\Models\AS2Exception */ @@ -36,6 +51,7 @@ public function build($partner_from, $partner_to) { $adapter = new AdapterModel($this->partnerFactory, $this->AS2_DIR_BIN); $adapter->initialize($partner_from, $partner_to); + return $adapter; } } \ No newline at end of file diff --git a/Factories/Client.php b/Factories/Client.php index 9660fb5..6836c1c 100644 --- a/Factories/Client.php +++ b/Factories/Client.php @@ -11,27 +11,38 @@ use TechData\AS2SecureBundle\Factories\Request as RequestFactory; use TechData\AS2SecureBundle\Models\Client as ClientModel; +/** + * Class Client + * + * @package TechData\AS2SecureBundle\Factories + */ class Client { - + /** * @var RequestFactory */ private $requestFactory; - + + /** + * Client constructor. + * + * @param Request $requestFactory + */ function __construct(RequestFactory $requestFactory) { $this->requestFactory = $requestFactory; } - - + + /** * @return ClientModel */ public function build() { $client = new ClientModel($this->requestFactory); + return $client; } - + } \ No newline at end of file diff --git a/Factories/MDN.php b/Factories/MDN.php index 423430b..7f4a5ae 100644 --- a/Factories/MDN.php +++ b/Factories/MDN.php @@ -11,29 +11,30 @@ use TechData\AS2SecureBundle\Models\MDN as MDNModel; +/** + * Class MDN + * + * @package TechData\AS2SecureBundle\Factories + */ class MDN extends AbstractFactory { - - function __construct() - { - - } - /** - * @param null $data + * @param null $data * @param array $params + * * @return MDNModel * @throws \TechData\AS2SecureBundle\Models\AS2Exception */ - public function build($data = null, $params = array()) + public function build($data = null, $params = []) { $mdn = new MDNModel(); $mdn->setPartnerFactory($this->getPartnerFactory()); $mdn->setAdapterFactory($this->getAdapterFactory()); $mdn->initialize($data, $params); + return $mdn; - + } - - + + } \ No newline at end of file diff --git a/Factories/Message.php b/Factories/Message.php index 232154a..487e26c 100644 --- a/Factories/Message.php +++ b/Factories/Message.php @@ -12,30 +12,42 @@ use TechData\AS2SecureBundle\Factories\MDN as MDNFactory; use TechData\AS2SecureBundle\Models\Message as MessageModel; +/** + * Class Message + * + * @package TechData\AS2SecureBundle\Factories + */ class Message extends AbstractFactory { /** * @var MDNFactory */ private $mdnFactory; - + + /** + * Message constructor. + * + * @param MDN $mdnFactory + */ function __construct(MDNFactory $mdnFactory) { $this->mdnFactory = $mdnFactory; } - + /** - * @param null $data + * @param null $data * @param array $params + * * @return MessageModel */ - public function build($data = null, $params = array()) + public function build($data = null, $params = []) { $message = new MessageModel($this->mdnFactory); $message->setPartnerFactory($this->getPartnerFactory()); $message->setAdapterFactory($this->getAdapterFactory()); $message->initialize($data, $params); + return $message; - + } } \ No newline at end of file diff --git a/Factories/Partner.php b/Factories/Partner.php index e60e549..bd4c045 100644 --- a/Factories/Partner.php +++ b/Factories/Partner.php @@ -12,36 +12,51 @@ */ class Partner { - + + /** + * @var PartnerProvider + */ private $partnerProvider; - private $loadedPartners = array(); - + /** + * @var array + */ + private $loadedPartners = []; + + /** + * Partner constructor. + * + * @param PartnerProvider $partnerProvider + */ public function __construct(PartnerProvider $partnerProvider) { $this->partnerProvider = $partnerProvider; } - + /** - * @param $partnerId + * @param $partnerId * @param bool $reload + * * @return PartnerModel */ - public function getPartner($partnerId, $reload=FALSE) + public function getPartner($partnerId, $reload = false) { - if($reload || !array_key_exists(trim($partnerId), $this->loadedPartners)) { - $partnerData = $this->partnerProvider->getPartner($partnerId); - $as2partner = $this->makeNewPartner($partnerData); + if ($reload || !array_key_exists(trim($partnerId), $this->loadedPartners)) { + $partnerData = $this->partnerProvider->getPartner($partnerId); + $as2partner = $this->makeNewPartner($partnerData); $this->loadedPartners[trim($partnerId)] = $as2partner; } + return $this->loadedPartners[trim($partnerId)]; } - + /** * @param array $partnerData + * * @return PartnerModel */ - private function makeNewPartner($partnerData) { - return new PartnerModel((array)$partnerData); + private function makeNewPartner($partnerData) + { + return new PartnerModel((array) $partnerData); } - + } diff --git a/Factories/Request.php b/Factories/Request.php index 0d53ae7..9f39bfa 100644 --- a/Factories/Request.php +++ b/Factories/Request.php @@ -8,13 +8,22 @@ namespace TechData\AS2SecureBundle\Factories; +use Symfony\Component\EventDispatcher\EventDispatcher; use TechData\AS2SecureBundle\Factories\AbstractFactory; use TechData\AS2SecureBundle\Factories\MDN as MDNFactory; use TechData\AS2SecureBundle\Factories\Message as MessageFactory; use TechData\AS2SecureBundle\Models\Request as RequestModel; +/** + * Class Request + * + * @package TechData\AS2SecureBundle\Factories + */ class Request extends AbstractFactory { + /** + * @var null + */ protected $request = null; /** * @var MDNFactory @@ -24,25 +33,39 @@ class Request extends AbstractFactory * @var MessageFactory */ private $messageFactory; - - function __construct(MDNFactory $mdnFactory, MessageFactory $messageFactory) + /** + * @var EventDispatcher + */ + private $eventDispatcher; + + /** + * Request constructor. + * + * @param MDN $mdnFactory + * @param Message $messageFactory + * @param EventDispatcher $eventDispatcher + */ + function __construct(MDNFactory $mdnFactory, MessageFactory $messageFactory, EventDispatcher $eventDispatcher) { - $this->mdnFactory = $mdnFactory; - $this->messageFactory = $messageFactory; + $this->mdnFactory = $mdnFactory; + $this->messageFactory = $messageFactory; + $this->eventDispatcher = $eventDispatcher; } - + /** * @param $content * @param $headers + * * @return RequestModel */ public function build($content, $headers) { - $request = new RequestModel($this->mdnFactory, $this->messageFactory); + $request = new RequestModel($this->mdnFactory, $this->messageFactory, $this->eventDispatcher); $request->setPartnerFactory($this->getPartnerFactory()); $request->setAdapterFactory($this->getAdapterFactory()); $request->initialize($content, $headers); + return $request; - + } } \ No newline at end of file diff --git a/Interfaces/Events.php b/Interfaces/Events.php index 26cd1eb..957d729 100644 --- a/Interfaces/Events.php +++ b/Interfaces/Events.php @@ -9,10 +9,27 @@ namespace TechData\AS2SecureBundle\Interfaces; +/** + * Interface Events + * + * @package TechData\AS2SecureBundle\Interfaces + */ interface Events { + /** + * + */ CONST LOG = 'tech_data_as2_secure.event.log'; + /** + * + */ CONST ERROR = 'tech_data_as2_secure.event.error'; + /** + * + */ CONST MESSAGE_RECIEVED = 'tech_data_as2_secure.event.message_received'; + /** + * + */ CONST MESSAGE_SENT = 'tech_data_as2_secure.event.message_sent'; } \ No newline at end of file diff --git a/Interfaces/MessageSender.php b/Interfaces/MessageSender.php index 5676b78..63f1138 100644 --- a/Interfaces/MessageSender.php +++ b/Interfaces/MessageSender.php @@ -9,12 +9,18 @@ namespace TechData\AS2SecureBundle\Interfaces; +/** + * Interface MessageSender + * + * @package TechData\AS2SecureBundle\Interfaces + */ interface MessageSender { /** * @param $toPartner * @param $fromPartner * @param $messageContent + * * @throws \Exception * @throws \TechData\AS2SecureBundle\Models\AS2Exception * @throws \TechData\AS2SecureBundle\Models\Exception diff --git a/Interfaces/PartnerInterface.php b/Interfaces/PartnerInterface.php new file mode 100644 index 0000000..d93a68d --- /dev/null +++ b/Interfaces/PartnerInterface.php @@ -0,0 +1,19 @@ + + * @author Sebastien MALOT * * @copyright Copyright (c) 2010, Sebastien MALOT * @@ -24,66 +25,121 @@ * You should have received a copy of the GNU General Public License * along with AS2Secure. * - * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License - * @version 0.9.0 + * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License + * @version 0.9.0 * */ +/** + * Class AS2Exception + * + * @package TechData\AS2SecureBundle\Models + */ class AS2Exception extends \Exception { /** * Refers to RFC 4130 * http://rfclibrary.hosting.com/rfc/rfc4130/rfc4130-34.asp */ - + const STATUS_ERROR = 'error'; + /** + * + */ const STATUS_FAILURE = 'failure'; + /** + * + */ const STATUS_WARNING = 'warning'; - - protected static $level_error = array( - 1 => 'authentication-failed', // the receiver could not authenticate the sender - 2 => 'decompression-failed', // - 3 => 'decryption-failed', // the receiver could not decrypt the message contents - 4 => 'insufficient-message-security', // - 5 => 'integrity-check-failed', // the receiver could not verify content integrity - 6 => 'unexpected-processing-error', // a catch-all for any additional processing errors - ); - - protected static $level_failure = array( - 101 => 'unsupported format', // sha1, md5 - 102 => 'unsupported MIC-algorithms', // - ); - - protected static $level_warning = array( - 201 => 'duplicate-document', // an identical message already exists at the destination server - 202 => 'sender-equals-receiver', // the AS2-To name is identical to the AS2-From name - ); - + + /** + * @var array + */ + protected static $level_error + = [ + 1 => 'authentication-failed', + // the receiver could not authenticate the sender + 2 => 'decompression-failed', + // + 3 => 'decryption-failed', + // the receiver could not decrypt the message contents + 4 => 'insufficient-message-security', + // + 5 => 'integrity-check-failed', + // the receiver could not verify content integrity + 6 => 'unexpected-processing-error', + // a catch-all for any additional processing errors + ]; + + /** + * @var array + */ + protected static $level_failure + = [ + 101 => 'unsupported format', + // sha1, md5 + 102 => 'unsupported MIC-algorithms', + // + ]; + + /** + * @var array + */ + protected static $level_warning + = [ + 201 => 'duplicate-document', + // an identical message already exists at the destination server + 202 => 'sender-equals-receiver', + // the AS2-To name is identical to the AS2-From name + ]; + + /** + * + */ const DEFAULT_ERROR = 6; - + /* -------------------------------------------------- */ - + + /** + * AS2Exception constructor. + * + * @param string $message + * @param int $code + * @param null $previous + */ public function __construct($message = '', $code = self::DEFAULT_ERROR, $previous = null) { if ($previous) - parent::__construct($message, $code, $previous); + parent::__construct("As2Gateway " . $message, $code, $previous); else - parent::__construct($message, $code); + parent::__construct("As2Gateway " . $message, $code); } - + + /** + * @return string + */ public function getLevel() { - if (in_array($this->code, array_keys(self::$level_error))) return self::STATUS_ERROR; - if (in_array($this->code, array_keys(self::$level_failure))) return self::STATUS_FAILURE; - if (in_array($this->code, array_keys(self::$level_warning))) return self::STATUS_WARNING; + if (in_array($this->code, array_keys(self::$level_error))) + return self::STATUS_ERROR; + if (in_array($this->code, array_keys(self::$level_failure))) + return self::STATUS_FAILURE; + if (in_array($this->code, array_keys(self::$level_warning))) + return self::STATUS_WARNING; else return self::STATUS_ERROR; } - + + /** + * @return mixed + */ public function getMessageShort() { - if (in_array($this->code, array_keys(self::$level_error))) return self::$level_error[$this->code]; - if (in_array($this->code, array_keys(self::$level_failure))) return self::$level_failure[$this->code]; - if (in_array($this->code, array_keys(self::$level_warning))) return self::$level_warning[$this->code]; + if (in_array($this->code, array_keys(self::$level_error))) + return self::$level_error[$this->code]; + if (in_array($this->code, array_keys(self::$level_failure))) + return self::$level_failure[$this->code]; + if (in_array($this->code, array_keys(self::$level_warning))) + return self::$level_warning[$this->code]; else return self::$level_error[self::DEFAULT_ERROR];; } } diff --git a/Models/AbstractBase.php b/Models/AbstractBase.php index a2dbda8..bf18167 100644 --- a/Models/AbstractBase.php +++ b/Models/AbstractBase.php @@ -1,10 +1,11 @@ + * @author Sebastien MALOT * * @copyright Copyright (c) 2010, Sebastien MALOT * @@ -25,28 +26,66 @@ * You should have received a copy of the GNU General Public License * along with AS2Secure. * - * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License - * @version 0.9.0 + * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License + * @version 0.9.0 * */ use TechData\AS2SecureBundle\Factories\Partner as PartnerFacotry; use TechData\AS2SecureBundle\Factories\Adapter as AdapterFactory; +/** + * Class AbstractBase + * + * @package TechData\AS2SecureBundle\Models + */ abstract class AbstractBase { // Injected Services + /** + * @var null + */ protected $adapter = null; - + // Properties + /** + * @var null + */ protected $filename = null; + /** + * @var null + */ protected $mimetype = null; + /** + * @var null + */ protected $path = null; - protected $files = array(); + /** + * @var array + */ + protected $files = []; + /** + * @var null + */ protected $headers = null; + /** + * @var string + */ protected $message_id = ''; + /** + * @var bool + */ protected $is_signed = false; + /** + * @var bool + */ protected $is_crypted = false; + /** + * @var null + */ protected $partner_from = null; + /** + * @var null + */ protected $partner_to = null; /** @@ -59,109 +98,168 @@ abstract class AbstractBase */ private $adapterFactory; + /** + * @param $partner + * + * @return string + */ protected static function generateMessageID($partner) { - if ($partner instanceof Partner) $id = $partner->id; + if ($partner instanceof Partner) + $id = $partner->id; else $id = 'unknown'; + return '<' . uniqid('', true) . '@' . round(microtime(true)) . '_' . str_replace(' ', '', strtolower($id) . '_' . php_uname('n')) . '>'; } - + + /** + * @return null + */ public function getPath() { return $this->path; } - + + /** + * @param $file + */ public function addFile($file) { $this->files[] = realpath($file); } - - + + // partner handle - + + /** + * @return array + */ public function getFiles() { return $this->files; } - + + /** + * @return null + */ public function getFileName() { return $this->filename; } - + + /** + * @return bool|string + */ public function getContent() { return file_get_contents($this->path); } - + + /** + * @return null + */ public function getHeaders() { return $this->headers; } - + // message properties - + + /** + * @param $headers + */ public function setHeaders($headers) { $this->headers = $headers; } - + + /** + * @param $token + * + * @return mixed + */ public function getHeader($token) { return $this->headers->getHeader($token); } - + + /** + * @return array + */ public function getAuthentication() { - return array('method' => Partner::METHOD_NONE, - 'login' => '', - 'password' => ''); + return [ + 'method' => Partner::METHOD_NONE, + 'login' => '', + 'password' => '' + ]; } - + + /** + * @return string + */ public function getMessageId() { return $this->message_id; } - + + /** + * @param $id + */ public function setMessageId($id) { $this->message_id = $id; } - + + /** + * @return bool + */ public function isCrypted() { return $this->is_crypted; } - + + /** + * @return bool + */ public function isSigned() { return $this->is_signed; } - + + /** + * + */ public function encode() { // TODO } - + + /** + * + */ public function decode() { // TODO } - + + /** + * + */ public function getUrl() { // TODO } - + /** * @return PartnerFacotry */ - + protected function getPartnerFactory() { return $this->partnerFactory; } - + /** * @param PartnerFacotry $partnerFactory */ @@ -171,45 +269,57 @@ public function setPartnerFactory(PartnerFacotry $partnerFactory) } /** - * + * * @return AdapterFactory */ - public function getAdapterFactory() { + public function getAdapterFactory() + { return $this->adapterFactory; } - - public function setAdapterFactory(AdapterFactory $adapterFactory) { + + /** + * @param AdapterFactory $adapterFactory + */ + public function setAdapterFactory(AdapterFactory $adapterFactory) + { $this->adapterFactory = $adapterFactory; } - - final protected function initializeBase($data, $params = array()) + + /** + * @param $data + * @param array $params + * + * @throws AS2Exception + */ + final protected function initializeBase($data, $params = []) { if (is_null($this->headers)) $this->headers = new Header(); - + if (is_array($data)) { $this->path = $data; - } elseif ($data) { + } + elseif ($data) { // do nothing // content : default is file if (isset($params['is_file']) && $params['is_file'] === false) { $file = Adapter::getTempFilename(); file_put_contents($file, $data); $this->path = $file; - // filename if (isset($params['filename'])) $this->filename = $params['filename']; - } else { + } + else { $this->path = $data; // filename $this->filename = (isset($params['filename']) ? $params['filename'] : basename($this->path)); } - + // mimetype handle $this->mimetype = (isset($params['mimetype']) ? $params['mimetype'] : Adapter::detectMimeType($this->path)); } - + // partners if (isset($params['partner_from']) && $params['partner_from']) { $this->setPartnerFrom($params['partner_from']); @@ -219,27 +329,39 @@ final protected function initializeBase($data, $params = array()) $this->setPartnerTo($params['partner_to']); } else throw new AS2Exception('NO AS2 To Partner specified.'); - + $this->adapter = $this->getAdapterFactory()->build($this->getPartnerFrom(), $this->getPartnerTo()); } - + + /** + * @return null + */ public function getPartnerFrom() { return $this->partner_from; } - + + /** + * @param $partner_from + */ public function setPartnerFrom($partner_from) { $this->partner_from = $this->getPartnerFactory()->getPartner($partner_from); } - + + /** + * @return null + */ public function getPartnerTo() { return $this->partner_to; } - + + /** + * @param $partner_to + */ public function setPartnerTo($partner_to) { - $this->partner_from = $this->getPartnerFactory()->getPartner($partner_to); + $this->partner_to = $this->getPartnerFactory()->getPartner($partner_to); } } diff --git a/Models/Adapter.php b/Models/Adapter.php index c10f8a5..6254c44 100644 --- a/Models/Adapter.php +++ b/Models/Adapter.php @@ -1,10 +1,11 @@ + * @author Sebastien MALOT * * @copyright Copyright (c) 2010, Sebastien MALOT * @@ -25,13 +26,18 @@ * You should have received a copy of the GNU General Public License * along with AS2Secure. * - * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License - * @version 0.9.0 + * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License + * @version 0.9.0 * */ use TechData\AS2SecureBundle\Factories\Partner as PartnerFactory; +/** + * Class Adapter + * + * @package TechData\AS2SecureBundle\Models + */ class Adapter { /** @@ -39,31 +45,52 @@ class Adapter * for overriding PATH usage */ public static $ssl_adapter = 'AS2Secure.jar'; + /** + * @var string + */ public static $ssl_openssl = 'openssl'; + /** + * @var string + */ public static $javapath = 'java'; /** * Array to store temporary files created and scheduled to unlink */ protected static $tmp_files = null; + /** + * @var null + */ protected $partner_from = null; + /** + * @var null + */ protected $partner_to = null; /** * @var PartnerFactory */ private $partnerFactory; + /** + * @var + */ private $AS2_DIR_BIN; - + + /** + * Adapter constructor. + * + * @param PartnerFactory $partnerFactory + * @param $AS2_DIR_BIN + */ function __construct(PartnerFactory $partnerFactory, $AS2_DIR_BIN) { $this->partnerFactory = $partnerFactory; - $this->AS2_DIR_BIN = $AS2_DIR_BIN; + $this->AS2_DIR_BIN = $AS2_DIR_BIN; } - + /** * Calculate the message integrity check (MIC) using SHA1 or MD5 algo * * @param string $input The file to use - * @param string $algo The algo to use + * @param string $algo The algo to use * * @return string The hash calculated */ @@ -74,7 +101,7 @@ public static function calculateMicChecksum($input, $algo = 'sha1') else return base64_encode(self::hex2bin(md5_file($input))) . ', md5'; } - + /** * Convert a string from hexadecimal format to binary format * @@ -85,14 +112,15 @@ public static function calculateMicChecksum($input, $algo = 'sha1') public static function hex2bin($str) { $bin = ''; - $i = 0; + $i = 0; do { $bin .= chr(hexdec($str{$i} . $str{($i + 1)})); - $i += 2; + $i += 2; } while ($i < strlen($str)); + return $bin; } - + /** * Extract the message integrity check (MIC) from the digital signature * @@ -103,50 +131,48 @@ public static function hex2bin($str) public function getMicChecksum($input) { try { - $command = self::$javapath . ' -jar ' . escapeshellarg($this->AS2_DIR_BIN . self::$ssl_adapter) . - ' checksum' . - ' -in ' . escapeshellarg($input) . - ' 2>/dev/null'; - + $command = self::$javapath . ' -jar ' . escapeshellarg($this->AS2_DIR_BIN . self::$ssl_adapter) . ' checksum' . ' -in ' . escapeshellarg($input) . ' 2>/dev/null'; + $dump = self::exec($command, true); - + return $dump[0]; - } catch (Exception $e) { + } catch (\Exception $e) { return false; } } - + /** * Execute a command line and throw Exception if an error appends * - * @param string $command The command line to execute - * @param boolean $return_output True to return all data from standard output + * @param string $command The command line to execute + * @param boolean $return_output True to return all data from standard output * False to return only the error code * * @return string The error code or the content from standard output */ public static function exec($command, $return_output = false) { - $output = array(); + $output = []; $return_var = 0; try { exec($command, $output, $return_var); $line = (isset($output[0]) ? $output[0] : 'Unexpected error in command line : ' . $command); - if ($return_var) throw new Exception($line, (int)$return_var); - } catch (Exception $e) { + if ($return_var) + throw new \Exception($line, (int) $return_var); + } catch (\Exception $e) { throw $e; } - + if ($return_output) return $output; else return $return_var; } - + /** * Extract CA from a PKCS12 Certificate * - * @param string $input The PKCS12 Certificate + * @param string $input The PKCS12 Certificate * @param string $password The PKCS12 Certificate's password * * @return string The file which contains the CA @@ -155,12 +181,12 @@ public static function getCAFromPKCS12($input, $password = '') { return self::getDataFromPKCS12($input, 'extracerts', $password); } - + /** * Extract Part from a PKCS12 Certificate * - * @param string $input The PKCS12 Certificate - * @param string $token The Part to extract + * @param string $input The PKCS12 Certificate + * @param string $token The Part to extract * @param string $password The PKCS12 Certificate's password * * @return string The file which contains the Part @@ -169,23 +195,23 @@ protected static function getDataFromPKCS12($input, $token, $password = '') { try { $pkcs12 = file_get_contents($input); - - $certs = array(); + + $certs = []; openssl_pkcs12_read($pkcs12, $certs, $password); - + if (!isset($certs[$token])) { throw new AS2Exception('Unexpected error while extracting certificates from pkcs12 container.'); } - + $output = self::getTempFilename(); file_put_contents($output, $certs[$token]); - + return $output; - } catch (Exception $e) { + } catch (\Exception $e) { throw $e; } } - + /** * Create a temporary file into temporary directory and add it to * the garbage collector at shutdown @@ -195,16 +221,20 @@ protected static function getDataFromPKCS12($input, $token, $password = '') public static function getTempFilename() { if (is_null(self::$tmp_files)) { - self::$tmp_files = array(); - register_shutdown_function(array('TechData\AS2SecureBundle\Models\Adapter', '_deleteTempFiles')); + self::$tmp_files = []; + register_shutdown_function([ + 'TechData\AS2SecureBundle\Models\Adapter', + '_deleteTempFiles' + ]); } - - $dir = sys_get_temp_dir(); - $filename = tempnam($dir, 'as2file_'); + + $dir = sys_get_temp_dir(); + $filename = tempnam($dir, 'as2file_'); self::$tmp_files[] = $filename; + return $filename; } - + /** * Garbage collector to delete temp files created with 'self::getTempFilename' * with shutdown function @@ -215,7 +245,7 @@ public static function _deleteTempFiles() foreach (self::$tmp_files as $file) @unlink($file); } - + /** * Determine the mimetype of a file (also called 'Content-Type') * @@ -228,89 +258,92 @@ public static function detectMimeType($file) // for old PHP (deprecated) if (function_exists('mime_content_type')) return mime_content_type($file); - + // for PHP > 5.3.0 / PECL FileInfo > 0.1.0 if (function_exists('finfo_file')) { - $finfo = finfo_open(FILEINFO_MIME); + $finfo = finfo_open(FILEINFO_MIME); $mimetype = finfo_file($finfo, $file); finfo_close($finfo); + return $mimetype; } - + $os = self::detectOS(); // for Unix OS : command line if ($os == 'UNIX') { $mimetype = trim(exec('file -b -i ' . escapeshellarg($file))); - $parts = explode(';', $mimetype); + $parts = explode(';', $mimetype); + return trim($parts[0]); } - + // fallback for Windows and Others OS // source code found at : // @link http://fr2.php.net/manual/en/function.mime-content-type.php#87856 - $mime_types = array( - 'txt' => 'text/plain', - 'htm' => 'text/html', + $mime_types = [ + 'txt' => 'text/plain', + 'htm' => 'text/html', 'html' => 'text/html', - 'php' => 'text/html', - 'css' => 'text/css', - 'js' => 'application/javascript', + 'php' => 'text/html', + 'css' => 'text/css', + 'js' => 'application/javascript', 'json' => 'application/json', - 'xml' => 'application/xml', - 'swf' => 'application/x-shockwave-flash', - 'flv' => 'video/x-flv', - + 'xml' => 'application/xml', + 'swf' => 'application/x-shockwave-flash', + 'flv' => 'video/x-flv', + // images - 'png' => 'image/png', - 'jpe' => 'image/jpeg', + 'png' => 'image/png', + 'jpe' => 'image/jpeg', 'jpeg' => 'image/jpeg', - 'jpg' => 'image/jpeg', - 'gif' => 'image/gif', - 'bmp' => 'image/bmp', - 'ico' => 'image/vnd.microsoft.icon', + 'jpg' => 'image/jpeg', + 'gif' => 'image/gif', + 'bmp' => 'image/bmp', + 'ico' => 'image/vnd.microsoft.icon', 'tiff' => 'image/tiff', - 'tif' => 'image/tiff', - 'svg' => 'image/svg+xml', + 'tif' => 'image/tiff', + 'svg' => 'image/svg+xml', 'svgz' => 'image/svg+xml', - + // archives - 'zip' => 'application/zip', - 'rar' => 'application/x-rar-compressed', - 'exe' => 'application/x-msdownload', - 'msi' => 'application/x-msdownload', - 'cab' => 'application/vnd.ms-cab-compressed', - + 'zip' => 'application/zip', + 'rar' => 'application/x-rar-compressed', + 'exe' => 'application/x-msdownload', + 'msi' => 'application/x-msdownload', + 'cab' => 'application/vnd.ms-cab-compressed', + // audio/video - 'mp3' => 'audio/mpeg', - 'qt' => 'video/quicktime', - 'mov' => 'video/quicktime', - + 'mp3' => 'audio/mpeg', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + // adobe - 'pdf' => 'application/pdf', - 'psd' => 'image/vnd.adobe.photoshop', - 'ai' => 'application/postscript', - 'eps' => 'application/postscript', - 'ps' => 'application/postscript', - + 'pdf' => 'application/pdf', + 'psd' => 'image/vnd.adobe.photoshop', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + // ms office - 'doc' => 'application/msword', - 'rtf' => 'application/rtf', - 'xls' => 'application/vnd.ms-excel', - 'ppt' => 'application/vnd.ms-powerpoint', - + 'doc' => 'application/msword', + 'rtf' => 'application/rtf', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + // open office - 'odt' => 'application/vnd.oasis.opendocument.text', - 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', - ); - + 'odt' => 'application/vnd.oasis.opendocument.text', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + ]; + $ext = strtolower(array_pop(explode('.', $file))); if (array_key_exists($ext, $mime_types)) { return $mime_types[$ext]; - } else { + } + else { return 'application/octet-stream'; } } - + /** * Determinate the Server OS * @@ -320,26 +353,35 @@ public static function detectMimeType($file) public static function detectOS() { $os = php_uname('s'); - if (stripos($os, 'win') !== false) return 'WIN'; - if (stripos($os, 'linux') !== false || stripos($os, 'unix') !== false) return 'UNIX'; + if (stripos($os, 'win') !== false) + return 'WIN'; + if (stripos($os, 'linux') !== false || stripos($os, 'unix') !== false) + return 'UNIX'; + return 'OTHER'; } - + + /** + * @param $partner_from + * @param $partner_to + * + * @throws AS2Exception + */ public function initialize($partner_from, $partner_to) { try { $this->partner_from = $this->partnerFactory->getPartner($partner_from); - } catch (Exception $e) { + } catch (\Exception $e) { throw new AS2Exception('Sender AS2 id "' . $partner_from . '" is unknown.'); } - + try { $this->partner_to = $this->partnerFactory->getPartner($partner_to); - } catch (Exception $e) { + } catch (\Exception $e) { throw new AS2Exception('Receiver AS2 id "' . $partner_to . '" is unknown.'); } } - + /** * Generate a mime multipart file from files * @@ -351,31 +393,26 @@ public function compose($files) { try { if (!is_array($files) || !count($files)) - throw new Exception('No file provided.'); - + throw new \Exception('No file provided.'); + $args = ''; foreach ($files as $file) { - $args .= ' -file ' . escapeshellarg($file['path']) . - ' -mimetype ' . escapeshellarg($file['mimetype']) . - ' -name ' . escapeshellarg($file['filename']); + $args .= ' -file ' . escapeshellarg($file['path']) . ' -mimetype ' . escapeshellarg($file['mimetype']) . ' -name ' . escapeshellarg($file['filename']); } - + $output = self::getTempFilename(); - + // execute main operation - $command = self::$javapath . ' -jar ' . escapeshellarg($this->AS2_DIR_BIN . self::$ssl_adapter) . - ' compose' . - $args . - ' -out ' . escapeshellarg($output); - + $command = self::$javapath . ' -jar ' . escapeshellarg($this->AS2_DIR_BIN . self::$ssl_adapter) . ' compose' . $args . ' -out ' . escapeshellarg($output); + $result = self::exec($command); - + return $output; - } catch (Exception $e) { + } catch (\Exception $e) { throw $e; } } - + /** * Extract files from mime multipart file * @@ -387,38 +424,37 @@ public function extract($input) { try { $output = self::getTempFilename(); - + // execute main operation - $command = self::$javapath . ' -jar ' . escapeshellarg($this->AS2_DIR_BIN . self::$ssl_adapter) . - ' extract' . - ' -in ' . escapeshellarg($input) . - ' -out ' . escapeshellarg($output); + $command = self::$javapath . ' -jar ' . escapeshellarg($this->AS2_DIR_BIN . self::$ssl_adapter) . ' extract' . ' -in ' . escapeshellarg($input) . ' -out ' . escapeshellarg($output); $results = self::exec($command, true); - + // array returned - $files = array(); - + $files = []; + foreach ($results as $tmp) { $tmp = explode(';', $tmp); - if (count($tmp) <= 1) continue; - if (count($tmp) != 3) throw new AS2Exception('Unexpected data structure while extracting message.'); - - $file = array(); - $file['path'] = trim($tmp[0], '"'); + if (count($tmp) <= 1) + continue; + if (count($tmp) != 3) + throw new AS2Exception('Unexpected data structure while extracting message.'); + + $file = []; + $file['path'] = trim($tmp[0], '"'); $file['mimetype'] = trim($tmp[1], '"'); $file['filename'] = trim($tmp[2], '"'); - $files[] = $file; - + $files[] = $file; + // schedule file deletion Adapter::addTempFileForDelete($file['path']); } - + return $files; - } catch (Exception $e) { + } catch (\Exception $e) { throw $e; } } - + /** * Schedule file for deletion * @@ -426,12 +462,15 @@ public function extract($input) public static function addTempFileForDelete($file) { if (is_null(self::$tmp_files)) { - self::$tmp_files = array(); - register_shutdown_function(array('Adapter', '_deleteTempFiles')); + self::$tmp_files = []; + register_shutdown_function([ + 'Adapter', + '_deleteTempFiles' + ]); } self::$tmp_files[] = $file; } - + /** * Compress a file which contains mime headers * @@ -443,21 +482,18 @@ public function compress($input) { try { $output = self::getTempFilename(); - + // execute main operation - $command = self::$javapath . ' -jar ' . escapeshellarg($this->AS2_DIR_BIN . self::$ssl_adapter) . - ' compress' . - ' -in ' . escapeshellarg($input) . - ' -out ' . escapeshellarg($output); - + $command = self::$javapath . ' -jar ' . escapeshellarg($this->AS2_DIR_BIN . self::$ssl_adapter) . ' compress' . ' -in ' . escapeshellarg($input) . ' -out ' . escapeshellarg($output); + $result = self::exec($command); - + return $output; - } catch (Exception $e) { + } catch (\Exception $e) { throw $e; } } - + /** * Decompress a file which contains mime headers * @@ -469,27 +505,24 @@ public function decompress($input) { try { $output = self::getTempFilename(); - + // execute main operation - $command = self::$javapath . ' -jar ' . escapeshellarg($this->AS2_DIR_BIN . self::$ssl_adapter) . - ' decompress' . - ' -in ' . escapeshellarg($input) . - ' -out ' . escapeshellarg($output); - + $command = self::$javapath . ' -jar ' . escapeshellarg($this->AS2_DIR_BIN . self::$ssl_adapter) . ' decompress' . ' -in ' . escapeshellarg($input) . ' -out ' . escapeshellarg($output); + $result = self::exec($command); - + return $output; - } catch (Exception $e) { + } catch (\Exception $e) { throw $e; } } - + /** * Sign a file which contains mime headers * - * @param string $input The file to sign + * @param string $input The file to sign * @param boolean $use_zlib Use Zlib to compress main container - * @param string $encoding Encoding to use for main container (base64 | binary) + * @param string $encoding Encoding to use for main container (base64 | binary) * * @return string The content signed */ @@ -497,33 +530,26 @@ public function sign($input, $use_zlib = false, $encoding = 'base64') { try { if (!$this->partner_from->sec_pkcs12) - throw new Exception('Config error : PKCS12 (' . $this->partner_from->id . ')'); - + throw new \Exception('Config error : PKCS12 (' . $this->partner_from->id . ')'); + $password = ($this->partner_from->sec_pkcs12_password ? ' -password ' . escapeshellarg($this->partner_from->sec_pkcs12_password) : ' -nopassword'); - - if ($use_zlib) $compress = ' -compress'; + + if ($use_zlib) + $compress = ' -compress'; else $compress = ''; - + $output = self::getTempFilename(); - + // execute main operation - $command = self::$javapath . ' -jar ' . escapeshellarg($this->AS2_DIR_BIN . self::$ssl_adapter) . - ' sign' . - ' -pkcs12 ' . escapeshellarg($this->partner_from->sec_pkcs12) . - $password . - $compress . - ' -encoding ' . escapeshellarg(strtolower($encoding)) . - ' -in ' . escapeshellarg($input) . - ' -out ' . escapeshellarg($output) . - ' >/dev/null'; - $result = self::exec($command); - + $command = self::$javapath . ' -jar ' . escapeshellarg($this->AS2_DIR_BIN . self::$ssl_adapter) . ' sign' . ' -pkcs12 ' . escapeshellarg($this->partner_from->sec_pkcs12) . $password . $compress . ' -encoding ' . escapeshellarg(strtolower($encoding)) . ' -in ' . escapeshellarg($input) . ' -out ' . escapeshellarg($output) . ' >/dev/null'; + $result = self::exec($command); + return $output; - } catch (Exception $e) { + } catch (\Exception $e) { throw $e; } } - + /** * Verify a signed file * @@ -535,33 +561,27 @@ public function verify($input) { try { if ($this->partner_from->sec_pkcs12) - $security = ' -pkcs12 ' . escapeshellarg($this->partner_from->sec_pkcs12) . - ($this->partner_from->sec_pkcs12_password ? ' -password ' . escapeshellarg($this->partner_from->sec_pkcs12_password) : ' -nopassword'); + $security = ' -pkcs12 ' . escapeshellarg($this->partner_from->sec_pkcs12) . ($this->partner_from->sec_pkcs12_password ? ' -password ' . escapeshellarg($this->partner_from->sec_pkcs12_password) : ' -nopassword'); else $security = ' -cert ' . escapeshellarg($this->partner_from->sec_certificate); - + $output = self::getTempFilename(); - - $command = self::$javapath . ' -jar ' . escapeshellarg($this->AS2_DIR_BIN . self::$ssl_adapter) . - ' verify' . - $security . - ' -in ' . escapeshellarg($input) . - ' -out ' . escapeshellarg($output) . - ' >/dev/null 2>&1'; - + + $command = self::$javapath . ' -jar ' . escapeshellarg($this->AS2_DIR_BIN . self::$ssl_adapter) . ' verify' . $security . ' -in ' . escapeshellarg($input) . ' -out ' . escapeshellarg($output) . ' >/dev/null 2>&1'; + // on error, an exception is throw $result = self::exec($command); - + return $output; - } catch (Exception $e) { + } catch (\Exception $e) { throw $e; } } - + /** * Encrypt a file * - * @param string $input The file to encrypt + * @param string $input The file to encrypt * @param string $cypher The Cypher to use for encryption * * @return string The message encrypted @@ -573,30 +593,25 @@ public function encrypt($input, $cypher = 'des3') $certificate = self::getPublicFromPKCS12($this->partner_to->sec_pkcs12, $this->partner_to->sec_pkcs12_password); else $certificate = $this->partner_to->sec_certificate; - - if (!$certificate) throw new Exception('Unable to extract private key from PKCS12 file. (' . $this->partner_to->sec_pkcs12 . ' - using:' . $this->partner_to->sec_pkcs12_password . ')'); - + + if (!$certificate) + throw new \Exception('Unable to extract private key from PKCS12 file. (' . $this->partner_to->sec_pkcs12 . ' - using:' . $this->partner_to->sec_pkcs12_password . ')'); + $output = self::getTempFilename(); - - $command = self::$ssl_openssl . ' smime ' . - ' -encrypt' . - ' -in ' . escapeshellarg($input) . - ' -out ' . escapeshellarg($output) . - ' -des3 ' . escapeshellarg($certificate); - $result = $this->exec($command); - - $headers = 'Content-Type: application/pkcs7-mime; smime-type="enveloped-data"; name="smime.p7m"' . "\n" . - 'Content-Disposition: attachment; filename="smime.p7m"' . "\n" . - 'Content-Transfer-Encoding: binary' . "\n\n"; + + $command = self::$ssl_openssl . ' smime ' . ' -encrypt' . ' -in ' . escapeshellarg($input) . ' -out ' . escapeshellarg($output) . ' -des3 ' . escapeshellarg($certificate); + $result = $this->exec($command); + + $headers = 'Content-Type: application/pkcs7-mime; smime-type="enveloped-data"; name="smime.p7m"' . "\n" . 'Content-Disposition: attachment; filename="smime.p7m"' . "\n" . 'Content-Transfer-Encoding: binary' . "\n\n"; $content = file_get_contents($output); - + // we remove header auto-added by openssl $content = substr($content, strpos($content, "\n\n") + 2); $content = base64_decode($content); - + $content = $headers . $content; file_put_contents($output, $content); - + /*if ($this->partner_to->sec_pkcs12) $security = ' -pkcs12 '.escapeshellarg($this->partner_to->sec_pkcs12). ($this->partner_to->sec_pkcs12_password?' -password '.escapeshellarg($this->partner_to->sec_pkcs12_password):' -nopassword'); @@ -611,13 +626,13 @@ public function encrypt($input, $cypher = 'des3') ' -out '.escapeshellarg($output); $result = $this->exec($command);*/ - + return $output; - } catch (Exception $e) { + } catch (\Exception $e) { throw $e; } } - + /** * Fix the content type to match with RFC 2311 * @@ -638,11 +653,11 @@ public function encrypt($input, $cypher = 'des3') $content = str_replace('Content-Type: ' . $from, 'Content-Type: ' . $to, $content); file_put_contents($file, $content); }*/ - + /** * Extract Public certificate from a PKCS12 Certificate * - * @param string $input The PKCS12 Certificate + * @param string $input The PKCS12 Certificate * @param string $password The PKCS12 Certificate's password * * @return string The file which contains the Public Certificate @@ -651,7 +666,7 @@ public static function getPublicFromPKCS12($input, $password = '') { return self::getDataFromPKCS12($input, 'cert', $password); } - + /** * Decrypt a message * @@ -665,20 +680,16 @@ public function decrypt($input) file_put_contents('/tmp/decrypt', 'try to decrypt file :'."\n", FILE_APPEND); file_put_contents('/tmp/decrypt', '---------------------------------------------------------------'."\n", FILE_APPEND); file_put_contents('/tmp/decrypt', print_r(file_get_contents($input), true)."\n", FILE_APPEND);*/ - + try { $private_key = self::getPrivateFromPKCS12($this->partner_to->sec_pkcs12, $this->partner_to->sec_pkcs12_password, ''); if (!$private_key) throw new AS2Exception('Unable to extract private key from PKCS12 file. (' . $this->partner_to->sec_pkcs12 . ' - using:' . $this->partner_to->sec_pkcs12_password . ')'); - + $output = self::getTempFilename(); - - $command = self::$ssl_openssl . ' smime ' . - ' -decrypt ' . - ' -in ' . escapeshellarg($input) . - ' -inkey ' . escapeshellarg($private_key) . - ' -out ' . escapeshellarg($output); - + + $command = self::$ssl_openssl . ' smime ' . ' -decrypt ' . ' -in ' . escapeshellarg($input) . ' -inkey ' . escapeshellarg($private_key) . ' -out ' . escapeshellarg($output); + // seems to generate non-conform message /*$security = ' -pkcs12 '.escapeshellarg($this->partner_to->sec_pkcs12). ($this->partner_to->sec_pkcs12_password?' -password '.escapeshellarg($this->partner_to->sec_pkcs12_password):' -nopassword'); @@ -689,19 +700,19 @@ public function decrypt($input) ' -in '.escapeshellarg($input). ' -out '.escapeshellarg($output). ' >/dev/null';*/ - + $result = $this->exec($command); - + return $output; - } catch (Exception $e) { + } catch (\Exception $e) { throw $e; } } - + /** * Extract Private Certificate from a PKCS12 Certificate * - * @param string $input The PKCS12 Certificate + * @param string $input The PKCS12 Certificate * @param string $password The PKCS12 Certificate's password * * @return string The file which contains the Private Certificate diff --git a/Models/Client.php b/Models/Client.php index b0c46b9..775b99a 100644 --- a/Models/Client.php +++ b/Models/Client.php @@ -1,9 +1,11 @@ + * @author Sebastien MALOT * * @copyright Copyright (c) 2010, Sebastien MALOT * @@ -24,28 +26,47 @@ * You should have received a copy of the GNU General Public License * along with AS2Secure. * - * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License - * @version 0.9.0 + * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License + * @version 0.9.0 * */ use TechData\AS2SecureBundle\Factories\Request as RequestFactory; +/** + * Class Client + * + * @package TechData\AS2SecureBundle\Models + */ class Client { /** * @var RequestFactory */ private $requestFactory; - protected $response_headers = array(); + /** + * @var array + */ + protected $response_headers = []; + /** + * @var string + */ protected $response_content = ''; + /** + * @var int + */ protected $response_indice = 0; - + + /** + * Client constructor. + * + * @param RequestFactory $requestFactory + */ public function __construct(RequestFactory $requestFactory) { $this->requestFactory = $requestFactory; } - + /** * Send request to the partner (manage headers, security, ...) * @@ -55,16 +76,17 @@ public function __construct(RequestFactory $requestFactory) */ public function sendRequest($request) { - if (!$request instanceof Message && !$request instanceof MDN) throw new AS2Exception('Unexpected format'); - + if (!$request instanceof Message && !$request instanceof MDN){ + throw new AS2Exception('Unexpected format'); + } // format headers $headers = $request->getHeaders()->toFormattedArray(); - + // initialize variables for building response headers with curl - $this->response_headers = array(); + $this->response_headers = []; $this->response_content = ''; - $this->response_indice = 0; - + $this->response_indice = 0; + $log = "Header: " . implode(',', $headers) . PHP_EOL; // send as2 message with headers $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $request->getUrl()); @@ -80,8 +102,12 @@ public function sendRequest($request) curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $request->getContent()); curl_setopt($ch, CURLOPT_USERAGENT, 'AS2Secure - PHP Lib for AS2 message encoding / decoding'); - curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'handleResponseHeader')); - + curl_setopt($ch, CURLOPT_HEADERFUNCTION, [ + $this, + 'handleResponseHeader' + ]); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + $log .= " Content:" . $request->getContent(); // authentication setup $auth = $request->getAuthentication(); if ($auth['method'] != Partner::METHOD_NONE) { @@ -89,34 +115,32 @@ public function sendRequest($request) curl_setopt($ch, CURLOPT_USERPWD, urlencode($auth['login']) . ':' . urlencode($auth['password'])); } $response = curl_exec($ch); - $info = curl_getinfo($ch); - $error = curl_error($ch); + $info = curl_getinfo($ch); + $error = curl_error($ch); curl_close($ch); - $this->response_content = $response; - - // handle http error response - if ($info['http_code'] != 200) - throw new AS2Exception('HTTP Error Code : ' . $info['http_code'] . '(url:' . $request->getUrl() . ').'); - - // handle curl error - if ($error) - throw new AS2Exception($error); - + + $log .= " Response code:" . $info['http_code']; + if ($request instanceof Message && $request->getPartnerTo()->mdn_request == Partner::ACK_SYNC) { $temp_response = $this->requestFactory->build($response, new Header($this->response_headers[count($this->response_headers) - 1])); - $as2_response = $temp_response->getObject(); + $as2_response = $temp_response->getObject(); $as2_response->decode(); - } else { + } + else { $as2_response = null; } - - return array('request' => $request, - 'headers' => $this->response_headers[count($this->response_headers) - 1], + + return [ + 'request' => $request, + 'headers' => ($this->response_headers) ? $this->response_headers[count($this->response_headers) - 1] : $this->response_headers, 'response' => $as2_response, - 'info' => $info); + 'info' => $info, + 'error' => $error, + 'log' => $log + ]; } - + /** * Return the last request : headers/content * @@ -124,14 +148,16 @@ public function sendRequest($request) */ public function getLastResponse() { - return array('headers' => $this->response_headers[count($this->response_headers) - 1], - 'content' => $this->response_content); + return [ + 'headers' => $this->response_headers[count($this->response_headers) - 1], + 'content' => $this->response_content + ]; } - + /** * Allow to retrieve HTTP headers even if there is HTTP redirections * - * @param object $curl The curl instance + * @param object $curl The curl instance * @param string $header The header received * * @return int The length of current received header @@ -140,10 +166,13 @@ protected function handleResponseHeader($curl, $header) { if (!trim($header) && isset($this->response_headers[$this->response_indice]) && count($this->response_headers[$this->response_indice])) { $this->response_indice++; - } else { + } + else { $pos = strpos($header, ':'); - if ($pos !== false) $this->response_headers[$this->response_indice][trim(strtolower(substr($header, 0, $pos)))] = trim(substr($header, $pos + 1)); + if ($pos !== false) + $this->response_headers[$this->response_indice][trim(strtolower(substr($header, 0, $pos)))] = trim(substr($header, $pos + 1)); } + return strlen($header); } } diff --git a/Models/Header.php b/Models/Header.php index 18d9074..6a1c265 100644 --- a/Models/Header.php +++ b/Models/Header.php @@ -1,9 +1,11 @@ + * @author Sebastien MALOT * * @copyright Copyright (c) 2010, Sebastien MALOT * @@ -24,8 +26,8 @@ * You should have received a copy of the GNU General Public License * along with AS2Secure. * - * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License - * @version 0.9.0 + * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License + * @version 0.9.0 * */ @@ -35,19 +37,20 @@ class Header implements Countable, ArrayAccess, Iterator { - protected $headers = array(); - + protected $headers = []; + protected $_position = null; - + public function __construct($data = null) { if (is_array($data)) { $this->headers = $data; - } elseif ($data instanceof Header) { + } + elseif ($data instanceof Header) { $this->headers = $data->getHeaders(); } } - + /** * Reset all current headers with new values * @@ -55,10 +58,10 @@ public function __construct($data = null) */ public function setHeaders($headers) { - $this->headers = array(); + $this->headers = []; $this->addHeaders($headers); } - + /** * Add new header (or override current one) * @@ -69,7 +72,7 @@ public function addHeader($key, $value) { $this->headers[$key] = "$value"; } - + /** * Add a set of headers (or override currents) * @@ -80,7 +83,7 @@ public function addHeaders($values) foreach ($values as $key => $value) $this->addHeader($key, $value); } - + /** * Add a set of headers extracted from a mime message * @@ -94,7 +97,7 @@ public function addHeadersFromMessage($message) $this->addHeader($key, $value); } } - + /** * Remove an header * @@ -104,7 +107,7 @@ public function removeHeader($key) { unset($this->headers[$key]); } - + /** * Return all headers as an array * @@ -114,7 +117,7 @@ public function getHeaders() { return $this->headers; } - + /** * Return all headers as a formatted array * @@ -122,13 +125,14 @@ public function getHeaders() */ public function toFormattedArray() { - $tmp = array(); + $tmp = []; foreach ($this->headers as $key => $val) { $tmp[] = $key . ': ' . $val; } + return $tmp; } - + /** * Return the value of an header * @@ -140,10 +144,13 @@ public function getHeader($key) { $key = strtolower($key); $tmp = array_change_key_case($this->headers); - if (isset($tmp[$key])) return $tmp[$key]; + if (isset($tmp[$key])) { + return trim($tmp[$key], '"'); + } + return false; } - + /** * Return the count of headers * @@ -153,7 +160,7 @@ public function count() { return count($this->headers); } - + /** * Check if an header exists * @@ -164,9 +171,10 @@ public function count() public function exists($key) { $tmp = array_change_key_case($this->headers); + return array_key_exists(strtolower($key), $tmp); } - + /** * Magic method that returns headers serialized as in mime message * @@ -175,68 +183,69 @@ public function exists($key) public function __toString() { $ret = ''; - + foreach ($this->headers as $key => $value) { $ret .= $key . ': ' . $value . "\n"; } - + return rtrim($ret); } - + /***************************/ /** ArrayAccess interface **/ /***************************/ - + public function offsetExists($offset) { return array_key_exists($this->headers, $offset); } - + public function offsetGet($offset) { return $this->headers[$offset]; } - + public function offsetSet($offset, $value) { $this->headers[$offset] = $value; } - + public function offsetUnset($offset) { unset($this->headers[$offset]); } - + /************************/ /** Iterator interface **/ /************************/ - + public function current() { return $this->headers[$this->key()]; } - + public function key() { $keys = array_keys($this->headers); + return $keys[$this->_position]; } - + public function next() { $this->_position++; } - + public function rewind() { $this->_position = 0; } - + public function valid() { return ($this->_position >= 0 && $this->_position < count($this->headers)); } - + /** * Extract headers from mime message and return a new instance of Header * @@ -246,19 +255,24 @@ public function valid() */ protected function parseText($text) { - if (strpos($text, "\n\n") !== false) $text = substr($text, 0, strpos($text, "\n\n")); + if (strpos($text, "\n\n") !== false) + $text = substr($text, 0, strpos($text, "\n\n")); $text = rtrim($text) . "\n"; - - $matches = array(); + + $matches = []; preg_match_all('/(.*?):\s*(.*?\n(\s.*?\n)*)/', $text, $matches); if ($matches) { - foreach ($matches[2] as &$value) $value = trim(str_replace(array("\r", "\n"), ' ', $value)); + foreach ($matches[2] as &$value) + $value = trim(str_replace([ + "\r", + "\n" + ], ' ', $value)); unset($value); if (count($matches[1]) && count($matches[1]) == count($matches[2])) { return array_combine($matches[1], $matches[2]); } } - - return array(); + + return []; } } diff --git a/Models/Horde/MIME.php b/Models/Horde/MIME.php index f93666e..ae14c71 100644 --- a/Models/Horde/MIME.php +++ b/Models/Horde/MIME.php @@ -108,7 +108,7 @@ class Horde_MIME * * @return boolean True if it does, false if it doesn't. */ - public function is8bit($string, $charset = null) + public static function is8bit($string, $charset = null) { /* ISO-2022-JP is a 7bit charset, but it is an 8bit representation so * it needs to be entirely encoded. */ @@ -127,7 +127,7 @@ public function is8bit($string, $charset = null) * @return string The text, encoded only if it contains non-ASCII * characters. */ - public function encode($text, $charset = null) + public static function encode($text, $charset = null) { if (is_null($charset)) { $charset = 'UTF-8'; @@ -172,7 +172,7 @@ public function encode($text, $charset = null) * @return string The text, encoded only if it contains non-ASCII * characters. */ - protected function _encode($text, $charset) + protected static function _encode($text, $charset) { $encoded = trim(base64_encode($text)); $c_size = strlen($charset) + 7; @@ -207,7 +207,7 @@ protected function _encode($text, $charset) * * @return string The quoted-printable encoded string. */ - public function quotedPrintableEncode($text, $eol) + public static function quotedPrintableEncode($text, $eol) { $line = $output = ''; $curr_length = 0; @@ -266,7 +266,7 @@ public function quotedPrintableEncode($text, $eol) * @return string The text, encoded only if it contains non-ascii * characters */ - public function encodeAddress($addresses, $charset = null, $defserver = null) + public static function encodeAddress($addresses, $charset = null, $defserver = null) { if (is_array($addresses)) { $addr_arr = $addresses; @@ -277,7 +277,7 @@ public function encodeAddress($addresses, $charset = null, $defserver = null) return $addresses; } - $parser = &new Mail_RFC822(); + $parser = new \Mail_RFC822(); $addr_arr = $parser->parseAddressList($addresses, $defserver, true, false); } @@ -315,7 +315,7 @@ public function encodeAddress($addresses, $charset = null, $defserver = null) * * @return string The decoded text. */ - public function decode($string, $to_charset = null) + public static function decode($string, $to_charset = null) { if (($pos = strpos($string, '=?')) === false) { return $string; @@ -387,7 +387,7 @@ public function decode($string, $to_charset = null) * * @return string The decoded text. */ - public function decodeAddrString($string, $to_charset = null) + public static function decodeAddrString($string, $to_charset = null) { $addr_list = array(); foreach (Horde_MIME::parseAddressList($string) as $ob) { @@ -408,7 +408,7 @@ public function decodeAddrString($string, $to_charset = null) * * @return array The encoded parameter string. */ - public function encodeRFC2231($name, $string, $charset, $lang = null) + public static function encodeRFC2231($name, $string, $charset, $lang = null) { $encode = $wrap = false; $output = array(); @@ -462,7 +462,7 @@ public function encodeRFC2231($name, $string, $charset, $lang = null) * @return array The decoded text, or the original string if it was not * encoded. */ - public function decodeRFC2231($string, $to_charset = null) + public static function decodeRFC2231($string, $to_charset = null) { if (($pos = strpos($string, '*')) === false) { return false; @@ -849,7 +849,7 @@ protected function _rfc822Encode($str, $type = 'address') * * @return mixed See above. */ - public function type($input, $format = null) + public static function type($input, $format = null) { return Horde_MIME::_getCode($input, $format, 'mime_types'); } @@ -864,7 +864,7 @@ public function type($input, $format = null) * * @return mixed See above. */ - public function encoding($input, $format = null) + public static function encoding($input, $format = null) { return Horde_MIME::_getCode($input, $format, 'mime_encodings'); } @@ -882,7 +882,7 @@ public function encoding($input, $format = null) * * @return mixed See above. */ - protected function _getCode($input, $format, $type) + protected static function _getCode($input, $format, $type) { $numeric = is_numeric($input); if (!$numeric) { @@ -899,7 +899,7 @@ protected function _getCode($input, $format, $type) break; } - $vars = get_class_vars('Horde_MIME'); + $vars = get_class_vars('TechData\AS2SecureBundle\Models\Horde\Horde_MIME'); if ($numeric) { if (isset($vars[$type][$input])) { @@ -938,7 +938,7 @@ public function generateMessageID() * * @return string The header text, with linebreaks inserted. */ - public function wrapHeaders($header, $text, $eol = "\r\n") + public static function wrapHeaders($header, $text, $eol = "\r\n") { $header = rtrim($header); $text = rtrim($text); diff --git a/Models/Horde/MIME/Message.php b/Models/Horde/MIME/Message.php index e1096d9..dd9611b 100644 --- a/Models/Horde/MIME/Message.php +++ b/Models/Horde/MIME/Message.php @@ -1,4 +1,5 @@ * @package Horde_MIME */ +/** + * Class Horde_MIME_Message + * + * @package TechData\AS2SecureBundle\Models\Horde\MIME + */ class Horde_MIME_Message extends Horde_MIME_Part { - + /** * Has the message been parsed via buildMessage()? * * @var boolean */ protected $_build = false; - + /** * The server to default unqualified addresses to. * * @var string */ protected $_defaultServer = null; - + /** * Constructor - creates a new MIME email message. * - * @param string $defaultServer The server to default unqualified + * @param string $defaultServer The server to default unqualified * addresses to. */ public function __construct($defaultServer = null) { if (is_null($defaultServer) && isset($_SERVER['SERVER_NAME'])) { $this->_defaultServer = $_SERVER['SERVER_NAME']; - } else { + } + else { $this->_defaultServer = $defaultServer; } } - + /** * Create a MIME_Message object from a MIME_Part object. * This public function can be called statically via: * MIME_Message::convertMIMEPart(); * - * @param MIME_Part &$mime_part The MIME_Part object. - * @param string $server The server to default unqualified + * @param MIME_Part &$mime_part The MIME_Part object. + * @param string $server The server to default unqualified * addresses to. * * @return MIME_Message The new MIME_Message object. @@ -63,44 +70,61 @@ public function &convertMIMEPart(&$mime_part, $server = null) if (!$mime_part->getMIMEId()) { $mime_part->setMIMEId(1); } - - $mime_message = &new Horde_MIME_Message($server); + + $mime_message = new Horde_MIME_Message($server); $mime_message->addPart($mime_part); $mime_message->buildMessage(); - + return $mime_message; } - + /** * Take a set of headers and make sure they are encoded properly. * - * @param array $headers The headers to encode. + * @param array $headers The headers to encode. * @param string $charset The character set to use. * * @return array The array of encoded headers. */ public function encode($headers, $charset) { - $addressKeys = array('To', 'Cc', 'Bcc', 'From'); - $asciikeys = array('MIME-Version', 'Received', 'Message-ID', 'Date', 'Content-Disposition', 'Content-Transfer-Encoding', 'Content-ID', 'Content-Type', 'Content-Description'); + $addressKeys = [ + 'To', + 'Cc', + 'Bcc', + 'From' + ]; + $asciikeys = [ + 'MIME-Version', + 'Received', + 'Message-ID', + 'Date', + 'Content-Disposition', + 'Content-Transfer-Encoding', + 'Content-ID', + 'Content-Type', + 'Content-Description' + ]; foreach ($headers as $key => $val) { if (is_array($val)) { foreach ($val as $key2 => $val2) { $headers[$key][$key2] = Horde_MIME::wrapHeaders($key, $val2, $this->getEOL()); } - } else { + } + else { if (in_array($key, $addressKeys)) { $text = Horde_MIME::encodeAddress($val, $charset, $this->_defaultServer); - } else { + } + else { $text = Horde_MIME::encode($val, in_array($key, $asciikeys) ? 'US-ASCII' : $charset); } $headers[$key] = Horde_MIME::wrapHeaders($key, $text, $this->getEOL()); } } - + return $headers; } - + /** * Add the proper set of MIME headers for this message to an array. * @@ -108,19 +132,21 @@ public function encode($headers, $charset) * * @return array The full set of headers including MIME headers. */ - public function header($headers = array()) + public function header($headers = []) { /* Per RFC 2045 [4], this MUST appear in the message headers. */ $headers['MIME-Version'] = '1.0'; - + if ($this->_build) { return parent::header($headers); - } else { + } + else { $this->buildMessage(); + return $this->encode($this->header($headers), $this->getCharset()); } } - + /** * Return the entire message contents, including headers, as a string. * @@ -130,12 +156,14 @@ public function toString($headers = false) { if ($this->_build) { return parent::toString($headers); - } else { + } + else { $this->buildMessage(); + return $this->toString($headers); } } - + /** * Build message from current contents. */ @@ -144,11 +172,12 @@ public function buildMessage() if ($this->_build) { return; } - + if (empty($this->_flags['setType'])) { if (count($this->_parts) > 1) { $this->setType('multipart/mixed'); - } else { + } + else { /* Copy the information from the single part to the current base part. */ if (($obVars = get_object_vars(reset($this->_parts)))) { @@ -158,11 +187,11 @@ public function buildMessage() } } } - + /* Set the build flag now. */ $this->_build = true; } - + /** * Get a list of all MIME subparts. * @@ -172,12 +201,14 @@ public function getParts() { if ($this->_build) { return parent::getParts(); - } else { + } + else { $this->buildMessage(); + return $this->getParts(); } } - + /** * Return the base part of the message. This public function does NOT * return a reference to make sure that the whole MIME_Message @@ -188,9 +219,10 @@ public function getParts() public function getBasePart() { $this->buildMessage(); + return $this; } - + /** * Retrieve a specific MIME part. * @@ -203,13 +235,17 @@ public function &getPart($id) { if ($this->_build) { $part = parent::getPart($id); - } else { + } + else { $this->buildMessage(); $part = $this->getPart($id); } if (is_a($part, 'Horde_MIME_Message')) { - $newpart = &new Horde_MIME_Part(); - $skip = array('_build', '_defaultServer'); + $newpart = new Horde_MIME_Part(); + $skip = [ + '_build', + '_defaultServer' + ]; foreach (array_keys(get_object_vars($part)) as $key) { /* Ignore local variables that aren't a part of the original * class. */ @@ -217,10 +253,12 @@ public function &getPart($id) $newpart->$key = &$part->$key; } } + return $newpart; - } else { + } + else { return $part; } } - + } diff --git a/Models/Horde/MIME/Part.php b/Models/Horde/MIME/Part.php index ea922e4..6d5a883 100644 --- a/Models/Horde/MIME/Part.php +++ b/Models/Horde/MIME/Part.php @@ -1,8 +1,16 @@ ID mapping cache. * * @var array */ - protected $_idmap = array(); - + protected $_idmap = []; + /** * Unique MIME_Part boundary string. * * @var string */ protected $_boundary = null; - + /** * Default value for this Part's size. * * @var integer */ protected $_bytes = 0; - + /** * The content-ID for this part. * * @var string */ protected $_contentid = null; - + /** * MIME_Part constructor. * - * @param string $mimetype The content type of the part. - * @param string $contents The body of the part. - * @param string $charset The character set of the part. + * @param string $mimetype The content type of the part. + * @param string $contents The body of the part. + * @param string $charset The character set of the part. * @param string $disposition The content disposition of the part. - * @param string $encoding The content encoding of the contents. + * @param string $encoding The content encoding of the contents. */ - public function __construct($mimetype = null, $contents = null, - $charset = MIME_DEFAULT_CHARSET, $disposition = null, - $encoding = null) + public function __construct($mimetype = null, $contents = null, $charset = MIME_DEFAULT_CHARSET, $disposition = null, $encoding = null) { /* Create the unique MIME_Part boundary string. */ $this->_generateBoundary(); - + /* The character set should always be set, even if we are dealing * with Content-Types other than text/*. */ $this->setCharset($charset); - + if (!is_null($mimetype)) { $this->setType($mimetype); } @@ -227,22 +233,22 @@ public function __construct($mimetype = null, $contents = null, $this->setDisposition($disposition); } } - + /** * Set the content-disposition of this part. * - * @param string $disposition The content-disposition to set (inline or + * @param string $disposition The content-disposition to set (inline or * attachment). */ public function setDisposition($disposition) { $disposition = Horde_String::lower($disposition); - + if (($disposition == 'inline') || ($disposition == 'attachment')) { $this->_disposition = $disposition; } } - + /** * Get the content-disposition of this part. * @@ -252,7 +258,7 @@ public function getDisposition() { return $this->_disposition; } - + /** * Set the name of this part. * TODO: MIME encode here instead of in header() - add a charset @@ -264,12 +270,12 @@ public function setName($name) { $this->setContentTypeParameter('name', $name); } - + /** * Get the name of this part. * - * @param boolean $decode MIME decode description? - * @param boolean $default If the name parameter doesn't exist, should we + * @param boolean $decode MIME decode description? + * @param boolean $default If the name parameter doesn't exist, should we * use the default name from the description * parameter? * @@ -278,18 +284,19 @@ public function setName($name) public function getName($decode = false, $default = false) { $name = $this->getContentTypeParameter('name'); - + if ($default && empty($name)) { $name = preg_replace('|\W|', '_', $this->getDescription(false, true)); } - + if ($decode) { return trim(Horde_MIME::decode($name)); - } else { + } + else { return $name; } } - + /** * Set the body contents of this part. * @@ -298,17 +305,17 @@ public function getName($decode = false, $default = false) */ public function setContents($contents, $encoding = null) { - $this->_contents = $contents; + $this->_contents = $contents; $this->_flags['contentsSet'] = true; - $this->_currentEncoding = (is_null($encoding)) ? $this->getCurrentEncoding() : Horde_MIME::encoding($encoding, MIME_STRING); + $this->_currentEncoding = (is_null($encoding)) ? $this->getCurrentEncoding() : Horde_MIME::encoding($encoding, MIME_STRING); } - + /** * Add to the body contents of this part. * - * @param string $contents The contents to append to the current part + * @param string $contents The contents to append to the current part * body. - * @param string $encoding The current encoding of the contents. If not + * @param string $encoding The current encoding of the contents. If not * specified, will try to auto determine the * encoding. */ @@ -316,17 +323,17 @@ public function appendContents($contents, $encoding = null) { $this->setContents($this->_contents . $contents, $encoding); } - + /** * Clears the body contents of this part. */ public function clearContents() { - $this->_contents = ''; + $this->_contents = ''; $this->_flags['contentsSet'] = false; - $this->_currentEncoding = null; + $this->_currentEncoding = null; } - + /** * Return the body of the part. * @@ -336,7 +343,7 @@ public function getContents() { return $this->_contents; } - + /** * Returns the contents in strict RFC 822 & 2045 output - namely, all * newlines end with the canonical sequence. @@ -347,28 +354,28 @@ public function getCanonicalContents() { return $this->replaceEOL($this->_contents, MIME_PART_RFC_EOL); } - + /** * Transfer encode the contents (to the transfer encoding identified via * getTransferEncoding()) and set as the part's new contents. */ public function transferEncodeContents() { - $contents = $this->transferEncode(); + $contents = $this->transferEncode(); $this->_currentEncoding = $this->_flags['lastTransferEncode']; $this->setContents($contents, $this->_currentEncoding); $this->setTransferEncoding($this->_currentEncoding); } - + /** * Transfer decode the contents and set them as the new contents. */ public function transferDecodeContents() { - $contents = $this->transferDecode(); + $contents = $this->transferDecode(); $this->_currentEncoding = $this->_flags['lastTransferDecode']; $this->setTransferEncoding($this->_currentEncoding); - + /* Don't set contents if they are empty, because this will do stuff like reset the internal bytes field, even though we shouldn't do that (the user has their reasons to set the bytes field to a @@ -377,7 +384,7 @@ public function transferDecodeContents() $this->setContents($contents, $this->_currentEncoding); } } - + /** * Set the mimetype of this part. * @@ -391,32 +398,34 @@ public function setType($mimetype) if ($this->_transferEncoding == 'x-unknown') { return; } - + /* Set the 'setType' flag. */ $this->_flags['setType'] = true; - + list($this->_type, $this->_subtype) = explode('/', Horde_String::lower($mimetype)); if (($type = Horde_MIME::type($this->_type, MIME_STRING))) { $this->_type = $type; - + /* Set the boundary string for 'multipart/*' parts. */ if ($type == 'multipart') { if (!$this->getContentTypeParameter('boundary')) { $this->setContentTypeParameter('boundary', $this->_generateBoundary()); } - } else { + } + else { $this->clearContentTypeParameter('boundary'); } - } else { + } + else { $this->_type = 'x-unknown'; $this->clearContentTypeParameter('boundary'); } } - + /** * Get the full MIME Content-Type of this part. * - * @param boolean $charset Append character set information to the end of + * @param boolean $charset Append character set information to the end of * the content type if this is a text/* part. * * @return string The mimetype of this part @@ -428,13 +437,14 @@ public function getType($charset = false) return false; } $ptype = $this->getPrimaryType(); - $type = $ptype . '/' . $this->getSubType(); + $type = $ptype . '/' . $this->getSubType(); if ($charset && ($ptype == 'text')) { $type .= '; charset=' . $this->getCharset(); } + return $type; } - + /** * If the subtype of a MIME part is unrecognized by an application, the * default type should be used instead (See RFC 2046). This method @@ -448,18 +458,18 @@ public function getDefaultType() case 'text': /* RFC 2046 (4.1.4): text parts default to text/plain. */ return 'text/plain'; - + case 'multipart': /* RFC 2046 (5.1.3): multipart parts default to multipart/mixed. */ return 'multipart/mixed'; - + default: /* RFC 2046 (4.2, 4.3, 4.4, 4.5.3, 5.2.4): all others default to application/octet-stream. */ return 'application/octet-stream'; } } - + /** * Get the primary type of this part. * @@ -469,7 +479,7 @@ public function getPrimaryType() { return $this->_type; } - + /** * Get the subtype of this part. * @@ -479,7 +489,7 @@ public function getSubType() { return $this->_subtype; } - + /** * Set the character set of this part. * @@ -489,7 +499,7 @@ public function setCharset($charset) { $this->setContentTypeParameter('charset', $charset); } - + /** * Get the character set to use for of this part. Returns a charset for * all types (not just 'text/*') since we use this charset to determine @@ -501,9 +511,10 @@ public function setCharset($charset) public function getCharset() { $charset = $this->getContentTypeParameter('charset'); + return (empty($charset)) ? null : $charset; } - + /** * Set the description of this part. * @@ -513,12 +524,12 @@ public function setDescription($description) { $this->_description = Horde_MIME::encode($description, $this->getCharset()); } - + /** * Get the description of this part. * - * @param boolean $decode MIME decode description? - * @param boolean $default If the name parameter doesn't exist, should we + * @param boolean $decode MIME decode description? + * @param boolean $default If the name parameter doesn't exist, should we * use the default name from the description * parameter? * @@ -527,21 +538,22 @@ public function setDescription($description) public function getDescription($decode = false, $default = false) { $desc = $this->_description; - + if ($default && empty($desc)) { $desc = $this->getName(); if (empty($desc)) { $desc = MIME_DEFAULT_DESCRIPTION; } } - + if ($decode) { return Horde_MIME::decode($desc); - } else { + } + else { return $desc; } } - + /** * Set the transfer encoding to use for this part. * @@ -551,7 +563,8 @@ public function setTransferEncoding($encoding) { if (($mime_encoding = Horde_MIME::encoding($encoding, MIME_STRING))) { $this->_transferEncoding = $mime_encoding; - } else { + } + else { /* RFC 2045: Any entity with unrecognized encoding must be treated as if it has a Content-Type of "application/octet-stream" regardless of what the Content-Type field actually says. */ @@ -559,42 +572,44 @@ public function setTransferEncoding($encoding) $this->_transferEncoding = 'x-unknown'; } } - + /** * Add a MIME subpart. * - * @param MIME_Part $mime_part Add a MIME_Part subpart to the current + * @param MIME_Part $mime_part Add a MIME_Part subpart to the current * MIME_Part. - * @param string $index The index of the added MIME_Part. + * @param string $index The index of the added MIME_Part. */ public function addPart($mime_part, $index = null) { /* Add the part to the parts list. */ if (is_null($index)) { end($this->_parts); - $id = key($this->_parts) + 1; + $id = key($this->_parts) + 1; $ptr = &$this->_parts; - } else { + } + else { $ptr = &$this->_partFind($index, $this->_parts, true); if (($pos = strrpos($index, '.'))) { $id = substr($index, $pos + 1); - } else { + } + else { $id = $index; } } - + /* Set the MIME ID if it has not already been set. */ if ($mime_part->getMIMEId() === null) { $mime_part->setMIMEId($id); } - + /* Store the part now. */ $ptr[$id] = $mime_part; - + /* Clear the ID -> Part mapping cache. */ - $this->_idmap = array(); + $this->_idmap = []; } - + /** * Get a list of all MIME subparts. * @@ -604,7 +619,7 @@ public function getParts() { return $this->_parts; } - + /** * Retrieve a specific MIME part. * @@ -616,41 +631,35 @@ public function getParts() public function getPart($id) { $mimeid = $this->getMIMEId(); - + /* This will convert '#.0' to simply '#', which is how the part is * internally stored. */ $search_id = $id; - if (($str = strrchr($id, '.')) && - ($str == '.0') - ) { + if (($str = strrchr($id, '.')) && ($str == '.0')) { $search_id = substr($search_id, 0, -2); } - + /* Return this part if: 1) There is only one part (e.g. the MIME ID is 0, or the MIME ID is 1 and there are no subparts. 2) $id matches this parts MIME ID. */ - if (($search_id == 0) || - (($search_id == 1) && !count($this->_parts)) || - (!empty($mimeid) && ($search_id == $mimeid)) - ) { + if (($search_id == 0) || (($search_id == 1) && !count($this->_parts)) || (!empty($mimeid) && ($search_id == $mimeid))) { $part = $this; - } else { + } + else { $part = $this->_partFind($id, $this->_parts); } - - if ($part && - ($search_id != $id) && - ($part->getType() == 'message/rfc822') - ) { - $ret_part = Horde_Util::cloneObject($part); - $ret_part->_parts = array(); + + if ($part && ($search_id != $id) && ($part->getType() == 'message/rfc822')) { + $ret_part = Horde_Util::cloneObject($part); + $ret_part->_parts = []; + return $ret_part; } - + return $part; } - + /** * Remove a MIME_Part subpart. * @@ -660,32 +669,32 @@ public function removePart($id) { if (($ptr = &$this->_partFind($id, $this->_parts))) { unset($ptr); - $this->_idmap = array(); + $this->_idmap = []; } } - + /** * Alter a current MIME subpart. * - * @param string $id The MIME Part ID to alter. + * @param string $id The MIME Part ID to alter. * @param MIME_Part $mime_part The MIME Part to store. */ public function alterPart($id, $mime_part) { if (($ptr = &$this->_partFind($id, $this->_parts))) { - $ptr = $mime_part; - $this->_idmap = array(); + $ptr = $mime_part; + $this->_idmap = []; } } - + /** * Function used to find a specific MIME Part by ID. * * @access private * - * @param string $id The MIME_Part ID string. - * @param array &$parts A list of MIME_Part objects. - * @param boolean $retarray Return a pointer to the array that stores + * @param string $id The MIME_Part ID string. + * @param array &$parts A list of MIME_Part objects. + * @param boolean $retarray Return a pointer to the array that stores * (would store) the part rather than the part * itself? */ @@ -697,26 +706,29 @@ public function &_partFind($id, &$parts, $retarray = false) * of MIME_Parts or an array of arrays. */ $check = reset($this->_idmap); if (empty($check) || !is_a($check, 'MIME_Part')) { - $this->_idmap = array(); + $this->_idmap = []; $this->_generateIdMap($this->_parts); } - + if ($retarray) { if ($pos = strrpos($id, '.')) { $id = substr($id, 0, $pos); - } else { + } + else { return $parts; } } - + if (isset($this->_idmap[$id])) { return $this->_idmap[$id]; - } else { + } + else { $part = false; + return $part; } } - + /** * Generates a mapping of MIME_Parts with their MIME IDs. * @@ -728,24 +740,24 @@ protected function _generateIdMap(&$parts) { if (!empty($parts)) { foreach (array_keys($parts) as $key) { - $ptr = &$parts[$key]; + $ptr = &$parts[$key]; $this->_idmap[$ptr->getMIMEId()] = &$ptr; $this->_generateIdMap($ptr->_parts); } } } - + /** * Add information about the MIME_Part. * * @param string $label The information label. - * @param mixed $data The information to store. + * @param mixed $data The information to store. */ public function setInformation($label, $data) { $this->_information[$label] = $data; } - + /** * Retrieve information about the MIME_Part. * @@ -758,18 +770,18 @@ public function getInformation($label) { return (isset($this->_information[$label])) ? $this->_information[$label] : false; } - + /** * Add a disposition parameter to this part. * * @param string $label The disposition parameter label. - * @param string $data The disposition parameter data. + * @param string $data The disposition parameter data. */ public function setDispositionParameter($label, $data) { $this->_dispositionParameters[$label] = $data; } - + /** * Get a disposition parameter from this part. * @@ -782,7 +794,7 @@ public function getDispositionParameter($label) { return (isset($this->_dispositionParameters[$label])) ? $this->_dispositionParameters[$label] : false; } - + /** * Get all parameters from the Content-Disposition header. * @@ -793,29 +805,29 @@ public function getAllDispositionParameters() { return $this->_dispositionParameters; } - + /** * Add a content type parameter to this part. * * @param string $label The disposition parameter label. - * @param string $data The disposition parameter data. + * @param string $data The disposition parameter data. */ public function setContentTypeParameter($label, $data) { $this->_contentTypeParameters[$label] = $data; } - + /** * Clears a content type parameter from this part. * * @param string $label The disposition parameter label. - * @param string $data The disposition parameter data. + * @param string $data The disposition parameter data. */ public function clearContentTypeParameter($label) { unset($this->_contentTypeParameters[$label]); } - + /** * Get a content type parameter from this part. * @@ -828,7 +840,7 @@ public function getContentTypeParameter($label) { return (isset($this->_contentTypeParameters[$label])) ? $this->_contentTypeParameters[$label] : false; } - + /** * Get all parameters from the Content-Type header. * @@ -839,7 +851,7 @@ public function getAllContentTypeParameters() { return $this->_contentTypeParameters; } - + /** * Sets a new string to use for EOLs. * @@ -849,7 +861,7 @@ public function setEOL($eol) { $this->_eol = $eol; } - + /** * Get the string to use for EOLs. * @@ -859,7 +871,7 @@ public function getEOL() { return $this->_eol; } - + /** * Add the appropriate MIME headers for this part to an existing array. * @@ -867,14 +879,14 @@ public function getEOL() * * @return array The headers, with the MIME headers added. */ - public function header($headers = array()) + public function header($headers = []) { - $eol = $this->getEOL(); + $eol = $this->getEOL(); $ptype = $this->getPrimaryType(); - + /* Get the character set for this part. */ $charset = $this->getCharset(); - + /* Get the Content-Type - this is ALWAYS required. */ $ctype = $this->getType(true); foreach ($this->getAllContentTypeParameters() as $key => $value) { @@ -896,23 +908,23 @@ public function header($headers = array()) $ctype .= '; ' . $encode_2231; } $headers['Content-Type'] = Horde_MIME::wrapHeaders('Content-Type', $ctype, $eol); - + /* Get the description, if any. */ if (($descrip = $this->getDescription())) { $headers['Content-Description'] = Horde_MIME::wrapHeaders('Content-Description', Horde_MIME::encode($descrip, $charset), $eol); } - + /* message/* parts require no additional header information. */ if ($ptype == 'message') { return $headers; } - + /* Don't show Content-Disposition for multipart messages unless there is a name parameter. */ $name = $this->getName(); if (($ptype != 'multipart') || !empty($name)) { $disp = $this->getDisposition(); - + /* Add any disposition parameter information, if available. */ if (!empty($name)) { $encode_2231 = Horde_MIME::encodeRFC2231('filename', $name, $charset); @@ -924,21 +936,21 @@ public function header($headers = array()) }*/ $disp .= '; ' . $encode_2231; } - + $headers['Content-Disposition'] = Horde_MIME::wrapHeaders('Content-Disposition', $disp, $eol); } - + /* Add transfer encoding information. */ $headers['Content-Transfer-Encoding'] = $this->getTransferEncoding(); - + /* Add content ID information. */ if (!is_null($this->_contentid)) { $headers['Content-ID'] = $this->_contentid; } - + return $headers; } - + /** * Return the entire part in MIME format. Includes headers on request. * @@ -948,9 +960,9 @@ public function header($headers = array()) */ public function toString($headers = true) { - $eol = $this->getEOL(); + $eol = $this->getEOL(); $ptype = $this->getPrimaryType(); - + if ($headers) { $text = ''; foreach ($this->header() as $key => $val) { @@ -958,24 +970,26 @@ public function toString($headers = true) } $text .= $eol; } - + /* Any information about a message/* is embedded in the message contents themself. Simply output the contents of the part directly and return. */ if ($ptype == 'message') { if (isset($text)) { return $text . $this->_contents; - } else { + } + else { return $this->_contents; } } - + if (isset($text)) { $text .= $this->transferEncode(); - } else { + } + else { $text = $this->transferEncode(); } - + /* Deal with multipart messages. */ if ($ptype == 'multipart') { $boundary = trim($this->getContentTypeParameter('boundary'), '"'); @@ -984,7 +998,7 @@ public function toString($headers = true) } reset($this->_parts); while (list(, $part) = each($this->_parts)) { - $text .= $eol . '--' . $boundary . $eol; + $text .= $eol . '--' . $boundary . $eol; $oldEOL = $part->getEOL(); $part->setEOL($eol); $text .= $part->toString(true); @@ -992,10 +1006,10 @@ public function toString($headers = true) } $text .= $eol . '--' . $boundary . '--' . $eol; } - + return $text; } - + /** * Returns the encoded part in strict RFC 822 & 2045 output - namely, all * newlines end with the canonical sequence. @@ -1007,9 +1021,10 @@ public function toString($headers = true) public function toCanonicalString($headers = true) { $string = $this->toString($headers); + return $this->replaceEOL($string, MIME_PART_RFC_EOL); } - + /** * Should we make sure the message is encoded via 7-bit (e.g. to adhere * to mail delivery standards such as RFC 2821)? @@ -1020,7 +1035,7 @@ public function strict7bit($use7bit) { $this->_encode7bit = $use7bit; } - + /** * Get the transfer encoding for the part based on the user requested * transfer encoding and the current contents of the part. @@ -1030,15 +1045,15 @@ public function strict7bit($use7bit) public function getTransferEncoding() { $encoding = $this->_transferEncoding; - $ptype = $this->getPrimaryType(); - $text = str_replace($this->getEOL(), ' ', $this->_contents); - + $ptype = $this->getPrimaryType(); + $text = str_replace($this->getEOL(), ' ', $this->_contents); + /* If there are no contents, return whatever the current value of $_transferEncoding is. */ if (empty($text)) { return $encoding; } - + switch ($ptype) { case 'message': /* RFC 2046 [5.2.1] - message/rfc822 messages only allow 7bit, @@ -1048,12 +1063,13 @@ public function getTransferEncoding() only allow 7bit encodings. */ $encoding = ($this->getSubType() == 'rfc822') ? '8bit' : '7bit'; break; - + case 'text': $eol = $this->getEOL(); if (Horde_MIME::is8bit($text)) { $encoding = ($this->_encode7bit) ? 'quoted-printable' : '8bit'; - } elseif (preg_match("/(?:" . $eol . "|^)[^" . $eol . "]{999,}(?:" . $eol . "|$)/", $this->_contents)) { + } + elseif (preg_match("/(?:" . $eol . "|^)[^" . $eol . "]{999,}(?:" . $eol . "|$)/", $this->_contents)) { /* If the text is longer than 998 characters between * linebreaks, use quoted-printable encoding to ensure the * text will not be chopped (i.e. by sendmail if being sent @@ -1061,27 +1077,25 @@ public function getTransferEncoding() $encoding = 'quoted-printable'; } break; - + default: if (Horde_MIME::is8bit($text)) { $encoding = ($this->_encode7bit) ? 'base64' : '8bit'; } break; } - + /* Need to do one last check for binary data if encoding is 7bit or * 8bit. If the message contains a NULL character at all, the message * MUST be in binary format. RFC 2046 [2.7, 2.8, 2.9]. Q-P and base64 * can handle binary data fine so no need to switch those encodings. */ - if ((($encoding == '8bit') || ($encoding == '7bit')) && - preg_match('/\x00/', $text) - ) { + if ((($encoding == '8bit') || ($encoding == '7bit')) && preg_match('/\x00/', $text)) { $encoding = ($this->_encode7bit) ? 'base64' : 'binary'; } - + return $encoding; } - + /** * Retrieves the current encoding of the contents in the object. * @@ -1091,7 +1105,7 @@ public function getCurrentEncoding() { return (is_null($this->_currentEncoding)) ? $this->_transferEncoding : $this->_currentEncoding; } - + /** * Encodes the contents with the part's transfer encoding. * @@ -1100,44 +1114,42 @@ public function getCurrentEncoding() public function transferEncode() { $encoding = $this->getTransferEncoding(); - $eol = $this->getEOL(); - + $eol = $this->getEOL(); + /* Set the 'lastTransferEncode' flag so that transferEncodeContents() can save a call to getTransferEncoding(). */ $this->_flags['lastTransferEncode'] = $encoding; - + /* If contents are empty, or contents are already encoded to the correct encoding, return now. */ - if (!strlen($this->_contents) || - ($encoding == $this->_currentEncoding) - ) { + if (!strlen($this->_contents) || ($encoding == $this->_currentEncoding)) { return $this->_contents; } - + switch ($encoding) { /* Base64 Encoding: See RFC 2045, section 6.8 */ case 'base64': /* Keeping these two lines separate seems to use much less memory than combining them (as of PHP 4.3). */ $encoded_contents = base64_encode($this->_contents); + return rtrim(chunk_split($encoded_contents, 76, $eol), $eol); - + /* Quoted-Printable Encoding: See RFC 2045, section 6.7 */ case 'quoted-printable': $output = Horde_MIME::quotedPrintableEncode($this->_contents, $eol); - if (($eollength = Horde_String::length($eol)) && - (substr($output, $eollength * -1) == $eol) - ) { + if (($eollength = Horde_String::length($eol)) && (substr($output, $eollength * -1) == $eol)) { return substr($output, 0, $eollength * -1); - } else { + } + else { return $output; } - + default: return $this->replaceEOL($this->_contents); } } - + /** * Decodes the contents of the part to either a 7bit or 8bit encoding. * @@ -1147,26 +1159,27 @@ public function transferEncode() public function transferDecode() { $encoding = $this->getCurrentEncoding(); - + /* If the contents are empty, return now. */ if (!strlen($this->_contents)) { $this->_flags['lastTransferDecode'] = $encoding; + return $this->_contents; } - + switch ($encoding) { case 'base64': - $message = base64_decode($this->_contents); + $message = base64_decode($this->_contents); $this->_flags['lastTransferDecode'] = '8bit'; break; - + case 'quoted-printable': - $message = preg_replace("/=\r?\n/", '', $this->_contents); - $message = $this->replaceEOL($message); - $message = quoted_printable_decode($message); + $message = preg_replace("/=\r?\n/", '', $this->_contents); + $message = $this->replaceEOL($message); + $message = quoted_printable_decode($message); $this->_flags['lastTransferDecode'] = (Horde_MIME::is8bit($message)) ? '8bit' : '7bit'; break; - + /* Support for uuencoded encoding - although not required by RFCs, some mailers may still encode this way. */ case 'uuencode': @@ -1174,28 +1187,28 @@ public function transferDecode() case 'x-uue': if (function_exists('convert_uudecode')) { $message = convert_uuencode($this->_contents); - } else { - $files = &Mail_mimeDecode::uudecode($this->_contents); + } + else { + $files = \Mail_mimeDecode::uudecode($this->_contents); $message = $files[0]['filedata']; } $this->_flags['lastTransferDecode'] = '8bit'; break; - + default: - if (isset($this->_flags['lastTransferDecode']) && - ($this->_flags['lastTransferDecode'] != $encoding) - ) { + if (isset($this->_flags['lastTransferDecode']) && ($this->_flags['lastTransferDecode'] != $encoding)) { $message = $this->replaceEOL($this->_contents); - } else { + } + else { $message = $this->_contents; } $this->_flags['lastTransferDecode'] = $encoding; break; } - + return $message; } - + /** * Split the contents of the current Part into its respective subparts, * if it is multipart MIME encoding. Unlike the imap_*() public functions, this @@ -1212,18 +1225,19 @@ public function splitContents() if (!($boundary = $this->getContentTypeParameter('boundary'))) { return false; } - + if (!strlen($this->_contents)) { return false; } - - $eol = $this->getEOL(); + + $eol = $this->getEOL(); $retvalue = false; - + $boundary = '--' . $boundary; if (substr($this->_contents, 0, strlen($boundary)) == $boundary) { $pos1 = 0; - } else { + } + else { $pos1 = strpos($this->_contents, $eol . $boundary); } if ($pos1 === false) { @@ -1234,10 +1248,10 @@ public function splitContents() return false; } $pos1 += strlen($eol); - + reset($this->_parts); $part_ptr = key($this->_parts); - + while ($pos2 = strpos($this->_contents, $eol . $boundary, $pos1)) { $this->_parts[$part_ptr]->setContents(substr($this->_contents, $pos1, $pos2 - $pos1)); $this->_parts[$part_ptr]->splitContents(); @@ -1252,16 +1266,16 @@ public function splitContents() } $pos1 += strlen($eol); } - + return true; } - + /** * Replace newlines in this part's contents with those specified by either * the given newline sequence or the part's current EOL setting. * - * @param string $text The text to replace. - * @param string $eol The EOL sequence to use. If not present, uses the + * @param string $text The text to replace. + * @param string $eol The EOL sequence to use. If not present, uses the * part's current EOL setting. * * @return string The text with the newlines replaced by the desired @@ -1272,9 +1286,10 @@ public function replaceEOL($text, $eol = null) if (is_null($eol)) { $eol = $this->getEOL(); } + return preg_replace("/\r?\n/", $eol, $text); } - + /** * Return the unique MIME_Part boundary string generated for this object. * This may not be the boundary string used when building the message @@ -1287,7 +1302,7 @@ public function getUniqueID() { return $this->_boundary; } - + /** * Determine the size of a MIME_Part and its child members. * @@ -1296,10 +1311,11 @@ public function getUniqueID() public function getBytes() { $bytes = 0; - + if (empty($this->_flags['contentsSet']) && $this->_bytes) { $bytes = $this->_bytes; - } elseif ($this->getPrimaryType() == 'multipart') { + } + elseif ($this->getPrimaryType() == 'multipart') { reset($this->_parts); while (list(, $part) = each($this->_parts)) { /* Skip multipart entries (since this may result in double @@ -1308,17 +1324,19 @@ public function getBytes() $bytes += $part->getBytes(); } } - } else { + } + else { if ($this->getPrimaryType() == 'text') { $bytes = Horde_String::length($this->_contents, $this->getCharset()); - } else { + } + else { $bytes = strlen($this->_contents); } } - + return $bytes; } - + /** * Explicitly set the size (in bytes) of this part. This value will only * be returned (via getBytes()) if there are no contents currently set. @@ -1333,19 +1351,19 @@ public function setBytes($bytes) { $this->_bytes = $bytes; } - + /** * Add to the list of CIDs for this part. * - * @param array $cids A list of MIME IDs of the part. + * @param array $cids A list of MIME IDs of the part. * Key - MIME ID * Value - CID for the part */ - public function addCID($cids = array()) + public function addCID($cids = []) { $this->_cids += $cids; } - + /** * Returns the list of CIDs for this part. * @@ -1354,13 +1372,14 @@ public function addCID($cids = array()) public function getCIDList() { asort($this->_cids, SORT_STRING); + return $this->_cids; } - + /** * Sets the Content-ID header for this part. * - * @param string $cid Use this CID (if not already set). Else, generate a + * @param string $cid Use this CID (if not already set). Else, generate a * random CID. */ public function setContentID($cid = null) @@ -1368,9 +1387,10 @@ public function setContentID($cid = null) if (is_null($this->_contentid)) { $this->_contentid = (is_null($cid)) ? (base_convert(microtime(), 10, 36) . '@' . $_SERVER['SERVER_NAME']) : $cid; } + return $this->_contentid; } - + /** * Returns the Content-ID for this part. * @@ -1380,7 +1400,7 @@ public function getContentID() { return $this->_contentid; } - + /** * Alter the MIME ID of this part. * @@ -1390,7 +1410,7 @@ public function setMIMEId($mimeid) { $this->_mimeid = $mimeid; } - + /** * Returns the MIME ID of this part. * @@ -1400,7 +1420,7 @@ public function getMIMEId() { return $this->_mimeid; } - + /** * Returns the relative MIME ID of this part. * e.g., if the base part has MIME ID of 2, and you want the first @@ -1413,9 +1433,10 @@ public function getMIMEId() public function getRelativeMIMEId($id) { $rel = $this->getMIMEId(); + return (empty($rel)) ? $id : $rel . '.' . $id; } - + /** * Returns a mapping of all MIME IDs to their content-types. * @@ -1423,13 +1444,14 @@ public function getRelativeMIMEId($id) */ public function contentTypeMap() { - $map = array($this->getMIMEId() => $this->getType()); + $map = [$this->getMIMEId() => $this->getType()]; foreach ($this->_parts as $val) { $map += $val->contentTypeMap(); } + return $map; } - + /** * Generate the unique boundary string (if not already done). * @@ -1442,7 +1464,8 @@ protected function _generateBoundary() if (is_null($this->_boundary)) { $this->_boundary = '=_' . base_convert(microtime(), 10, 36); } + return $this->_boundary; } - + } diff --git a/Models/Horde/MIME/Structure.php b/Models/Horde/MIME/Structure.php index 76f3b1f..92148ce 100644 --- a/Models/Horde/MIME/Structure.php +++ b/Models/Horde/MIME/Structure.php @@ -1,5 +1,8 @@ addPart(Horde_MIME_Structure::_parse($body)); $msgOb->buildMessage(); @@ -63,7 +66,7 @@ protected static function &_parse($body, $ref = 0) $multipart = Horde_MIME::type('multipart'); } - $mime_part = &new Horde_MIME_Part(); + $mime_part = new Horde_MIME_Part(); /* Top multiparts don't get their own line. */ if (empty($ref) && @@ -287,7 +290,8 @@ public static function addMultipartInfo(&$parts, $info = array()) break; } - Horde_MIME_Structure::addMultipartInfo($ptr->getParts(), $new_info); + $parts = $ptr->getParts(); + Horde_MIME_Structure::addMultipartInfo($parts, $new_info); } } @@ -307,7 +311,7 @@ public static function &parseTextMIMEMessage($text) 'decode_headers' => false ); - $mimeDecode = &new Mail_mimeDecode($text, MIME_PART_EOL); + $mimeDecode = new \Mail_mimeDecode($text); if (!($structure = $mimeDecode->decode($decode_args))) { $message = false; } else { @@ -358,7 +362,7 @@ protected static function _convertMimeDecodeData(&$ob) $ob->$if_var = 1; $ob->$param_value = array(); foreach ($ob->$param_key as $key => $val) { - $newOb = &new stdClass; + $newOb = new \stdClass(); $newOb->attribute = $key; $newOb->value = $val; array_push($ob->$param_value, $newOb); diff --git a/Models/Horde/String.php b/Models/Horde/String.php index bfb2b18..e1cbee5 100644 --- a/Models/Horde/String.php +++ b/Models/Horde/String.php @@ -29,7 +29,7 @@ class Horde_String * * @see Util::extensionExists() */ - public function extensionExists($ext) + public static function extensionExists($ext) { static $cache = array(); @@ -46,7 +46,7 @@ public function extensionExists($ext) * * @param string $charset The charset to use as the default one. */ - public function setDefaultCharset($charset) + public static function setDefaultCharset($charset) { self::$charset = $charset; if (Horde_String::extensionExists('mbstring') && @@ -75,7 +75,7 @@ function_exists('mb_regex_encoding') * * @return mixed The converted input data. */ - public function convertCharset($input, $from, $to = null) + public static function convertCharset($input, $from, $to = null) { /* Don't bother converting numbers. */ if (is_numeric($input)) { @@ -128,7 +128,7 @@ public function convertCharset($input, $from, $to = null) * * @return string The converted string. */ - protected function _convertCharset($input, $from, $to) + protected static function _convertCharset($input, $from, $to) { $output = ''; $from_check = (($from == 'iso-8859-1') || ($from == 'us-ascii')); @@ -193,7 +193,7 @@ protected function _convertCharset($input, $from, $to) * * @return string The string with lowercase characters */ - public function lower($string, $locale = false, $charset = null) + public static function lower($string, $locale = false, $charset = null) { static $lowers; @@ -239,7 +239,7 @@ function_exists('mb_strtolower') * * @return string The string with uppercase characters */ - public function upper($string, $locale = false, $charset = null) + public static function upper($string, $locale = false, $charset = null) { static $uppers; @@ -284,7 +284,7 @@ public function upper($string, $locale = false, $charset = null) * * @return string The capitalized string. */ - public function ucfirst($string, $locale = false, $charset = null) + public static function ucfirst($string, $locale = false, $charset = null) { if ($locale) { $first = Horde_String::substr($string, 0, 1, $charset); @@ -309,7 +309,7 @@ public function ucfirst($string, $locale = false, $charset = null) * * @return string The string's part. */ - public function substr($string, $start, $length = null, $charset = null) + public static function substr($string, $start, $length = null, $charset = null) { if (is_null($length)) { $length = Horde_String::length($string, $charset) - $start; @@ -360,7 +360,7 @@ public function substr($string, $start, $length = null, $charset = null) * * @return string The string's part. */ - public function length($string, $charset = null) + public static function length($string, $charset = null) { if (is_null($charset)) { $charset = self::$charset; @@ -393,7 +393,7 @@ public function length($string, $charset = null) * * @return integer The position of first occurrence. */ - public function pos($haystack, $needle, $offset = 0, $charset = null) + public static function pos($haystack, $needle, $offset = 0, $charset = null) { if (Horde_String::extensionExists('mbstring')) { if (is_null($charset)) { @@ -427,7 +427,7 @@ public function pos($haystack, $needle, $offset = 0, $charset = null) * * @return string The padded string. */ - public function pad($input, $length, $pad = ' ', $type = STR_PAD_RIGHT, + public static function pad($input, $length, $pad = ' ', $type = STR_PAD_RIGHT, $charset = null) { $mb_length = Horde_String::length($input, $charset); @@ -484,7 +484,7 @@ public function pad($input, $length, $pad = ' ', $type = STR_PAD_RIGHT, * * @return string Horde_String containing the wrapped text. */ - public function wordwrap($string, $width = 75, $break = "\n", $cut = false, + public static function wordwrap($string, $width = 75, $break = "\n", $cut = false, $charset = null, $line_folding = false) { /* Get the user's default character set if none passed in. */ @@ -555,7 +555,7 @@ public function wordwrap($string, $width = 75, $break = "\n", $cut = false, * * @return string Horde_String containing the wrapped text. */ - public function wrap($text, $length = 80, $break_char = "\n", $charset = null, + public static function wrap($text, $length = 80, $break_char = "\n", $charset = null, $quote = false) { $paragraphs = array(); @@ -588,7 +588,7 @@ public function wrap($text, $length = 80, $break_char = "\n", $charset = null, * * @return boolean True if the parameter was alphabetic only. */ - public function isAlpha($string, $charset = null) + public static function isAlpha($string, $charset = null) { if (!Horde_String::extensionExists('mbstring')) { return ctype_alpha($string); @@ -620,7 +620,7 @@ public function isAlpha($string, $charset = null) * * @return boolean True if the parameter was lowercase. */ - public function isLower($string, $charset = null) + public static function isLower($string, $charset = null) { return ((Horde_String::lower($string, true, $charset) === $string) && Horde_String::isAlpha($string, $charset)); @@ -635,7 +635,7 @@ public function isLower($string, $charset = null) * * @return boolean True if the parameter was uppercase. */ - public function isUpper($string, $charset = null) + public static function isUpper($string, $charset = null) { return ((Horde_String::upper($string, true, $charset) === $string) && Horde_String::isAlpha($string, $charset)); @@ -653,7 +653,7 @@ public function isUpper($string, $charset = null) * * @return array The matches array from the first regex that matches. */ - public function regexMatch($text, $regex, $charset = null) + public static function regexMatch($text, $regex, $charset = null) { if (!empty($charset)) { $regex = Horde_String::convertCharset($regex, $charset, 'utf-8'); @@ -683,7 +683,7 @@ public function regexMatch($text, $regex, $charset = null) * * @return string The charset to use with mbstring public functions. */ - protected function _mbstringCharset($charset) + protected static function _mbstringCharset($charset) { /* mbstring public functions do not handle the 'ks_c_5601-1987' & * 'ks_c_5601-1989' charsets. However, these charsets are used, for diff --git a/Models/MDN.php b/Models/MDN.php index 29a7b72..5c4a7f5 100644 --- a/Models/MDN.php +++ b/Models/MDN.php @@ -1,10 +1,13 @@ + * @author Sebastien MALOT * * @copyright Copyright (c) 2010, Sebastien MALOT * @@ -25,11 +28,10 @@ * You should have received a copy of the GNU General Public License * along with AS2Secure. * - * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License - * @version 0.9.0 + * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License + * @version 0.9.0 * */ - class MDN extends AbstractBase { /** @@ -37,17 +39,41 @@ class MDN extends AbstractBase * http://rfclibrary.hosting.com/rfc/rfc4130/rfc4130-34.asp */ const ACTION_AUTO = 'automatic-action'; + /** + * + */ const ACTION_MANUAL = 'manual-action'; + /** + * + */ const SENDING_AUTO = 'MDN-sent-automatically'; + /** + * + */ const SENDING_MANUAL = 'MDN-sent-manually'; + /** + * + */ const TYPE_PROCESSED = 'processed'; + /** + * + */ const TYPE_FAILED = 'failed'; + /** + * + */ const MODIFIER_ERROR = 'error'; + /** + * + */ const MODIFIER_WARNING = 'warning'; /** * Human readable message */ protected $message = ''; + /** + * @var string + */ protected $url = ''; /** * Valid tokens : @@ -61,86 +87,104 @@ class MDN extends AbstractBase * reporting-ua : user-agent */ protected $attributes = null; - + + /** + * MDN constructor. + */ function __construct() { - + } - - - public function initialize($data = null, $params = array()) + + + /** + * @param null $data + * @param array $params + * + * @throws AS2Exception + */ + public function initialize($data = null, $params = []) { - - $this->attributes = new Header(array('action-mode' => self::ACTION_AUTO, - 'sending-mode' => self::SENDING_AUTO)); - + + $this->attributes = new Header([ + 'action-mode' => self::ACTION_AUTO, + 'sending-mode' => self::SENDING_AUTO + ]); + // adapter - if (!($data instanceof AS2Exception) && $data instanceof Exception) $data = new AS2Exception($data->getMessage(), 6); + if (!($data instanceof AS2Exception) && $data instanceof Exception) + $data = new AS2Exception($data->getMessage(), 6); // full automatic handling if ($data instanceof AS2Exception) { $this->setMessage($data->getMessage()); //$this->setHeaders($data->getHeaders()); $this->setAttribute('disposition-type', $data->getLevel()); $this->setAttribute('disposition-modifier', $data->getMessageShort()); - + try { $this->setPartnerFrom($params['partner_from']); - } catch (Exception $e) { + } catch (\Exception $e) { $this->partner_from = false; } try { $this->setPartnerTo($params['partner_to']); - } catch (Exception $e) { + } catch (\Exception $e) { $this->partner_to = false; } - } elseif ($data instanceof Request) { // parse response - $params = array('is_file' => false, - 'mimetype' => 'multipart/report', + } + elseif ($data instanceof Request) { // parse response + $params = [ + 'is_file' => false, + 'mimetype' => 'multipart/report', 'partner_from' => $data->getPartnerFrom(), - 'partner_to' => $data->getPartnerTo()); + 'partner_to' => $data->getPartnerTo() + ]; $this->initializeBase($data->getContent(), $params); - + // check requirements if ($this->partner_from->mdn_signed && !$data->isSigned()) { throw new AS2Exception('MDN from this partner are defined to be signed.', 4); } - } elseif ($data instanceof Message) { // standard processed message + } + elseif ($data instanceof Message) { // standard processed message $params['partner_from'] = $data->getPartnerTo(); - $params['partner_to'] = $data->getPartnerFrom(); - + $params['partner_to'] = $data->getPartnerFrom(); + $this->initializeBase(false, $params); - } elseif ($data instanceof Horde_MIME_Part) { + } + elseif ($data instanceof Horde_MIME_Part) { try { $this->setPartnerFrom($params['partner_from']); - } catch (Exception $e) { + } catch (\Exception $e) { $this->partner_from = false; } try { $this->setPartnerTo($params['partner_to']); - } catch (Exception $e) { + } catch (\Exception $e) { $this->partner_to = false; } - + $this->path = Adapter::getTempFilename(); file_put_contents($this->path, $data->toString(true)); - + $this->initializeBase(false, $params); - } else { + } + else { throw new AS2Exception('Not handled case.'); } } - + /** * Set attribute for computer readable message * - * @param string $key Token + * @param string $key Token * @param string $value Value */ public function setAttribute($key, $value) { $this->attributes->addHeader($key, $value); } - + /** * Return the human readable message * @@ -150,7 +194,7 @@ public function __toString() { return $this->getMessage(); } - + /** * Return the human readable message * @@ -160,7 +204,7 @@ public function getMessage() { return $this->message; } - + /** * Set the human readable message * @@ -170,7 +214,7 @@ public function setMessage($message) { $this->message = $message; } - + /** * Return the computer readable message * @@ -180,7 +224,7 @@ public function getAttributes() { return $this->attributes->getHeaders(); } - + /** * Encode and generate MDN from attributes and message (if exists) * @@ -190,15 +234,15 @@ public function encode($message = null) { // container $container = new Horde_MIME_Part('multipart/report', ' '); - + // first part $text = new Horde_MIME_Part('text/plain', $this->getMessage(), MIME_DEFAULT_CHARSET, null, '7bit'); // add human readable message $container->addPart($text); - + // second part $lines = new Header(); - $lines->addHeader('Reporting-UA', 'AS2Secure - PHP Lib for AS2 message encoding / decoding'); + $lines->addHeader('Reporting-UA', 'ETP_EETP'); if ($this->getPartnerFrom()) { $lines->addHeader('Original-Recipient', 'rfc822; "' . $this->getPartnerFrom()->id . '"'); $lines->addHeader('Final-Recipient', 'rfc822; "' . $this->getPartnerFrom()->id . '"'); @@ -211,64 +255,67 @@ public function encode($message = null) if ($this->getAttribute('received-content-mic')) { $lines->addHeader('Received-Content-MIC', $this->getAttribute('received-content-mic')); } - + // build computer readable message $mdn = new Horde_MIME_Part('message/disposition-notification', $lines, MIME_DEFAULT_CHARSET, null, '7bit'); $container->addPart($mdn); - + $this->setMessageId(self::generateMessageID($this->getPartnerFrom())); - + // headers setup - $this->headers = new Header(array('AS2-Version' => '1.0', - 'Message-ID' => $this->getMessageId(), + $this->headers = new Header([ + 'AS2-Version' => '1.0', + 'Message-ID' => $this->getMessageId(), 'Mime-Version' => '1.0', - 'Server' => 'AS2Secure - PHP Lib for AS2 message encoding / decoding', - 'User-Agent' => 'AS2Secure - PHP Lib for AS2 message encoding / decoding', - )); + 'Server' => 'AS2Secure - PHP Lib for AS2 message encoding / decoding', + 'User-Agent' => 'AS2Secure - PHP Lib for AS2 message encoding / decoding', + ]); $this->headers->addHeaders($container->header()); - + if ($this->getPartnerFrom()) { - $headers_from = array( - 'AS2-From' => '"' . $this->getPartnerFrom()->id . '"', - 'From' => $this->getPartnerFrom()->email, - 'Subject' => $this->getPartnerFrom()->mdn_subject, + $headers_from = [ + 'AS2-From' => $this->getPartnerFrom()->id, + 'From' => $this->getPartnerFrom()->email, + 'Subject' => $this->getPartnerFrom()->mdn_subject, 'Disposition-Notification-To' => $this->getPartnerFrom()->send_url, - ); + ]; $this->headers->addHeaders($headers_from); } - + if ($this->getPartnerTo()) { - $headers_to = array( - 'AS2-To' => '"' . $this->getPartnerTo()->id . '"', + $headers_to = [ + 'AS2-To' => $this->getPartnerTo()->id, 'Recipient-Address' => $this->getPartnerTo()->send_url, - ); + ]; $this->headers->addHeaders($headers_to); } - + if ($message && ($url = $message->getHeader('Receipt-Delivery-Option')) && $this->getPartnerFrom()) { $this->url = $url; $this->headers->addHeader('Recipient-Address', $this->getPartnerFrom()->send_url); } - + $this->path = Adapter::getTempFilename(); - + // signing if requested if ($message && $message->getHeader('Disposition-Notification-Options')) { file_put_contents($this->path, $container->toCanonicalString(true)); $this->path = $this->adapter->sign($this->path); - + $content = file_get_contents($this->path); $this->headers->addHeadersFromMessage($content); - + // TODO : replace with futur AS2MimePart to separate content from header - if (strpos($content, "\n\n") !== false) $content = substr($content, strpos($content, "\n\n") + 2); + if (strpos($content, "\n\n") !== false) + $content = substr($content, strpos($content, "\n\n") + 2); file_put_contents($this->path, ltrim($content)); - } else { + } + else { file_put_contents($this->path, $container->toCanonicalString(false)); $content = $container->toString(); } } - + /** * Return an attribute fromcomputer readable message * @@ -280,7 +327,7 @@ public function getAttribute($key) { return $this->attributes->getHeader($key); } - + /** * Decode MDN stored into path file and set attributes * @@ -288,19 +335,19 @@ public function getAttribute($key) public function decode() { // parse mime message - $params = array('include_bodies' => true, + $params = [ + 'include_bodies' => true, 'decode_headers' => true, - 'decode_bodies' => true, - 'input' => false, - 'crlf' => "\n" - ); - $decoder = new Mail_mimeDecode(file_get_contents($this->path)); + 'decode_bodies' => true, + 'input' => false, + 'crlf' => "\n" + ]; + $decoder = new \Mail_mimeDecode(file_get_contents($this->path)); $structure = $decoder->decode($params); - // reset values before decoding (for security reasons) $this->setMessage(''); $this->attributes = null; - + // should contains 2 parts foreach ($structure->parts as $num => $part) { if (strtolower($part->headers['content-type']) == 'message/disposition-notification') { @@ -312,13 +359,15 @@ public function decode() $headers = new Header(); $headers->addHeadersFromMessage($part->body); $this->attributes = $headers; - } else { + } + else { // human readable message $this->setMessage(trim($part->body)); } } + $this->setMessageId($this->getHeader('message-id')); } - + /** * Return the url to send message * diff --git a/Models/Mail/mimeDecode.php b/Models/Mail/mimeDecode.php index a0d0694..69504cc 100644 --- a/Models/Mail/mimeDecode.php +++ b/Models/Mail/mimeDecode.php @@ -165,7 +165,7 @@ class Mail_mimeDecode extends PEAR * @param string The input to decode * @access public */ - function Mail_mimeDecode($input) + function __construct($input) { list($header, $body) = $this->_splitBodyHeader($input); diff --git a/Models/Message.php b/Models/Message.php index ad72d46..7080fce 100644 --- a/Models/Message.php +++ b/Models/Message.php @@ -1,9 +1,11 @@ + * @author Sebastien MALOT * * @copyright Copyright (c) 2010, Sebastien MALOT * @@ -24,13 +26,19 @@ * You should have received a copy of the GNU General Public License * along with AS2Secure. * - * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License - * @version 0.9.0 + * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License + * @version 0.9.0 * */ use TechData\AS2SecureBundle\Factories\MDN as MDNFactory; +use TechData\AS2SecureBundle\Models\Horde\MIME\Horde_MIME_Part; +/** + * Class Message + * + * @package TechData\AS2SecureBundle\Models + */ class Message extends AbstractBase { // Injected Services @@ -38,44 +46,60 @@ class Message extends AbstractBase * @var MDNFactory */ private $mdnFactory; - - + + + /** + * @var bool + */ protected $mic_checksum = false; - + + /** + * Message constructor. + * + * @param MDNFactory $mdnFactory + */ function __construct(MDNFactory $mdnFactory) { $this->mdnFactory = $mdnFactory; } - - - public function initialize($data, $params = array()) + + + /** + * @param $data + * @param array $params + * + * @throws AS2Exception + */ + public function initialize($data, $params = []) { $this->initializeBase($data, $params); - + if ($data instanceof Request) { $this->path = $data->getPath(); - } elseif ($data instanceof Horde_MIME_Part) { + } + elseif ($data instanceof Horde_MIME_Part) { $this->path = Adapter::getTempFilename(); file_put_contents($this->path, $data->toString(true)); - } elseif ($data) { + } + elseif ($data) { if (!isset($params['is_file']) || $params['is_file']) $this->addFile($data, '', '', true); else $this->addFile($data, '', '', false); } - + if (isset($params['mic'])) { $this->mic_checksum = $params['mic']; } } - + /** * Add file to the message * - * @param string $data The content or the file - * @param string $mimetype The mimetype of the message - * @param boolean $is_file If file - * @param string $encoding The encoding to use for transfert + * @param string $data The content or the file + * @param string $mimetype The mimetype of the message + * @param boolean $is_file If file + * @param string $encoding The encoding to use for transfert * * @return boolean */ @@ -84,21 +108,24 @@ public function addFile($data, $mimetype = '', $filename = '', $is_file = true, if (!$is_file) { $file = Adapter::getTempFilename(); file_put_contents($file, $data); - $data = $file; - $is_file = true; - } else { - if (!$filename) $filename = basename($data); + $data = $file; } - - if (!$mimetype) $mimetype = Adapter::detectMimeType($data); - - $this->files[] = array('path' => $data, + if (!$filename){ + $filename = basename($data); + } + if (!$mimetype) + $mimetype = Adapter::detectMimeType($data); + + $this->files[] = [ + 'path' => $data, 'mimetype' => $mimetype, 'filename' => $filename, - 'encoding' => $encoding); + 'encoding' => $encoding + ]; + return true; } - + /** * Return files which compose the message (should contain at least one file) * @@ -108,7 +135,7 @@ public function getFiles() { return $this->files; } - + /** * Return the last calculated checksum * @@ -118,7 +145,7 @@ public function getMicChecksum() { return $this->mic_checksum; } - + /** * Return the url to send message * @@ -128,7 +155,7 @@ public function getUrl() { return $this->getPartnerTo()->send_url; } - + /** * Return the authentication to use to send message to the partner * @@ -136,11 +163,13 @@ public function getUrl() */ public function getAuthentication() { - return array('method' => $this->getPartnerTo()->send_credencial_method, - 'login' => $this->getPartnerTo()->send_credencial_login, - 'password' => $this->getPartnerTo()->send_credencial_password); + return [ + 'method' => $this->getPartnerTo()->send_credencial_method, + 'login' => $this->getPartnerTo()->send_credencial_login, + 'password' => $this->getPartnerTo()->send_credencial_password + ]; } - + /** * Build message and encode it (signing and/or crypting) * @@ -149,26 +178,26 @@ public function encode() { if (!$this->getPartnerFrom() instanceof Partner || !$this->getPartnerTo() instanceof Partner) throw new AS2Exception('Object not properly initialized'); - + // initialisation $this->mic_checksum = false; $this->setMessageId(self::generateMessageID($this->getPartnerFrom())); - + // chargement et construction du message $files = $this->getFiles(); - + // initial message creation : mime_part // TODO : use adapter to build multipart file try { // managing all files (parts) - $parts = array(); + $parts = []; foreach ($files as $file) { $mime_part = new Horde_MIME_Part($file['mimetype']); $mime_part->setContents(file_get_contents($file['path'])); $mime_part->setName($file['filename']); if ($file['encoding']) $mime_part->setTransferEncoding($file['encoding']); - + $parts[] = $mime_part; } if (count($parts) > 1) { @@ -176,43 +205,47 @@ public function encode() $mime_part = new Horde_MIME_Part('multipart/mixed'); foreach ($parts as $part) $mime_part->addPart($part); - } else { + } + else { // handling mono part (body) $mime_part = $parts[0]; } - + $file = Adapter::getTempFilename(); file_put_contents($file, $mime_part->toString()); - } catch (Exception $e) { + } catch (\Exception $e) { throw $e; + return false; } - + // signing file if wanted by Partner_To if ($this->getPartnerTo()->sec_signature_algorithm != Partner::SIGN_NONE) { try { - $file = $this->adapter->sign($file, $this->getPartnerTo()->send_compress, $this->getPartnerTo()->send_encoding); + $file = $this->adapter->sign($file, $this->getPartnerTo()->send_compress, $this->getPartnerTo()->send_encoding); $this->is_signed = true; - + //echo file_get_contents($file); $this->mic_checksum = $this->getMicChecksum($file); - } catch (Exception $e) { + } catch (\Exception $e) { throw $e; + return false; } } - + // crypting file if wanted by Partner_To if ($this->getPartnerTo()->sec_encrypt_algorithm != Partner::CRYPT_NONE) { try { - $file = $this->adapter->encrypt($file); + $file = $this->adapter->encrypt($file); $this->is_crypted = true; - } catch (Exception $e) { + } catch (\Exception $e) { throw $e; + return false; } } - + $this->path = $file; /*if ($mime_part->getTransferEncoding() == 'base64'){ file_put_contents($this->path, base64_decode($mime_part->toString(false))); @@ -220,41 +253,42 @@ public function encode() else{ file_put_contents($this->path, $mime_part->toString()); }*/ - + // headers setup - $headers = array( - 'AS2-From' => '"' . $this->getPartnerFrom()->id . '"', - 'AS2-To' => '"' . $this->getPartnerTo()->id . '"', - 'AS2-Version' => '1.0', - 'From' => $this->getPartnerFrom()->email, - 'Subject' => $this->getPartnerFrom()->send_subject, - 'Message-ID' => $this->getMessageId(), - 'Mime-Version' => '1.0', + $headers = [ + 'AS2-From' => $this->getPartnerFrom()->id, + 'AS2-To' => $this->getPartnerTo()->id, + 'AS2-Version' => '1.0', + 'From' => $this->getPartnerFrom()->email, + 'Subject' => $this->getPartnerFrom()->send_subject, + 'Message-ID' => $this->getMessageId(), + 'Mime-Version' => '1.0', 'Disposition-Notification-To' => $this->getPartnerFrom()->send_url, - 'Recipient-Address' => $this->getPartnerTo()->send_url, - 'User-Agent' => 'AS2Secure - PHP Lib for AS2 message encoding / decoding', - ); - + 'Recipient-Address' => $this->getPartnerTo()->send_url, + 'User-Agent' => 'AS2Secure - PHP Lib for AS2 message encoding / decoding', + ]; + if ($this->getPartnerTo()->mdn_signed) { $headers['Disposition-Notification-Options'] = 'signed-receipt-protocol=optional, pkcs7-signature; signed-receipt-micalg=optional, sha1'; } - + if ($this->getPartnerTo()->mdn_request == Partner::ACK_ASYNC) { $headers['Receipt-Delivery-Option'] = $this->getPartnerFrom()->send_url; } - + $this->headers = new Header($headers); - + // look for additionnal headers from message // eg : content-type $content = file_get_contents($this->path); $this->headers->addHeadersFromMessage($content); - if (strpos($content, "\n\n") !== false) $content = substr($content, strpos($content, "\n\n") + 2); + if (strpos($content, "\n\n") !== false) + $content = substr($content, strpos($content, "\n\n") + 2); file_put_contents($this->path, $content); - + return true; } - + /** * Decode message extracting files from message * @@ -263,10 +297,10 @@ public function encode() public function decode() { $this->files = $this->adapter->extract($this->getPath()); - + return true; } - + /** * Generate a MDN from the message * @@ -277,29 +311,38 @@ public function decode() public function generateMDN($exception = null) { $mdn = $this->mdnFactory->build($this); - + $message_id = $this->getHeader('message-id'); - $partner = $this->getPartnerTo()->id; - $mic = $this->getMicChecksum(); - - $mdn->setAttribute('Original-Recipient', 'rfc822; "' . $partner . '"'); - $mdn->setAttribute('Final-Recipient', 'rfc822; "' . $partner . '"'); + $partner = $this->getPartnerTo()->id; + $mic = $this->getMicChecksum(); + + $mdn->setAttribute('Original-Recipient', 'rfc822; ' . $partner . ''); + $mdn->setAttribute('Final-Recipient', 'rfc822; ' . $partner . ''); $mdn->setAttribute('Original-Message-ID', $message_id); if ($mic) $mdn->setAttribute('Received-Content-MIC', $mic); - + if (is_null($exception)) { $mdn->setMessage('The AS2 message has been received.'); $mdn->setAttribute('Disposition-Type', 'processed'); - } else { + } + else { if (!$exception instanceof AS2Exception) $exception = new AS2Exception($exception->getMessage()); - + $mdn->setMessage($exception->getMessage()); $mdn->setAttribute('Disposition-Type', 'failure'); $mdn->setAttribute('Disposition-Modifier', $exception->getLevel() . ': ' . $exception->getMessageShort()); } - + return $mdn; } + + /** + * @return bool|string + */ + public function getContent() + { + return base64_decode(parent::getContent()); + } } diff --git a/Models/Partner.php b/Models/Partner.php index c22fffd..b3167df 100644 --- a/Models/Partner.php +++ b/Models/Partner.php @@ -1,9 +1,10 @@ + * @author Sebastien MALOT * * @copyright Copyright (c) 2010, Sebastien MALOT * @@ -24,84 +25,236 @@ * You should have received a copy of the GNU General Public License * along with AS2Secure. * - * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License - * @version 0.9.0 + * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License + * @version 0.9.0 * */ +/** + * Class Partner + * + * @package TechData\AS2SecureBundle\Models + */ class Partner { // general information + /** + * + */ const METHOD_NONE = 'NONE'; + /** + * + */ const METHOD_AUTO = CURLAUTH_ANY; + /** + * + */ const METHOD_BASIC = CURLAUTH_BASIC; + /** + * + */ const METHOD_DIGECT = CURLAUTH_DIGEST; + /** + * + */ const METHOD_NTLM = CURLAUTH_NTLM; - + // security + /** + * + */ const METHOD_GSS = CURLAUTH_GSSNEGOTIATE; // must contain private/certificate/ca chain + /** + * + */ const ENCODING_BASE64 = 'base64'; + /** + * + */ const ENCODING_BINARY = 'binary'; // must contain certificate/ca chain + /** + * + */ const ACK_SYNC = 'SYNC'; + /** + * + */ const ACK_ASYNC = 'ASYNC'; - + // sending data + /** + * + */ const SIGN_NONE = 'none'; + /** + * + */ const SIGN_SHA1 = 'sha1'; // full url including "http://" or "https://" + /** + * + */ const SIGN_MD5 = 'md5'; + /** + * + */ const CRYPT_NONE = 'none'; + /** + * + */ const CRYPT_RC2_40 = 'rc2-40'; + /** + * + */ const CRYPT_RC2_64 = 'rc2-64'; + /** + * + */ const CRYPT_RC2_128 = 'rc2-128'; + /** + * + */ const CRYPT_DES = 'des'; - + // notification process + /** + * + */ const CRYPT_3DES = 'des3'; + /** + * + */ const CRYPT_AES_128 = 'aes128'; + /** + * + */ const CRYPT_AES_192 = 'aes192'; + /** + * + */ const CRYPT_AES_256 = 'aes256'; - protected static $stack = array(); + /** + * @var array + */ + protected static $stack = []; + /** + * @var bool + */ protected $is_local = false; + /** + * @var string + */ protected $name = ''; - + // event trigger connector + /** + * @var string + */ protected $id = ''; - + // + /** + * @var string + */ protected $email = ''; - + // security methods + /** + * @var string + */ protected $comment = ''; + /** + * @var string + */ protected $sec_pkcs12 = ''; + /** + * @var string + */ protected $sec_pkcs12_password = ''; + /** + * @var string + */ protected $sec_certificate = ''; + /** + * @var string + */ protected $sec_signature_algorithm = self::SIGN_SHA1; + /** + * @var string + */ protected $sec_encrypt_algorithm = self::CRYPT_3DES; - + // transfert content encoding + /** + * @var bool + */ protected $send_compress = false; + /** + * @var string + */ protected $send_url = ''; - + // ack methods + /** + * @var string + */ protected $send_subject = 'AS2 Message Subject'; + /** + * @var string + */ protected $send_content_type = 'application/EDI-Consent'; - + // + /** + * @var string + */ protected $send_credencial_method = self::METHOD_NONE; + /** + * @var string + */ protected $send_credencial_login = ''; + /** + * @var string + */ protected $send_credencial_password = ''; - + // http://www.openssl.org/docs/apps/enc.html#SUPPORTED_CIPHERS + /** + * @var string + */ protected $send_encoding = self::ENCODING_BASE64; + /** + * @var string + */ protected $mdn_url = ''; // default + /** + * @var string + */ protected $mdn_subject = 'AS2 MDN Subject'; + /** + * @var string + */ protected $mdn_request = self::ACK_SYNC; + /** + * @var bool + */ protected $mdn_signed = true; + /** + * @var string + */ protected $mdn_credencial_method = self::METHOD_NONE; + /** + * @var string + */ protected $mdn_credencial_login = ''; + /** + * @var string + */ protected $mdn_credencial_password = ''; + /** + * @var string + */ protected $connector_class = 'AS2Connector'; - + /** * Restricted constructor * @@ -113,12 +266,12 @@ public function __construct($data) foreach ($data as $key => $value) { if (!property_exists($this, $key) || is_null($value)) continue; - + $this->$key = $value; } } - - + + /** * Return the list of available signatures * @@ -126,11 +279,12 @@ public function __construct($data) */ public static function getAvailablesSignatures() { - return array('NONE' => self::SIGN_NONE, + return [ + 'NONE' => self::SIGN_NONE, 'SHA1' => self::SIGN_SHA1, - ); + ]; } - + /** * Return the list of available cypher * @@ -138,18 +292,19 @@ public static function getAvailablesSignatures() */ public static function getAvailablesEncryptions() { - return array('NONE' => self::CRYPT_NONE, - 'RC2_40' => self::CRYPT_RC2_40, - 'RC2_64' => self::CRYPT_RC2_64, + return [ + 'NONE' => self::CRYPT_NONE, + 'RC2_40' => self::CRYPT_RC2_40, + 'RC2_64' => self::CRYPT_RC2_64, 'RC2_128' => self::CRYPT_RC2_128, - 'DES' => self::CRYPT_DES, - '3DES' => self::CRYPT_3DES, + 'DES' => self::CRYPT_DES, + '3DES' => self::CRYPT_3DES, 'AES_128' => self::CRYPT_AES_128, 'AES_192' => self::CRYPT_AES_192, 'AES_256' => self::CRYPT_AES_256, - ); + ]; } - + /** * Magic getter * @@ -164,7 +319,7 @@ public function __get($key) else return null; // for strict processes : throw new Exception } - + /** * Magic setter * @@ -178,7 +333,7 @@ public function __set($key, $value) $this->$key = $value; // for strict processes : throw new Exception if property doesn't exists } - + /** * Magic method * diff --git a/Models/Request.php b/Models/Request.php index 6ceac32..3b385e0 100644 --- a/Models/Request.php +++ b/Models/Request.php @@ -1,10 +1,11 @@ + * @author Sebastien MALOT * * @copyright Copyright (c) 2010, Sebastien MALOT * @@ -25,17 +26,28 @@ * You should have received a copy of the GNU General Public License * along with AS2Secure. * - * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License - * @version 0.8.2 + * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License + * @version 0.8.2 * */ +use Symfony\Component\EventDispatcher\EventDispatcher; +use TechData\AS2SecureBundle\Events\Log; use TechData\AS2SecureBundle\Factories\MDN as MDNFactory; use TechData\AS2SecureBundle\Factories\Message as MessageFactory; use Mail_mimeDecode; +use TechData\AS2SecureBundle\Models\Horde\MIME\Horde_MIME_Structure; +/** + * Class Request + * + * @package TechData\AS2SecureBundle\Models + */ class Request extends AbstractBase { // Injected Services + /** + * @var null + */ protected $request = null; /** * @var MDNFactory @@ -45,36 +57,59 @@ class Request extends AbstractBase * @var MessageFactory */ private $messageFactory; - - function __construct(MDNFactory $mdnFactory, MessageFactory $messageFactory) + + /** + * @var EventDispatcher + */ + private $eventDispatcher; + + /** + * Request constructor. + * + * @param MDNFactory $mdnFactory + * @param MessageFactory $messageFactory + * @param EventDispatcher $eventDispatcher + */ + function __construct(MDNFactory $mdnFactory, MessageFactory $messageFactory, EventDispatcher $eventDispatcher) { - $this->mdnFactory = $mdnFactory; - $this->messageFactory = $messageFactory; + $this->mdnFactory = $mdnFactory; + $this->messageFactory = $messageFactory; + $this->eventDispatcher = $eventDispatcher; } - - + + /** + * @param $content + * @param $headers + * + * @throws AS2Exception + */ public function initialize($content, $headers) { - + // build params to match parent::__construct $this->headers = $headers; - $mimetype = $this->getHeader('content-type'); + $mimetype = $this->getHeader('content-type'); if (($pos = strpos($mimetype, ';')) !== false) { $mimetype = substr($mimetype, 0, $pos); } - $params = array('partner_from' => $this->getHeader('as2-from'), - 'partner_to' => $this->getHeader('as2-to'), - 'mimetype' => $mimetype, - 'is_file' => false); - + $params = [ + 'partner_from' => $this->getHeader('as2-from'), + 'partner_to' => $this->getHeader('as2-to'), + 'mimetype' => $mimetype, + 'is_file' => false + ]; + // content is stored into new file $this->initializeBase($content, $params); - + $message_id = $this->getHeader('message-id'); - $message_id = str_replace(array('<', '>'), '', $message_id); + $message_id = str_replace([ + '<', + '>' + ], '', $message_id); $this->setMessageId($message_id); } - + /** * @Return a file with the decrypted content * false if the original document wasn't crypted @@ -82,103 +117,113 @@ public function initialize($content, $headers) public function decrypt() { $mimetype = $this->getHeader('content-type'); - if (($pos = strpos($mimetype, ';')) !== false) $mimetype = trim(substr($mimetype, 0, $pos)); - + if (($pos = strpos($mimetype, ';')) !== false) + $mimetype = trim(substr($mimetype, 0, $pos)); + if ($mimetype == 'application/pkcs7-mime' || $mimetype == 'application/x-pkcs7-mime') { try { $content = $this->getHeaders(true) . "\n\n"; $content .= file_get_contents($this->getPath()); - - $input = Adapter::getTempFilename(); + + $input = Adapter::getTempFilename(); $mime_part = Horde_MIME_Structure::parseTextMIMEMessage($content); file_put_contents($input, $mime_part->toString(true)); - + // get input file and returns decrypted file // throw an exception on error $output = $this->adapter->decrypt($input); - + return $output; - } catch (Exception $e) { + } catch (\Exception $e) { // throw $e; } } - + return false; } - + + /** + * @return MDN|Message + * @throws AS2Exception + */ public function getObject() { // setup of full message $content = $this->getHeaders(true) . "\n\n"; $content .= file_get_contents($this->getPath()); - $input = Adapter::getTempFilename(); + $input = Adapter::getTempFilename(); file_put_contents($input, $content); - // setup of mailmime decoder - $params = array('include_bodies' => false, + $params = [ + 'include_bodies' => false, 'decode_headers' => true, - 'decode_bodies' => false, - 'input' => false, + 'decode_bodies' => false, + 'input' => false, //'crlf' => "\n" - ); - $decoder = new Mail_mimeDecode(file_get_contents($input)); + ]; + $decoder = new \Mail_mimeDecode(file_get_contents($input)); $structure = $decoder->decode($params); - $mimetype = $structure->ctype_primary . '/' . $structure->ctype_secondary; - + if (isset($structure->ctype_primary) && isset($structure->ctype_secondary)) { + $mimetype = $structure->ctype_primary . '/' . $structure->ctype_secondary; + } + else { + $mimetype = $structure->headers['content-type']; + } + // handle crypted content $crypted = false; if (strtolower($mimetype) == 'application/pkcs7-mime') { try { // rewrite message into base64 encoding - $content = file_get_contents($input); + $content = file_get_contents($input); $mime_part = Horde_MIME_Structure::parseTextMIMEMessage($content); - $input = Adapter::getTempFilename(); + $input = Adapter::getTempFilename(); file_put_contents($input, $mime_part->toString(true)); - - $this->eventDispatcher->dispatch('log', new Log(Log::TYPE_INFO, 'AS2 message is encrypted.')); + + $this->eventDispatcher->dispatch(Log::EVENT, new Log(Log::TYPE_INFO, 'AS2 message is encrypted.')); $input = $this->adapter->decrypt($input); - $this->eventDispatcher->dispatch('log', new Log(Log::TYPE_INFO, 'The data has been decrypted using the key "' . $this->getPartnerTo() . '".')); + $this->eventDispatcher->dispatch(Log::EVENT, new Log(Log::TYPE_INFO, 'The data has been decrypted using the key "' . $this->getPartnerTo() . '".')); $crypted = true; - + // reload extracted content to get mimetype - $decoder = new Mail_mimeDecode(file_get_contents($input)); + $decoder = new \Mail_mimeDecode(file_get_contents($input)); $structure = $decoder->decode($params); - $mimetype = $structure->ctype_primary . '/' . $structure->ctype_secondary; - } catch (Exception $e) { + $mimetype = $structure->ctype_primary . '/' . $structure->ctype_secondary; + } catch (\Exception $e) { throw new AS2Exception($e->getMessage(), 3); } } - + // handle signed content $signed = false; - $mic = false; + $mic = false; if (strtolower($mimetype) == 'multipart/signed') { try { - $this->eventDispatcher->dispatch('log', new Log(Log::TYPE_INFO, 'AS2 message is signed.')); - + $this->eventDispatcher->dispatch(Log::EVENT, new Log(Log::TYPE_INFO, 'AS2 message is signed.')); // get MicChecksum from signature $mic = $this->adapter->getMicChecksum($input); - - $input = $this->adapter->verify($input); + + $input = $this->adapter->verify($input); $signed = true; - - $this->eventDispatcher->dispatch('log', new Log(Log::TYPE_INFO, 'The sender used the algorithm "' . $structure->ctype_parameters['micalg'] . '" to sign the message.')); - + + $this->eventDispatcher->dispatch(Log::EVENT, new Log(Log::TYPE_INFO, 'The sender used the algorithm "' . $structure->ctype_parameters['micalg'] . '" to sign the message.')); + // reload extracted content to get mimetype - $decoder = new Mail_mimeDecode(file_get_contents($input)); + $decoder = new \Mail_mimeDecode(file_get_contents($input)); $structure = $decoder->decode($params); - $mimetype = $structure->ctype_primary . '/' . $structure->ctype_secondary; - - $this->eventDispatcher->dispatch('log', new Log(Log::TYPE_INFO, 'Using certificate "' . $this->getPartnerFrom() . '" to verify signature.')); - } catch (Exception $e) { + $mimetype = $structure->ctype_primary . '/' . $structure->ctype_secondary; + + $this->eventDispatcher->dispatch(Log::EVENT, new Log(Log::TYPE_INFO, 'Using certificate "' . $this->getPartnerFrom() . '" to verify signature.')); + } catch (\Exception $e) { throw new AS2Exception($e->getMessage(), 5); } - } else { + } + else { // check requested algo $mic = Adapter::calculateMicChecksum($input, 'sha1'); } - + // security check if (strtolower($mimetype) == 'multipart/report') { // check about sign @@ -189,7 +234,8 @@ public function getObject() if ($this->getPartnerFrom()->sec_signature_algorithm != Partner::SIGN_NONE && $this->getPartnerFrom()->mdn_signed && !$signed) { throw new AS2Exception('AS2 message is not signed and should be.', 4); } - } else { + } + else { // check about crypt /*if ($this->getPartnerFrom()->sec_encrypt_algorithm == Partner::CRYPT_NONE && $crypted){ throw new AS2Exception('AS2 message is crypted and shouldn\'t be.', 4); @@ -198,7 +244,7 @@ public function getObject() if ($this->getPartnerFrom()->sec_encrypt_algorithm != Partner::CRYPT_NONE && !$crypted) { throw new AS2Exception('AS2 message is not crypted and should be.', 4); } - + // check about sign /*if ($this->getPartnerFrom()->sec_signature_algorithm == Partner::SIGN_NONE && $signed){ throw new AS2Exception('AS2 message is signed and shouldn\'t be.', 4); @@ -208,42 +254,53 @@ public function getObject() throw new AS2Exception('AS2 message is not signed and should be.', 4); } } - + try { // build object with extracted content - $message = file_get_contents($input); + $message = file_get_contents($input); $mime_part = Horde_MIME_Structure::parseTextMIMEMessage($message); - switch (strtolower($mimetype)) { case 'multipart/report': - $params = array('partner_from' => $this->getPartnerTo(), - 'partner_to' => $this->getPartnerFrom(), - 'is_file' => false, - 'mic' => $mic); + $params = [ + 'partner_from' => $this->getPartnerTo(), + 'partner_to' => $this->getPartnerFrom(), + 'is_file' => false, + 'mic' => $mic + ]; $object = $this->mdnFactory->build($mime_part, $params); + return $object; - + default: - $params = array('partner_from' => $this->getPartnerFrom(), - 'partner_to' => $this->getPartnerTo(), - 'is_file' => false, - 'mic' => $mic); + $params = [ + 'partner_from' => $this->getPartnerFrom(), + 'partner_to' => $this->getPartnerTo(), + 'is_file' => false, + 'mic' => $mic + ]; $object = $this->messageFactory->build($mime_part, $params); $object->setHeaders($this->getHeaders()); + return $object; } - } catch (Exception $e) { + } catch (\Exception $e) { throw new AS2Exception($e->getMessage(), 6); } - + throw new AS2Exception('Unexpected error while handling message.', 6); } - + + /** + * @throws AS2Exception + */ public function encode() { throw new AS2Exception('This method is not available.'); } - + + /** + * @throws AS2Exception + */ public function decode() { throw new AS2Exception('This method is not available.'); diff --git a/Models/Server.php b/Models/Server.php index 8293b51..cffe4f8 100644 --- a/Models/Server.php +++ b/Models/Server.php @@ -1,10 +1,11 @@ + * @author Sebastien MALOT * * @copyright Copyright (c) 2010, Sebastien MALOT * @@ -25,28 +26,40 @@ * You should have received a copy of the GNU General Public License * along with AS2Secure. * - * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License - * @version 0.9.0 + * @license http://www.gnu.org/licenses/lgpl-3.0.html GNU General Public License + * @version 0.9.0 * */ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use TechData\AS2SecureBundle\Events\Log; use TechData\AS2SecureBundle\Events\MessageSent; +use TechData\AS2SecureBundle\Events\MessageReceived; use TechData\AS2SecureBundle\Models\Client; use TechData\AS2SecureBundle\Factories\MDN as MdnFactory; +/** + * Class Server + * + * @package TechData\AS2SecureBundle\Models + */ class Server { + /** + * + */ const TYPE_MESSAGE = 'Message'; + /** + * + */ const TYPE_MDN = 'MDN'; - + /** * * @var EventDispatcherInterface */ private $eventDispatcher; - + /** * @var MdnFactory */ @@ -55,14 +68,21 @@ class Server * @var Client */ private $client; - + + /** + * Server constructor. + * + * @param EventDispatcherInterface $eventDispatcher + * @param MdnFactory $mdnFactory + * @param \TechData\AS2SecureBundle\Models\Client $client + */ public function __construct(EventDispatcherInterface $eventDispatcher, MdnFactory $mdnFactory, Client $client) { $this->eventDispatcher = $eventDispatcher; - $this->mdnFactory = $mdnFactory; - $this->client = $client; + $this->mdnFactory = $mdnFactory; + $this->client = $client; } - + /** * Handle a request (server side) * @@ -74,100 +94,128 @@ public function handle(Request $request) { // handle any problem in case of SYNC MDN process ob_start(); - + try { - $error = null; + $error = null; $headers = $request->getHeaders(); - $object = $request->getObject(); - } catch (Exception $e) { + $object = $request->getObject(); + } catch (\Exception $e) { // get error while handling request $error = $e; //throw $e; } - + // $mdn = null; - + if ($object instanceof Message || (!is_null($error) && !($object instanceof MDN))) { $object_type = self::TYPE_MESSAGE; - $this->eventDispatcher->dispatch('log', new Log(Log::TYPE_INFO, 'Incoming transmission is a Message.')); - + $this->eventDispatcher->dispatch(Log::EVENT, new Log(Log::TYPE_INFO, 'Incoming transmission is a Message.')); + try { if (is_null($error)) { $object->decode(); $files = $object->getFiles(); - $this->eventDispatcher->dispatch('log', new Log(Log::TYPE_INFO, count($files) . ' payload(s) found in incoming transmission.')); + $this->eventDispatcher->dispatch(Log::EVENT, new Log(Log::TYPE_INFO, count($files) . ' payload(s) found in incoming transmission.')); foreach ($files as $key => $file) { $content = file_get_contents($file['path']); - $this->eventDispatcher->dispatch('log', new Log(Log::TYPE_INFO, 'Payload #' . ($key + 1) . ' : ' . round(strlen($content) / 1024, 2) . ' KB / "' . $file['filename'] . '".')); - $this->saveMessage($content, array(), 'payload'); + $this->eventDispatcher->dispatch(Log::EVENT, new Log(Log::TYPE_INFO, 'Payload #' . ($key + 1) . ' : ' . round(strlen($content) / 1024, 2) . ' KB / "' . $file['filename'] . '".')); + $this->saveMessage($content, [], 'payload'); } - + $mdn = $object->generateMDN($error); $mdn->encode($object); - } else { + } + else { throw $error; } - } catch (Exception $e) { - $params = array('partner_from' => $headers->getHeader('as2-from'), - 'partner_to' => $headers->getHeader('as2-to')); - $mdn = $this->mdnFactory->build($e, $params); + } catch (\Exception $e) { + $this->eventDispatcher->dispatch(Log::EVENT, new Log(Log::TYPE_ERROR, $e->getMessage())); + $params = [ + 'partner_from' => $headers->getHeader('as2-from'), + 'partner_to' => $headers->getHeader('as2-to') + ]; + $mdn = $this->mdnFactory->build($e, $params); $mdn->setAttribute('original-message-id', $headers->getHeader('message-id')); $mdn->encode(); } - } elseif ($object instanceof MDN) { + } + elseif ($object instanceof MDN) { $object_type = self::TYPE_MDN; - $this->eventDispatcher->dispatch('log', new Log(Log::TYPE_INFO, 'Incoming transmission is a MDN.')); - } else { - $this->eventDispatcher->dispatch('log', new Log(Log::TYPE_ERROR, 'Malformed data.')); + $object->decode(); + + $event = (new MessageReceived())->setMessageId($object->getMessageId())->setMessage($object->getMessage())->setType(MessageReceived::TYPE_MDN)->setOriginalMessageId($object->getAttribute('original-message-id')); + + $this->eventDispatcher->dispatch(MessageReceived::EVENT, $event); + + $this->eventDispatcher->dispatch(Log::EVENT, new Log(Log::TYPE_INFO, 'Incoming transmission is a MDN guid: ' . $object->getAttribute('original-message-id') . ',Content: ' . $object)); } - + else { + $this->eventDispatcher->dispatch(Log::EVENT, new Log(Log::TYPE_ERROR, 'Malformed data.')); + } + // build MDN if (!is_null($error) && $object_type == self::TYPE_MESSAGE) { - $params = array('partner_from' => $headers->getHeader('as2-from'), - 'partner_to' => $headers->getHeader('as2-to')); - $mdn = $this->mdnFactory->build($e, $params); + $params = [ + 'partner_from' => $headers->getHeader('as2-from'), + 'partner_to' => $headers->getHeader('as2-to') + ]; + $mdn = $this->mdnFactory->build($e, $params); $mdn->setAttribute('original-message-id', $headers->getHeader('message-id')); $mdn->encode(); } - + // send MDN if (!is_null($mdn)) { if (!$headers->getHeader('receipt-delivery-option')) { // SYNC method - + // re-active output data ob_end_clean(); - + // send headers foreach ($mdn->getHeaders() as $key => $value) { - $header = str_replace(array("\r", "\n", "\r\n"), '', $key . ': ' . $value); + $header = str_replace([ + "\r", + "\n", + "\r\n" + ], '', $key . ': ' . $value); header($header); } - + // output MDN echo $mdn->getContent(); - - $this->eventDispatcher->dispatch('log', new Log(Log::TYPE_INFO, 'An AS2 MDN has been sent.')); - } else { + + $this->eventDispatcher->dispatch(Log::EVENT, new Log(Log::TYPE_INFO, 'An AS2 MDN has been sent.')); + } + else { // ASYNC method - // cut connection and wait a few seconds $this->closeConnectionAndWait(5); - // delegate the mdn sending to the client $result = $this->client->sendRequest($mdn); + + $messageSent = new MessageSent(); + $messageSent->setCode($result['info']['http_code']); + $messageSent->setMessageId($mdn->getMessageId()); + $messageSent->setType(MessageSent::TYPE_MDN); + $messageSent->setMessage($mdn->getContent()); + $messageSent->setOriginalMessageId($mdn->getAttribute('original-message-id')); + $this->eventDispatcher->dispatch(MessageSent::EVENT, $messageSent); + + $this->eventDispatcher->dispatch(Log::EVENT, new Log(Log::TYPE_INFO, 'Log sending MDN: ' . $result['log'])); if ($result['info']['http_code'] == '200') { - $this->eventDispatcher->dispatch('log', new Log(Log::TYPE_INFO, 'An AS2 MDN has been sent.')); - } else { - $this->eventDispatcher->dispatch('log', new Log(Log::TYPE_ERROR, 'An error occurs while sending MDN message : ' . $result['info']['http_code'])); + $this->eventDispatcher->dispatch(Log::EVENT, new Log(Log::TYPE_INFO, 'An AS2 MDN has been sent.')); + } + else { + $this->eventDispatcher->dispatch(Log::EVENT, new Log(Log::TYPE_ERROR, 'An error occurs while sending MDN message : ' . $result['info']['http_code'] . " error: " . $result['error'])); } } } - + return $request; } - + /** * Save the content of the request for futur handle and/or backup * @@ -179,15 +227,15 @@ public function handle(Request $request) */ protected function saveMessage($content, $headers, $type = 'raw') { - + $message = new MessageSent(); $message->setMessage($content); $message->setHeaders($headers); $message->setMessageType($type); $this->eventDispatcher->dispatch('messageSent', $message); - + } - + /** * Close current HTTP connection and wait some secons * @@ -205,9 +253,8 @@ protected function closeConnectionAndWait($sleep) header("Content-Length: $size"); ob_end_flush(); // Strange behaviour, will not work flush(); // Unless both are called ! - ob_end_clean(); session_write_close(); - + // wait some seconds before sending MDN notification sleep($sleep); } diff --git a/Providers/PartnerProviderResolver.php b/Providers/PartnerProviderResolver.php new file mode 100644 index 0000000..4cb1ceb --- /dev/null +++ b/Providers/PartnerProviderResolver.php @@ -0,0 +1,58 @@ +mapping = []; + } + + /** + * @param PartnerInterface $partner + */ + protected function getId(PartnerInterface $partner) { + $data = $partner->getData(); + if(empty($data['id'])) { + throw new \InvalidArgumentException('id is not found in your data partner'); + } + + return $data['id']; + } + + /** + * @param Partner $partner + */ + public function add(PartnerInterface $partner) + { + $partnerId = $this->getId($partner); + $this->mapping[$partnerId] = $partner; + } + + /** + * @param null $context + * + * @return mixed + */ + public function getPartner($partnerId, $reload = false) + { + return $this->mapping[$partnerId]->getData(); + } +} \ No newline at end of file diff --git a/README.md b/README.md index 4edd065..6c24377 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,40 @@ ## AS2 Symfony Bundle +Installation +============ + +Add the bunde to your `composer.json` file: + +```javascript +require: { + // ... + "techdata/as2secure-bundle": "^0.1.0@dev" +} +``` + +Or install directly through composer with: + +```shell +composer require techdata/as2secure-bundle dev-master +``` + +Register the bundle with your kernel: + +```php +// in AppKernel::registerBundles() +$bundles = array( + // ... + new TechData\AS2SecureBundle\TechDataAS2SecureBundle(), + // ... +); +``` + +add to routing.yml +```yml +_tech_data: + resource: "@TechDataAS2SecureBundle/Resources/config/routing.xml" +``` + ### Prior Art The contents of this library are largely based on the work done by Sebastien Malot on the AS2Secure library. The original can be found at [http://www.as2secure.com/](http://www.as2secure.com/ "www.as2secure.com"). In accordance with the license associated to that library, we continue to follow the LGPL license. @@ -23,12 +58,80 @@ To send a message you leverage the `tech_data_as2_secure.handler.as2` service, w There is an endpoint which is enabled an located at `/edi/as2/in` by default. This can be overridden to put the route wherever you want. ### Partner Provider Service -In order to get the partner info, and keep the system as data-store agnostic as possible, it is up to the implementer to create the Partner Provider Service. This service must implement the `TechData\AS2SecureBundle\Interfaces\PartnerProvider` interface and must be a public service (it is by default). +For add the new provider un create the new class for implement TechData\AS2SecureBundle\Models\Partner\ParterInterface +And create the service wioth tag tech_data_as2_partner +#### PHP class +```php +root_dir = $root_dir; + } + + /** + * @return array + */ + public function getData() : array + { + + return [ + 'is_local' => true, + 'name' => 'mycompanyAS2', + 'id' => $this->getId(), + 'email' => 'info@mendelson.de', + 'comment' => '', + + // security + 'sec_pkcs12' => $this->root_dir.'/../demo/mycompanyAS2/key1.p12', + 'sec_pkcs12_password' => 'test', + + 'sec_signature_algorithm' => Partner::SIGN_SHA1, + 'sec_encrypt_algorithm' => Partner::CRYPT_3DES, + + 'send_url' => 'http://loaclhost/edi/as2/in', + + // notification process + 'mdn_request' => Partner::ACK_SYNC, + ]; + } + /** + * @return string + */ + public function getId(): string + { + return 'mycompanyAS2'; + } -The ID of this service must be provided as a parameter. (See below). +} +``` +#### Service +```yml + app.partner.mendelson: + class: AppBundle\Partner\MendelsonPartner + arguments: ['%kernel.root_dir%'] + tags: + - { name: tech_data_as2_partner} +``` ### Required Parameters There are two required parameters which must be filled out. - `tech_data_as2_secure.factory.adapter.bin_location` - This is the real folder location where the `AS2Secure.jar` can be found. -- `tech_data_as2_secure.partner_provider.service_id` - This is the service ID of the partner provider service which must be supplied by the implementer, and must implement the `TechData\AS2SecureBundle\Interfaces\PartnerProvider` interface. \ No newline at end of file diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 0d899e9..1b2f33a 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -20,6 +20,7 @@ TechData\AS2SecureBundle\Models\Client TechData\AS2SecureBundle\Services\AS2 TechData\AS2SecureBundle\Controller\AS2Controller + TechData\AS2SecureBundle\Providers\PartnerProviderResolver @@ -42,8 +43,11 @@ + + + + - %tech_data_as2_secure.factory.adapter.bin_location% @@ -87,7 +91,10 @@ - + + + + diff --git a/Services/AS2.php b/Services/AS2.php index 545b28f..3fe1759 100644 --- a/Services/AS2.php +++ b/Services/AS2.php @@ -4,6 +4,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; +use TechData\AS2SecureBundle\Events\Log; use TechData\AS2SecureBundle\Events\MessageReceived; use TechData\AS2SecureBundle\Events\MessageSent; use TechData\AS2SecureBundle\Factories\Adapter as AdapterFactory; @@ -22,26 +23,29 @@ */ class AS2 implements MessageSender { - + + /** + * + */ CONST EVENT_MESSAGE_RECEIVED = 'message_received'; - + /** * * @var EventDispatcherInterface */ private $eventDispatcher; - + /** * * @var PartnerFactory */ private $partnerFactory; - + /** * @var Server */ private $as2Server; - + /** * @var RequestFactory */ @@ -58,89 +62,124 @@ class AS2 implements MessageSender * @var Client */ private $client; - - public function __construct(EventDispatcherInterface $eventDispatcher, Server $server, RequestFactory $requestFactory, - PartnerFactory $partnerFactory, MessageFactory $messageFactory, AdapterFactory $adapterFactory, Client $client) + + /** + * AS2 constructor. + * + * @param EventDispatcherInterface $eventDispatcher + * @param Server $server + * @param RequestFactory $requestFactory + * @param PartnerFactory $partnerFactory + * @param MessageFactory $messageFactory + * @param AdapterFactory $adapterFactory + * @param Client $client + */ + public function __construct(EventDispatcherInterface $eventDispatcher, Server $server, RequestFactory $requestFactory, PartnerFactory $partnerFactory, MessageFactory $messageFactory, AdapterFactory $adapterFactory, Client $client) { $this->eventDispatcher = $eventDispatcher; - $this->as2Server = $server; - $this->requestFactory = $requestFactory; - $this->partnerFactory = $partnerFactory; - $this->messageFactory = $messageFactory; - $this->adapterFactory = $adapterFactory; - $this->client = $client; + $this->as2Server = $server; + $this->requestFactory = $requestFactory; + $this->partnerFactory = $partnerFactory; + $this->messageFactory = $messageFactory; + $this->adapterFactory = $adapterFactory; + $this->client = $client; } - + + /** + * @param Request $request + */ public function handleRequest(Request $request) { // Convert the symfony request to a as2s request $as2Request = $this->requestToAS2Request($request); - // Take the request and lets AS2S handle it - $as2Response = $this->as2Server->handle($as2Request); - // Get the partner and verify they are authorized - $partner = $as2Response->getPartnerFrom(); + // @TODO Authorize the partner. - // process all EDI-X12 messages contained in the AS2 payload - $response_object = $as2Response->getObject(); + + + $as2Response = $this->as2Server->handle($as2Request); + try { + + $partner = $as2Response->getPartnerFrom(); + $response_object = $as2Response->getObject(); // the AS2 payload may be further encoded, try to decode it. + $response_object->decode(); } catch (\Exception $e) { - // there was an exception while attemptiong to decode, so the message was probably not encoded... ignore the exception + $this->eventDispatcher->dispatch(Log::EVENT, new Log(Log::TYPE_ERROR, $e->getMessage())); } $files = $response_object->getFiles(); foreach ($files as $file) { // We have an incoming message. Lets fire the event for it. - $event = new MessageReceived(); - $event->setMessage(file_get_contents($file['path'])); + $event = (new MessageReceived())->setMessageId($as2Response->getMessageId())->setMessage(file_get_contents($file['path']))->setType(MessageReceived::TYPE_MESSAGE)->setSendingPartnerId($partner->id)->setReceivingPartnerId($as2Response->getPartnerTo()->id); + $this->eventDispatcher->dispatch(MessageReceived::EVENT, $event); } } - - private function requestToAS2Request(Request $request) { - $flattenedHeaders = array(); - foreach($request->headers as $key => $header) { + + /** + * @param Request $request + * + * @return \TechData\AS2SecureBundle\Models\Request + */ + private function requestToAS2Request(Request $request) + { + $flattenedHeaders = []; + foreach ($request->headers as $key => $header) { $flattenedHeaders[$key] = reset($header); - } + } + return $this->requestFactory->build($request->getContent(), new Header($flattenedHeaders)); } /** - * @param $toPartner - * @param $fromPartner - * @param $messageContent + * @param $toPartner + * @param $fromPartner + * @param string $messageContent + * @param string $mimeType + * @param string $encoding + * + * @return array * @throws \Exception * @throws \TechData\AS2SecureBundle\Models\AS2Exception * @throws \TechData\AS2SecureBundle\Models\Exception */ - public function sendMessage($toPartner, $fromPartner, $messageContent) + public function sendMessage($toPartner, $fromPartner, $messageContent,$mimeType = "application/xml") { // process request to build outbound AS2 message to VAR - + // initialize outbound AS2Message object - $message = $this->messageFactory->build(false, array( + $message = $this->messageFactory->build(false, [ 'partner_from' => $fromPartner, - 'partner_to' => $toPartner, - )); - + 'partner_to' => $toPartner, + ]); + // initialize AS2Adapter for public key encryption between StreamOne and the receiving VAR $adapter = $this->adapterFactory->build($fromPartner, $toPartner); - + // write the EDI message that will be sent to a temp file, then use the AS2 adapter to encrypt it - $tmp_file = $adapter->getTempFilename(); - file_put_contents($tmp_file, $messageContent); - $message->addFile($tmp_file, 'application/edi-x12'); + $message->addFile($messageContent, $mimeType, "", false); $message->encode(); - + // send AS2 message $result = $this->client->sendRequest($message); + $this->eventDispatcher->dispatch(Log::EVENT, new Log(Log::TYPE_INFO, "Log sending Message: " . $result['log'])); $messageSent = new MessageSent(); - $messageSent->setMessage(print_r($result, true)); + $messageSent->setCode($result['info']['http_code']); + $messageSent->setMessageId($message->getMessageId()); + $messageSent->setType(MessageSent::TYPE_MESSAGE); + $messageSent->setMessage($messageContent); $this->eventDispatcher->dispatch(MessageSent::EVENT, $messageSent); - + + //clean sended files + foreach ($message->getFiles() as $file) + { + unlink($file['path']); + } + return $result; } } diff --git a/composer.json b/composer.json index 2696c94..ac8ffd0 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,10 @@ { "name": "Westin Pigott", "email": "westin.pigott@techdata.com" + }, + { + "name": "Yury Dombrovsky", + "email": "ydombrovsky95@gmail.com " } ], "require": {