diff --git a/src/BeSimple/SoapClient/Curl.php b/src/BeSimple/SoapClient/Curl.php index c128ae4..5789853 100644 --- a/src/BeSimple/SoapClient/Curl.php +++ b/src/BeSimple/SoapClient/Curl.php @@ -1,305 +1,305 @@ - - * (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 - * @param int $followLocationMaxRedirects - */ - public function __construct(array $options, $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 - * @param string $request - * @param array $requestHeaders - * @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 resource $ch - * @param int $maxRedirects - * @param int $redirects - * @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 - * - * @var 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)); - } + + * (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 + * @param int $followLocationMaxRedirects + */ + 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 + * @param string $request + * @param array $requestHeaders + * @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 resource $ch + * @param int $maxRedirects + * @param int $redirects + * @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 + * + * @var 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/tests/BeSimple/Tests/SoapClient/CurlTest.php b/tests/BeSimple/Tests/SoapClient/CurlTest.php new file mode 100644 index 0000000..928eae6 --- /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