diff --git a/.gitignore b/.gitignore index 22d0d82..f8d178e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ vendor +/phpunit.xml +.buildpath +.project +.settings \ No newline at end of file diff --git a/src/BeSimple/SoapClient/Curl.php b/src/BeSimple/SoapClient/Curl.php new file mode 100644 index 0000000..ae57933 --- /dev/null +++ b/src/BeSimple/SoapClient/Curl.php @@ -0,0 +1,313 @@ + + * (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\SoapClient; + +/** + * cURL wrapper class for doing HTTP requests that uses the soap class options. + * + * @author Andreas Schamberger + */ +class Curl +{ + /** + * HTTP User Agent. + * + * @var string + */ + const USER_AGENT = 'PHP-SOAP/\BeSimple\SoapClient'; + + /** + * Curl resource. + * + * @var resource + */ + private $ch; + + /** + * Maximum number of location headers to follow. + * + * @var int + */ + private $followLocationMaxRedirects; + + /** + * Request response data. + * + * @var string + */ + private $response; + + /** + * Constructor. + * + * @param array $options Options array from SoapClient constructor + * @param int $followLocationMaxRedirects Redirection limit for Location header + */ + public function __construct(array $options = array(), $followLocationMaxRedirects = 10) + { + // set the default HTTP user agent + if (!isset($options['user_agent'])) { + $options['user_agent'] = self::USER_AGENT; + } + $this->followLocationMaxRedirects = $followLocationMaxRedirects; + + // make http request + $this->ch = curl_init(); + $curlOptions = array( + CURLOPT_ENCODING => '', + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_FAILONERROR => false, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_HEADER => true, + CURLOPT_USERAGENT => $options['user_agent'], + CURLINFO_HEADER_OUT => true, + ); + curl_setopt_array($this->ch, $curlOptions); + if (isset($options['compression']) && !($options['compression'] & SOAP_COMPRESSION_ACCEPT)) { + curl_setopt($this->ch, CURLOPT_ENCODING, 'identity'); + } + if (isset($options['connection_timeout'])) { + curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, $options['connection_timeout']); + } + if (isset($options['proxy_host'])) { + $port = isset($options['proxy_port']) ? $options['proxy_port'] : 8080; + curl_setopt($this->ch, CURLOPT_PROXY, $options['proxy_host'] . ':' . $port); + } + if (isset($options['proxy_user'])) { + curl_setopt($this->ch, CURLOPT_PROXYUSERPWD, $options['proxy_user'] . ':' . $options['proxy_password']); + } + if (isset($options['login'])) { + curl_setopt($this->ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + curl_setopt($this->ch, CURLOPT_USERPWD, $options['login'].':'.$options['password']); + } + if (isset($options['local_cert'])) { + curl_setopt($this->ch, CURLOPT_SSLCERT, $options['local_cert']); + curl_setopt($this->ch, CURLOPT_SSLCERTPASSWD, $options['passphrase']); + } + } + + /** + * Destructor. + */ + public function __destruct() + { + curl_close($this->ch); + } + + /** + * Execute HTTP request. + * Returns true if request was successfull. + * + * @param string $location HTTP location + * @param string $request Request body + * @param array $requestHeaders Request header strings + * + * @return bool + */ + public function exec($location, $request = null, $requestHeaders = array()) + { + curl_setopt($this->ch, CURLOPT_URL, $location); + + if (!is_null($request)) { + curl_setopt($this->ch, CURLOPT_POST, true); + curl_setopt($this->ch, CURLOPT_POSTFIELDS, $request); + } + + if (count($requestHeaders) > 0) { + curl_setopt($this->ch, CURLOPT_HTTPHEADER, $requestHeaders); + } + + $this->response = $this->execManualRedirect($this->followLocationMaxRedirects); + + return ($this->response === false) ? false : true; + } + + /** + * Custom curl_exec wrapper that allows to follow redirects when specific + * http response code is set. SOAP only allows 307. + * + * @param int $redirects Current redirection count + * + * @return mixed + */ + private function execManualRedirect($redirects = 0) + { + if ($redirects > $this->followLocationMaxRedirects) { + + // TODO Redirection limit reached, aborting + return false; + } + curl_setopt($this->ch, CURLOPT_HEADER, true); + curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($this->ch); + $httpResponseCode = curl_getinfo($this->ch, CURLINFO_HTTP_CODE); + if ($httpResponseCode == 307) { + $headerSize = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $headerSize); + $matches = array(); + preg_match('/Location:(.*?)\n/', $header, $matches); + $url = trim(array_pop($matches)); + // @parse_url to suppress E_WARNING for invalid urls + if (($url = @parse_url($url)) !== false) { + $lastUrl = parse_url(curl_getinfo($this->ch, CURLINFO_EFFECTIVE_URL)); + if (!isset($url['scheme'])) { + $url['scheme'] = $lastUrl['scheme']; + } + if (!isset($url['host'])) { + $url['host'] = $lastUrl['host']; + } + if (!isset($url['path'])) { + $url['path'] = $lastUrl['path']; + } + $newUrl = $url['scheme'] . '://' . $url['host'] . $url['path'] . ($url['query'] ? '?' . $url['query'] : ''); + curl_setopt($this->ch, CURLOPT_URL, $newUrl); + + return $this->execManualRedirect($redirects++); + } + } + + return $response; + } + + /** + * Error code mapping from cURL error codes to PHP ext/soap error messages + * (where applicable) + * + * http://curl.haxx.se/libcurl/c/libcurl-errors.html + * + * @return array(int=>string) + */ + protected function getErrorCodeMapping() + { + return array( + 1 => 'Unknown protocol. Only http and https are allowed.', //CURLE_UNSUPPORTED_PROTOCOL + 3 => 'Unable to parse URL', //CURLE_URL_MALFORMAT + 5 => 'Could not connect to host', //CURLE_COULDNT_RESOLVE_PROXY + 6 => 'Could not connect to host', //CURLE_COULDNT_RESOLVE_HOST + 7 => 'Could not connect to host', //CURLE_COULDNT_CONNECT + 9 => 'Could not connect to host', //CURLE_REMOTE_ACCESS_DENIED + 28 => 'Failed Sending HTTP SOAP request', //CURLE_OPERATION_TIMEDOUT + 35 => 'Could not connect to host', //CURLE_SSL_CONNECT_ERROR + 41 => 'Can\'t uncompress compressed response', //CURLE_FUNCTION_NOT_FOUND + 51 => 'Could not connect to host', //CURLE_PEER_FAILED_VERIFICATION + 52 => 'Error Fetching http body, No Content-Length, connection closed or chunked data', //CURLE_GOT_NOTHING + 53 => 'SSL support is not available in this build', //CURLE_SSL_ENGINE_NOTFOUND + 54 => 'SSL support is not available in this build', //CURLE_SSL_ENGINE_SETFAILED + 55 => 'Failed Sending HTTP SOAP request', //CURLE_SEND_ERROR + 56 => 'Error Fetching http body, No Content-Length, connection closed or chunked data', //CURLE_RECV_ERROR + 58 => 'Could not connect to host', //CURLE_SSL_CERTPROBLEM + 59 => 'Could not connect to host', //CURLE_SSL_CIPHER + 60 => 'Could not connect to host', //CURLE_SSL_CACERT + 61 => 'Unknown Content-Encoding', //CURLE_BAD_CONTENT_ENCODING + 65 => 'Failed Sending HTTP SOAP request', //CURLE_SEND_FAIL_REWIND + 66 => 'SSL support is not available in this build', //CURLE_SSL_ENGINE_INITFAILED + 67 => 'Could not connect to host', //CURLE_LOGIN_DENIED + 77 => 'Could not connect to host', //CURLE_SSL_CACERT_BADFILE + 80 => 'Error Fetching http body, No Content-Length, connection closed or chunked data', //CURLE_SSL_SHUTDOWN_FAILED + ); + } + + /** + * Gets the curl error message. + * + * @return string + */ + public function getErrorMessage() + { + $errorCodeMapping = $this->getErrorCodeMapping(); + $errorNumber = curl_errno($this->ch); + if (isset($errorCodeMapping[$errorNumber])) { + + return $errorCodeMapping[$errorNumber]; + } + + return curl_error($this->ch); + } + + /** + * Gets the request headers as a string. + * + * @return string + */ + public function getRequestHeaders() + { + return curl_getinfo($this->ch, CURLINFO_HEADER_OUT); + } + + /** + * Gets the whole response (including headers) as a string. + * + * @return string + */ + public function getResponse() + { + return $this->response; + } + + /** + * Gets the response body as a string. + * + * @return string + */ + public function getResponseBody() + { + $headerSize = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE); + + return substr($this->response, $headerSize); + } + + /** + * Gets the response content type. + * + * @return string + */ + public function getResponseContentType() + { + return curl_getinfo($this->ch, CURLINFO_CONTENT_TYPE); + } + + /** + * Gets the response headers as a string. + * + * @return string + */ + public function getResponseHeaders() + { + $headerSize = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE); + + return substr($this->response, 0, $headerSize); + } + + /** + * Gets the response http status code. + * + * @return string + */ + public function getResponseStatusCode() + { + return curl_getinfo($this->ch, CURLINFO_HTTP_CODE); + } + + /** + * Gets the response http status message. + * + * @return string + */ + public function getResponseStatusMessage() + { + preg_match('/HTTP\/(1\.[0-1]+) ([0-9]{3}) (.*)/', $this->response, $matches); + + return trim(array_pop($matches)); + } +} \ No newline at end of file diff --git a/src/BeSimple/SoapClient/MimeFilter.php b/src/BeSimple/SoapClient/MimeFilter.php new file mode 100644 index 0000000..beee156 --- /dev/null +++ b/src/BeSimple/SoapClient/MimeFilter.php @@ -0,0 +1,138 @@ + + * (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\SoapClient; + +use BeSimple\SoapCommon\Helper; +use BeSimple\SoapCommon\Mime\MultiPart as MimeMultiPart; +use BeSimple\SoapCommon\Mime\Parser as MimeParser; +use BeSimple\SoapCommon\Mime\Part as MimePart; +use BeSimple\SoapCommon\SoapRequest; +use BeSimple\SoapCommon\SoapRequestFilter; +use BeSimple\SoapCommon\SoapResponse; +use BeSimple\SoapCommon\SoapResponseFilter; + +/** + * MIME filter. + * + * @author Andreas Schamberger + */ +class MimeFilter implements SoapRequestFilter, SoapResponseFilter +{ + /** + * Attachment type. + * + * @var int Helper::ATTACHMENTS_TYPE_SWA | Helper::ATTACHMENTS_TYPE_MTOM + */ + protected $attachmentType = Helper::ATTACHMENTS_TYPE_SWA; + + /** + * Constructor. + * + * @param int $attachmentType Helper::ATTACHMENTS_TYPE_SWA | Helper::ATTACHMENTS_TYPE_MTOM + */ + public function __construct($attachmentType) + { + $this->attachmentType = $attachmentType; + } + + /** + * Reset all properties to default values. + */ + public function resetFilter() + { + $this->attachmentType = Helper::ATTACHMENTS_TYPE_SWA; + } + + /** + * Modify the given request XML. + * + * @param \BeSimple\SoapCommon\SoapRequest $request SOAP request + * + * @return void + */ + public function filterRequest(SoapRequest $request) + { + // get attachments from request object + $attachmentsToSend = $request->getAttachments(); + + // build mime message if we have attachments + if (count($attachmentsToSend) > 0) { + $multipart = new MimeMultiPart(); + $soapPart = new MimePart($request->getContent(), 'text/xml', 'utf-8', MimePart::ENCODING_EIGHT_BIT); + $soapVersion = $request->getVersion(); + // change content type headers for MTOM with SOAP 1.1 + if ($soapVersion == SOAP_1_1 && $this->attachmentType & Helper::ATTACHMENTS_TYPE_MTOM) { + $multipart->setHeader('Content-Type', 'type', 'application/xop+xml'); + $multipart->setHeader('Content-Type', 'start-info', 'text/xml'); + $soapPart->setHeader('Content-Type', 'application/xop+xml'); + $soapPart->setHeader('Content-Type', 'type', 'text/xml'); + } + // change content type headers for SOAP 1.2 + elseif ($soapVersion == SOAP_1_2) { + $multipart->setHeader('Content-Type', 'type', 'application/soap+xml'); + $soapPart->setHeader('Content-Type', 'application/soap+xml'); + } + $multipart->addPart($soapPart, true); + foreach ($attachmentsToSend as $cid => $attachment) { + $multipart->addPart($attachment, false); + } + $request->setContent($multipart->getMimeMessage()); + + // TODO + $headers = $multipart->getHeadersForHttp(); + list($name, $contentType) = explode(': ', $headers[0]); + + $request->setContentType($contentType); + } + } + + /** + * Modify the given response XML. + * + * @param \BeSimple\SoapCommon\SoapResponse $response SOAP response + * + * @return void + */ + public function filterResponse(SoapResponse $response) + { + // array to store attachments + $attachmentsRecieved = array(); + + // check content type if it is a multipart mime message + $responseContentType = $response->getContentType(); + if (false !== stripos($responseContentType, 'multipart/related')) { + // parse mime message + $headers = array( + 'Content-Type' => trim($responseContentType), + ); + $multipart = MimeParser::parseMimeMessage($response->getContent(), $headers); + // get soap payload and update SoapResponse object + $soapPart = $multipart->getPart(); + // convert href -> myhref for external references as PHP throws exception in this case + // http://svn.php.net/viewvc/php/php-src/branches/PHP_5_4/ext/soap/php_encoding.c?view=markup#l3436 + $content = preg_replace('/href=(?!#)/', 'myhref=', $soapPart->getContent()); + $response->setContent($content); + $response->setContentType($soapPart->getHeader('Content-Type')); + // store attachments + $attachments = $multipart->getParts(false); + foreach ($attachments as $cid => $attachment) { + $attachmentsRecieved[$cid] = $attachment; + } + } + + // add attachments to response object + if (count($attachmentsRecieved) > 0) { + $response->setAttachments($attachmentsRecieved); + } + } +} \ No newline at end of file diff --git a/src/BeSimple/SoapClient/SoapClient.php b/src/BeSimple/SoapClient/SoapClient.php index b40ad00..90d8f1d 100644 --- a/src/BeSimple/SoapClient/SoapClient.php +++ b/src/BeSimple/SoapClient/SoapClient.php @@ -12,9 +12,322 @@ namespace BeSimple\SoapClient; +use BeSimple\SoapCommon\Helper; +use BeSimple\SoapCommon\SoapKernel; +use BeSimple\SoapCommon\Converter\MtomTypeConverter; +use BeSimple\SoapCommon\Converter\SwaTypeConverter; + /** - * @author Francis Besset + * Extended SoapClient that uses a a cURL wrapper for all underlying HTTP + * requests in order to use proper authentication for all requests. This also + * adds NTLM support. A custom WSDL downloader resolves remote xsd:includes and + * allows caching of all remote referenced items. + * + * @author Andreas Schamberger */ class SoapClient extends \SoapClient { + /** + * Soap version. + * + * @var int + */ + protected $soapVersion = SOAP_1_1; + + /** + * Tracing enabled? + * + * @var boolean + */ + protected $tracingEnabled = false; + + /** + * cURL instance. + * + * @var \BeSimple\SoapClient\Curl + */ + protected $curl = null; + + /** + * Last request headers. + * + * @var string + */ + private $lastRequestHeaders = ''; + + /** + * Last request. + * + * @var string + */ + private $lastRequest = ''; + + /** + * Last response headers. + * + * @var string + */ + private $lastResponseHeaders = ''; + + /** + * Last response. + * + * @var string + */ + private $lastResponse = ''; + + /** + * Last response. + * + * @var \BeSimple\SoapCommon\SoapKernel + */ + protected $soapKernel = null; + + /** + * Constructor. + * + * @param string $wsdl WSDL file + * @param array(string=>mixed) $options Options array + */ + public function __construct($wsdl, array $options = array()) + { + // tracing enabled: store last request/response header and body + if (isset($options['trace']) && $options['trace'] === true) { + $this->tracingEnabled = true; + } + // store SOAP version + if (isset($options['soap_version'])) { + $this->soapVersion = $options['soap_version']; + } + $this->curl = new Curl($options); + $wsdlFile = $this->loadWsdl($wsdl, $options); + // TODO $wsdlHandler = new WsdlHandler($wsdlFile, $this->soapVersion); + $this->soapKernel = new SoapKernel(); + // set up type converter and mime filter + $this->configureMime($options); + // we want the exceptions option to be set + $options['exceptions'] = true; + // disable obsolete trace option for native SoapClient as we need to do our own tracing anyways + $options['trace'] = false; + // disable WSDL caching as we handle WSDL caching for remote URLs ourself + $options['cache_wsdl'] = WSDL_CACHE_NONE; + parent::__construct($wsdlFile, $options); + } + + + /** + * Perform HTTP request with cURL. + * + * @param SoapRequest $soapRequest SoapRequest object + * + * @return SoapResponse + */ + private function __doHttpRequest(SoapRequest $soapRequest) + { + // HTTP headers + $headers = array( + 'Content-Type:' . $soapRequest->getContentType(), + 'SOAPAction: "' . $soapRequest->getAction() . '"', + ); + // execute HTTP request with cURL + $responseSuccessfull = $this->curl->exec( + $soapRequest->getLocation(), + $soapRequest->getContent(), + $headers + ); + // tracing enabled: store last request header and body + if ($this->tracingEnabled === true) { + $this->lastRequestHeaders = $this->curl->getRequestHeaders(); + $this->lastRequest = $soapRequest->getContent(); + } + // in case of an error while making the http request throw a soapFault + if ($responseSuccessfull === false) { + // get error message from curl + $faultstring = $this->curl->getErrorMessage(); + throw new \SoapFault('HTTP', $faultstring); + } + // tracing enabled: store last response header and body + if ($this->tracingEnabled === true) { + $this->lastResponseHeaders = $this->curl->getResponseHeaders(); + $this->lastResponse = $this->curl->getResponseBody(); + } + // wrap response data in SoapResponse object + $soapResponse = SoapResponse::create( + $this->curl->getResponseBody(), + $soapRequest->getLocation(), + $soapRequest->getAction(), + $soapRequest->getVersion(), + $this->curl->getResponseContentType() + ); + + return $soapResponse; + } + + /** + * Custom request method to be able to modify the SOAP messages. + * $oneWay parameter is not used at the moment. + * + * @param string $request Request string + * @param string $location Location + * @param string $action SOAP action + * @param int $version SOAP version + * @param int $oneWay 0|1 + * + * @return string + */ + public function __doRequest($request, $location, $action, $version, $oneWay = 0) + { + // wrap request data in SoapRequest object + $soapRequest = SoapRequest::create($request, $location, $action, $version); + + // do actual SOAP request + $soapResponse = $this->__doRequest2($soapRequest); + + // return SOAP response to ext/soap + return $soapResponse->getContent(); + } + + /** + * Runs the currently registered request filters on the request, performs + * the HTTP request and runs the response filters. + * + * @param SoapRequest $soapRequest SOAP request object + * + * @return SoapResponse + */ + protected function __doRequest2(SoapRequest $soapRequest) + { + // run SoapKernel on SoapRequest + $this->soapKernel->filterRequest($soapRequest); + + // perform HTTP request with cURL + $soapResponse = $this->__doHttpRequest($soapRequest); + + // run SoapKernel on SoapResponse + $this->soapKernel->filterResponse($soapResponse); + + return $soapResponse; + } + + /** + * Get last request HTTP headers. + * + * @return string + */ + public function __getLastRequestHeaders() + { + return $this->lastRequestHeaders; + } + + /** + * Get last request HTTP body. + * + * @return string + */ + public function __getLastRequest() + { + return $this->lastRequest; + } + + /** + * Get last response HTTP headers. + * + * @return string + */ + public function __getLastResponseHeaders() + { + return $this->lastResponseHeaders; + } + + /** + * Get last response HTTP body. + * + * @return string + */ + public function __getLastResponse() + { + return $this->lastResponse; + } + + /** + * Get SoapKernel instance. + * + * @return \BeSimple\SoapCommon\SoapKernel + */ + public function getSoapKernel() + { + return $this->soapKernel; + } + + /** + * Configure filter and type converter for SwA/MTOM. + * + * @param array &$options SOAP constructor options array. + * + * @return void + */ + private function configureMime(array &$options) + { + if (isset($options['attachment_type']) && Helper::ATTACHMENTS_TYPE_BASE64 !== $options['attachment_type']) { + // register mime filter in SoapKernel + $mimeFilter = new MimeFilter($options['attachment_type']); + $this->soapKernel->registerFilter($mimeFilter); + // configure type converter + if (Helper::ATTACHMENTS_TYPE_SWA === $options['attachment_type']) { + $converter = new SwaTypeConverter(); + $converter->setKernel($this->soapKernel); + } elseif (Helper::ATTACHMENTS_TYPE_MTOM === $options['attachment_type']) { + $converter = new MtomTypeConverter(); + $converter->setKernel($this->soapKernel); + } + // configure typemap + if (!isset($options['typemap'])) { + $options['typemap'] = array(); + } + $soapKernel = $this->soapKernel; + $options['typemap'][] = array( + 'type_name' => $converter->getTypeName(), + 'type_ns' => $converter->getTypeNamespace(), + 'from_xml' => function($input) use ($converter) { + return $converter->convertXmlToPhp($input); + }, + 'to_xml' => function($input) use ($converter) { + return $converter->convertPhpToXml($input); + }, + ); + } + } + + /** + * Downloads WSDL files with cURL. Uses all SoapClient options for + * authentication. Uses the WSDL_CACHE_* constants and the 'soap.wsdl_*' + * ini settings. Does only file caching as SoapClient only supports a file + * name parameter. + * + * @param string $wsdl WSDL file + * @param array(string=>mixed) $options Options array + * + * @return string + */ + private function loadWsdl($wsdl, array $options) + { + // option to resolve wsdl/xsd includes + $resolveRemoteIncludes = true; + if (isset($options['resolve_wsdl_remote_includes'])) { + $resolveRemoteIncludes = $options['resolve_wsdl_remote_includes']; + } + // option to enable cache + $wsdlCache = WSDL_CACHE_DISK; + if (isset($options['cache_wsdl'])) { + $wsdlCache = $options['cache_wsdl']; + } + $wsdlDownloader = new WsdlDownloader($this->curl, $resolveRemoteIncludes, $wsdlCache); + try { + $cacheFileName = $wsdlDownloader->download($wsdl); + } catch (\RuntimeException $e) { + throw new \SoapFault('WSDL', "SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl . "' : failed to load external entity \"" . $wsdl . "\""); + } + + return $cacheFileName; + } } \ No newline at end of file diff --git a/src/BeSimple/SoapClient/SoapClientBuilder.php b/src/BeSimple/SoapClient/SoapClientBuilder.php index 65a6f9b..3759f13 100644 --- a/src/BeSimple/SoapClient/SoapClientBuilder.php +++ b/src/BeSimple/SoapClient/SoapClientBuilder.php @@ -15,25 +15,35 @@ namespace BeSimple\SoapClient; use BeSimple\SoapCommon\AbstractSoapBuilder; /** + * Fluent interface builder for SoapClient instance. + * * @author Francis Besset * @author Christian Kerl */ class SoapClientBuilder extends AbstractSoapBuilder { + /** + * Authentication options. + * + * @var array(string=>mixed) + */ protected $soapOptionAuthentication = array(); /** - * @return SoapClientBuilder + * Create new instance with default options. + * + * @return \BeSimple\SoapClient\SoapClientBuilder */ - static public function createWithDefaults() + public static function createWithDefaults() { return parent::createWithDefaults() - ->withUserAgent('BeSimpleSoap') - ; + ->withUserAgent('BeSimpleSoap'); } /** - * @return SoapClient + * Finally returns a SoapClient instance. + * + * @return \BeSimple\SoapClient\SoapClient */ public function build() { @@ -42,13 +52,22 @@ class SoapClientBuilder extends AbstractSoapBuilder return new SoapClient($this->wsdl, $this->getSoapOptions()); } + /** + * Get final array of SOAP options. + * + * @return array(string=>mixed) + */ public function getSoapOptions() { return parent::getSoapOptions() + $this->soapOptionAuthentication; } /** - * @return SoapClientBuilder + * Configure option 'trace'. + * + * @param boolean $trace Enable/Disable + * + * @return \BeSimple\SoapClient\SoapClientBuilder */ public function withTrace($trace = true) { @@ -58,7 +77,11 @@ class SoapClientBuilder extends AbstractSoapBuilder } /** - * @return SoapClientBuilder + * Configure option 'exceptions'. + * + * @param boolean $exceptions Enable/Disable + * + * @return \BeSimple\SoapClient\SoapClientBuilder */ public function withExceptions($exceptions = true) { @@ -68,7 +91,11 @@ class SoapClientBuilder extends AbstractSoapBuilder } /** - * @return SoapClientBuilder + * Configure option 'user_agent'. + * + * @param string $userAgent User agent string + * + * @return \BeSimple\SoapClient\SoapClientBuilder */ public function withUserAgent($userAgent) { @@ -77,18 +104,37 @@ class SoapClientBuilder extends AbstractSoapBuilder return $this; } + /** + * Enable gzip compression. + * + * @return \BeSimple\SoapClient\SoapClientBuilder + */ public function withCompressionGzip() { $this->soapOptions['compression'] = SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP; - } - public function withCompressionDeflate() - { - $this->soapOptions['compression'] = SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_DEFLATE; + return $this; } /** - * @return SoapClientBuilder + * Enable deflate compression. + * + * @return \BeSimple\SoapClient\SoapClientBuilder + */ + public function withCompressionDeflate() + { + $this->soapOptions['compression'] = SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_DEFLATE; + + return $this; + } + + /** + * Configure basic authentication + * + * @param string $username Username + * @param string $password Password + * + * @return \BeSimple\SoapClient\SoapClientBuilder */ public function withBasicAuthentication($username, $password) { @@ -102,7 +148,12 @@ class SoapClientBuilder extends AbstractSoapBuilder } /** - * @return SoapClientBuilder + * Configure digest authentication. + * + * @param string $certificate Certificate + * @param string $passphrase Passphrase + * + * @return \BeSimple\SoapClient\SoapClientBuilder */ public function withDigestAuthentication($certificate, $passphrase = null) { @@ -118,6 +169,16 @@ class SoapClientBuilder extends AbstractSoapBuilder return $this; } + /** + * Configure proxy. + * + * @param string $host Host + * @param int $port Port + * @param string $username Username + * @param string $password Password + * + * @return \BeSimple\SoapClient\SoapClientBuilder + */ public function withProxy($host, $port, $username = null, $password = null) { $this->soapOptions['proxy_host'] = $host; @@ -131,6 +192,9 @@ class SoapClientBuilder extends AbstractSoapBuilder return $this; } + /** + * Validate options. + */ protected function validateOptions() { $this->validateWsdl(); diff --git a/src/BeSimple/SoapClient/SoapRequest.php b/src/BeSimple/SoapClient/SoapRequest.php index 28f2926..00580cb 100644 --- a/src/BeSimple/SoapClient/SoapRequest.php +++ b/src/BeSimple/SoapClient/SoapRequest.php @@ -12,186 +12,37 @@ namespace BeSimple\SoapClient; +use BeSimple\SoapCommon\SoapRequest as CommonSoapRequest; +use BeSimple\SoapCommon\SoapMessage; + /** - * @author Francis Besset + * SoapRequest class for SoapClient. Provides factory function for request object. + * + * @author Andreas Schamberger */ -class SoapRequest +class SoapRequest extends CommonSoapRequest { - protected $function; - protected $arguments; - protected $options; - protected $headers; - - public function __construct($function = null, array $arguments = array(), array $options = array(), array $headers = array()) - { - $this->function = $function; - $this->arguments = $arguments; - $this->options = $options; - $this->setHeaders($headers); - } - /** - * @return string The function name - */ - public function getFunction() - { - return $this->function; - } - - /** - * @param string The function name + * Factory function for SoapRequest. * - * @return SoapRequest - */ - public function setFunction($function) - { - $this->function = $function; - - return $this; - } - - /** - * @return array An array with all arguments - */ - public function getArguments() - { - return $this->arguments; - } - - /** - * @param string The name of the argument - * @param mixed The default value returned if the argument is not exists + * @param string $content Content + * @param string $location Location + * @param string $action SOAP action + * @param string $version SOAP version * - * @return mixed + * @return BeSimple\SoapClient\SoapRequest */ - public function getArgument($name, $default = null) + public static function create($content, $location, $action, $version) { - return $this->hasArgument($name) ? $this->arguments[$name] : $default; + $request = new SoapRequest(); + // $content is if unmodified from SoapClient not a php string type! + $request->setContent((string) $content); + $request->setLocation($location); + $request->setAction($action); + $request->setVersion($version); + $contentType = SoapMessage::getContentTypeForVersion($version); + $request->setContentType($contentType); + + return $request; } - - /** - * @param string The name of the argument - * - * @return boolean - */ - public function hasArgument($name) - { - return isset($this->arguments[$name]); - } - - /** - * @param array An array with arguments - * - * @return SoapRequest - */ - public function setArguments(array $arguments) - { - $this->arguments = $arguments; - - return $this; - } - - /** - * @param string The name of argument - * @param mixed The value of argument - * - * @return SoapRequest - */ - public function addArgument($name, $value) - { - $this->arguments[$name] = $value; - - return $this; - } - - /** - * @return array An array with all options - */ - public function getOptions() - { - return $this->options; - } - - /** - * @param string The name of the option - * @param mixed The default value returned if the option is not exists - * - * @return mixed - */ - public function getOption($name, $default = null) - { - return $this->hasOption($name) ? $this->options[$name] : $default; - } - - /** - * @param string The name of the option - * - * @return boolean - */ - public function hasOption($name) - { - return isset($this->options[$name]); - } - - /** - * @param array An array with options - * - * @return SoapRequest - */ - public function setOptions(array $options) - { - $this->options = $options; - - return $this; - } - - /** - * @return array|null - */ - public function getHeaders() - { - return empty($this->headers) ? null : $this->headers; - } - - /** - * @param array $headers - * - * @return SoapRequest - */ - public function setHeaders(array $headers) - { - $this->headers = array(); - - foreach ($headers as $header) { - $this->addHeader($header); - } - - return $this; - } - - /** - * @param \SoapHeader $header - * - * @return SoapRequest - */ - public function addHeader(\SoapHeader $header) - { - $this->headers[] = $header; - - return $this; - } - - /** - * @param string The name of option - * @param mixed The value of option - * - * @return SoapRequest - */ - public function addOption($name, $value) - { - $this->options[$name] = $value; - - return $this; - } - -} +} \ No newline at end of file diff --git a/src/BeSimple/SoapClient/SoapResponse.php b/src/BeSimple/SoapClient/SoapResponse.php new file mode 100644 index 0000000..24a12c3 --- /dev/null +++ b/src/BeSimple/SoapClient/SoapResponse.php @@ -0,0 +1,47 @@ + + * (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\SoapClient; + +use BeSimple\SoapCommon\SoapResponse as CommonSoapResponse; +use BeSimple\SoapCommon\SoapMessage; + +/** + * SoapResponse class for SoapClient. Provides factory function for response object. + * + * @author Andreas Schamberger + */ +class SoapResponse extends CommonSoapResponse +{ + /** + * Factory function for SoapResponse. + * + * @param string $content Content + * @param string $location Location + * @param string $action SOAP action + * @param string $version SOAP version + * @param string $contentType Content type header + * + * @return BeSimple\SoapClient\SoapResponse + */ + public static function create($content, $location, $action, $version, $contentType) + { + $response = new SoapResponse(); + $response->setContent($content); + $response->setLocation($location); + $response->setAction($action); + $response->setVersion($version); + $response->setContentType($contentType); + + return $response; + } +} \ No newline at end of file diff --git a/src/BeSimple/SoapClient/WsAddressingFilter.php b/src/BeSimple/SoapClient/WsAddressingFilter.php new file mode 100644 index 0000000..721fe87 --- /dev/null +++ b/src/BeSimple/SoapClient/WsAddressingFilter.php @@ -0,0 +1,347 @@ + + * (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\SoapClient; + +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; + +/** + * This plugin implements a subset of the following standards: + * * Web Services Addressing 1.0 - Core + * http://www.w3.org/TR/2006/REC-ws-addr-core + * * Web Services Addressing 1.0 - SOAP Binding + * http://www.w3.org/TR/ws-addr-soap + * + * Per default this plugin uses the SoapClient's $action and $location values + * for wsa:Action and wsa:To. Therefore the only REQUIRED property 'wsa:Action' + * is always set automatically. + * + * Limitation: wsa:From, wsa:FaultTo and wsa:ReplyTo only support the + * wsa:Address element of the endpoint reference at the moment. + * + * @author Andreas Schamberger + */ +class WsAddressingFilter implements SoapRequestFilter, SoapResponseFilter +{ + /** + * (2.1) Endpoint reference (EPR) anonymous default address. + * + * Some endpoints cannot be located with a meaningful IRI; this URI is used + * to allow such endpoints to send and receive messages. The precise meaning + * of this URI is defined by the binding of Addressing to a specific + * protocol and/or the context in which the EPR is used. + * + * @see http://www.w3.org/TR/2006/REC-ws-addr-core-20060509/#predefaddr + */ + const ENDPOINT_REFERENCE_ANONYMOUS = 'http://www.w3.org/2005/08/addressing/anonymous'; + + /** + * (2.1) Endpoint reference (EPR) address for discarting messages. + * + * Messages sent to EPRs whose [address] is this value MUST be discarded + * (i.e. not sent). This URI is typically used in EPRs that designate a + * reply or fault endpoint (see section 3.1 Abstract Property Definitions) + * to indicate that no reply or fault message should be sent. + * + * @see http://www.w3.org/TR/2006/REC-ws-addr-core-20060509/#predefaddr + */ + const ENDPOINT_REFERENCE_NONE = 'http://www.w3.org/2005/08/addressing/none'; + + /** + * (3.1) Predefined value for reply. + * + * Indicates that this is a reply to the message identified by the [message id] IRI. + * + * see http://www.w3.org/TR/2006/REC-ws-addr-core-20060509/#predefrels + */ + const RELATIONSHIP_TYPE_REPLY = 'http://www.w3.org/2005/08/addressing/reply'; + + /** + * FaultTo. + * + * @var string + */ + protected $faultTo; + + /** + * From. + * + * @var string + */ + protected $from; + + /** + * MessageId. + * + * @var string + */ + protected $messageId; + + /** + * List of reference parameters associated with this soap message. + * + * @var unknown_type + */ + protected $referenceParametersSet = array(); + + /** + * List of reference parameters recieved with this soap message. + * + * @var unknown_type + */ + protected $referenceParametersRecieved = array(); + + /** + * RelatesTo. + * + * @var string + */ + protected $relatesTo; + + /** + * RelatesTo@RelationshipType. + * + * @var string + */ + protected $relatesToRelationshipType; + + /** + * ReplyTo. + * + * @var string + */ + protected $replyTo; + + /** + * Add additional reference parameters + * + * @param string $ns Namespace URI + * @param string $pfx Namespace prefix + * @param string $parameter Parameter name + * @param string $value Parameter value + * + * @return void + */ + public function addReferenceParameter($ns, $pfx, $parameter, $value) + { + $this->referenceParametersSet[] = array( + 'ns' => $ns, + 'pfx' => $pfx, + 'parameter' => $parameter, + 'value' => $value, + ); + } + + /** + * Get additional reference parameters. + * + * @param string $ns Namespace URI + * @param string $parameter Parameter name + * + * @return string|null + */ + public function getReferenceParameter($ns, $parameter) + { + if (isset($this->referenceParametersRecieved[$ns][$parameter])) { + + return $this->referenceParametersRecieved[$ns][$parameter]; + } + + return null; + } + + /** + * Reset all properties to default values. + */ + public function resetFilter() + { + $this->faultTo = null; + $this->from = null; + $this->messageId = null; + $this->referenceParametersRecieved = array(); + $this->referenceParametersSet = array(); + $this->relatesTo = null; + $this->relatesToRelationshipType = null; + $this->replyTo = null; + } + + /** + * Set FaultTo address of type xs:anyURI. + * + * @param string $faultTo xs:anyURI + * + * @return void + */ + public function setFaultTo($faultTo) + { + $this->faultTo = $faultTo; + } + + /** + * Set From address of type xs:anyURI. + * + * @param string $from xs:anyURI + * + * @return void + */ + public function setFrom($from) + { + $this->from = $from; + } + + /** + * Set MessageId of type xs:anyURI. + * Default: UUID v4 e.g. 'uuid:550e8400-e29b-11d4-a716-446655440000' + * + * @param string $messageId xs:anyURI + * + * @return void + */ + public function setMessageId($messageId = null) + { + if (null === $messageId) { + $messageId = 'uuid:' . Helper::generateUUID(); + } + $this->messageId = $messageId; + } + + /** + * Set RelatesTo of type xs:anyURI with the optional relationType + * (of type xs:anyURI). + * + * @param string $relatesTo xs:anyURI + * @param string $relationType xs:anyURI + * + * @return void + */ + public function setRelatesTo($relatesTo, $relationType = null) + { + $this->relatesTo = $relatesTo; + if (null !== $relationType && $relationType != self::RELATIONSHIP_TYPE_REPLY) { + $this->relatesToRelationshipType = $relationType; + } + } + + /** + * Set ReplyTo address of type xs:anyURI + * Default: self::ENDPOINT_REFERENCE_ANONYMOUS + * + * @param string $replyTo xs:anyURI + * + * @return void + */ + public function setReplyTo($replyTo = null) + { + if (null === $replyTo) { + $replyTo = self::ENDPOINT_REFERENCE_ANONYMOUS; + } + $this->replyTo = $replyTo; + } + + /** + * 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(); + + // create FilterHelper + $filterHelper = new FilterHelper($dom); + + // add the neccessary namespaces + $filterHelper->addNamespace(Helper::PFX_WSA, Helper::NS_WSA); + + $action = $filterHelper->createElement(Helper::NS_WSA, 'Action', $request->getAction()); + $filterHelper->addHeaderElement($action); + + $to = $filterHelper->createElement(Helper::NS_WSA, 'To', $request->getLocation()); + $filterHelper->addHeaderElement($to); + + if (null !== $this->faultTo) { + $faultTo = $filterHelper->createElement(Helper::NS_WSA, 'FaultTo'); + $filterHelper->addHeaderElement($faultTo); + + $address = $filterHelper->createElement(Helper::NS_WSA, 'Address', $this->faultTo); + $faultTo->appendChild($address); + } + + if (null !== $this->from) { + $from = $filterHelper->createElement(Helper::NS_WSA, 'From'); + $filterHelper->addHeaderElement($from); + + $address = $filterHelper->createElement(Helper::NS_WSA, 'Address', $this->from); + $from->appendChild($address); + } + + if (null !== $this->messageId) { + $messageId = $filterHelper->createElement(Helper::NS_WSA, 'MessageID', $this->messageId); + $filterHelper->addHeaderElement($messageId); + } + + if (null !== $this->relatesTo) { + $relatesTo = $filterHelper->createElement(Helper::NS_WSA, 'RelatesTo', $this->relatesTo); + if (null !== $this->relatesToRelationshipType) { + $filterHelper->setAttribute($relatesTo, Helper::NS_WSA, 'RelationshipType', $this->relatesToRelationshipType); + } + $filterHelper->addHeaderElement($relatesTo); + } + + if (null !== $this->replyTo) { + $replyTo = $filterHelper->createElement(Helper::NS_WSA, 'ReplyTo'); + $filterHelper->addHeaderElement($replyTo); + + $address = $filterHelper->createElement(Helper::NS_WSA, 'Address', $this->replyTo); + $replyTo->appendChild($address); + } + + foreach ($this->referenceParametersSet as $rp) { + $filterHelper->addNamespace($rp['pfx'], $rp['ns']); + $parameter = $filterHelper->createElement($rp['ns'], $rp['parameter'], $rp['value']); + $filterHelper->setAttribute($parameter, Helper::NS_WSA, 'IsReferenceParameter', 'true'); + $filterHelper->addHeaderElement($parameter); + } + } + + /** + * Modify the given response XML. + * + * @param \BeSimple\SoapCommon\SoapResponse $response SOAP response + * + * @return void + */ + public function filterResponse(CommonSoapResponse $response) + { + // get \DOMDocument from SOAP response + $dom = $response->getContentDocument(); + + $this->referenceParametersRecieved = array(); + $referenceParameters = $dom->getElementsByTagNameNS(Helper::NS_WSA, 'ReferenceParameters')->item(0); + if (null !== $referenceParameters) { + foreach ($referenceParameters->childNodes as $childNode) { + if (!isset($this->referenceParametersRecieved[$childNode->namespaceURI])) { + $this->referenceParametersRecieved[$childNode->namespaceURI] = array(); + } + $this->referenceParametersRecieved[$childNode->namespaceURI][$childNode->localName] = $childNode->nodeValue; + } + } + } +} \ No newline at end of file diff --git a/src/BeSimple/SoapClient/WsSecurityFilter.php b/src/BeSimple/SoapClient/WsSecurityFilter.php new file mode 100644 index 0000000..cb3fc7c --- /dev/null +++ b/src/BeSimple/SoapClient/WsSecurityFilter.php @@ -0,0 +1,581 @@ + + * (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\SoapClient; + +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'; + + /** + * (UT 3.1) Password type: plain text. + */ + const PASSWORD_TYPE_TEXT = 0; + + /** + * (UT 3.1) Password type: digest. + */ + 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. + * + * @var string + */ + protected $password; + + /** + * (UT 3.1) Password type: text or digest. + * + * @var int + */ + 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. + * + * @var string + */ + protected $username; + + /** + * 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; + } + + /** + * Add user data. + * + * @param string $username Username + * @param string $password Password + * @param int $passwordType self::PASSWORD_TYPE_DIGEST | self::PASSWORD_TYPE_TEXT + * + * @return void + */ + public function addUserData($username, $password = null, $passwordType = self::PASSWORD_TYPE_DIGEST) + { + $this->username = $username; + $this->password = $password; + $this->passwordType = $passwordType; + } + + /** + * Reset all properties to default values. + */ + 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; + } + + /** + * 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(); + + // 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, $request->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->username) { + $usernameToken = $filterHelper->createElement(Helper::NS_WSS, 'UsernameToken'); + $security->appendChild($usernameToken); + + $username = $filterHelper->createElement(Helper::NS_WSS, 'Username', $this->username); + $usernameToken->appendChild($username); + + if (null !== $this->password + && (null === $this->userSecurityKey + || (null !== $this->userSecurityKey && !$this->userSecurityKey->hasPrivateKey()))) { + + if (self::PASSWORD_TYPE_DIGEST === $this->passwordType) { + $nonce = mt_rand(); + $password = base64_encode(sha1($nonce . $createdTimestamp . $this->password, true)); + $passwordType = Helper::NAME_WSS_UTP . '#PasswordDigest'; + } else { + $password = $this->password; + $passwordType = Helper::NAME_WSS_UTP . '#PasswordText'; + } + $password = $filterHelper->createElement(Helper::NS_WSS, 'Password', $password); + $filterHelper->setAttribute($password, null, 'Type', $passwordType); + $usernameToken->appendChild($password); + if (self::PASSWORD_TYPE_DIGEST === $this->passwordType) { + $nonce = $filterHelper->createElement(Helper::NS_WSS, 'Nonce', base64_encode($nonce)); + $usernameToken->appendChild($nonce); + + $created = $filterHelper->createElement(Helper::NS_WSU, 'Created', $createdTimestamp); + $usernameToken->appendChild($created); + } + } + } + + 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->userSecurityKey->getPublicKey()); + } + $nodes = $this->createNodeListForSigning($dom, $security); + $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, + ); + foreach ($nodes as $node) { + XmlSecurityDSig::addNodeToSignature($signature, $node, XmlSecurityDSig::SHA1, XmlSecurityDSig::EXC_C14N, $options); + } + XmlSecurityDSig::signDocument($signature, $this->userSecurityKey->getPrivateKey(), XmlSecurityDSig::EXC_C14N); + + $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); + + // 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); + } + } + } + } + + /** + * 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(); + + // locate security header + $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); + } + // 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) { + // 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'); + } + } + } + } + + /** + * 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, XmlSecurityKey::TYPE_PRIVATE); + } else { + //$valueType = $key->getAttribute('ValueType'); + + return $this->serviceSecurityKey->getPublicKey(); + } + } + } + } + + return null; + } +} \ No newline at end of file diff --git a/src/BeSimple/SoapClient/WsdlDownloader.php b/src/BeSimple/SoapClient/WsdlDownloader.php new file mode 100644 index 0000000..453eaba --- /dev/null +++ b/src/BeSimple/SoapClient/WsdlDownloader.php @@ -0,0 +1,259 @@ + + * (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\SoapClient; + +use BeSimple\SoapCommon\Helper; + +/** + * Downloads WSDL files with cURL. Uses the WSDL_CACHE_* constants and the + * 'soap.wsdl_*' ini settings. Does only file caching as SoapClient only + * supports a file name parameter. The class also resolves remote XML schema + * includes. + * + * @author Andreas Schamberger + */ +class WsdlDownloader +{ + /** + * Cache enabled. + * + * @var bool + */ + protected $cacheEnabled; + + /** + * Cache dir. + * + * @var string + */ + protected $cacheDir; + + /** + * Cache TTL. + * + * @var int + */ + protected $cacheTtl; + + /** + * cURL instance for downloads. + * + * @var unknown_type + */ + protected $curl; + + /** + * Resolve WSDl/XSD includes. + * + * @var boolean + */ + protected $resolveRemoteIncludes = true; + + /** + * Constructor. + * + * @param \BeSimple\SoapClient\Curl $curl Curl instance + * @param boolean $resolveRemoteIncludes WSDL/XSD include enabled? + * @param boolean $cacheWsdl Cache constant + */ + public function __construct(Curl $curl, $resolveRemoteIncludes = true, $cacheWsdl = WSDL_CACHE_DISK) + { + $this->curl = $curl; + $this->resolveRemoteIncludes = $resolveRemoteIncludes; + // get current WSDL caching config + $this->cacheEnabled = (bool) ini_get('soap.wsdl_cache_enabled'); + if ($this->cacheEnabled === true + && $cacheWsdl === WSDL_CACHE_NONE) { + $this->cacheEnabled = false; + } + $this->cacheDir = ini_get('soap.wsdl_cache_dir'); + if (!is_dir($this->cacheDir)) { + $this->cacheDir = sys_get_temp_dir(); + } + $this->cacheDir = rtrim($this->cacheDir, '/\\'); + $this->cacheTtl = ini_get('soap.wsdl_cache_ttl'); + } + + /** + * Download given WSDL file and return name of cache file. + * + * @param string $wsdl WSDL file URL/path + * + * @return string + */ + public function download($wsdl) + { + // download and cache remote WSDL files or local ones where we want to + // resolve remote XSD includes + $isRemoteFile = $this->isRemoteFile($wsdl); + if ($isRemoteFile === true || $this->resolveRemoteIncludes === true) { + $cacheFile = $this->cacheDir . DIRECTORY_SEPARATOR . 'wsdl_' . md5($wsdl) . '.cache'; + if ($this->cacheEnabled === false + || !file_exists($cacheFile) + || (filemtime($cacheFile) + $this->cacheTtl) < time()) { + if ($isRemoteFile === true) { + // execute request + $responseSuccessfull = $this->curl->exec($wsdl); + // get content + if ($responseSuccessfull === true) { + $response = $this->curl->getResponseBody(); + if ($this->resolveRemoteIncludes === true) { + $this->resolveRemoteIncludes($response, $cacheFile, $wsdl); + } else { + file_put_contents($cacheFile, $response); + } + } else { + throw new \ErrorException("SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'"); + } + } elseif (file_exists($wsdl)) { + $response = file_get_contents($wsdl); + $this->resolveRemoteIncludes($response, $cacheFile); + } else { + throw new \ErrorException("SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'"); + } + } + + return $cacheFile; + } elseif (file_exists($wsdl)) { + + return realpath($wsdl); + } else { + throw new \ErrorException("SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'"); + } + } + + /** + * Do we have a remote file? + * + * @param string $file File URL/path + * + * @return boolean + */ + private function isRemoteFile($file) + { + $isRemoteFile = false; + // @parse_url to suppress E_WARNING for invalid urls + if (($url = @parse_url($file)) !== false) { + if (isset($url['scheme']) && substr($url['scheme'], 0, 4) == 'http') { + $isRemoteFile = true; + } + } + + return $isRemoteFile; + } + + /** + * Resolves remote WSDL/XSD includes within the WSDL files. + * + * @param string $xml XML file + * @param string $cacheFile Cache file name + * @param boolean $parentFile Parent file name + * + * @return void + */ + private function resolveRemoteIncludes($xml, $cacheFile, $parentFile = null) + { + $doc = new \DOMDocument(); + $doc->loadXML($xml); + $xpath = new \DOMXPath($doc); + $xpath->registerNamespace(Helper::PFX_XML_SCHEMA, Helper::NS_XML_SCHEMA); + $xpath->registerNamespace(Helper::PFX_WSDL, Helper::NS_WSDL); + // WSDL include/import + $query = './/' . Helper::PFX_WSDL . ':include | .//' . Helper::PFX_WSDL . ':import'; + $nodes = $xpath->query($query); + if ($nodes->length > 0) { + foreach ($nodes as $node) { + $location = $node->getAttribute('location'); + if ($this->isRemoteFile($location)) { + $location = $this->download($location); + $node->setAttribute('location', $location); + } elseif (!is_null($parentFile)) { + $location = $this->resolveRelativePathInUrl($parentFile, $location); + $location = $this->download($location); + $node->setAttribute('location', $location); + } + } + } + // XML schema include/import + $query = './/' . Helper::PFX_XML_SCHEMA . ':include | .//' . Helper::PFX_XML_SCHEMA . ':import'; + $nodes = $xpath->query($query); + if ($nodes->length > 0) { + foreach ($nodes as $node) { + $schemaLocation = $node->getAttribute('schemaLocation'); + if ($this->isRemoteFile($schemaLocation)) { + $schemaLocation = $this->download($schemaLocation); + $node->setAttribute('schemaLocation', $schemaLocation); + } elseif (!is_null($parentFile)) { + $schemaLocation = $this->resolveRelativePathInUrl($parentFile, $schemaLocation); + $schemaLocation = $this->download($schemaLocation); + $node->setAttribute('schemaLocation', $schemaLocation); + } + } + } + $doc->save($cacheFile); + } + + /** + * Resolves the relative path to base into an absolute. + * + * @param string $base Base path + * @param string $relative Relative path + * + * @return string + */ + private function resolveRelativePathInUrl($base, $relative) + { + $urlParts = parse_url($base); + // combine base path with relative path + if (isset($urlParts['path']) && strpos($relative, '/') === 0) { + // $relative is absolute path from domain (starts with /) + $path = $relative; + } elseif (isset($urlParts['path']) && strrpos($urlParts['path'], '/') === (strlen($urlParts['path']) )) { + // base path is directory + $path = $urlParts['path'] . $relative; + } elseif (isset($urlParts['path'])) { + // strip filename from base path + $path = substr($urlParts['path'], 0, strrpos($urlParts['path'], '/')) . '/' . $relative; + } else { + // no base path + $path = '/' . $relative; + } + // foo/./bar ==> foo/bar + $path = preg_replace('~/\./~', '/', $path); + // remove double slashes + $path = preg_replace('~/+~', '/', $path); + // split path by '/' + $parts = explode('/', $path); + // resolve /../ + foreach ($parts as $key => $part) { + if ($part == "..") { + $keyToDelete = $key-1; + while ($keyToDelete > 0) { + if (isset($parts[$keyToDelete])) { + unset($parts[$keyToDelete]); + break; + } else { + $keyToDelete--; + } + } + unset($parts[$key]); + } + } + $hostname = $urlParts['scheme'] . '://' . $urlParts['host']; + if (isset($urlParts['port'])) { + $hostname .= ':' . $urlParts['port']; + } + + return $hostname . implode('/', $parts); + } +} \ No newline at end of file diff --git a/tests/AxisInterop/MTOM.php b/tests/AxisInterop/MTOM.php new file mode 100644 index 0000000..cbfe886 --- /dev/null +++ b/tests/AxisInterop/MTOM.php @@ -0,0 +1,47 @@ +'; + +$options = array( + 'soap_version' => 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, +); + +/* + * 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 { + + $attachment = new stdClass(); + $attachment->fileName = 'test123.txt'; + $attachment->binaryData = 'This is a test.'; + + 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/tests/AxisInterop/MTOM.wsdl b/tests/AxisInterop/MTOM.wsdl new file mode 100644 index 0000000..178ee35 --- /dev/null +++ b/tests/AxisInterop/MTOM.wsdl @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/AxisInterop/SwA.php b/tests/AxisInterop/SwA.php new file mode 100644 index 0000000..0cf5b77 --- /dev/null +++ b/tests/AxisInterop/SwA.php @@ -0,0 +1,86 @@ +'; + +$options = array( + 'soap_version' => 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/tests/AxisInterop/SwA.wsdl b/tests/AxisInterop/SwA.wsdl new file mode 100644 index 0000000..a4de7e0 --- /dev/null +++ b/tests/AxisInterop/SwA.wsdl @@ -0,0 +1,162 @@ + + + BeSimpleSwaService + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/AxisInterop/SwA/build.xml b/tests/AxisInterop/SwA/build.xml new file mode 100644 index 0000000..f5261ed --- /dev/null +++ b/tests/AxisInterop/SwA/build.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/AxisInterop/SwA/resources/META-INF/services.xml b/tests/AxisInterop/SwA/resources/META-INF/services.xml new file mode 100644 index 0000000..8b49f87 --- /dev/null +++ b/tests/AxisInterop/SwA/resources/META-INF/services.xml @@ -0,0 +1,15 @@ + + + BeSimple test service for SwA. + true + besimple.service.BeSimpleSwaService + + urn:uploadFile + + + + urn:downloadFile + + + + diff --git a/tests/AxisInterop/SwA/src/besimple/service/BeSimpleSwaService.java b/tests/AxisInterop/SwA/src/besimple/service/BeSimpleSwaService.java new file mode 100644 index 0000000..b173e15 --- /dev/null +++ b/tests/AxisInterop/SwA/src/besimple/service/BeSimpleSwaService.java @@ -0,0 +1,78 @@ +package besimple.service; + +import java.io.File; +import java.io.FileOutputStream; + +import javax.xml.namespace.QName; + +import javax.activation.DataHandler; +import javax.activation.FileDataSource; + +import org.apache.axiom.attachments.Attachments; +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.om.OMAttribute; +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMFactory; +import org.apache.axiom.om.OMNamespace; + +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.context.OperationContext; +import org.apache.axis2.wsdl.WSDLConstants; + +public class BeSimpleSwaService { + + String namespace = "http://service.besimple"; + + public OMElement uploadFile(OMElement element) throws Exception { + OMElement dataElement = (OMElement)element.getFirstChildWithName(new QName(namespace, "data")); + OMAttribute hrefAttribute = dataElement.getAttribute(new QName("href")); + + String contentID = hrefAttribute.getAttributeValue(); + contentID = contentID.trim(); + if (contentID.substring(0, 3).equalsIgnoreCase("cid")) { + contentID = contentID.substring(4); + } + OMElement nameElement = (OMElement)element.getFirstChildWithName(new QName(namespace, "name")); + String name = nameElement.getText(); + + MessageContext msgCtx = MessageContext.getCurrentMessageContext(); + Attachments attachment = msgCtx.getAttachmentMap(); + DataHandler dataHandler = attachment.getDataHandler(contentID); + + File file = new File(name); + FileOutputStream fileOutputStream = new FileOutputStream(file); + dataHandler.writeTo(fileOutputStream); + fileOutputStream.flush(); + fileOutputStream.close(); + + OMFactory factory = OMAbstractFactory.getOMFactory(); + OMNamespace omNs = factory.createOMNamespace(namespace, "swa"); + OMElement wrapperElement = factory.createOMElement("uploadFileResponse", omNs); + OMElement returnElement = factory.createOMElement("return", omNs, wrapperElement); + returnElement.setText("File saved succesfully."); + + return wrapperElement; + } + + public OMElement downloadFile(OMElement element) throws Exception { + OMElement nameElement = (OMElement)element.getFirstChildWithName(new QName(namespace, "name")); + String name = nameElement.getText(); + + MessageContext msgCtxIn = MessageContext.getCurrentMessageContext(); + OperationContext operationContext = msgCtxIn.getOperationContext(); + MessageContext msgCtxOut = operationContext.getMessageContext(WSDLConstants.MESSAGE_LABEL_OUT_VALUE); + + FileDataSource fileDataSource = new FileDataSource(name); + DataHandler dataHandler = new DataHandler(fileDataSource); + + String contentID = "cid:" + msgCtxOut.addAttachment(dataHandler); + + OMFactory factory = OMAbstractFactory.getOMFactory(); + OMNamespace omNs = factory.createOMNamespace(namespace, "swa"); + OMElement wrapperElement = factory.createOMElement("downloadFileResponse", omNs); + OMElement dataElement = factory.createOMElement("data", omNs, wrapperElement); + dataElement.addAttribute("href", contentID, null); + + return wrapperElement; + } +} diff --git a/tests/AxisInterop/WsAddressing.php b/tests/AxisInterop/WsAddressing.php new file mode 100644 index 0000000..9ec1bcc --- /dev/null +++ b/tests/AxisInterop/WsAddressing.php @@ -0,0 +1,75 @@ +'; + +$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/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/tests/AxisInterop/WsSecuritySigEnc.php b/tests/AxisInterop/WsSecuritySigEnc.php new file mode 100644 index 0000000..d6a31fb --- /dev/null +++ b/tests/AxisInterop/WsSecuritySigEnc.php @@ -0,0 +1,116 @@ +'; + +$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/tests/AxisInterop/WsSecuritySigEnc.wsdl b/tests/AxisInterop/WsSecuritySigEnc.wsdl new file mode 100644 index 0000000..620ea51 --- /dev/null +++ b/tests/AxisInterop/WsSecuritySigEnc.wsdl @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/AxisInterop/WsSecurityUserPass.php b/tests/AxisInterop/WsSecurityUserPass.php new file mode 100644 index 0000000..595d9ba --- /dev/null +++ b/tests/AxisInterop/WsSecurityUserPass.php @@ -0,0 +1,81 @@ +'; + +$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-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/tests/AxisInterop/WsSecurityUserPass.wsdl b/tests/AxisInterop/WsSecurityUserPass.wsdl new file mode 100644 index 0000000..6e72411 --- /dev/null +++ b/tests/AxisInterop/WsSecurityUserPass.wsdl @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/AxisInterop/axis_services/besimple-swa.aar b/tests/AxisInterop/axis_services/besimple-swa.aar new file mode 100644 index 0000000..bb41750 Binary files /dev/null and b/tests/AxisInterop/axis_services/besimple-swa.aar differ diff --git a/tests/AxisInterop/axis_services/library-signencr.aar b/tests/AxisInterop/axis_services/library-signencr.aar new file mode 100644 index 0000000..25ffc2d Binary files /dev/null and b/tests/AxisInterop/axis_services/library-signencr.aar differ diff --git a/tests/AxisInterop/axis_services/library-username-digest.aar b/tests/AxisInterop/axis_services/library-username-digest.aar new file mode 100644 index 0000000..f6f8595 Binary files /dev/null and b/tests/AxisInterop/axis_services/library-username-digest.aar differ diff --git a/tests/AxisInterop/axis_services/sample-mtom.aar b/tests/AxisInterop/axis_services/sample-mtom.aar new file mode 100644 index 0000000..a215edc Binary files /dev/null and b/tests/AxisInterop/axis_services/sample-mtom.aar differ diff --git a/tests/AxisInterop/axis_services/version2.aar b/tests/AxisInterop/axis_services/version2.aar new file mode 100644 index 0000000..df76662 Binary files /dev/null and b/tests/AxisInterop/axis_services/version2.aar differ diff --git a/tests/AxisInterop/clientcert.pem b/tests/AxisInterop/clientcert.pem new file mode 100644 index 0000000..f433d48 --- /dev/null +++ b/tests/AxisInterop/clientcert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICoDCCAgkCBEnhw2IwDQYJKoZIhvcNAQEFBQAwgZYxCzAJBgNVBAYTAk5aMRMw +EQYDVQQIEwpXZWxsaW5ndG9uMRowGAYDVQQHExFQYXJhcGFyYXVtdSBCZWFjaDEq +MCgGA1UEChMhU29zbm9za2kgU29mdHdhcmUgQXNzb2NpYXRlcyBMdGQuMRAwDgYD +VQQLEwdVbmtub3duMRgwFgYDVQQDEw9EZW5uaXMgU29zbm9za2kwHhcNMDkwNDEy +MTAzMzA2WhcNMzYwODI3MTAzMzA2WjCBljELMAkGA1UEBhMCTloxEzARBgNVBAgT +CldlbGxpbmd0b24xGjAYBgNVBAcTEVBhcmFwYXJhdW11IEJlYWNoMSowKAYDVQQK +EyFTb3Nub3NraSBTb2Z0d2FyZSBBc3NvY2lhdGVzIEx0ZC4xEDAOBgNVBAsTB1Vu +a25vd24xGDAWBgNVBAMTD0Rlbm5pcyBTb3Nub3NraTCBnzANBgkqhkiG9w0BAQEF +AAOBjQAwgYkCgYEAhOVyNK8xyxtb4DnKtU6mF9KoiFqCk7eKoLE26+9h410CtTkx +zWAfgnR+8i+LPbdsPY+yXAo6NYpCCKolXfDLe+AG2GwnMZGrIl6+BLF3hqTmIXBF +TLGUmC7A7uBTivaWgdH1w3hb33rASoVU67BVtQ3QQi99juZX4vU9o9pScocCAwEA +ATANBgkqhkiG9w0BAQUFAAOBgQBMNPo1KAGbz8Jl6HGbtAcetieSJ3bEAXmv1tcj +ysBS67AXzdu1Ac+onHh2EpzBM7kuGbw+trU+AhulooPpewIQRApXP1F0KHRDcbqW +jwvknS6HnomN9572giLGKn2601bHiRUj35hiA8aLmMUBppIRPFFAoQ0QUBCPx+m8 +/0n33w== +-----END CERTIFICATE----- diff --git a/tests/AxisInterop/clientkey.pem b/tests/AxisInterop/clientkey.pem new file mode 100644 index 0000000..a47f923 --- /dev/null +++ b/tests/AxisInterop/clientkey.pem @@ -0,0 +1,14 @@ +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAITlcjSvMcsbW+A5yrVOphfSqIha +gpO3iqCxNuvvYeNdArU5Mc1gH4J0fvIviz23bD2PslwKOjWKQgiqJV3wy3vgBthsJzGRqyJevgSx +d4ak5iFwRUyxlJguwO7gU4r2loHR9cN4W996wEqFVOuwVbUN0EIvfY7mV+L1PaPaUnKHAgMBAAEC +gYAZ6UqtLwN8YGc3fs0hMKZ9upsViuAuwPiMgED/G3twgzAF+ZLWQkmie+hMfCyf6eV200+pVm0n +Bz/8xH/oowxpX0Kk3szoB4vFghjU84GKUcrbhu/NRIm7l3drnfbzqhQkHDCx6n1CotI4Gs49cDWu +4uEAuxJkEIVY553unZjZgQJBAOJVIallNKmD0iQlvtWRmRzpmYDjt9vhNY6WBTIOx6SDn9SRaoSA +fkipQ2HXo04r78TQ674+zfZ1lRTkFG7px6ECQQCWUPHp3pSZOM1oGzJrNvNaw+MizZAZjq34npHm +9GRquFLG7BlCaI9QNGE7pN2ryYsYCRUMaM2e4GR0tUXxVGknAkAgrxqFU9AfCqI2Bh1gyf3KZxF7 +w2axofwR8ygc6nV6FGfoUneHWubhp0/LuVAj4cRmL6Vbe8ZSaPh2Y9lviuMBAkEAicP8Q+1E4j1m +PPEYP51oYprANOiUFmhnWEL00+jPk+QFsd03tV6hYs/vAbwzkjuwqMHCMdJoCiH8z95IEUvc5wJA +MvLOuZdu4dmhOXg/YKsbMSPjFNEVskLQNSXqw6O2wIrpPg1NQvBBAOTbiuZj3vind4VPos1wc4vB +QocvdUC6dA== +-----END PRIVATE KEY----- diff --git a/tests/AxisInterop/image.jpg b/tests/AxisInterop/image.jpg new file mode 100644 index 0000000..4331bf5 Binary files /dev/null and b/tests/AxisInterop/image.jpg differ diff --git a/tests/AxisInterop/servercert.pem b/tests/AxisInterop/servercert.pem new file mode 100644 index 0000000..040b22c --- /dev/null +++ b/tests/AxisInterop/servercert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICoDCCAgkCBEnhwzMwDQYJKoZIhvcNAQEFBQAwgZYxCzAJBgNVBAYTAk5aMRMw +EQYDVQQIEwpXZWxsaW5ndG9uMRowGAYDVQQHExFQYXJhcGFyYXVtdSBCZWFjaDEq +MCgGA1UEChMhU29zbm9za2kgU29mdHdhcmUgQXNzb2NpYXRlcyBMdGQuMRAwDgYD +VQQLEwdVbmtub3duMRgwFgYDVQQDEw9EZW5uaXMgU29zbm9za2kwHhcNMDkwNDEy +MTAzMjE5WhcNMzYwODI3MTAzMjE5WjCBljELMAkGA1UEBhMCTloxEzARBgNVBAgT +CldlbGxpbmd0b24xGjAYBgNVBAcTEVBhcmFwYXJhdW11IEJlYWNoMSowKAYDVQQK +EyFTb3Nub3NraSBTb2Z0d2FyZSBBc3NvY2lhdGVzIEx0ZC4xEDAOBgNVBAsTB1Vu +a25vd24xGDAWBgNVBAMTD0Rlbm5pcyBTb3Nub3NraTCBnzANBgkqhkiG9w0BAQEF +AAOBjQAwgYkCgYEA1H3mjQCF9uce2jmm/Yq9kE4ytfvkp4c8G90cDfJXJvOiGQds +p2vDZXKuCkHQ7vsBBXPNTt8J/d8ZbEwyuB9Ccz5pJqi6Ig6Y2/mEsPthDyh5SrJV +yQ/wxUGwmfSuwdrIMnplMTq+OR9BOfT3CvjSvuy9d6BQNo4wOMkDvmZTtI8CAwEA +ATANBgkqhkiG9w0BAQUFAAOBgQCqv4475QaqlKcN2QCZJbLVKZEX+76XLQurGkgf +2fCgesRHjfUfOHyTTlhWQdEKTcBB2XviUyyW6I//fmKfXUIiQqvgh4LHdXRPEXDf +Y9nr89MjyQpDlnl6AlrvSej30a9iwVRUeVk4d6gxWHMRonKBFgh+TGexxUXHtPkf +B1Pdtg== +-----END CERTIFICATE----- diff --git a/tests/BeSimple/Tests/SoapClient/CurlTest.php b/tests/BeSimple/Tests/SoapClient/CurlTest.php new file mode 100644 index 0000000..b940b2a --- /dev/null +++ b/tests/BeSimple/Tests/SoapClient/CurlTest.php @@ -0,0 +1,180 @@ + + * (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\SoapClient; + +use BeSimple\SoapClient\Curl; + +/** + * @author Andreas Schamberger + */ +class CurlTest extends \PHPUnit_Framework_TestCase +{ + protected $webserverProcessId; + + protected function startPhpWebserver() + { + if ('Windows' == substr(php_uname('s'), 0, 7 )) { + $powershellCommand = "\$app = start-process php.exe -ArgumentList '-S localhost:8000 -t ".__DIR__.DIRECTORY_SEPARATOR."Fixtures' -WindowStyle 'Hidden' -passthru; Echo \$app.Id;"; + $shellCommand = 'powershell -command "& {'.$powershellCommand.'}"'; + } else { + $shellCommand = "nohup php -S localhost:8000 -t ".__DIR__.DIRECTORY_SEPARATOR."Fixtures &"; + } + $output = array(); + exec($shellCommand, $output); + $this->webserverProcessId = $output[0]; // pid is in first element + } + + protected function stopPhpWebserver() + { + if (!is_null($this->webserverProcessId)) { + if ('Windows' == substr(php_uname('s'), 0, 7 )) { + exec('TASKKILL /F /PID ' . $this->webserverProcessId); + } else { + exec('kill ' . $this->webserverProcessId); + } + $this->webserverProcessId = null; + } + } + + public function testExec() + { + $this->startPhpWebserver(); + + $curl = new Curl(); + + $this->assertTrue($curl->exec('http://localhost:8000/curl.txt')); + $this->assertTrue($curl->exec('http://localhost:8000/404.txt')); + + $this->stopPhpWebserver(); + } + + public function testGetErrorMessage() + { + $this->startPhpWebserver(); + + $curl = new Curl(); + + $curl->exec('http://unknown/curl.txt'); + $this->assertEquals('Could not connect to host', $curl->getErrorMessage()); + + $curl->exec('xyz://localhost:8000/@404.txt'); + $this->assertEquals('Unknown protocol. Only http and https are allowed.', $curl->getErrorMessage()); + + $curl->exec(''); + $this->assertEquals('Unable to parse URL', $curl->getErrorMessage()); + + $this->stopPhpWebserver(); + } + + public function testGetRequestHeaders() + { + $this->startPhpWebserver(); + + $curl = new Curl(); + + $curl->exec('http://localhost:8000/curl.txt'); + $this->assertEquals(136, strlen($curl->getRequestHeaders())); + + $curl->exec('http://localhost:8000/404.txt'); + $this->assertEquals(135, strlen($curl->getRequestHeaders())); + + $this->stopPhpWebserver(); + } + + public function testGetResponse() + { + $this->startPhpWebserver(); + + $curl = new Curl(); + + $curl->exec('http://localhost:8000/curl.txt'); + $this->assertEquals(150, strlen($curl->getResponse())); + + $curl->exec('http://localhost:8000/404.txt'); + $this->assertEquals(1282, strlen($curl->getResponse())); + + $this->stopPhpWebserver(); + } + + public function testGetResponseBody() + { + $this->startPhpWebserver(); + + $curl = new Curl(); + + $curl->exec('http://localhost:8000/curl.txt'); + $this->assertEquals('This is a testfile for cURL.', $curl->getResponseBody()); + + $this->stopPhpWebserver(); + } + + public function testGetResponseContentType() + { + $this->startPhpWebserver(); + + $curl = new Curl(); + + $curl->exec('http://localhost:8000/curl.txt'); + $this->assertEquals('text/plain; charset=UTF-8', $curl->getResponseContentType()); + + $curl->exec('http://localhost:8000/404.txt'); + $this->assertEquals('text/html; charset=UTF-8', $curl->getResponseContentType()); + + $this->stopPhpWebserver(); + } + + public function testGetResponseHeaders() + { + $this->startPhpWebserver(); + + $curl = new Curl(); + + $curl->exec('http://localhost:8000/curl.txt'); + $this->assertEquals(122, strlen($curl->getResponseHeaders())); + + $curl->exec('http://localhost:8000/404.txt'); + $this->assertEquals(130, strlen($curl->getResponseHeaders())); + + $this->stopPhpWebserver(); + } + + public function testGetResponseStatusCode() + { + $this->startPhpWebserver(); + + $curl = new Curl(); + + $curl->exec('http://localhost:8000/curl.txt'); + $this->assertEquals(200, $curl->getResponseStatusCode()); + + $curl->exec('http://localhost:8000/404.txt'); + $this->assertEquals(404, $curl->getResponseStatusCode()); + + $this->stopPhpWebserver(); + } + + public function testGetResponseStatusMessage() + { + $this->startPhpWebserver(); + + $curl = new Curl(); + + $curl->exec('http://localhost:8000/curl.txt'); + $this->assertEquals('OK', $curl->getResponseStatusMessage()); + + $curl->exec('http://localhost:8000/404.txt'); + $this->assertEquals('Not Found', $curl->getResponseStatusMessage()); + + $this->stopPhpWebserver(); + } +} \ No newline at end of file diff --git a/tests/BeSimple/Tests/SoapClient/Fixtures/curl.txt b/tests/BeSimple/Tests/SoapClient/Fixtures/curl.txt new file mode 100644 index 0000000..070def3 --- /dev/null +++ b/tests/BeSimple/Tests/SoapClient/Fixtures/curl.txt @@ -0,0 +1 @@ +This is a testfile for cURL. \ No newline at end of file diff --git a/tests/BeSimple/Tests/SoapClient/Fixtures/type_include.xsd b/tests/BeSimple/Tests/SoapClient/Fixtures/type_include.xsd new file mode 100644 index 0000000..a41dd9a --- /dev/null +++ b/tests/BeSimple/Tests/SoapClient/Fixtures/type_include.xsd @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/BeSimple/Tests/SoapClient/Fixtures/wsdl_include.wsdl b/tests/BeSimple/Tests/SoapClient/Fixtures/wsdl_include.wsdl new file mode 100644 index 0000000..775240a --- /dev/null +++ b/tests/BeSimple/Tests/SoapClient/Fixtures/wsdl_include.wsdl @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/tests/BeSimple/Tests/SoapClient/Fixtures/wsdlinclude/wsdlinctest_absolute.xml b/tests/BeSimple/Tests/SoapClient/Fixtures/wsdlinclude/wsdlinctest_absolute.xml new file mode 100644 index 0000000..dae033e --- /dev/null +++ b/tests/BeSimple/Tests/SoapClient/Fixtures/wsdlinclude/wsdlinctest_absolute.xml @@ -0,0 +1,5 @@ + + + wsdlincludetest + + diff --git a/tests/BeSimple/Tests/SoapClient/Fixtures/wsdlinclude/wsdlinctest_relative.xml b/tests/BeSimple/Tests/SoapClient/Fixtures/wsdlinclude/wsdlinctest_relative.xml new file mode 100644 index 0000000..8148e60 --- /dev/null +++ b/tests/BeSimple/Tests/SoapClient/Fixtures/wsdlinclude/wsdlinctest_relative.xml @@ -0,0 +1,5 @@ + + + wsdlincludetest + + diff --git a/tests/BeSimple/Tests/SoapClient/Fixtures/xsdinclude/xsdinctest_absolute.xml b/tests/BeSimple/Tests/SoapClient/Fixtures/xsdinclude/xsdinctest_absolute.xml new file mode 100644 index 0000000..dc1b373 --- /dev/null +++ b/tests/BeSimple/Tests/SoapClient/Fixtures/xsdinclude/xsdinctest_absolute.xml @@ -0,0 +1,9 @@ + + + xsdinctest + + + + + + diff --git a/tests/BeSimple/Tests/SoapClient/Fixtures/xsdinclude/xsdinctest_relative.xml b/tests/BeSimple/Tests/SoapClient/Fixtures/xsdinclude/xsdinctest_relative.xml new file mode 100644 index 0000000..58cea74 --- /dev/null +++ b/tests/BeSimple/Tests/SoapClient/Fixtures/xsdinclude/xsdinctest_relative.xml @@ -0,0 +1,9 @@ + + + xsdinctest + + + + + + diff --git a/tests/BeSimple/Tests/SoapClient/SoapRequestTest.php b/tests/BeSimple/Tests/SoapClient/SoapRequestTest.php deleted file mode 100644 index c56d195..0000000 --- a/tests/BeSimple/Tests/SoapClient/SoapRequestTest.php +++ /dev/null @@ -1,113 +0,0 @@ - - * (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\Tests\SoapClient; - -use BeSimple\SoapClient\SoapRequest; - -class SoapRequestTest extends \PHPUnit_Framework_TestCase -{ - public function testSetFunction() - { - $soapRequest = new SoapRequest(); - $soapRequest->setFunction('foo'); - - $this->assertEquals('foo', $soapRequest->getFunction()); - } - - public function testSetArguments() - { - $soapRequest = new SoapRequest(); - $arguments = array( - 'foo' => true, - 'bar' => false, - ); - $soapRequest->setArguments($arguments); - - $this->assertEquals($arguments, $soapRequest->getArguments()); - } - - public function testGetArgument() - { - $soapRequest = new SoapRequest(); - - $this->assertSame(null, $soapRequest->getArgument('foo')); - $this->assertFalse($soapRequest->getArgument('foo', false)); - - $soapRequest->addArgument('foo', 'bar'); - - $this->assertEquals('bar', $soapRequest->getArgument('foo', false)); - } - - public function testSetOptions() - { - $soapRequest = new SoapRequest(); - $options = array( - 'uri' => 'foo', - 'soapaction' => 'bar', - ); - $soapRequest->setOptions($options); - - $this->assertEquals($options, $soapRequest->getOptions()); - } - - public function testGetOption() - { - $soapRequest = new SoapRequest(); - - $this->assertSame(null, $soapRequest->getOption('soapaction')); - $this->assertFalse($soapRequest->getOption('soapaction', false)); - - $soapRequest->addOption('soapaction', 'foo'); - - $this->assertEquals('foo', $soapRequest->getOption('soapaction')); - } - - public function testSetHeaders() - { - $soapRequest = new SoapRequest(); - - $this->assertEquals(null, $soapRequest->getHeaders()); - - $header1 = new \SoapHeader('foobar', 'foo', 'bar'); - $header2 = new \SoapHeader('barfoo', 'bar', 'foo'); - $soapRequest - ->addHeader($header1) - ->addHeader($header2) - ; - - $this->assertSame(array($header1, $header2), $soapRequest->getHeaders()); - } - - public function testConstruct() - { - $soapRequest = new SoapRequest(); - - $this->assertNull($soapRequest->getFunction()); - $this->assertEquals(array(), $soapRequest->getArguments()); - $this->assertEquals(array(), $soapRequest->getOptions()); - $this->assertEquals(null, $soapRequest->getHeaders()); - - $arguments = array('bar' => 'foobar'); - $options = array('soapaction' => 'foobar'); - $headers = array( - new \SoapHeader('foobar', 'foo', 'bar'), - new \SoapHeader('barfoo', 'bar', 'foo'), - ); - $soapRequest = new SoapRequest('foo', $arguments, $options, $headers); - - $this->assertEquals('foo', $soapRequest->getFunction()); - $this->assertEquals($arguments, $soapRequest->getArguments()); - $this->assertEquals($options, $soapRequest->getOptions()); - $this->assertSame($headers, $soapRequest->getHeaders()); - } -} diff --git a/tests/BeSimple/Tests/SoapClient/WsdlDownloaderTest.php b/tests/BeSimple/Tests/SoapClient/WsdlDownloaderTest.php new file mode 100644 index 0000000..14ac58a --- /dev/null +++ b/tests/BeSimple/Tests/SoapClient/WsdlDownloaderTest.php @@ -0,0 +1,265 @@ + + * (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\SoapClient; + +use BeSimple\SoapClient\WsdlDownloader; +use BeSimple\SoapClient\Curl; + +/** + * @author Andreas Schamberger + */ +class WsdlDownloaderTest extends \PHPUnit_Framework_TestCase +{ + protected $webserverProcessId; + + protected function startPhpWebserver() + { + if ('Windows' == substr(php_uname('s'), 0, 7 )) { + $powershellCommand = "\$app = start-process php.exe -ArgumentList '-S localhost:8000 -t ".__DIR__.DIRECTORY_SEPARATOR."Fixtures' -WindowStyle 'Hidden' -passthru; Echo \$app.Id;"; + $shellCommand = 'powershell -command "& {'.$powershellCommand.'}"'; + } else { + $shellCommand = "nohup php -S localhost:8000 -t ".__DIR__.DIRECTORY_SEPARATOR."Fixtures &"; + } + $output = array(); + exec($shellCommand, $output); + $this->webserverProcessId = $output[0]; // pid is in first element + } + + protected function stopPhpWebserver() + { + if (!is_null($this->webserverProcessId)) { + if ('Windows' == substr(php_uname('s'), 0, 7 )) { + exec('TASKKILL /F /PID ' . $this->webserverProcessId); + } else { + exec('kill ' . $this->webserverProcessId); + } + $this->webserverProcessId = null; + } + } + + public function testDownload() + { + $this->startPhpWebserver(); + + $curl = new Curl(); + $wd = new WsdlDownloader($curl); + + $cacheDir = ini_get('soap.wsdl_cache_dir'); + if (!is_dir($cacheDir)) { + $cacheDir = sys_get_temp_dir(); + $cacheDirForRegExp = preg_quote( $cacheDir ); + } + + $tests = array( + 'localWithAbsolutePath' => array( + 'source' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/xsdinclude/xsdinctest_absolute.xml', + 'assertRegExp' => '~.*'.$cacheDirForRegExp.'\\\wsdl_.*\.cache.*~', + ), + 'localWithRelativePath' => array( + 'source' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/xsdinclude/xsdinctest_relative.xml', + 'assertRegExp' => '~.*\.\./type_include\.xsd.*~', + ), + 'remoteWithAbsolutePath' => array( + 'source' => 'http://localhost:8000/xsdinclude/xsdinctest_absolute.xml', + 'assertRegExp' => '~.*'.$cacheDirForRegExp.'\\\wsdl_.*\.cache.*~', + ), + 'remoteWithAbsolutePath' => array( + 'source' => 'http://localhost:8000/xsdinclude/xsdinctest_relative.xml', + 'assertRegExp' => '~.*'.$cacheDirForRegExp.'\\\wsdl_.*\.cache.*~', + ), + ); + + foreach ($tests as $name => $values) { + $cacheFileName = $wd->download($values['source']); + $result = file_get_contents($cacheFileName); + $this->assertRegExp($values['assertRegExp'],$result,$name); + unlink($cacheFileName); + } + + $this->stopPhpWebserver(); + } + + public function testIsRemoteFile() + { + $curl = new Curl(); + $wd = new WsdlDownloader($curl); + + $class = new \ReflectionClass($wd); + $method = $class->getMethod('isRemoteFile'); + $method->setAccessible(true); + + $this->assertTrue($method->invoke($wd, 'http://www.php.net/')); + $this->assertTrue($method->invoke($wd, 'http://localhost/')); + $this->assertTrue($method->invoke($wd, 'http://mylocaldomain/')); + $this->assertTrue($method->invoke($wd, 'http://www.php.net/dir/test.html')); + $this->assertTrue($method->invoke($wd, 'http://localhost/dir/test.html')); + $this->assertTrue($method->invoke($wd, 'http://mylocaldomain/dir/test.html')); + $this->assertTrue($method->invoke($wd, 'https://www.php.net/')); + $this->assertTrue($method->invoke($wd, 'https://localhost/')); + $this->assertTrue($method->invoke($wd, 'https://mylocaldomain/')); + $this->assertTrue($method->invoke($wd, 'https://www.php.net/dir/test.html')); + $this->assertTrue($method->invoke($wd, 'https://localhost/dir/test.html')); + $this->assertTrue($method->invoke($wd, 'https://mylocaldomain/dir/test.html')); + $this->assertFalse($method->invoke($wd, 'c:/dir/test.html')); + $this->assertFalse($method->invoke($wd, '/dir/test.html')); + $this->assertFalse($method->invoke($wd, '../dir/test.html')); + } + + public function testResolveWsdlIncludes() + { + $this->startPhpWebserver(); + + $curl = new Curl(); + $wd = new WsdlDownloader($curl); + + $class = new \ReflectionClass($wd); + $method = $class->getMethod('resolveRemoteIncludes'); + $method->setAccessible(true); + + $cacheDir = ini_get('soap.wsdl_cache_dir'); + if (!is_dir($cacheDir)) { + $cacheDir = sys_get_temp_dir(); + $cacheDirForRegExp = preg_quote( $cacheDir ); + } + + $remoteUrlAbsolute = 'http://localhost:8000/wsdlinclude/wsdlinctest_absolute.xml'; + $remoteUrlRelative = 'http://localhost:8000/wsdlinclude/wsdlinctest_relative.xml'; + $tests = array( + 'localWithAbsolutePath' => array( + 'source' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/wsdlinclude/wsdlinctest_absolute.xml', + 'cacheFile' => $cacheDir.'/cache_local_absolute.xml', + 'remoteParentUrl' => null, + 'assertRegExp' => '~.*'.$cacheDirForRegExp.'\\\wsdl_.*\.cache.*~', + ), + 'localWithRelativePath' => array( + 'source' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/wsdlinclude/wsdlinctest_relative.xml', + 'cacheFile' => $cacheDir.'/cache_local_relative.xml', + 'remoteParentUrl' => null, + 'assertRegExp' => '~.*\.\./wsdl_include\.wsdl.*~', + ), + 'remoteWithAbsolutePath' => array( + 'source' => $remoteUrlAbsolute, + 'cacheFile' => $cacheDir.'/cache_remote_absolute.xml', + 'remoteParentUrl' => $remoteUrlAbsolute, + 'assertRegExp' => '~.*'.$cacheDirForRegExp.'\\\wsdl_.*\.cache.*~', + ), + 'remoteWithAbsolutePath' => array( + 'source' => $remoteUrlRelative, + 'cacheFile' => $cacheDir.'/cache_remote_relative.xml', + 'remoteParentUrl' => $remoteUrlRelative, + 'assertRegExp' => '~.*'.$cacheDirForRegExp.'\\\wsdl_.*\.cache.*~', + ), + ); + + foreach ($tests as $name => $values) { + $wsdl = file_get_contents( $values['source'] ); + $method->invoke($wd, $wsdl, $values['cacheFile'],$values['remoteParentUrl']); + $result = file_get_contents($values['cacheFile']); + $this->assertRegExp($values['assertRegExp'],$result,$name); + unlink($values['cacheFile']); + } + + $this->stopPhpWebserver(); + } + + public function testResolveXsdIncludes() + { + $this->startPhpWebserver(); + + $curl = new Curl(); + $wd = new WsdlDownloader($curl); + + $class = new \ReflectionClass($wd); + $method = $class->getMethod('resolveRemoteIncludes'); + $method->setAccessible(true); + + $cacheDir = ini_get('soap.wsdl_cache_dir'); + if (!is_dir($cacheDir)) { + $cacheDir = sys_get_temp_dir(); + $cacheDirForRegExp = preg_quote( $cacheDir ); + } + + $remoteUrlAbsolute = 'http://localhost:8000/xsdinclude/xsdinctest_absolute.xml'; + $remoteUrlRelative = 'http://localhost:8000/xsdinclude/xsdinctest_relative.xml'; + $tests = array( + 'localWithAbsolutePath' => array( + 'source' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/xsdinclude/xsdinctest_absolute.xml', + 'cacheFile' => $cacheDir.'/cache_local_absolute.xml', + 'remoteParentUrl' => null, + 'assertRegExp' => '~.*'.$cacheDirForRegExp.'\\\wsdl_.*\.cache.*~', + ), + 'localWithRelativePath' => array( + 'source' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/xsdinclude/xsdinctest_relative.xml', + 'cacheFile' => $cacheDir.'/cache_local_relative.xml', + 'remoteParentUrl' => null, + 'assertRegExp' => '~.*\.\./type_include\.xsd.*~', + ), + 'remoteWithAbsolutePath' => array( + 'source' => $remoteUrlAbsolute, + 'cacheFile' => $cacheDir.'/cache_remote_absolute.xml', + 'remoteParentUrl' => $remoteUrlAbsolute, + 'assertRegExp' => '~.*'.$cacheDirForRegExp.'\\\wsdl_.*\.cache.*~', + ), + 'remoteWithAbsolutePath' => array( + 'source' => $remoteUrlRelative, + 'cacheFile' => $cacheDir.'/cache_remote_relative.xml', + 'remoteParentUrl' => $remoteUrlRelative, + 'assertRegExp' => '~.*'.$cacheDirForRegExp.'\\\wsdl_.*\.cache.*~', + ), + ); + + foreach ($tests as $name => $values) { + $wsdl = file_get_contents( $values['source'] ); + $method->invoke($wd, $wsdl, $values['cacheFile'],$values['remoteParentUrl']); + $result = file_get_contents($values['cacheFile']); + $this->assertRegExp($values['assertRegExp'],$result,$name); + unlink($values['cacheFile']); + } + + $this->stopPhpWebserver(); + } + + public function testResolveRelativePathInUrl() + { + $curl = new Curl(); + $wd = new WsdlDownloader($curl); + + $class = new \ReflectionClass($wd); + $method = $class->getMethod('resolveRelativePathInUrl'); + $method->setAccessible(true); + + $this->assertEquals('http://localhost:8080/test', $method->invoke($wd, 'http://localhost:8080/sub', '/test')); + $this->assertEquals('http://localhost:8080/test', $method->invoke($wd, 'http://localhost:8080/sub/', '/test')); + + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost/sub', '/test')); + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost/sub/', '/test')); + + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost', './test')); + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost/', './test')); + + $this->assertEquals('http://localhost/sub/test', $method->invoke($wd, 'http://localhost/sub/sub', './test')); + $this->assertEquals('http://localhost/sub/sub/test', $method->invoke($wd, 'http://localhost/sub/sub/', './test')); + + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost/sub/sub', '../test')); + $this->assertEquals('http://localhost/sub/test', $method->invoke($wd, 'http://localhost/sub/sub/', '../test')); + + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost/sub/sub/sub', '../../test')); + $this->assertEquals('http://localhost/sub/test', $method->invoke($wd, 'http://localhost/sub/sub/sub/', '../../test')); + + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost/sub/sub/sub/sub', '../../../test')); + $this->assertEquals('http://localhost/sub/test', $method->invoke($wd, 'http://localhost/sub/sub/sub/sub/', '../../../test')); + + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost/sub/sub/sub', '../../../test')); + $this->assertEquals('http://localhost/test', $method->invoke($wd, 'http://localhost/sub/sub/sub/', '../../../test')); + } +} \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 2e0638c..d2872c6 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -16,6 +16,13 @@ spl_autoload_register(function($class) { if (file_exists($path) && is_readable($path)) { require_once $path; + return true; + } + } elseif (0 === strpos($class, 'ass\XmlSecurity\\')) { + $path = __DIR__.'/../vendor/XmlSecurity/src/'.strtr($class, '\\', '/').'.php'; + if (file_exists($path) && is_readable($path)) { + require_once $path; + return true; } } elseif (0 === strpos($class, 'BeSimple\SoapCommon\\')) { diff --git a/vendors.php b/vendors.php old mode 100755 new mode 100644 index cf763ae..a0e0703 --- a/vendors.php +++ b/vendors.php @@ -25,6 +25,7 @@ if (!is_dir($vendorDir = dirname(__FILE__).'/vendor')) { $deps = array( array('besimple-soapcommon', 'http://github.com/BeSimple/BeSimpleSoapCommon.git', 'origin/HEAD'), + array('XmlSecurity', 'https://github.com/aschamberger/XmlSecurity.git', 'origin/HEAD'), ); foreach ($deps as $dep) {