From b5af83f9d259217aad459e73c833773b5c2f87d5 Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Fri, 23 Aug 2013 20:30:50 +0200 Subject: [PATCH 01/26] add workaround for cli webserver, option implemented in client --- src/BeSimple/SoapServer/SoapRequest.php | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/BeSimple/SoapServer/SoapRequest.php b/src/BeSimple/SoapServer/SoapRequest.php index 8a03d7a..427673f 100644 --- a/src/BeSimple/SoapServer/SoapRequest.php +++ b/src/BeSimple/SoapServer/SoapRequest.php @@ -32,10 +32,21 @@ class SoapRequest extends CommonSoapRequest */ public static function create($content, $version) { - $content = is_null($content) ? file_get_contents("php://input") : $content; $location = self::getCurrentUrl(); - $action = $_SERVER[SoapMessage::SOAP_ACTION_HEADER]; - $contentType = $_SERVER[SoapMessage::CONTENT_TYPE_HEADER]; + /* + * Work around missing header/php://input access in PHP cli webserver by + * setting headers additionally as GET parameters and SOAP request body + * explicitly as POST variable + */ + if (php_sapi_name() == "cli-server") { + $content = is_null($content) ? $_POST['request'] : $content; + $action = $_GET[SoapMessage::SOAP_ACTION_HEADER]; + $contentType = $_GET[SoapMessage::CONTENT_TYPE_HEADER]; + } else { + $content = is_null($content) ? file_get_contents("php://input") : $content; + $action = $_SERVER[SoapMessage::SOAP_ACTION_HEADER]; + $contentType = $_SERVER[SoapMessage::CONTENT_TYPE_HEADER]; + } $request = new SoapRequest(); // $content is if unmodified from SoapClient not a php string type! From ee321fca42293b7c1362b1c27eed8d4f4512c12e Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Mon, 19 Aug 2013 19:23:29 +0200 Subject: [PATCH 02/26] fix missing 'cid:' in SwaTypeConverter --- src/BeSimple/SoapCommon/Converter/SwaTypeConverter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BeSimple/SoapCommon/Converter/SwaTypeConverter.php b/src/BeSimple/SoapCommon/Converter/SwaTypeConverter.php index 40d6372..138b973 100644 --- a/src/BeSimple/SoapCommon/Converter/SwaTypeConverter.php +++ b/src/BeSimple/SoapCommon/Converter/SwaTypeConverter.php @@ -83,7 +83,7 @@ class SwaTypeConverter implements TypeConverterInterface, SoapKernelAwareInterfa $this->soapKernel->addAttachment($part); - return sprintf('<%s href="%s"/>', $this->getTypeName(), $contentId); + return sprintf('<%s href="%s"/>', $this->getTypeName(), 'cid:' . $contentId); } /** From 5e6fd5de25d461980d04d51bc24325e83496c659 Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Wed, 21 Aug 2013 22:28:22 +0200 Subject: [PATCH 03/26] extract common code used in client/server --- .../WsSecurityFilterClientServer.php | 358 ++++++++++++++++++ 1 file changed, 358 insertions(+) create mode 100644 src/BeSimple/SoapCommon/WsSecurityFilterClientServer.php diff --git a/src/BeSimple/SoapCommon/WsSecurityFilterClientServer.php b/src/BeSimple/SoapCommon/WsSecurityFilterClientServer.php new file mode 100644 index 0000000..af0f21d --- /dev/null +++ b/src/BeSimple/SoapCommon/WsSecurityFilterClientServer.php @@ -0,0 +1,358 @@ + + * (c) Francis Besset + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace BeSimple\SoapCommon; + +use ass\XmlSecurity\DSig as XmlSecurityDSig; +use ass\XmlSecurity\Enc as XmlSecurityEnc; +use ass\XmlSecurity\Key as XmlSecurityKey; +use ass\XmlSecurity\Pem as XmlSecurityPem; + +use BeSimple\SoapCommon\FilterHelper; +use BeSimple\SoapCommon\Helper; +use BeSimple\SoapCommon\SoapRequest as CommonSoapRequest; +use BeSimple\SoapCommon\SoapRequestFilter; +use BeSimple\SoapCommon\SoapResponse as CommonSoapResponse; +use BeSimple\SoapCommon\SoapResponseFilter; +use BeSimple\SoapCommon\WsSecurityKey; + +/** + * WS-Security common code for client & server. + * + * @author Andreas Schamberger + */ +abstract class WsSecurityFilterClientServer +{ + /** + * The date format to be used with {@link \DateTime} + */ + const DATETIME_FORMAT = 'Y-m-d\TH:i:s.000\Z'; + + /** + * (X509 3.2.1) Reference to a Subject Key Identifier + */ + const TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER = 0; + + /** + * (X509 3.2.1) Reference to a Security Token + */ + const TOKEN_REFERENCE_SECURITY_TOKEN = 1; + + /** + * (SMS_1.1 7.3) Key Identifiers + */ + const TOKEN_REFERENCE_THUMBPRINT_SHA1 = 2; + + /** + * Actor. + * + * @var string + */ + protected $actor; + + /** + * (SMS 10) Add security timestamp. + * + * @var boolean + */ + protected $addTimestamp; + + /** + * Encrypt the signature? + * + * @var boolean + */ + protected $encryptSignature; + + /** + * (SMS 10) Security timestamp expires time in seconds. + * + * @var int + */ + protected $expires; + + /** + * Sign all headers. + * + * @var boolean + */ + protected $signAllHeaders; + + /** + * (X509 3.2) Token reference type for encryption. + * + * @var int + */ + protected $tokenReferenceEncryption = null; + + /** + * (X509 3.2) Token reference type for signature. + * + * @var int + */ + protected $tokenReferenceSignature = null; + + /** + * Service WsSecurityKey. + * + * @var \BeSimple\SoapCommon\WsSecurityKey + */ + protected $serviceSecurityKey; + + /** + * User WsSecurityKey. + * + * @var \BeSimple\SoapCommon\WsSecurityKey + */ + protected $userSecurityKey; + + /** + * Constructor. + * + * @param boolean $addTimestamp (SMS 10) Add security timestamp. + * @param int $expires (SMS 10) Security timestamp expires time in seconds. + * @param string $actor SOAP actor + */ + public function __construct($addTimestamp = true, $expires = 300, $actor = null) + { + $this->addTimestamp = $addTimestamp; + $this->expires = $expires; + $this->actor = $actor; + } + + /** + * Reset all properties to default values. + */ + public function resetFilter() + { + $this->actor = null; + $this->addTimestamp = null; + $this->encryptSignature = null; + $this->expires = null; + $this->serviceSecurityKey = null; + $this->signAllHeaders = null; + $this->tokenReferenceEncryption = null; + $this->tokenReferenceSignature = null; + $this->userSecurityKey = null; + } + + /** + * Get service security key. + * + * @param \BeSimple\SoapCommon\WsSecurityKey $serviceSecurityKey Service security key + * + * @return void + */ + public function setServiceSecurityKeyObject(WsSecurityKey $serviceSecurityKey) + { + $this->serviceSecurityKey = $serviceSecurityKey; + } + + /** + * Get user security key. + * + * @param \BeSimple\SoapCommon\WsSecurityKey $userSecurityKey User security key + * + * @return void + */ + public function setUserSecurityKeyObject(WsSecurityKey $userSecurityKey) + { + $this->userSecurityKey = $userSecurityKey; + } + + /** + * Set security options. + * + * @param int $tokenReference self::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER | self::TOKEN_REFERENCE_SECURITY_TOKEN | self::TOKEN_REFERENCE_THUMBPRINT_SHA1 + * @param boolean $encryptSignature Encrypt signature + * + * @return void + */ + public function setSecurityOptionsEncryption($tokenReference, $encryptSignature = false) + { + $this->tokenReferenceEncryption = $tokenReference; + $this->encryptSignature = $encryptSignature; + } + + /** + * Set security options. + * + * @param int $tokenReference self::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER | self::TOKEN_REFERENCE_SECURITY_TOKEN | self::TOKEN_REFERENCE_THUMBPRINT_SHA1 + * @param boolean $signAllHeaders Sign all headers? + * + * @return void + */ + public function setSecurityOptionsSignature($tokenReference, $signAllHeaders = false) + { + $this->tokenReferenceSignature = $tokenReference; + $this->signAllHeaders = $signAllHeaders; + } + + /** + * Adds the configured KeyInfo to the parentNode. + * + * @param FilterHelper $filterHelper Filter helper object + * @param int $tokenReference Token reference type + * @param string $guid Unique ID + * @param \ass\XmlSecurity\Key $xmlSecurityKey XML security key + * + * @return \DOMElement + */ + protected function createKeyInfo(FilterHelper $filterHelper, $tokenReference, $guid, XmlSecurityKey $xmlSecurityKey = null) + { + $keyInfo = $filterHelper->createElement(XmlSecurityDSig::NS_XMLDSIG, 'KeyInfo'); + $securityTokenReference = $filterHelper->createElement(Helper::NS_WSS, 'SecurityTokenReference'); + $keyInfo->appendChild($securityTokenReference); + // security token + if (self::TOKEN_REFERENCE_SECURITY_TOKEN === $tokenReference) { + $reference = $filterHelper->createElement(Helper::NS_WSS, 'Reference'); + $filterHelper->setAttribute($reference, null, 'URI', '#' . $guid); + if (null !== $xmlSecurityKey) { + $filterHelper->setAttribute($reference, null, 'ValueType', Helper::NAME_WSS_X509 . '#X509v3'); + } + $securityTokenReference->appendChild($reference); + // subject key identifier + } elseif (self::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER === $tokenReference && null !== $xmlSecurityKey) { + $keyIdentifier = $filterHelper->createElement(Helper::NS_WSS, 'KeyIdentifier'); + $filterHelper->setAttribute($keyIdentifier, null, 'EncodingType', Helper::NAME_WSS_SMS . '#Base64Binary'); + $filterHelper->setAttribute($keyIdentifier, null, 'ValueType', Helper::NAME_WSS_X509 . '#509SubjectKeyIdentifier'); + $securityTokenReference->appendChild($keyIdentifier); + $certificate = $xmlSecurityKey->getX509SubjectKeyIdentifier(); + $dataNode = new \DOMText($certificate); + $keyIdentifier->appendChild($dataNode); + // thumbprint sha1 + } elseif (self::TOKEN_REFERENCE_THUMBPRINT_SHA1 === $tokenReference && null !== $xmlSecurityKey) { + $keyIdentifier = $filterHelper->createElement(Helper::NS_WSS, 'KeyIdentifier'); + $filterHelper->setAttribute($keyIdentifier, null, 'EncodingType', Helper::NAME_WSS_SMS . '#Base64Binary'); + $filterHelper->setAttribute($keyIdentifier, null, 'ValueType', Helper::NAME_WSS_SMS_1_1 . '#ThumbprintSHA1'); + $securityTokenReference->appendChild($keyIdentifier); + $thumbprintSha1 = base64_encode(sha1(base64_decode($xmlSecurityKey->getX509Certificate(true)), true)); + $dataNode = new \DOMText($thumbprintSha1); + $keyIdentifier->appendChild($dataNode); + } + + return $keyInfo; + } + + /** + * Create a list of \DOMNodes that should be encrypted. + * + * @param \DOMDocument $dom DOMDocument to query + * @param \DOMElement $security Security element + * + * @return \DOMNodeList + */ + protected function createNodeListForEncryption(\DOMDocument $dom, \DOMElement $security) + { + $xpath = new \DOMXPath($dom); + $xpath->registerNamespace('SOAP-ENV', $dom->documentElement->namespaceURI); + $xpath->registerNamespace('ds', XmlSecurityDSig::NS_XMLDSIG); + if ($this->encryptSignature === true) { + $query = '//ds:Signature | //SOAP-ENV:Body'; + } else { + $query = '//SOAP-ENV:Body'; + } + + return $xpath->query($query); + } + + /** + * Create a list of \DOMNodes that should be signed. + * + * @param \DOMDocument $dom DOMDocument to query + * @param \DOMElement $security Security element + * + * @return array(\DOMNode) + */ + protected function createNodeListForSigning(\DOMDocument $dom, \DOMElement $security) + { + $nodes = array(); + $body = $dom->getElementsByTagNameNS($dom->documentElement->namespaceURI, 'Body')->item(0); + if (null !== $body) { + $nodes[] = $body; + } + foreach ($security->childNodes as $node) { + if (XML_ELEMENT_NODE === $node->nodeType) { + $nodes[] = $node; + } + } + if ($this->signAllHeaders) { + foreach ($security->parentNode->childNodes as $node) { + if (XML_ELEMENT_NODE === $node->nodeType && + Helper::NS_WSS !== $node->namespaceURI) { + $nodes[] = $node; + } + } + } + + return $nodes; + } + + /** + * Gets the referenced node for the given URI. + * + * @param \DOMElement $node Node + * @param string $uri URI + * + * @return \DOMElement + */ + protected function getReferenceNodeForUri(\DOMElement $node, $uri) + { + $url = parse_url($uri); + $referenceId = $url['fragment']; + $query = '//*[@'.Helper::PFX_WSU.':Id="'.$referenceId.'" or @Id="'.$referenceId.'"]'; + $xpath = new \DOMXPath($node->ownerDocument); + $xpath->registerNamespace(Helper::PFX_WSU, Helper::NS_WSU); + + return $xpath->query($query)->item(0); + } + + + /** + * Tries to resolve a key from the given \DOMElement. + * + * @param \DOMElement $node Node where to resolve the key + * @param string $algorithm XML security key algorithm + * + * @return \ass\XmlSecurity\Key|null + */ + public function keyInfoSecurityTokenReferenceResolver(\DOMElement $node, $algorithm) + { + foreach ($node->childNodes as $key) { + if (Helper::NS_WSS === $key->namespaceURI) { + switch ($key->localName) { + case 'KeyIdentifier': + + return $this->serviceSecurityKey->getPublicKey(); + case 'Reference': + $uri = $key->getAttribute('URI'); + $referencedNode = $this->getReferenceNodeForUri($node, $uri); + + if (XmlSecurityEnc::NS_XMLENC === $referencedNode->namespaceURI + && 'EncryptedKey' == $referencedNode->localName) { + $key = XmlSecurityEnc::decryptEncryptedKey($referencedNode, $this->userSecurityKey->getPrivateKey()); + + return XmlSecurityKey::factory($algorithm, $key, false, XmlSecurityKey::TYPE_PRIVATE); + } elseif (Helper::NS_WSS === $referencedNode->namespaceURI + && 'BinarySecurityToken' == $referencedNode->localName) { + + $key = XmlSecurityPem::formatKeyInPemFormat($referencedNode->textContent); + + return XmlSecurityKey::factory(XmlSecurityKey::RSA_SHA1, $key, false, XmlSecurityKey::TYPE_PUBLIC); + } + } + } + } + + return null; + } +} \ No newline at end of file From 6a5109c526165e41d8e6a4c2a17788bcb4fe0020 Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Mon, 19 Aug 2013 19:26:11 +0200 Subject: [PATCH 04/26] fix SoapFault handling to throw proper soap fault to client --- src/BeSimple/SoapServer/SoapServer.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/BeSimple/SoapServer/SoapServer.php b/src/BeSimple/SoapServer/SoapServer.php index ced2ad0..d8d24a8 100644 --- a/src/BeSimple/SoapServer/SoapServer.php +++ b/src/BeSimple/SoapServer/SoapServer.php @@ -70,7 +70,12 @@ class SoapServer extends \SoapServer $soapRequest = SoapRequest::create($request, $this->soapVersion); // handle actual SOAP request - $soapResponse = $this->handle2($soapRequest); + try { + $soapResponse = $this->handle2($soapRequest); + } catch (\SoapFault $fault) { + // issue an error to the client + $this->fault($fault->faultcode, $fault->faultstring); + } // send SOAP response to client $soapResponse->send(); From 580e6f4da7e98add5ebe42cc0c2fa87fea68447a Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Mon, 19 Aug 2013 19:27:34 +0200 Subject: [PATCH 05/26] added WsSecurityFilter that supports UsernamePassword for now --- src/BeSimple/SoapServer/WsSecurityFilter.php | 177 +++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 src/BeSimple/SoapServer/WsSecurityFilter.php diff --git a/src/BeSimple/SoapServer/WsSecurityFilter.php b/src/BeSimple/SoapServer/WsSecurityFilter.php new file mode 100644 index 0000000..394791b --- /dev/null +++ b/src/BeSimple/SoapServer/WsSecurityFilter.php @@ -0,0 +1,177 @@ + + * (c) Francis Besset + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace BeSimple\SoapServer; + +use ass\XmlSecurity\DSig as XmlSecurityDSig; +use ass\XmlSecurity\Enc as XmlSecurityEnc; +use ass\XmlSecurity\Key as XmlSecurityKey; + +use BeSimple\SoapCommon\FilterHelper; +use BeSimple\SoapCommon\Helper; +use BeSimple\SoapCommon\SoapRequest as CommonSoapRequest; +use BeSimple\SoapCommon\SoapRequestFilter; +use BeSimple\SoapCommon\SoapResponse as CommonSoapResponse; +use BeSimple\SoapCommon\SoapResponseFilter; +use BeSimple\SoapCommon\WsSecurityKey; + +/** + * This plugin implements a subset of the following standards: + * * Web Services Security: SOAP Message Security 1.0 (WS-Security 2004) + * http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0.pdf + * * Web Services Security UsernameToken Profile 1.0 + * http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0.pdf + * * Web Services Security X.509 Certificate Token Profile + * http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0.pdf + * + * @author Andreas Schamberger + */ +class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter +{ + /* + * The date format to be used with {@link \DateTime} + */ + const DATETIME_FORMAT = 'Y-m-d\TH:i:s.000\Z'; + + /** + * Actor. + * + * @var string + */ + protected $actor; + + /** + * (SMS 10) Add security timestamp. + * + * @var boolean + */ + protected $addTimestamp; + + /** + * (SMS 10) Security timestamp expires time in seconds. + * + * @var int + */ + protected $expires; + + /** + * Username/password callback that returns password or null. + * + * @var callable + */ + protected $usernamePasswordCallback; + + /** + * Constructor. + * + * @param boolean $addTimestamp (SMS 10) Add security timestamp. + * @param int $expires (SMS 10) Security timestamp expires time in seconds. + * @param string $actor SOAP actor + */ + public function __construct($addTimestamp = true, $expires = 300, $actor = null) + { + $this->addTimestamp = $addTimestamp; + $this->expires = $expires; + $this->actor = $actor; + } + + /** + * Set username/password callback that returns password or null. + * + * @param callable $callback Username/password callback function + * + * @return void + */ + public function setUsernamePasswordCallback($callback) + { + $this->usernamePasswordCallback = $callback; + } + + /** + * Reset all properties to default values. + */ + public function resetFilter() + { + $this->actor = null; + $this->addTimestamp = null; + $this->expires = null; + $this->usernamePasswordCallback = null; + } + + /** + * Modify the given request XML. + * + * @param \BeSimple\SoapCommon\SoapRequest $request SOAP request + * + * @return void + */ + public function filterRequest(CommonSoapRequest $request) + { + // get \DOMDocument from SOAP request + $dom = $request->getContentDocument(); + + // locate security header + $security = $dom->getElementsByTagNameNS(Helper::NS_WSS, 'Security')->item(0); + if (null !== $security) { + + // is security header still valid? + $query = '//'.Helper::PFX_WSU.':Timestamp/'.Helper::PFX_WSU.':Expires'; + $xpath = new \DOMXPath($dom); + $xpath->registerNamespace(Helper::PFX_WSU, Helper::NS_WSU); + $expires = $xpath->query($query, $security)->item(0); + + if (null !== $expires) { + $expiresDatetime = \DateTime::createFromFormat(self::DATETIME_FORMAT, $expires->textContent, new \DateTimeZone('UTC')); + $currentDatetime = new \DateTime('now', new \DateTimeZone('UTC')); + + if ($currentDatetime > $expiresDatetime) { + throw new \SoapFault('wsu:MessageExpired', 'Security semantics are expired'); + } + } + + $usernameToken = $security->getElementsByTagNameNS(Helper::NS_WSS, 'UsernameToken')->item(0); + if (null !== $usernameToken) { + $usernameTokenUsername = $usernameToken->getElementsByTagNameNS(Helper::NS_WSS, 'Username')->item(0); + $usernameTokenPassword = $usernameToken->getElementsByTagNameNS(Helper::NS_WSS, 'Password')->item(0); + + $password = call_user_func($this->usernamePasswordCallback, $usernameTokenUsername->textContent); + + if ($usernameTokenPassword->getAttribute('Type') == Helper::NAME_WSS_UTP . '#PasswordDigest') { + $nonce = $usernameToken->getElementsByTagNameNS(Helper::NS_WSS, 'Nonce')->item(0); + $created = $usernameToken->getElementsByTagNameNS(Helper::NS_WSU, 'Created')->item(0); + $password = base64_encode(sha1(base64_decode($nonce->textContent) . $created->textContent . $password, true)); + } + + if (null === $password || $usernameTokenPassword->textContent != $password) { + throw new \SoapFault('wsse:FailedAuthentication', 'The security token could not be authenticated or authorized'); + } + } + + $security->parentNode->removeChild($security); + } + } + + /** + * Modify the given request XML. + * + * @param \BeSimple\SoapCommon\SoapResponse $response SOAP response + * + * @return void + */ + public function filterResponse(CommonSoapResponse $response) + { + // get \DOMDocument from SOAP response + $dom = $response->getContentDocument(); + + } + +} \ No newline at end of file From 52290910330321c859517492bd91808b02ad779f Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Tue, 20 Aug 2013 22:27:39 +0200 Subject: [PATCH 06/26] further steps with WS-Security (signing) --- src/BeSimple/SoapServer/WsSecurityFilter.php | 286 +++++++++++++++++++ 1 file changed, 286 insertions(+) diff --git a/src/BeSimple/SoapServer/WsSecurityFilter.php b/src/BeSimple/SoapServer/WsSecurityFilter.php index 394791b..81e5486 100644 --- a/src/BeSimple/SoapServer/WsSecurityFilter.php +++ b/src/BeSimple/SoapServer/WsSecurityFilter.php @@ -15,6 +15,7 @@ namespace BeSimple\SoapServer; use ass\XmlSecurity\DSig as XmlSecurityDSig; use ass\XmlSecurity\Enc as XmlSecurityEnc; use ass\XmlSecurity\Key as XmlSecurityKey; +use ass\XmlSecurity\Pem as XmlSecurityPem; use BeSimple\SoapCommon\FilterHelper; use BeSimple\SoapCommon\Helper; @@ -42,6 +43,21 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter */ const DATETIME_FORMAT = 'Y-m-d\TH:i:s.000\Z'; + /** + * (X509 3.2.1) Reference to a Subject Key Identifier + */ + const TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER = 0; + + /** + * (X509 3.2.1) Reference to a Security Token + */ + const TOKEN_REFERENCE_SECURITY_TOKEN = 1; + + /** + * (SMS_1.1 7.3) Key Identifiers + */ + const TOKEN_REFERENCE_THUMBPRINT_SHA1 = 2; + /** * Actor. * @@ -63,6 +79,34 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter */ protected $expires; + /** + * Sign all headers. + * + * @var boolean + */ + protected $signAllHeaders; + + /** + * (X509 3.2) Token reference type for encryption. + * + * @var int + */ + protected $tokenReferenceEncryption = null; + + /** + * (X509 3.2) Token reference type for signature. + * + * @var int + */ + protected $tokenReferenceSignature = null; + + /** + * Service WsSecurityKey. + * + * @var \BeSimple\SoapCommon\WsSecurityKey + */ + protected $serviceSecurityKey; + /** * Username/password callback that returns password or null. * @@ -104,9 +148,40 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter $this->actor = null; $this->addTimestamp = null; $this->expires = null; + $this->serviceSecurityKey = null; + $this->signAllHeaders = null; + $this->tokenReferenceEncryption = null; + $this->tokenReferenceSignature = null; $this->usernamePasswordCallback = null; } + /** + * Get service security key. + * + * @param \BeSimple\SoapCommon\WsSecurityKey $serviceSecurityKey Service security key + * + * @return void + */ + public function setServiceSecurityKeyObject(WsSecurityKey $serviceSecurityKey) + { + $this->serviceSecurityKey = $serviceSecurityKey; + } + + + /** + * Set security options. + * + * @param int $tokenReference self::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER | self::TOKEN_REFERENCE_SECURITY_TOKEN | self::TOKEN_REFERENCE_THUMBPRINT_SHA1 + * @param boolean $signAllHeaders Sign all headers? + * + * @return void + */ + public function setSecurityOptionsSignature($tokenReference, $signAllHeaders = false) + { + $this->tokenReferenceSignature = $tokenReference; + $this->signAllHeaders = $signAllHeaders; + } + /** * Modify the given request XML. * @@ -156,6 +231,27 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter } } + // add SecurityTokenReference resolver for KeyInfo + $keyResolver = array($this, 'keyInfoSecurityTokenReferenceResolver'); + XmlSecurityDSig::addKeyInfoResolver(Helper::NS_WSS, 'SecurityTokenReference', $keyResolver); + + // locate signature node + $signature = XmlSecurityDSig::locateSignature($security); + if (null !== $signature) { + // verify references + $options = array( + 'id_ns_prefix' => Helper::PFX_WSU, + 'id_prefix_ns' => Helper::NS_WSU, + ); + if (XmlSecurityDSig::verifyReferences($signature, $options) !== true) { + throw new \SoapFault('wsse:FailedCheck', 'The signature or decryption was invalid'); + } + // verify signature + if (XmlSecurityDSig::verifyDocumentSignature($signature) !== true) { + throw new \SoapFault('wsse:FailedCheck', 'The signature or decryption was invalid'); + } + } + $security->parentNode->removeChild($security); } } @@ -172,6 +268,196 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter // get \DOMDocument from SOAP response $dom = $response->getContentDocument(); + // create FilterHelper + $filterHelper = new FilterHelper($dom); + + // add the neccessary namespaces + $filterHelper->addNamespace(Helper::PFX_WSS, Helper::NS_WSS); + $filterHelper->addNamespace(Helper::PFX_WSU, Helper::NS_WSU); + $filterHelper->registerNamespace(XmlSecurityDSig::PFX_XMLDSIG, XmlSecurityDSig::NS_XMLDSIG); + + // init timestamp + $dt = new \DateTime('now', new \DateTimeZone('UTC')); + $createdTimestamp = $dt->format(self::DATETIME_FORMAT); + + // create security header + $security = $filterHelper->createElement(Helper::NS_WSS, 'Security'); + $filterHelper->addHeaderElement($security, true, $this->actor, $response->getVersion()); + + if (true === $this->addTimestamp || null !== $this->expires) { + $timestamp = $filterHelper->createElement(Helper::NS_WSU, 'Timestamp'); + $created = $filterHelper->createElement(Helper::NS_WSU, 'Created', $createdTimestamp); + $timestamp->appendChild($created); + if (null !== $this->expires) { + $dt->modify('+' . $this->expires . ' seconds'); + $expiresTimestamp = $dt->format(self::DATETIME_FORMAT); + $expires = $filterHelper->createElement(Helper::NS_WSU, 'Expires', $expiresTimestamp); + $timestamp->appendChild($expires); + } + $security->appendChild($timestamp); + } + + if (null !== $this->serviceSecurityKey && $this->serviceSecurityKey->hasKeys()) { + $guid = 'CertId-' . Helper::generateUUID(); + // add token references + $keyInfo = null; + if (null !== $this->tokenReferenceSignature) { + $keyInfo = $this->createKeyInfo($filterHelper, $this->tokenReferenceSignature, $guid, $this->serviceSecurityKey->getPublicKey()); + } + $nodes = $this->createNodeListForSigning($dom, $security); + $signature = XmlSecurityDSig::createSignature($this->serviceSecurityKey->getPrivateKey(), XmlSecurityDSig::EXC_C14N, $security, null, $keyInfo); + $options = array( + 'id_ns_prefix' => Helper::PFX_WSU, + 'id_prefix_ns' => Helper::NS_WSU, + ); + foreach ($nodes as $node) { + XmlSecurityDSig::addNodeToSignature($signature, $node, XmlSecurityDSig::SHA1, XmlSecurityDSig::EXC_C14N, $options); + } + XmlSecurityDSig::signDocument($signature, $this->serviceSecurityKey->getPrivateKey(), XmlSecurityDSig::EXC_C14N); + + $publicCertificate = $this->serviceSecurityKey->getPublicKey()->getX509Certificate(true); + $binarySecurityToken = $filterHelper->createElement(Helper::NS_WSS, 'BinarySecurityToken', $publicCertificate); + $filterHelper->setAttribute($binarySecurityToken, null, 'EncodingType', Helper::NAME_WSS_SMS . '#Base64Binary'); + $filterHelper->setAttribute($binarySecurityToken, null, 'ValueType', Helper::NAME_WSS_X509 . '#X509v3'); + $filterHelper->setAttribute($binarySecurityToken, Helper::NS_WSU, 'Id', $guid); + $security->insertBefore($binarySecurityToken, $signature); + } + } + /** + * Adds the configured KeyInfo to the parentNode. + * + * @param FilterHelper $filterHelper Filter helper object + * @param int $tokenReference Token reference type + * @param string $guid Unique ID + * @param \ass\XmlSecurity\Key $xmlSecurityKey XML security key + * + * @return \DOMElement + */ + protected function createKeyInfo(FilterHelper $filterHelper, $tokenReference, $guid, XmlSecurityKey $xmlSecurityKey = null) + { + $keyInfo = $filterHelper->createElement(XmlSecurityDSig::NS_XMLDSIG, 'KeyInfo'); + $securityTokenReference = $filterHelper->createElement(Helper::NS_WSS, 'SecurityTokenReference'); + $keyInfo->appendChild($securityTokenReference); + // security token + if (self::TOKEN_REFERENCE_SECURITY_TOKEN === $tokenReference) { + $reference = $filterHelper->createElement(Helper::NS_WSS, 'Reference'); + $filterHelper->setAttribute($reference, null, 'URI', '#' . $guid); + if (null !== $xmlSecurityKey) { + $filterHelper->setAttribute($reference, null, 'ValueType', Helper::NAME_WSS_X509 . '#X509v3'); + } + $securityTokenReference->appendChild($reference); + // subject key identifier + } elseif (self::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER === $tokenReference && null !== $xmlSecurityKey) { + $keyIdentifier = $filterHelper->createElement(Helper::NS_WSS, 'KeyIdentifier'); + $filterHelper->setAttribute($keyIdentifier, null, 'EncodingType', Helper::NAME_WSS_SMS . '#Base64Binary'); + $filterHelper->setAttribute($keyIdentifier, null, 'ValueType', Helper::NAME_WSS_X509 . '#509SubjectKeyIdentifier'); + $securityTokenReference->appendChild($keyIdentifier); + $certificate = $xmlSecurityKey->getX509SubjectKeyIdentifier(); + $dataNode = new \DOMText($certificate); + $keyIdentifier->appendChild($dataNode); + // thumbprint sha1 + } elseif (self::TOKEN_REFERENCE_THUMBPRINT_SHA1 === $tokenReference && null !== $xmlSecurityKey) { + $keyIdentifier = $filterHelper->createElement(Helper::NS_WSS, 'KeyIdentifier'); + $filterHelper->setAttribute($keyIdentifier, null, 'EncodingType', Helper::NAME_WSS_SMS . '#Base64Binary'); + $filterHelper->setAttribute($keyIdentifier, null, 'ValueType', Helper::NAME_WSS_SMS_1_1 . '#ThumbprintSHA1'); + $securityTokenReference->appendChild($keyIdentifier); + $thumbprintSha1 = base64_encode(sha1(base64_decode($xmlSecurityKey->getX509Certificate(true)), true)); + $dataNode = new \DOMText($thumbprintSha1); + $keyIdentifier->appendChild($dataNode); + } + + return $keyInfo; + } + + /** + * Create a list of \DOMNodes that should be signed. + * + * @param \DOMDocument $dom DOMDocument to query + * @param \DOMElement $security Security element + * + * @return array(\DOMNode) + */ + protected function createNodeListForSigning(\DOMDocument $dom, \DOMElement $security) + { + $nodes = array(); + $body = $dom->getElementsByTagNameNS($dom->documentElement->namespaceURI, 'Body')->item(0); + if (null !== $body) { + $nodes[] = $body; + } + foreach ($security->childNodes as $node) { + if (XML_ELEMENT_NODE === $node->nodeType) { + $nodes[] = $node; + } + } + if ($this->signAllHeaders) { + foreach ($security->parentNode->childNodes as $node) { + if (XML_ELEMENT_NODE === $node->nodeType && + Helper::NS_WSS !== $node->namespaceURI) { + $nodes[] = $node; + } + } + } + + return $nodes; + } + + /** + * Gets the referenced node for the given URI. + * + * @param \DOMElement $node Node + * @param string $uri URI + * + * @return \DOMElement + */ + protected function getReferenceNodeForUri(\DOMElement $node, $uri) + { + $url = parse_url($uri); + $referenceId = $url['fragment']; + $query = '//*[@'.Helper::PFX_WSU.':Id="'.$referenceId.'" or @Id="'.$referenceId.'"]'; + $xpath = new \DOMXPath($node->ownerDocument); + $xpath->registerNamespace(Helper::PFX_WSU, Helper::NS_WSU); + + return $xpath->query($query)->item(0); + } + + /** + * Tries to resolve a key from the given \DOMElement. + * + * @param \DOMElement $node Node where to resolve the key + * @param string $algorithm XML security key algorithm + * + * @return \ass\XmlSecurity\Key|null + */ + public function keyInfoSecurityTokenReferenceResolver(\DOMElement $node, $algorithm) + { + foreach ($node->childNodes as $key) { + if (Helper::NS_WSS === $key->namespaceURI) { + switch ($key->localName) { + case 'KeyIdentifier': + + return $this->serviceSecurityKey->getPublicKey(); + case 'Reference': + $uri = $key->getAttribute('URI'); + $referencedNode = $this->getReferenceNodeForUri($node, $uri); + + if (XmlSecurityEnc::NS_XMLENC === $referencedNode->namespaceURI + && 'EncryptedKey' == $referencedNode->localName) { + $key = XmlSecurityEnc::decryptEncryptedKey($referencedNode, $this->serviceSecurityKey->getPrivateKey()); + + return XmlSecurityKey::factory($algorithm, $key, false, XmlSecurityKey::TYPE_PRIVATE); + } elseif (Helper::NS_WSS === $referencedNode->namespaceURI + && 'BinarySecurityToken' == $referencedNode->localName) { + + $key = XmlSecurityPem::formatKeyInPemFormat($referencedNode->textContent); + + return XmlSecurityKey::factory(XmlSecurityKey::RSA_SHA1, $key, false, XmlSecurityKey::TYPE_PUBLIC); + } + } + } + } + + return null; + } } \ No newline at end of file From 96f8415b9238497e35bf122927f77f4585a4c194 Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Wed, 21 Aug 2013 22:32:09 +0200 Subject: [PATCH 07/26] added encryption to WS Security --- src/BeSimple/SoapServer/WsSecurityFilter.php | 295 +++---------------- 1 file changed, 38 insertions(+), 257 deletions(-) diff --git a/src/BeSimple/SoapServer/WsSecurityFilter.php b/src/BeSimple/SoapServer/WsSecurityFilter.php index 81e5486..6e412b2 100644 --- a/src/BeSimple/SoapServer/WsSecurityFilter.php +++ b/src/BeSimple/SoapServer/WsSecurityFilter.php @@ -24,6 +24,7 @@ use BeSimple\SoapCommon\SoapRequestFilter; use BeSimple\SoapCommon\SoapResponse as CommonSoapResponse; use BeSimple\SoapCommon\SoapResponseFilter; use BeSimple\SoapCommon\WsSecurityKey; +use BeSimple\SoapCommon\WsSecurityFilterClientServer; /** * This plugin implements a subset of the following standards: @@ -36,77 +37,8 @@ use BeSimple\SoapCommon\WsSecurityKey; * * @author Andreas Schamberger */ -class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter +class WsSecurityFilter extends WsSecurityFilterClientServer implements SoapRequestFilter, SoapResponseFilter { - /* - * The date format to be used with {@link \DateTime} - */ - const DATETIME_FORMAT = 'Y-m-d\TH:i:s.000\Z'; - - /** - * (X509 3.2.1) Reference to a Subject Key Identifier - */ - const TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER = 0; - - /** - * (X509 3.2.1) Reference to a Security Token - */ - const TOKEN_REFERENCE_SECURITY_TOKEN = 1; - - /** - * (SMS_1.1 7.3) Key Identifiers - */ - const TOKEN_REFERENCE_THUMBPRINT_SHA1 = 2; - - /** - * Actor. - * - * @var string - */ - protected $actor; - - /** - * (SMS 10) Add security timestamp. - * - * @var boolean - */ - protected $addTimestamp; - - /** - * (SMS 10) Security timestamp expires time in seconds. - * - * @var int - */ - protected $expires; - - /** - * Sign all headers. - * - * @var boolean - */ - protected $signAllHeaders; - - /** - * (X509 3.2) Token reference type for encryption. - * - * @var int - */ - protected $tokenReferenceEncryption = null; - - /** - * (X509 3.2) Token reference type for signature. - * - * @var int - */ - protected $tokenReferenceSignature = null; - - /** - * Service WsSecurityKey. - * - * @var \BeSimple\SoapCommon\WsSecurityKey - */ - protected $serviceSecurityKey; - /** * Username/password callback that returns password or null. * @@ -114,20 +46,6 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter */ protected $usernamePasswordCallback; - /** - * Constructor. - * - * @param boolean $addTimestamp (SMS 10) Add security timestamp. - * @param int $expires (SMS 10) Security timestamp expires time in seconds. - * @param string $actor SOAP actor - */ - public function __construct($addTimestamp = true, $expires = 300, $actor = null) - { - $this->addTimestamp = $addTimestamp; - $this->expires = $expires; - $this->actor = $actor; - } - /** * Set username/password callback that returns password or null. * @@ -145,43 +63,10 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter */ public function resetFilter() { - $this->actor = null; - $this->addTimestamp = null; - $this->expires = null; - $this->serviceSecurityKey = null; - $this->signAllHeaders = null; - $this->tokenReferenceEncryption = null; - $this->tokenReferenceSignature = null; + parent::resetFilter(); $this->usernamePasswordCallback = null; } - /** - * Get service security key. - * - * @param \BeSimple\SoapCommon\WsSecurityKey $serviceSecurityKey Service security key - * - * @return void - */ - public function setServiceSecurityKeyObject(WsSecurityKey $serviceSecurityKey) - { - $this->serviceSecurityKey = $serviceSecurityKey; - } - - - /** - * Set security options. - * - * @param int $tokenReference self::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER | self::TOKEN_REFERENCE_SECURITY_TOKEN | self::TOKEN_REFERENCE_THUMBPRINT_SHA1 - * @param boolean $signAllHeaders Sign all headers? - * - * @return void - */ - public function setSecurityOptionsSignature($tokenReference, $signAllHeaders = false) - { - $this->tokenReferenceSignature = $tokenReference; - $this->signAllHeaders = $signAllHeaders; - } - /** * Modify the given request XML. * @@ -235,6 +120,17 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter $keyResolver = array($this, 'keyInfoSecurityTokenReferenceResolver'); XmlSecurityDSig::addKeyInfoResolver(Helper::NS_WSS, 'SecurityTokenReference', $keyResolver); + // do we have a reference list in header + $referenceList = XmlSecurityEnc::locateReferenceList($security); + // get a list of encrypted nodes + $encryptedNodes = XmlSecurityEnc::locateEncryptedData($dom, $referenceList); + // decrypt them + if (null !== $encryptedNodes) { + foreach ($encryptedNodes as $encryptedNode) { + XmlSecurityEnc::decryptNode($encryptedNode); + } + } + // locate signature node $signature = XmlSecurityDSig::locateSignature($security); if (null !== $signature) { @@ -297,15 +193,15 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter $security->appendChild($timestamp); } - if (null !== $this->serviceSecurityKey && $this->serviceSecurityKey->hasKeys()) { + if (null !== $this->userSecurityKey && $this->userSecurityKey->hasKeys()) { $guid = 'CertId-' . Helper::generateUUID(); // add token references $keyInfo = null; if (null !== $this->tokenReferenceSignature) { - $keyInfo = $this->createKeyInfo($filterHelper, $this->tokenReferenceSignature, $guid, $this->serviceSecurityKey->getPublicKey()); + $keyInfo = $this->createKeyInfo($filterHelper, $this->tokenReferenceSignature, $guid, $this->userSecurityKey->getPublicKey()); } $nodes = $this->createNodeListForSigning($dom, $security); - $signature = XmlSecurityDSig::createSignature($this->serviceSecurityKey->getPrivateKey(), XmlSecurityDSig::EXC_C14N, $security, null, $keyInfo); + $signature = XmlSecurityDSig::createSignature($this->userSecurityKey->getPrivateKey(), XmlSecurityDSig::EXC_C14N, $security, null, $keyInfo); $options = array( 'id_ns_prefix' => Helper::PFX_WSU, 'id_prefix_ns' => Helper::NS_WSU, @@ -313,151 +209,36 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter foreach ($nodes as $node) { XmlSecurityDSig::addNodeToSignature($signature, $node, XmlSecurityDSig::SHA1, XmlSecurityDSig::EXC_C14N, $options); } - XmlSecurityDSig::signDocument($signature, $this->serviceSecurityKey->getPrivateKey(), XmlSecurityDSig::EXC_C14N); + XmlSecurityDSig::signDocument($signature, $this->userSecurityKey->getPrivateKey(), XmlSecurityDSig::EXC_C14N); - $publicCertificate = $this->serviceSecurityKey->getPublicKey()->getX509Certificate(true); + $publicCertificate = $this->userSecurityKey->getPublicKey()->getX509Certificate(true); $binarySecurityToken = $filterHelper->createElement(Helper::NS_WSS, 'BinarySecurityToken', $publicCertificate); $filterHelper->setAttribute($binarySecurityToken, null, 'EncodingType', Helper::NAME_WSS_SMS . '#Base64Binary'); $filterHelper->setAttribute($binarySecurityToken, null, 'ValueType', Helper::NAME_WSS_X509 . '#X509v3'); $filterHelper->setAttribute($binarySecurityToken, Helper::NS_WSU, 'Id', $guid); $security->insertBefore($binarySecurityToken, $signature); - } - } - - /** - * Adds the configured KeyInfo to the parentNode. - * - * @param FilterHelper $filterHelper Filter helper object - * @param int $tokenReference Token reference type - * @param string $guid Unique ID - * @param \ass\XmlSecurity\Key $xmlSecurityKey XML security key - * - * @return \DOMElement - */ - protected function createKeyInfo(FilterHelper $filterHelper, $tokenReference, $guid, XmlSecurityKey $xmlSecurityKey = null) - { - $keyInfo = $filterHelper->createElement(XmlSecurityDSig::NS_XMLDSIG, 'KeyInfo'); - $securityTokenReference = $filterHelper->createElement(Helper::NS_WSS, 'SecurityTokenReference'); - $keyInfo->appendChild($securityTokenReference); - // security token - if (self::TOKEN_REFERENCE_SECURITY_TOKEN === $tokenReference) { - $reference = $filterHelper->createElement(Helper::NS_WSS, 'Reference'); - $filterHelper->setAttribute($reference, null, 'URI', '#' . $guid); - if (null !== $xmlSecurityKey) { - $filterHelper->setAttribute($reference, null, 'ValueType', Helper::NAME_WSS_X509 . '#X509v3'); - } - $securityTokenReference->appendChild($reference); - // subject key identifier - } elseif (self::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER === $tokenReference && null !== $xmlSecurityKey) { - $keyIdentifier = $filterHelper->createElement(Helper::NS_WSS, 'KeyIdentifier'); - $filterHelper->setAttribute($keyIdentifier, null, 'EncodingType', Helper::NAME_WSS_SMS . '#Base64Binary'); - $filterHelper->setAttribute($keyIdentifier, null, 'ValueType', Helper::NAME_WSS_X509 . '#509SubjectKeyIdentifier'); - $securityTokenReference->appendChild($keyIdentifier); - $certificate = $xmlSecurityKey->getX509SubjectKeyIdentifier(); - $dataNode = new \DOMText($certificate); - $keyIdentifier->appendChild($dataNode); - // thumbprint sha1 - } elseif (self::TOKEN_REFERENCE_THUMBPRINT_SHA1 === $tokenReference && null !== $xmlSecurityKey) { - $keyIdentifier = $filterHelper->createElement(Helper::NS_WSS, 'KeyIdentifier'); - $filterHelper->setAttribute($keyIdentifier, null, 'EncodingType', Helper::NAME_WSS_SMS . '#Base64Binary'); - $filterHelper->setAttribute($keyIdentifier, null, 'ValueType', Helper::NAME_WSS_SMS_1_1 . '#ThumbprintSHA1'); - $securityTokenReference->appendChild($keyIdentifier); - $thumbprintSha1 = base64_encode(sha1(base64_decode($xmlSecurityKey->getX509Certificate(true)), true)); - $dataNode = new \DOMText($thumbprintSha1); - $keyIdentifier->appendChild($dataNode); - } - - return $keyInfo; - } - - /** - * Create a list of \DOMNodes that should be signed. - * - * @param \DOMDocument $dom DOMDocument to query - * @param \DOMElement $security Security element - * - * @return array(\DOMNode) - */ - protected function createNodeListForSigning(\DOMDocument $dom, \DOMElement $security) - { - $nodes = array(); - $body = $dom->getElementsByTagNameNS($dom->documentElement->namespaceURI, 'Body')->item(0); - if (null !== $body) { - $nodes[] = $body; - } - foreach ($security->childNodes as $node) { - if (XML_ELEMENT_NODE === $node->nodeType) { - $nodes[] = $node; - } - } - if ($this->signAllHeaders) { - foreach ($security->parentNode->childNodes as $node) { - if (XML_ELEMENT_NODE === $node->nodeType && - Helper::NS_WSS !== $node->namespaceURI) { - $nodes[] = $node; + // encrypt soap document + if (null !== $this->serviceSecurityKey && $this->serviceSecurityKey->hasKeys()) { + $guid = 'EncKey-' . Helper::generateUUID(); + // add token references + $keyInfo = null; + if (null !== $this->tokenReferenceEncryption) { + $keyInfo = $this->createKeyInfo($filterHelper, $this->tokenReferenceEncryption, $guid, $this->serviceSecurityKey->getPublicKey()); + } + $encryptedKey = XmlSecurityEnc::createEncryptedKey($guid, $this->serviceSecurityKey->getPrivateKey(), $this->serviceSecurityKey->getPublicKey(), $security, $signature, $keyInfo); + $referenceList = XmlSecurityEnc::createReferenceList($encryptedKey); + // token reference to encrypted key + $keyInfo = $this->createKeyInfo($filterHelper, self::TOKEN_REFERENCE_SECURITY_TOKEN, $guid); + $nodes = $this->createNodeListForEncryption($dom, $security); + foreach ($nodes as $node) { + $type = XmlSecurityEnc::ELEMENT; + if ($node->localName == 'Body') { + $type = XmlSecurityEnc::CONTENT; + } + XmlSecurityEnc::encryptNode($node, $type, $this->serviceSecurityKey->getPrivateKey(), $referenceList, $keyInfo); } } } - - return $nodes; - } - - /** - * Gets the referenced node for the given URI. - * - * @param \DOMElement $node Node - * @param string $uri URI - * - * @return \DOMElement - */ - protected function getReferenceNodeForUri(\DOMElement $node, $uri) - { - $url = parse_url($uri); - $referenceId = $url['fragment']; - $query = '//*[@'.Helper::PFX_WSU.':Id="'.$referenceId.'" or @Id="'.$referenceId.'"]'; - $xpath = new \DOMXPath($node->ownerDocument); - $xpath->registerNamespace(Helper::PFX_WSU, Helper::NS_WSU); - - return $xpath->query($query)->item(0); - } - - /** - * Tries to resolve a key from the given \DOMElement. - * - * @param \DOMElement $node Node where to resolve the key - * @param string $algorithm XML security key algorithm - * - * @return \ass\XmlSecurity\Key|null - */ - public function keyInfoSecurityTokenReferenceResolver(\DOMElement $node, $algorithm) - { - foreach ($node->childNodes as $key) { - if (Helper::NS_WSS === $key->namespaceURI) { - switch ($key->localName) { - case 'KeyIdentifier': - - return $this->serviceSecurityKey->getPublicKey(); - case 'Reference': - $uri = $key->getAttribute('URI'); - $referencedNode = $this->getReferenceNodeForUri($node, $uri); - - if (XmlSecurityEnc::NS_XMLENC === $referencedNode->namespaceURI - && 'EncryptedKey' == $referencedNode->localName) { - $key = XmlSecurityEnc::decryptEncryptedKey($referencedNode, $this->serviceSecurityKey->getPrivateKey()); - - return XmlSecurityKey::factory($algorithm, $key, false, XmlSecurityKey::TYPE_PRIVATE); - } elseif (Helper::NS_WSS === $referencedNode->namespaceURI - && 'BinarySecurityToken' == $referencedNode->localName) { - - $key = XmlSecurityPem::formatKeyInPemFormat($referencedNode->textContent); - - return XmlSecurityKey::factory(XmlSecurityKey::RSA_SHA1, $key, false, XmlSecurityKey::TYPE_PUBLIC); - } - } - } - } - - return null; } } \ No newline at end of file From 8d62141a3d81f25be630fbe6cf941d8cc5a93a43 Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Fri, 23 Aug 2013 20:55:26 +0200 Subject: [PATCH 08/26] add workaround for cli webserver option --- src/BeSimple/SoapClient/SoapClient.php | 41 ++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/BeSimple/SoapClient/SoapClient.php b/src/BeSimple/SoapClient/SoapClient.php index d0d99e8..b8a75c7 100644 --- a/src/BeSimple/SoapClient/SoapClient.php +++ b/src/BeSimple/SoapClient/SoapClient.php @@ -15,6 +15,7 @@ namespace BeSimple\SoapClient; use BeSimple\SoapCommon\Helper; use BeSimple\SoapCommon\Converter\MtomTypeConverter; use BeSimple\SoapCommon\Converter\SwaTypeConverter; +use BeSimple\SoapCommon\SoapMessage; /** * Extended SoapClient that uses a a cURL wrapper for all underlying HTTP @@ -40,6 +41,15 @@ class SoapClient extends \SoapClient */ protected $tracingEnabled = false; + /** + * Work around missing header/php://input access in PHP cli webserver by + * setting headers additionally as GET parameters and SOAP request body + * explicitly as POST variable. + * + * @var boolean + */ + private $cliWebserverWorkaround = false; + /** * cURL instance. * @@ -98,6 +108,10 @@ class SoapClient extends \SoapClient if (isset($options['soap_version'])) { $this->soapVersion = $options['soap_version']; } + // activate cli webserver workaround + if (isset($options['cli_webserver_workaround'])) { + $this->cliWebserverWorkaround = $options['cli_webserver_workaround']; + } $this->curl = new Curl($options); $wsdlFile = $this->loadWsdl($wsdl, $options); // TODO $wsdlHandler = new WsdlHandler($wsdlFile, $this->soapVersion); @@ -128,10 +142,33 @@ class SoapClient extends \SoapClient 'Content-Type:' . $soapRequest->getContentType(), 'SOAPAction: "' . $soapRequest->getAction() . '"', ); + + $location = $soapRequest->getLocation(); + $content = $soapRequest->getContent(); + /* + * Work around missing header/php://input access in PHP cli webserver by + * setting headers additionally as GET parameters and SOAP request body + * explicitly as POST variable + */ + if ($this->cliWebserverWorkaround === true) { + if (strpos($location, '?') === false) { + $location .= '?'; + } else { + $location .= '&'; + } + $location .= SoapMessage::CONTENT_TYPE_HEADER.'='.urlencode($soapRequest->getContentType()); + $location .= '&'; + $location .= SoapMessage::SOAP_ACTION_HEADER.'='.urlencode($soapRequest->getAction()); + + $content = http_build_query(array('request' => $content)); + + $headers = array(); + } + // execute HTTP request with cURL $responseSuccessfull = $this->curl->exec( - $soapRequest->getLocation(), - $soapRequest->getContent(), + $location, + $content, $headers ); // tracing enabled: store last request header and body From 1cde22d8bbcfdc387ed03bcc685aea99f4fde4cf Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Mon, 19 Aug 2013 22:16:45 +0200 Subject: [PATCH 09/26] fix securitykey factory method call --- src/BeSimple/SoapClient/WsSecurityFilter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BeSimple/SoapClient/WsSecurityFilter.php b/src/BeSimple/SoapClient/WsSecurityFilter.php index cb3fc7c..6eab21b 100644 --- a/src/BeSimple/SoapClient/WsSecurityFilter.php +++ b/src/BeSimple/SoapClient/WsSecurityFilter.php @@ -566,7 +566,7 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter && 'EncryptedKey' == $referencedNode->localName) { $key = XmlSecurityEnc::decryptEncryptedKey($referencedNode, $this->userSecurityKey->getPrivateKey()); - return XmlSecurityKey::factory($algorithm, $key, XmlSecurityKey::TYPE_PRIVATE); + return XmlSecurityKey::factory($algorithm, $key, false, XmlSecurityKey::TYPE_PRIVATE); } else { //$valueType = $key->getAttribute('ValueType'); From 9bc9410172604662ccabe6148c558b4c9fd8864c Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Tue, 20 Aug 2013 22:14:27 +0200 Subject: [PATCH 10/26] add BinarySecurityToken resolving to key resolver --- src/BeSimple/SoapClient/WsSecurityFilter.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/BeSimple/SoapClient/WsSecurityFilter.php b/src/BeSimple/SoapClient/WsSecurityFilter.php index 6eab21b..14ec49a 100644 --- a/src/BeSimple/SoapClient/WsSecurityFilter.php +++ b/src/BeSimple/SoapClient/WsSecurityFilter.php @@ -15,6 +15,7 @@ namespace BeSimple\SoapClient; use ass\XmlSecurity\DSig as XmlSecurityDSig; use ass\XmlSecurity\Enc as XmlSecurityEnc; use ass\XmlSecurity\Key as XmlSecurityKey; +use ass\XmlSecurity\Pem as XmlSecurityPem; use BeSimple\SoapCommon\FilterHelper; use BeSimple\SoapCommon\Helper; @@ -390,10 +391,8 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter $security = $dom->getElementsByTagNameNS(Helper::NS_WSS, 'Security')->item(0); if (null !== $security) { // add SecurityTokenReference resolver for KeyInfo - if (null !== $this->serviceSecurityKey) { - $keyResolver = array($this, 'keyInfoSecurityTokenReferenceResolver'); - XmlSecurityDSig::addKeyInfoResolver(Helper::NS_WSS, 'SecurityTokenReference', $keyResolver); - } + $keyResolver = array($this, 'keyInfoSecurityTokenReferenceResolver'); + XmlSecurityDSig::addKeyInfoResolver(Helper::NS_WSS, 'SecurityTokenReference', $keyResolver); // do we have a reference list in header $referenceList = XmlSecurityEnc::locateReferenceList($security); // get a list of encrypted nodes @@ -420,6 +419,8 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter throw new \SoapFault('wsse:FailedCheck', 'The signature or decryption was invalid'); } } + + $security->parentNode->removeChild($security); } } @@ -567,6 +568,12 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter $key = XmlSecurityEnc::decryptEncryptedKey($referencedNode, $this->userSecurityKey->getPrivateKey()); return XmlSecurityKey::factory($algorithm, $key, false, XmlSecurityKey::TYPE_PRIVATE); + } elseif (Helper::NS_WSS === $referencedNode->namespaceURI + && 'BinarySecurityToken' == $referencedNode->localName) { + + $key = XmlSecurityPem::formatKeyInPemFormat($referencedNode->textContent); + + return XmlSecurityKey::factory(XmlSecurityKey::RSA_SHA1, $key, false, XmlSecurityKey::TYPE_PUBLIC); } else { //$valueType = $key->getAttribute('ValueType'); From b21d758e77f93082768efd0b96fed16d534bdb38 Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Wed, 21 Aug 2013 19:17:51 +0200 Subject: [PATCH 11/26] remove now obsolete code --- src/BeSimple/SoapClient/WsSecurityFilter.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/BeSimple/SoapClient/WsSecurityFilter.php b/src/BeSimple/SoapClient/WsSecurityFilter.php index 14ec49a..69d9d30 100644 --- a/src/BeSimple/SoapClient/WsSecurityFilter.php +++ b/src/BeSimple/SoapClient/WsSecurityFilter.php @@ -574,10 +574,6 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter $key = XmlSecurityPem::formatKeyInPemFormat($referencedNode->textContent); return XmlSecurityKey::factory(XmlSecurityKey::RSA_SHA1, $key, false, XmlSecurityKey::TYPE_PUBLIC); - } else { - //$valueType = $key->getAttribute('ValueType'); - - return $this->serviceSecurityKey->getPublicKey(); } } } From 19def707d398441070d1acf697c35683dd628ccb Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Fri, 23 Aug 2013 20:59:35 +0200 Subject: [PATCH 12/26] use WsSecurityFilterClientServer from common --- src/BeSimple/SoapClient/WsSecurityFilter.php | 319 +------------------ 1 file changed, 6 insertions(+), 313 deletions(-) diff --git a/src/BeSimple/SoapClient/WsSecurityFilter.php b/src/BeSimple/SoapClient/WsSecurityFilter.php index 69d9d30..b66aae3 100644 --- a/src/BeSimple/SoapClient/WsSecurityFilter.php +++ b/src/BeSimple/SoapClient/WsSecurityFilter.php @@ -24,6 +24,7 @@ use BeSimple\SoapCommon\SoapRequestFilter; use BeSimple\SoapCommon\SoapResponse as CommonSoapResponse; use BeSimple\SoapCommon\SoapResponseFilter; use BeSimple\SoapCommon\WsSecurityKey; +use BeSimple\SoapCommon\WsSecurityFilterClientServer; /** * This plugin implements a subset of the following standards: @@ -36,13 +37,8 @@ use BeSimple\SoapCommon\WsSecurityKey; * * @author Andreas Schamberger */ -class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter +class WsSecurityFilter extends WsSecurityFilterClientServer implements SoapRequestFilter, SoapResponseFilter { - /* - * The date format to be used with {@link \DateTime} - */ - const DATETIME_FORMAT = 'Y-m-d\TH:i:s.000\Z'; - /** * (UT 3.1) Password type: plain text. */ @@ -53,49 +49,6 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter */ const PASSWORD_TYPE_DIGEST = 1; - /** - * (X509 3.2.1) Reference to a Subject Key Identifier - */ - const TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER = 0; - - /** - * (X509 3.2.1) Reference to a Security Token - */ - const TOKEN_REFERENCE_SECURITY_TOKEN = 1; - - /** - * (SMS_1.1 7.3) Key Identifiers - */ - const TOKEN_REFERENCE_THUMBPRINT_SHA1 = 2; - - /** - * Actor. - * - * @var string - */ - protected $actor; - - /** - * (SMS 10) Add security timestamp. - * - * @var boolean - */ - protected $addTimestamp; - - /** - * Encrypt the signature? - * - * @var boolean - */ - protected $encryptSignature; - - /** - * (SMS 10) Security timestamp expires time in seconds. - * - * @var int - */ - protected $expires; - /** * (UT 3.1) Password. * @@ -110,34 +63,6 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter */ protected $passwordType; - /** - * Sign all headers. - * - * @var boolean - */ - protected $signAllHeaders; - - /** - * (X509 3.2) Token reference type for encryption. - * - * @var int - */ - protected $tokenReferenceEncryption = null; - - /** - * (X509 3.2) Token reference type for signature. - * - * @var int - */ - protected $tokenReferenceSignature = null; - - /** - * Service WsSecurityKey. - * - * @var \BeSimple\SoapCommon\WsSecurityKey - */ - protected $serviceSecurityKey; - /** * (UT 3.1) Username. * @@ -152,20 +77,6 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter */ protected $userSecurityKey; - /** - * Constructor. - * - * @param boolean $addTimestamp (SMS 10) Add security timestamp. - * @param int $expires (SMS 10) Security timestamp expires time in seconds. - * @param string $actor SOAP actor - */ - public function __construct($addTimestamp = true, $expires = 300, $actor = null) - { - $this->addTimestamp = $addTimestamp; - $this->expires = $expires; - $this->actor = $actor; - } - /** * Add user data. * @@ -187,70 +98,10 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter */ public function resetFilter() { - $this->actor = null; - $this->addTimestamp = null; - $this->encryptSignature = null; - $this->expires = null; - $this->password = null; - $this->passwordType = null; - $this->serviceSecurityKey = null; - $this->signAllHeaders = null; - $this->tokenReferenceEncryption = null; - $this->tokenReferenceSignature = null; - $this->username = null; - $this->userSecurityKey = null; - } - - /** - * Get service security key. - * - * @param \BeSimple\SoapCommon\WsSecurityKey $serviceSecurityKey Service security key - * - * @return void - */ - public function setServiceSecurityKeyObject(WsSecurityKey $serviceSecurityKey) - { - $this->serviceSecurityKey = $serviceSecurityKey; - } - - /** - * Get user security key. - * - * @param \BeSimple\SoapCommon\WsSecurityKey $userSecurityKey User security key - * - * @return void - */ - public function setUserSecurityKeyObject(WsSecurityKey $userSecurityKey) - { - $this->userSecurityKey = $userSecurityKey; - } - - /** - * Set security options. - * - * @param int $tokenReference self::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER | self::TOKEN_REFERENCE_SECURITY_TOKEN | self::TOKEN_REFERENCE_THUMBPRINT_SHA1 - * @param boolean $encryptSignature Encrypt signature - * - * @return void - */ - public function setSecurityOptionsEncryption($tokenReference, $encryptSignature = false) - { - $this->tokenReferenceEncryption = $tokenReference; - $this->encryptSignature = $encryptSignature; - } - - /** - * Set security options. - * - * @param int $tokenReference self::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER | self::TOKEN_REFERENCE_SECURITY_TOKEN | self::TOKEN_REFERENCE_THUMBPRINT_SHA1 - * @param boolean $signAllHeaders Sign all headers? - * - * @return void - */ - public function setSecurityOptionsSignature($tokenReference, $signAllHeaders = false) - { - $this->tokenReferenceSignature = $tokenReference; - $this->signAllHeaders = $signAllHeaders; + parent::resetFilter(); + $this->password = null; + $this->passwordType = null; + $this->username = null; } /** @@ -423,162 +274,4 @@ class WsSecurityFilter implements SoapRequestFilter, SoapResponseFilter $security->parentNode->removeChild($security); } } - - /** - * Adds the configured KeyInfo to the parentNode. - * - * @param FilterHelper $filterHelper Filter helper object - * @param int $tokenReference Token reference type - * @param string $guid Unique ID - * @param \ass\XmlSecurity\Key $xmlSecurityKey XML security key - * - * @return \DOMElement - */ - protected function createKeyInfo(FilterHelper $filterHelper, $tokenReference, $guid, XmlSecurityKey $xmlSecurityKey = null) - { - $keyInfo = $filterHelper->createElement(XmlSecurityDSig::NS_XMLDSIG, 'KeyInfo'); - $securityTokenReference = $filterHelper->createElement(Helper::NS_WSS, 'SecurityTokenReference'); - $keyInfo->appendChild($securityTokenReference); - // security token - if (self::TOKEN_REFERENCE_SECURITY_TOKEN === $tokenReference) { - $reference = $filterHelper->createElement(Helper::NS_WSS, 'Reference'); - $filterHelper->setAttribute($reference, null, 'URI', '#' . $guid); - if (null !== $xmlSecurityKey) { - $filterHelper->setAttribute($reference, null, 'ValueType', Helper::NAME_WSS_X509 . '#X509v3'); - } - $securityTokenReference->appendChild($reference); - // subject key identifier - } elseif (self::TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER === $tokenReference && null !== $xmlSecurityKey) { - $keyIdentifier = $filterHelper->createElement(Helper::NS_WSS, 'KeyIdentifier'); - $filterHelper->setAttribute($keyIdentifier, null, 'EncodingType', Helper::NAME_WSS_SMS . '#Base64Binary'); - $filterHelper->setAttribute($keyIdentifier, null, 'ValueType', Helper::NAME_WSS_X509 . '#509SubjectKeyIdentifier'); - $securityTokenReference->appendChild($keyIdentifier); - $certificate = $xmlSecurityKey->getX509SubjectKeyIdentifier(); - $dataNode = new \DOMText($certificate); - $keyIdentifier->appendChild($dataNode); - // thumbprint sha1 - } elseif (self::TOKEN_REFERENCE_THUMBPRINT_SHA1 === $tokenReference && null !== $xmlSecurityKey) { - $keyIdentifier = $filterHelper->createElement(Helper::NS_WSS, 'KeyIdentifier'); - $filterHelper->setAttribute($keyIdentifier, null, 'EncodingType', Helper::NAME_WSS_SMS . '#Base64Binary'); - $filterHelper->setAttribute($keyIdentifier, null, 'ValueType', Helper::NAME_WSS_SMS_1_1 . '#ThumbprintSHA1'); - $securityTokenReference->appendChild($keyIdentifier); - $thumbprintSha1 = base64_encode(sha1(base64_decode($xmlSecurityKey->getX509Certificate(true)), true)); - $dataNode = new \DOMText($thumbprintSha1); - $keyIdentifier->appendChild($dataNode); - } - - return $keyInfo; - } - - /** - * Create a list of \DOMNodes that should be encrypted. - * - * @param \DOMDocument $dom DOMDocument to query - * @param \DOMElement $security Security element - * - * @return \DOMNodeList - */ - protected function createNodeListForEncryption(\DOMDocument $dom, \DOMElement $security) - { - $xpath = new \DOMXPath($dom); - $xpath->registerNamespace('SOAP-ENV', $dom->documentElement->namespaceURI); - $xpath->registerNamespace('ds', XmlSecurityDSig::NS_XMLDSIG); - if ($this->encryptSignature === true) { - $query = '//ds:Signature | //SOAP-ENV:Body'; - } else { - $query = '//SOAP-ENV:Body'; - } - - return $xpath->query($query); - } - - /** - * Create a list of \DOMNodes that should be signed. - * - * @param \DOMDocument $dom DOMDocument to query - * @param \DOMElement $security Security element - * - * @return array(\DOMNode) - */ - protected function createNodeListForSigning(\DOMDocument $dom, \DOMElement $security) - { - $nodes = array(); - $body = $dom->getElementsByTagNameNS($dom->documentElement->namespaceURI, 'Body')->item(0); - if (null !== $body) { - $nodes[] = $body; - } - foreach ($security->childNodes as $node) { - if (XML_ELEMENT_NODE === $node->nodeType) { - $nodes[] = $node; - } - } - if ($this->signAllHeaders) { - foreach ($security->parentNode->childNodes as $node) { - if (XML_ELEMENT_NODE === $node->nodeType && - Helper::NS_WSS !== $node->namespaceURI) { - $nodes[] = $node; - } - } - } - - return $nodes; - } - - /** - * Gets the referenced node for the given URI. - * - * @param \DOMElement $node Node - * @param string $uri URI - * - * @return \DOMElement - */ - protected function getReferenceNodeForUri(\DOMElement $node, $uri) - { - $url = parse_url($uri); - $referenceId = $url['fragment']; - $query = '//*[@'.Helper::PFX_WSU.':Id="'.$referenceId.'" or @Id="'.$referenceId.'"]'; - $xpath = new \DOMXPath($node->ownerDocument); - $xpath->registerNamespace(Helper::PFX_WSU, Helper::NS_WSU); - - return $xpath->query($query)->item(0); - } - - /** - * Tries to resolve a key from the given \DOMElement. - * - * @param \DOMElement $node Node where to resolve the key - * @param string $algorithm XML security key algorithm - * - * @return \ass\XmlSecurity\Key|null - */ - public function keyInfoSecurityTokenReferenceResolver(\DOMElement $node, $algorithm) - { - foreach ($node->childNodes as $key) { - if (Helper::NS_WSS === $key->namespaceURI) { - switch ($key->localName) { - case 'KeyIdentifier': - - return $this->serviceSecurityKey->getPublicKey(); - case 'Reference': - $uri = $key->getAttribute('URI'); - $referencedNode = $this->getReferenceNodeForUri($node, $uri); - - if (XmlSecurityEnc::NS_XMLENC === $referencedNode->namespaceURI - && 'EncryptedKey' == $referencedNode->localName) { - $key = XmlSecurityEnc::decryptEncryptedKey($referencedNode, $this->userSecurityKey->getPrivateKey()); - - return XmlSecurityKey::factory($algorithm, $key, false, XmlSecurityKey::TYPE_PRIVATE); - } elseif (Helper::NS_WSS === $referencedNode->namespaceURI - && 'BinarySecurityToken' == $referencedNode->localName) { - - $key = XmlSecurityPem::formatKeyInPemFormat($referencedNode->textContent); - - return XmlSecurityKey::factory(XmlSecurityKey::RSA_SHA1, $key, false, XmlSecurityKey::TYPE_PUBLIC); - } - } - } - } - - return null; - } } \ No newline at end of file From f8a80c036100dd3ea5a77bb73ab1fe66bb1f9de1 Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Fri, 23 Aug 2013 21:45:09 +0200 Subject: [PATCH 13/26] fix wrong method call --- src/BeSimple/SoapClient/Curl.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BeSimple/SoapClient/Curl.php b/src/BeSimple/SoapClient/Curl.php index 54e40cf..699e56c 100644 --- a/src/BeSimple/SoapClient/Curl.php +++ b/src/BeSimple/SoapClient/Curl.php @@ -128,7 +128,7 @@ class Curl curl_setopt($this->ch, CURLOPT_HTTPHEADER, $requestHeaders); } - $this->response = $this->execManualRedirect($this->followLocationMaxRedirects); + $this->response = $this->execManualRedirect(); return ($this->response === false) ? false : true; } From 2b1226430482673614874304e34b579e682ef735 Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Fri, 23 Aug 2013 23:25:16 +0200 Subject: [PATCH 14/26] Axis and SoapServer interop tests --- .../Fixtures/AttachmentRequest.php | 7 + .../AxisInterop/Fixtures/AttachmentType.php | 9 + .../AxisInterop/Fixtures/BookInformation.php | 11 + .../AxisInterop/{ => Fixtures}/MTOM.wsdl | 178 +++++----- .../Tests/AxisInterop/{ => Fixtures}/SwA.wsdl | 324 +++++++++--------- .../{ => Fixtures}/WsSecuritySigEnc.wsdl | 0 .../{ => Fixtures}/WsSecurityUserPass.wsdl | 0 .../Tests/AxisInterop/Fixtures/addBook.php | 11 + .../AxisInterop/Fixtures/addBookResponse.php | 8 + .../AxisInterop/Fixtures/base64Binary.php | 9 + .../AxisInterop/{ => Fixtures}/clientcert.pem | 0 .../AxisInterop/{ => Fixtures}/clientkey.pem | 28 +- .../AxisInterop/Fixtures/downloadFile.php | 8 + .../Fixtures/downloadFileResponse.php | 8 + .../Tests/AxisInterop/Fixtures/getBook.php | 8 + .../AxisInterop/Fixtures/getBookResponse.php | 8 + .../AxisInterop/Fixtures/getBooksByType.php | 8 + .../Fixtures/getBooksByTypeResponse.php | 8 + .../AxisInterop/{ => Fixtures}/image.jpg | Bin .../AxisInterop/{ => Fixtures}/servercert.pem | 0 .../Tests/AxisInterop/Fixtures/uploadFile.php | 9 + .../Fixtures/uploadFileResponse.php | 8 + .../SoapClient/Tests/AxisInterop/MTOM.php | 100 ------ .../Tests/AxisInterop/MtomAxisInteropTest.php | 51 +++ .../SoapClient/Tests/AxisInterop/SwA.php | 84 ----- .../Tests/AxisInterop/SwA/build.xml | 76 ++-- .../SwA/resources/META-INF/services.xml | 28 +- .../Tests/AxisInterop/SwaAxisInteropTest.php | 77 +++++ .../SoapClient/Tests/AxisInterop/TestCase.php | 23 ++ .../Tests/AxisInterop/WsAddressing.php | 73 ---- .../WsAddressingAxisInteropTest.php | 59 ++++ .../Tests/AxisInterop/WsSecuritySigEnc.php | 116 ------- .../WsSecuritySigEncAxisInteropTest.php | 106 ++++++ .../Tests/AxisInterop/WsSecurityUserPass.php | 79 ----- .../WsSecurityUserPassAxisInteropTest.php | 96 ++++++ .../Fixtures/AttachmentRequest.php | 7 + .../ServerInterop/Fixtures/AttachmentType.php | 9 + .../Fixtures/BookInformation.php | 11 + .../ServerInterop/{ => Fixtures}/MTOM.wsdl | 178 +++++----- .../Tests/ServerInterop/Fixtures/SwA.wsdl | 162 +++++++++ .../Fixtures/WsSecuritySigEnc.wsdl | 184 ++++++++++ .../Fixtures/WsSecurityUserPass.wsdl | 184 ++++++++++ .../Tests/ServerInterop/Fixtures/addBook.php | 11 + .../Fixtures/addBookResponse.php | 8 + .../ServerInterop/Fixtures/base64Binary.php | 9 + .../ServerInterop/Fixtures/clientcert.pem | 17 + .../ServerInterop/Fixtures/clientkey.pem | 14 + .../ServerInterop/Fixtures/downloadFile.php | 8 + .../Fixtures/downloadFileResponse.php | 8 + .../Tests/ServerInterop/Fixtures/getBook.php | 8 + .../Fixtures/getBookResponse.php | 8 + .../ServerInterop/Fixtures/getBooksByType.php | 8 + .../Fixtures/getBooksByTypeResponse.php | 8 + .../Tests/ServerInterop/Fixtures/image.jpg | Bin 0 -> 75596 bytes .../ServerInterop/Fixtures/servercert.pem | 17 + .../ServerInterop/Fixtures/serverkey.pem | 14 + .../ServerInterop/Fixtures/uploadFile.php | 9 + .../Fixtures/uploadFileResponse.php | 8 + .../{MTOM.php => MTOMClient.php} | 29 +- .../Tests/ServerInterop/MTOMServer.php | 34 +- .../ServerInterop/MtomServerInteropTest.php | 44 +++ .../Tests/ServerInterop/SwAServer.php | 49 +++ .../Tests/ServerInterop/SwaClient.php | 53 +++ .../ServerInterop/SwaServerInteropTest.php | 64 ++++ .../Tests/ServerInterop/TestCase.php | 29 ++ .../ServerInterop/WsSecuritySigEncServer.php | 82 +++++ .../WsSecuritySigEncServerClient.php | 83 +++++ .../WsSecuritySigEncServerInteropTest.php | 73 ++++ .../WsSecurityUserPassServer.php | 78 +++++ .../WsSecurityUserPassServerClient.php | 68 ++++ .../WsSecurityUserPassServerInteropTest.php | 86 +++++ src/BeSimple/SoapClient/Tests/bin/axis.sh | 44 +++ .../SoapClient/Tests/bin/phpwebserver.sh | 15 + 73 files changed, 2435 insertions(+), 904 deletions(-) create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/AttachmentRequest.php create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/AttachmentType.php create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/BookInformation.php rename src/BeSimple/SoapClient/Tests/AxisInterop/{ => Fixtures}/MTOM.wsdl (97%) rename src/BeSimple/SoapClient/Tests/AxisInterop/{ => Fixtures}/SwA.wsdl (98%) rename src/BeSimple/SoapClient/Tests/AxisInterop/{ => Fixtures}/WsSecuritySigEnc.wsdl (100%) rename src/BeSimple/SoapClient/Tests/AxisInterop/{ => Fixtures}/WsSecurityUserPass.wsdl (100%) create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/addBook.php create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/addBookResponse.php create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/base64Binary.php rename src/BeSimple/SoapClient/Tests/AxisInterop/{ => Fixtures}/clientcert.pem (100%) rename src/BeSimple/SoapClient/Tests/AxisInterop/{ => Fixtures}/clientkey.pem (98%) create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/downloadFile.php create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/downloadFileResponse.php create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/getBook.php create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/getBookResponse.php create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/getBooksByType.php create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/getBooksByTypeResponse.php rename src/BeSimple/SoapClient/Tests/AxisInterop/{ => Fixtures}/image.jpg (100%) rename src/BeSimple/SoapClient/Tests/AxisInterop/{ => Fixtures}/servercert.pem (100%) create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/uploadFile.php create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/uploadFileResponse.php delete mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/MTOM.php create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/MtomAxisInteropTest.php delete mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/SwA.php create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/SwaAxisInteropTest.php create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/TestCase.php delete mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/WsAddressing.php create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/WsAddressingAxisInteropTest.php delete mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/WsSecuritySigEnc.php create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/WsSecuritySigEncAxisInteropTest.php delete mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/WsSecurityUserPass.php create mode 100644 src/BeSimple/SoapClient/Tests/AxisInterop/WsSecurityUserPassAxisInteropTest.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/AttachmentRequest.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/AttachmentType.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/BookInformation.php rename src/BeSimple/SoapClient/Tests/ServerInterop/{ => Fixtures}/MTOM.wsdl (93%) create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/SwA.wsdl create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/WsSecuritySigEnc.wsdl create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/WsSecurityUserPass.wsdl create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/addBook.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/addBookResponse.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/base64Binary.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/clientcert.pem create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/clientkey.pem create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/downloadFile.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/downloadFileResponse.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/getBook.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/getBookResponse.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/getBooksByType.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/getBooksByTypeResponse.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/image.jpg create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/servercert.pem create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/serverkey.pem create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/uploadFile.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/uploadFileResponse.php rename src/BeSimple/SoapClient/Tests/ServerInterop/{MTOM.php => MTOMClient.php} (65%) create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/MtomServerInteropTest.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/SwAServer.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/SwaClient.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/SwaServerInteropTest.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/TestCase.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/WsSecuritySigEncServer.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/WsSecuritySigEncServerClient.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/WsSecuritySigEncServerInteropTest.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/WsSecurityUserPassServer.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/WsSecurityUserPassServerClient.php create mode 100644 src/BeSimple/SoapClient/Tests/ServerInterop/WsSecurityUserPassServerInteropTest.php create mode 100644 src/BeSimple/SoapClient/Tests/bin/axis.sh create mode 100644 src/BeSimple/SoapClient/Tests/bin/phpwebserver.sh diff --git a/src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/AttachmentRequest.php b/src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/AttachmentRequest.php new file mode 100644 index 0000000..e4f0236 --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/AttachmentRequest.php @@ -0,0 +1,7 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BeSimple/SoapClient/Tests/AxisInterop/SwA.wsdl b/src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/SwA.wsdl similarity index 98% rename from src/BeSimple/SoapClient/Tests/AxisInterop/SwA.wsdl rename to src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/SwA.wsdl index a4de7e0..d63fe2d 100644 --- a/src/BeSimple/SoapClient/Tests/AxisInterop/SwA.wsdl +++ b/src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/SwA.wsdl @@ -1,162 +1,162 @@ - - - BeSimpleSwaService - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + BeSimpleSwaService + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BeSimple/SoapClient/Tests/AxisInterop/WsSecuritySigEnc.wsdl b/src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/WsSecuritySigEnc.wsdl similarity index 100% rename from src/BeSimple/SoapClient/Tests/AxisInterop/WsSecuritySigEnc.wsdl rename to src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/WsSecuritySigEnc.wsdl diff --git a/src/BeSimple/SoapClient/Tests/AxisInterop/WsSecurityUserPass.wsdl b/src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/WsSecurityUserPass.wsdl similarity index 100% rename from src/BeSimple/SoapClient/Tests/AxisInterop/WsSecurityUserPass.wsdl rename to src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/WsSecurityUserPass.wsdl diff --git a/src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/addBook.php b/src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/addBook.php new file mode 100644 index 0000000..7a171b0 --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/AxisInterop/Fixtures/addBook.php @@ -0,0 +1,11 @@ + SOAP_1_1, - 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 - 'trace' => true, // enables use of the methods SoapClient->__getLastRequest, SoapClient->__getLastRequestHeaders, SoapClient->__getLastResponse and SoapClient->__getLastResponseHeaders - 'attachment_type' => BeSimpleSoapHelper::ATTACHMENTS_TYPE_MTOM, - 'cache_wsdl' => WSDL_CACHE_NONE, - 'classmap' => array( - 'base64Binary' => 'base64Binary', - 'AttachmentRequest' => 'AttachmentRequest', - ), -); - -/* - * Deploy "axis_services/sample-mtom.aar" to Apache Axis2 to get this - * example to work. - * - * Apache Axis2 MTOM example. - * - */ -$sc = new BeSimpleSoapClient('MTOM.wsdl', $options); - -//var_dump($sc->__getFunctions()); -//var_dump($sc->__getTypes()); - -try { - $b64 = new base64Binary(); - $b64->_ = 'This is a test. :)'; - $b64->contentType = 'text/plain'; - - $attachment = new AttachmentRequest(); - $attachment->fileName = 'test123.txt'; - $attachment->binaryData = $b64; - - var_dump($sc->attachment($attachment)); - -} catch (Exception $e) { - var_dump($e); -} - -// var_dump( -// $sc->__getLastRequestHeaders(), -// $sc->__getLastRequest(), -// $sc->__getLastResponseHeaders(), -// $sc->__getLastResponse() -// ); \ No newline at end of file diff --git a/src/BeSimple/SoapClient/Tests/AxisInterop/MtomAxisInteropTest.php b/src/BeSimple/SoapClient/Tests/AxisInterop/MtomAxisInteropTest.php new file mode 100644 index 0000000..27d9e20 --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/AxisInterop/MtomAxisInteropTest.php @@ -0,0 +1,51 @@ + SOAP_1_1, + 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 + 'attachment_type' => BeSimpleSoapHelper::ATTACHMENTS_TYPE_MTOM, + 'cache_wsdl' => WSDL_CACHE_NONE, + 'classmap' => array( + 'base64Binary' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\base64Binary', + 'AttachmentRequest' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\AttachmentRequest', + ), + ); + + public function testAttachment() + { + $sc = new BeSimpleSoapClient(__DIR__.'/Fixtures/MTOM.wsdl', $this->options); + + $b64 = new base64Binary(); + $b64->_ = 'This is a test. :)'; + $b64->contentType = 'text/plain'; + + $attachment = new AttachmentRequest(); + $attachment->fileName = 'test123.txt'; + $attachment->binaryData = $b64; + + $this->assertEquals('File saved succesfully.', $sc->attachment($attachment)); + +// $fileCreatedByServer = __DIR__.'/'.$attachment->fileName; +// $this->assertEquals($b64->_, file_get_contents($fileCreatedByServer)); +// unlink($fileCreatedByServer); + } +} \ No newline at end of file diff --git a/src/BeSimple/SoapClient/Tests/AxisInterop/SwA.php b/src/BeSimple/SoapClient/Tests/AxisInterop/SwA.php deleted file mode 100644 index eaf1bc6..0000000 --- a/src/BeSimple/SoapClient/Tests/AxisInterop/SwA.php +++ /dev/null @@ -1,84 +0,0 @@ - SOAP_1_1, - 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 - 'trace' => true, // enables use of the methods SoapClient->__getLastRequest, SoapClient->__getLastRequestHeaders, SoapClient->__getLastResponse and SoapClient->__getLastResponseHeaders - 'attachment_type' => BeSimpleSoapHelper::ATTACHMENTS_TYPE_SWA, - 'cache_wsdl' => WSDL_CACHE_NONE, -); - -/* - * Deploy "axis_services/besimple-swa.aar" to Apache Axis2 to get this - * example to work. - * - * Run ant to rebuild aar. - * - * Example based on: - * http://axis.apache.org/axis2/java/core/docs/mtom-guide.html#a3 - * http://wso2.org/library/1675 - * - * Doesn't work directly with ?wsdl served by Apache Axis! - * - */ - -$sc = new BeSimpleSoapClient('SwA.wsdl', $options); - -//var_dump($sc->__getFunctions()); -//var_dump($sc->__getTypes()); - -try { - $file = new stdClass(); - $file->name = 'upload.txt'; - $file->data = 'This is a test text!'; - $result = $sc->uploadFile($file); - - var_dump( - $result->return - ); - - $file = new stdClass(); - $file->name = 'upload.txt'; - $result = $sc->downloadFile($file); - - var_dump( - $result->data - ); - - $file = new stdClass(); - $file->name = 'image.jpg'; // source: http://www.freeimageslive.com/galleries/light/pics/swirl3768.jpg - $file->data = file_get_contents('image.jpg'); - $result = $sc->uploadFile($file); - - var_dump( - $result->return - ); - - $crc32 = crc32($file->data); - - $file = new stdClass(); - $file->name = 'image.jpg'; - $result = $sc->downloadFile($file); - - file_put_contents('image2.jpg', $result->data); - - - var_dump( - crc32($result->data) === $crc32 - ); - -} catch (Exception $e) { - var_dump($e); -} - -// var_dump( -// $sc->__getLastRequestHeaders(), -// $sc->__getLastRequest(), -// $sc->__getLastResponseHeaders(), -// $sc->__getLastResponse() -// ); \ No newline at end of file diff --git a/src/BeSimple/SoapClient/Tests/AxisInterop/SwA/build.xml b/src/BeSimple/SoapClient/Tests/AxisInterop/SwA/build.xml index f5261ed..836bda8 100644 --- a/src/BeSimple/SoapClient/Tests/AxisInterop/SwA/build.xml +++ b/src/BeSimple/SoapClient/Tests/AxisInterop/SwA/build.xml @@ -1,38 +1,38 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BeSimple/SoapClient/Tests/AxisInterop/SwA/resources/META-INF/services.xml b/src/BeSimple/SoapClient/Tests/AxisInterop/SwA/resources/META-INF/services.xml index 8b49f87..8bac2eb 100644 --- a/src/BeSimple/SoapClient/Tests/AxisInterop/SwA/resources/META-INF/services.xml +++ b/src/BeSimple/SoapClient/Tests/AxisInterop/SwA/resources/META-INF/services.xml @@ -1,15 +1,15 @@ - - + + BeSimple test service for SwA. - true - besimple.service.BeSimpleSwaService - - urn:uploadFile - - - - urn:downloadFile - - - - + true + besimple.service.BeSimpleSwaService + + urn:uploadFile + + + + urn:downloadFile + + + + diff --git a/src/BeSimple/SoapClient/Tests/AxisInterop/SwaAxisInteropTest.php b/src/BeSimple/SoapClient/Tests/AxisInterop/SwaAxisInteropTest.php new file mode 100644 index 0000000..4b9921f --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/AxisInterop/SwaAxisInteropTest.php @@ -0,0 +1,77 @@ + SOAP_1_1, + 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 + 'attachment_type' => BeSimpleSoapHelper::ATTACHMENTS_TYPE_SWA, + 'cache_wsdl' => WSDL_CACHE_NONE, + 'classmap' => array( + 'downloadFile' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\downloadFile', + 'downloadFileResponse' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\downloadFileResponse', + 'uploadFile' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\uploadFile', + 'uploadFileResponse' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\uploadFileResponse', + ), + ); + + public function testUploadDownloadText() + { + $sc = new BeSimpleSoapClient(__DIR__.'/Fixtures/SwA.wsdl', $this->options); + + $upload = new uploadFile(); + $upload->name = 'upload.txt'; + $upload->data = 'This is a test. :)'; + $result = $sc->uploadFile($upload); + + $this->assertEquals('File saved succesfully.', $result->return); + + $download = new downloadFile(); + $download->name = 'upload.txt'; + $result = $sc->downloadFile($download); + + $this->assertEquals($upload->data, $result->data); + } + + public function testUploadDownloadImage() + { + $sc = new BeSimpleSoapClient(__DIR__.'/Fixtures/SwA.wsdl', $this->options); + + $upload = new uploadFile(); + $upload->name = 'image.jpg'; + $upload->data = file_get_contents(__DIR__.'/Fixtures/image.jpg'); // source: http://www.freeimageslive.com/galleries/light/pics/swirl3768.jpg; + $result = $sc->uploadFile($upload); + + $this->assertEquals('File saved succesfully.', $result->return); + + $download = new downloadFile(); + $download->name = 'image.jpg'; + $result = $sc->downloadFile($download); + + $this->assertEquals($upload->data, $result->data); + } +} \ No newline at end of file diff --git a/src/BeSimple/SoapClient/Tests/AxisInterop/TestCase.php b/src/BeSimple/SoapClient/Tests/AxisInterop/TestCase.php new file mode 100644 index 0000000..eb57435 --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/AxisInterop/TestCase.php @@ -0,0 +1,23 @@ +markTestSkipped( + 'The Axis server is not started on port 8080.' + ); + } + + curl_close($ch); + } +} \ No newline at end of file diff --git a/src/BeSimple/SoapClient/Tests/AxisInterop/WsAddressing.php b/src/BeSimple/SoapClient/Tests/AxisInterop/WsAddressing.php deleted file mode 100644 index dacabcb..0000000 --- a/src/BeSimple/SoapClient/Tests/AxisInterop/WsAddressing.php +++ /dev/null @@ -1,73 +0,0 @@ - SOAP_1_2, - 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 - 'trace' => true, // enables use of the methods SoapClient->__getLastRequest, SoapClient->__getLastRequestHeaders, SoapClient->__getLastResponse and SoapClient->__getLastResponseHeaders -); - -/* - * Deploy "axis_services/version2.aar" to Apache Axis2 to get this example to - * work. - * - * To rebuild the "axis_services/version2.aar" the following steps need to be - * done to build a working Apache Axis2 version service with SOAP session - * enabled. - * - * 1) Go to $AXIS_HOME/samples/version and edit the following files: - * - * resources/META-INF/services.xml: - * - * ... - * - * - * build.xml: - * replace version.aar with version2.aar - * - * 2) Run ant build.xml in "$AXIS_HOME/samples/version" - * - */ - -$sc = new BeSimpleSoapClient('http://localhost:8080/axis2/services/Version2?wsdl', $options); -$soapKernel = $sc->getSoapKernel(); -$wsaFilter = new BeSimpleWsAddressingFilter(); -$soapKernel->registerFilter($wsaFilter); - -//var_dump($sc->__getFunctions()); -//var_dump($sc->__getTypes()); - -try { - $wsaFilter->setReplyTo(BeSimpleWsAddressingFilter::ENDPOINT_REFERENCE_ANONYMOUS); - $wsaFilter->setMessageId(); - - var_dump($sc->getVersion()); - - $soapSessionId1 = $wsaFilter->getReferenceParameter('http://ws.apache.org/namespaces/axis2', 'ServiceGroupId'); - echo 'ID1: ' .$soapSessionId1 . PHP_EOL; - - $wsaFilter->addReferenceParameter('http://ws.apache.org/namespaces/axis2', 'axis2', 'ServiceGroupId', $soapSessionId1); - - var_dump($sc->getVersion()); - - $soapSessionId2 = $wsaFilter->getReferenceParameter('http://ws.apache.org/namespaces/axis2', 'ServiceGroupId'); - echo 'ID2: ' . $soapSessionId2 . PHP_EOL; - - if ($soapSessionId1 == $soapSessionId2) { - echo PHP_EOL; - echo 'SOAP session worked :)'; - } -} catch (Exception $e) { - var_dump($e); -} - -// var_dump( -// $sc->__getLastRequestHeaders(), -// $sc->__getLastRequest(), -// $sc->__getLastResponseHeaders(), -// $sc->__getLastResponse() -// ); \ No newline at end of file diff --git a/src/BeSimple/SoapClient/Tests/AxisInterop/WsAddressingAxisInteropTest.php b/src/BeSimple/SoapClient/Tests/AxisInterop/WsAddressingAxisInteropTest.php new file mode 100644 index 0000000..cf7308a --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/AxisInterop/WsAddressingAxisInteropTest.php @@ -0,0 +1,59 @@ + + * ... + * + * + * build.xml: + * replace version.aar with version2.aar + * + * 2) Run ant build.xml in "$AXIS_HOME/samples/version" + * + */ + +use BeSimple\SoapClient\SoapClient as BeSimpleSoapClient; +use BeSimple\SoapClient\WsAddressingFilter as BeSimpleWsAddressingFilter; + +use BeSimple\SoapClient\Tests\AxisInterop\TestCase; + +class WsAddressingAxisInteropTest extends TestCase +{ + private $options = array( + 'soap_version' => SOAP_1_2, + 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 + ); + + public function testSession() + { + $sc = new BeSimpleSoapClient('http://localhost:8080/axis2/services/Version2?wsdl', $this->options); + $soapKernel = $sc->getSoapKernel(); + $wsaFilter = new BeSimpleWsAddressingFilter(); + $soapKernel->registerFilter($wsaFilter); + + $wsaFilter->setReplyTo(BeSimpleWsAddressingFilter::ENDPOINT_REFERENCE_ANONYMOUS); + $wsaFilter->setMessageId(); + + $version = $sc->getVersion(); + + $soapSessionId1 = $wsaFilter->getReferenceParameter('http://ws.apache.org/namespaces/axis2', 'ServiceGroupId'); + + $wsaFilter->addReferenceParameter('http://ws.apache.org/namespaces/axis2', 'axis2', 'ServiceGroupId', $soapSessionId1); + + $version = $sc->getVersion(); + + $soapSessionId2 = $wsaFilter->getReferenceParameter('http://ws.apache.org/namespaces/axis2', 'ServiceGroupId'); + + $this->assertEquals($soapSessionId1, $soapSessionId2); + } +} \ No newline at end of file diff --git a/src/BeSimple/SoapClient/Tests/AxisInterop/WsSecuritySigEnc.php b/src/BeSimple/SoapClient/Tests/AxisInterop/WsSecuritySigEnc.php deleted file mode 100644 index d6a31fb..0000000 --- a/src/BeSimple/SoapClient/Tests/AxisInterop/WsSecuritySigEnc.php +++ /dev/null @@ -1,116 +0,0 @@ -'; - -$options = array( - 'soap_version' => SOAP_1_2, - 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 - 'trace' => true, // enables use of the methods SoapClient->__getLastRequest, SoapClient->__getLastRequestHeaders, SoapClient->__getLastResponse and SoapClient->__getLastResponseHeaders -); - -/* - * Deploy "axis_services/library-signencr.aar" to Apache Axis2 to get this - * example to work. - * - * Links: - * http://www.dcc.uchile.cl/~pcamacho/tutorial/web/xmlsec/xmlsec.html - * http://www.aleksey.com/xmlsec/xmldsig-verifier.html - * - * Using code from axis example: - * http://www.ibm.com/developerworks/java/library/j-jws5/index.html - * - * Download key tool to export private key - * http://couchpotato.net/pkeytool/ - * - * keytool -export -alias serverkey -keystore server.keystore -storepass nosecret -file servercert.cer - * openssl x509 -out servercert.pem -outform pem -in servercert.pem -inform der - * - * keytool -export -alias clientkey -keystore client.keystore -storepass nosecret -file clientcert.cer - * openssl x509 -out clientcert.pem -outform pem -in clientcert.pem -inform der - * java -jar pkeytool.jar -exportkey -keystore client.keystore -storepass nosecret -keypass clientpass -rfc -alias clientkey -file clientkey.pem - * - * C:\Program Files\Java\jre6\bin\keytool -export -alias serverkey -keystore server.keystore -storepass nosecret -file servercert.cer - * C:\xampp\apache\bin\openssl x509 -out servercert.pem -outform pem -in servercert.cer -inform der - * - * C:\Program Files\Java\jre6\bin\keytool -export -alias clientkey -keystore client.keystore -storepass nosecret -file clientcert.cer - * C:\xampp\apache\bin\openssl x509 -out clientcert.pem -outform pem -in clientcert.cer -inform der - * java -jar C:\axis2\pkeytool\pkeytool.jar -exportkey -keystore client.keystore -storepass nosecret -keypass clientpass -rfc -alias clientkey -file clientkey.pem - * - * build.properties: - * server-policy=hash-policy-server.xml - * - * allows both text and digest! - */ - -class getBook {} -class getBookResponse {} -class getBooksByType {} -class getBooksByTypeResponse {} -class addBook {} -class addBookResponse {} -class BookInformation {} - -$options['classmap'] = array( - 'getBook' => 'getBook', - 'getBookResponse' => 'getBookResponse', - 'getBooksByType' => 'getBooksByType', - 'getBooksByTypeResponse' => 'getBooksByTypeResponse', - 'addBook' => 'addBook', - 'addBookResponse' => 'addBookResponse', - 'BookInformation' => 'BookInformation', -); - -$sc = new BeSimpleSoapClient('WsSecuritySigEnc.wsdl', $options); - -$wssFilter = new BeSimpleWsSecurityFilter(); -// user key for signature and encryption -$securityKeyUser = new BeSimpleWsSecurityKey(); -$securityKeyUser->addPrivateKey(XmlSecurityKey::RSA_SHA1, 'clientkey.pem', true); -$securityKeyUser->addPublicKey(XmlSecurityKey::RSA_SHA1, 'clientcert.pem', true); -$wssFilter->setUserSecurityKeyObject($securityKeyUser); -// service key for encryption -$securityKeyService = new BeSimpleWsSecurityKey(); -$securityKeyService->addPrivateKey(XmlSecurityKey::TRIPLEDES_CBC); -$securityKeyService->addPublicKey(XmlSecurityKey::RSA_1_5, 'servercert.pem', true); -$wssFilter->setServiceSecurityKeyObject($securityKeyService); -// TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER | TOKEN_REFERENCE_SECURITY_TOKEN | TOKEN_REFERENCE_THUMBPRINT_SHA1 -$wssFilter->setSecurityOptionsSignature(BeSimpleWsSecurityFilter::TOKEN_REFERENCE_SECURITY_TOKEN); -$wssFilter->setSecurityOptionsEncryption(BeSimpleWsSecurityFilter::TOKEN_REFERENCE_THUMBPRINT_SHA1); - -$soapKernel = $sc->getSoapKernel(); -$soapKernel->registerFilter($wssFilter); - -//var_dump($sc->__getFunctions()); -//var_dump($sc->__getTypes()); - -try { - $gb = new getBook(); - $gb->isbn = '0061020052'; - var_dump($sc->getBook($gb)); - - $ab = new addBook(); - $ab->isbn = '0445203498'; - $ab->title = 'The Dragon Never Sleeps'; - $ab->author = 'Cook, Glen'; - $ab->type = 'scifi'; - var_dump($sc->addBook($ab)); - - // getBooksByType("scifi"); -} catch (Exception $e) { - var_dump($e); -} - -//var_dump( -// $sc->__getLastRequestHeaders(), -// $sc->__getLastRequest(), -// $sc->__getLastResponseHeaders(), -// $sc->__getLastResponse() -//); diff --git a/src/BeSimple/SoapClient/Tests/AxisInterop/WsSecuritySigEncAxisInteropTest.php b/src/BeSimple/SoapClient/Tests/AxisInterop/WsSecuritySigEncAxisInteropTest.php new file mode 100644 index 0000000..6e4f0d9 --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/AxisInterop/WsSecuritySigEncAxisInteropTest.php @@ -0,0 +1,106 @@ + SOAP_1_2, + 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 + 'classmap' => array( + 'getBook' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\getBook', + 'getBookResponse' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\getBookResponse', + 'getBooksByType' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\getBooksByType', + 'getBooksByTypeResponse' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\getBooksByTypeResponse', + 'addBook' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\addBook', + 'addBookResponse' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\addBookResponse', + 'BookInformation' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\BookInformation', + ), + ); + + public function testSigEnc() + { + $sc = new BeSimpleSoapClient(__DIR__.'/Fixtures/WsSecuritySigEnc.wsdl', $this->options); + + $wssFilter = new BeSimpleWsSecurityFilter(); + // user key for signature and encryption + $securityKeyUser = new BeSimpleWsSecurityKey(); + $securityKeyUser->addPrivateKey(XmlSecurityKey::RSA_SHA1, __DIR__.'/Fixtures/clientkey.pem', true); + $securityKeyUser->addPublicKey(XmlSecurityKey::RSA_SHA1, __DIR__.'/Fixtures/clientcert.pem', true); + $wssFilter->setUserSecurityKeyObject($securityKeyUser); + // service key for encryption + $securityKeyService = new BeSimpleWsSecurityKey(); + $securityKeyService->addPrivateKey(XmlSecurityKey::TRIPLEDES_CBC); + $securityKeyService->addPublicKey(XmlSecurityKey::RSA_1_5, __DIR__.'/Fixtures/servercert.pem', true); + $wssFilter->setServiceSecurityKeyObject($securityKeyService); + // TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER | TOKEN_REFERENCE_SECURITY_TOKEN | TOKEN_REFERENCE_THUMBPRINT_SHA1 + $wssFilter->setSecurityOptionsSignature(BeSimpleWsSecurityFilter::TOKEN_REFERENCE_SECURITY_TOKEN); + $wssFilter->setSecurityOptionsEncryption(BeSimpleWsSecurityFilter::TOKEN_REFERENCE_THUMBPRINT_SHA1); + + $soapKernel = $sc->getSoapKernel(); + $soapKernel->registerFilter($wssFilter); + + $gb = new getBook(); + $gb->isbn = '0061020052'; + $result = $sc->getBook($gb); + $this->assertInstanceOf('BeSimple\SoapClient\Tests\AxisInterop\Fixtures\BookInformation', $result->getBookReturn); + + $ab = new addBook(); + $ab->isbn = '0445203498'; + $ab->title = 'The Dragon Never Sleeps'; + $ab->author = 'Cook, Glen'; + $ab->type = 'scifi'; + + $this->assertTrue((bool) $sc->addBook($ab)); + + // getBooksByType("scifi"); + } +} \ No newline at end of file diff --git a/src/BeSimple/SoapClient/Tests/AxisInterop/WsSecurityUserPass.php b/src/BeSimple/SoapClient/Tests/AxisInterop/WsSecurityUserPass.php deleted file mode 100644 index 231f1e5..0000000 --- a/src/BeSimple/SoapClient/Tests/AxisInterop/WsSecurityUserPass.php +++ /dev/null @@ -1,79 +0,0 @@ - SOAP_1_2, - 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 - 'trace' => true, // enables use of the methods SoapClient->__getLastRequest, SoapClient->__getLastRequestHeaders, SoapClient->__getLastResponse and SoapClient->__getLastResponseHeaders -); - -/* - * Deploy "axis_services/library-username-digest.aar" to Apache Axis2 to get - * this example to work. - * - * Using code from axis example: - * http://www.ibm.com/developerworks/java/library/j-jws4/index.html - * - * build.properties: - * server-policy=hash-policy-server.xml - * - * allows both text and digest! - */ - -class getBook {} -class getBookResponse {} -class getBooksByType {} -class getBooksByTypeResponse {} -class addBook {} -class addBookResponse {} -class BookInformation {} - -$options['classmap'] = array( - 'getBook' => 'getBook', - 'getBookResponse' => 'getBookResponse', - 'getBooksByType' => 'getBooksByType', - 'getBooksByTypeResponse' => 'getBooksByTypeResponse', - 'addBook' => 'addBook', - 'addBookResponse' => 'addBookResponse', - 'BookInformation' => 'BookInformation', -); - -$sc = new BeSimpleSoapClient('WsSecurityUserPass.wsdl', $options); - -$wssFilter = new BeSimpleWsSecurityFilter(true, 600); -$wssFilter->addUserData('libuser', 'books', BeSimpleWsSecurityFilter::PASSWORD_TYPE_TEXT); -//$wssFilter->addUserData( 'libuser', 'books', BeSimpleWsSecurityFilter::PASSWORD_TYPE_DIGEST ); - -$soapKernel = $sc->getSoapKernel(); -$soapKernel->registerFilter($wssFilter); - -//var_dump($sc->__getFunctions()); -//var_dump($sc->__getTypes()); - -try { - $gb = new getBook(); - $gb->isbn = '0061020052'; - var_dump($sc->getBook($gb)); - - $ab = new addBook(); - $ab->isbn = '0445203498'; - $ab->title = 'The Dragon Never Sleeps'; - $ab->author = 'Cook, Glen'; - $ab->type = 'scifi'; - var_dump($sc->addBook($ab)); - - // getBooksByType("scifi"); -} catch (Exception $e) { - var_dump($e); -} - -//var_dump( -// $sc->__getLastRequestHeaders(), -// $sc->__getLastRequest(), -// $sc->__getLastResponseHeaders(), -// $sc->__getLastResponse() -//); diff --git a/src/BeSimple/SoapClient/Tests/AxisInterop/WsSecurityUserPassAxisInteropTest.php b/src/BeSimple/SoapClient/Tests/AxisInterop/WsSecurityUserPassAxisInteropTest.php new file mode 100644 index 0000000..6ab2824 --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/AxisInterop/WsSecurityUserPassAxisInteropTest.php @@ -0,0 +1,96 @@ + SOAP_1_2, + 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 + 'classmap' => array( + 'getBook' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\getBook', + 'getBookResponse' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\getBookResponse', + 'getBooksByType' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\getBooksByType', + 'getBooksByTypeResponse' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\getBooksByTypeResponse', + 'addBook' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\addBook', + 'addBookResponse' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\addBookResponse', + 'BookInformation' => 'BeSimple\SoapClient\Tests\AxisInterop\Fixtures\BookInformation', + ), + ); + + public function testUserPassText() + { + $sc = new BeSimpleSoapClient(__DIR__.'/Fixtures/WsSecurityUserPass.wsdl', $this->options); + + $wssFilter = new BeSimpleWsSecurityFilter(true, 600); + $wssFilter->addUserData('libuser', 'books', BeSimpleWsSecurityFilter::PASSWORD_TYPE_TEXT); + + $soapKernel = $sc->getSoapKernel(); + $soapKernel->registerFilter($wssFilter); + + $gb = new getBook(); + $gb->isbn = '0061020052'; + $result = $sc->getBook($gb); + $this->assertInstanceOf('BeSimple\SoapClient\Tests\AxisInterop\Fixtures\BookInformation', $result->getBookReturn); + + $ab = new addBook(); + $ab->isbn = '0445203498'; + $ab->title = 'The Dragon Never Sleeps'; + $ab->author = 'Cook, Glen'; + $ab->type = 'scifi'; + + $this->assertTrue((bool) $sc->addBook($ab)); + + // getBooksByType("scifi"); + } + + public function testUserPassDigest() + { + $sc = new BeSimpleSoapClient(__DIR__.'/Fixtures/WsSecurityUserPass.wsdl', $this->options); + + $wssFilter = new BeSimpleWsSecurityFilter(true, 600); + $wssFilter->addUserData( 'libuser', 'books', BeSimpleWsSecurityFilter::PASSWORD_TYPE_DIGEST ); + + $soapKernel = $sc->getSoapKernel(); + $soapKernel->registerFilter($wssFilter); + + $gb = new getBook(); + $gb->isbn = '0061020052'; + $result = $sc->getBook($gb); + $this->assertInstanceOf('BeSimple\SoapClient\Tests\AxisInterop\Fixtures\BookInformation', $result->getBookReturn); + + $ab = new addBook(); + $ab->isbn = '0445203498'; + $ab->title = 'The Dragon Never Sleeps'; + $ab->author = 'Cook, Glen'; + $ab->type = 'scifi'; + + $this->assertTrue((bool) $sc->addBook($ab)); + + // getBooksByType("scifi"); + } +} \ No newline at end of file diff --git a/src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/AttachmentRequest.php b/src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/AttachmentRequest.php new file mode 100644 index 0000000..8be17dc --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/AttachmentRequest.php @@ -0,0 +1,7 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/SwA.wsdl b/src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/SwA.wsdl new file mode 100644 index 0000000..46b14b7 --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/SwA.wsdl @@ -0,0 +1,162 @@ + + + BeSimpleSwaService + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/WsSecuritySigEnc.wsdl b/src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/WsSecuritySigEnc.wsdl new file mode 100644 index 0000000..d329f1f --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/WsSecuritySigEnc.wsdl @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/WsSecurityUserPass.wsdl b/src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/WsSecurityUserPass.wsdl new file mode 100644 index 0000000..7d4b33f --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/WsSecurityUserPass.wsdl @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/addBook.php b/src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/addBook.php new file mode 100644 index 0000000..8d22133 --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/ServerInterop/Fixtures/addBook.php @@ -0,0 +1,11 @@ +-wkEbFlgZrq{)d0v*W3NnUA?Mm z^~3J2-Mjy;|J?_m%S+2i10WzE0J8r&;O`bd0sspQ0}BHU3kw4a2L}s}h>nPefPjdN ziiV7ihmDVqhmDI%NJ9OEkcg5P7nh8QjFN_yo`Ig=3o{!t9UC7p!AmHHO5aAKA z5D~HH2yqGN{?F}iKL7(BQU{6*3W5Rvi2(tH0r7VjKnwst!2Fl(|8LNcQ2+9T0KosV zs-puSp#F`aVc}q);o$z6AOMh1&=@e7ZSv@oUWk< zR1$8fcw9iUu)#%c$?&wsyL)PL_Y#^?jr7BRJ_#WJQ2zq{uZR$kP=J3s^v}kR0f2;n zgocKOfrf;D{4vNG6-%f2#?sUA9tV=>>3C#Ex|-JPW^ zp~aT5vbe=+p)a)@#EgPG94yJCdaPwOH-u`H$HWQniSDcp|6T@A^gTt*T#7_*y! z$jU|C2k_-rK`h|F7u4BthI>?$)Y%i0Nqfqojvv`Q4@ZPs##HK#ZO-8@2bOlEQb!~D}zF8Mt@&eoD47KusH-E-H$9MKU<3$y) zzS(z^24KcBH-3JdK&&O^!cWi$0p21vGou8X0i>nG+xE`qw-uiie|h!AbbTn0mi&F# z$N6VaC`%I5Cr9Go`!8wJR|#>L*mTvpEFBZ8X4a=KmQ2FN0tPS zXfl2wlE{_iZ4jG7$HI*v+zPa-!SQf&U@C#q7ThV6Zi?(%(RNXvj8`sS2<{&{X}~1} z^t^Ku7ix(j%JDyu-xCx?7V+qA93zN#cE87ulT^hDC6=zR;TY=}O4RnDE$LA%|)T#&S zcf5vhTkX3iE|b-Y$LTFpTV|JnyPX>pCd;WMg0AVb#~E5;hZL>??MK^?3_?;HA=W5& znqbO=6n6eGA6(4@PcF-MylUTKZa%8*kou||3zn{gX4Q60>4?PbGSc4((gk=H3|RtB z9&RP&Bd_}zKvnbbHZozaqFuQICuZ%@cI)eu7UmFeU-IdMTb^3Q1H+*PG z7kQ}?74YpN<}veA5l4lMGZz-967cH5i76#t75cajAxF{iBA<#a=BVSz{~wqc85-$~ zF{MMw8tRP69;Xw{kkRH&)tcpC<_>;OL`pajnsmaRWR9Fhm(HYIz^6}m1L-Rr769!n zV8;&#&*FDv-)Xwl^WuMFG%vb#02ci`znYqv^E$i@HFF)M5Bz|_p|%pMc#gUm9h((fSFClDAxoLA zB6Ir>Uy-x-J5GJ&_IL;k8gWBnsn9>g(DUbC1Vki^$w|qAvg9X6INtw}oH|y)-%Q)B~2B`oe*rcC#6qY9#qADd?wIKXtX8_ZN`- z68o(SC3Tx3&)60k)_23^{1Eb2nK$WS8TrQb_cBKxM%pSrscAJyk2{wJO&gaiaJ{`^ zLdfXpIKjQHfdkcXqFCKP9jmZH;NGeOMkx;nP-0H=9TAg{sC{J`f>fz*1l@C1KzI8> zw&C3~#iiNuexv45Nz5T{99l+%qQ4<>CA{?y0F3o?95i z{tU)4Uk$z^;n8xnB)9mQwR-Y$M{OTu?YUG9IZgc!*Hd=yQzt7b>S>{6OL~136i%Be zGbZYdxMsZblJM9@)yr4e6LYSmUfZ~K+e|*|Dua2~Z)oC14Bg2>Q5(a0Tgj=jy3k23 zylq^9yJNaJS3cOI97-YtwBAq-ID~4;8}`5&+X8BZZSVAL*AC=Eg%4oLE!fB{&)!g< z+L}tZ#tny+-cdiSI=p02Ye!KbqvRdUx7jF412-=wrz5ygqLe9Q*j2f+f+m<0Eg_ZB z&iIKo63dTlL!&gyH71&`v;6&Dg3GR&!llrP)Pl#1$H@NXc;iUUaQiFd8iaf2kXSc_ zh(hN*y=rU6snDK!Y{YgfF_+#kttpQH!$7l;J2hPyb4JA`Bg$0v8jMcF=}>6H{KV#x zzJ8)YV|wDIC~hdJ9IetsMSENvB?*Mf>b{#&tTvwYfumjP%tbsDTh9&pD)X~=)1t9* zP0WpXt&K77tiyg$c`*2-%oh76oC|3g?EF#=y<#S>V|Dg5$~dD^VYVDp?zJN?11vQ} zWQpvQJ;Z`{A+7j~5@{eyo(kI+>+K#O3%C=Qw?Gx=Y$U;lzc%2`wRzd$h}qAt+5r2o z=ge43-pNNDcMHLDNPL-M5$`i+Exg=1j%AE=k(E*2>GTh?KTWu#+vJ^vFjd(P5#?~@ zA$(?Z2AxXw4I!sik?NUbd6i?!18LX6v;VUo-sJg8q7I`UTY~(PaOQZaXT^ftgG4X5DFxe;CQ)>N>BGG3h>(L)P ziZnzx`L8#>7(`bk{~Q;`BnzASTm+HkS=m>bVlcD=ED)Lp7T4V+{ z>1u_1m)weJm~>~6I$j?5I@dzVg|DfTi_DeBr?dkjM*{!rhn!NAxebggWlf-MFgrc7 z{V^KOT;P+iYhT7fkLU`lVf*Nu#!r_1LT~ zW1rz+_Q|iDP+e*L7ocAyEGXX8r$lvQVz zA@(D~N4;AVuiz>FrJbXC=j47-rZrCN1$N0JN_ly9+Rq8(WsE4GjFQsghF-^`dTA4A zFCD{KxzgE*>DcU*@dG*z=Q##aYflfp+jgHbp&zliAY>gn_PNu1F?uDDQ!{yYL*Q>Y z%!(ZtHqWVcYw47zcK@to+vl@wx0jkfBoah$kYPSIN<9p>hTvOT+KzOlXR8=LzEV&1 zvW`qLd{G&*y8w<#RLBAg^zG=K_bH(?F}`lRx1eBq#QI-Mt5fs+!_jmFq$hU4$V!- zTvhp&vfmrws`=}E8{fY27Na8-4B#LUD|fPtuHFeeNwTl<*lAOlZRe|6k|>jKLiEM9 z0!_d@AmOVTY)sjWyduOk?6k}{s2_k%6Z6FJrOpNzPh66~={P!uO*k%$x(>z>62ar$ zMW0fu!BY11QKaZY5dY|jH>0H)PBUu4nk~XLrP7rAFdIE|{p1Yqjpm009N|tlpQ~C1 z(Ir-FJPvZ{t1v_dTrcy4CpOaI4F}LBdRYcHJfQ+kj~__gobj~R^nbiD=(#;;=*MB!JkL*gxHVow5~0WM2^LJ!;( zm}Y!;9B+ScudvwsmPU3gd!5p7fpt}F1s{dICSFDwWf(c>~f*=SNbmiv#Ci zZc~%6iz5QW2jZD^@AFT9#)yCc;qc62W5$%tf-xy7af{KmcCNj8{=O>vRGcj)Q|4t@ zwQ>#q!HPT54;G^lGh*izsyOVEMFVi6&W5*bI-KTE#Ywz#coq^SM7w;4(VFilp^^MQ zq{lzJ&I&ec9ObvzVr6AA`O2hA23-V>XbP;lbKlK&Xb0)3ef^5ulI4ax$`#W@m{{_76Lv|ZBanYP|7_NrruM-uOL!jN+&(Ybkp$62JO!{rPst>mxBc3tNs z*H>$?ZSX;vFNgB_PBS<>*n^z9^gsHb+3^lLSBY+=rAF&!Q8; zZ?)p{NIC2&IUSkO*IGBzGbO!}nYz-U+8JvywQU8nsQ1R*XX7NmfV_xj#Vyp*o|+`;eHF|o zrhwOUFws*7onh{km#SC!H>LA1tRLOw@GVE~K+7&%1DRbTRNC01QlXo!4a!On0eGUx zTWi#l1DrS8o(-fMebo!UW~k6_b}k(*;J6#*=qN_YO8e2_U>l2@O6nOx*wt+58ZSsDsBUOg_O2n2(!I^8=8-L zfn|z5l^`@46@LQmsXL;2652I9=L)bNHLTeaG;SL;beE+>cU|tDB!rLfT62m3)&b^I zSBKh=3=5BrZFg_3q+Ym~y0DeC1decG4n$y}W>(~Ew84(EUhURoq^CFW|0z@JGa|(4 zBuVdY#Es{`O>}_9$-AbRl~9OP+;m(>*}aCwO#FgVmuKFPeWvAOcw^pvIyJxkj>Cb{gteD>}m%q;Jz|uAJizt-V3Qm%6F_z-`}S{%<;Np zL6|2W1gse{pLRn&%u>$c5~V^V)H$3b_n+GIaO^|unKnriwItRI>oT$(rpoq)01zGP zRy+qMm}$MBixyWxxdXgdSyHMx51=Lhq_CHXgg;#5XIw1eDJCS{3>sg`%}61f!;A20 z!I9z*&ujlEkRL3pOlCC$K0aoM2-*fntvX}kmvX&vAsVT&k?yfCtA4$2{q&nlB1wd*osNS9`FXW9vlV)6_G zj1mzWQ;M<(GM;%5k*w1Ntp@%j>iGJW!|$}r-h+}92co!U6XiCvCJ*auE`CGWdcknI_2U`4{OCckwu|_d(g$;W4Ng{tZjPj94MWqNJTX5Gwbb|wTr^`BX`xjFobZ5lM$klcP!xSp(x@6-S z&zH21s-MQieXhH*cRJo^ruoK(7Q&OABVp19HIDvxhM`{j3(d-Ce-bIn_6V69yrXk%y`5 zzH!!;Vq(NPcCV=CrU`J@c)V^tW6WX$^=OUL$Rs>`@l!w5vLqxik8-NOK{>|zVn0rM z0jEM$dUR)zjZ|!JKLeSqVW*?epQ$!Tdw41!O0>COF(@q`Bg$P}!QZn;zWhZ~qKh{d=qX#6_}<;Qj7aaBS^ zh>!(?ot7n=@s(Zgs@24Fm9H-kY$R}iF;~D^3oHj-P6^qRUOK$|?vcKXuSCKq5$?jX z?V7aWqA|8-77L0SvaZRo#B0(?(1k@*!%}|5aPn<4(MQ9A53uhkZQJtJl8^s}Qtt2_ zNI>bPZ1!XGF+|rY%Jm3tPv784Jqh&`coVtm}>8T4bTo zYx5ea;)I%%QBlU%6744Gxds+SBFqXVvwxMK;g6v~~|(`nF{ya`s?f+vE#v z6W%G5R5F9+)@*@&ZUQ!r@uE?Cj_PRR~%R3jzAwc&Pfe zDaH1%r}ZIj)&30_F4d1_M2qYC2mnBrvn<%ij~_d6b}XP9mwiJFN(pGC1>(rldzGb( z%n}p%0^i zB@fS8;-f;HkPl}%V`7FzNHzG6`tFxvQ0l4khna*9)pWGY`xyaTMs_S&sRP`hG_MBM z|F~C`PC=-x~PVob5!QVqsL~kBdCg)Df7gp z2LulX7Fb~4feM#}}8oM#i4 zZ7Mj$y52bS?*0p)m)>)(6}-`K&cRH&bQx(nZHjuyI=IhkNYnmaJclzF0SCs-19Hs0snhJ`8Fel z#HCT?)Zz5nH@>s{jKvd}MlNQi0D{A;HB?fPE)kpRxzc!fy2k)r%1-txmlwz+L4|f4 zf8meI-YLJyG+uJ(o>L+#qcgU2wYZ9>(b}K<0V#yZEsFUjW1&^%r2{D1xifIeVc>LjMPb zPNhhxw8lx9xpp46@wQo?INo<|H6DKdXJD%R*4Oq_1=sQ0R0U6mdO(hOAL>!BY^|bT zLWURF=6kZz`p`!)orv9K*0)LQ<2){yFmrFw!P``tICBz>tN80O=lxZ|<2FvzQy=v% z3uh6}F70MiG3aym+2!SyUP4AUPsk3CJmF!`phqu!r$cCs3*cbI>+~&}^yvqIlHuwo={}y&r73k?t=#hPauBkxI1%^+Y{{A{ z$Ir!Gx~pCF!v8u$X$pNi`*0{641*Kn&`a@7VUk#wGj})(T(o0)9KIOn$YG8yl*WIq9&f+Kkt7Rk*YQ?+>N>CA0i++wq!y zP~pwM!FP*ppU*Q$ui8(^bZg>{5_9MwMOO2W&Ucp|POCAC-x>W`q5viyow4kmjJ3JR zA4c&)PbabB+8ujRtR#O^Lwg_9hC~p-Q=HOgGih!mx^^dC+Oz_o)D55rwVBm*Xz_X1 zho~wPec-pKl#QY&78Kgy!s+M_kVNx0&5rTcd91_yV!$t4Y(Etccxh-A>Y@F7H*H7llO-k%_e7#Z|b-#DM?Cv#L>g4A_Y&R@YIeYcfZfs?*$ zuypfIRG6)tz;h_PSyJYa(T-olP3CnXS_Ybcn382>o#zepI0*zx&}^G11>@BK(D5nty=AgI9-T|sua!U*OH6RbOdBxhs{&Eo0B*a3g9 zGuU)8f04_|yTG@K=VW;GX5CsrG%LGPEo-w|$c`KAP1R9Gf{4pTcyV|p23OzXvGbpb zC0GHf1gIzP6EBnkjuh*3W9dqpiGEOymLp1|1E)*tEQ~OEL5l^~M+?xZn$a~lr$bO< zZ@P`AAJLEhE*Gd6RW-hLh&--nE;w5SV)cV`|5Z2D)f^T$JOcy-0vn>(l6f0MIOI!` z67;fyS(0p5&2->u?^^L5`$A@^h^b8t)iK?j2D=?&G~v;UDja@dn3M)Cu%~!IMv@BJ zUjRW7r|sDmxw|(tqtvb4Ux1{_b+fMhHg2@{b~O?;nX^1rm#dXH(+x0gyC-lH{W6{* zriUZ_(euHwb$ambvXF-JQG=3kw%lFuchTKl0PGB`q4f4z)=s*&H)U|xduhqL+^CtO z7Vu!^FJO?Z_P5}gKIPlD&8DHRH^D3Xj&V9h;=)nNNai|3v8!C$tIMQ8wny)v-i~2-(XHybskWdQt*#ek*a`D({N6CD7`vgD zIIL2tfoM%>U*+4}WI?-TTCF3Tq2lan2?Fx%OIkoOk`<%tT>!Lge!-^Hrf};f$Jh76 zhM?iT5rLVWO)VjP+$t{_+>Z>BIv+xT5&BxPjaiBGicbwvoG<0Rl=4aEf;N=Ku+_BQ zgNuO(qkrfeNOP*mMbNn|r_r{FFQG3Deuo4>bg)t@eN=vDj(W!{E^j&qewnyf!g}js zy~u)FtPoiH11cHk_ta`@s9nb6_4ZiIrlKOQZ{y-8{3h3-$UC1PPFMa zq0(Kv@s!srNi?CX@rr}*WuKq5iW5BV*@Wd*5PGsZs35Nf&&+V}% zD=0(BWShHnZ_iBnn?c3)YVgJ>S)Mbc!s6> zeyIh`d|M+0@mi#z;x)*XJP;*2a)wV6yp{myOC%-#%0Lp46r*VHAB&htB+DPwem(^9 zPD0nBeaT3-92wtmoJ76PSKQkC+BZLKI=t(n?Wi}fP9^esR3AUaPBS*y3h(`*Xhkj% z8oSSwx`DOH1#2>)3BR!vE6p4US0SzyaFQINY|>E%iY3AIhj&5??7Y0Z9z>~~YP9rJ zvK-3Gai=15tBT`W8LPH&cHzbVlwavt^1mDD_tN zf-p-8*d1yWVtx*IB)N1zj+Jb4;h;E9;I#@=4q2J!h%IjVV=Xrb%BGM(-yNgnHx0bC66~Z0sDBZY!y7 z#iU1@d5*XC+t(ehj`6+)OAo-=zBIFa8xd@#G%MG`Xhd)}D{`{jigQuaQ|zsa5(6E8 zm@1|=nqOtRLS4fU%x{oc$$xsrg~ew6X;2Q9Q>UbK{|J^ z(9F@3#`ei$7ldOt%GOSn{f^yJX29A_o#5TdEM!2-NYRv@hq=hy7mH|{g%}@txLIYJ ztv7!-bqb+r>|S4=E!?^1ViZABR-f2>TaB7(H+kfmQHAW0=T`d3k@}VbZUg^2^Xv`I0(~RC1cRXFxud-4&kd zuF(_znmJ*RzWJe5A{onLj(+Paajmr6zq`Z!Qov-NK1O8GOD1@AJGQGaxAbOIMy$le zKq!W@SJ(%8zA~?YlG`zsG{1&{t8g{4O|)8vaHj!p}xUo_i%w0 zTXoF?y=m1kO=vVENq1-#tp%2Ee&EKdn;yd4cvGx12a1_rs)+pDGd{U!o%Z;KNGjD{+VOw7&5U; z|0jp}mW4a`UYpj~eMrJJm5t%s%(cwZW}lf5)%2_DzlztsyEOhznahy6h*nR0^h+?b zJu;f}mlA{|+y-g&iS*scr|STD$y5u32C}p0#NdJxZX8uIfYL(Bax<6FfjwE-e`LLh1zv1Dhg9|Q2iE#1rwgE_;+$O*V{Smn zU&0@`Uo!DAP-CVfjBvoBB8*CI-5A(KVSr+t+ilEl6>Bvm&&*_?E1F$oQK8S$;ZGsW z7bI&1EfXP_gGnLSh*_tpqvEU+#=hc0dE?3>+YqHOc^{+2y73*D10~A%Q@UFeFd8>6 z^3Ym4sS!{XI!Ra#r~Lo(C^#BjBEFmry~-CDSVrP1HC&N4EJE?`oLf9k8Z@9=hD)Q-(+HE;Mt z4j$9E@$!1dv4k$7#OR%Vh3GE;G`cMEGS}`Ak?Wn_cdGdrcy!!JA3@77w=4~KH12$K z>avZYqR^YH5^@tISca0Y^PDeuE5fggO>nKDQc4!x@gb4BQ3-B_bp9NnZ^R0`PID5B zvuFMRojHDoT5OlQ25+B8(T~}+w_}QEQa+58{foM};3vD#YDaGCP#ufS-mSdbNHA6U zc!x6&sm%g*q5!wCt_O)-Ez`WiiZD?^r0TqSQAx^?+O^E$fmKB}y5m;t2~Inu-l5bs z*KuqjQM)B7Z4MB-UDoVt@;Yg5>6QYq>3qxFg?L_d=CBdD111HbGa*tg;3D6zc1xy+ zIORcA@hBSwkyK9tAJwH(@%$OXU&R^or=ai24HLI8B-2uR0WV7=xMDcXQLd?El9FvW z@I|YzTwozRuCG%m#cEG$OC5zHLY4k>J2KyAf7+u*=l@ zY3Q$E0PeLRp-N)u=va}<}~j z_DP9Qw|0z8W?l!BN~1Gu7*`VOnOeFI3^!NAaTY0}$($XW&$ac1eJ3$7%NitqXb17} zd!Zs2$D#|y16isbU@Jc7*&DowCmj5bLtOH{)7UFhLm1lqy{Q!k6-a;97enzz*r=&~ zW6?OfLHw0W)QcL9(og=?fGabO@|1Tu&6SU3egRy^>P%H=w^xn>9iX|a8N7vX&6*Ct z@<<*(Gb|QxW^fb;yl1j_ZVf$=muTWE*ODHJS`NN1&7ZD&DL^mK8S_-uq(m{lvTDvY zXuIX<&wI^?LmeW*%DtL(d4UNS3{0{+=B-O*(E1iKd+S;F>@@VE05_D2T*o;s@>IVx z4l|}r08_GjRCnlZQsNb48W*yUsjb1uq>Q|40NK58RnEkUnw(WAm{MDcFz_H zXJmiKgjMvPY-4aSoR+R@I0Nyylso0ZM5hANx+Z|o(;`sydbOP)t<}BO9NCR%%y*dw zgzd?lz*8bCX%c_6331?etMR9*#QiB4R&Vt4`3c{Oo;uI%f}CDHcdjRXjPl10G_)cc zUV>vS@}PtPXUb`8Y$|?|e*&Jpb$*T?I{JL7lLc$kLQi9J$f4TF9p{R}Qe6$eiwOrC zla#}n^>u4nvwZdjzDEXW#G~I1#=z;yG&S9js&`{wd@uuG%(e1 zep6Tgn`@R+%u#Ag1KOi5LcMb zPy_#2c*@GlJxtdusY#%sP(mnt_eczn>n+8zHEa+g)=>$x*zbJw@56#`t`&lG zP7KZNGA^yo}&zQNCIu^l*y6Q4*wG-<5o*8FD4f-MP8u+ck#%&}V z5RI#}+B{vBG*lb5|8!1gd)jjPmfmAGOB7K!x+HPF`9BT3OTY5IOa}&vAjGW@MG=t> zOf7K=yQCa}ZVN&?u@Se5ZgfEy}lfA+^HNSCfuvEHX6q8Tg_r_extB&k$FNSAnGcjYt!=ppVN zCtH6FySW*}?XLQK0!^J}WKLX8ssKkeM z<+UWol?&e5bsNHiADSJ0%M{_xD1xqodG>D9b|e-D1%%ePneQ#FIl90_Z?d#=>Y$_+PhPr9>oXyR3{(|dlm z3$jfXN6t<7myexIOR6cA)>4~%Feh#@U;Ea}87`=3`HdFnX-$9nZR^~@QQ%1rF9#o5 z7`LG^TqE;k_%IDtTJV0}5ADWzE!=RJKBaN?Fa~rf_+?Su-^Vq0R}*UFO52D`XpQ*s zhr^K2AQ=qGnA-C*OaDNr1y%ZMf-`3U?P`g!1~=1#0LJKGvy}(+lT-96gzZGbxNHDG6uq(MO2(;^?|H^NJ`6t4*za zUx-9>GVxU_odk0BJGgH>bDF}{X`^>XD|}nf=+WU&{kwt;ny*y@lH+d1S}b0|0;MRv zP}zDz6877`_jDw#FE|dFZD`S%1Iq7F&JeBQgcqRp7acRbh1uf_+JsO>;@k2*S7_r| z++Kx$D)ul^e*X}Lz3{$8vmOhggMc?E3z|t-p-sB{Lm#emEf%KmOFls^)x5B~4$4f8 z-)tr>&PVgyMtNZxY1`#_lN<%_N$61W41FIU|77p>SDJg-a*0rgD!bZ*C?w-&53P!Q zzJIKC$MRpmN!M|7m?h&bs-7S**m@~0 zHeq^UreLPoWQO?KoNnkl<;^SqCknNF;}o$=>MIu)$~HEP<+8eZmW&FiDL)cSX+qvZ zi92|yFgfaVzC3| zJz7!QWblpe%ZMOlw)qq1;=?|pvHk@#^Wot9A>|7pgdFdu zok3&SnUB)rJN8P}@25DI@2MHBKt(J0AGH zbRL*p!5wDYX>I25pb~!lb~m}BC8)~@MeLgjTdri>US~#W%|3oGZL1Mxwb4nBW+A`v z#m;buO=K|;zV(`#3L}J=DW)4hhPFVJ>q{4-USWL_LFT&|Z(7$uPhalv`55`lM~&N$ zJ@L>P|H2}c4ej+u&tJflyQb&*KTK%IIz(veP^B6<$v8X+ ztI0PCR7m`hvh~yZZ1(Jxr7g0Kl`FBz#7^b2T;^@*gHUW!o{;QiLA4QMxTQEj6y}R= zo3fn~Mo3h0*C*gZD}dal>qj6+gKGsD6@_KlRS6AE=>p%|gJ|c%u-bFCLLvD)pI0{? z8E!L3ia=!f(DfU50{%m$_aFB)?*pT2{=#cR^Rbv9M%;2z-Pu1VjGBEwFJqn7r>nIL zu(h>H4}0D55JctBmtT#_A4)Cz$~R}syfS+B#_%mYexUBWN4%bdOk)T%-=c2j$|aZG z`@Vjo=CPjsjDbZ)s|oquvH1#L;NVqZbe=Eg7jx-O9x1nzi*G%$?P`U`m*3AXeZ+rZ ztw8C*;c-yya~!hkr%rsg%6}X@&&{}4PF%-XK0010Yd)>Fe<&T{jHhE`{>NbBG-bwN zK!7~ooN~pmjc@0MPv@H3kYtJ|JW_^6&Bg9s!GbDc=MZpV-=OM1tt7xF#S|v0g_LUbtk9-zL7AHyGnv7R+4~SCm1_E*8kZeH=}_}tM1S2HU0rL?l-Uku zPRe@X1<`Q}W7TXcs|oK;9y`m)y`}9m-F()5eXfUrkCx`3ymYOW*O?klHHL)GP*JIRU5=TWmP=^arlfJrCuf#t#?Xi5r##FG^7#vWAPP&>5~d5GRq?f%qXTsQG-6M$G8f6%-@@CDhJ z4a+28@L~*yiYroPK*i?EYX=#FO_!J(I{!UzunOofsT5hee{#p`)S*ySZlCZ>C=fp7 z-2dClLu*J5*|+b={LCLipU$FV^K#+HQ~c*uQu?sgemW(sYpx>>`x=8syJG7$-gqsw z%>4Zdf+txxZK}SB7qzh?3i+glL-x;`wpnv6LK zDFAa{$+7)W$h9G8y2;ztoaQ(+D29kcVm1-)(O?cKCg@c-Y_PiT){o-(GtL=z>o4F~ zE$Jlbvz$`l$*J*P=4HLl`o`3fE3ig5@`kihdIUKY!ZOKJJF>Q|lB!gF)#G~!%j|kx zT*8K7tU{o;<@aA|?Dn2xk%sNzpdXHV@0tt(6!RpExdSWS%S(d}&>Sm#&M{e}EA&h0 zS}pjo@h8VsGzN*&SN4`aF{+mSoQ!>Dkdu%OkKn^Kdi37AXNinRMAlck;yc^EpbRm2 zsiz#Y{7C$aQeAya$q`}D1@6{7wKC zy{1^UwO$lZW0`nA#a3H~?Cvh}@Al_%nxwR`S65c{=9osaB|T!3&`sIMMc-Y>&#JfG z*fLwrWe*MO7^$;ffW2j!K)<``f|%@B-X}xxP$y#^dE+Yb!>+8qU{^Tcnf_i{P-X8H znd0mz%V1HiZSDK{7hvu~yQsJfBLaD3seUj^dnY+`C;3M^lD~~FfV#7PFLlzerM+NQ z9yE%!V@5bH90Di8uCIPMBh={b7DGC$9kt>I)`A7;=599_t0pyvp*Vf;_@|+Kw<7cd zA6yHQJ55-qX-waC1I{qmdkv3Mzb9wuBH!6K@z{b**CoXlmX}6%m2j1jolp++@tSK# zdHL(8cD}Bft*9+`vWmH7qtx?7@P?`F~V{!F`>d3+(7;E2dE~| zR4croaC-S6KXX`V-|&-c%*DT6ptykX#`Qy!GBkh+CrsgBcA*Z}*rbj9U?V6ZhRhh1 zXCgA~16h~GW}Ex9zD-bSDi`C};+ZJr+n@N=!fl9>8%uoARPI%U5UzmlD@OyhNVJ$T z6*iotA$+zbv75xh=XtT(!FH#-RIRf!0=7MccENtu2!8=6<#Y;7$KM)@+TNIdF^yU+ z^Tj_U*(v|z59ji!IGG`~SXy2b+a)M>?48QsjfmYfV}cCg#3G>Bt=*kUq+)N7D zN9AEB7odR!?oQt`k|GD5E!$X@OyUU@U;hH20&rP_BhbD@{SN?jK#IS~W|nowYWsC1 zI_at5j_;AA`&RuvJ;VBa756kJ?UZBGq65X~Nw(ft^=ve)9ZC7D(x)A!Xe?v3;gnrP z&5XVO0K1I&g+<}mMZGMF9&U;3b*x7FhMz&u#8hgVMfoTmKjQ;eyc z>y^jEDy#)QsJig#3*|KDZQduR_K3WqA0&x3>l1Ldq>tr@d~CC*rm4&<90HPxj;29y z$7{=qZbp*b1>opMR}@+#+WbyK{<%0ml-u<~PqbB%=`7R8KQxLWJUXi0qrA#+Vbvh& z(Qd19)KFDA3uJ>M#bx&ew`QJS(#UGnxlfM0i1PyIq^X zQPhRRb)vN)Und}RQS#?>mj=V4wS7+e8(euN9fRfG;yP%IYQ+U&AfDxZHWzRtb-FSb znmf+`4ge`;VR!q)ryGTUEDe&>s+!S`u@HI@QHD2My6_fGwm?nD0P<2lRU2Ddbqj+C z$m_JY-=gP|PpCN1eLx>BR|9K{@fS>0z3+XR{LQkLei~c2Sa=~_hdHI$bsa)9)NgRA zklciyK5aBY)`IFshm+%VV3!Sh*>dKOG{M4wjE6a%)!7=6Gp!!wCAk9y^AeYK3tsAc z;LH~po2OOdgEWs>x+`W~&R00LK)yyaaa?Uf2UR+7MiIO(3>pEw!5rurkqPN70_|^@ zUKQ>HWfndPz}z04yY5oHuBB-fj1)7VaUzI_F*!iW=H~K6$1CRmG(32PCmkeA9oUDH zQ@xW~-s?#BByvR+WV#V&44O~~c^BrF^+U+9p2Mc7@grHiz<9ZIR74N&2lO=|as(tlQw{x5AN%Zz^GBIzAS3C4PAR|UJ4+Xw3NB<5~a8vUVV^d$Kv9kjN%4JWPE%Tb$k zu|4%*Z>?aGX`_tXC?DEaxLYXmB&H|J$!Th3dEajY%cOS_N%2tc$0OhqPR3Z^ZFKxZjcBcgh63yQuJ$Je1a|;$;ICr0 z2>$^5TG9Uih-du^u~%U1lQDv|!|*z0TmJw`ml6K}^}lcA4i@ZDh5+-quJ$Kcv+uTm z-<6fxrx~JY9*2j*)6QqP{^4YguHtv^u(}h~u9d$Bt;C;7Z`FSy!CzL&gLZyv?sdEq zhi4`lfJd=hquMq?Z%;)_8R{JW0R2RyRBa_p{{V84zxYUx#i(bk(wg-)?#-2GHfDJ= zYjb(0-Ln$K`@xsQt34LfSJ8`8h$9DnN_IEk7i#vS7OS>;x`tc*lZ!qUTQ(!4SFidt z+J9men`5e_ZP^iXd4NbqMX>mR9ol&uIz~z|EOQmLg?(}HsCoTSFygqhnu?mEZRa^i z=ZNc+R^LVd-HEPg)|Q|ikhr5E*y_=1(^Rx`bPjLCBO*ARQ5JmillhC3jC(PPRsiWJ z-sX?I_S)XtE@|RwM|s5=%HC&S81_1 zr@+zaRhVrncV0?{`~rQLZU-15yJ%n_Z*4OPzn-g>c*%W!h{|d2ukC;T04_kWxD7jl zX2z*Y`9#-A`UO3PVbnFHqKx7-4LoC~5szz$Ptw=yrVrl2@Oi5yzwabw{{T09lh51i zNCMhy8a6a~q0*1gu4z=nRNCO4Xa4|7FaH4PrR~4DJw>s&Sj!~zS>M0y<}bI9(O9Wy zX{i_+PfrYBeg*)ts-3Rd$=!IHMF8N@sX;}xn`9>;&Vs|6E((LgR+i^HusDj|YK#8> z^lSLOLxxoLvN|RR$uZ0#?oVZQH)a>YKb9^Hyv9Wz`6?o zhN_;C(bUuc9l506E~~2vMpWO#j=C5=UWA;cuHQyIJW>~L7V$`I4>}7UG(+DV&R2ut zn3}d2v@dPfuCDeWnq38ocW!oHYH_rN*Rw$JQRft4I!Hk15*>-he_h2Kk}9JzWwhU! zgQ+R@)5hBqD}0(tuN+!2pue?y>wt7^y{I}RBJl#CkrQ7fFUQ=t_RUH&~Zf3&^-5}7p`jC}1MMH+6#3?y3Aokm{c{a7m5?Rh$HHB6SDz%9@ zRGYkz0qu>61TPq5aRimh%Bf;+b`tM32U(6)w2<0R0OEC5NwTqo**+lrN8Y*`#%M?- zQfJyK(sgKH94v-rJyc(r=KVM=Z@E=T^=5$jsHum1w!m>g&yK~phh7;%q|{>76^AR0 zo5&_RcLQskI;^7}qpB;ZZj%}94XkXKHSKnW_Em>UcZcYf$5FQ8JL0Vf;y!MZrvUT=#FG-6U?2w?0PSDfSZDSssvA$ig~lxT#&Y89JXbG zCz9rnMuz*;oY;)T z`lq9-cmuR~B=F8uR8q8^_qSABAU)uca`>o9lor3pxffI%=|+XdTXOA9lP{3o@y#@S zGhgDpQIrO>I2RtbvL5!{;bk>SJJ`{PQ)H8Qoz5LVPI^pjA)_n0xO>jxeZiJ83B9gLL&Dk@WIRb={!N(yx*Igt`XaF4-L}@`Sg_7t< zOI<6+edWogRX7gooZkaw9BNs6U&RSoUmIF=8@DG^Or; zy+Xw^9OCH!H{EUsn1pt|;oz*J6bV5hOVivzyN{7d70=VW;nX<--pCDZmd?*hH%@5f zK~O}4Ya~D3oBse3Ix5LB0@)pZ8>qOO4*aLui>DD}IYzxyYDHLNqu4e|*+6L%JFcmx zR?V1Ydzv|}mPeCuNigTOR=9gqAz<1)gu1?6?v=$LSIMoONj(Dbr*LrwGj5=-HkgBP z86F{9;X*jTkEp~XWTYGU0Fp5&ow#4fBI_vScYCuo=$eZQtdqRhWVbyPRO#DW9PLGP z7#M`sHx}|pIE1HhD3@W?d9wCT&C5mSVD-&zXH!XU1as5wgtS4;bfoHBEQgxDT;8*- z(G+Q>%135z*2$AcQg|BVdmb(!3GX4*%BMcvtt7Y~{yEt@ZHV>KA)WoRig()yRET{(46(4#()!|sFN<1eU!CF`^SHoIw-1m$nR>rGF%~tSZ+K& zs#(NvsL0Do)BazPRpLI8WV61-Dmp5HHG9#N_)S~Q*d>1}3#(bj>0`fVO&dVq2gwUI zKiU#WyMGA+$U2oeM0Iz$8x{vxb#qHUjE4MFohHuC_YCqeqsR5!f{z*yxzB>^zYONy z#Mjp^bKKzewa0k^#Uth=*?Wbh_bd=#RN=%+R^2y{Z}7S&4apIYFA(;B(PN^Omti03o75>HgI*byX#P%}C-@^GC&wxJ^p|v#h^5&$Zq+tB* zGM{PrEceq|eP;{wa_6P9c_1GYIS4;HZ}JANUnJWq@?0aVd~H8=J1~FxfTL%|rk>(i zT_yL2z0UZmu&Zg@=KE5;CA?e@nxt@aqqI;|ZMKDkVk2-39t;UzSw8-;^3hj?K;PZB zis`CoKC?jaPr-`W8K%nu)sKs+jI1j};H;hf5QFfg(z`W;eM(QZj?=OXtk%NcOV)PXyzGS{s&7{&wN!pc_(4 zj{g7)uFFvNu)8bwa4EP(aA=UPtz>O1XaUL*#J$;{Atgl3Un|~EW_9YC6;qR>WTLGd z5zz=Jt0HNjUsn{M);L&`r@cc7>JI4j@JbJ>AZd|o!=h!TA*aPHlE%#d_b5^6H@l>; zO33SXaFbq^8f{=a(hYlC_l1-pY-~4ln)t4os2bhi_^olQx)UE*a?D2s!CfQ0 zRw$3GBLll&ntmuwfD_dW$l4Q~yu^cW5`&z5c{J#&uCJB)4HsKcEBD^ZXF=7X#K|IO zuJU}+y*inPg03~PKSC}O?SPSUU=YqWR>|+JqqW0Mofkg0dxfKEw^bDk5!e?x{S=)% zaWn&vo`_~0QSLoYJw&d#Bq>@-DJ6B?n50m)_AH1t{LR$WKiM|qpK6s#xlyGQhpDjE zM8N0ezg5|r1Wwi8l2AxG5I7IO=d@|)F*8g1&yS4h%r6mNG@Y=fS%R%wx~ zXi=`MeeHL6H3v~OO+$z+b%!#m7$j0O?YMnTCw&RJ8P#ye-9t(6Q7?tfc@DOQ;U**B zH*wKEYiw0UHY8n71ozIyp=Q7!m-lZh@hh#9yhAT?i>5OdckFyta|1*TUd~m*3*jN- z^6yac#Vk%DRyn7t(c)wGkU-|2b{8FMZ7Uk#*FurhOL1FB%QKKIb7Y|>ch(B=i0pKF zm?SJbj^$pf*~-W@YkNV_7|Ba!1+L79GVtNzmdShCTn`o5aQdj7-i6}b3T4cAJ>>SszOy+&iS@!doRV6A(NjIex88ZF=R_cgH2U_rR*Ef@l9rTDQkoHUr z=f2CKOG5YRHt8A~1EZ$N2KX;vRdf!-K*X5o zDfel>^HqBMMYpqe_@N`JzT#F>NR>*<%4(gy64$sgC~bWS2lP%yRYR_3z|r|*C-qsB zb+2u^#>c89n1j#WKGZTsUo-U*E-zz5lCG2czQWyOD7rf82FLc!x#S}YG-e>=Lu=h_ z4bXIA{%7hoMJ){md{zd!@I@1vIp}@}%r_T-r01eziRC+o=(BNMEycH$BSTjymAZ=* zlEYJLR{cj*V%3crJYEMpgN8A--S))(h{&h5l|yFzs$tlk7F}>yI%NK6}8#zO;Vq?rvoYDDr>A3&9HDgWg;TAcuqUelS)1`Y5-gcGsRvJrSBRpt zvL^9L_iWSMwiTM)@!OS!lQM0EX!T}me}n+DQ;HMBT{iql#Ax7$H%V-ej}sF^`sxrY zT++C4n12{m7Pc|>mwwD0v$OD&IhJhf zvVCI-`@`D2D`Md9dad?@@UCamh+COv&dVpHF!4Us<0}sLk>;yl--+`|16@g5n0<|z zmLW7-%!PNhhw;0`SK7{{EHLvcao=H+C&ck^aZB`w^c|s4A~FEGz3ZWe@fEnNWp~h? z?V{rHPg0cGq5MPIpqmz($xpH^-!xT59ITy=q8@sqsj6YVmcLb9f*o}_^->gVV320j zQZr*HvTM>~4j|bkO4z}$2iTf?Y`7pDQ)WAjMS^_6P#=oBJM7R`YwH~p1d>V(k&yfM zfC!1<4}^o>Bxn%KZW&yY7a-U$q#R-7=^0C@r7Vz)niShlBY%P0PZF`RzOSmzLcaK=v zC{sAwGmn}iJZ?I!7XiUx_(*NE&D>vf?_$P~0J`pC?^UMjfIZH(T~N9iK@J9v4IX7L zF=(~h>`i2OaU0W(rIFj3d=So%$~HRL4smyHPsZVLPMs{54jVJvGQ{!+MJ-8zJh&IO z+<_=MgJ^&vFMF)Udz7st6B`Fu4+Self_7vV{{Zl;6xcLcT-^95PY|Qjoa)9TRvkoK zKpC7*lCZ;uOz;^TPH{WUt`&;|!eXS0wR~mIJAqdWIyMS7IncB?@v`$P;+&p`mx;&a ze+s#j;y7Djh}^NS9LF9?5MiP222y#8Eka$8ma?(a)XW2OU99AliEO+OQ4<+1lYs+r z(8Va}aKpr4tsC95I;W$fg~IWd4mVuHAc^j8qr;F62V1EsxSbYe5E|jW=m`jRgxvI9 zF|6LFWMtdD$b(25AueNgUvfaEiNKD@x4#uB+2%g8ldPpA-zf1^P=-k1%ifW#NFw_C!rm&1AnpcL(Z-2t4ELDil5}>YCX<%{Q*I&a$W7+%_^|c|c z05oqPtEDyp1Z*9IO(TgRNqtn)Pua3bA|!8T1;tYo<+0{g@g&uwp;^RsDE3ZuG`D#B zuHvZG)V_}^qL5kRLJHJt=`p%`U??kuxpd0*3){s~F(~RLo!?nS*~~P|>kF+)q`I-N zb>}ZMlV0U{@Wdm^K)K;%UBvJhDkaVvklNP+dbH03O_RS+%U?j-xisg~G~7060OSLH zYq}L=wmn!)t%{r*F38An?>DNJp}_!cwGdeJxQsb@Ww0$wNz=_Q9Hpoi9d*| zJ_~`>K6vHG=Cm8$*7&YEt-muo{7d*>5=F|IdU}}~XbHC$>H>2Vs&sU(Wi&3DR|3|y z+9Ii<5k}FGp!81Lj~;7^`ky}K9M?$8WY~;DB$WlR=Qnp}eS8&mdO0a$ah@_IajiG70q-736*Uii z!(E}`gAj*mZ*=ZYp$e57(jerxxRZ3YX5n&)Z8)~%;~_hPQdokf#-q>45f)*6Q}NQh z!ud*(rNU>M)nl*VojVteD*#3jkXUbR)ZHe{)VK`H4dz{I3zpho8OT0=CL1|3WVpljSXxi9LBIx^`U zPZL4jNYNf-Wxym|K<#wZKUX=F%`6{z$CpI~c{^O;rz({umTk=I=@JO(aY$yvgmFf} za@hAcbyQ4ZvP@u>&Kg+ILqkQB&MXX%kV_-vzWl{j6yj3oMVLwss|2g3W!^W9z^w*4N{2XRVIXlQM7c1*BhPB_sIjrRT{er{ z(!$e|xsy5<0zQn05t#j>n;1N`D?n{Ut=X^+r8?Oqk=D#f=dA+2$^l9FsMLjfOHmgL)Z;0gfo;(ajq32~`_CM^L~`n&ZW&$2v_5 z;9vuNf_;OvAjAU#IhRo-P7+@dDmaeDRmRgTDeV|Ks-1N$O)JZ0W7m>+u4`Mx-Mp*x;|>@RGbVFdXEaNlARCnOZhG^m9@sFT+TYhjVy^ zOvd(e62f2ouAR*s?6{>)X=$nB)xy=Kd#wm(yEhceoPYLHgm57>CcC#TSvGMoO}XN{ zNg5KGX(O8Bi77J+a@ZwzAidf-NjD28dxBrpRvx%s-btR~p)xE;?ulKY=%j`gFYAQA zs=yjZ5y>IA6C%Wil3!>~1t!ca6YG0Z&BDWN-Uz5jWkPI$Z zFllXI!cT&1tg6FEg>NjWz&a_&@CuiDu{->wI@}7RY1q5iQAl-$X^0o! zthTi8@v>0a`pys&Ka<@c#gjd6X2P)(&y& zl@2NnRnV)|P>D6MB?f4o)}1cis`|=GOs3CwhE$m-X^VY*59KLcSeaX?nblJkJ3vBU zM($Ty9YzaDCwbD3@|DQ&8cQ3#KLQGrsxE~-5`ZFqBAoPT2)JvHlF%81LmPXB3-dQb zLx#64+aw_MHc09&=DJ3E6%@wAFx#p!EFO2XS+*+i9R^ZEdooWP_e3`hb9Z)kKbR$T zqukDE6u59nZFlL?=(b=?ZHu1bdJ8LCaOnxKEYtHM;J|9;2eFCI{t%JZis~z?81Bln zctis)aFN9s6%0(#v4A)bt`xf;Su1nO=pz3B5&HX-j5{Mt!Fr5Af!w2Q{wh+&L3JT4 zWhK<4#Rgwhwj~hQj~&ZFg96$5a?<%7BZlzlG(JHePbL9MPi0R`FU_U@02R+XW%frj z@$2P_We*O+>S~_W!3*Eu30G_7WzG+)3+xy959t{f7s^WT7v5uNcb1oDUpHe3u0c3S9179tz6Pwh9q7g*G1D;%bSA8~; zEm+-CI`VrWlE=-jh9zqc**EuHQtf#Xth88M6o6!4zk*4{7<3IbPRCl;Z*P*6r6;+^ zR;_BDV7ps#^6M=2-z~Zjx7?09XM&uoUaoxmkpWoxv@m&*w|Pn>9%_kmO>j&;5vn5pJ|Irg(WOe*zY4% z8C$eCjPViLV-$J(X6xx+VLM)9jSxm#KVl9w6Br4h@0Q0BWk@cRv3B=oLuK zQu~=@d)#-D;*uC$Ymt6HU1u3R*_|qq^H0#eHx$|YJbz|jmBVpkKT?{g+Yj~-wXOWd zf@<`6vAnYkIEo33zepN0g;|$=&_gccaCfSqP9h*Ze0>ayp)=?)CDHoK-^w zhFFRH;OT7@j+hw8abt<*LX@JB$6Ayk+C=0;xLAs4wDu*9R6wnTCerx*_g+I|{B_Dee#VMd13qTkD05J>Hc$#t2=e;a# z1*UIJPoAQ}=aA_O{fa`LXR4$Y$Q$CmgNp>%j4oSM*LqBi-ZK|8{{WPJsi`)zW3Zx4 z1`^2}O^#-^-ttemFEHPhEr_vMzvvN%&3YVjt_@%SDre4BrY$*XakTE0^y zO;nC2c`hXTN(9}`CTZT_e-XOe8j|x%(A>3Y`RaNr8zc6&WAMg3v~*07;OHE|uYn|= za$=|HNu9w(M8>pl4~td#}H|s^b%hpm()BQ)a!6cCESUZmAj(j>x?{H9CG+q*`H~ z2EOiaC!xBwY`)O4j-W8b_HH)4)>|GSu7k)aDm+5(13ie~maZL52Cg18BGiqKAIF*^ z29SqLcUbOJ%yVjS3Ys%bB-R92L#BOvAdi}UBnp;OB*CDInMC8p+Q0XNadr1 zg(j0%`;g?bE1cnR%$uf6f#bTsZOB;`>URd)xGl`;kji#kmpuOfD7VFTDI|JVK#eah z8^Gk{&hwsr1B#PvHfE66obIkDx;iJD2DT=608i?(tdaw$k|3EwJmirG1DlSBxNzXZ zd1?i@M%>o(Ix8g>&s0^v)skIwxUl!Db{DZEWpznxV~`g<)2imFic6`~=c!g|94eNHqqC0gA9>oJdN_3VFFvJr zGsMLBjH+oJfH6!xxP>#Jl(SvOE~Cw5J5Pa}GN=c#;oFE?Dye-TWb`&(?z); z8oE}Rm%1jmD~-S>a#R!njXAQP5Q~n^Co3)!jckr^v}WA`eD1?G0%%&|13)8)N*H8M z&>LHZI z@y_F848?YDEo{OkWngi&&_`!32{Mmj)ApMUYb5RB)vna(xmBl+DJI2tIuxa?iB-T; zz3fxwz_K*jL4O4cEJdy@JHoupaO2(bRh&iwDqKTtb;pRqMJvS~ULu5Jy)J*qw1pI1r+@=Fs0^U++JdrbZ1PbfICp~Jdq+gK+sH6 z6UY!QwAyc@8uu^}O&C9!);|>C`ZUA=0nk&lej@$Ib9DZpdCDJBGpcEs2(&Ru#-0J= zRAf(+QrKki?F!N1`ZSe{%=+pW?ab~|7!Jwd^afFhONG|vAh7yDW>04IIb$iMb*U-i z=%n0T%$=T)@#>Iy7oVDajh}Iensq-lwhRkr_n5HJ{{S4H)hV!v~2aN1tCy zIlokgNHY^`b7nb!Jq^lVVIltRKOl8mW2kV3_nL5^da$4QlG*4-y3>MNtUuLmd;b6s zex|xXn1fgY#QF@I>$3&w-B^N6m65Y&*3WBTwatqxD0c`v>(k zYvum>80#tQNbH_X`l1;^^4o|cK1FJC!B!u3*Wq^jvBndzI~pytHT0x%1)n9k=iA)R z#2v1Z^?$*YM?)YsH1dn`2jrxnz$@k<@le#czre3mG2@Os>WiPtFdqfTV#k_0(mBoR zHU9uquNg15s3nb)S61~Yzq6QzB=_z^K|t|0bi^chD&+ba!37^|e!rvCuOere%J zigtbAJ)tYbPr4D}r)|+%Zp_!`%4r@wiP<(w6Un73i17+;Dq8&HcLTpRRO_O|Viz?L zx52<6%ul)%;%Z+>BC0lD5_5yKmlAOwmKLeH)mbNB7cH&m{{VrgHWjfO$x`V$4)5quH2WbF9805x+_RSx`Yu0e zU||-lk=*Xqh39R38%OVEC&XLek-Dstc*zAtvvNdFVwoBx`)f=caT|#0P|H)WoIG3U{sP~!Z*~6wx4eHR^+{&OGWU|YNB;mQAF2{f zxq$kV`i>-~UAJHjDcf{ZYW5vN8~w2?HbR6R^wQt>hsiw-$>Gh^nO*$DYhQHnY7+57xf;V|+uPe~V{MMUGIfyem5SE5kI z>vm-RXW3?NtZ~13-en0qx$WORda5c7?#iqL-t#rs>0--xnHPxFHux$%SgIpum6kgD zpHgQxz>tM}l4i4|lcBjzu(#{%I`FpNpV-)C!z&!qn-P0_0I~?-Qg)wdi;Hs3F0#K! zlGg2{9WGJ~&IEmlJ@cFNw*&6JW6nb{Jt*y>Rh1O8vS$@*GIY(JqvI9}B zuO#(KZC+N9bL6)xF6R9@e_yewOwF2s)|R3$dJ=vKLbGM#XYV0CEp`42B%ZWp7JZX% z#B{N6@VaG$eOUhhr+1q*gN%Zsw3Gp?!gH z1a(cx$E#y64X4&m)YatMoF+frQn}XQqf1^T5qzzyQw zAs$i#FASm8dzw-DN2Lc!2MhlIydNbWS@fABU;Unr48NR{@UqS7=8QY{thnUssL3h^ z2^xA`QDJE=gD(!lr{(_uCXZS4p@_*IT6k&UaQn?^KNSsI^pTHA8HiO_yYMlsBg_Ru zmPs=Z*#HNrDT!ma1kGd66-9@pzEaV_(2meWrl)K1sn}zi5UDLY!I+WpQ1v*B(pbw- zeX1-#=Wmh3codgVwCFRq^Rl9hR>6cg2HNDZ8@#2O0xvtPQ zVp7?Koo@uB_}Lu>&~xi%0cN$M$u#^@`U7L&pBPC$_h(;`dUxuJTJ+!^d6Bv`(Hkv$ z-hS}_Zn{fq3x=fh7gMalPD_Xz=1O)9Cg(_@9x1ManH9k^vuIpXtZSt8SZ=JN3X!h6 zT*jV_{)zVCPqt@EA@CMkF|#7P+DU8=V&mcy@zOs`<~YY9;rc5a7$A^p*?S2F zV>K{^r}2=|Jh!Phb`F{%eLoVWjE)UAKKm%k7fp3JrH`W>9IyLp{tW7wvN&#IJ}Blz zERD+NJ;S2DvkTc;CXrEyI%ki;r|6cl%-Q_{%$@&x=9qJv{AWot~aHWAc-erZ!}z99CN(o#bU27`^1RZi04^Zxp! z%x@-}DK%DKVse%$r)c|@$hIz$G96b?-8g^pr0Js0g2n$X5@_#6G1xsT)scjJ;g=+;EuZq%%`*v)G+jk$X7 zL&=HPNq*7S>vYQlFh|1IQ(ta3M#zbpq!)fvifyOEp3)2K2NJQjPgZVo*e0)1^Ku?N z>G=-qb>TGL?AqkotR;;kPneDYQ+AhVW5nD(kh=%}L3 zEO+t>%Z6YSlg8J&QV$VjNc!c?(Txop$w`wMta!5=@ZO0X3bH3sg;}d7V_2rcaGF+* z(nx_E$Ws)x^cE4*%EuAF6@v-tC2=luyve#gj#*-Gc$LA!k<~_cN7bB_JTb|9Q@b~S zb2JfR5cPFUri{2ZGq?bvc3%{Zs}EuAgPb%v>XnUFK?E*)-ZTq~E6roZ;5AbtMH6Tq z_f<2J0V+v*J|(x>8&SkM6f|JymkP7qU_;>jQ8uy=*>*)8STB zliY}WU93-xlIIO90;pn?<&qZVSLx}bizKYKdw!}n62k=Nv^E6wUY#p5s&Y;ybu>Sf zMPf9?!ufUZS_|-Iyc>`R=76ifgZDgWtm$S}Ji3s+hK2r-tJut%wr{lzrFM8Ys*c>w2 zYaOM@H|o49-+c)K#a0g zYU8SYI;Psj=fxgbb_0SZ4RnY5#~X4_GZB=3c>8#$Smv=d0>{li8GNK=Y4#*|bVrpMr$%XN-8qck$ppmHgNDLSIclgI`lx9j_Y2_y{2vg>ioJoR8k&w zPF)VAAk>F%ceyg-cG=$L9ahOAb=pmmx>g><&!hqOy^F$d?awN@YK_2i$AWNGHKSJ) zxea1}w-?@Y>Xg|vxi?TT)rSvVILfhY9q(HqtWI{rOs2_kJd?jg49cN>_UcM9imD$- ziSIq6Vc@x-+c4yoP|yxK5`o{Clyw{I%MwXPNsnNi(GCd9@y#hl(d|wd5CCA23-Qf$ z$1Lr6PASf#T0#4NBxRiD+;1BBr|ARS;t8?6?4|zzY3UpZj|hNxuD5IBjF!iR(vRg) zdH#|PRa&x#x+3RPV1t19tv&yVKcvXhtdz_ONVQ!@1-pxd>WVIT?Nfq zFE6=VFjdkx7DV?mkp&$T4z6d}!6=jXO0iRIMDjklggLYX3n;8=TyB~!+6+Qx zMQh!qG-^rdpkZFDteuys4(*r4o^+&?Uz&UNGZ&Gc>dYNXfE5uwGl|McmcF6Z;9Qy* zm2=zvyEwND<#Z_2?vAXui*CIcDt0Fv2Hk{xFDqA{u-NOrxsU$Qe^0b8#ahyuQt;8yA$jDK(Ug*}uUr8@V{DnJYv4=ZMKBG=eO;55Isnp@MK1?gn zWB9B9^^4*5eYK-&sh}`3_K-{iVhUKmA&x!?Z&6pI~1S*URp9 zAETUI@79ns4kku~SNb=_j`*Sjtf50oiB?SV>1(HRj~>vZNmEc-FIgezWLnjGuR~?T zKbd+z)7r77(c3{7BSZEA7XPO8r$53 zxKsMsxq7{7zeHT=@Ew?A`}pv9t;oBV-tzR^U@@e`iAY^BWA#@mqnTDNK;LEI- z9vV-2kK6b!U9QH!MVV1IL%94GowOS<2DG?GXh-5dJE|+Cg`?MRJzNq!Hk=D~k>8c> zhc2G&zsNZ_zci%(044pqZvQ-5~M_FT^R@ zJ#lo)3l70pDl6qI)9npyMsC`&R}MY<1sNrCUgL7|Pdk+l9wQBiBwwISI}?OB0$pxJ zS~ODG7mXOlM2U}2j9`s#Tbk*UKOB+9 z%?%u^n|?J==LVO#z6jbu%FA0=;5B{6jh9Q|j9u;*9LlB3h}08e_gMQnlqw^*H@c?37?RcwHt|gy zN>_h+ehIRpHxlS-BE8&O;<+v@;l4Z5A&yQis51W5(|Ff|r9BtEzeQ zx`#{1IOsf;9-Y#>{op!oQYO3+w2o{)j^cZEjB00 z$_~1?ww^-cr^R;CjkRM=HjT+SDt^xS#$N>mU5ki`Y%g(lf`h2U=aq$wIa$d^85|AO zokX@gxlLa{fWVv$0XP_VH7GIMlyaSDOF-qSP#qGR8^H$0*q#wQ>H+p6lx)@;ZMhWa zZS7{+Es)Abs_$YZ3L1$wG3Ut~qvo;)V@`poZH(W&qLZe`rV5D_uUfYZD^wDL&i?$4E%_qi`L zzF4>TB=KF5L4ypQYh`1PfKwEkFI_X51Tqdr+ABM@u8NW6)ReT*le{|bzrcHtH9LPZ zON4bMFn5T+Wxat8CL&%~TlXg_)n(;Flv@ReObL8WYe(KYl%zWg6io)V5T%6iynx%v z_f-mwvUILFrjhsjPA>SU*>c_%>>d zy{aPA@zX-ly29t=qUyHK5R~_;OF`hq+x1yL){^Gbnl8B;u4Z{v%nxGaUGIpek-2!M zbeEzhsw>@-Bo=E9`*A_bDRWK74oW^pxv}QYWGSCGwqt&)rZkdT6@+iFO+j-tX+av9 zHL)Vxl9?--d&FC=tJ4hQx=Ftvp2g!kXvbr#aBj9Ig5;Bu-t+B#3OupqHgr8%Pta;7?WBimGSW!yR^TTz&A zb^0Nsgu?8uxNzdVaMQ$-yp<-bG9;mUo^Bd^PZVuDuWPw=x`~O;%b_;CkqwGVi(He{ z4FJ(#imAYXac-!_&f-8PMO$PN7i%?#jmkC*oMdqz+QlPCTO!9zRb!RM*ikH;?gHfT zP;p5c1=!JkWT}iUp0^Nqj+UNjUCE5ld9HeqOHxycOEU~$dATpMc$=vjCP5(u(%HTm zl_!VnMkzBvs(e(;g&wDAGi!y8E5W{nf;mp!&To^7VwdiN;xRb7L9 z8mf#DYhB}22UCbT8^qTh#O`agw?%r|Xd$YgaL(4s-C9<6Q-w>}I%(=6?(P8UO06uf zbH52FR9BXS9wywGpBWZ9*aZnCS&LC}d0AOI2;_)|5FB_Ys>&AT?tpKui_)hxYctq{ zmcVVSGsa9{f{@u{BO}}A$#t-HZ@2^wBI*30)-3^DSf)@zOCyOP;h=ElpzEvVq>!=g z0Qsvkfi?qh;Hi5}49%)^j%h3#wA_kFHb{5tr-6GfGaHe|yz5z39abB5ojg-UtPMcl z$!kQLsT5mZjXK-Mn&HPBTkC7hR9{*xpt?hAblf#t;TMerTt+_?F;#F=Bci& zaXK{FHfvtpEQi7lJPWVw2^#mc;F8^-AntX%RV-G#{nVjY?n`_j@u`sLTy5f`duN@%rFN3L?v!k9G)WOq@x0viToXF`{H~*mr5qQpE%aK( z<03@LZ=p8vUNX7ES1A7gwX*X}x3DB5VDXn=;_heOyV^7j_w!NjY=qk9MBA9|FE?7k z@&3e%r)My^>1+d61sGvGTvALU@m&o->&vkw>_iV$$%j{+Kqx-Qb0v2m1XwMooM|wF zTOqxPNp$ab5}Pa|kX{kEjq6#1JeaJ`JJ$n;7cRC36t>cysM&*2IfshqbFHA!I@(dD z^h|WZ+ln@0r64d|Y@B3nB_9nLc9{wCM?r?Y#CI*?jjU2M`;pL76R8??QdD$-&Ii5Z z6wMY6a|@08?5F84mqr0(b{y@hg-XgjMsth^dP+9N&|Wm*vEqezGC#R% zj#`z`MRh2okpqf?Wn&w12BZZEi$+a30B&cPZNiZnXmh5d7MgnUjF5JToVMRds`!(JP2_1w&~X47!~`H$FJghum1q0 zP>wxR>sWx20MS4$zj3;=_pFXdb(=d?KJ3vV!f-~NOe7D)_aB2&dn_I~T+>VkCx({~ zh*{^?HDqY}Cf$nFJA8-94j*~5Ds4Skrb#v%2nHCVl=5^zpQ%m^UWO=6SX{?{8*Dv| z1!bnbp~Ls3Hfp9j)$B0$fK0+~;+yj&YPy(GNbVrbBY~9}=1}N_U@P=4TEX zE|N~7$YwTEndIV1=rV5AbUDX@?|6*}TH(rjzk8JDdzl^yEe^h<1f!U4a@P(-b*ypJ zl2h@5ED6wZO|hcdyn>aW`^5Ppak3^n+@;pNkr8RQ^%|i=T1CaecFnM{)1A@GHbhF( zRS!~E=n+M*Kh-7P3KkZySeEbNN-~yS$oZVc8&W3ag;C(#LStEV+N9?fGx;j-cL!Vc$3 z0~_rnN=33&`|7GFY1lZLfbVl|qbyB0{K%892>i&YHjf}3c_-OVri7BNux!pA$nRN= zG0))GAujmY84ee-@Q_4Rv%G#O0~&mQ{ZwU*hfBY~H;a!%-{bkQpUH>AD}K@tTzH-6 zDvSx?htcB?=mp4Hz{GIc5L*@%NYl;Wy!+B)+9{(2w8Ea9uGY~+*8CSdv5n}X?k6=- z?GIP~0JA8d*^GY>7o@`~pAa_9lEd0m?LX1pCdL<6*3i)dygp##>>8_PFO1d7!yMH! zco%3@PPTf6YQ~q7#Or0RT3wM?)U8vp_x}145bS0LOxC)$9(=F+&8CXu+Ek1mV4@onmHivA=X#OHyqVu(TF|CAb$UPYo}X z5vzfF(@7fD6KI>=b!(j~MxpZC%~v*tY*n>oxMfkA9wa+=iB-7qDr`n9&5_KQ@KKd) zs;2}s@5tkF1GlKckHxRK8aO8z-cMf59h)4gklJY^z3sg?FRN8!|1z5cZN`xh~Ux98-Z=wc=Arh9?J$B zSsM$S?(2=p=aoq(dz{mhuX7>VotbqhGQit{eNnBIGFCn(7P+hFs_Z?rH@hy=z^wag zwpFeVmY4uW@z46Mh*VQXcBr)6%pB)yVRYG%*08WeRntjJA!~v5Aqy|Oi!CdhbIi98 zW`1iVIzxNrv0ac0>g%R#FTUw+#LUGBh&$4qBgplWZ)o5wxJVict#)kBsN*Dg(39;( z(+tMhkTJIUo1-Z*$1@)Gm$V%~HoBxY)M!0yOjF^q%FyOD?r$&&2*|K$+;oah@~H|9 zp%7E{bnv;X$5QZCvl zo;_CixTf2*b2ob#{$ifd3T=a(oU9u9GwtA=k7sJ#{{VM?vvP7xps054%nv`BWMlOp zn|5Bstb=(4jh3te<^nuO2~63FmmgLk{89jh82wZC$rwEubl=ytK#|%3{DELzWU<*w zfHOJtqsCUH#j^#*#-f#nnRV1Fv5Kc1CXo09gNj#!-IicFEN_kVET?55V|)0GC_j`t z+!L4?nNp7x55>_N2|j z00e`v(H3T9Z8_;;3WOM*Cee0HGf(lZjAIjdv!<2aCg^3K)$D6exhVaRdstoDQ@C;u z0S?`kaosI5e~MauXX7E0FYXzWDyR*ViGp&WJAV4 z62`(;wb@K?U!Q6dZO7}Mqk}*4(%Fq-A>G#woy0%o?!6!K6srA)j>6ZpT+_zjIEh-F zrr8N5aAcCx)1UW`2jG+St|K7UQV->7dK$b%-`~q_Hb~uXsd^2TtLneLprAQ@Oau9*yWRvb(E_xOg=W*uo65}ix}1mHJ}8m@?27>C%Zg} z;wnksGa_oRtTBxak_^1En=0-jHUmsu+tNz+b$K4ea;%6=O(R{uDIsH6^zIYAvKLr5 zjmxJC4lA>4?!dv%8q$ar59w2C0^HJm4htRlQ?qBP!6oAoZatn zCyR<2o|b1>#=t3ROf<=t*73F1Tw@q56@#!T*(!z&YGLvvI|j*z-?Xibgm4WdLc18M zKiOMVP8RFw!rbzu$Fh`e9qRHA@G8&NH3WL@A$6FH?LM=R zwEKd~xn?^hOb(Uz9P)ou;Mod7+qV(`^=I`}f2^u4>$!QjoZ#r^Eot`+zU7Q;lsWR8 zMnCfZ095hVrk35fqy+LUx9Y1S6R5q*+?*K2P&N-MTBma~`Z8_19^&kxz7JtblVYZ5 zyKAYP?NE)zYC+ZII5D~SU29bCY_GX0&5WpVx23=jTdv8mRWXa2x?(zC00d1R7b^2HX}1vKdKJglRo?o5crBKv z)&2pJUCTV1Er7DwYinQrLGwmX;QJ{Pa@Et<%AAkx*Ire#x*_%H=;s*B_9r1Fb4%N*$pRZRX z!pTV1OAdu@UA^1&V009gt@^U%6=o9@h0);Hc5WAoHNB*# ziR5)QL`O}O_q#S8ulXUsq`C^@O*`3KEZ4qoUPqJEuChlHa4wS!Pz!dm0y#Ch`${L$ zV2ov8Aaf?=2N(bo7c2xIl) zE@2$CQJSAk2{|XZaKg4?mT+HPQY3#6!=Up}`$LD}^f-uZ1dV>0w43EBK03;(c!-Xq zv~kl_9X&%>Bq8PUzj-!LrH84W@?@b2=W=tp>@$c}xzBT7mg8}7z;hTc4)YVH9%U;sbIJHeR?qR;R;+d$1 z8ofxnf-*JmqQ1HzqT__S6*@8xBdbcN2W( z+%!kiRJaR+yO>aLz2KBYa+Z=a5pioE23RSvAP6@tc;XbrHajMbl!+P7ilq$AH$Fi) z$(#tbU{XpEmg2anZeD#Ro}m4cfN|iFEIQn1?e|rfCUb3k-aC}FJyYxj?%qVD%Hy*V z*4QrYVf#yw$tkU4g~o|&out^a*bxnduNP(giCnF?9IQ7R2mk_nQE`l*4EAwKr=)Q< zu_|JRXDTb49ULxiS7;YKN|9T#6)MI$$%E0v*YUHI%@zw*=La$?vwhU|4YIO8NeriJJMqW9o#|jTDpna zi6U+sSt)2P-F_%jtthfMd~HnWcRq02Et(Rw(=nhv+qv{veN{1u?rFB27tnaD5=`f@ z;_W-U7H_p!4`-TC(oOY0s`a4rEl+X9R9czKq9;p*;{XEcUDTq)Ww8mPPo;qNl-Uk< zMcj-r=s2zjZgdJy(Fi(?S347$)<+?@AZ%e8Svtm(rI8(h2XRYmBx*&IsE(+N!b?dG zCftH6widf%zgwh=a&=wo+%z}QQa3{=Sav$Rx^^J=u9}B9+(ghiCVMwuis+5UntaS; zm`f^1&_=`JoOJTo5gP&sLgQ-!lp=7@+#{KXD`6tCRvznP_9Yt0-fx+M*o6=s_Oe@G za9cADMmj6%hK-a9axUh*`xwl(AhEPOXWSQ3Q zxf2uT($6Ypp93TS@au&qN3+4s4P;Jr`Vy7au-^(RK+H(YT#bpf4js(eepPy)*o;m> z-p>x3BeyxtuvR)hqkKfW?Mx82i7p4=qby`&yok>iMjOc)gb-Bb1b_kfsVaIo$+3{z zpjxaO0Ygq2YcUqJ!+ToLFYQMPEtSHbcb3fY_??+^%O0PXQ<`2QP5Bd-?2O8b4U$J$ zjeL&e1=WuZ*-TcYqb)IGPU9wlTYqct%5r@Um7>}VtODpdjkl<9A_xG#5_c#|5n6jI z5tbtpKO`;touJrhfCi?aj69kytTcNyLrLosA>v%Ab8TKFDbeAh@{6FS#;cyfCaRLh zeclC>94gOtMpQ91JkznKKCchM*=+Gf2LZhWF49s5S&ehK6Qa!H#2{;$>1HGy4oy$B za;FrGf@vamOy@CKVOX*7#ea(I(`j+$R8$C~VRN}OUfv~18raw`E2C#J=>o?aiPTF8 z4wok~4U$EBLqlYh@*a2iBJhodUKX3tn@O4{(>dZ+2#EFXqQ`cnte+H_w1+fJC%y)S z6^7z;@V&08Ys?M%h1Vq!k7IOIbitG~q5uZ3-~y!zbEQ2H_*^`&)il=S9%z9%W3{hh zY^%7djuYR0TSCQ#dtr)8nsHhF~ zxl%N=I*T{lr`bVG5WLece=ztXV3vXpQ#8&T8Z14fRn$w$jyF|XLO&0}A1NKv*6PCr z8z|x#SR|KsLE?s!7%X8CF_ChxxkJTXXE%Gxoye8XJ|$1FB~z@Cmv&@*-)iU<-qGzx zHYg2&dg-Q${;?yXY=^9y0RZr@O0eT(dueI^02%f!`aCz(lD0tIfV2R36tkD`fR5}S z+yTE;(F+=VLPtb$44VRAXy8(ai-QgMsSyBnjfuy}I>=-i7!R>*h&h=X6Jss0j_G}q zE(ndmO16rii?$07Vs*5YF0jboGLBSi`jKKL8(u9u7h6sXjiLZ}DR$A+j_7j$@w!-` znWI^wze2WD4j~oj_t3_?DVz?B_bGu%A=T9p=#<2kMf|*FyG?5;@2#Dabb!=?6J0M?(y4t_khmyP=Iw4^mceyec%Xj4iz$JrD z(RV%1_bThL0uKKGYDAT;b&mzD_csl`rIc027_(Th(6D`A9ebS@n_F|;KJ{O8 z<9Ih+ZAFfT>u4StEUc^}bF5MHxS7$in+|HA+3s{UvL%&H15=yLD~wq=&{9R(Sz9b% zhYKjGfbimMZNl6XT|IM{2J$&6DC?U$ayVO>nvPwLbxKtB)C|-)ukMl(UpSf8coqrz z+Ge<3E0UvxZv>2_9;&IW%fc~|xcq`L-r%`I_64Un2FQq6H{Js(eA2zmxN-1Vv{8II z07%xzEZ_=?#Au_9rQlo1S)#+-;w~MpL`lP=dvZv?Th&Tak5)E&8TNMCz-!>0gd6A$ z&kgGD`z#*tV2Q*X*;-B1-44Ox5|NUIW{>xPFniY=t42GqC5Wd5tFfL#6puCyKE!P< z8uyWzfSw#yr$_X4S(Lh8MuyxT^6!F`r`c>Ck}&%0PA>1P*oYr9?ogI49W^;+kD|7B z{fx4o29o1K6s0~6_gDskN1wrI0;gpuNDYP61;2X4{7CpEzicqtU-c>=)66tC;H2?0 zepr!s%ChoPf1L`24$D?d8d*;^Q^YdE+Etqk*y);WI|EzQ{{SSJHr{Dp_Hn&~lUfhP z>yl&D6E^v(Ea>rypDNJchVdi%rFnpc0-AM7x&Qg z6RCY7y?@b^*KFNEoyHj6b^EM8B_AfwRflU&BY%m6ev4WCrNrSS(A7Q~ zTk=fFwD^p&USC3S{M76x;ErnFPrv#yolEN#>;8;!*hGu~xue>=Fo{jA82wh@&7`Rj zS@?b*E4tG)c0HtX33Rwd7JO;ILyJ*wA8>K1bceZzGYgTo`y+MJgib}oM%@-jJiVH0lb$URq%4=w#t zJXY}~Lqyr**;;nni{N+3K52|Xgd9bXg zw;?G_&9u0i*^$%AM-iBnvf^K1pEUMyUZYTb6XMQwrGh37eX~!&6aI-YJoda9Ip_Y0 zJ-D@FZ($|R5=!eaTHx!D`*S3r!^C$+am4*g)BTet=#F?vb*?0ID{yPQoZ;Gd~E->sy&%*0Zj0ppG zhOiHQsD@y>8BHJ}Y2~)3y{Et`MXL)t#lgK285EO0ASKTHEd?vX&*qrCyxy{^{{RMp zYxYK#VCsB1k8^mtcf6agOSd$1@BYC-3}c>954lmRb6eP#K& zGjDK6@bs6OSNw^};r@AF_hju}-q*_SvmKLo04?uPRhvhN)R0X>+;{*y=wwSk`rOKTLzRM*rJWSRbMvRpK-b<54q6TGYCF2psD zynyBL1xY(*BMr+N8*db{#KUNdFA$=%iJLYSM|{EVJO%8M_ej>08yW`v+r6s!Cd-z& z#z~=!aJ!Thb_YikhquKbY=4&Sw~{cc88P9Dr#HD%mS;VrI#b{frzTrGxZk3lo`(d9 z+1-k|cK!(9%6+7y8TNKA^f6Y<{{X=wnj7u~b-}KYeurYJzEu4PNXTK)x_)Aw24X`P z!UMSv)k?b_pn-*ycvJz$d6C8FNp_yF#OR}ykTZi=z$~hxIefmX+3HJGA5+j^_0p1E z1KPpG+7D`RxK=}+S>cEd1=+p|xrpsMVh-)@fR+^W@P(a$~G88Nq^xjt%&jNa`b zWjQZ1G)k)6CWVK$sWVDHyI}oP6@Jr1_T_?@HG_6zqNAFp$Tc?VkW7+0xJevO1<_87 zBfg|1@pmm!W79s_T*eMf7xYwXig`JPXcb-=y2rcB(Q-KxM;6i7A6tBh+u?x-5{kM1Yo%>e_Ms zVxJSzUq?GZQ>dbQIhh{DkBWX@qr7S#-ik(Z2C6aouPx&#cSoberF%KA zQn}kx@PWE;OBGSHcWI*|_*IHOqwGlAG}w3@Yhxd(a$SR~jhB+M3TPkw^GWz6=V|S5 z@o8MLYUO zNDsuI@r$Bu1jG{7xcDWJEr2XH>a7&m=EhUWf2%9yw+GdW zdw@k$ZEnVf4$o%pdAg;@JWWoJe&5NRY2QdcZ{((BoY;0`mEOux_YKv)4X@cj``w#_ zkMZ2SQMMZ={npLH{{Wu#xr)C-_Wn)BskaULeP5+N7S`rsI1rEhmRQ92R~dp5@eqR%)XWtUE28{xAIX%46oF*-+tuebQ52`%MPVn zM$zoNQs##Uk>GHapr3+reY3y|e#>X5kMX_d^;?fqq+hYPv*`!z{FMe_jwg8k05y}* zVS_LQ?f9!*AJPRaHk9}_7{GWK@9{{f`bL;Zni@PdGB`GwpQySVO3&r@2NJ7xQTHJk zg3QsGta%5BjFfF2%+^+)?DW-Ak-sEH%1Hex<3}a)yNLnm{KS+4v_4L}kcjYHU zGgo~Vulf}{L+F?1{RTs^*tP8JF1JoSnMAi_F=miP8Ch@PP{(g^$2!KgupSqAPD!>n zk?l;vd z!mh2dQp3jI6r22(Np|X}Gqa|rr-8q8v4q>pa)-n=ryP`vPdFSh41oI(6VE)LG&DBf zg|^L2(5E)ZK}^(iWsE3 z3G+)h)c3e8@UmVS4pe9#TupnlJP<9Q0XA_tAL|$Sl|*(oINalZ1lYyuAAbF=il&e< z7q?Zv)*fgs389Fm)e%;T$`IHQm`O(K^RmYzWPnpzI=e|kQm zsnu-lT5j>xW=zE9um$eFij%^zd0~(-?*=k??kQaq^#i1vzf(lREhIgx!Xn+-On@Jn zeXpluiF9<$ZvomWeN4GPm4w*!NNr(~KIrdwS#AP-NxC<8xp^|sIlbtqug|rA%?Bx< zcXl_^H~F4_RaT0wS10Eqe z1K<`r=`s(#QoSMW)$TeXu(X#z@F|+x_P%F#StG5;)I0P;&9v1G*3CEn0DR(pD=qYw z1Ei#Rr5TI0bwIz0D-TU=6g+5CG^4dOU|0v6^N^p_HtnaY0{L>DgoE)}!Kouo*xRD( zDuYeT?>#j~gHN&@Qm5F`8?}|EgNW+#Z|_PQU0WOby`y|3aQ#+l>a4_CXMyIp>V{wU zuP3V93UB2`;M44CAGS4*7cO(WWp~X#7T(uJayKtqzW)Ffl=?$J{p;5R;2Vh#u?||3 z`A|7}PfHrv+j^H9$YUq2Q}GGYwyl+!W`>GE`Js&diy;yMb&!uF>~0pgke?(T5{vmk z%F^hKT0OC$i1)DiH#ej#2!pn`aT;N?W5OogWX+Amw=!jFTyWUj$`6{{r5@hqcuGvw zq1$XVhg>d|{{ZlURJ(14xc>mzRHObIR$&xDr!ad@fkZYFA?}v=-sp1FyL+&5RJ(hc zG21*f?IE}|5j@=s59peQYfMsSr^B%5&o**Y(mO5lxg*0-T>`MQ zqos?in|WU57Ko|Y)reuX7{2)Gi2hOXRmiB_Yl3aRiYhul33G|Jh)C&BUnr{$LO&`d z#@6EQAiAS5{uJTbEMBArzNO%PO+E@T7-`v*lXK8rNzxdFyC~fA?oggZv{5-zq_t5b zu)~SeJ*^dU8}pE>?h}W|+HvUS5EkxJ3!ZPx`tMH?wpg=5gTO~ zl8`zg1IF?S$uS9sXy%TZ&H(-6qOVd!UhplVu(t41*l5+$2;4WZA&S@0yf!*9AZQwrclant zUMzTo)Y5=S-h5Ebs4g~bg0y0ytR`cxan$8D9MpXB<~FsFwKdr(-4|vvAH8)7`2zuS zy{c6wZ4j;ZCYiK&gGGfzzS5H>EnPmYe2(%}udQB9!3oCV(vQ5nr8u2Y#K*Z{W+PU^ zzCe0};Hs^YjFwfmVvlzBFEt(e&y=zeBB%OR+&2Kk;pnRm6Umwb-%daSzj(ZL?OQrGGXl!tJ5PF1C!4;z500x2vreO2)CIQ{FTYX}Y0e(mEh&-BADYUt+NY7z zs^@Ovcdenhh?c3{hFnNJkWMpz)Q9GcgJ{FMJ`BO~CY8v~r#yVsQwhNA$ z`6a~Hms-Ufh}=@kCOc6h^JZ>g$)PxL`nelt%6yb8bhA7Q0uj*R(z_@i4-%bHU4~UI z7|ddz19=>8)h(6sp7wL(oTI?ykbqk9PU|%4tTbCASs@K=EVtzT?fs}qRN>}n0o6-# z9-gLm`mQ?jQxw^zvuxs2-4@CiZRt}{(u?XPloxqfPE%6gG$QVOMRbFBjAduEs81J9 zXlwf%)oZSrm6}=!9Pk`0FS~hGS`C)OWoUd9?{hi4hJ1BLw`gLgX&7}41QKvByWRRA z-Hzk4%3k{1qwuk%pnDe`O4aA(Ryka%&ucH={eLB|4cR(o(}P?InZv!2)&WKBjs;0U zh6$-ln(NyuoqMQu<2aiSXlq;v?&j3_YEu}_Jylr`XH_q0eKLkvc&JjPTRM|^5sezk zqHb43KDrS+PX%W1CxpGTCn6a-K>n7s8EeZpiYV;$FkmSoUcvX!bL_ zhUVA0<(xp#O;zC4FiKkBEx?kH>^@M@nel_s$`>n@5n-nK>Ee}O)1mh%DDbyD+c*h- zRpdIz-8k5-aM=<60CaKDdLw-e!kLc=4j1hOx-_Ro6xQFo)8s*~wrtJzr{kd4)(6jWHwtJs1)0**IyaJCxp|Q~Ih-0CZ`Cghr7+A%E%x0T zBsZ8?8zFObBbsGwq!E-SFJfBf>P74kW|_ivlWTNNO8|DF*Z3^tVzG@gTd+8vVyo1$ zPT1TKR)LCAmqY`3TSZl_qj;OuC)k=Y16h@bFvp$6bTRkV_fmq9{m>XkQdf;6q1u73 zRM!`VM&xz4r7{6a9nCHX zifj^D4vnxwFv=)_Y#QmPUhJCP-Q{O)1m>GRPc&V|V@Qp8u?r-JWecUy|J z$%H~RX>ITLlW}2@+<}u$r5wh(D}9cZ%W}9PI$5UNRhiT@>~-AWEy*lLMArJn-9TF~ zZi?$E-JxLDnld_9+V&*$Qt>{GukNwt-Ba;Z#vNwEo2Agq!*V7pI!BO82powk7Q4K6by z&)PViM)q4PX3EP`GY1~hRTpndOF&8v}eGuW?84HY6 zRPA-!2_uMcM6(icAV%QEjt$ssAr8=Zqp7Q6be3!MQqP_ew16LVq7Nl;spjRmN=qwsJ7GNv%RH~he!_V?@{%WNmDs%j#lz2 z0vt`ISG}&W8?JXM?lrbGP7>3?#%p%=oK+!7Mco=o6n-s$wx+g8F$p3vhepF-8@MQD z&Kh(&b3s05Na9>g&l{xW915$I-iMz$&Q9oZSqq$O?_x_{ZK6(1cNFYkUdSu58n!tL zb8kn%L?5fSCZS_(yHzzy4rU83if~M=MG-!0-7hPPeIuW zxL*8KvRh%J6@5tRN;4A%V`mlWo3`K^*-APR;9Od4Hz@j+n{ErH8(2pxy$Gqb7ebc1 zQV8~`DXDWsn)XrkF_^qjk*o(H&Q15K*3#l87WNDWGlrya3dvVIP*c81Mo4wjh9kHJc}orAt}L3c(pj%bHdAxH0FuMGDBiZ0d(Mg ziEO93Utw*)dlgh9QCu9zxFjgC7M`d{FkJeUD`b#uAYmoOmt7JRNT<`)TkdYQraCQx0&r*e)OcsE4r zVm8nPeRj!EzyX&SqPgI}=YEKmV0%9Vd8NeXk^G{k{Ygxqzp0F}zx@;jLgS`Or`bgJ z5#1{P00etC3vOv^Y92onfq^Z!&KF8gVT94Qk(K4-C(HI@C-!9~EFNjC*+%CcO&9nn zY8{Wns%&GVoQ@>6%4%J)o->vSVuicD!Vuqmlyz3qRZk{o%~2sgjzSU3F35BpO|dl} z_lYT>vD8Z_9_{GZHg^lBllx992AkUpONy$$7Ln%pS`S@QQex6x&X9x6qRVBM)#$&Q zn_lJe2ViuM{{U5}t!TcoxJEDFosS=;rxG#>96k_t5(~)o=(Cqsm%F_3d`fY(*>rIl zq;#PC&A-XA+|XUMH4lVDDqPl$!WIU67fvo}n$t7iBip#h;%umN)zSBxBc0PKA()hL z?-Cz0I8@fiylF?Fb#5izA?2gqy6WKXT0(gORXghlUf%3)Y5pbF)i8s<+Kxz+;K!kF zj=ixQ!NUBMj5w%!K_1KRRA$w(+Oyc?RVE`8Zv!B)O6jI7=q9dQDluUsW+>R!lj}R5 zf{%3f-+T2zzNw|Tn!)0jePy=+b)!iwkvKsui6Zjht%&MVMdb7YiV`ZyDiTJS*;VtC4)`6w{o0)WUdD*$hMo*ReXosB1%HX&zdmp@rbzAtSDe5*qoOZd^#< zq^av=HUzl%9m`l2EHv%3cMR8}M6YvQsP`{na!DgkcQ%3S#}+!57tzcD_b*X*)_ZC82Tc8T1J!73Ch-HE3t`#ns z@V%^ciQRCin2eQFwFQ#8#O^i%7jV{rqMQrj%=)3UYqSRkE=&IVr4q_(zV zZe?)rO<~pwkOYU_B=o-b2mY!bH&UL?; zOLi_}Kn@4Qs0<;-Nw0IKHDJgc8Qp%VSdTz?Lo9nqz(ZaJ*gBxt^tuBP4!g348yhID z-OX{#;twk$VZN|iTPVgD@$A+ zdf$46shXaWTNqsMKNFIjJBxHT98goS)eS5Txf_v4$Z^=jY;Z7Bv8S%9ljD;UX60qL zv?v;=rKf$I2t$ca5}PUxWQo~vikTWi<#8LW$qwr0=x*!vH&E`BgS~sTCyF))5fZMg zapVeZ)lRu3#^-vB?G5!Su9R~|$Q)4fQc1pJ?Ob%^>Pn3=>bfL%1+Fg?knNqo=#|@F z*3-d92_M9OonZv(<$|hQl%towYog+)ER+W3aN%VW1Hg8J+Kgk~9dcX(4Hk^9TB4E9 zg70u{m(-;RSjMzTD~NdjmW>)Yo2aOq=hNA)Ik;+DT}caczcfoe&JGb<2xGjjgI~Zrb@>o1K>$*NghYUP`fu>1Z=sP#H@jvf0FNRX4bj zN!IGgjE9O~9mPSm0#8IG7J0bIHza{92u{YszRBTEL9j%{P2IiLP-h~G2kdS&-j@^t z==kZwn_;~Z1t<>HyVYY%bqne-1wP`@?o(KN58RbWOQz*Hhf<8-5vA4A%C1o%${H8g z7ai(aIwI!S*o1vGGMpb~He_>38p#=i@ChH4xrMl>n&=$TKz+whw2I6+ zjFLkGN&F>Nu}m(UHN~%YwRXa)o`s=VGb`Hqm&ZyVum#4~REIQ+jq2~V@xs#Ejc>hE zBn@cpojR|Z@jJTC=bekbeNBoMVZ3noiR&TxrAr}4qSEyj-o$#F$hfAL5C zDYjAIxx3WWK!1oo=~ZUR@uzHksbWX}0Jc8Wv&XP*yB4?5V0msSYW#aa{{Tu%6u1yQ z`dXvYwIB4Vu$lXe=h~H={{XgkwQTY1YoggDJ_ft>R`K2nL z*+Y$;Grgn)5y4<@nX#_93BBF1EE%_zSwUfvOT81~HV*}u+2BIDS-FdZaZs2T(5Qi(_c;Rp(X6?hA&dLM?p| z<9)-P#eiZ}8EtSpmpwjR-LV}3P#v@Q-5|)~ccAMT&gAo|g~=~=olwuLXgXM^o4D~@ z%5SMk>ab>Lly!ExU&%E3_h05vKn>SkEF#w$C3P4aox=S&*0MvY9^*}JM;+xp9^y1NL$)$EA|YrS(<_#WV~mp8>Yrr#>v9mT8uJOpwA%Jeip4OoEr_<{ z<7al^xdzMIdgvzm(YaLFB*{^`bRn2~mySx2_zx;ZRf{p87vozY{%s?3oVJ>u)q zUpdm}nJPC9Ib|9P9!gq4-UJQ&)l<+*+30o=bFPD;DXZ#cU7hlc`}H=?5#{5%uw;L5H zi@@f{+qT|FSgG7d09^2~P(#>hg6+}9=(r&+Xas7ArlEU?%nw6#FG)Z}x7lXRuq72E zzUFNdvy%Q4yjhlOv zEHW&(1UX|a7PnP0_Xfn`P5cyg6%*s50JIMjM65lSWsi9CQfpFgRy90Pns7-sDh8?s zf={rOWqd{8wZhgMR7G5D5*9e|+=?=}UvqCIwDu>Qz}*2R+|gBZ>}b1hegGi^P7b?9 zzA5ZI#~^SHEj9GmsY;%;ogg54Kd#=Q5M+YQ&;h$S4wBfot?3!Uz2$&2B6?<^L#A;DLj8=6Qr zx@#Qsi;2eWD~=#)7ehDzme}vTY@*$aw4Lc~PZ-HMQgw zpfYLoA@O)eqBOGPbx}=(*jcx-xYbdKsS8>S$3+uQL`WUaYGoUoRpWVFhiy3~<+epb zNaMjwicsARf^sNW(l@72Ic$jNs0iXbl!P>`BIIsFv8`y)zU2I1o)!tLk)+PS00Ubz zb1GvEiJ7-K{dWZ)6XIvAT0D`I)zLCQE)R)Psdlsy&RZ5bLdG}@gQb$sdS^s$n|VA` zH`b10&Q{x!T=(=-4$L~;Pc&RI@@qm zifZ?UhhI+=j6$+WBLqB;HI@yj-r$_v^k{z=e=^h6%|bxQjAr~PSw^vqOi5X!K%FbkZh&)isH1Abh3yK1h16E$;zy* zc=g@p?n<^!sn*HJlnX9S^g}lz8L^`#64^;R%6+oCsu7+k_8k@BCRz=T_O7=Su`8{j zU|gjDY^KPeIj3W$&g)-ZsD~p2iZi?uqJ5yc+)q^@4Gr`qUnE*{T-@Pdg+$i}Me1G79aTqwdqf!Uqu;KNSgLvuv9=<>D9eMY5-aVc3;u1}NUv zpTaVQY{T|$SjFd?DLcbSB{@FTQO4Us!fm?h9IimPX4M$N) z=#1jLzP7e{f-EI@a2$sZauiaV+go)~02}GJXuKmgx%MxjO4=Vn+rEHIr6lROI4N(# zUN$KB+FCDRqIt30TplY0HUP>u5;XvO(UkOdkN{2mQ;iz$2>KY@`J14yL7}etl8``I z8yjGB=DO(1ss@?___krla$aD*XX$RL1nw~4?(yCgMy5#_iAeSWMfD2lIkgn81ZX#^ zKWOJXy4%X7G@aPdeMxwl%9ev-9C#JUsB$on4bO@il2=CXK+F{MPxOd|j;OfUR_(C? z=22IB6ojyrySl=JfncYx?+xKaV^{%jGgE*$g<=h}t~5o&;9F-Kjv)o08@ZQyBg2R{)1rDh zH(OGzxYiStnV`^d(L#dI;hRnym6e*%l$Ew4y%Q+vy4qdZ+oF^?2)<)vfcWl8Ya@OE zSx61FDaTQQMb5q{_~dDI#nbJv;q6AlrqJ9pjj-NAk)wr;kPBTk8=Bi++>NG@px({% zO2>+G!fuvEy_!5}$sZUFus2+_CuV6jfTn<0huhkNk|qIg(rl+C zg|4@F4;3FQUB`i~x+4m!s3PtS6EgzVwlRjBHpmJ5+v5nDA z$=FU)?A2Z~vjs?Lamni8jNH2Y0uEea2A<saG*MVa{1&a40u znK-RPgbd_!3cT8&4+OtYw*l-+#xj~G#Y4`narUC=s~XuDa}5rvsp)TcBK8ZhOzYb+ z++pKV&gPzKqBKpEJ-6aGpz3i_*xbe)<8_oKA1R)S*S(O_7q#|v%6f85>NR?4tA~0^ zyD~$_BXC00*Gyx#Xe^NwnmML0>+Tmo&fKJvzmn-p=Y7HEl)2Y)kTp_X`WvDqHq;>} zc_VU#MkJnvKWUnKX1>~4L29lz{03SwK)G(Jq!?|{e9k(Xja&CTd_-+9_}cEN_}c9> zMMFg+j^un8bM3iOoe47Z@bUn>KtsQM0AY;sMKUXJN5w$?K@sQ<9G6^aSnh@zNo+G>ghd78PD<%jBd+fU~53};^042 zbX`YSa-t&WoDpBd^H2!H?KU8D(HodJEZ7WCkpsjeWqwp72Q3PEQ#AlMe zMX^<{<45Nq`6WrQHO%|eeo`Nj+2hr{gnN{*+YQUb zy%pix8;Lc#Q8e`vu>PqH*!)TE?$F$g>Gm@aAe@vf z2k}hXg}BgNP4F(I1+bgjDNiyN?kqmVeurc#`#>Ev8&5|Ud;*jGhpc-;y)lGxWDh^0 z4-o~_E_CC&Ge~JAdxl7yd>V-ubxxmV?*JC1MX*(GZ||z@&6LOWDfjF&alA5Wslhw@ zM@JvTrRM41aXFeP?q)2*Dp`S3Mao72SyySW&1&f8R6yNNO$}Ad93!K5?Q^N@-K_q9crD@{{*|??vPe@tH z(v)r1c$M2TJ3T8655(WXAl^=H=2XM~!_V%+j9@h&W`0(mR%=OH$!gQV+49 zmi#`>-xYfph>^{%dH1B2Cac;$BZ|^FM?0%)JB9YL- z2Mb*LN^E1;fp{*gC!lqgJg8QenVho2)~URW8fS(?=gF&DFRqH6i)~}6qavOdnnvR8 z@d_Hh8=7dyo^o5uHma+P*1AYsTcF`(Y(5V&yvt#5D*piTYD!jZo+mx;RU?0b%^u|y zHZMH-$tfrEI_jGxNiOd>*6~k6K5?NVm6v>I!aF0{h8_^|#>J=8ONjelAS{T6Y@+$K zJybRv-qFCi=G;;8Ijq!pXtbiXL2z3I%Tz$hAM8zb)*JOG66b13PSX<5=e?l1{oC+L z<75npAowR^Jz`6Ok^050fY=hC*{$sDcinaFk12GIeVe!sij#tq5>(W;9K^AH$_L3Q z5jBI%tn#xI6*Ur1DRFa-mpl`@M~I5Lg9{!+QScnp39;0YO6#Q+O=wy%P*y~&b+Ox4 zpE9`W_FCCN^HE1GT~`|5d$uaMhWnDo5Mk3O2~pw!ukSDc%|>MaSc9rqaKCxljtgcA zC0Sa+>oK?2ik-qI4|Z(3l}V2lupQ&cT5tvt042WEvYDl^GUtZ0o3468SkN@L#W7Ih zxNhPQRdSmP1Aag$rXti<(qd6XLD|N8@8!W&8&b{3GJh6=qE}}zNEkSfqat9wx3?=R zMdd?VLP{Ezw|R1w!>1xOf>?0Z;6j}U~a7_^py7%yqP zlzgsiZ{F_(El>9Gi|<3$QDzq!9!d(Pq;JpyN5?A;hT#C|TN|uc;5w9a6pj@(aMPmp zDM4lzhF;u?bx>sNNBVOv248wt)*uoZ+xRG1oaVfO-Q3cmkP%=F(`FvXm-@)a3kye> zP#D8xjm7bX*zn4W1;O?@c`m)T8DHw3G*s+tsI5A-Kt{gh=GMwM=%2*sgl;d5q_=vH zYptMYR8zRQT49Z?nb)x?ev>&i%u@}`ym3#*46h?C*3LSQaroUc&9jkVxfe#n#)H^SmENv{);kv6$9=_Kb3GpJ->G^`Bvrcy};z3b46y>VDP)S zK~d*Dz5zK=w)6p=%r&n5I){~-7^MeTf)B-K;yEv@PWBX`44>@X>?rc_>}=6}3&dN0T-Ryn?B$jg z{Fi3i{7L@+_kaiGV?v}hDNpxC{{X_4Dd#`jzx;^u@$4JNvMue~+)<~yRPp2z-M4s~ z+pS!u#>%3~T9f7eMbFl9d|K65 zZW&ndtZg1H3Fxqzi|Sz>E=QJ+VA>svTzh+8lf5Hv);6UxZK{dP;Q>8iX;r4-5)s~- z5#R|=MT3i7)3)E?qsqhSHduBnqi(7s8Azl9{{Y8o<2KS}KlfCI`-y;RprgVf2lubH zMxsU$O<|LH`Q{{V{8q1lB~TfZC6^VxN{ zbt890cJ+}CQlDUDq}8lS)NQ>~j_$Sr;Y`7Aa_HM?(;ik#7=I*f9{_`H9{{#WRy)X) zWj-4#-S!j%`C%(^^p@!f{{RwRQMmTQ5-z-TwI$z`*7zr6$MI^`+DFz&)z2uHu==n| za<(3M+}00yUNFYVa@5z-#yN>|`Moh7nDxzoOh0>w5ZeM{OuG=)Ca$l^Omnh!yB z;Wir#t`Dtzyt^n*#l$Avi(&DL$4fMh2Yj;FA1060HdWDoe zb}@T)aoqX2M>}Zy`JsZA58;nDz1Kobw3SW;uB((Z>6Na^Jp>7p0h=e)R&cnX4XN4r6#{MWrMLPlq4(QXuahnHPhcYZlhX4*zZ zTe!y1(wV5nC7t2ZR5|V(T=G>x@;%nMK4m)>GEr2Zx(9<5sbu=X=epx_$v+ia@)LbM z>H?kLXgjypdY1&{Zy=e)2~sI&JZc_EE^#SwPD8J95ar-+9>vx?6WtmmXiHrNx^m%T za)8AiSxEgx?dr~UmZA1=h+(6@Cg3xMlvni|@oo5d)RJ9xc>w9w5Fe!j}3>lgU z;-zRQ$#XylF+8^j!h!OCx8_yHT4)9y9T~7{6pEcdc zDP7zc+0Gr^d2UYzxSKQ(JW{(7=7$hHi4J4q*4lDGyN35V;L~=xRxp8cbHPSql|~9l z2savlM-+UF2fSOVip1l5z%|aZ2Sm_ABv6SjbDVT>H%+)3*pfOaJW80drzik13(Dyx-KjzIkyqo`^iG!u?XDGAT=9JNckSywc6>; zO~ZvWSds@o6wVz-8%ek~9QZ8cHPH`Cfd&vydE9s_EhRHzfvx>faZ(JV*!9tHu-uCq zcqqBwVs2LjbO$zJVvy=!U)}_x#6;MQEV{^DoM=iJ!I%fpm|O#z59*XDA!*VW{DL{u z$2cJs6#*n1xA99Vg1U(S#Z3rsyBdF&a+HS-r+bB&zlxQ^FiC1$+cW{qTxd3Gnl`l0 zcM-vIRK#0ma975{pHj0&vW95d_~d8>IbE{yQ?NSW{Oqr=!xRk-iGiSU3DK$7Rn5#! z=a&3Jrfc*u`kXtmkb=uNZ@97ws?K3xk5!^=R4jY5GB60#j9dQ?@l1QmGv?rD)6# zl6|;wxGv4AxY2CAe9Z^$-*{i2>N!72G!`Op$W%e_cB6}pg%WWpYHkh;1r=@^!?H(?r;fH5% zk!dA0Qn!N!fn`qG7{dXUVSjDCs7j5hr(m#gdFik8NbvKYnna!!txr7tG&Wc5j4^j9 zDWi>phW`M9BM#ceO(1OS^SZ$uwGz43hyXh3QP^%RJkvC}#D1te60jeI(`#es*l{X3 zv~0}H$do=SjZxwB?tA8DU^D|$>{%@>Q{K=+$UO>6+~FgHj_x>K<#VhmPgZg-3yPMk ze;?W@Dy5Oa_I2zWKSiF^I;GW4b2AfuJOZ;SbTa91B<-m8}@N0n?F5OLaC&WdttjYe8ol zO2lN5I5y3=<`8m1ZF^Z+1V+QEZPX@E;_bmv#WHp)GDCLU1s#dd`b{I)O^vix=h0cE ztZ65ji<*(mE)oJ1Dp68aeMOZuC1S|kpRN(y!IIqax8k`T6D{oNpkd_c*gsW7`p}*7 zU(F}$5%`xs9wAjyG((0pt+Vk8A<)#1(KaY4!?na{$NXdRS^3039)%eWP8#)TyfHkQPw&-_#J zONxRQ4KX?){8QWXS>M(ln57EvE#4^cmvHfWngp100sjEBD4pR@or?|3-tBcC{{ZZ@ zXMb2E9ep9G*HE`7dlI4__<*zj08S>|?K~H~Ka;)7aX#Z`U0HsHnv^_= zaXLnheWQ=SF3%1c-PoiKKar9jhzfz}+{s<^B-*S=+B&+T$AEbQ*buX0bq{T%sFlN!kgDyRb+KI$y~`$+$Ylh!M7!}j zo0nYpT_oaeAll?8(G#!9C`Zhc+1U1BX1V0)apsWv$wBp#O@NcQu68FivYVlqhR)-y zo%-Fxlhtq%Z*sgN%57t&^tjk_-OVH+apIWdO(bcpxY-dLM2$7)(NE!9KRs0&Dj51` z<96XJ;f_9A)Qmei<~v%fJOjS-llmyS@lMwj)Qaj=8%}fIaqv&JJifKdx(yY#>KK3- z>+tc=ImiA~<1l(rX1frh7W|JtqFy>T6%}63b-~wid63C&>vSB|s9^MfTw*Xdepnor z9R(0}wWqDA&&_T+`}#3>i~5-ZOHAimF;Zd+0jX0k(cz$6)zlYsrvv_F(**!{25P5x zF{AZcm|dAUR8s!{(GXxZ0Y4Qu15i}6VxS~z!Sp@4EZiTJB+J_|`317xOvk+=rv7cE?0LzcgY z#u`}5MIp=DI{i56oP!OLXNP1i8|(tpro$;^cnzh4C>zUuNUC~BU>1rPoBSbjyi>8r z{7+30l~vIOI0+(i9c=;81yK%G_kbsr)`_&T)3SR{NXbYV9YU&4S3|q%_TQwgRi{zQ zTOCv}5}Z@9SCx$bmo$YNP#E!j=X>^c9^x(0r4^96jH=hOBtKr}Rwg zeS+$>(5S;e8)4b|l!GWUGQq0jhhfxHxB*F6_j3;)C1;@7S~uD9K{Ffqr5~VCo-IV? ztB!>|%+8Mol2-v69lqO@ijQTdXbya5G2!!CO&%3TNIu2}gUsqtVkpMu4{GI}E>hU@ zs`#v)ky;s~xFtgf83`Q_l{iD%3y1@v)g_2Emtz=DUC0L*+Fa?q4nG!xQbxRlhCXkh znny(AcI0vJ3HISrJlI0wJQeBqt{)A_orI6U8%OAxcGS{0NZRKS&qVxLy^(Hew7jq@ z)LT1KHl{Ms;H)%TGZPvN#GZ+_ZAoxFlq8fm$7*Gk(n#2Wt8qS#$Z6ZV?4ttSd(~E=uKI!ki><79(XKb;b=4d^dZV@&NIlj60DVg} zemh$`GSpU0BY8SS4=x~FMrVh4lVt!*lF93z1V>_6&~-ja>YQVuIu$s>M2SaY3}Eh; zIP^LuU5PlGhFaWIcVutg(&tSR?St5kP}oIm@>IrSB)i$ zVeNITY{!XK8cHU>8>uIr;HvS}I1n$nSZFZ_WQYeFJ-TXA$|F4sE*|DF)^q)db+a20 z5YwL(4NF}UrwzM9xN3^4khEMsPD`JK@m%L{_^uA<<(1vyN{hrQV}<3H?+ysq>ST^r z9UY+j&YV*{!ePUN$)kk>W6?gbeW z1w}KM_>OPH0y2h=JWLr?gAK!78O<;aA;JU4rQEag8+;cnTysk49Mf`2?9QKph+GVDZsJ0&Z9WX> zTqbdEd+zaD9?*<3ox(~R27zt+&qbDP6-)*Rn)yD^T(k{Jwi3Lm>tIux%$b3)>3yd} znAiy8Waw*oFFH3Qt|PSbOA>Tl4IC1KMYkr@WSEfqoyl2<;FQogC3zx-NJewWuID_H zjqpkh=$gYM#@gae(tl8W6;==RjHdaz%`OWe=KDY z2FwS$Na_!SWTD1L*`iZKPT~)islf>+y4wz_{{SAzOhj>mU zzeKMQ1-lW~rnhU<&4e$_!s%_NI+8t$Om@fALM;3_k#v%Fx8hUrY=ot^4a3JrRWtsi zyv#d;nWVM4GZw<@TEq^4JSlX!W3=M5d3JheeSfOjI#e|L7hu`VQ_S`=XaO+UjxrmwKAeOAC z(>~200xUP9TPeyfe;cb9z$PyMI)logmiVWgvvo}u$wu%$4NDK|k=KWD9eRDr*pnp0zx_^V4WTB{6{#15v& zm^ON{L!?XtmTUg1a>YdEc(^X8s>4YjBJK?*sy;eWT1(z+I&W6GeVCXZv~xi5QZVe? z1NV;1d@i9ZQaP$)q1>d1pom?p&hEOSaQLd_cr^6p_ulOXv0a&lQB#3-w0{+VPWoLO z11ORv+k;m;t7h38(#Gb}F}NNRM_Cg~s-8Vilm{mmQ>}euzR~s&hRWcrq62A#rMr`UA$tffocoa-nW8G^u>q4j5`2#?i z^zG&v%atojg+op(9QPiU3bMs^I;Y}EY-f~vJv5|Qk3B4`R2wcujIC<`^D5XQjxB5l zR}{?HjVw3qCuR8qbs1Kajw$16)Y2kpC?Td`Ye3VYGriM)tgmfte3a(V%ajc*bw;YK zk>l+pC!)FSJ{<#MeDxIo%Sc{Y_F6Rio zw)X0{$qQY)YsoFG09#SaB=%qtX4u~P1Z)%=2x%IVsvudmx5Y6?K?S5-qZ= zqE5z4(sJlEPDEVCf(ZwrKNQ0y!|L4|fi3%{$Z-q6+*e{r>~tWiDGXIny`Z?0t<}Zh z)6%uuw7r!|nw~<_xEtMDFhcj!WRrN`WjV&$f?dG`E|Sg}|4-*WC@S(y-P zo&gha#Vc4=3)Du2pn&H1%#SE_WMlO**oc2VNMM4>B+D zRZx!Sp7P&BLQ~-{c>$~>ok;1pqqPakAgZliO%=9aIpbYzO->V=oedwjN8FJC^dc9tvA%sXmKcGX=OZNy;?tA z>BizDOLrS|3WrCDxJ=M@6+c)5DI%$j+B(-9ul-j$r5pa{cy!}((E651OCzFgUL=x$ zgB{SZ&CKTbD)TDkoQR98-T)|y=|wG5uH$R)O$;=4XF7vzr@1?eV{^{uFt{;;?vI+O z;&Zecn;Y;%RN_#%x(k&9iBz^y2-4gaR8(oL2aIx*w#E6H68km-tHOLx$;hW0Lm}Y2 zRTtO_l)D49k^Up>O3N7g$JmM(d+eN#4td~8Y^iiiaHfochsvjjpXlQ zajfW7q-@1)>>}5xL^EV@0^Hq;f6R_#I--|Sq}Fsx5uQrb`zu|jF@StRSlLSQ+xA2J z$mc_`WowE3*R-d9Wot!=hhI%Fkpc|yAd%gu19@JqW zqyGS)P@fmbf96%_unCE~ntTxrz@7J4ZlK{0>ttOhTUPyF@=8mzxNE<0fnl+pd0c)a zJqFkq8EB|5){b6|)A0&2ECuKHjVHqC=Is2J{{UBc9Lq{vl*M%EY{Q?n)h-6q_t2+gxgAX;mXf z~-CtM-N0i~fhOLDb5z$+9yWsck)N@$I>Rw3#>FP< zy8PCL&4Q}Te$obYx%nqX!AR4Y^)%zD1uMo=?kmU8UooAw7qoYakBI?3$hjSQ5>|}e zjG>GI{6(Pgvq#Ml2F_q?9qHzk38@unBlO|uFcW5MZpxswhSf5 zugPf4p~30M?6At2+&=J$$KzyIq!^qXM}j;aWHvITELHYICE_>o$JawYrNS`^hTbZ9 z9j@@hr<$6k**rz|Y`Iw8Zj%211!I<{9hdhMP(lwE$O-tNq@&s@jU=p&A^8oy0ZrDE z>X8{@>PxIwxXnJv_G0tZ29L@ z1M1ECH6~>DnHf}1%~#O{NsHkWgX~9Wcx7$v=1_GTT5o76o8tFh;FjHxWyWf!5z&L@ zmHR748^MyL_VNoZ3g1vJS#H}SrrJvPgOsu{`CIcr)MC}o1GK1pj||Gk^(GL;{iN5o zIfCP&+3Iojrl0wVDU{*z#CX-Dys-8uPpg_)2<;qv6ZF*8l@0v_KAKqSLCL_p}?!<%ba#&^A#&kv%#bprrp7!>Mo;f zCAZkqv<_}488(`>SAPAm^k_+vqa^9#XW18cC}M@WT==Cp(?_d}W5nvH4aI6DxqU>A zJr{i}$zhf5JlslN3+l=8Q@_O`^DZSz3oc)A`BiqmY~qGTGM=2i++1`~_>DlOpv?Bh z-8hv|lByS*yfN1|IYIqZsA1H#W$@S-OIa(HijA|bc{vPG1JD&8F45OdaQe5~@~B4E z)3a7nu%5c7qQt|lBZe?fCa$goR>iGkJ2Hp$P z;*v3z?K{hI_ZC=ZEeYB2YRlwda6JWpO$w^UoY~|u9z2zGmC}+s#FNC7WL2Gmdub#V z91gcCj)j)Px*2vv;As##rntIC0dNbI4E9akHPJhTTm-FxdvLEe0BwSjs4h5cVEO}it1 z1a=<;taY5rc_CYh$ZdBYB8;QNoacd<`%rN+l&!FP#fZ2eC$a^k+UdD#$vWKmr3^^M z%{M6I7vcqyCpZ%8_$G`*H@Z%!^I4s@ZN1BltS5LE>txm%>;bUJoD$Q1h}OdAHsla_ zSvl!e+|Un#U!^_&0DDBO3#&N6h_jtz)lO2d zt+WbgCd_`ofy~)I4j&`iV%B+(pRs=$L*tw*}h8*TPz_5QZk)@&_H#3=H zkr*;Iz!Kzr1`}UWK#B+3@o-Y9QCn6@rAse~*pZ{dTPt$1Mh_yLb{7;bv*ZMxL0RxT z2+~u7RPbaxxFef{QwcjmcOCk#K2??XJc;8{lS$}j81~FTQA%9ld+sHwfl-80(#hG$ zBlO*%TjtY3*r*O$0J3eOf}UDwq?OrnjrU#nEKHt{W6;3jWmY<}y%ZENixTG_6(@$t zPYkvZ=yzr+r%fczBU^<(h0;qS+HYb`ofo50QMRn~Wg91%mI&q{?lHw1I^{^JsFl$Z zp56w^LJTuAEax5D!tNn~#~mJk}~T6)q1qaU^v>is!mV3s?m8 zbu*Y=;6Whq(L__WSIqM3WxoQw2+dgSj8h=-%J&M_i>P@k-NdDB3&U{_sP{24Q zl}3*?wCyGw>c<^!k|%L>?`0b5mW>0F4N=m`IHWkF!>4t%ew`ATFdCAfduJ2BMEhI_ z-m<6|)qM%ZS2O?tu?oXaiaJ^j%#&fvsxw?0#CfUw4q*3oZy*9J6(c*H6sLU|HA#pD zmpHn>C|pjs%~V`Tu}#-F!8D9%Id7tmk@@2%c~@05B|k7%q*;ZaZ{iTMZ@unQaL!2T z7iD-N?$n^=bS3Ypu79G;Nw3QKXjp&VPQm)4slJNqo$E4%zxalqg3qV=M8{<}HDq`+ zCckg7`)P>I-vedixK@8MI-IM0OVOHt`Y@6*J6fzhKNpvL0y>ZA#xQQ|PKz%#vELxE zY4*Dqoc*RDP=B1E&OeAd)~lTO$bM^Zsz0CD7OMXM?t2@&n*)SWk?9z`VE#x)>Qvqd zuy{RN3*oPtrHwW@8o^`Bd0#bsX;SxH^%5T!60I9Cwz!@L6WK*n_`~&Z4$%1}o*tuL z4D}XtYRW$aO@Cy43g4srEspNCrNjJ2kI@$f#9IwcsQv zU+E(mBF7S|zxahmm9F=Z&hS{b{{VCRo6qcK5*=|&NaX1pie}2-%^5}?ANk$$Up3V2 zopnRAO;J%HJIkI^ZQ7cv==nd)R^;lxoIj^mlGdyK{!gVHvX~nVXVS5wtd0ZXo0A3E z)->jhm4lycZ~2$a&e}@1{rP>jUO4?xZ+ktugn25TTMF>n-DG5^{IP#U^N*&?Y)w!A^Wc=~*&F`=d(F@VzfVtg#r-BPd9&!lwHrFYe@KJM zBl1lhxWIt0l0rW$dVE*U9-hbO`JZnE<6j6mNg?nyTlDC6W?m6bt7p(hZg3EE?Qi}N z`K~)}FpQ+NvpdKv0hp8*xpcsuWhmki6Wnx`-Fu_@d_Lj*CXd|RoAiZ-hY{Gk4tauj z{UBju?#xe;!9`po8@>J-FE+idb%W$+9ITJ&@DJ%!A4lqMvF+{<;dgf6^k_e-QQN#4 z_PtN}N6lc2s}|B%k0iEKx##!p9!P$l0bhjC`WzG(9z%Yg;V!wAot(mnVWB`T;Ag%i1YybRTYhGu0x1KM;?0yvEJ1Doh8t2 zM-H{hTK@oUFda=S6AdwMY0Leo(btDAp%m87I=NVFl&JB6?AO6by$ zLUN~vhS5Q=V|a|i?J8z-d`dcuVl|g^PLa(U^<1XGF)FD65SU}OnY+H_aKdokLZj@R zAuc+S-2&pRRuR$(95w4xroJ0orWvY3i%TZWduR$w@2X|Hc&1}c;%>A!;bdTCXum!v zJbAId3!An!;xElfVlL9L7~>O?Sxn-w^R#Q4)=*WGO6O<|x!p@*k1UM1lWT5OStQLR z?{78gLN`}EygY8M79R8KNXCy8^l!V{nr#OlqgeBMcYKqXoEiw%!83u-;#e%g#*$uo z9TMVm8u8o${Pj_co!yYJ>tyJAfCrDYbGVRjWYlvy)@cIca7{@xjUc(!)iFUGO!8%8 zTzmqsN3#`mLA#rMbt)2Zi`3?vp-ucusBJB2n8gflak0CsqL|5E?&DnV4OX8^ur%Us z*0yr<_*g8Ctl4^KASQ|+(mkq+83-(~0`VFUN)gn|A;W4MF7c3cM7B8Tv^u+rm#%^F zx$HLt8xW%^8Cj7M3alPBxDRFCyk0EtP#V2zpC#lywIrlfY=C~Yhqjct^Ai^squxw5M^U+Dt zY@KCE@0_EeYuj^5agOY+mlliI-FdR~b=A^6iFp0xLm1m`Dd_e(7)7Odb@kAt;zw(f zb?K$KN9_(>Nzr+M(l`dV+mf?ln=~RV^nBO8rE?F!%N#pX<#?WA;f|Pqo_|gnT_(BbkTRS zaAC^V9|fF&aC%u>*X6ysBTIPSR6KN&yL%3*Uk;#}Vj~ce?pC^#rjeUVP$n?E_leVz zdLfn8Amu#Ork7-K2mbOd!M}=U1`m3Wi)CpbfA-8qAb5z{Tz^2JEJPPmo>;0&OW*d? zsz!1FX5AAEP9zY*Z^p{mPlw>J7Y8QnNd2+g=EMWd4udz%+v@tXbxY%%ww)i5U+8AZhHirKI z66Kvry%mlMxQs*cs~y93W{R4`he+B5@&YnATa-;2g89i8<2R8gb z;-IOUSoh^)BzWWjRgN7v-sep`1ZM>-)-8KQiHgu;iNG*#Jr-@WG%l-P5xkZ(?i^Nu zM}StQ;|rXrdt0Ino|OK&wu)XzX;-#L~j6CS+J<4);7ZD2v_Uan|JWf>>SSt)eMNbPuSrb6; zQ9Db9vRW6u7@2mBY%Emmv|S(;xDjwgmo#S6x#vQxqa`btHakSvDV%t??kW=tq%DS5 z%ck?qUAFd=LtG+zO9Q*j94x6g{vgaN0-3~-E5=s**SkheQAgI%shX0Pb0gX*#F(W; zj+u>=9LCz*r>iPENu2&9-3IyY({-v%U7k&8sY$bA6LN->_XW5KWO)*i~B-BA}g0ki+VWCJqOBthQ$Y0NjtIl!r9j z=&w$-*?LvAEfsEa#%=dOXSLh8KG{218hRm!yO(rjtJ1#3Dho-_qcYrcL02;- zSAn?~PVppdKNMi~aUi#|BRg3M-dA(H{p8SLJkyPg@?08>n__`?G~-F;lE}5yI@s)) zCOlIX!g!QiP7NNms8%yn^F@^-p=dh>#WO*zyAOzn~1{2OMxU5qf+iJLJt7Fa&$yI zMieGLh}m>a&BUR=uw5GQD_A+${{UzWt#qLAz`E(7rMIO4bA3^o}#juA)Rd zP?9c0m+8416+7NPc5TgBsdjS^m|2vimi%Nk{?#`@v6Yh$9_)=9!&*uCC{Dd7Zz3|q zLOZgZ6&G+^Sam3tQ4QyRMSP~%=p&KBs;9U)y{)&!6-|TSlyCca#Gr28^ZEtPEOkGY z?B{sc^wgolND1z-&OQoOj|8op_bEsm-J-ZR8N#Tuih5@|#N{5qgp+IE9mO>)O$U>1 zed=!!T3%TCl6C7(%MVZrU6QVvAZ;6Nb#C+SQ`EZ{SX$}lA*Xn-ZM~`vqiZoYwzf5Z#?cz;H57-u3skG5=jt1!OJlv0WQ+k$m@yn4ozsX zt2Ul)B={$wq?PaE>`buqVlu)~j@B#;$FU3M6*-5F?<6gDBU3zis%CS*;e8OMjp4j} z!a<>XfVnmuP*9~8aK@Yyy0Ht3R5|y#-@@t$M*TlUY^cLSjfg(g$ndL;8rzO)+<2v- z-wLpdY*yliOK&9;9V^{#;R?qV6C=nCX(ytSrP;cv24==U>Qv>4LeCcn^(&NAcJ923 zatdCajxlcTVmYf79?8li23G(%1!AMX>1n_vxttdrOhSE6I<`IwXL7xQ)5yn+k&xbk z-t}*Rg{=j{UP(<5!rM0^Y?^gcK-)FXCCe#hJi4`AS9=eVyb@UK;^C&tt~mwWW{zZ} zIm(vS#SAq8&1lT+3Zdc>7fE4jD{j<@s-2ooA#S!rMS``1SGPBTC*ZpkXrsT62=yyWW3rO%!`_l(bgV6n zrsZ2crem$!NoMqFG7K(-jTvN|o{^85Ze}qjEL2v-2AgLXWX~WZol2Ucz~N@BYe4W< zI!ro;Q26I@@dNZyETqEXv2<<&tb_ClQ^xd1Elg~i>QtbhfrJY=$Ca$7Fw7nG9;8LL z(?V3UD==D7c0k<2j%23oRWm!W!~p0@<(*oy@}_dmwOKEMFY*^nvvh2a?OAya`|7Lv zO@jIhBbp){$2f~ya+7w|+}V&ck2D@3wB4Nt>sY?u?rol4p`-kodU=C3D|*Uojlfa;*CnQm*?T zfwJ@z(ps|;W4b{ZarfCi+AW`@aI`fQJznt7a389KeN3*@xCATYbFZ21AukSZ%^{U3 z%grbLKfz^wBZpJS4JI2$6NeWxy!**sn`veQ7~~L-_Y4fbu~yp)h`Si)9|DPo4y|^v z(@m7}VF3;d7nlx(HRksuX2ztbH+FZE#mG5osvd3*QPCSovzVnwymW*PWU0y>lEj@6 zY|f4`#`=^T=)SK`0yUPgQ)R*oYW!Q#+l}icXtlFsgp>hdJi| z0P3YIT_v#z_>C2!)XyQA`K&JC$)QTpP*zmP(*zP&@4kUvfx@V_8fb{*K}<=sSY#Is zn>R?>qbl;BmM1*1lb`)K0%6z;(YEu*)|0~5MgGrGon$X_x*ZgKM%U9Z_Kp(a%#IJQXc?z_}Y zRc%a8AP(W&sSF}zsrY1D+|ix1^2kn5){b{cJ4|A?Xk2f>X3Uyd4|(nfu86iu=P-7X zW&m#mJHtgCFT?4shKCN;u*d_UyLg-Gk%;Y7u?+OlHz3sJDw#)JPY?mo<^y@+eo6CM zD$Y>W!2@+XZl}OW-o&{oYFEpl8?=*FSC?W~lU&xGF(Dl6E!`>NA%WvSawf zxVsw$P}*3x05_g%B-=`6wpTT*01?T0ICSl1dKBW_EUj3{=YFSp70x+|FA{{sP~mGJ zudRq&m0MD?JSvw?!XEN;UTAZT!t0oKh2Tity+Y3Vkp0fsw!|e8N!+~CaGZ^#l<8k# zlI(Sn-4{;MJQ5>_ke`A(5pud|!pyuQW=>&_%?@5k=QhtnVXnz28^KaCv=gHAsqA`D zd7y@7r|n=Q07yYllE;=6F$8jDAOm6)W8f^QB;R_p<7T9Z$~{Cfh1^1LQ8;j0G&>)& z`EF~;Jd020knNYi8tV#|xN{4Z^#1;gvf=G>A7vA{6u(1o;I>w5p3mC;nQt4W{{UsM zBKr!HZhL|%bo)O*r%!8{3n<^oC{jC;**|0OOt)=wed!I@{1wi(r(4>xsZa0dGMz5w zM%qX43%-c^+RI^f#^C^IhzohHGh=XvIR5}{0QFmqPw(hI5}A=Uj!Ry}chIEdwezwV zLk6|&KG2_~*(?nH`l+r1lgRxR{YojgmJJMf zVxVRD_fNzl$L$7ZSzFa!7ZHqip@&`#kQ7vV1zIA`Ll_SN59kpN z+Kf%_xRy^0%aUy-EhB7YeYp3Qz9IESe0SLF8v$7(OPe$x^6cI6MN(`WhCtexdoVb( z1M#wpIKCqAL6G}XPOPnetuwF6`ux&9c!x-A=;qMPzYb@H8m@ z0NR?_<@-v9<#zBKqNAW;BTanMt)KzGwWp_bHx7(;iCE$2!(>hUZ5~}7?RT56ePv92 zVD}#t7~DPf2eKsp01A3K8A;KH!q!u29l?^C3X-}9XvqEPF;+y^YY<8LzEf)l1L6|C zt(y06)2Tz?H^H^VzO{}dG9hHRs=yz+W%}xs783FSHnNx5{5GAH&2?j1`0c1Vk9w5n z2%=M*BNoWk)(7d?x5y`(R$IA&UgL`djpHReWBEtP8rkrcjUkK2C|r*cx?Jrgi*_SGY9Z|D+Q_fH#3(s7YIbX# zuRebz0(SPOpWR1p1Y9H?w62rY?%#k_jvBm9ns{7H6|^T^fmW7~*D-_mTz^Et!75_a z!R{xM9r8j&wUw-G$Km+tZ|+NafPP40fMZn$ydKfi zJLHIV+*LW20ig2QL=4j zBOvwig1FtJ#GEW;u;*l2#J;E!<{H(a$4|1)Y45*8imi^0Su?tPx_&R{tIfu$T_7@4 zym{F-Q;pOTTH2SE_@q>;-^@uuwrwTsS)tgvD}Ailk3;=Zzhm&GyX_^+IUoL24s2GW z-qXLy#Rn|`t0Yw0eC|Ezr1q5S4O#CqqBwf;-l3))oWNrMIm~UpI7@B?v+WuhfAh6v zrm^l8dz^Zdp*gmMt;y8uHfMyX)}u8ju$`Zb-M)@`7quwCv(UQk6xZQn@>yGIh&f3< z)ZnWlOPd}=4n|#M#dtY&sLe;~90o1UgxiKfejzLNhYO9Q#cu|*1(nS=+3HC=u7GYP z=wEEM=uwY{C)Juh9|P1ocBh1xhdCiEW>3D`#u+%42rm7%ZTwvAW4zds>GR)yfRAP)DupOEBM8 zI4+pssmEk>#+535R2J&zIo;=<73Njkl5eK%P=+|cvx)Et*xws;X!2Ca6|x=4TS4~E zGs(y_=81ulpI!Fo4bxB;O5gyuiY@fghU!QMa0wljgk@KtG_9wmlRU-2-18}HGifmR z%VDYP?sO85RCw`G-&YBmM4*=SRcv<&btJjsYm9f-T>@856?AN*RXX1Uo~5rDucWG) z^kXpK;1pZxrMG>oAahjcv1d~{%q5_H6kK~s3ADh!%GN`JZ4uWRDhkM-kTOAV@;Y5T zPuPaoHvTFNn?*uM9fOB5N_XpaIOd29r>^99QekVMW`cN_3n7FTo$ey`ONG7SX7ze1 z#CX<60v&H8dvRvCT)STtCq<%8n+wFHbvsL<0LSoI#?nY7mCtL#ukKdNTNH@E;2r?7 z&ueCg_Kq@=>*yCv8g|uBOCNxj7xGXRy0YTcF_sn^7Zm{JYuyWr6BV07!NjEJ zbT-p%>0-oc7Yt+_Q_O^w2*te)bePm_yR4oKEq3Jm6yoy@yZ}jf>1v(hlzxft+ngP(w0L~P{%HXH5p}Bch+Ro^iB@ZIU~CyJ zkIv0#H`rygS;^XZhc`o)*qi;Osv7pjb&^xygTc${V!Sl(=QY?U zC;+=^GWaw4uRe;A&eo{g{35b=@yd<@Qan@9V>ON=EOH+*mEfiI1D2_K<~5J2=<*v@ zk2sv0Wd%NKGOT!(ACk@4ieiLXIHvcfn}}ls{_anDPEt$f6=AAgSWn z-=bWuz?*KyI++hbs$&>}HvNSo1-QK34*j932e~672mPBsN0pqqzfpJ%zccmz3*0i{ z#huJHbJfKq)if|0x{{JH`QLK0YMgF)UPI@V$Ei@&*sPhX(VN7lkB7=Fx}b~Zw?Gpvl4qaZzryOP!)f&?$kOX` zV1C~@m&(W^@EWN4oxGMqaWU`&th`;mu_XIbk&x}cl~sVGx(u+CR;d@6_R2{O^Iyj2 z^+HLt6Fd;o1^F~8o@}lFH}y_t7PtTsI0aRPrnW_uDskBs5spsr>$=`cpIA#E)$a2l zSKB8G8=@hR#8?6o@M5|Q;+Dvl6;d>M?mh)@)IHnWeW(U8?$X}WR5a4KfB`97B7}3W zWHPrbO_2|l zB)oOxo&&_Eg;0=Ok$y@PMc)L=YjRJqv4RPZli;(05>Z>CQN@{iq?6#d$&T-WZW&{C zgp!JIj5oT#N}E<77&hibx7TbN~=!7BB#BJ z_j0-q6{Tqb(Sd#_s%*j8WH(Ztio(Hg4sG{H<&l9^ah1wbrHU(p>yf^}5l>u3fH;o2 zCvluUNarRP-0*jXw&-YZnz9Knn1=%+gKzC!H;Y$n?Nf}ScF9;Kj%L`99m#Tdj{|U+ ziwi6b?JD45ZNco!tAH- zxMd#oGX#u;eT*?cKM~*UE7Mjm{{VXF4=|+a$?S;nuIxF7VRZ4l3aaJ_e+wF10=Z!L zJ{-o0&V5ggD&Pjtw;rU>qD*9cp!Fvn5$)(lXS!W4H}8 zkUDr;B6MQp4a$LuO3_aCIf2Y}nXIL2_L8Qy0}z>kgjm?0daU9Vu{t9IUwdlj0lhX7 zjY#P*QjDVuPFH0EXt2W*pFBFob+W5TDH;b^Uu!4LnmIRaRD5?2d{UVr^-A^S2Wy^` zD@m`26jc^GzEiPcW;0+(9F#NMNz`hR98QWEni1ERLeR?dD9pzYx^W3WBQuAARqAPs zPSVOoIT<+X#Z_8GBVJ7HRa4)4lXV$TPDeQ(BLO;D16?4EnLrmpO3Lh&S-E+NFL~qJa=GVDQ}Z$tjHSCWzesUEyO{Z>Wa zg(=+1X8!;TZ6BiG`bn;gS}DT^O9jcqw^Vjw(?^Zkf$)|lF*V@9Z!@!}%UC=p-OXej`&tuLPIAn|+W4FaWO|v^f{UfEw zIl0Xr600)p&19avWQWZ#?Xs#mdQ84WZYuhaywxT17hl;7Ykaa(u<|oE^h;*Q*BvFq zX(JtFKP8_(YG-lQSzLTdZQDgNjqf4xD{-Us1DvDW(%rIAGxxaNE^bY^`6WfOSZLOK zaf63-pVej`UP^c$YUsu$Km4XuSJZBDpF>f82Y|=B7m@xA1NB{I6SLO)OiG~m0J8y# z%zybyZY3`?>PhuuEMyHd;Wz>HaS5&0DpwM~+lb0Hlz%Kd+)XTCIAJc1-ZqLvYt|Z2f0rO4`pThvx5@9|=^;x*Yr3Z>w#H7Ce z0D3lk!OcL?8-QU%-T0eFfeX&T@OIn(0I2EEg`W&YLNzO`#3UMKvN>OH^GML+pxO2` z=c|#&rT$BWn>xe&>xWQQf9cW_`^Do_$kC@yJ;zye$J8xo~3l{aRa)_cKH&sw!ZxRso44c zsH!mRBdXba8(AU#@*DJwFCD3${k?}qfT-hM9`xwU(3wz&V{sXD{idlttxNouKE0`V z{rrc_s*G-NJZy-FmXW^6)fo0S5td)9WN`J|N#^A9Yn;}4ru|e51d=p%sMj69f(mXL z5_4}tO|>p|;sS6=6ZbX*O17t5zKjwus!`-KNK+P_uDIjtFj9!Wdax4APU z&N^70sZ!b@5cih0FDd8tgER3eZ9r#>&?g~;GNZ=Q9nSM*2 zbyq?X#9u@SNkyh&+c%lfOi@v~Yjz^dzVHxkCEm1*%0BM|Z7w4uZ5^A~E%-{|t16P& z0_%ILmkcXsVk{4NH|o2zR8&+n8H_>FKe`D}_8D9ZSkue&t^mb6}LVGj^T>f{pEl@wrM<&^oeLK4&?tw(+l$4x)*| zXZB2aj`|KO+k=YuPNh+G2kkGY8YZW!%G(4)w9*Ef#_{3`5!5nBb#J!8Rb?JBi+X^~ zS1Y$@hJ8$+Z#Pm-pmp`hf8qqx=k56L{- zIxB)@kHp;^(F?B<0?)qlZfWK4S01x#;JVkZU2^#~r zxjx9p-2^FyQ$fL!IP`VDL`-zdX#W6LVe0(f(JQEslEkirJS_uoH&XOWbjHnz?>z<4 zP%*Kc3@q?yzs(lMz}pwp7jzf>RA*BABc65Kh9jYyHQd0_)T5~~IzbWj&YFz^YFDyC zOKJ;Szzq|WRWVb)yCVya6TNcIQ^i#$FfdG8oLcr~mqEc*Z&{f~0_}b( zMqFZ9Au+hJ!;!kGRu>7}_i1x}@k>TBnzcbQHKp|?D`p*&fSd-q?y&Dw$?9Q-O91x{ zrpVYBCVN`sz7heh|PDqlE#GL1Mo=N6S(wmDqJ6~4#C{h7;8+awOiybfIj}9hqjf*r@ z@&MVOb3q!Bz@aNKS5>;w003yNUk&^J!;^I1X&YeX76CRcjpp!gJlvy5}^EQI+Y z9gIdd3;3p=jB;r9CV33nA7%qc!aKL;a*d9Hnji}yW}Iz*iiUPEAsPY)u@5FF`y0pr z>Tb4Zt&MbxPiY$&VCI3)fSYw}AaB2y%~RyW#og z){X}0qljx@k>U`pvPG}c+Bg&ZPD&V=Z>yAa0{;LNQx$K26t>Rdb#ltTV{wsuqs1L8 zRxUbqU1ligj{B8W!m2}GV(ki)l2(UYYE_{-lu}YgIMU~FPP7oZ$eeg083Va5k&GKY zeMPaRgUlxfh)*`1+>+Z)c&e&$D4@n#-l=1D^zf!U)2yW@f5BEXrPy@_3f7C=I@=S) z653B6VsW%~wZSUNgojk*4#tb>6Rj=a0wuJcl%tq((oD{tR zSf?YGu~kAtsIjpi-Nh_Fns8H|e!!O4Ip5f*qQm6(n!rW@=1M4GWvzG* zk1!X?5ireneYB5mx&f+oSH}U(8FRyir0-mh6x*>^hI*#;Af%;pNxOhdO-$0?-h%Fz zD$D4ROu*~qY5YhkmQup|TxsUIV^;eds!lSqE3T!u0KMKpEMbMiM>vn+{;Eb=$QbK} z<^!r3*uZJK8rJkyQk&SWc|DIsgtU>F&hP;l9uXKIUE{=AM$3)7>)IbAG?=^GH@Kd6 zTP0QtOt7+wmNRF|W8rk*^jQA;-Q{Ie^!c6lwCcEOWp=%xvfK$h*f?@~vfJr!G*}k+ zo0N~Nz%~;w4ukqCk(J-=Jg$`4$QnGAxpZz3Yjz=jR!~oxN2N(2ws^;n{{RZKDqIK~ z(UDGC-lYdd!njzR8TWRPqp4YD=Ix>$%~f5sj=hOUlDW+t!@q)8MZE;b%PiFmF0*Bf z5&rz#kB*X0JA`ad(d8<5hFrM7uN*sm`^9(UhWT-UeF^Wo)ny4HgLT1DI!by*njKy=HDe#RM!Pwv=CxQcS6*hAYJu zcXM)-lIV%JSR$6Ppplk0pCva(vv`&;(P&LnHMuE5hhlg(Q3Mwg} zj-|jz?9T6Mqf(tqUKT)=Jue-?L!8hZfM?Ebm9$@EyN8`a4)@u_wZDz++Fj` z=ehT}T&BdG6wQ&ZA;9RXcrVcZ7pQzQI1&YvVZ{j05D{3E;&k+;T=2i5MLorCu{q*w^e7YYSXQdfq8* ztG0k%{Zx5<&Q}9<;Nr(?s%wVipQQ2{t~zqzmnawJP;t4}Cg_j>7dz41*8#*~al4@c z{8S{8%Eu5u3e+<-mUDF~I}pRYA%)ErdElc`lX}Efx{yw(mwsRZOR7z^ko!{=v~$y* z_%=W|IC0T=@|>dV(HTvmLDg)(dDrtzn#S%LsOJHFNxEz}H0Yx1G)dHCjJ0+eARTyjLcM`bCco!g??9KkGk&<*%<2#AOcMf!o+%-t;ZJ@A2 zW+3R40dT#RbsIKz7RmnrFi%G)aT+N0Li_PWM9UpXxK)JY_Z~8nByheSC>8eeIzzeQsrfYhe{{YOSCfSXY zq>l1c(hm!K*9xhIj)^z;B;!gNfB1a29uWTk_HOnk#tSjs5%w>nTZe35cYd}Z)wxIL zh;i6@Xtk;m5y+3wD)DKpp(pqYa9tVmTQEwF5j)t5k7uf!G=;~&E${2Ch>iNNyP?cd z&erTzI|zn^$bU7@#Hw*SQjpG3TSb^Vc5bgr!r|nKi)AXGYerVXF5m3rPWv1lEB
R%yr zFhLmQV27LOYZWn;vPhf!ElvLbKS(9{8M#|XqPpLstVkPrB>w>9Aw18cy1?7XOFMj6 zeo1rvBVzLtddGwvM%-d@i$1bb(=ktK-}w85H`O4v>;(7y%(m*&@0scKO}yO_qJvpgt~y;Mk$T!po9ubWxAqHvVcREMB86#LvHqSJtyQi^}gZ zokm^#f${f5ta~WZFWNTVE6&+y+V9@C+JR;(J9kR;(TYsxYa_u&B>RWs?npOfnU?O5 zStYwS6J22TrUoNXYj&^pr21Uf{o}9B>tvsEe0|9e*|-`m=>T1a0H@9Ci+CvaRm^M* zuOr1Rvq^7R{yCP(KHypVlE((3jCY>vry;}e_{H2W;^3i7k0)3AP1l6{E&>@P+*YOB zpX{*q*futMZir-*QMSy6GJ3R?T5VO2tS{9o(%c5G)hnXkP&hZcEYa3L({U4T(SbYl zh};?nuqv3UBpbf|Yt5bjwSt zW0(%sHaRJRl0j!i;5rhkt*dC`ayqBkUO=-QZ#5ZV)94t^q*jfVF-cF^hr-u5ju%U( zg|0tn!Y$8of@UD$Y{cHlGU9*(-nG=ZXMGB+MDp41+?m8B0faWf+kjP?=^k#8SzhMH zm_K@jtHelYyq6_)(xa6Ur%qdrR7gX+#1Ayfp2O=n-^S`Dde)a;FuH5gfCkRF@Lf5Q zI@W~1djZfB?p$=N-UDyfRS0Vfjq~WZ`SkUT(9VqPP6wBIRjwMA7|HLoK2bP5P!QV>8* zdO>yXdz7xawt-D8iMLk*KJgwlO&Zq&yWe#Yckk6Y)=6vVlZ-X)9BU*lxplk}n>lu_ zB>12ME<4W}A_Jg~JGsQ3NO2jPAMNbzY?SGXfv_ZaARSp1jz$8z79{q9(9x9i+?CW7 zNe|jb`k>iz*;;eH*5;aZM2_6+1HlP1i8?sl4kHVaBzss|2Ytmj+PH;_Blf6H+gn(t zBbmh5XtrP}qIH$8IyJ2T^hP$SK+6}~os=Xl$S&imTdF{C*5N*6wHe71^tAxLQb|V1 zU#3=@0IIF64BQG>t;|Ng)Uw1Y8tISX_0u-(%yOPey)5!M+sc)!{?Nge*S8fGVRKz) zXt=6kR_&6T>vNF@z2hh(qMzE*5!$!dgl$QJP;H>|UZ|;kRGFQ)ZVR4Rj;Nk!q!|r# zrOp|w3U3jH(Pjh_yx6Gn=QpA@jmBQ=fDm-m4nCJcruSr5Xl_@#Qc*aiKVvWr7HL6W zlIW*XI=To$BqZ)jG9E~FT=7dL>gXXc%HhQoPf?ZHYNOeGz$fElm7v-kHche3nme=B zN@GFNKuJMThcUs|uv0NHuV`@8uE@N_QtA%a%UmsT5wy^k9NO7-ILRBVI3l8pPzLox zhOliOAL%d3T+kXWZ^?RE7dBfpqQi(q;qXdq8*mDmq9JT3y?crHhZrB&r% zbt3d<%oSA5jmGVdkAcKlB=EO)=q#uyYPKqqMI=t| z4G{7hlfhc5sT>+Zz#&&^;StI}+_F6T|Qm-9!EVT-Ww>pcdtRx=L zHrBd|r*5LbO<~M;YSO#t$5X1B2W~r5gi7R73Jjl<=lI*K9?`zDJH5?btQRuY|IU_owSErmBRO*yjPc&yD{Kh%&y(Xc`M7y z2FF1?LS9FareeIjnFcE2n-lN3$;QI#>I(Ambdc&OTVXr_x?3QBCFSJyK}iqA6T#3M zbkz_dckuY=yu6tpi)#o5Ua( zt?lN#yoP3GC{+`^$e1NAcXiiZUQD$@w1<0F19DQ?GxGun=DfUvNDe4?)VK?~dS&yu z+HPK6K_mo~!K$Zu0ii(Rvb@+`d3hyAWIx!B4mq_qUGEJ*US3?+X10QvoY^~P8!s;+ zm>JnF% zlU#I$;d>nGo8=3VOdTb%-GO;|B(w)YGa1EWYYZ_rUMd0AAYA0XRpsU8)|+y%2}!XG zsUVv)YL)2^2TKLzn)324O(JcA z_M{tt0OGydIPo`LUQ=>lT8_f^hiY_3Wow(6yTbDF$&D9rnIX?>Xj8F7BW|Zv<>f>( zM;`2_ebS4X8*)HiUQ?57-G(14kEk?U?H=D;0KB}p=ClVMnJy)Ik2^5Dw!U@qM7wExz6(Fq2#4= zBp{1fd3k+q8+MOLC9&ZbJyW6%i_6Q=ibE)&`01Q?1Z_1F&jNgxmzCob%Ek+FLK-PD z+b^|9)6l;wZhV6B^14Y4u`fwOoQ9UUS@4aNt*dYlQohHD7G7RlILt)p#_>7o&BZoe zFmI#TSVQY2WPJvuT SOAP_1_1, @@ -28,12 +15,14 @@ $options = array( 'attachment_type' => BeSimpleSoapHelper::ATTACHMENTS_TYPE_MTOM, 'cache_wsdl' => WSDL_CACHE_NONE, 'classmap' => array( - 'base64Binary' => 'base64Binary', - 'AttachmentRequest' => 'AttachmentRequest', + 'base64Binary' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\base64Binary', + 'AttachmentRequest' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\AttachmentRequest', ), + 'cli_webserver_workaround' => true, // Work around missing header access in PHP cli webserver by setting headers additionally as GET parameters. + 'connection_timeout' => 1, ); -$sc = new BeSimpleSoapClient('MTOM.wsdl', $options); +$sc = new BeSimpleSoapClient('Fixtures/MTOM.wsdl', $options); //var_dump($sc->__getFunctions()); //var_dump($sc->__getTypes()); diff --git a/src/BeSimple/SoapClient/Tests/ServerInterop/MTOMServer.php b/src/BeSimple/SoapClient/Tests/ServerInterop/MTOMServer.php index 5deee38..f86bda5 100644 --- a/src/BeSimple/SoapClient/Tests/ServerInterop/MTOMServer.php +++ b/src/BeSimple/SoapClient/Tests/ServerInterop/MTOMServer.php @@ -1,25 +1,11 @@ SOAP_1_1, @@ -27,8 +13,8 @@ $options = array( 'attachment_type' => BeSimpleSoapHelper::ATTACHMENTS_TYPE_MTOM, 'cache_wsdl' => WSDL_CACHE_NONE, 'classmap' => array( - 'base64Binary' => 'base64Binary', - 'AttachmentRequest' => 'AttachmentRequest', + 'base64Binary' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\base64Binary', + 'AttachmentRequest' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\AttachmentRequest', ), ); @@ -38,16 +24,12 @@ class Mtom { $b64 = $attachment->binaryData; - file_put_contents('test.txt', var_export(array( - $attachment->fileName, - $b64->_, - $b64->contentType - ), true)); + file_put_contents(__DIR__.'/'.$attachment->fileName, $b64->_); - return 'done'; + return 'File saved succesfully.'; } } -$ss = new BeSimpleSoapServer('MTOM.wsdl', $options); +$ss = new BeSimpleSoapServer(__DIR__.'/Fixtures/MTOM.wsdl', $options); $ss->setClass('Mtom'); $ss->handle(); diff --git a/src/BeSimple/SoapClient/Tests/ServerInterop/MtomServerInteropTest.php b/src/BeSimple/SoapClient/Tests/ServerInterop/MtomServerInteropTest.php new file mode 100644 index 0000000..7277bed --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/ServerInterop/MtomServerInteropTest.php @@ -0,0 +1,44 @@ + SOAP_1_1, + 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 + 'attachment_type' => BeSimpleSoapHelper::ATTACHMENTS_TYPE_MTOM, + 'cache_wsdl' => WSDL_CACHE_NONE, + 'classmap' => array( + 'base64Binary' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\base64Binary', + 'AttachmentRequest' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\AttachmentRequest', + ), + 'cli_webserver_workaround' => true, // Work around missing header access in PHP cli webserver by setting headers additionally as GET parameters. + ); + + public function testAttachment() + { + $sc = new BeSimpleSoapClient(__DIR__.'/Fixtures/MTOM.wsdl', $this->options); + + $b64 = new base64Binary(); + $b64->_ = 'This is a test. :)'; + $b64->contentType = 'text/plain'; + + $attachment = new AttachmentRequest(); + $attachment->fileName = 'test123.txt'; + $attachment->binaryData = $b64; + + $this->assertEquals('File saved succesfully.', $sc->attachment($attachment)); + + $fileCreatedByServer = __DIR__.'/'.$attachment->fileName; + $this->assertEquals($b64->_, file_get_contents($fileCreatedByServer)); + unlink($fileCreatedByServer); + } +} \ No newline at end of file diff --git a/src/BeSimple/SoapClient/Tests/ServerInterop/SwAServer.php b/src/BeSimple/SoapClient/Tests/ServerInterop/SwAServer.php new file mode 100644 index 0000000..337a0a0 --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/ServerInterop/SwAServer.php @@ -0,0 +1,49 @@ + SOAP_1_1, + 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 + 'attachment_type' => BeSimpleSoapHelper::ATTACHMENTS_TYPE_SWA, + 'cache_wsdl' => WSDL_CACHE_NONE, + 'classmap' => array( + 'downloadFile' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\downloadFile', + 'downloadFileResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\downloadFileResponse', + 'uploadFile' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\uploadFile', + 'uploadFileResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\uploadFileResponse', + ), +); + +class SwA +{ + public function uploadFile(uploadFile $uploadFile) + { + file_put_contents(__DIR__.'/'.$uploadFile->name, $uploadFile->data); + + $ufr = new uploadFileResponse(); + $ufr->return = 'File saved succesfully.'; + + return $ufr; + } + + public function downloadFile(downloadFile $downloadFile) + { + $dfr = new downloadFileResponse(); + $dfr->data = file_get_contents(__DIR__.'/'.$downloadFile->name); + + return $dfr; + } +} + +$ss = new BeSimpleSoapServer(__DIR__.'/Fixtures/SwA.wsdl', $options); +$ss->setClass('SwA'); +$ss->handle(); diff --git a/src/BeSimple/SoapClient/Tests/ServerInterop/SwaClient.php b/src/BeSimple/SoapClient/Tests/ServerInterop/SwaClient.php new file mode 100644 index 0000000..054fefe --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/ServerInterop/SwaClient.php @@ -0,0 +1,53 @@ + SOAP_1_1, + 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 + 'attachment_type' => BeSimpleSoapHelper::ATTACHMENTS_TYPE_SWA, + 'cache_wsdl' => WSDL_CACHE_NONE, + 'trace' => true, // enables use of the methods SoapClient->__getLastRequest, SoapClient->__getLastRequestHeaders, SoapClient->__getLastResponse and SoapClient->__getLastResponseHeaders + 'classmap' => array( + 'downloadFile' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\downloadFile', + 'downloadFileResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\downloadFileResponse', + 'uploadFile' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\uploadFile', + 'uploadFileResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\uploadFileResponse', + ), + 'cli_webserver_workaround' => true, // Work around missing header access in PHP cli webserver by setting headers additionally as GET parameters. +); + +$sc = new BeSimpleSoapClient(__DIR__.'/Fixtures/SwA.wsdl', $options); + +try { + + $upload = new uploadFile(); + $upload->name = 'upload.txt'; + $upload->data = 'This is a test. :)'; + $result = $sc->uploadFile($upload); + + var_dump($result); + + $download = new downloadFile(); + $download->name = 'upload.txt'; + var_dump($sc->downloadFile($download)); +} catch (Exception $e) { + var_dump($e); +} + +// var_dump( +// $sc->__getLastRequestHeaders(), +// $sc->__getLastRequest(), +// $sc->__getLastResponseHeaders(), +// $sc->__getLastResponse() +// ); \ No newline at end of file diff --git a/src/BeSimple/SoapClient/Tests/ServerInterop/SwaServerInteropTest.php b/src/BeSimple/SoapClient/Tests/ServerInterop/SwaServerInteropTest.php new file mode 100644 index 0000000..90f7fa0 --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/ServerInterop/SwaServerInteropTest.php @@ -0,0 +1,64 @@ + SOAP_1_1, + 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 + 'attachment_type' => BeSimpleSoapHelper::ATTACHMENTS_TYPE_SWA, + 'cache_wsdl' => WSDL_CACHE_NONE, + 'classmap' => array( + 'downloadFile' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\downloadFile', + 'downloadFileResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\downloadFileResponse', + 'uploadFile' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\uploadFile', + 'uploadFileResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\uploadFileResponse', + ), + 'cli_webserver_workaround' => true, // Work around missing header access in PHP cli webserver by setting headers additionally as GET parameters. + ); + + public function testUploadDownloadText() + { + $sc = new BeSimpleSoapClient(__DIR__.'/Fixtures/SwA.wsdl', $this->options); + + $upload = new uploadFile(); + $upload->name = 'upload.txt'; + $upload->data = 'This is a test. :)'; + $result = $sc->uploadFile($upload); + + $this->assertEquals('File saved succesfully.', $result->return); + + $download = new downloadFile(); + $download->name = 'upload.txt'; + $result = $sc->downloadFile($download); + + $this->assertEquals($upload->data, $result->data); + } + + public function testUploadDownloadImage() + { + $sc = new BeSimpleSoapClient(__DIR__.'/Fixtures/SwA.wsdl', $this->options); + + $upload = new uploadFile(); + $upload->name = 'image.jpg'; + $upload->data = file_get_contents(__DIR__.'/Fixtures/image.jpg'); // source: http://www.freeimageslive.com/galleries/light/pics/swirl3768.jpg; + $result = $sc->uploadFile($upload); + + $this->assertEquals('File saved succesfully.', $result->return); + + $download = new downloadFile(); + $download->name = 'image.jpg'; + $result = $sc->downloadFile($download); + + $this->assertEquals($upload->data, $result->data); + } +} \ No newline at end of file diff --git a/src/BeSimple/SoapClient/Tests/ServerInterop/TestCase.php b/src/BeSimple/SoapClient/Tests/ServerInterop/TestCase.php new file mode 100644 index 0000000..b0f1f40 --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/ServerInterop/TestCase.php @@ -0,0 +1,29 @@ +markTestSkipped( + 'The PHP cli webserver is not available with PHP 5.3.' + ); + } + + $ch = curl_init('http://localhost:8081/'); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($ch, CURLOPT_HEADER, true); + curl_setopt($ch, CURLOPT_NOBODY, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + if (curl_exec($ch) === false) { + $this->markTestSkipped( + 'The PHP webserver is not started on port 8081.' + ); + } + + curl_close($ch); + } +} \ No newline at end of file diff --git a/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecuritySigEncServer.php b/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecuritySigEncServer.php new file mode 100644 index 0000000..c8b01c6 --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecuritySigEncServer.php @@ -0,0 +1,82 @@ + SOAP_1_1, + 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 + 'cache_wsdl' => WSDL_CACHE_NONE, + 'classmap' => array( + 'getBook' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBook', + 'getBookResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBookResponse', + 'getBooksByType' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBooksByType', + 'getBooksByTypeResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBooksByTypeResponse', + 'addBook' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\addBook', + 'addBookResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\addBookResponse', + 'BookInformation' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\BookInformation', + ), +); + +class WsSecuritySigEncServer +{ + public function getBook(getBook $gb) + { + $bi = new BookInformation(); + $bi->isbn = $gb->isbn; + $bi->title = 'title'; + $bi->author = 'author'; + $bi->type = 'scifi'; + + $br = new getBookResponse(); + $br->getBookReturn = $bi; + + return $br; + } + + public function addBook(addBook $ab) + { + $abr = new addBookResponse(); + $abr->addBookReturn = true; + + return $abr; + } +} + +$ss = new BeSimpleSoapServer(__DIR__.'/Fixtures/WsSecurityUserPass.wsdl', $options); + +$wssFilter = new BeSimpleWsSecurityFilter(); + +// user key for signature and encryption +$securityKeyUser = new BeSimpleWsSecurityKey(); +$securityKeyUser->addPrivateKey(XmlSecurityKey::RSA_SHA1, __DIR__.'/Fixtures/serverkey.pem', true); +$securityKeyUser->addPublicKey(XmlSecurityKey::RSA_SHA1, __DIR__.'/Fixtures/servercert.pem', true); +$wssFilter->setUserSecurityKeyObject($securityKeyUser); +// service key for encryption +$securityKeyService = new BeSimpleWsSecurityKey(); +$securityKeyService->addPrivateKey(XmlSecurityKey::TRIPLEDES_CBC); +$securityKeyService->addPublicKey(XmlSecurityKey::RSA_1_5, __DIR__.'/Fixtures/clientcert.pem', true); +$wssFilter->setServiceSecurityKeyObject($securityKeyService); +// TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER | TOKEN_REFERENCE_SECURITY_TOKEN | TOKEN_REFERENCE_THUMBPRINT_SHA1 +$wssFilter->setSecurityOptionsSignature(BeSimpleWsSecurityFilter::TOKEN_REFERENCE_SECURITY_TOKEN); +$wssFilter->setSecurityOptionsEncryption(BeSimpleWsSecurityFilter::TOKEN_REFERENCE_THUMBPRINT_SHA1); + +$soapKernel = $ss->getSoapKernel(); +$soapKernel->registerFilter($wssFilter); + +$ss->setClass('WsSecuritySigEncServer'); +$ss->handle(); diff --git a/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecuritySigEncServerClient.php b/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecuritySigEncServerClient.php new file mode 100644 index 0000000..6c7fe1d --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecuritySigEncServerClient.php @@ -0,0 +1,83 @@ + SOAP_1_2, + 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 + 'trace' => true, // enables use of the methods SoapClient->__getLastRequest, SoapClient->__getLastRequestHeaders, SoapClient->__getLastResponse and SoapClient->__getLastResponseHeaders + 'classmap' => array( + 'getBook' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBook', + 'getBookResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBookResponse', + 'getBooksByType' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBooksByType', + 'getBooksByTypeResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBooksByTypeResponse', + 'addBook' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\addBook', + 'addBookResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\addBookResponse', + 'BookInformation' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\BookInformation', + ), + 'cli_webserver_workaround' => true, // Work around missing header access in PHP cli webserver by setting headers additionally as GET parameters. +); + +$sc = new BeSimpleSoapClient(__DIR__.'/Fixtures/WsSecuritySigEnc.wsdl', $options); + +//var_dump($sc->__getFunctions()); +//var_dump($sc->__getTypes()); + +try { + $wssFilter = new BeSimpleWsSecurityFilter(); + // user key for signature and encryption + $securityKeyUser = new BeSimpleWsSecurityKey(); + $securityKeyUser->addPrivateKey(XmlSecurityKey::RSA_SHA1, __DIR__.'/Fixtures/clientkey.pem', true); + $securityKeyUser->addPublicKey(XmlSecurityKey::RSA_SHA1, __DIR__.'/Fixtures/clientcert.pem', true); + $wssFilter->setUserSecurityKeyObject($securityKeyUser); + // service key for encryption + $securityKeyService = new BeSimpleWsSecurityKey(); + $securityKeyService->addPrivateKey(XmlSecurityKey::TRIPLEDES_CBC); + $securityKeyService->addPublicKey(XmlSecurityKey::RSA_1_5, __DIR__.'/Fixtures/servercert.pem', true); + $wssFilter->setServiceSecurityKeyObject($securityKeyService); + // TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER | TOKEN_REFERENCE_SECURITY_TOKEN | TOKEN_REFERENCE_THUMBPRINT_SHA1 + $wssFilter->setSecurityOptionsSignature(BeSimpleWsSecurityFilter::TOKEN_REFERENCE_SECURITY_TOKEN); + $wssFilter->setSecurityOptionsEncryption(BeSimpleWsSecurityFilter::TOKEN_REFERENCE_THUMBPRINT_SHA1); + + $soapKernel = $sc->getSoapKernel(); + $soapKernel->registerFilter($wssFilter); + + $gb = new getBook(); + $gb->isbn = '0061020052'; + $result = $sc->getBook($gb); + var_dump($result->getBookReturn); + + $ab = new addBook(); + $ab->isbn = '0445203498'; + $ab->title = 'The Dragon Never Sleeps'; + $ab->author = 'Cook, Glen'; + $ab->type = 'scifi'; + + var_dump($sc->addBook($ab)); + +} catch (Exception $e) { + var_dump($e); +} + +// var_dump( +// $sc->__getLastRequestHeaders(), +// $sc->__getLastRequest(), +// $sc->__getLastResponseHeaders(), +// $sc->__getLastResponse() +// ); diff --git a/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecuritySigEncServerInteropTest.php b/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecuritySigEncServerInteropTest.php new file mode 100644 index 0000000..7cbbef4 --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecuritySigEncServerInteropTest.php @@ -0,0 +1,73 @@ + SOAP_1_2, + 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 + 'classmap' => array( + 'getBook' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBook', + 'getBookResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBookResponse', + 'getBooksByType' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBooksByType', + 'getBooksByTypeResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBooksByTypeResponse', + 'addBook' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\addBook', + 'addBookResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\addBookResponse', + 'BookInformation' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\BookInformation', + ), + 'cli_webserver_workaround' => true, // Work around missing header access in PHP cli webserver by setting headers additionally as GET parameters. + ); + + public function testSigEnc() + { + $sc = new BeSimpleSoapClient(__DIR__.'/Fixtures/WsSecuritySigEnc.wsdl', $this->options); + + $wssFilter = new BeSimpleWsSecurityFilter(); + // user key for signature and encryption + $securityKeyUser = new BeSimpleWsSecurityKey(); + $securityKeyUser->addPrivateKey(XmlSecurityKey::RSA_SHA1, __DIR__.'/Fixtures/clientkey.pem', true); + $securityKeyUser->addPublicKey(XmlSecurityKey::RSA_SHA1, __DIR__.'/Fixtures/clientcert.pem', true); + $wssFilter->setUserSecurityKeyObject($securityKeyUser); + // service key for encryption + $securityKeyService = new BeSimpleWsSecurityKey(); + $securityKeyService->addPrivateKey(XmlSecurityKey::TRIPLEDES_CBC); + $securityKeyService->addPublicKey(XmlSecurityKey::RSA_1_5, __DIR__.'/Fixtures/servercert.pem', true); + $wssFilter->setServiceSecurityKeyObject($securityKeyService); + // TOKEN_REFERENCE_SUBJECT_KEY_IDENTIFIER | TOKEN_REFERENCE_SECURITY_TOKEN | TOKEN_REFERENCE_THUMBPRINT_SHA1 + $wssFilter->setSecurityOptionsSignature(BeSimpleWsSecurityFilter::TOKEN_REFERENCE_SECURITY_TOKEN); + $wssFilter->setSecurityOptionsEncryption(BeSimpleWsSecurityFilter::TOKEN_REFERENCE_THUMBPRINT_SHA1); + + $soapKernel = $sc->getSoapKernel(); + $soapKernel->registerFilter($wssFilter); + + $gb = new getBook(); + $gb->isbn = '0061020052'; + $result = $sc->getBook($gb); + $this->assertInstanceOf('BeSimple\SoapClient\Tests\ServerInterop\Fixtures\BookInformation', $result->getBookReturn); + + $ab = new addBook(); + $ab->isbn = '0445203498'; + $ab->title = 'The Dragon Never Sleeps'; + $ab->author = 'Cook, Glen'; + $ab->type = 'scifi'; + + $this->assertTrue((bool) $sc->addBook($ab)); + + // getBooksByType("scifi"); + } +} \ No newline at end of file diff --git a/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecurityUserPassServer.php b/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecurityUserPassServer.php new file mode 100644 index 0000000..8a2e2dc --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecurityUserPassServer.php @@ -0,0 +1,78 @@ + SOAP_1_1, + 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 + 'cache_wsdl' => WSDL_CACHE_NONE, + 'classmap' => array( + 'getBook' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBook', + 'getBookResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBookResponse', + 'getBooksByType' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBooksByType', + 'getBooksByTypeResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBooksByTypeResponse', + 'addBook' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\addBook', + 'addBookResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\addBookResponse', + 'BookInformation' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\BookInformation', + ), +); + +class Auth +{ + public static function usernamePasswordCallback($user) + { + if ($user == 'libuser') { + return 'books'; + } + + return null; + } +} + +class WsSecurityUserPassServer +{ + public function getBook(getBook $gb) + { + $bi = new BookInformation(); + $bi->isbn = $gb->isbn; + $bi->title = 'title'; + $bi->author = 'author'; + $bi->type = 'scifi'; + + $br = new getBookResponse(); + $br->getBookReturn = $bi; + + return $br; + } + + public function addBook(addBook $ab) + { + $abr = new addBookResponse(); + $abr->addBookReturn = true; + + return $abr; + } +} + +$ss = new BeSimpleSoapServer(__DIR__.'/Fixtures/WsSecurityUserPass.wsdl', $options); + +$wssFilter = new BeSimpleWsSecurityFilter(); +$wssFilter->setUsernamePasswordCallback(array('Auth', 'usernamePasswordCallback')); + +$soapKernel = $ss->getSoapKernel(); +$soapKernel->registerFilter($wssFilter); + +$ss->setClass('WsSecurityUserPassServer'); +$ss->handle(); diff --git a/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecurityUserPassServerClient.php b/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecurityUserPassServerClient.php new file mode 100644 index 0000000..b69b3d9 --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecurityUserPassServerClient.php @@ -0,0 +1,68 @@ + SOAP_1_2, + 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 + 'trace' => true, // enables use of the methods SoapClient->__getLastRequest, SoapClient->__getLastRequestHeaders, SoapClient->__getLastResponse and SoapClient->__getLastResponseHeaders + 'classmap' => array( + 'getBook' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBook', + 'getBookResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBookResponse', + 'getBooksByType' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBooksByType', + 'getBooksByTypeResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBooksByTypeResponse', + 'addBook' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\addBook', + 'addBookResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\addBookResponse', + 'BookInformation' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\BookInformation', + ), + 'cli_webserver_workaround' => true, // Work around missing header access in PHP cli webserver by setting headers additionally as GET parameters. +); + +$sc = new BeSimpleSoapClient(__DIR__.'/Fixtures/WsSecurityUserPass.wsdl', $options); + +//var_dump($sc->__getFunctions()); +//var_dump($sc->__getTypes()); + +try { + $wssFilter = new BeSimpleWsSecurityFilter(true, 600); + $wssFilter->addUserData('libuser', 'books', BeSimpleWsSecurityFilter::PASSWORD_TYPE_DIGEST); + + $soapKernel = $sc->getSoapKernel(); + $soapKernel->registerFilter($wssFilter); + + $gb = new getBook(); + $gb->isbn = '0061020052'; + $result = $sc->getBook($gb); + var_dump($result->getBookReturn); + + $ab = new addBook(); + $ab->isbn = '0445203498'; + $ab->title = 'The Dragon Never Sleeps'; + $ab->author = 'Cook, Glen'; + $ab->type = 'scifi'; + + var_dump($sc->addBook($ab)); + +} catch (Exception $e) { + var_dump($e); +} + +// var_dump( +// $sc->__getLastRequestHeaders(), +// $sc->__getLastRequest(), +// $sc->__getLastResponseHeaders(), +// $sc->__getLastResponse() +// ); diff --git a/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecurityUserPassServerInteropTest.php b/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecurityUserPassServerInteropTest.php new file mode 100644 index 0000000..4030507 --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/ServerInterop/WsSecurityUserPassServerInteropTest.php @@ -0,0 +1,86 @@ + SOAP_1_2, + 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, // make sure that result is array for size=1 + 'classmap' => array( + 'getBook' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBook', + 'getBookResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBookResponse', + 'getBooksByType' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBooksByType', + 'getBooksByTypeResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\getBooksByTypeResponse', + 'addBook' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\addBook', + 'addBookResponse' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\addBookResponse', + 'BookInformation' => 'BeSimple\SoapClient\Tests\ServerInterop\Fixtures\BookInformation', + ), + 'cli_webserver_workaround' => true, // Work around missing header access in PHP cli webserver by setting headers additionally as GET parameters. + ); + + public function testUserPassText() + { + $sc = new BeSimpleSoapClient(__DIR__.'/Fixtures/WsSecurityUserPass.wsdl', $this->options); + + $wssFilter = new BeSimpleWsSecurityFilter(true, 600); + $wssFilter->addUserData('libuser', 'books', BeSimpleWsSecurityFilter::PASSWORD_TYPE_TEXT); + + $soapKernel = $sc->getSoapKernel(); + $soapKernel->registerFilter($wssFilter); + + $gb = new getBook(); + $gb->isbn = '0061020052'; + $result = $sc->getBook($gb); + $this->assertInstanceOf('BeSimple\SoapClient\Tests\ServerInterop\Fixtures\BookInformation', $result->getBookReturn); + + $ab = new addBook(); + $ab->isbn = '0445203498'; + $ab->title = 'The Dragon Never Sleeps'; + $ab->author = 'Cook, Glen'; + $ab->type = 'scifi'; + + $this->assertTrue((bool) $sc->addBook($ab)); + + // getBooksByType("scifi"); + } + + public function testUserPassDigest() + { + $sc = new BeSimpleSoapClient(__DIR__.'/Fixtures/WsSecurityUserPass.wsdl', $this->options); + + $wssFilter = new BeSimpleWsSecurityFilter(true, 600); + $wssFilter->addUserData( 'libuser', 'books', BeSimpleWsSecurityFilter::PASSWORD_TYPE_DIGEST ); + + $soapKernel = $sc->getSoapKernel(); + $soapKernel->registerFilter($wssFilter); + + $gb = new getBook(); + $gb->isbn = '0061020052'; + $result = $sc->getBook($gb); + $this->assertInstanceOf('BeSimple\SoapClient\Tests\ServerInterop\Fixtures\BookInformation', $result->getBookReturn); + + $ab = new addBook(); + $ab->isbn = '0445203498'; + $ab->title = 'The Dragon Never Sleeps'; + $ab->author = 'Cook, Glen'; + $ab->type = 'scifi'; + + $this->assertTrue((bool) $sc->addBook($ab)); + + // getBooksByType("scifi"); + } +} \ No newline at end of file diff --git a/src/BeSimple/SoapClient/Tests/bin/axis.sh b/src/BeSimple/SoapClient/Tests/bin/axis.sh new file mode 100644 index 0000000..a6d9ff0 --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/bin/axis.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd $DIR + +VERSION_AXIS=1.5.1 +ZIP_AXIS=axis2-$VERSION_AXIS-bin.zip +if [[ "$VERSION_AXIS" > "1.5.1" ]]; then + PATH_AXIS=http://archive.apache.org/dist/axis/axis2/java/core/$VERSION_AXIS/$ZIP_AXIS +else + PATH_AXIS=http://archive.apache.org/dist/ws/axis2/${VERSION_AXIS//./_}/$ZIP_AXIS +fi + +if [ ! -f "$DIR/$ZIP_AXIS" ]; then + curl -O -s $PATH_AXIS +fi + +VERSION_RAMPART=1.5 +ZIP_RAMPART=rampart-dist-$VERSION_RAMPART-bin.zip +PATH_RAMPART=http://archive.apache.org/dist/axis/axis2/java/rampart/$VERSION_RAMPART/$ZIP_RAMPART + +if [ ! -f "$DIR/$ZIP_RAMPART" ]; then + curl -O -s $PATH_RAMPART +fi + +unzip -qq "$DIR/$ZIP_AXIS" + +AXIS_DIR=$DIR/axis2-$VERSION_AXIS + +unzip -qq -j "$DIR/$ZIP_RAMPART" '*/lib/*.jar' -d $AXIS_DIR/lib +unzip -qq -j "$DIR/$ZIP_RAMPART" '*/modules/*.mar' -d $AXIS_DIR/repository/modules + +cp -r $DIR/../AxisInterop/axis_services/* $AXIS_DIR/repository/services + +$AXIS_DIR/bin/axis2server.sh& + +echo "Waiting until Axis is ready on port 8080" +while [[ -z `curl -s 'http://localhost:8080' ` ]] +do + echo -n "." + sleep 2s +done + +echo "Axis is up" \ No newline at end of file diff --git a/src/BeSimple/SoapClient/Tests/bin/phpwebserver.sh b/src/BeSimple/SoapClient/Tests/bin/phpwebserver.sh new file mode 100644 index 0000000..682f802 --- /dev/null +++ b/src/BeSimple/SoapClient/Tests/bin/phpwebserver.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +DIR="$( cd -P "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd $DIR + +php -S localhost:8081 -t "$DIR/.."& + +echo "Waiting until PHP webserver is ready on port 8081" +while [[ -z `curl -s 'http://localhost:8081' ` ]] +do + echo -n "." + sleep 2s +done + +echo "PHP webserver is up" \ No newline at end of file From 4fa7fd759a6f6edb0606c2331b4d495c4ac6bea8 Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Sat, 24 Aug 2013 08:24:31 +0200 Subject: [PATCH 15/26] add phpwebserver to travis --- .travis.yml | 1 + src/BeSimple/SoapClient/Tests/bin/phpwebserver.sh | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/.travis.yml b/.travis.yml index 7604e9c..d1cfc26 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,3 +8,4 @@ php: before_script: - curl -s http://getcomposer.org/installer | php - php composer.phar install --dev --prefer-source + - ./src/BeSimple/SoapClient/Tests/bin/phpwebserver.sh diff --git a/src/BeSimple/SoapClient/Tests/bin/phpwebserver.sh b/src/BeSimple/SoapClient/Tests/bin/phpwebserver.sh index 682f802..0d07b8e 100644 --- a/src/BeSimple/SoapClient/Tests/bin/phpwebserver.sh +++ b/src/BeSimple/SoapClient/Tests/bin/phpwebserver.sh @@ -1,5 +1,12 @@ #!/bin/bash +PHPWEBSERVEROK="$( php -r "if (version_compare(phpversion(), '5.4', '<')) { echo 'nok'; } else { echo 'ok'; }" )" + +if [ "$PHPWEBSERVEROK" != "ok" ]; then + echo "No PHP webserver available before version 5.4..." + exit +fi + DIR="$( cd -P "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd $DIR From 3f41d4b7bc46fcb329b419dc944f7300c0a67de6 Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Sat, 24 Aug 2013 08:41:59 +0200 Subject: [PATCH 16/26] change file permissions --- src/BeSimple/SoapClient/Tests/bin/phpwebserver.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 src/BeSimple/SoapClient/Tests/bin/phpwebserver.sh diff --git a/src/BeSimple/SoapClient/Tests/bin/phpwebserver.sh b/src/BeSimple/SoapClient/Tests/bin/phpwebserver.sh old mode 100644 new mode 100755 From 155956ca04f0728d57fd0d0c627de5b34d29b310 Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Sat, 24 Aug 2013 08:44:10 +0200 Subject: [PATCH 17/26] change file permissions --- src/BeSimple/SoapClient/Tests/bin/axis.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 src/BeSimple/SoapClient/Tests/bin/axis.sh diff --git a/src/BeSimple/SoapClient/Tests/bin/axis.sh b/src/BeSimple/SoapClient/Tests/bin/axis.sh old mode 100644 new mode 100755 From 76360c193e7c1068191939b29f9d3315e26463a2 Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Sat, 24 Aug 2013 09:03:23 +0200 Subject: [PATCH 18/26] add php ini for travis and update travis config --- .travis.php.ini | 1 + 1 file changed, 1 insertion(+) create mode 100644 .travis.php.ini diff --git a/.travis.php.ini b/.travis.php.ini new file mode 100644 index 0000000..01d4d71 --- /dev/null +++ b/.travis.php.ini @@ -0,0 +1 @@ +soap.wsdl_cache_dir = /tmp \ No newline at end of file From e9388023c16be3f2dd111a1d8b8d6327a1b43f1d Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Sat, 24 Aug 2013 09:05:58 +0200 Subject: [PATCH 19/26] add php ini for travis and update travis config --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index d1cfc26..7844b6a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,3 +9,4 @@ before_script: - curl -s http://getcomposer.org/installer | php - php composer.phar install --dev --prefer-source - ./src/BeSimple/SoapClient/Tests/bin/phpwebserver.sh + - phpenv config-add .travis.php.ini From cbd49c3342a37e906aff1eb4b64828eb4cc0a431 Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Sat, 24 Aug 2013 09:21:13 +0200 Subject: [PATCH 20/26] add php ini for travis and update travis config --- .travis.php.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.php.ini b/.travis.php.ini index 01d4d71..74d95e4 100644 --- a/.travis.php.ini +++ b/.travis.php.ini @@ -1 +1 @@ -soap.wsdl_cache_dir = /tmp \ No newline at end of file +soap.wsdl_cache_dir = "/tmp" \ No newline at end of file From d8d6ca5795ae5c20ffc5f0a6c45c50a8ee996b35 Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Sat, 24 Aug 2013 09:39:56 +0200 Subject: [PATCH 21/26] fix problem with cache path --- src/BeSimple/SoapClient/WsdlDownloader.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/BeSimple/SoapClient/WsdlDownloader.php b/src/BeSimple/SoapClient/WsdlDownloader.php index 6e403ce..97a7d67 100644 --- a/src/BeSimple/SoapClient/WsdlDownloader.php +++ b/src/BeSimple/SoapClient/WsdlDownloader.php @@ -74,11 +74,8 @@ class WsdlDownloader // get current WSDL caching config $this->cacheEnabled = $cacheWsdl === Cache::TYPE_NONE ? Cache::DISABLED : Cache::ENABLED == Cache::isEnabled(); - - if ($this->cacheEnabled) { - $this->cacheDir = Cache::getDirectory(); - $this->cacheTtl = Cache::getLifetime(); - } + $this->cacheDir = Cache::getDirectory(); + $this->cacheTtl = Cache::getLifetime(); } /** From dc3fcaf81fb9b068aae29a46dbc3251c32ab8a23 Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Sat, 24 Aug 2013 09:47:34 +0200 Subject: [PATCH 22/26] remove ini and try axis build --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7844b6a..1ac72d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,4 +9,4 @@ before_script: - curl -s http://getcomposer.org/installer | php - php composer.phar install --dev --prefer-source - ./src/BeSimple/SoapClient/Tests/bin/phpwebserver.sh - - phpenv config-add .travis.php.ini + - ./src/BeSimple/SoapClient/Tests/bin/axis.sh From 8e305da9c094ac5ddf5522af3462d4a1aff663f8 Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Sat, 24 Aug 2013 10:53:24 +0200 Subject: [PATCH 23/26] change url for check if axis is ready --- src/BeSimple/SoapClient/Tests/bin/axis.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BeSimple/SoapClient/Tests/bin/axis.sh b/src/BeSimple/SoapClient/Tests/bin/axis.sh index a6d9ff0..d143ebd 100755 --- a/src/BeSimple/SoapClient/Tests/bin/axis.sh +++ b/src/BeSimple/SoapClient/Tests/bin/axis.sh @@ -35,7 +35,7 @@ cp -r $DIR/../AxisInterop/axis_services/* $AXIS_DIR/repository/services $AXIS_DIR/bin/axis2server.sh& echo "Waiting until Axis is ready on port 8080" -while [[ -z `curl -s 'http://localhost:8080' ` ]] +while [[ -z `curl -s 'http://localhost:8080/axis2/services/' ` ]] do echo -n "." sleep 2s From 1f1d513ea30d69275f11e54e04dd7d7ca6530abb Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Sat, 24 Aug 2013 11:01:59 +0200 Subject: [PATCH 24/26] remove obsolete file --- .travis.php.ini | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .travis.php.ini diff --git a/.travis.php.ini b/.travis.php.ini deleted file mode 100644 index 74d95e4..0000000 --- a/.travis.php.ini +++ /dev/null @@ -1 +0,0 @@ -soap.wsdl_cache_dir = "/tmp" \ No newline at end of file From 461675042a6e3d40036ff82bd1dfdff42098ab2a Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Fri, 30 Aug 2013 22:07:44 +0200 Subject: [PATCH 25/26] fix soap header handling --- src/BeSimple/SoapClient/SoapClient.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/BeSimple/SoapClient/SoapClient.php b/src/BeSimple/SoapClient/SoapClient.php index b8a75c7..5476488 100644 --- a/src/BeSimple/SoapClient/SoapClient.php +++ b/src/BeSimple/SoapClient/SoapClient.php @@ -138,10 +138,18 @@ class SoapClient extends \SoapClient private function __doHttpRequest(SoapRequest $soapRequest) { // HTTP headers - $headers = array( - 'Content-Type:' . $soapRequest->getContentType(), - 'SOAPAction: "' . $soapRequest->getAction() . '"', - ); + $soapVersion = $soapRequest->getVersion(); + $soapAction = $soapRequest->getAction(); + if (SOAP_1_1 == $soapVersion) { + $headers = array( + 'Content-Type:' . $soapRequest->getContentType(), + 'SOAPAction: "' . $soapAction . '"', + ); + } else { + $headers = array( + 'Content-Type:' . $soapRequest->getContentType() . '; action="' . $soapAction . '"', + ); + } $location = $soapRequest->getLocation(); $content = $soapRequest->getContent(); From 9ddac35f18ec0061fb28e08e8e5bbddd1182922f Mon Sep 17 00:00:00 2001 From: Andreas Schamberger Date: Wed, 30 Oct 2013 12:31:00 +0100 Subject: [PATCH 26/26] Fix doc comment --- src/BeSimple/SoapCommon/WsSecurityFilterClientServer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BeSimple/SoapCommon/WsSecurityFilterClientServer.php b/src/BeSimple/SoapCommon/WsSecurityFilterClientServer.php index af0f21d..00231cc 100644 --- a/src/BeSimple/SoapCommon/WsSecurityFilterClientServer.php +++ b/src/BeSimple/SoapCommon/WsSecurityFilterClientServer.php @@ -146,7 +146,7 @@ abstract class WsSecurityFilterClientServer } /** - * Get service security key. + * Set service security key. * * @param \BeSimple\SoapCommon\WsSecurityKey $serviceSecurityKey Service security key * @@ -158,7 +158,7 @@ abstract class WsSecurityFilterClientServer } /** - * Get user security key. + * Set user security key. * * @param \BeSimple\SoapCommon\WsSecurityKey $userSecurityKey User security key *