skip_slow_tests = in_array(getenv('PHP_CURL_CLASS_SKIP_SLOW_TESTS'), ['1', 'y', 'Y']); } public function testExtensionsLoaded() { $this->assertTrue(extension_loaded('curl')); $this->assertTrue(extension_loaded('gd')); $this->assertTrue(extension_loaded('mbstring')); } public function testCaseInsensitiveArrayGet() { $array = new CaseInsensitiveArray(); $this->assertTrue(is_object($array)); $this->assertCount(0, $array); $this->assertNull($array[(string)rand()]); $array['foo'] = 'bar'; $this->assertNotEmpty($array); $this->assertCount(1, $array); } public function testCaseInsensitiveArraySet() { $array = new CaseInsensitiveArray(); foreach (['FOO', 'FOo', 'Foo', 'fOO', 'fOo', 'foO', 'foo'] as $key) { $value = mt_rand(); $array[$key] = $value; $this->assertCount(1, $array); $this->assertEquals($value, $array['FOO']); $this->assertEquals($value, $array['FOo']); $this->assertEquals($value, $array['Foo']); $this->assertEquals($value, $array['fOO']); $this->assertEquals($value, $array['fOo']); $this->assertEquals($value, $array['foO']); $this->assertEquals($value, $array['foo']); } $array['baz'] = 'qux'; $this->assertCount(2, $array); } public function testBuildPostDataArgSeparator() { $data = [ 'foo' => 'Hello', 'bar' => 'World', ]; foreach ([false, '&', '&'] as $arg_separator) { if ($arg_separator) { ini_set('arg_separator.output', $arg_separator); } $curl = new Curl(); $this->assertEquals('foo=Hello&bar=World', $curl->buildPostData($data)); } } public function testBuildPostDataIntegerKey() { $curl = new Curl(); $this->assertEquals('abc=foo&123=bar', $curl->buildPostData([ 'abc' => 'foo', '123' => 'bar', ])); } public function testBuildPostDataObject() { $data = new \stdClass(); $data->{'abc'} = 'foo'; $data->{'123'} = 'bar'; $curl = new Curl(); $this->assertEquals('abc=foo&123=bar', $curl->buildPostData($data)); } public function testUserAgent() { $php_version = preg_replace('/([\.\+\?\*\(\)\[\]\^\$\/])/', '\\\\\1', 'PHP/' . PHP_VERSION); $curl_version = curl_version(); $curl_version = 'curl\/' . $curl_version['version']; $test = new Test(); $user_agent = $test->server('server', 'GET', ['key' => 'HTTP_USER_AGENT']); $this->assertMatchesRegularExpression('/' . $php_version . '/', $user_agent); $this->assertMatchesRegularExpression('/' . $curl_version . '/', $user_agent); } public function testGet() { $test = new Test(); $this->assertEquals('GET', $test->server('request_method', 'GET')); } public function testUrl() { $data = ['foo' => 'bar']; // curl -v --get --request GET "http://127.0.0.1:8000/" --data "foo=bar" $test = new Test(); $test->server('server', 'GET', $data); $this->assertEquals(Test::TEST_URL . '?' . http_build_query($data), $test->curl->url); // curl -v --request POST "http://127.0.0.1:8000/" --data "foo=bar" $test = new Test(); $test->server('server', 'POST', $data); $this->assertEquals(Test::TEST_URL, $test->curl->url); // curl -v --request PUT "http://127.0.0.1:8000/" --data "foo=bar" $test = new Test(); $test->server('server', 'PUT', $data); $this->assertEquals(Test::TEST_URL, $test->curl->url); // curl -v --request PATCH "http://127.0.0.1:8000/" --data "foo=bar" $test = new Test(); $test->server('server', 'PATCH', $data); $this->assertEquals(Test::TEST_URL, $test->curl->url); // curl -v --request SEARCH "http://127.0.0.1:8000/" --data "foo=bar" $test = new Test(); $test->server('server', 'SEARCH', $data); $this->assertEquals(Test::TEST_URL, $test->curl->url); // curl -v --request DELETE "http://127.0.0.1:8000/?foo=bar" $test = new Test(); $test->server('server', 'DELETE', $data); $this->assertEquals(Test::TEST_URL . '?' . http_build_query($data), $test->curl->url); // curl -v --get --request HEAD --head "http://127.0.0.1:8000/" --data "foo=bar" $test = new Test(); $test->server('server', 'HEAD', $data); $this->assertEquals(Test::TEST_URL . '?' . http_build_query($data), $test->curl->url); // curl -v --get --request OPTIONS "http://127.0.0.1:8000/" --data "foo=bar" $test = new Test(); $test->server('server', 'OPTIONS', $data); $this->assertEquals(Test::TEST_URL . '?' . http_build_query($data), $test->curl->url); } public function testSetUrlInConstructor() { $data = ['key' => 'value']; $curl = new Curl(Test::TEST_URL); $curl->setHeader('X-DEBUG-TEST', 'delete_with_body'); $curl->delete($data, ['wibble' => 'wubble']); $this->assertEquals('{"get":{"key":"value"},"delete":{"wibble":"wubble"}}', $curl->rawResponse); $curl = new Curl(Test::TEST_URL); $curl->setHeader('X-DEBUG-TEST', 'get'); $curl->delete($data); $this->assertEquals('key=value', $curl->response); $curl = new Curl(Test::TEST_URL); $curl->setHeader('X-DEBUG-TEST', 'get'); $curl->get($data); $this->assertEquals('key=value', $curl->response); $curl = new Curl(Test::TEST_URL); $curl->setHeader('X-DEBUG-TEST', 'get'); $curl->head($data); $this->assertEquals('HEAD /?key=value HTTP/1.1', $curl->requestHeaders['Request-Line']); $curl = new Curl(Test::TEST_URL); $curl->setHeader('X-DEBUG-TEST', 'get'); $curl->options($data); $this->assertEquals('key=value', $curl->response); $curl = new Curl(Test::TEST_URL); $curl->setHeader('X-DEBUG-TEST', 'request_method'); $curl->patch($data); $this->assertEquals('PATCH', $curl->response); $curl = new Curl(Test::TEST_URL); $curl->setHeader('X-DEBUG-TEST', 'post'); $curl->post($data); $this->assertEquals('key=value', $curl->response); $curl = new Curl(Test::TEST_URL); $curl->setHeader('X-DEBUG-TEST', 'put'); $curl->put($data); $this->assertEquals('key=value', $curl->response); $curl = new Curl(Test::TEST_URL); $curl->setHeader('X-DEBUG-TEST', 'search'); $curl->search($data); $this->assertEquals('key=value', $curl->response); } public function testSetUrl() { $data = ['key' => 'value']; $curl = new Curl(); $curl->setUrl(Test::TEST_URL); $curl->delete($data); $this->assertEquals('DELETE /?key=value HTTP/1.1', $curl->requestHeaders['Request-Line']); $this->assertEquals(Test::TEST_URL . '?key=value', $curl->effectiveUrl); $curl = new Curl(); $curl->setUrl(Test::TEST_URL); $curl->get($data); $this->assertEquals('GET /?key=value HTTP/1.1', $curl->requestHeaders['Request-Line']); $this->assertEquals(Test::TEST_URL . '?key=value', $curl->effectiveUrl); $curl = new Curl(); $curl->setUrl(Test::TEST_URL); $curl->head($data); $this->assertEquals('HEAD /?key=value HTTP/1.1', $curl->requestHeaders['Request-Line']); $this->assertEquals(Test::TEST_URL . '?key=value', $curl->effectiveUrl); $curl = new Curl(); $curl->setUrl(Test::TEST_URL); $curl->options($data); $this->assertEquals('OPTIONS /?key=value HTTP/1.1', $curl->requestHeaders['Request-Line']); $this->assertEquals(Test::TEST_URL . '?key=value', $curl->effectiveUrl); $curl = new Curl(); $curl->setUrl(Test::TEST_URL); $curl->patch($data); $this->assertEquals('PATCH / HTTP/1.1', $curl->requestHeaders['Request-Line']); $this->assertEquals(Test::TEST_URL, $curl->effectiveUrl); $curl = new Curl(); $curl->setUrl(Test::TEST_URL); $curl->post($data); $this->assertEquals('POST / HTTP/1.1', $curl->requestHeaders['Request-Line']); $this->assertEquals(Test::TEST_URL, $curl->effectiveUrl); $curl = new Curl(); $curl->setUrl(Test::TEST_URL); $curl->put($data); $this->assertEquals('PUT / HTTP/1.1', $curl->requestHeaders['Request-Line']); $this->assertEquals(Test::TEST_URL, $curl->effectiveUrl); $curl = new Curl(); $curl->setUrl(Test::TEST_URL); $curl->search($data); $this->assertEquals('SEARCH / HTTP/1.1', $curl->requestHeaders['Request-Line']); $this->assertEquals(Test::TEST_URL, $curl->effectiveUrl); } public function testEffectiveUrl() { $test = new Test(); $test->server('redirect', 'GET'); $this->assertEquals(Test::TEST_URL, $test->curl->effectiveUrl); $test = new Test(); $test->curl->setOpt(CURLOPT_FOLLOWLOCATION, true); $test->server('redirect', 'GET'); $this->assertEquals(Test::TEST_URL . '?redirect', $test->curl->effectiveUrl); $test = new Test(); $test->server('get', 'GET'); $this->assertEquals(Test::TEST_URL, $test->curl->effectiveUrl); $test->server('get', 'GET', ['a' => '1', 'b' => '2']); $this->assertEquals(Test::TEST_URL . '?a=1&b=2', $test->curl->effectiveUrl); } public function testPostRequestMethod() { $test = new Test(); $this->assertEquals('POST', $test->server('request_method', 'POST')); } public function testPostContinueResponseHeader() { // 100 Continue responses may contain additional optional headers per // RFC 2616, Section 10.1: // This class of status code indicates a provisional response, // consisting only of the Status-Line and optional headers, and is // terminated by an empty line. $response = 'HTTP/1.1 100 Continue' . "\r\n" . 'Date: Fri, 01 Jan 1990 00:00:00 GMT' . "\r\n" . 'Server: PHP-Curl-Class' . "\r\n" . "\r\n" . 'HTTP/1.1 200 OK' . "\r\n" . 'Date: Fri, 01 Jan 1990 00:00:00 GMT' . "\r\n" . 'Cache-Control: private' . "\r\n" . 'Vary: Accept-Encoding' . "\r\n" . 'Content-Length: 2' . "\r\n" . 'Content-Type: text/plain;charset=UTF-8' . "\r\n" . 'Server: PHP-Curl-Class' . "\r\n" . 'Connection: keep-alive' . "\r\n" . "\r\n"; $reflector = new \ReflectionClass('\Curl\Curl'); $reflection_method = $reflector->getMethod('parseResponseHeaders'); $reflection_method->setAccessible(true); $curl = new Curl(); $response_headers = $reflection_method->invoke($curl, $response); $this->assertEquals('HTTP/1.1 200 OK', $response_headers['Status-Line']); } public function testPostData() { $test = new Test(); $this->assertEquals('key=value', $test->server('post', 'POST', [ 'key' => 'value', ])); } public function testPostDataEmptyJson() { $test = new Test(); $test->curl->setHeader('Content-Type', 'application/json'); $test->server('post_json', 'POST'); $this->assertEquals('', $test->curl->response); $this->assertEquals('', $test->curl->getOpt(CURLOPT_POSTFIELDS)); } public function testPostAssociativeArrayData() { $data = [ 'username' => 'myusername', 'password' => 'mypassword', 'more_data' => [ 'param1' => 'something', 'param2' => 'other thing', 'param3' => 123, 'param4' => 3.14, ], ]; $test = new Test(); $test->curl->setDefaultJsonDecoder(true); $response = $test->server('post_multidimensional', 'POST', $data); $this->assertEquals($data, $response['post']); } public function testPostContentLength() { $test_data = [ [false, 0], ['', 0], [[], 0], [null, 0], ]; foreach ($test_data as $data) { $test = new Test(); list($post_data, $expected_content_length) = $data; if ($post_data === false) { $test->server('post', 'POST'); } else { $test->server('post', 'POST', $post_data); } $this->assertEquals($expected_content_length, $test->curl->requestHeaders['Content-Length']); } } public function testPostMultidimensionalData() { $data = [ 'key' => 'file', 'file' => [ 'wibble', 'wubble', 'wobble', ], ]; $this->assertEquals( 'key=file&file[0]=wibble&file[1]=wubble&file[2]=wobble', urldecode(http_build_query($data)) ); $test = new Test(); $test->curl->setDefaultJsonDecoder(true); $response = $test->server('post_multidimensional', 'POST', $data); $this->assertEquals($data, $response['post']); } public function testPostMultidimensionalDataWithFile() { $tests = []; $file_path_1 = \Helper\get_png(); $tests[] = [ 'file_path' => $file_path_1, 'post_data_image' => '@' . $file_path_1, ]; if (class_exists('CURLFile')) { $file_path_2 = \Helper\get_png(); $tests[] = [ 'file_path' => $file_path_2, 'post_data_image' => new \CURLFile($file_path_2), ]; } if (class_exists('CURLStringFile')) { $file_path_3 = \Helper\get_png(); $tests[] = [ 'file_path' => $file_path_3, 'post_data_image' => new \CURLStringFile(file_get_contents($file_path_3), 'image', 'image/png'), ]; } foreach ($tests as $test_data) { $file_path = $test_data['file_path']; $post_data_image = $test_data['post_data_image']; $test = new Test(); // Return associative for comparison. $assoc = true; $test->curl->setDefaultJsonDecoder($assoc); // Keep POST data separate from FILES data for comparison. $post_data_without_file = [ 'key' => 'value', 'alpha' => [ 'a' => '1', 'b' => '2', 'c' => '3', ], ]; $post_data = $post_data_without_file; $post_data['image'] = $post_data_image; $test->server('post_multidimensional_with_file', 'POST', $post_data); // Expect "Content-Type: multipart/form-data" in request headers. preg_match( '/^multipart\/form-data; boundary=/', $test->curl->requestHeaders['Content-Type'], $content_type ); $this->assertTrue(!empty($content_type)); // Expect received POST data to match POSTed data less the file. $this->assertEquals($post_data_without_file, $test->curl->response['post']); // Expect POSTed files is received as $_FILES. $this->assertTrue(isset($test->curl->response['files']['image']['tmp_name'])); $this->assertEquals(0, $test->curl->response['files']['image']['error']); unlink($file_path); $this->assertFalse(file_exists($file_path)); } } public function testPostFilePathUpload() { $file_path = \Helper\get_png(); $test = new Test(); $this->assertEquals('image/png', $test->server('post_file_path_upload', 'POST', [ 'key' => 'image', 'image' => '@' . $file_path, ])); unlink($file_path); $this->assertFalse(file_exists($file_path)); } public function testPostCurlFileUpload() { if (!class_exists('CURLFile')) { $this->markTestSkipped(); } $file_path = \Helper\get_png(); $test = new Test(); $this->assertEquals('image/png', $test->server('post_file_path_upload', 'POST', [ 'key' => 'image', 'image' => new \CURLFile($file_path), ])); unlink($file_path); $this->assertFalse(file_exists($file_path)); } public function testPostCurlStringFileUpload() { if (!class_exists('CURLStringFile')) { $this->markTestSkipped(); } $file_path = \Helper\get_png(); $test = new Test(); $this->assertEquals('image/png', $test->server('post_file_path_upload', 'POST', [ 'key' => 'image', 'image' => new \CURLStringFile(file_get_contents($file_path), 'image', 'image/png'), ])); unlink($file_path); $this->assertFalse(file_exists($file_path)); } public function testPostNonFilePathUpload() { $test = new Test(); $test->server('post', 'POST', [ 'foo' => 'bar', 'file' => '@not-a-file', ]); $this->assertFalse($test->curl->error); $this->assertEquals('foo=bar&file=%40not-a-file', $test->curl->response); } public function testPostRedirectGet() { // Follow 303 redirection with GET $test = new Test(); $test->curl->setOpt(CURLOPT_FOLLOWLOCATION, true); $this->assertEquals('Redirected: GET', $test->server('post_redirect_get', 'POST')); // Follow 303 redirection with POST $test = new Test(); $test->curl->setOpt(CURLOPT_FOLLOWLOCATION, true); $this->assertEquals('Redirected: POST', $test->server('post_redirect_get', 'POST', [], true)); // Ensure that it is possible to reuse an existing Curl object. $this->assertEquals('Redirected: GET', $test->server('post_redirect_get', 'POST')); } public function testPutRequestMethod() { $test = new Test(); $this->assertEquals('PUT', $test->server('request_method', 'PUT')); } public function testPutData() { $test = new Test(); $this->assertEquals('key=value', $test->server('put', 'PUT', [ 'key' => 'value', ])); $test = new Test(); $this->assertEquals('{"key":"value"}', $test->server('put', 'PUT', json_encode([ 'key' => 'value', ]))); } public function testPutFileHandle() { $png = \Helper\create_png(); $tmp_file = \Helper\create_tmp_file($png); $test = new Test(); $test->curl->setHeader('X-DEBUG-TEST', 'put_file_handle'); $test->curl->setOpt(CURLOPT_PUT, true); $test->curl->setOpt(CURLOPT_INFILE, $tmp_file); $test->curl->setOpt(CURLOPT_INFILESIZE, strlen($png)); $test->curl->put(Test::TEST_URL); fclose($tmp_file); $this->assertEquals('image/png', $test->curl->response); } public function testMultipartFormDataContentType() { // Use a PUT request instead of a POST request so the request // multipart/form-data is not automatically parsed and can be tested // against. $test = new Test(); $test->curl->setHeader('Content-Type', 'multipart/form-data'); $test->server('put', 'PUT', [ 'foo' => 'bar', ]); // Check the "expect" header value only when it is provided in the request. if (isset($test->curl->requestHeaders['Expect'])) { $this->assertEquals('100-continue', $test->curl->requestHeaders['Expect']); } $this->assertStringStartsWith('multipart/form-data; boundary=', $test->curl->requestHeaders['Content-Type']); $expected_contains = "\r\n" . 'Content-Disposition: form-data; name="foo"' . "\r\n" . "\r\n" . 'bar' . "\r\n" . ''; $this->assertStringContainsString($expected_contains, $test->curl->response); } public function testPatchRequestMethod() { $test = new Test(); $this->assertEquals('PATCH', $test->server('request_method', 'PATCH')); } public function testPatchData() { $test = new Test(); $this->assertEquals('key=value', $test->server('patch', 'PATCH', [ 'key' => 'value', ])); $test = new Test(); $this->assertEquals('{"key":"value"}', $test->server('patch', 'PATCH', json_encode([ 'key' => 'value', ]))); } public function testPatchRequestMethodWithMultidimArray() { $data = [ 'data' => [ 'foo' => 'bar', 'wibble' => 'wubble', ], ]; $curl = new Curl(); $curl->setHeader('X-DEBUG-TEST', 'data_values'); $curl->patch(Test::TEST_URL, $data); $this->assertEquals('{"data":{"foo":"bar","wibble":"wubble"}}', $curl->rawResponse); $this->assertEquals(json_decode(json_encode($data), false), $curl->response); } public function testDeleteRequestMethod() { $test = new Test(); $this->assertEquals('DELETE', $test->server('request_method', 'DELETE')); } public function testDeleteRequestBody() { $test = new Test(); $test->server('delete_with_body', 'DELETE', ['foo' => 'bar'], ['wibble' => 'wubble']); $this->assertEquals('{"get":{"foo":"bar"},"delete":{"wibble":"wubble"}}', $test->curl->rawResponse); } public function testDeleteContentLengthSetWithBody() { $request_body = 'a=1&b=2&c=3'; $test = new Test(); $test->server('request_method', 'DELETE', [], $request_body); $this->assertEquals(strlen($request_body), $test->curl->requestHeaders['content-length']); } public function testDeleteContentLengthUnsetWithoutBody() { $request_body = []; $test = new Test(); $test->server('request_method', 'DELETE', [], $request_body); $this->assertFalse(isset($test->curl->requestHeaders['content-length'])); } public function testHeadRequestMethod() { $test = new Test(); $test->server('request_method', 'HEAD'); $this->assertEquals('HEAD', $test->curl->responseHeaders['X-REQUEST-METHOD']); $this->assertEmpty($test->curl->response); } public function testOptionsRequestMethod() { $test = new Test(); $test->server('request_method', 'OPTIONS'); $this->assertEquals('OPTIONS', $test->curl->responseHeaders['X-REQUEST-METHOD']); } public function testDownloadToFile() { // Create and upload a file. $upload_file_path = \Helper\get_png(); $uploaded_file_path = \Helper\upload_file_to_server($upload_file_path); // Download the file. $downloaded_file_path = tempnam('/tmp', 'php-curl-class.'); $download_test = new Test(); $download_test->curl->setHeader('X-DEBUG-TEST', 'download_response'); $this->assertTrue($download_test->curl->download(Test::TEST_URL . '?' . http_build_query([ 'file_path' => $uploaded_file_path, ]), $downloaded_file_path)); $this->assertNotEquals($uploaded_file_path, $downloaded_file_path); $this->assertEquals(filesize($upload_file_path), filesize($downloaded_file_path)); $this->assertEquals(md5_file($upload_file_path), md5_file($downloaded_file_path)); $this->assertEquals(md5_file($upload_file_path), $download_test->curl->responseHeaders['ETag']); $this->assertEquals($download_test->curl->downloadFileName, $downloaded_file_path . '.pccdownload'); // Ensure successive requests set the appropriate values. $this->assertEquals('GET', $download_test->server('request_method', 'GET')); $this->assertFalse(is_bool($download_test->curl->response)); $this->assertFalse(is_bool($download_test->curl->rawResponse)); // Remove server file. \Helper\remove_file_from_server($uploaded_file_path); unlink($upload_file_path); unlink($downloaded_file_path); $this->assertFalse(file_exists($upload_file_path)); $this->assertFalse(file_exists($downloaded_file_path)); } public function testDownloadCallback() { // Create and upload a file. $upload_file_path = \Helper\get_png(); $uploaded_file_path = \Helper\upload_file_to_server($upload_file_path); // Download the file. $download_callback_called = false; $curl = new Curl(); $curl->setHeader('X-DEBUG-TEST', 'download_response'); $curl->download(Test::TEST_URL . '?' . http_build_query([ 'file_path' => $uploaded_file_path, ]), function ($instance, $fh) use (&$download_callback_called) { \PHPUnit\Framework\Assert::assertFalse($download_callback_called); \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue(is_resource($fh)); \PHPUnit\Framework\Assert::assertEquals('stream', get_resource_type($fh)); \PHPUnit\Framework\Assert::assertGreaterThan(0, strlen(stream_get_contents($fh))); \PHPUnit\Framework\Assert::assertEquals(0, strlen(stream_get_contents($fh))); \PHPUnit\Framework\Assert::assertTrue(fclose($fh)); $download_callback_called = true; }); $this->assertTrue($download_callback_called); // Remove server file. \Helper\remove_file_from_server($uploaded_file_path); unlink($upload_file_path); $this->assertFalse(file_exists($upload_file_path)); } public function testDownloadRange() { // Create and upload a file. $filename = \Helper\get_png(); $uploaded_file_path = \Helper\upload_file_to_server($filename); $filesize = filesize($filename); foreach ([ false, 0, 1, 2, 3, 5, 10, 25, 50, $filesize - 3, $filesize - 2, $filesize - 1, // A partial temporary file having the exact same file size as the complete source file should only // occur under certain circumstances (almost never). When the download successfully completed, the // temporary file should have been moved to the download destination save path. However, it is possible // that a larger file download was interrupted after which the source file was updated and now has the // exact same file size as the partial temporary. When resuming the download, the range is now // unsatisfiable as the first byte position exceeds the available range. The entire file should be // downloaded again. $filesize - 0, // A partial temporary file having a larger file size than the complete source file should only occur // under certain circumstances. This is possible when a download was interrupted after which the source // file was updated with a smaller file. When resuming the download, the range is now unsatisfiable as // the first byte position exceeds the the available range. The entire file should be downloaded again. $filesize + 1, $filesize + 2, $filesize + 3, ] as $length) { $source = Test::TEST_URL; $destination = \Helper\get_tmp_file_path(); // Start with no file. if ($length === false) { $this->assertFalse(file_exists($destination)); // Start with $length bytes of file. } else { // Simulate resuming partially downloaded temporary file. $partial_filename = $destination . '.pccdownload'; if ($length === 0) { $partial_content = ''; } else { $file = fopen($filename, 'rb'); $partial_content = fread($file, $length); fclose($file); } // Partial content size should be $length bytes large for testing resume download behavior. if ($length <= $filesize) { $this->assertEquals($length, strlen($partial_content)); // Partial content should not be larger than the original file size. } else { $this->assertEquals($filesize, strlen($partial_content)); } file_put_contents($partial_filename, $partial_content); $this->assertEquals(strlen($partial_content), strlen(file_get_contents($partial_filename))); } // Download (the remaining bytes of) the file. $curl = new Curl(); $curl->setHeader('X-DEBUG-TEST', 'download_file_range'); $curl->download($source . '?' . http_build_query([ 'file_path' => $uploaded_file_path, ]), $destination); clearstatcache(); $expected_bytes_downloaded = $filesize - min($length, $filesize); $bytes_downloaded = $curl->responseHeaders['content-length']; if ($length === false || $length === 0) { $expected_http_status_code = 200; // 200 OK $this->assertEquals($expected_bytes_downloaded, $bytes_downloaded); } elseif ($length >= $filesize) { $expected_http_status_code = 416; // 416 Requested Range Not Satisfiable } else { $expected_http_status_code = 206; // 206 Partial Content $this->assertEquals($expected_bytes_downloaded, $bytes_downloaded); } $this->assertEquals($expected_http_status_code, $curl->httpStatusCode); if (!$curl->error) { $this->assertEquals($filesize, filesize($destination)); unlink($destination); $this->assertFalse(file_exists($destination)); } } // Remove server file. \Helper\remove_file_from_server($uploaded_file_path); unlink($filename); $this->assertFalse(file_exists($filename)); } public function testDownloadErrorDeleteTemporaryFile() { $destination = \Helper\get_tmp_file_path(); $test = new Test(); $test->curl->setHeader('X-DEBUG-TEST', '404'); $test->curl->download(Test::TEST_URL, $destination); $this->assertFalse(file_exists($test->curl->downloadFileName)); $this->assertFalse(file_exists($destination)); } public function testDownloadCallbackError() { if ($this->skip_slow_tests) { $this->markTestSkipped(); } $download_before_send_called = false; $download_callback_called = false; $curl = new Curl(); $curl->beforeSend(function ($instance) use (&$download_before_send_called) { \PHPUnit\Framework\Assert::assertFalse($download_before_send_called); $download_before_send_called = true; }); $curl->download(Test::ERROR_URL, function ($instance, $fh) use (&$download_callback_called) { $download_callback_called = true; }); $this->assertTrue($download_before_send_called); $this->assertFalse($download_callback_called); } public function testMaxFilesize() { $tests = [ [ 'bytes' => 1, 'max_filesize' => false, 'expect_error' => false, ], [ 'bytes' => 1, 'max_filesize' => 1, 'expect_error' => false, ], [ 'bytes' => 1, 'max_filesize' => 2, 'expect_error' => false, ], [ 'bytes' => 1, 'max_filesize' => 0, 'expect_error' => true, ], [ 'bytes' => 2, 'max_filesize' => false, 'expect_error' => false, ], [ 'bytes' => 2, 'max_filesize' => 2, 'expect_error' => false, ], [ 'bytes' => 2, 'max_filesize' => 3, 'expect_error' => false, ], [ 'bytes' => 2, 'max_filesize' => 1, 'expect_error' => true, ], [ 'bytes' => 1000, 'max_filesize' => false, 'expect_error' => false, ], [ 'bytes' => 1000, 'max_filesize' => 1000, 'expect_error' => false, ], [ 'bytes' => 1000, 'max_filesize' => 1001, 'expect_error' => false, ], [ 'bytes' => 1000, 'max_filesize' => 999, 'expect_error' => true, ], [ 'bytes' => 1000, 'max_filesize' => 0, 'expect_error' => true, ], ]; foreach ($tests as $test) { $bytes = $test['bytes']; $max_filesize = $test['max_filesize']; $expect_error = $test['expect_error']; $test = new Test(); if ($max_filesize !== false) { $test->curl->setMaxFilesize($max_filesize); } $test->server('download_file_size', 'GET', [ 'bytes' => $bytes, ]); // Ensure exceeding download limit aborts the transfer and sets a CURLE_ABORTED_BY_CALLBACK error. if ($expect_error) { $this->assertTrue($test->curl->error); $this->assertEquals(CURLE_ABORTED_BY_CALLBACK, $test->curl->errorCode); } else { $str = str_repeat('.', $bytes); $this->assertEquals(md5($str), $test->curl->responseHeaders['etag']); $this->assertEquals($str, $test->curl->response); } } } public function testBasicHttpAuth() { $test = new Test(); $this->assertEquals('canceled', $test->server('http_basic_auth', 'GET')); $username = 'myusername'; $password = 'mypassword'; $test = new Test(); $test->curl->setBasicAuthentication($username, $password); $test->server('http_basic_auth', 'GET'); $json = $test->curl->response; $this->assertEquals($username, $json->username); $this->assertEquals($password, $json->password); } public function testDigestHttpAuth() { $username = 'myusername'; $password = 'mypassword'; $invalid_password = 'anotherpassword'; $test = new Test(); $test->server('http_digest_auth', 'GET'); $this->assertEquals('canceled', $test->curl->response); $this->assertEquals(401, $test->curl->httpStatusCode); $test = new Test(); $test->curl->setDigestAuthentication($username, $invalid_password); $test->server('http_digest_auth', 'GET'); $this->assertEquals('invalid', $test->curl->response); $this->assertEquals(401, $test->curl->httpStatusCode); $test = new Test(); $test->curl->setDigestAuthentication($username, $password); $test->server('http_digest_auth', 'GET'); $this->assertEquals('valid', $test->curl->response); $this->assertEquals(200, $test->curl->httpStatusCode); } public function testReferrer() { $test = new Test(); $test->curl->setReferrer('myreferrer'); $this->assertEquals('myreferrer', $test->server('server', 'GET', [ 'key' => 'HTTP_REFERER', ])); $test = new Test(); $test->curl->setReferer('myreferer'); $this->assertEquals('myreferer', $test->server('server', 'GET', [ 'key' => 'HTTP_REFERER', ])); } public function testResponseBody() { foreach ([ 'GET' => 'OK', 'POST' => 'OK', 'PUT' => 'OK', 'PATCH' => 'OK', 'SEARCH' => 'OK', 'DELETE' => 'OK', 'HEAD' => '', 'OPTIONS' => 'OK', ] as $request_method => $expected_response) { $curl = new Curl(); $curl->setHeader('X-DEBUG-TEST', 'response_body'); $this->assertEquals($expected_response, $curl->$request_method(Test::TEST_URL)); } } public function testSetCookie() { $test = new Test(); $test->curl->setCookie('mycookie', 'yum'); $test->server('setcookie', 'GET'); $this->assertEquals('yum', $test->curl->responseCookies['mycookie']); } public function testSetCookies() { $cookies = [ 'mycookie' => 'yum', 'fruit' => 'apple', 'color' => 'red', ]; $test = new Test(); $test->curl->setCookies($cookies); $test->server('setcookie', 'GET'); $this->assertEquals('yum', $test->curl->responseCookies['mycookie']); $this->assertEquals('apple', $test->curl->responseCookies['fruit']); $this->assertEquals('red', $test->curl->responseCookies['color']); } public function testSetCookieEncodingSpace() { $curl = new Curl(); $curl->setCookie('cookie', 'Om nom nom nom'); $this->assertEquals('cookie=Om%20nom%20nom%20nom', $curl->getOpt(CURLOPT_COOKIE)); } public function testSetMultipleCookies() { $curl = new Curl(); $curl->setCookie('cookie', 'Om nom nom nom'); $curl->setCookie('foo', 'bar'); $this->assertEquals('cookie=Om%20nom%20nom%20nom; foo=bar', $curl->getOpt(CURLOPT_COOKIE)); } public function testSetCookieEncodingColon() { $curl = new Curl(); $curl->setCookie('JSESSIONID', '0000wd-PcsB3bZ-KzYGAqm_rKlm:17925chrl'); $this->assertEquals('JSESSIONID=0000wd-PcsB3bZ-KzYGAqm_rKlm:17925chrl', $curl->getOpt(CURLOPT_COOKIE)); } public function testSetCookieString() { $cookie_string = 'fruit=apple; color=red'; $test = new Test(); $test->curl->setCookieString($cookie_string); $this->assertEquals($cookie_string, $test->curl->getOpt(CURLOPT_COOKIE)); $this->assertEquals('fruit=apple&color=red', $test->server('cookie', 'GET')); } public function testCookieFile() { $cookie_file = __DIR__ . '/cookiefile.txt'; $cookie_data = implode("\t", [ '127.0.0.1', // domain 'FALSE', // tailmatch '/', // path 'FALSE', // secure '0', // expires 'mycookie', // name 'yum', // value ]) . "\n"; file_put_contents($cookie_file, $cookie_data); $test = new Test(); $test->curl->setCookieFile($cookie_file); $this->assertEquals($cookie_data, file_get_contents($test->curl->getOpt(CURLOPT_COOKIEFILE))); $this->assertEquals('yum', $test->server('cookie', 'GET', [ 'key' => 'mycookie', ])); unlink($cookie_file); $this->assertFalse(file_exists($cookie_file)); } public function testCookieJar() { $cookie_jar = __DIR__ . '/cookiejar.txt'; $test = new Test(); $test->curl->setCookieJar($cookie_jar); $test->server('cookiejar', 'GET'); $test->curl->close(); $this->assertTrue(strpos(file_get_contents($cookie_jar), "\t" . 'mycookie' . "\t" . 'yum') !== false); unlink($cookie_jar); $this->assertFalse(file_exists($cookie_jar)); } public function testMultipleCookieResponse() { $test = new Test(); $test->server('multiple_cookie', 'GET'); $this->assertEquals('cookie1=scrumptious,cookie2=mouthwatering', $test->curl->responseHeaders['Set-Cookie']); $this->assertEquals('scrumptious', $test->curl->responseCookies['cookie1']); $this->assertEquals('mouthwatering', $test->curl->responseCookies['cookie2']); } public function testDefaultTimeout() { if ($this->skip_slow_tests) { $this->markTestSkipped(); } $test = new Test('8001'); $test->server('timeout', 'GET', [ 'seconds' => '31', ]); $this->assertTrue($test->curl->error); $this->assertTrue($test->curl->curlError); $this->assertEquals(CURLE_OPERATION_TIMEOUTED, $test->curl->errorCode); $this->assertEquals(CURLE_OPERATION_TIMEOUTED, $test->curl->curlErrorCode); $this->assertFalse($test->curl->httpError); } public function testTimeoutError() { if ($this->skip_slow_tests) { $this->markTestSkipped(); } $test = new Test('8002'); $test->curl->setTimeout(5); $test->server('timeout', 'GET', [ 'seconds' => '10', ]); $this->assertTrue($test->curl->error); $this->assertTrue($test->curl->curlError); $this->assertEquals(CURLE_OPERATION_TIMEOUTED, $test->curl->errorCode); $this->assertEquals(CURLE_OPERATION_TIMEOUTED, $test->curl->curlErrorCode); $this->assertFalse($test->curl->httpError); } public function testTimeout() { if ($this->skip_slow_tests) { $this->markTestSkipped(); } $test = new Test('8003'); $test->curl->setTimeout(10); $test->server('timeout', 'GET', [ 'seconds' => '5', ]); $this->assertFalse($test->curl->error, $test->message); $this->assertFalse($test->curl->curlError, $test->message); $this->assertNotEquals(CURLE_OPERATION_TIMEOUTED, $test->curl->errorCode, $test->message); $this->assertNotEquals(CURLE_OPERATION_TIMEOUTED, $test->curl->curlErrorCode, $test->message); $this->assertFalse($test->curl->httpError, $test->message); } public function testError() { if ($this->skip_slow_tests) { $this->markTestSkipped(); } $test = new Test('8004'); $test->curl->get(Test::ERROR_URL); $this->assertTrue($test->curl->error); $this->assertTrue($test->curl->curlError); $possible_errors = [CURLE_SEND_ERROR, CURLE_OPERATION_TIMEOUTED, CURLE_COULDNT_CONNECT, CURLE_GOT_NOTHING]; $this->assertTrue( in_array($test->curl->errorCode, $possible_errors, true), 'errorCode: ' . $test->curl->errorCode ); $this->assertTrue( in_array($test->curl->curlErrorCode, $possible_errors, true), 'curlErrorCode: ' . $test->curl->curlErrorCode ); } public function testErrorMessage() { $test = new Test(); $test->server('error_message', 'GET'); $this->assertEquals('HTTP/1.1 401 Unauthorized', $test->curl->errorMessage); } public function testRequestHeaderCaseSensitivity() { $content_type = 'application/json'; $curl = new Curl(); $curl->setHeader('Content-Type', $content_type); $headers = \Helper\get_curl_property_value($curl, 'headers'); $this->assertEquals($content_type, $headers['Content-Type']); $this->assertEquals($content_type, $headers['content-type']); $this->assertEquals($content_type, $headers['CONTENT-TYPE']); $this->assertEquals($content_type, $headers['cOnTeNt-TyPe']); } public function testResponseHeaders() { $test = new Test(); $test->curl->setHeader('Content-Type', 'application/json'); $test->curl->setHeader('X-Requested-With', 'XMLHttpRequest'); $test->curl->setHeader('Accept', 'application/json'); $this->assertEquals('application/json', $test->server('server', 'GET', ['key' => 'CONTENT_TYPE'])); $this->assertEquals('XMLHttpRequest', $test->server('server', 'GET', ['key' => 'HTTP_X_REQUESTED_WITH'])); $this->assertEquals('application/json', $test->server('server', 'GET', ['key' => 'HTTP_ACCEPT'])); } public function testResponseHeaderCaseSensitivity() { $content_type = 'application/json'; $test = new Test(); $test->curl->setHeader('Content-Type', $content_type); $test->server('response_header', 'GET'); $request_headers = $test->curl->requestHeaders; $response_headers = $test->curl->responseHeaders; $this->assertEquals($content_type, $request_headers['Content-Type']); $this->assertEquals($content_type, $request_headers['content-type']); $this->assertEquals($content_type, $request_headers['CONTENT-TYPE']); $this->assertEquals($content_type, $request_headers['cOnTeNt-TyPe']); $etag = $response_headers['ETag']; $this->assertEquals($etag, $response_headers['ETAG']); $this->assertEquals($etag, $response_headers['etag']); $this->assertEquals($etag, $response_headers['eTAG']); $this->assertEquals($etag, $response_headers['eTaG']); } public function testHeaderOutOptional() { // CURLINFO_HEADER_OUT is true by default. $test_1 = new Test(); $test_1->server('response_header', 'GET'); $this->assertNotEmpty($test_1->curl->requestHeaders); $this->assertNotEmpty($test_1->curl->requestHeaders['Request-Line']); // CURLINFO_HEADER_OUT is set to true. $test_2 = new Test(); $test_2->curl->setOpt(CURLINFO_HEADER_OUT, true); $test_2->server('response_header', 'GET'); $this->assertNotEmpty($test_2->curl->requestHeaders); $this->assertNotEmpty($test_2->curl->requestHeaders['Request-Line']); // CURLINFO_HEADER_OUT is set to false. $test_3 = new Test(); $test_3->curl->setOpt(CURLINFO_HEADER_OUT, false); $test_3->curl->verbose(); $test_3->server('response_header', 'GET'); $this->assertEmpty($test_3->curl->requestHeaders); } public function testHeaderRedirect() { $test = new Test(); $test->curl->setOpt(CURLOPT_FOLLOWLOCATION, true); $test->server('redirect', 'GET'); $this->assertEquals('OK', $test->curl->response); } public function testRequestUrl() { $test = new Test(); $this->assertFalse(substr($test->server('request_uri', 'GET'), -1) === '?'); $test = new Test(); $this->assertFalse(substr($test->server('request_uri', 'POST'), -1) === '?'); $test = new Test(); $this->assertFalse(substr($test->server('request_uri', 'PUT'), -1) === '?'); $test = new Test(); $this->assertFalse(substr($test->server('request_uri', 'PATCH'), -1) === '?'); $test = new Test(); $this->assertFalse(substr($test->server('request_uri', 'SEARCH'), -1) === '?'); $test = new Test(); $this->assertFalse(substr($test->server('request_uri', 'DELETE'), -1) === '?'); } public function testNestedData() { $test = new Test(); $data = [ 'username' => 'myusername', 'password' => 'mypassword', 'more_data' => [ 'param1' => 'something', 'param2' => 'other thing', 'another' => [ 'extra' => 'level', 'because' => 'I need it', ], ], ]; $this->assertEquals(http_build_query($data), $test->server('post', 'POST', $data)); } public function testPostStringUrlEncodedContentType() { $test = new Test(); $test->server('server', 'POST', 'foo=bar'); $this->assertEquals('application/x-www-form-urlencoded', $test->curl->requestHeaders['Content-Type']); } public function testPostArrayUrlEncodedContentType() { $test = new Test(); $test->server('server', 'POST', [ 'foo' => 'bar', ]); $this->assertEquals('application/x-www-form-urlencoded', $test->curl->requestHeaders['Content-Type']); } public function testPostFileFormDataContentType() { $file_path = \Helper\get_png(); $test = new Test(); $test->server('server', 'POST', [ 'image' => '@' . $file_path, ]); // Check the "expect" header value only when it is provided in the request. if (isset($test->curl->requestHeaders['Expect'])) { $this->assertEquals('100-continue', $test->curl->requestHeaders['Expect']); } preg_match('/^multipart\/form-data; boundary=/', $test->curl->requestHeaders['Content-Type'], $content_type); $this->assertTrue(!empty($content_type)); unlink($file_path); $this->assertFalse(file_exists($file_path)); } public function testPostCurlFileFormDataContentType() { if (!class_exists('CURLFile')) { $this->markTestSkipped(); } $file_path = \Helper\get_png(); $test = new Test(); $test->server('server', 'POST', [ 'image' => new \CURLFile($file_path), ]); // Check the "expect" header value only when it is provided in the request. if (isset($test->curl->requestHeaders['Expect'])) { $this->assertEquals('100-continue', $test->curl->requestHeaders['Expect']); } preg_match('/^multipart\/form-data; boundary=/', $test->curl->requestHeaders['Content-Type'], $content_type); $this->assertTrue(!empty($content_type)); unlink($file_path) ; $this->assertFalse(file_exists($file_path)); } public function testJsonRequest() { foreach ([ [ [ 'key' => 'value', ], '{"key":"value"}', ], [ [ 'key' => 'value', 'strings' => [ 'a', 'b', 'c', ], ], '{"key":"value","strings":["a","b","c"]}', ], ] as $test) { list($data, $expected_response) = $test; $test = new Test(); $this->assertEquals($expected_response, $test->server('post_json', 'POST', json_encode($data))); foreach ([ 'Content-Type', 'content-type', 'CONTENT-TYPE'] as $key) { foreach ([ 'APPLICATION/JSON', 'APPLICATION/JSON; CHARSET=UTF-8', 'APPLICATION/JSON;CHARSET=UTF-8', 'application/json', 'application/json; charset=utf-8', 'application/json;charset=UTF-8', ] as $value) { $test = new Test(); $test->curl->setHeader($key, $value); $this->assertEquals($expected_response, $test->server('post_json', 'POST', json_encode($data))); $test = new Test(); $test->curl->setHeader($key, $value); $this->assertEquals($expected_response, $test->server('post_json', 'POST', $data)); } } } } public function testJsonResponse() { foreach ([ 'Content-Type', 'content-type', 'CONTENT-TYPE'] as $key) { foreach ([ 'APPLICATION/JSON', 'APPLICATION/JSON; CHARSET=UTF-8', 'APPLICATION/JSON;CHARSET=UTF-8', 'application/json', 'application/json; charset=utf-8', 'application/json;charset=UTF-8', ] as $value) { $test = new Test(); $test->server('json_response', 'POST', [ 'key' => $key, 'value' => $value, ]); $response = $test->curl->response; $this->assertNotNull($response); $this->assertNull($response->null); $this->assertTrue($response->true); $this->assertFalse($response->false); $this->assertTrue(is_int($response->integer)); $this->assertTrue(is_float($response->float)); $this->assertEmpty($response->empty); $this->assertTrue(is_string($response->string)); $this->assertEquals(json_encode([ 'null' => null, 'true' => true, 'false' => false, 'integer' => 1, 'float' => 3.14, 'empty' => '', 'string' => 'string', ]), $test->curl->rawResponse); } } } /** * @expectedException \ErrorException */ public function testJsonEncode() { $this->expectException(\ErrorException::class); $data = [ 'malformed' => pack('H*', 'c32e'), ]; $test = new Test(); $test->curl->setHeader('Content-Type', 'application/json'); $test->server('post_json', 'POST', $data); } public function testJsonDecoderOptions() { // Implicit default json decoder should return object. $test = new Test(); $test->server('json_response', 'GET'); $this->assertTrue(is_object($test->curl->response)); // Explicit default json decoder should return object. $test = new Test(); $test->curl->setDefaultJsonDecoder(); $test->server('json_response', 'GET'); $this->assertTrue(is_object($test->curl->response)); // Explicit default json decoder with options should return associative array as specified. $assoc = true; $depth = 512; $options = 0; $test = new Test(); $test->curl->setDefaultJsonDecoder($assoc, $depth, $options); $test->server('json_response', 'GET'); $this->assertTrue(is_array($test->curl->response)); } public function testJsonDecoder() { $test = new Test(); $test->server('json_response', 'GET'); $this->assertTrue(is_object($test->curl->response)); $this->assertFalse(is_array($test->curl->response)); $test = new Test(); $test->curl->setJsonDecoder(function ($response) { return json_decode($response, true); }); $test->server('json_response', 'GET'); $this->assertFalse(is_object($test->curl->response)); $this->assertTrue(is_array($test->curl->response)); $test = new Test(); $test->curl->setJsonDecoder(false); $test->server('json_response', 'GET'); $this->assertTrue(is_string($test->curl->response)); } public function testJsonContentTypeDetection() { $json_content_types = [ 'application/alto-costmap+json', 'application/alto-costmapfilter+json', 'application/alto-directory+json', 'application/alto-endpointprop+json', 'application/alto-endpointpropparams+json', 'application/alto-endpointcost+json', 'application/alto-endpointcostparams+json', 'application/alto-error+json', 'application/alto-networkmapfilter+json', 'application/alto-networkmap+json', 'application/calendar+json', 'application/coap-group+json', 'application/jose+json', 'application/jrd+json', 'application/json', 'application/json-patch+json', 'application/json-seq', 'application/jwk+json', 'application/jwk-set+json', 'application/ld+json', 'application/merge-patch+json', 'application/rdap+json', 'application/reputon+json', 'application/vcard+json', 'application/vnd.apache.thrift.json', 'application/vnd.api+json', 'application/vnd.bekitzur-stech+json', 'application/vnd.collection.doc+json', 'application/vnd.collection+json', 'application/vnd.collection.next+json', 'application/vnd.document+json', 'application/vnd.geo+json', 'application/vnd.hal+json', 'application/vnd.heroku+json', 'application/vnd.ims.lis.v2.result+json', 'application/vnd.ims.lti.v2.toolconsumerprofile+json', 'application/vnd.ims.lti.v2.toolproxy.id+json', 'application/vnd.ims.lti.v2.toolproxy+json', 'application/vnd.ims.lti.v2.toolsettings+json', 'application/vnd.ims.lti.v2.toolsettings.simple+json', 'application/vnd.mason+json', 'application/vnd.micro+json', 'application/vnd.miele+json', 'application/vnd.oftn.l10n+json', 'application/vnd.oracle.resource+json', 'application/vnd.siren+json', 'application/vnd.xacml+json', // Non-standard 'application/x-json', 'text/json', 'text/x-json', ]; $curl = new Curl(); $json_pattern = \Helper\get_curl_property_value($curl, 'jsonPattern'); foreach ($json_content_types as $json_content_type) { $message = '"' . $json_content_type . '" does not match pattern ' . $json_pattern; $this->assertEquals(1, preg_match($json_pattern, $json_content_type), $message); } $not_json_content_types = [ 'application/1d-interleaved-parityfec', 'application/3gpdash-qoe-report+xml', 'application/3gpp-ims+xml', 'application/A2L', 'application/activemessage', 'application/activemessage', 'application/AML', 'application/andrew-inset', 'application/applefile', 'application/ATF', 'application/ATFX', 'application/atom+xml', 'application/atomcat+xml', 'application/atomdeleted+xml', 'application/atomicmail', 'application/atomsvc+xml', 'application/ATXML', 'application/auth-policy+xml', 'application/bacnet-xdd+zip', 'application/batch-SMTP', 'application/beep+xml', 'application/calendar+xml', 'application/call-completion', 'application/cals-1840', 'application/cbor', 'application/ccmp+xml', 'application/ccxml+xml', 'application/CDFX+XML', 'application/cdmi-capability', 'application/cdmi-container', 'application/cdmi-domain', 'application/cdmi-object', 'application/cdmi-queue', 'application/CEA', 'application/cea-2018+xml', 'application/cellml+xml', 'application/cfw', 'application/cms', 'application/cnrp+xml', 'application/commonground', 'application/conference-info+xml', 'application/cpl+xml', 'application/csrattrs', 'application/csta+xml', 'application/CSTAdata+xml', 'application/cybercash', 'application/dash+xml', 'application/dashdelta', 'application/davmount+xml', 'application/dca-rft', 'application/DCD', 'application/dec-dx', 'application/dialog-info+xml', 'application/dicom', 'application/DII', 'application/DIT', 'application/dns', 'application/dskpp+xml', 'application/dssc+der', 'application/dssc+xml', 'application/dvcs', 'application/ecmascript', 'application/EDI-consent', 'application/EDIFACT', 'application/EDI-X12', 'application/emotionml+xml', 'application/encaprtp', 'application/epp+xml', 'application/epub+zip', 'application/eshop', 'application/example', 'application/fastinfoset', 'application/fastsoap', 'application/fdt+xml', 'application/fits', 'application/font-sfnt', 'application/font-tdpfr', 'application/font-woff', 'application/framework-attributes+xml', 'application/gzip', 'application/H224', 'application/held+xml', 'application/http', 'application/hyperstudio', 'application/ibe-key-request+xml', 'application/ibe-pkg-reply+xml', 'application/ibe-pp-data', 'application/iges', 'application/im-iscomposing+xml', 'application/index', 'application/index.cmd', 'application/index.obj', 'application/index.response', 'application/index.vnd', 'application/inkml+xml', 'application/IOTP', 'application/ipfix', 'application/ipp', 'application/ISUP', 'application/its+xml', 'application/javascript', 'application/jose', 'application/jwt', 'application/kpml-request+xml', 'application/kpml-response+xml', 'application/link-format', 'application/load-control+xml', 'application/lost+xml', 'application/lostsync+xml', 'application/LXF', 'application/mac-binhex40', 'application/macwriteii', 'application/mads+xml', 'application/marc', 'application/marcxml+xml', 'application/mathematica', 'application/mbms-associated-procedure-description+xml', 'application/mbms-deregister+xml', 'application/mbms-envelope+xml', 'application/mbms-msk-response+xml', 'application/mbms-msk+xml', 'application/mbms-protection-description+xml', 'application/mbms-reception-report+xml', 'application/mbms-register-response+xml', 'application/mbms-register+xml', 'application/mbms-schedule+xml', 'application/mbms-user-service-description+xml', 'application/mbox+xml', 'application/media_control+xml', 'application/media-policy-dataset+xml', 'application/mediaservercontrol+xml', 'application/metalink4+xml', 'application/mets+xml', 'application/MF4', 'application/mikey', 'application/mods+xml', 'application/moss-keys', 'application/moss-signature', 'application/mosskey-data', 'application/mosskey-request', 'application/mp21', 'application/mp4', 'application/mpeg4-generic', 'application/mpeg4-iod', 'application/mpeg4-iod-xmt', 'application/mrb-consumer+xml', 'application/mrb-publish+xml', 'application/msc-ivr+xml', 'application/msc-mixer+xml', 'application/msword', 'application/mxf', 'application/nasdata', 'application/news-checkgroups', 'application/news-groupinfo', 'application/news-transmission', 'application/nlsml+xml', 'application/nss', 'application/ocsp-request', 'application/oscp-response', 'application/octet-stream', 'application/ODA', 'application/ODX', 'application/oebps-package+xml', 'application/ogg', 'application/oxps', 'application/p2p-overlay+xml', 'application/patch-ops-error+xml', 'application/pdf', 'application/PDX', 'application/pgp-encrypted', 'application/pgp-signature', 'application/pidf-diff+xml', 'application/pidf+xml', 'application/pkcs10', 'application/pkcs7-mime', 'application/pkcs7-signature', 'application/pkcs8', 'application/pkix-attr-cert', 'application/pkix-cert', 'application/pkix-crl', 'application/pkix-pkipath', 'application/pkixcmp', 'application/pls+xml', 'application/poc-settings+xml', 'application/postscript', 'application/provenance+xml', 'application/prs.alvestrand.titrax-sheet', 'application/prs.cww', 'application/prs.hpub+zip', 'application/prs.nprend', 'application/prs.plucker', 'application/prs.rdf-xml-crypt', 'application/prs.xsf+xml', 'application/pskc+xml', 'application/rdf+xml', 'application/QSIG', 'application/raptorfec', 'application/reginfo+xml', 'application/relax-ng-compact-syntax', 'application/remote-printing', 'application/resource-lists-diff+xml', 'application/resource-lists+xml', 'application/riscos', 'application/rlmi+xml', 'application/rls-services+xml', 'application/rpki-ghostbusters', 'application/rpki-manifest', 'application/rpki-roa', 'application/rpki-updown', 'application/rtf', 'application/rtploopback', 'application/rtx', 'application/samlassertion+xml', 'application/samlmetadata+xml', 'application/sbml+xml', 'application/scaip+xml', 'application/scvp-cv-request', 'application/scvp-cv-response', 'application/scvp-vp-request', 'application/scvp-vp-response', 'application/sdp', 'application/sep-exi', 'application/sep+xml', 'application/session-info', 'application/set-payment', 'application/set-payment-initiation', 'application/set-registration', 'application/set-registration-initiation', 'application/SGML', 'application/sgml-open-catalog', 'application/shf+xml', 'application/sieve', 'application/simple-filter+xml', 'application/simple-message-summary', 'application/simpleSymbolContainer', 'application/slate', 'application/smil', 'application/smil+xml', 'application/smpte336m', 'application/soap+fastinfoset', 'application/soap+xml', 'application/spirits-event+xml', 'application/sql', 'application/srgs', 'application/srgs+xml', 'application/sru+xml', 'application/ssml+xml', 'application/tamp-apex-update', 'application/tamp-apex-update-confirm', 'application/tamp-community-update', 'application/tamp-community-update-confirm', 'application/tamp-error', 'application/tamp-sequence-adjust', 'application/tamp-sequence-adjust-confirm', 'application/tamp-status-query', 'application/tamp-status-response', 'application/tamp-update', 'application/tamp-update-confirm', 'application/tei+xml', 'application/thraud+xml', 'application/timestamp-query', 'application/timestamp-reply', 'application/timestamped-data', 'application/ttml+xml', 'application/tve-trigger', 'application/ulpfec', 'application/urc-grpsheet+xml', 'application/urc-ressheet+xml', 'application/urc-targetdesc+xml', 'application/urc-uisocketdesc+xml', 'application/vcard+xml', 'application/vemmi', 'application/vnd.3gpp.bsf+xml', 'application/vnd.3gpp.pic-bw-large', 'application/vnd.3gpp.pic-bw-small', 'application/vnd.3gpp.pic-bw-var', 'application/vnd.3gpp.sms', 'application/vnd.3gpp2.bcmcsinfo+xml', 'application/vnd.3gpp2.sms', 'application/vnd.3gpp2.tcap', 'application/vnd.3M.Post-it-Notes', 'application/vnd.accpac.simply.aso', 'application/vnd.accpac.simply.imp', 'application/vnd-acucobol', 'application/vnd.acucorp', 'application/vnd.adobe.flash-movie', 'application/vnd.adobe.formscentral.fcdt', 'application/vnd.adobe.fxp', 'application/vnd.adobe.partial-upload', 'application/vnd.adobe.xdp+xml', 'application/vnd.adobe.xfdf', 'application/vnd.aether.imp', 'application/vnd.ah-barcode', 'application/vnd.ahead.space', 'application/vnd.airzip.filesecure.azf', 'application/vnd.airzip.filesecure.azs', 'application/vnd.americandynamics.acc', 'application/vnd.amiga.ami', 'application/vnd.amundsen.maze+xml', 'application/vnd.anser-web-certificate-issue-initiation', 'application/vnd.antix.game-component', 'application/vnd.apache.thrift.binary', 'application/vnd.apache.thrift.compact', 'application/vnd.apple.mpegurl', 'application/vnd.apple.installer+xml', 'application/vnd.arastra.swi', 'application/vnd.aristanetworks.swi', 'application/vnd.artsquare', 'application/vnd.astraea-software.iota', 'application/vnd.audiograph', 'application/vnd.autopackage', 'application/vnd.avistar+xml', 'application/vnd.balsamiq.bmml+xml', 'application/vnd.blueice.multipass', 'application/vnd.bluetooth.ep.oob', 'application/vnd.bluetooth.le.oob', 'application/vnd.bmi', 'application/vnd.businessobjects', 'application/vnd.cab-jscript', 'application/vnd.canon-cpdl', 'application/vnd.canon-lips', 'application/vnd.cendio.thinlinc.clientconf', 'application/vnd.century-systems.tcp_stream', 'application/vnd.chemdraw+xml', 'application/vnd.chipnuts.karaoke-mmd', 'application/vnd.cinderella', 'application/vnd.cirpack.isdn-ext', 'application/vnd.citationstyles.style+xml', 'application/vnd.claymore', 'application/vnd.cloanto.rp9', 'application/vnd.clonk.c4group', 'application/vnd.cluetrust.cartomobile-config', 'application/vnd.cluetrust.cartomobile-config-pkg', 'application/vnd.coffeescript', 'application/vnd.commerce-battelle', 'application/vnd.commonspace', 'application/vnd.cosmocaller', 'application/vnd.contact.cmsg', 'application/vnd.crick.clicker', 'application/vnd.crick.clicker.keyboard', 'application/vnd.crick.clicker.palette', 'application/vnd.crick.clicker.template', 'application/vnd.crick.clicker.wordbank', 'application/vnd.criticaltools.wbs+xml', 'application/vnd.ctc-posml', 'application/vnd.ctct.ws+xml', 'application/vnd.cups-pdf', 'application/vnd.cups-postscript', 'application/vnd.cups-ppd', 'application/vnd.cups-raster', 'application/vnd.cups-raw', 'application/vnd-curl', 'application/vnd.cyan.dean.root+xml', 'application/vnd.cybank', 'application/vnd-dart', 'application/vnd.data-vision.rdz', 'application/vnd.debian.binary-package', 'application/vnd.dece.data', 'application/vnd.dece.ttml+xml', 'application/vnd.dece.unspecified', 'application/vnd.dece-zip', 'application/vnd.denovo.fcselayout-link', 'application/vnd.desmume-movie', 'application/vnd.dir-bi.plate-dl-nosuffix', 'application/vnd.dm.delegation+xml', 'application/vnd.dna', 'application/vnd.dolby.mobile.1', 'application/vnd.dolby.mobile.2', 'application/vnd.doremir.scorecloud-binary-document', 'application/vnd.dpgraph', 'application/vnd.dreamfactory', 'application/vnd.dtg.local', 'application/vnd.dtg.local.flash', 'application/vnd.dtg.local.html', 'application/vnd.dvb.ait', 'application/vnd.dvb.dvbj', 'application/vnd.dvb.esgcontainer', 'application/vnd.dvb.ipdcdftnotifaccess', 'application/vnd.dvb.ipdcesgaccess', 'application/vnd.dvb.ipdcesgaccess2', 'application/vnd.dvb.ipdcesgpdd', 'application/vnd.dvb.ipdcroaming', 'application/vnd.dvb.iptv.alfec-base', 'application/vnd.dvb.iptv.alfec-enhancement', 'application/vnd.dvb.notif-aggregate-root+xml', 'application/vnd.dvb.notif-container+xml', 'application/vnd.dvb.notif-generic+xml', 'application/vnd.dvb.notif-ia-msglist+xml', 'application/vnd.dvb.notif-ia-registration-request+xml', 'application/vnd.dvb.notif-ia-registration-response+xml', 'application/vnd.dvb.notif-init+xml', 'application/vnd.dvb.pfr', 'application/vnd.dvb_service', 'application/vnd-dxr', 'application/vnd.dynageo', 'application/vnd.dzr', 'application/vnd.easykaraoke.cdgdownload', 'application/vnd.ecdis-update', 'application/vnd.ecowin.chart', 'application/vnd.ecowin.filerequest', 'application/vnd.ecowin.fileupdate', 'application/vnd.ecowin.series', 'application/vnd.ecowin.seriesrequest', 'application/vnd.ecowin.seriesupdate', 'application/vnd.emclient.accessrequest+xml', 'application/vnd.enliven', 'application/vnd.enphase.envoy', 'application/vnd.eprints.data+xml', 'application/vnd.epson.esf', 'application/vnd.epson.msf', 'application/vnd.epson.quickanime', 'application/vnd.epson.salt', 'application/vnd.epson.ssf', 'application/vnd.ericsson.quickcall', 'application/vnd.eszigno3+xml', 'application/vnd.etsi.aoc+xml', 'application/vnd.etsi.asic-s+zip', 'application/vnd.etsi.asic-e+zip', 'application/vnd.etsi.cug+xml', 'application/vnd.etsi.iptvcommand+xml', 'application/vnd.etsi.iptvdiscovery+xml', 'application/vnd.etsi.iptvprofile+xml', 'application/vnd.etsi.iptvsad-bc+xml', 'application/vnd.etsi.iptvsad-cod+xml', 'application/vnd.etsi.iptvsad-npvr+xml', 'application/vnd.etsi.iptvservice+xml', 'application/vnd.etsi.iptvsync+xml', 'application/vnd.etsi.iptvueprofile+xml', 'application/vnd.etsi.mcid+xml', 'application/vnd.etsi.mheg5', 'application/vnd.etsi.overload-control-policy-dataset+xml', 'application/vnd.etsi.pstn+xml', 'application/vnd.etsi.sci+xml', 'application/vnd.etsi.simservs+xml', 'application/vnd.etsi.timestamp-token', 'application/vnd.etsi.tsl+xml', 'application/vnd.etsi.tsl.der', 'application/vnd.eudora.data', 'application/vnd.ezpix-album', 'application/vnd.ezpix-package', 'application/vnd.f-secure.mobile', 'application/vnd.fastcopy-disk-image', 'application/vnd-fdf', 'application/vnd.fdsn.mseed', 'application/vnd.fdsn.seed', 'application/vnd.ffsns', 'application/vnd.fints', 'application/vnd.FloGraphIt', 'application/vnd.fluxtime.clip', 'application/vnd.font-fontforge-sfd', 'application/vnd.framemaker', 'application/vnd.frogans.fnc', 'application/vnd.frogans.ltf', 'application/vnd.fsc.weblaunch', 'application/vnd.fujitsu.oasys', 'application/vnd.fujitsu.oasys2', 'application/vnd.fujitsu.oasys3', 'application/vnd.fujitsu.oasysgp', 'application/vnd.fujitsu.oasysprs', 'application/vnd.fujixerox.ART4', 'application/vnd.fujixerox.ART-EX', 'application/vnd.fujixerox.ddd', 'application/vnd.fujixerox.docuworks', 'application/vnd.fujixerox.docuworks.binder', 'application/vnd.fujixerox.docuworks.container', 'application/vnd.fujixerox.HBPL', 'application/vnd.fut-misnet', 'application/vnd.fuzzysheet', 'application/vnd.genomatix.tuxedo', 'application/vnd.geocube+xml', 'application/vnd.geogebra.file', 'application/vnd.geogebra.tool', 'application/vnd.geometry-explorer', 'application/vnd.geonext', 'application/vnd.geoplan', 'application/vnd.geospace', 'application/vnd.gerber', 'application/vnd.globalplatform.card-content-mgt', 'application/vnd.globalplatform.card-content-mgt-response', 'application/vnd.gmx', 'application/vnd.google-earth.kml+xml', 'application/vnd.google-earth.kmz', 'application/vnd.gov.sk.e-form+xml', 'application/vnd.gov.sk.e-form+zip', 'application/vnd.gov.sk.xmldatacontainer+xml', 'application/vnd.grafeq', 'application/vnd.gridmp', 'application/vnd.groove-account', 'application/vnd.groove-help', 'application/vnd.groove-identity-message', 'application/vnd.groove-injector', 'application/vnd.groove-tool-message', 'application/vnd.groove-tool-template', 'application/vnd.groove-vcard', 'application/vnd.hal+xml', 'application/vnd.HandHeld-Entertainment+xml', 'application/vnd.hbci', 'application/vnd.hcl-bireports', 'application/vnd.hhe.lesson-player', 'application/vnd.hp-HPGL', 'application/vnd.hp-hpid', 'application/vnd.hp-hps', 'application/vnd.hp-jlyt', 'application/vnd.hp-PCL', 'application/vnd.hp-PCLXL', 'application/vnd.httphone', 'application/vnd.hydrostatix.sof-data', 'application/vnd.hzn-3d-crossword', 'application/vnd.ibm.afplinedata', 'application/vnd.ibm.electronic-media', 'application/vnd.ibm.MiniPay', 'application/vnd.ibm.modcap', 'application/vnd.ibm.rights-management', 'application/vnd.ibm.secure-container', 'application/vnd.iccprofile', 'application/vnd.ieee.1905', 'application/vnd.igloader', 'application/vnd.immervision-ivp', 'application/vnd.immervision-ivu', 'application/vnd.ims.imsccv1p1', 'application/vnd.ims.imsccv1p2', 'application/vnd.ims.imsccv1p3', 'application/vnd.informedcontrol.rms+xml', 'application/vnd.infotech.project', 'application/vnd.infotech.project+xml', 'application/vnd.informix-visionary', 'application/vnd.innopath.wamp.notification', 'application/vnd.insors.igm', 'application/vnd.intercon.formnet', 'application/vnd.intergeo', 'application/vnd.intertrust.digibox', 'application/vnd.intertrust.nncp', 'application/vnd.intu.qbo', 'application/vnd.intu.qfx', 'application/vnd.iptc.g2.catalogitem+xml', 'application/vnd.iptc.g2.conceptitem+xml', 'application/vnd.iptc.g2.knowledgeitem+xml', 'application/vnd.iptc.g2.newsitem+xml', 'application/vnd.iptc.g2.newsmessage+xml', 'application/vnd.iptc.g2.packageitem+xml', 'application/vnd.iptc.g2.planningitem+xml', 'application/vnd.ipunplugged.rcprofile', 'application/vnd.irepository.package+xml', 'application/vnd.is-xpr', 'application/vnd.isac.fcs', 'application/vnd.jam', 'application/vnd.japannet-directory-service', 'application/vnd.japannet-jpnstore-wakeup', 'application/vnd.japannet-payment-wakeup', 'application/vnd.japannet-registration', 'application/vnd.japannet-registration-wakeup', 'application/vnd.japannet-setstore-wakeup', 'application/vnd.japannet-verification', 'application/vnd.japannet-verification-wakeup', 'application/vnd.jcp.javame.midlet-rms', 'application/vnd.jisp', 'application/vnd.joost.joda-archive', 'application/vnd.jsk.isdn-ngn', 'application/vnd.kahootz', 'application/vnd.kde.karbon', 'application/vnd.kde.kchart', 'application/vnd.kde.kformula', 'application/vnd.kde.kivio', 'application/vnd.kde.kontour', 'application/vnd.kde.kpresenter', 'application/vnd.kde.kspread', 'application/vnd.kde.kword', 'application/vnd.kenameaapp', 'application/vnd.kidspiration', 'application/vnd.Kinar', 'application/vnd.koan', 'application/vnd.kodak-descriptor', 'application/vnd.las.las+xml', 'application/vnd.liberty-request+xml', 'application/vnd.llamagraphics.life-balance.desktop', 'application/vnd.llamagraphics.life-balance.exchange+xml', 'application/vnd.lotus-1-2-3', 'application/vnd.lotus-approach', 'application/vnd.lotus-freelance', 'application/vnd.lotus-notes', 'application/vnd.lotus-organizer', 'application/vnd.lotus-screencam', 'application/vnd.lotus-wordpro', 'application/vnd.macports.portpkg', 'application/vnd.marlin.drm.actiontoken+xml', 'application/vnd.marlin.drm.conftoken+xml', 'application/vnd.marlin.drm.license+xml', 'application/vnd.marlin.drm.mdcf', 'application/vnd.maxmind.maxmind-db', 'application/vnd.mcd', 'application/vnd.medcalcdata', 'application/vnd.mediastation.cdkey', 'application/vnd.meridian-slingshot', 'application/vnd.MFER', 'application/vnd.mfmp', 'application/vnd.micrografx.flo', 'application/vnd.micrografx-igx', 'application/vnd.microsoft.portable-executable', 'application/vnd-mif', 'application/vnd.minisoft-hp3000-save', 'application/vnd.mitsubishi.misty-guard.trustweb', 'application/vnd.Mobius.DAF', 'application/vnd.Mobius.DIS', 'application/vnd.Mobius.MBK', 'application/vnd.Mobius.MQY', 'application/vnd.Mobius.MSL', 'application/vnd.Mobius.PLC', 'application/vnd.Mobius.TXF', 'application/vnd.mophun.application', 'application/vnd.mophun.certificate', 'application/vnd.motorola.flexsuite', 'application/vnd.motorola.flexsuite.adsi', 'application/vnd.motorola.flexsuite.fis', 'application/vnd.motorola.flexsuite.gotap', 'application/vnd.motorola.flexsuite.kmr', 'application/vnd.motorola.flexsuite.ttc', 'application/vnd.motorola.flexsuite.wem', 'application/vnd.motorola.iprm', 'application/vnd.mozilla.xul+xml', 'application/vnd.ms-artgalry', 'application/vnd.ms-asf', 'application/vnd.ms-cab-compressed', 'application/vnd.ms-3mfdocument', 'application/vnd.ms-excel', 'application/vnd.ms-excel.addin.macroEnabled.12', 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', 'application/vnd.ms-excel.sheet.macroEnabled.12', 'application/vnd.ms-excel.template.macroEnabled.12', 'application/vnd.ms-fontobject', 'application/vnd.ms-htmlhelp', 'application/vnd.ms-ims', 'application/vnd.ms-lrm', 'application/vnd.ms-office.activeX+xml', 'application/vnd.ms-officetheme', 'application/vnd.ms-playready.initiator+xml', 'application/vnd.ms-powerpoint', 'application/vnd.ms-powerpoint.addin.macroEnabled.12', 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', 'application/vnd.ms-powerpoint.slide.macroEnabled.12', 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', 'application/vnd.ms-powerpoint.template.macroEnabled.12', 'application/vnd.ms-project', 'application/vnd.ms-tnef', 'application/vnd.ms-windows.printerpairing', 'application/vnd.ms-wmdrm.lic-chlg-req', 'application/vnd.ms-wmdrm.lic-resp', 'application/vnd.ms-wmdrm.meter-chlg-req', 'application/vnd.ms-wmdrm.meter-resp', 'application/vnd.ms-word.document.macroEnabled.12', 'application/vnd.ms-word.template.macroEnabled.12', 'application/vnd.ms-works', 'application/vnd.ms-wpl', 'application/vnd.ms-xpsdocument', 'application/vnd.msa-disk-image', 'application/vnd.mseq', 'application/vnd.msign', 'application/vnd.multiad.creator', 'application/vnd.multiad.creator.cif', 'application/vnd.musician', 'application/vnd.music-niff', 'application/vnd.muvee.style', 'application/vnd.mynfc', 'application/vnd.ncd.control', 'application/vnd.ncd.reference', 'application/vnd.nervana', 'application/vnd.netfpx', 'application/vnd.neurolanguage.nlu', 'application/vnd.nintendo.snes.rom', 'application/vnd.nintendo.nitro.rom', 'application/vnd.nitf', 'application/vnd.noblenet-directory', 'application/vnd.noblenet-sealer', 'application/vnd.noblenet-web', 'application/vnd.nokia.catalogs', 'application/vnd.nokia.conml+wbxml', 'application/vnd.nokia.conml+xml', 'application/vnd.nokia.iptv.config+xml', 'application/vnd.nokia.iSDS-radio-presets', 'application/vnd.nokia.landmark+wbxml', 'application/vnd.nokia.landmark+xml', 'application/vnd.nokia.landmarkcollection+xml', 'application/vnd.nokia.ncd', 'application/vnd.nokia.n-gage.ac+xml', 'application/vnd.nokia.n-gage.data', 'application/vnd.nokia.n-gage.symbian.install', 'application/vnd.nokia.pcd+wbxml', 'application/vnd.nokia.pcd+xml', 'application/vnd.nokia.radio-preset', 'application/vnd.nokia.radio-presets', 'application/vnd.novadigm.EDM', 'application/vnd.novadigm.EDX', 'application/vnd.novadigm.EXT', 'application/vnd.ntt-local.content-share', 'application/vnd.ntt-local.file-transfer', 'application/vnd.ntt-local.ogw_remote-access', 'application/vnd.ntt-local.sip-ta_remote', 'application/vnd.ntt-local.sip-ta_tcp_stream', 'application/vnd.oasis.opendocument.chart', 'application/vnd.oasis.opendocument.chart-template', 'application/vnd.oasis.opendocument.database', 'application/vnd.oasis.opendocument.formula', 'application/vnd.oasis.opendocument.formula-template', 'application/vnd.oasis.opendocument.graphics', 'application/vnd.oasis.opendocument.graphics-template', 'application/vnd.oasis.opendocument.image', 'application/vnd.oasis.opendocument.image-template', 'application/vnd.oasis.opendocument.presentation', 'application/vnd.oasis.opendocument.presentation-template', 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.spreadsheet-template', 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.text-master', 'application/vnd.oasis.opendocument.text-template', 'application/vnd.oasis.opendocument.text-web', 'application/vnd.obn', 'application/vnd.oipf.contentaccessdownload+xml', 'application/vnd.oipf.contentaccessstreaming+xml', 'application/vnd.oipf.cspg-hexbinary', 'application/vnd.oipf.dae.svg+xml', 'application/vnd.oipf.dae.xhtml+xml', 'application/vnd.oipf.mippvcontrolmessage+xml', 'application/vnd.oipf.pae.gem', 'application/vnd.oipf.spdiscovery+xml', 'application/vnd.oipf.spdlist+xml', 'application/vnd.oipf.ueprofile+xml', 'application/vnd.oipf.userprofile+xml', 'application/vnd.olpc-sugar', 'application/vnd.oma.bcast.associated-procedure-parameter+xml', 'application/vnd.oma.bcast.drm-trigger+xml', 'application/vnd.oma.bcast.imd+xml', 'application/vnd.oma.bcast.ltkm', 'application/vnd.oma.bcast.notification+xml', 'application/vnd.oma.bcast.provisioningtrigger', 'application/vnd.oma.bcast.sgboot', 'application/vnd.oma.bcast.sgdd+xml', 'application/vnd.oma.bcast.sgdu', 'application/vnd.oma.bcast.simple-symbol-container', 'application/vnd.oma.bcast.smartcard-trigger+xml', 'application/vnd.oma.bcast.sprov+xml', 'application/vnd.oma.bcast.stkm', 'application/vnd.oma.cab-address-book+xml', 'application/vnd.oma.cab-feature-handler+xml', 'application/vnd.oma.cab-pcc+xml', 'application/vnd.oma.cab-subs-invite+xml', 'application/vnd.oma.cab-user-prefs+xml', 'application/vnd.oma.dcd', 'application/vnd.oma.dcdc', 'application/vnd.oma.dd2+xml', 'application/vnd.oma.drm.risd+xml', 'application/vnd.oma.group-usage-list+xml', 'application/vnd.oma.pal+xml', 'application/vnd.oma.poc.detailed-progress-report+xml', 'application/vnd.oma.poc.final-report+xml', 'application/vnd.oma.poc.groups+xml', 'application/vnd.oma.poc.invocation-descriptor+xml', 'application/vnd.oma.poc.optimized-progress-report+xml', 'application/vnd.oma.push', 'application/vnd.oma.scidm.messages+xml', 'application/vnd.oma.xcap-directory+xml', 'application/vnd.omads-email+xml', 'application/vnd.omads-file+xml', 'application/vnd.omads-folder+xml', 'application/vnd.omaloc-supl-init', 'application/vnd.oma-scws-config', 'application/vnd.oma-scws-http-request', 'application/vnd.oma-scws-http-response', 'application/vnd.openeye.oeb', 'application/vnd.openxmlformats-officedocument.custom-properties+xml', 'application/vnd.openxmlformats-officedocument.customXmlProperties+xml', 'application/vnd.openxmlformats-officedocument.drawing+xml', 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml', 'application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml', 'application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml', 'application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml', 'application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml', 'application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml', 'application/vnd.openxmlformats-officedocument.extended-properties+xml', 'application/vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml', 'application/vnd.openxmlformats-officedocument.presentationml.comments+xml', 'application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml', 'application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml', 'application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml', 'application/vnd.openxmlformats-officedocument.presentationml.presProps+xml', 'application/vnd.openxmlformats-officedocument.presentationml.slide', 'application/vnd.openxmlformats-officedocument.presentationml.slide+xml', 'application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml', 'application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml', 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 'application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml', 'application/vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo+xml', 'application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml', 'application/vnd.openxmlformats-officedocument.presentationml.tags+xml', 'application/vnd.openxmlformats-officedocument.presentationml-template', 'application/vnd.openxmlformats-officedocument.presentationml.template.main+xml', 'application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml-template', 'application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml', 'application/vnd.openxmlformats-officedocument.theme+xml', 'application/vnd.openxmlformats-officedocument.themeOverride+xml', 'application/vnd.openxmlformats-officedocument.vmlDrawing', 'application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml', 'application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml', 'application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml', 'application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml', 'application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml', 'application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml', 'application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml', 'application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml', 'application/vnd.openxmlformats-officedocument.wordprocessingml-template', 'application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml', 'application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml', 'application/vnd.openxmlformats-package.core-properties+xml', 'application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml', 'application/vnd.openxmlformats-package.relationships+xml', 'application/vnd.orange.indata', 'application/vnd.osa.netdeploy', 'application/vnd.osgeo.mapguide.package', 'application/vnd.osgi.bundle', 'application/vnd.osgi.dp', 'application/vnd.osgi.subsystem', 'application/vnd.otps.ct-kip+xml', 'application/vnd.palm', 'application/vnd.panoply', 'application/vnd.paos+xml', 'application/vnd.pawaafile', 'application/vnd.pcos', 'application/vnd.pg.format', 'application/vnd.pg.osasli', 'application/vnd.piaccess.application-licence', 'application/vnd.picsel', 'application/vnd.pmi.widget', 'application/vnd.poc.group-advertisement+xml', 'application/vnd.pocketlearn', 'application/vnd.powerbuilder6', 'application/vnd.powerbuilder6-s', 'application/vnd.powerbuilder7', 'application/vnd.powerbuilder75', 'application/vnd.powerbuilder75-s', 'application/vnd.powerbuilder7-s', 'application/vnd.preminet', 'application/vnd.previewsystems.box', 'application/vnd.proteus.magazine', 'application/vnd.publishare-delta-tree', 'application/vnd.pvi.ptid1', 'application/vwg-multiplexed', 'application/vnd.pwg-xhtml-print+xml', 'application/vnd.qualcomm.brew-app-res', 'application/vnd.Quark.QuarkXPress', 'application/vnd.quobject-quoxdocument', 'application/vnd.radisys.moml+xml', 'application/vnd.radisys.msml-audit-conf+xml', 'application/vnd.radisys.msml-audit-conn+xml', 'application/vnd.radisys.msml-audit-dialog+xml', 'application/vnd.radisys.msml-audit-stream+xml', 'application/vnd.radisys.msml-audit+xml', 'application/vnd.radisys.msml-conf+xml', 'application/vnd.radisys.msml-dialog-base+xml', 'application/vnd.radisys.msml-dialog-fax-detect+xml', 'application/vnd.radisys.msml-dialog-fax-sendrecv+xml', 'application/vnd.radisys.msml-dialog-group+xml', 'application/vnd.radisys.msml-dialog-speech+xml', 'application/vnd.radisys.msml-dialog-transform+xml', 'application/vnd.radisys.msml-dialog+xml', 'application/vnd.radisys.msml+xml', 'application/vnd.rainstor.data', 'application/vnd.rapid', 'application/vnd.realvnc.bed', 'application/vnd.recordare.musicxml', 'application/vnd.recordare.musicxml+xml', 'application/vnd.renlearn.rlprint', 'application/vnd.rig.cryptonote', 'application/vnd.route66.link66+xml', 'application/vnd.rs-274x', 'application/vnd.ruckus.download', 'application/vnd.s3sms', 'application/vnd.sailingtracker.track', 'application/vnd.sbm.cid', 'application/vnd.sbm.mid2', 'application/vnd.scribus', 'application/vnd.sealed.3df', 'application/vnd.sealed.csf', 'application/vnd.sealed-doc', 'application/vnd.sealed-eml', 'application/vnd.sealed-mht', 'application/vnd.sealed.net', 'application/vnd.sealed-ppt', 'application/vnd.sealed-tiff', 'application/vnd.sealed-xls', 'application/vnd.sealedmedia.softseal-html', 'application/vnd.sealedmedia.softseal-pdf', 'application/vnd.seemail', 'application/vnd-sema', 'application/vnd.semd', 'application/vnd.semf', 'application/vnd.shana.informed.formdata', 'application/vnd.shana.informed.formtemplate', 'application/vnd.shana.informed.interchange', 'application/vnd.shana.informed.package', 'application/vnd.SimTech-MindMapper', 'application/vnd.smaf', 'application/vnd.smart.notebook', 'application/vnd.smart.teacher', 'application/vnd.software602.filler.form+xml', 'application/vnd.software602.filler.form-xml-zip', 'application/vnd.solent.sdkm+xml', 'application/vnd.spotfire.dxp', 'application/vnd.spotfire.sfs', 'application/vnd.sss-cod', 'application/vnd.sss-dtf', 'application/vnd.sss-ntf', 'application/vnd.stepmania.package', 'application/vnd.stepmania.stepchart', 'application/vnd.street-stream', 'application/vnd.sun.wadl+xml', 'application/vnd.sus-calendar', 'application/vnd.svd', 'application/vnd.swiftview-ics', 'application/vnd.syncml.dm.notification', 'application/vnd.syncml.dmddf+xml', 'application/vnd.syncml.dmtnds+wbxml', 'application/vnd.syncml.dmtnds+xml', 'application/vnd.syncml.dmddf+wbxml', 'application/vnd.syncml.dm+wbxml', 'application/vnd.syncml.dm+xml', 'application/vnd.syncml.ds.notification', 'application/vnd.syncml+xml', 'application/vnd.tao.intent-module-archive', 'application/vnd.tcpdump.pcap', 'application/vnd.tmd.mediaflex.api+xml', 'application/vnd.tmobile-livetv', 'application/vnd.trid.tpt', 'application/vnd.triscape.mxs', 'application/vnd.trueapp', 'application/vnd.truedoc', 'application/vnd.ubisoft.webplayer', 'application/vnd.ufdl', 'application/vnd.uiq.theme', 'application/vnd.umajin', 'application/vnd.unity', 'application/vnd.uoml+xml', 'application/vnd.uplanet.alert', 'application/vnd.uplanet.alert-wbxml', 'application/vnd.uplanet.bearer-choice', 'application/vnd.uplanet.bearer-choice-wbxml', 'application/vnd.uplanet.cacheop', 'application/vnd.uplanet.cacheop-wbxml', 'application/vnd.uplanet.channel', 'application/vnd.uplanet.channel-wbxml', 'application/vnd.uplanet.list', 'application/vnd.uplanet.listcmd', 'application/vnd.uplanet.listcmd-wbxml', 'application/vnd.uplanet.list-wbxml', 'application/vnd.uplanet.signal', 'application/vnd.valve.source.material', 'application/vnd.vcx', 'application/vnd.vd-study', 'application/vnd.vectorworks', 'application/vnd.verimatrix.vcas', 'application/vnd.vidsoft.vidconference', 'application/vnd.visio', 'application/vnd.visionary', 'application/vnd.vividence.scriptfile', 'application/vnd.vsf', 'application/vnd.wap.sic', 'application/vnd.wap-slc', 'application/vnd.wap-wbxml', 'application/vnd-wap-wmlc', 'application/vnd.wap.wmlscriptc', 'application/vnd.webturbo', 'application/vnd.wfa.p2p', 'application/vnd.wfa.wsc', 'application/vnd.windows.devicepairing', 'application/vnd.wmc', 'application/vnd.wmf.bootstrap', 'application/vnd.wolfram.mathematica', 'application/vnd.wolfram.mathematica.package', 'application/vnd.wolfram.player', 'application/vnd.wordperfect', 'application/vnd.wqd', 'application/vnd.wrq-hp3000-labelled', 'application/vnd.wt.stf', 'application/vnd.wv.csp+xml', 'application/vnd.wv.csp+wbxml', 'application/vnd.wv.ssp+xml', 'application/vnd.xara', 'application/vnd.xfdl', 'application/vnd.xfdl.webform', 'application/vnd.xmi+xml', 'application/vnd.xmpie.cpkg', 'application/vnd.xmpie.dpkg', 'application/vnd.xmpie.plan', 'application/vnd.xmpie.ppkg', 'application/vnd.xmpie.xlim', 'application/vnd.yamaha.hv-dic', 'application/vnd.yamaha.hv-script', 'application/vnd.yamaha.hv-voice', 'application/vnd.yamaha.openscoreformat.osfpvg+xml', 'application/vnd.yamaha.openscoreformat', 'application/vnd.yamaha.remote-setup', 'application/vnd.yamaha.smaf-audio', 'application/vnd.yamaha.smaf-phrase', 'application/vnd.yamaha.through-ngn', 'application/vnd.yamaha.tunnel-udpencap', 'application/vnd.yaoweme', 'application/vnd.yellowriver-custom-menu', 'application/vnd.zul', 'application/vnd.zzazz.deck+xml', 'application/voicexml+xml', 'application/vq-rtcpxr', 'application/watcherinfo+xml', 'application/whoispp-query', 'application/whoispp-response', 'application/wita', 'application/wordperfect5.1', 'application/wsdl+xml', 'application/wspolicy+xml', 'application/x-www-form-urlencoded', 'application/x400-bp', 'application/xacml+xml', 'application/xcap-att+xml', 'application/xcap-caps+xml', 'application/xcap-diff+xml', 'application/xcap-el+xml', 'application/xcap-error+xml', 'application/xcap-ns+xml', 'application/xcon-conference-info-diff+xml', 'application/xcon-conference-info+xml', 'application/xenc+xml', 'application/xhtml-voice+xml', 'application/xhtml+xml', 'application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity', 'application/xml-patch+xml', 'application/xmpp+xml', 'application/xop+xml', 'application/xv+xml', 'application/yang', 'application/yin+xml', 'application/zip', 'application/zlib', ]; foreach ($not_json_content_types as $json_content_type) { $message = '"' . $json_content_type . '" matches pattern ' . $json_pattern; $this->assertEquals(0, preg_match($json_pattern, $json_content_type), $message); } } public function testXmlDecoderOptions() { // Implicit default xml decoder should return object. $test = new Test(); $test->server('xml_with_cdata_response', 'GET'); $this->assertTrue(is_object($test->curl->response)); $this->assertFalse(strpos($test->curl->response->saveXML(), 'curl->setDefaultXmlDecoder(); $test->server('xml_with_cdata_response', 'GET'); $this->assertTrue(is_object($test->curl->response)); $this->assertFalse(strpos($test->curl->response->saveXML(), 'curl->setDefaultXmlDecoder($class_name, $options); $test->server('xml_with_cdata_response', 'GET'); $this->assertTrue(is_object($test->curl->response)); $this->assertTrue(strpos($test->curl->response->saveXML(), 'server('xml_with_cdata_response', 'POST'); $this->assertTrue(is_object($test->curl->response)); $this->assertInstanceOf('SimpleXMLElement', $test->curl->response); $this->assertFalse(strpos($test->curl->response->saveXML(), 'curl->setXmlDecoder(function ($response) { return simplexml_load_string($response, 'SimpleXMLElement', LIBXML_NOCDATA); }); $test->server('xml_with_cdata_response', 'POST'); $this->assertTrue(is_object($test->curl->response)); $this->assertInstanceOf('SimpleXMLElement', $test->curl->response); $this->assertTrue(strpos($test->curl->response->saveXML(), 'curl->setXmlDecoder(false); $test->server('xml_with_cdata_response', 'POST'); $this->assertTrue(is_string($test->curl->response)); } public function testXmlContentTypeDetection() { $xml_content_types = [ 'application/atom+xml', 'application/rss+xml', 'application/soap+xml', 'application/xml', 'text/xml', ]; $curl = new Curl(); $xml_pattern = \Helper\get_curl_property_value($curl, 'xmlPattern'); foreach ($xml_content_types as $xml_content_type) { $message = '"' . $xml_content_type . '" does not match pattern ' . $xml_pattern; $this->assertEquals(1, preg_match($xml_pattern, $xml_content_type), $message); } } public function testXmlResponse() { foreach ([ 'Content-Type', 'content-type', 'CONTENT-TYPE'] as $key) { foreach ([ 'application/atom+xml; charset=UTF-8', 'application/atom+xml;charset=UTF-8', 'application/rss+xml', 'application/rss+xml; charset=utf-8', 'application/rss+xml;charset=utf-8', 'application/soap+xml', 'application/soap+xml; charset=utf-8', 'application/soap+xml;charset=utf-8', 'application/xml', 'application/xml; charset=utf-8', 'application/xml;charset=utf-8', 'text/xml', 'text/xml; charset=utf-8', 'text/xml;charset=utf-8', ] as $value) { $test = new Test(); $test->server('xml_response', 'POST', [ 'key' => $key, 'value' => $value, ]); $this->assertInstanceOf('SimpleXMLElement', $test->curl->response); $doc = new \DOMDocument(); $doc->formatOutput = true; $rss = $doc->appendChild($doc->createElement('rss')); $rss->setAttribute('version', '2.0'); $channel = $doc->createElement('channel'); $title = $doc->createElement('title'); $title->appendChild($doc->createTextNode('Title')); $channel->appendChild($title); $link = $doc->createElement('link'); $link->appendChild($doc->createTextNode('Link')); $channel->appendChild($link); $description = $doc->createElement('description'); $description->appendChild($doc->createTextNode('Description')); $channel->appendChild($description); $rss->appendChild($channel); $xml = $doc->saveXML(); $this->assertEquals($xml, $test->curl->rawResponse); } } } public function testDefaultDecoder() { // Default. $test = new Test(); $test->server('download_file_size', 'GET'); $this->assertTrue(is_string($test->curl->response)); // Callable. $test = new Test(); $test->curl->setDefaultDecoder(function ($response) { return '123'; }); $test->server('download_file_size', 'GET'); $this->assertEquals('123', $test->curl->response); // "json". $test = new Test(); $test->curl->setDefaultDecoder('json'); $test->server('json_response', 'POST', [ 'key' => 'Content-Type', 'value' => 'application/but-not-json', ]); $this->assertInstanceOf('stdClass', $test->curl->response); // "xml". $test = new Test(); $test->curl->setDefaultDecoder('xml'); $test->server('xml_response', 'POST', [ 'key' => 'Content-Type', 'value' => 'text/but-not-xml', ]); $this->assertInstanceOf('SimpleXMLElement', $test->curl->response); // False. $test = new Test(); $test->curl->setDefaultDecoder('json'); $test->curl->setDefaultDecoder(false); $test->server('json_response', 'POST', [ 'key' => 'Content-Type', 'value' => 'application/but-not-json', ]); $this->assertTrue(is_string($test->curl->response)); } public function testEmptyResponse() { $response = "\r\n\r\n"; $reflector = new \ReflectionClass('\Curl\Curl'); $reflection_method = $reflector->getMethod('parseResponseHeaders'); $reflection_method->setAccessible(true); $curl = new Curl(); $response_headers = $reflection_method->invoke($curl, $response); $this->assertArrayHasKey('Status-Line', $response_headers); } public function testMalformedResponseHeaders() { $response = 'HTTP/1.0 403 Forbidden' . "\n" . 'Cache-Control: no-cache' . "\n" . 'Content-Type: text/html' . "\n" . 'Strict-Transport-Security: max-age=0' . "\r\n" . "\n"; $reflector = new \ReflectionClass('\Curl\Curl'); $reflection_method = $reflector->getMethod('parseResponseHeaders'); $reflection_method->setAccessible(true); $curl = new Curl(); $response_headers = $reflection_method->invoke($curl, $response); $this->assertTrue($response_headers instanceof CaseInsensitiveArray); } public function testArrayToStringConversion() { $test = new Test(); $test->server('post', 'POST', [ 'foo' => 'bar', 'baz' => [ ], ]); $this->assertEquals('foo=bar&baz=', $test->curl->response); $test = new Test(); $test->server('post', 'POST', [ 'foo' => 'bar', 'baz' => [ 'qux' => [ ], ], ]); $this->assertEquals('foo=bar&baz[qux]=', urldecode($test->curl->response)); $test = new Test(); $test->server('post', 'POST', [ 'foo' => 'bar', 'baz' => [ 'qux' => [ ], 'wibble' => 'wobble', ], ]); $this->assertEquals('foo=bar&baz[qux]=&baz[wibble]=wobble', urldecode($test->curl->response)); } public function testSuccessCallback() { $before_send_called = false; $success_called = false; $error_called = false; $complete_called = false; $test = new Test(); $curl = $test->curl; $curl->setHeader('X-DEBUG-TEST', 'get'); $curl->beforeSend(function ($instance) use ( &$before_send_called, &$success_called, &$error_called, &$complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($before_send_called); \PHPUnit\Framework\Assert::assertFalse($success_called); \PHPUnit\Framework\Assert::assertFalse($error_called); \PHPUnit\Framework\Assert::assertFalse($complete_called); $before_send_called = true; }); $curl->success(function ($instance) use ( &$before_send_called, &$success_called, &$error_called, &$complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($before_send_called); \PHPUnit\Framework\Assert::assertFalse($success_called); \PHPUnit\Framework\Assert::assertFalse($error_called); \PHPUnit\Framework\Assert::assertFalse($complete_called); $success_called = true; }); $curl->error(function ($instance) use ( &$before_send_called, &$success_called, &$error_called, &$complete_called ) { $error_called = true; }); $curl->complete(function ($instance) use ( &$before_send_called, &$success_called, &$error_called, &$complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($before_send_called); \PHPUnit\Framework\Assert::assertTrue($success_called); \PHPUnit\Framework\Assert::assertFalse($error_called); \PHPUnit\Framework\Assert::assertFalse($complete_called); $complete_called = true; }); $curl->get(Test::TEST_URL); $this->assertTrue($before_send_called); $this->assertTrue($success_called); $this->assertFalse($error_called); $this->assertTrue($complete_called); } public function testErrorCallback() { if ($this->skip_slow_tests) { $this->markTestSkipped(); } $before_send_called = false; $success_called = false; $error_called = false; $complete_called = false; $test = new Test(); $curl = $test->curl; $curl->setHeader('X-DEBUG-TEST', 'get'); $curl->setOpt(CURLOPT_CONNECTTIMEOUT_MS, 2000); $curl->beforeSend(function ($instance) use ( &$before_send_called, &$success_called, &$error_called, &$complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($before_send_called); \PHPUnit\Framework\Assert::assertFalse($success_called); \PHPUnit\Framework\Assert::assertFalse($error_called); \PHPUnit\Framework\Assert::assertFalse($complete_called); $before_send_called = true; }); $curl->success(function ($instance) use ( &$before_send_called, &$success_called, &$error_called, &$complete_called ) { $success_called = true; }); $curl->error(function ($instance) use ( &$before_send_called, &$success_called, &$error_called, &$complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($before_send_called); \PHPUnit\Framework\Assert::assertFalse($success_called); \PHPUnit\Framework\Assert::assertFalse($error_called); \PHPUnit\Framework\Assert::assertFalse($complete_called); $error_called = true; }); $curl->complete(function ($instance) use ( &$before_send_called, &$success_called, &$error_called, &$complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($before_send_called); \PHPUnit\Framework\Assert::assertFalse($success_called); \PHPUnit\Framework\Assert::assertTrue($error_called); \PHPUnit\Framework\Assert::assertFalse($complete_called); $complete_called = true; }); $curl->get(Test::ERROR_URL); $this->assertTrue($before_send_called); $this->assertFalse($success_called); $this->assertTrue($error_called); $this->assertTrue($complete_called); } public function testClose() { $curl = new Curl(); $curl->post(Test::TEST_URL); $this->assertTrue(is_object($curl->curl) || is_resource($curl->curl)); $curl->close(); $this->assertNull($curl->curl); } public function testCookieJarAfterClose() { $cookie_jar = tempnam('/tmp', 'php-curl-class.'); $curl = new Curl(); $curl->setCookieJar($cookie_jar); $curl->get(Test::TEST_URL); $curl->close(); $cookies = file_get_contents($cookie_jar); $this->assertNotEmpty($cookies); } /** * @expectedException \PHPUnit\Framework\Error\Warning */ public function testRequiredOptionCurlOptReturnTransferEmitsWarning() { $this->expectWarning(\PHPUnit\Framework\Error\Warning::class); $curl = new Curl(); $curl->setOpt(CURLOPT_RETURNTRANSFER, false); } public function testRequestMethodSuccessiveGetRequests() { $test = new Test(); $test->chainRequests('GET', 'POST'); $test->chainRequests('GET', 'PUT'); $test->chainRequests('GET', 'PATCH'); $test->chainRequests('GET', 'DELETE'); $test->chainRequests('GET', 'HEAD'); $test->chainRequests('GET', 'OPTIONS'); $test->chainRequests('GET', 'SEARCH'); $test->chainRequests('GET', 'GET'); $test = new Test(); $test->chainRequests('GET', 'POST', ['a' => '1']); $test->chainRequests('GET', 'PUT', ['b' => '22']); $test->chainRequests('GET', 'PATCH', ['c' => '333']); $test->chainRequests('GET', 'DELETE', ['d' => '4444']); $test->chainRequests('GET', 'HEAD', ['e' => '55555']); $test->chainRequests('GET', 'OPTIONS', ['f' => '666666']); $test->chainRequests('GET', 'SEARCH', ['h' => '7777777']); $test->chainRequests('GET', 'GET', ['g' => '88888888']); } public function testRequestMethodSuccessivePostRequests() { $test = new Test(); $test->chainRequests('POST', 'GET'); $test->chainRequests('POST', 'PUT'); $test->chainRequests('POST', 'PATCH'); $test->chainRequests('POST', 'DELETE'); $test->chainRequests('POST', 'HEAD'); $test->chainRequests('POST', 'OPTIONS'); $test->chainRequests('POST', 'SEARCH'); $test->chainRequests('POST', 'POST'); $test = new Test(); $test->chainRequests('POST', 'GET', ['a' => '1']); $test->chainRequests('POST', 'PUT', ['b' => '22']); $test->chainRequests('POST', 'PATCH', ['c' => '333']); $test->chainRequests('POST', 'DELETE', ['d' => '4444']); $test->chainRequests('POST', 'HEAD', ['e' => '55555']); $test->chainRequests('POST', 'OPTIONS', ['f' => '666666']); $test->chainRequests('POST', 'SEARCH', ['g' => '7777777']); $test->chainRequests('POST', 'POST', ['g' => '88888888']); } public function testRequestMethodSuccessivePutRequests() { $test = new Test(); $test->chainRequests('PUT', 'GET'); $test->chainRequests('PUT', 'POST'); $test->chainRequests('PUT', 'PATCH'); $test->chainRequests('PUT', 'DELETE'); $test->chainRequests('PUT', 'HEAD'); $test->chainRequests('PUT', 'OPTIONS'); $test->chainRequests('PUT', 'SEARCH'); $test->chainRequests('PUT', 'PUT'); $test = new Test(); $test->chainRequests('PUT', 'GET', ['a' => '1']); $test->chainRequests('PUT', 'POST', ['b' => '22']); $test->chainRequests('PUT', 'PATCH', ['c' => '333']); $test->chainRequests('PUT', 'DELETE', ['d' => '4444']); $test->chainRequests('PUT', 'HEAD', ['e' => '55555']); $test->chainRequests('PUT', 'OPTIONS', ['f' => '666666']); $test->chainRequests('PUT', 'SEARCH', ['f' => '7777777']); $test->chainRequests('PUT', 'PUT', ['g' => '88888888']); } public function testRequestMethodSuccessivePatchRequests() { $test = new Test(); $test->chainRequests('PATCH', 'GET'); $test->chainRequests('PATCH', 'POST'); $test->chainRequests('PATCH', 'PUT'); $test->chainRequests('PATCH', 'DELETE'); $test->chainRequests('PATCH', 'HEAD'); $test->chainRequests('PATCH', 'OPTIONS'); $test->chainRequests('PATCH', 'SEARCH'); $test->chainRequests('PATCH', 'PATCH'); $test = new Test(); $test->chainRequests('PATCH', 'GET', ['a' => '1']); $test->chainRequests('PATCH', 'POST', ['b' => '22']); $test->chainRequests('PATCH', 'PUT', ['c' => '333']); $test->chainRequests('PATCH', 'DELETE', ['d' => '4444']); $test->chainRequests('PATCH', 'HEAD', ['e' => '55555']); $test->chainRequests('PATCH', 'OPTIONS', ['f' => '666666']); $test->chainRequests('PATCH', 'SEARCH', ['f' => '7777777']); $test->chainRequests('PATCH', 'PATCH', ['g' => '88888888']); } public function testRequestMethodSuccessiveDeleteRequests() { $test = new Test(); $test->chainRequests('DELETE', 'GET'); $test->chainRequests('DELETE', 'POST'); $test->chainRequests('DELETE', 'PUT'); $test->chainRequests('DELETE', 'PATCH'); $test->chainRequests('DELETE', 'HEAD'); $test->chainRequests('DELETE', 'OPTIONS'); $test->chainRequests('DELETE', 'SEARCH'); $test->chainRequests('DELETE', 'DELETE'); $test = new Test(); $test->chainRequests('DELETE', 'GET', ['a' => '1']); $test->chainRequests('DELETE', 'POST', ['b' => '22']); $test->chainRequests('DELETE', 'PUT', ['c' => '333']); $test->chainRequests('DELETE', 'PATCH', ['d' => '4444']); $test->chainRequests('DELETE', 'HEAD', ['e' => '55555']); $test->chainRequests('DELETE', 'OPTIONS', ['f' => '666666']); $test->chainRequests('DELETE', 'SEARCH', ['f' => '7777777']); $test->chainRequests('DELETE', 'DELETE', ['g' => '88888888']); } public function testRequestMethodSuccessiveHeadRequests() { $test = new Test(); $test->chainRequests('HEAD', 'GET'); $test->chainRequests('HEAD', 'POST'); $test->chainRequests('HEAD', 'PUT'); $test->chainRequests('HEAD', 'PATCH'); $test->chainRequests('HEAD', 'DELETE'); $test->chainRequests('HEAD', 'OPTIONS'); $test->chainRequests('HEAD', 'SEARCH'); $test->chainRequests('HEAD', 'HEAD'); $test = new Test(); $test->chainRequests('HEAD', 'GET', ['a' => '1']); $test->chainRequests('HEAD', 'POST', ['b' => '22']); $test->chainRequests('HEAD', 'PUT', ['c' => '333']); $test->chainRequests('HEAD', 'PATCH', ['d' => '4444']); $test->chainRequests('HEAD', 'DELETE', ['e' => '55555']); $test->chainRequests('HEAD', 'OPTIONS', ['f' => '666666']); $test->chainRequests('HEAD', 'SEARCH', ['g' => '7777777']); $test->chainRequests('HEAD', 'HEAD', ['g' => '88888888']); } public function testRequestMethodSuccessiveOptionsRequests() { $test = new Test(); $test->chainRequests('OPTIONS', 'GET'); $test->chainRequests('OPTIONS', 'POST'); $test->chainRequests('OPTIONS', 'PUT'); $test->chainRequests('OPTIONS', 'PATCH'); $test->chainRequests('OPTIONS', 'DELETE'); $test->chainRequests('OPTIONS', 'SEARCH'); $test->chainRequests('OPTIONS', 'HEAD'); $test->chainRequests('OPTIONS', 'OPTIONS'); $test = new Test(); $test->chainRequests('OPTIONS', 'GET', ['a' => '1']); $test->chainRequests('OPTIONS', 'POST', ['b' => '22']); $test->chainRequests('OPTIONS', 'PUT', ['c' => '333']); $test->chainRequests('OPTIONS', 'PATCH', ['d' => '4444']); $test->chainRequests('OPTIONS', 'DELETE', ['e' => '55555']); $test->chainRequests('OPTIONS', 'SEARCH', ['g' => '666666']); $test->chainRequests('OPTIONS', 'HEAD', ['f' => '7777777']); $test->chainRequests('OPTIONS', 'OPTIONS', ['g' => '88888888']); } public function testRequestMethodSuccessiveSearchRequests() { $test = new Test(); $test->chainRequests('SEARCH', 'GET'); $test->chainRequests('SEARCH', 'POST'); $test->chainRequests('SEARCH', 'PUT'); $test->chainRequests('SEARCH', 'PATCH'); $test->chainRequests('SEARCH', 'DELETE'); $test->chainRequests('SEARCH', 'HEAD'); $test->chainRequests('SEARCH', 'OPTIONS'); $test->chainRequests('SEARCH', 'SEARCH'); $test = new Test(); $test->chainRequests('SEARCH', 'GET', ['a' => '1']); $test->chainRequests('SEARCH', 'POST', ['b' => '22']); $test->chainRequests('SEARCH', 'PUT', ['c' => '333']); $test->chainRequests('SEARCH', 'PATCH', ['d' => '4444']); $test->chainRequests('SEARCH', 'DELETE', ['e' => '55555']); $test->chainRequests('SEARCH', 'HEAD', ['f' => '666666']); $test->chainRequests('SEARCH', 'OPTIONS', ['g' => '7777777']); $test->chainRequests('SEARCH', 'SEARCH', ['g' => '88888888']); } public function testMemoryLeak() { // Skip memory leak test failing for PHP 7. // "Failed asserting that 8192 is less than 1000." if (getenv('CI_PHP_VERSION') === '7.0') { $this->markTestSkipped(); } ob_start(); echo '['; for ($i = 0; $i < 10; $i++) { if ($i >= 1) { echo ','; } echo '{"before":' . memory_get_usage() . ','; $curl = new Curl(); // Unset the $curl object instead of calling $curl->close(). Calling // unset($curl) should trigger the clean up: __destruct() which // calls $curl->close(). unset($curl); echo '"after":' . memory_get_usage() . '}'; sleep(1); } echo ']'; $html = ob_get_contents(); ob_end_clean(); $results = json_decode($html, true); // Ensure memory does not leak excessively after instantiating a new // Curl instance and cleaning up. Memory diffs in the 2000-6000+ range // indicate a memory leak. $max_memory_diff = 1000; foreach ($results as $i => $result) { $memory_diff = $result['after'] - $result['before']; // Skip the first test to allow memory usage to settle. if ($i >= 1) { $this->assertLessThan($max_memory_diff, $memory_diff); } } } public function testAlternativeStandardErrorOutput() { $buffer = fopen('php://memory', 'w+'); $curl = new Curl(); $curl->verbose(true, $buffer); $curl->post(Test::TEST_URL); rewind($buffer); $stderr = stream_get_contents($buffer); fclose($buffer); $this->assertNotEmpty($stderr); } public function testTotalTime() { $test = new Test(); $test->server('request_method', 'GET'); $this->assertTrue(is_float($test->curl->totalTime)); } public function testOptionSet() { // Skip this test on 8.0 and later: // "ValueError: curl_setopt(): cURL option must not contain any null bytes" if (version_compare(PHP_VERSION, '8.0.0', '>=')) { $this->markTestSkipped(); } $option = CURLOPT_ENCODING; $value = 'gzip'; $null = chr(0); // Ensure the option is stored when curl_setopt() succeeds. $curl = new Curl(); $success = $curl->setOpt($option, $value); $this->assertTrue($success); $this->assertEquals($value, $curl->getOpt($option)); // Ensure the option is not stored when curl_setopt() fails. Make curl_setopt() return false and suppress // errors. Triggers warning: "curl_setopt(): Curl option contains invalid characters (\0)". $curl = new Curl(); $success = @$curl->setOpt($option, $null); $this->assertFalse($success); $this->assertNull($curl->getOpt($option)); // Ensure options following a Curl::setOpt() failure are not set when using Curl::setOpts(). $options = [ $option => $null, CURLOPT_COOKIE => 'a=b', ]; $curl = new Curl(); $success = @$curl->setOpts($options); $this->assertFalse($success); $this->assertNull($curl->getOpt(CURLOPT_COOKIE)); // Ensure Curl::setOpts() returns true when all options are successfully set. $options = [ CURLOPT_COOKIE => 'a=b', CURLOPT_FOLLOWLOCATION => true, CURLOPT_VERBOSE => true, ]; $curl = new Curl(); $success = $curl->setOpts($options); $this->assertTrue($success); $this->assertEquals('a=b', $curl->getOpt(CURLOPT_COOKIE)); $this->assertTrue($curl->getOpt(CURLOPT_FOLLOWLOCATION)); $this->assertTrue($curl->getOpt(CURLOPT_VERBOSE)); } public function testBuildUrlArgs() { $tests = [ [ 'args' => [ 'url' => 'https://www.example.com/', 'mixed_data' => null, ], 'expected' => 'https://www.example.com/', ], [ 'args' => [ 'url' => 'https://www.example.com/', 'mixed_data' => '', ], 'expected' => 'https://www.example.com/', ], [ 'args' => [ 'url' => 'https://www.example.com/', 'mixed_data' => [], ], 'expected' => 'https://www.example.com/', ], [ 'args' => [ 'url' => 'https://www.example.com/', 'mixed_data' => [ 'a' => '1', 'b' => '2', 'c' => '3', ], ], 'expected' => 'https://www.example.com/?a=1&b=2&c=3', ], [ 'args' => [ 'url' => 'https://www.example.com/?a=base', 'mixed_data' => [ 'b' => '2', 'c' => '3', ], ], 'expected' => 'https://www.example.com/?a=base&b=2&c=3', ], [ 'args' => [ 'url' => 'https://www.example.com/?a=base', 'mixed_data' => 'b=2&c=3' ], 'expected' => 'https://www.example.com/?a=base&b=2&c=3', ], [ 'args' => [ 'url' => 'https://www.example.com/', 'mixed_data' => 'user_ids=user_1,user_2', ], 'expected' => 'https://www.example.com/?user_ids=user_1,user_2', ], ]; foreach ($tests as $test) { $actual_url = Url::buildUrl($test['args']['url'], $test['args']['mixed_data']); $this->assertEquals($test['expected'], $actual_url); $curl_2 = new Curl(); $curl_2->setUrl($test['args']['url'], $test['args']['mixed_data']); $this->assertEquals($test['expected'], $curl_2->url); } } public function testBuildUrlArgSeparator() { $base_url = 'https://www.example.com/path'; $data = [ 'arg' => 'value', 'another' => 'one', ]; $expected_url = $base_url . '?arg=value&another=one'; foreach ([false, '&', '&'] as $arg_separator) { if ($arg_separator) { ini_set('arg_separator.output', $arg_separator); } $actual_url = Url::buildUrl($base_url, $data); $this->assertEquals($expected_url, $actual_url); } } public function testUnsetHeader() { $request_key = 'X-Request-Id'; $request_value = '1'; $data = [ 'test' => 'server', 'key' => 'HTTP_X_REQUEST_ID', ]; $curl = new Curl(); $curl->setHeader($request_key, $request_value); $curl->get(Test::TEST_URL, $data); $this->assertEquals($request_value, $curl->response); $curl = new Curl(); $curl->setHeader($request_key, $request_value); $curl->unsetHeader($request_key); $curl->get(Test::TEST_URL, $data); $this->assertEquals('', $curl->response); } public function testRemoveHeader() { $curl = new Curl(); $curl->get(Test::TEST_URL); $this->assertEquals('127.0.0.1:8000', $curl->requestHeaders['host']); $curl = new Curl(); $curl->removeHeader('HOST'); $curl->get(Test::TEST_URL); $this->assertEquals('', $curl->requestHeaders['host']); } public function testGetInfo() { $test = new Test(); $test->server('server', 'GET'); $info = $test->curl->getInfo(); $expected_keys = [ 'url', 'content_type', 'http_code', 'header_size', 'request_size', 'filetime', 'ssl_verify_result', 'redirect_count', 'total_time', 'namelookup_time', 'connect_time', 'pretransfer_time', 'size_upload', 'size_download', 'speed_download', 'speed_upload', 'download_content_length', 'upload_content_length', 'starttransfer_time', 'redirect_time', 'certinfo', 'primary_ip', 'primary_port', 'local_ip', 'local_port', 'redirect_url', 'request_header', ]; foreach ($expected_keys as $key) { $this->assertArrayHasKey($key, $info); } } public function testRetry() { $tests = [ [ 'maximum_number_of_retries' => null, 'failures' => 0, 'expect_success' => true, 'expect_attempts' => 1, 'expect_retries' => 0, ], [ 'maximum_number_of_retries' => 0, 'failures' => 0, 'expect_success' => true, 'expect_attempts' => 1, 'expect_retries' => 0, ], [ 'maximum_number_of_retries' => 0, 'failures' => 1, 'expect_success' => false, 'expect_attempts' => 1, 'expect_retries' => 0, ], [ 'maximum_number_of_retries' => 1, 'failures' => 1, 'expect_success' => true, 'expect_attempts' => 2, 'expect_retries' => 1, ], [ 'maximum_number_of_retries' => 1, 'failures' => 2, 'expect_success' => false, 'expect_attempts' => 2, 'expect_retries' => 1, ], [ 'maximum_number_of_retries' => 2, 'failures' => 2, 'expect_success' => true, 'expect_attempts' => 3, 'expect_retries' => 2, ], [ 'maximum_number_of_retries' => 3, 'failures' => 3, 'expect_success' => true, 'expect_attempts' => 4, 'expect_retries' => 3, ], ]; foreach ($tests as $test) { $maximum_number_of_retries = $test['maximum_number_of_retries']; $failures = $test['failures']; $expect_success = $test['expect_success']; $expect_attempts = $test['expect_attempts']; $expect_retries = $test['expect_retries']; $test = new Test(); $test->curl->setOpt(CURLOPT_COOKIEJAR, '/dev/null'); if ($maximum_number_of_retries !== null) { $test->curl->setRetry($maximum_number_of_retries); } $test->server('retry', 'GET', ['failures' => $failures]); $this->assertEquals($expect_success, !$test->curl->error); $this->assertEquals($expect_attempts, $test->curl->attempts); $this->assertEquals($expect_retries, $test->curl->retries); } } public function testRetryCallable() { $tests = [ [ 'maximum_number_of_retries' => null, 'failures' => 0, 'expect_success' => true, 'expect_attempts' => 1, 'expect_retries' => 0, ], [ 'maximum_number_of_retries' => 0, 'failures' => 0, 'expect_success' => true, 'expect_attempts' => 1, 'expect_retries' => 0, ], [ 'maximum_number_of_retries' => 0, 'failures' => 1, 'expect_success' => false, 'expect_attempts' => 1, 'expect_retries' => 0, ], [ 'maximum_number_of_retries' => 1, 'failures' => 1, 'expect_success' => true, 'expect_attempts' => 2, 'expect_retries' => 1, ], [ 'maximum_number_of_retries' => 1, 'failures' => 2, 'expect_success' => false, 'expect_attempts' => 2, 'expect_retries' => 1, ], [ 'maximum_number_of_retries' => 2, 'failures' => 2, 'expect_success' => true, 'expect_attempts' => 3, 'expect_retries' => 2, ], [ 'maximum_number_of_retries' => 3, 'failures' => 3, 'expect_success' => true, 'expect_attempts' => 4, 'expect_retries' => 3, ], ]; foreach ($tests as $test) { $maximum_number_of_retries = $test['maximum_number_of_retries']; $failures = $test['failures']; $expect_success = $test['expect_success']; $expect_attempts = $test['expect_attempts']; $expect_retries = $test['expect_retries']; $test = new Test(); $test->curl->setOpt(CURLOPT_COOKIEJAR, '/dev/null'); if ($maximum_number_of_retries !== null) { $test->curl->setRetry(function ($instance) use ($maximum_number_of_retries) { $return = $instance->retries < $maximum_number_of_retries; return $return; }); } $test->server('retry', 'GET', ['failures' => $failures]); $this->assertEquals($expect_success, !$test->curl->error); $this->assertEquals($expect_attempts, $test->curl->attempts); $this->assertEquals($expect_retries, $test->curl->retries); } } public function testRelativeUrl() { $curl = new Curl(Test::TEST_URL . 'path/'); $this->assertEquals('http://127.0.0.1:8000/path/', (string)$curl->url); $curl->get('test', [ 'a' => '1', 'b' => '2', ]); $this->assertEquals('http://127.0.0.1:8000/path/test?a=1&b=2', (string)$curl->url); $curl->get('/root', [ 'c' => '3', 'd' => '4', ]); $this->assertEquals('http://127.0.0.1:8000/root?c=3&d=4', (string)$curl->url); $tests = [ [ 'args' => [ 'http://www.example.com/', '/foo', ], 'expected' => 'http://www.example.com/foo', ], [ 'args' => [ 'http://www.example.com/', '/foo/', ], 'expected' => 'http://www.example.com/foo/', ], [ 'args' => [ 'http://www.example.com/', '/dir/page.html', ], 'expected' => 'http://www.example.com/dir/page.html', ], [ 'args' => [ 'http://www.example.com/dir1/page2.html', '/dir/page.html', ], 'expected' => 'http://www.example.com/dir/page.html', ], [ 'args' => [ 'http://www.example.com/dir1/page2.html', 'dir/page.html', ], 'expected' => 'http://www.example.com/dir1/dir/page.html', ], [ 'args' => [ 'http://www.example.com/dir1/dir3/page.html', '../dir/page.html', ], 'expected' => 'http://www.example.com/dir1/dir/page.html', ], ]; foreach ($tests as $test) { $curl = new Curl($test['args']['0']); $curl->setUrl($test['args']['1']); $this->assertEquals( $test['expected'], $curl->getOpt(CURLOPT_URL), "Joint URLs: '{$test['args']['0']}', '{$test['args']['1']}'" ); $curl = new Curl($test['args']['0']); $curl->setUrl($test['args']['1'], ['a' => '1', 'b' => '2']); $this->assertEquals( $test['expected'] . '?a=1&b=2', $curl->getOpt(CURLOPT_URL), "Joint URL '{$test['args']['0']}' with parameters a=1, b=2" ); $curl = new Curl(); $curl->setUrl($test['args']['0']); $curl->setUrl($test['args']['1']); $this->assertEquals( $test['expected'], $curl->getOpt(CURLOPT_URL), "Joint URLs: '{$test['args']['0']}', '{$test['args']['1']}'" ); $curl = new Curl(); $curl->setUrl($test['args']['0'], ['a' => '1', 'b' => '2']); $curl->setUrl($test['args']['1']); $this->assertEquals( $test['expected'], $curl->getOpt(CURLOPT_URL), "Joint URL '{$test['args']['0']}' with parameters a=1, b=2 and URL '{$test['args']['1']}'" ); $curl = new Curl(); $curl->setUrl($test['args']['0']); $curl->setUrl($test['args']['1'], ['a' => '1', 'b' => '2']); $this->assertEquals( $test['expected'] . '?a=1&b=2', $curl->getOpt(CURLOPT_URL), "Joint URL '{$test['args']['0']}' and URL '{$test['args']['1']}' with parameters a=1, b=2" ); $curl = new Curl(); $curl->setUrl($test['args']['0'], ['a' => '1', 'b' => '2']); $curl->setUrl($test['args']['1'], ['c' => '3', 'd' => '4']); $this->assertEquals( $test['expected'] . '?c=3&d=4', $curl->getOpt(CURLOPT_URL), "Joint URL '{$test['args']['0']}' with parameters a=1, b=2 " . "and URL '{$test['args']['1']}' with parameters c=3, d=4" ); } } public function testReset() { $test = new Test(); $original_user_agent = $test->server('server', 'GET', ['key' => 'HTTP_USER_AGENT']); $this->assertNotEquals('New agent', $original_user_agent); $test->curl->setUserAgent('New agent'); $user_agent = $test->server('server', 'GET', ['key' => 'HTTP_USER_AGENT']); $this->assertEquals('New agent', $user_agent); $test->curl->reset(); $user_agent = $test->server('server', 'GET', ['key' => 'HTTP_USER_AGENT']); $this->assertEquals($original_user_agent, $user_agent); } public function testMock() { $curl = $this->getMockBuilder('Curl\Curl') ->getMock(); $curl->expects($this->once()) ->method('getRawResponse') ->will($this->returnValue('[]')); $this->assertEquals('[]', $curl->getRawResponse()); } public function testProxySettings() { $curl = new Curl(); $curl->setProxy('proxy.example.com', '1080', 'username', 'password'); $this->assertEquals('proxy.example.com', $curl->getOpt(CURLOPT_PROXY)); $this->assertEquals('1080', $curl->getOpt(CURLOPT_PROXYPORT)); $this->assertEquals('username:password', $curl->getOpt(CURLOPT_PROXYUSERPWD)); $curl->unsetProxy(); $this->assertNull($curl->getOpt(CURLOPT_PROXY)); } public function testSetProxyAuth() { $auth = CURLAUTH_BASIC; $curl = new Curl(); $this->assertNull($curl->getOpt(CURLOPT_PROXYAUTH)); $curl->setProxyAuth($auth); $this->assertEquals($auth, $curl->getOpt(CURLOPT_PROXYAUTH)); } public function testSetProxyType() { $type = CURLPROXY_SOCKS5; $curl = new Curl(); $this->assertNull($curl->getOpt(CURLOPT_PROXYTYPE)); $curl->setProxyType($type); $this->assertEquals($type, $curl->getOpt(CURLOPT_PROXYTYPE)); } public function testSetProxyTunnel() { $tunnel = true; $curl = new Curl(); $this->assertNull($curl->getOpt(CURLOPT_HTTPPROXYTUNNEL)); $curl->setProxyTunnel($tunnel); $this->assertEquals($tunnel, $curl->getOpt(CURLOPT_HTTPPROXYTUNNEL)); } public function testJsonSerializable() { if (!interface_exists('JsonSerializable')) { $this->markTestSkipped(); } $expected_response = '{"name":"Alice","email":"alice@example.com"}'; $user = new \Helper\User('Alice', 'alice@example.com'); $this->assertEquals($expected_response, json_encode($user)); $test = new Test(); $test->curl->setHeader('Content-Type', 'application/json'); $this->assertEquals($expected_response, $test->server('post_json', 'POST', $user)); } public function testSetFile() { $file = STDOUT; $curl = new Curl(); $curl->setFile($file); $this->assertEquals($file, $curl->getOpt(CURLOPT_FILE)); } public function testSetRange() { $range = '1000-'; $curl = new Curl(); $curl->setRange($range); $this->assertEquals($range, $curl->getOpt(CURLOPT_RANGE)); } public function testDisableTimeout() { $curl = new Curl(); $this->assertEquals(Curl::DEFAULT_TIMEOUT, $curl->getOpt(CURLOPT_TIMEOUT)); $curl->disableTimeout(); $this->assertNull($curl->getOpt(CURLOPT_TIMEOUT)); } public function testSetHeadersAssociativeArray() { $curl = new Curl(); $curl->setHeaders([ ' Key1 ' => ' Value1 ', ' Key2 ' => ' Value2', ' Key3 ' => 'Value3 ', ' Key4 ' => 'Value4', ' Key5' => ' Value5 ', ' Key6' => ' Value6', ' Key7' => 'Value7 ', ' Key8' => 'Value8', 'Key9 ' => ' Value9 ', 'Key10 ' => ' Value10', 'Key11 ' => 'Value11 ', 'Key12 ' => 'Value12', 'Key13' => ' Value13 ', 'Key14' => ' Value14', 'Key15' => 'Value15 ', 'Key16' => 'Value16', ]); $this->assertEquals([ 'Key1: Value1', 'Key2: Value2', 'Key3: Value3', 'Key4: Value4', 'Key5: Value5', 'Key6: Value6', 'Key7: Value7', 'Key8: Value8', 'Key9: Value9', 'Key10: Value10', 'Key11: Value11', 'Key12: Value12', 'Key13: Value13', 'Key14: Value14', 'Key15: Value15', 'Key16: Value16', ], $curl->getOpt(CURLOPT_HTTPHEADER)); $headers = \Helper\get_curl_property_value($curl, 'headers'); $this->assertEquals('Value1', $headers['Key1']); $this->assertEquals('Value2', $headers['Key2']); $this->assertEquals('Value3', $headers['Key3']); $this->assertEquals('Value4', $headers['Key4']); $this->assertEquals('Value5', $headers['Key5']); $this->assertEquals('Value6', $headers['Key6']); $this->assertEquals('Value7', $headers['Key7']); $this->assertEquals('Value8', $headers['Key8']); $this->assertEquals('Value9', $headers['Key9']); $this->assertEquals('Value10', $headers['Key10']); $this->assertEquals('Value11', $headers['Key11']); $this->assertEquals('Value12', $headers['Key12']); $this->assertEquals('Value13', $headers['Key13']); $this->assertEquals('Value14', $headers['Key14']); $this->assertEquals('Value15', $headers['Key15']); $this->assertEquals('Value16', $headers['Key16']); } public function testSetHeadersIndexedArray() { $curl = new Curl(); $curl->setHeaders([ ' Key1 : Value1 ', ' Key2 : Value2', ' Key3 :Value3 ', ' Key4 :Value4', ' Key5: Value5 ', ' Key6: Value6', ' Key7:Value7 ', ' Key8:Value8', 'Key9 : Value9 ', 'Key10 : Value10', 'Key11 :Value11 ', 'Key12 :Value12', 'Key13: Value13 ', 'Key14: Value14', 'Key15:Value15 ', 'Key16:Value16', ]); $this->assertEquals([ 'Key1: Value1', 'Key2: Value2', 'Key3: Value3', 'Key4: Value4', 'Key5: Value5', 'Key6: Value6', 'Key7: Value7', 'Key8: Value8', 'Key9: Value9', 'Key10: Value10', 'Key11: Value11', 'Key12: Value12', 'Key13: Value13', 'Key14: Value14', 'Key15: Value15', 'Key16: Value16', ], $curl->getOpt(CURLOPT_HTTPHEADER)); $headers = \Helper\get_curl_property_value($curl, 'headers'); $this->assertEquals('Value1', $headers['Key1']); $this->assertEquals('Value2', $headers['Key2']); $this->assertEquals('Value3', $headers['Key3']); $this->assertEquals('Value4', $headers['Key4']); $this->assertEquals('Value5', $headers['Key5']); $this->assertEquals('Value6', $headers['Key6']); $this->assertEquals('Value7', $headers['Key7']); $this->assertEquals('Value8', $headers['Key8']); $this->assertEquals('Value9', $headers['Key9']); $this->assertEquals('Value10', $headers['Key10']); $this->assertEquals('Value11', $headers['Key11']); $this->assertEquals('Value12', $headers['Key12']); $this->assertEquals('Value13', $headers['Key13']); $this->assertEquals('Value14', $headers['Key14']); $this->assertEquals('Value15', $headers['Key15']); $this->assertEquals('Value16', $headers['Key16']); } public function testSetAutoReferer() { $curl = new Curl(); $this->assertNull($curl->getOpt(CURLOPT_AUTOREFERER)); $curl->setAutoReferer(true); $this->assertTrue($curl->getOpt(CURLOPT_AUTOREFERER)); } public function testSetAutoReferrer() { $curl = new Curl(); $this->assertNull($curl->getOpt(CURLOPT_AUTOREFERER)); $curl->setAutoReferrer(true); $this->assertTrue($curl->getOpt(CURLOPT_AUTOREFERER)); } public function testSetFollowLocation() { $curl = new Curl(); $this->assertNull($curl->getOpt(CURLOPT_FOLLOWLOCATION)); $curl->setFollowLocation(true); $this->assertTrue($curl->getOpt(CURLOPT_FOLLOWLOCATION)); } public function testSetForbidReuse() { $curl = new Curl(); $this->assertNull($curl->getOpt(CURLOPT_FORBID_REUSE)); $curl->setForbidReuse(true); $this->assertTrue($curl->getOpt(CURLOPT_FORBID_REUSE)); } public function testSetMaximumRedirects() { $curl = new Curl(); $this->assertNull($curl->getOpt(CURLOPT_MAXREDIRS)); $curl->setMaximumRedirects(3); $this->assertEquals(3, $curl->getOpt(CURLOPT_MAXREDIRS)); } public function testDiagnoseOutputGet() { // Test diagnose() with default parameters. $test_1 = new Test(); $test_1->server('error_message', 'GET'); ob_start(); $test_1->curl->diagnose(); $test_1_output = ob_get_contents(); ob_end_clean(); // Test diagnose() with return=true. $test_2 = new Test(); $test_2->server('error_message', 'GET'); $test_2_output = $test_2->curl->diagnose(true); // Test diagnose() with return=false. $test_3 = new Test(); $test_3->server('error_message', 'GET'); ob_start(); $test_3->curl->diagnose(false); $test_3_output = ob_get_contents(); ob_end_clean(); foreach ([ '--- Begin PHP Curl Class diagnostic output ---', 'PHP Curl Class version: ' . Curl::VERSION, 'PHP version: ' . PHP_VERSION, 'CURLOPT_HTTPGET: true', 'Sent an HTTP GET request ', 'Request contained no body.', 'Received an HTTP status code of 401.', 'Received an HTTP 401 error response with message "HTTP/1.1 401 Unauthorized".', 'Received an empty response body (response="").', '--- End PHP Curl Class diagnostic output ---', ] as $expected_string) { $this->assertStringContainsString($expected_string, $test_1_output); $this->assertStringContainsString($expected_string, $test_2_output); $this->assertStringContainsString($expected_string, $test_3_output); } } public function testDiagnoseOutputPost() { $test = new Test(); $test->server('error_message', 'POST'); $test_output = $test->curl->diagnose(true); foreach ([ '--- Begin PHP Curl Class diagnostic output ---', 'PHP Curl Class version: ' . Curl::VERSION, 'PHP version: ' . PHP_VERSION, 'CURLOPT_POST: true', 'Sent an HTTP POST request ', 'Request contained no body.', 'Received an HTTP status code of 401.', 'Received an HTTP 401 error response with message "HTTP/1.1 401 Unauthorized".', 'Received an empty response body (response="").', '--- End PHP Curl Class diagnostic output ---', ] as $expected_string) { $this->assertStringContainsString($expected_string, $test_output); } } public function testDiagnoseAllowHeader() { $tests = [ [ 'http_method' => 'GET', 'allow_header_name' => 'Allow', 'allow_header_value' => 'POST, OPTIONS', 'expected' => 'Warning: A GET request was made, but only the following request types are allowed: POST, OPTIONS', ], [ 'http_method' => 'GET', 'allow_header_name' => 'allow', 'allow_header_value' => 'OPTIONS, POST', 'expected' => 'Warning: A GET request was made, but only the following request types are allowed: OPTIONS, POST', ], [ 'http_method' => 'POST', 'allow_header_name' => 'allow', 'allow_header_value' => 'GET, OPTIONS', 'expected' => 'Warning: A POST request was made, but only the following request types are allowed: GET, OPTIONS', ], [ 'http_method' => 'POST', 'allow_header_name' => 'allow', 'allow_header_value' => 'GET,OPTIONS', 'expected' => 'Warning: A POST request was made, but only the following request types are allowed: GET, OPTIONS', ], [ 'http_method' => 'POST', 'allow_header_name' => 'ALLOW', 'allow_header_value' => 'get,options', 'expected' => 'Warning: A POST request was made, but only the following request types are allowed: GET, OPTIONS', ], ]; foreach ($tests as $test_case) { $test = new Test(); $test->server('json_response', $test_case['http_method'], [ 'key' => $test_case['allow_header_name'], 'value' => $test_case['allow_header_value'], ]); $test_output = $test->curl->diagnose(true); $this->assertStringContainsString($test_case['expected'], $test_output); } } public function testDiagnoseErrorMessage() { $tests = [ [ 'body' => json_encode([ 'error' => [ 'code' => 503, 'message' => 'The service is currently unavailable.', 'errors' => [ [ 'message' => 'The service is currently unavailable.', 'domain' => 'global', 'reason' => 'backendError', ] ], 'status' => 'UNAVAILABLE', ], ], JSON_PRETTY_PRINT), 'expects' => [ 'Found 2 messages in response:', 'code: 503', 'message: The service is currently unavailable.', ], ], [ 'body' => json_encode([ 'errors' => [ [ 'id' => 'invalid_token', 'message' => 'The access token is invalid', ], ], ]), 'expects' => [ 'Found 1 message in response:', 'message: The access token is invalid', ], ], [ 'body' => json_encode([ 'apiVersion' => '2.0', 'error' => [ 'code' => 404, 'message' => 'File Not Found', 'errors' => [ [ 'domain' => 'Calendar', 'reason' => 'ResourceNotFoundException', 'message' => 'File Not Found', ], ], ], ]), 'expects' => [ 'Found 2 messages in response:', 'code: 404', 'message: File Not Found', ], ], ]; foreach ($tests as $test_case) { $test = new Test(); $test->server('json_response', 'POST', [ 'body' => $test_case['body'], ]); $test_output = $test->curl->diagnose(true); foreach ($test_case['expects'] as $expect) { $this->assertStringContainsString($expect, $test_output); } } } public function testDiagnoseRequestHeadersEmptyWarning() { $expect = 'Warning: Request headers (Curl::requestHeaders) are expected to be empty'; $test_1 = new Test(); $test_1->curl->verbose(); $test_1->server('json_response', 'GET'); $test_1_output = $test_1->curl->diagnose(true); $this->assertStringContainsString($expect, $test_1_output); $test_2 = new Test(); $test_2->curl->setOpt(CURLOPT_VERBOSE, true); $test_2->curl->setOpt(CURLINFO_HEADER_OUT, false); $test_2->server('json_response', 'GET'); $test_2_output = $test_2->curl->diagnose(true); $this->assertStringContainsString($expect, $test_2_output); $test_3 = new Test(); $test_3->curl->setOpt(CURLOPT_VERBOSE, true); $test_3->curl->setOpt(CURLINFO_HEADER_OUT, 0); $test_3->server('json_response', 'GET'); $test_3_output = $test_3->curl->diagnose(true); $this->assertStringContainsString($expect, $test_3_output); $test_4 = new Test(); $test_4->curl->setOpt(CURLOPT_VERBOSE, 1); $test_4->curl->setOpt(CURLINFO_HEADER_OUT, false); $test_4->server('json_response', 'GET'); $test_4_output = $test_4->curl->diagnose(true); $this->assertStringContainsString($expect, $test_4_output); $test_5 = new Test(); $test_5->curl->setOpt(CURLOPT_VERBOSE, 1); $test_5->curl->setOpt(CURLINFO_HEADER_OUT, 0); $test_5->server('json_response', 'GET'); $test_5_output = $test_5->curl->diagnose(true); $this->assertStringContainsString($expect, $test_5_output); } public function testStopRequest() { $response_length_bytes = 1e6; // 1e6 = 1 megabyte $stop_request_early = function ($ch, $header) { // Stop requests returning error responses early without downloading the // full error response. // // Check the header for the status line starting with "HTTP/". // Status-Line per RFC 2616: // 6.1 Status-Line: // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF if (stripos($header, 'HTTP/') === 0) { $status_line_parts = explode(' ', $header); if (isset($status_line_parts['1'])) { $http_status_code = $status_line_parts['1']; $http_error = in_array((int) floor($http_status_code / 100), [4, 5], true); if ($http_error) { // Return true to stop receiving the response. return true; } } } // Return false to continue receiving the response. return false; }; // Verify that full response is fetched for an error. $test_1 = new Test(); $test_1->server('download_file_size', 'GET', [ 'bytes' => $response_length_bytes, 'http_response_code' => '500', ]); $this->assertEquals($response_length_bytes, $test_1->curl->responseHeaders['Content-Length']); $this->assertEquals($response_length_bytes, $test_1->curl->getInfo(CURLINFO_SIZE_DOWNLOAD)); $this->assertEquals($response_length_bytes, strlen($test_1->curl->rawResponse)); $this->assertTrue($test_1->curl->error); $this->assertFalse($test_1->curl->curlError); $this->assertTrue($test_1->curl->httpError); // Verify that full response is not fetched for an error. $test_2 = new Test(); $test_2->curl->setStop($stop_request_early); $test_2->server('download_file_size', 'GET', [ 'bytes' => $response_length_bytes, 'http_response_code' => '500', ]); $this->assertEquals($response_length_bytes, $test_2->curl->responseHeaders['Content-Length']); $this->assertLessThan($response_length_bytes, $test_2->curl->getInfo(CURLINFO_SIZE_DOWNLOAD)); $this->assertLessThan($response_length_bytes, strlen($test_2->curl->rawResponse)); $this->assertTrue($test_2->curl->error); $this->assertTrue($test_2->curl->curlError); $this->assertTrue($test_2->curl->httpError); // Verify that full response is still fetched for a non-error. $test_3 = new Test(); $test_3->curl->setStop($stop_request_early); $test_3->server('download_file_size', 'GET', [ 'bytes' => $response_length_bytes, 'http_response_code' => '200', ]); $this->assertEquals($response_length_bytes, $test_3->curl->responseHeaders['Content-Length']); $this->assertEquals($response_length_bytes, $test_3->curl->getInfo(CURLINFO_SIZE_DOWNLOAD)); $this->assertEquals($response_length_bytes, strlen($test_3->curl->rawResponse)); $this->assertFalse($test_3->curl->error); $this->assertFalse($test_3->curl->curlError); $this->assertFalse($test_3->curl->httpError); } public function testPostDataArray() { $data = ['key' => 'value']; $curl = new Curl(); $curl->setHeader('X-DEBUG-TEST', 'post'); $curl->post(Test::TEST_URL, $data); $this->assertEquals('POST / HTTP/1.1', $curl->requestHeaders['Request-Line']); $this->assertEquals(Test::TEST_URL, $curl->url); $this->assertEquals(Test::TEST_URL, $curl->effectiveUrl); $this->assertEquals('key=value', $curl->response); } public function testPostDataArrayNullValues() { $data = [ 'key1' => 'value1', 'key2' => null, 'key3' => 'value3', ]; $curl = new Curl(); $curl->setHeader('X-DEBUG-TEST', 'post'); $curl->post(Test::TEST_URL, $data); $this->assertEquals('key1=value1&key2=&key3=value3', $curl->response); } public function testPostDataString() { $data = str_repeat('-', 100); $curl = new Curl(); $curl->setHeader('X-DEBUG-TEST', 'post_json'); $curl->post(Test::TEST_URL, $data); $this->assertEquals('POST / HTTP/1.1', $curl->requestHeaders['Request-Line']); $this->assertEquals(Test::TEST_URL, $curl->url); $this->assertEquals(Test::TEST_URL, $curl->effectiveUrl); $this->assertEquals($data, $curl->response); } public function testPatchDataArray() { $data = ['key' => 'value']; $curl = new Curl(); $curl->setHeader('X-DEBUG-TEST', 'patch'); $curl->patch(Test::TEST_URL, $data); $this->assertEquals('PATCH / HTTP/1.1', $curl->requestHeaders['Request-Line']); $this->assertEquals(Test::TEST_URL, $curl->url); } public function testPatchDataString() { $data = str_repeat('-', 100); $curl = new Curl(); $curl->setHeader('X-DEBUG-TEST', 'patch'); $curl->patch(Test::TEST_URL, $data); $this->assertEquals('PATCH / HTTP/1.1', $curl->requestHeaders['Request-Line']); $this->assertEquals(Test::TEST_URL, $curl->url); $this->assertEquals(Test::TEST_URL, $curl->effectiveUrl); $this->assertEquals($data, $curl->response); } public function testPutDataArray() { $data = ['key' => 'value']; $curl = new Curl(); $curl->setHeader('X-DEBUG-TEST', 'put'); $curl->put(Test::TEST_URL, $data); $this->assertEquals('PUT / HTTP/1.1', $curl->requestHeaders['Request-Line']); $this->assertEquals(Test::TEST_URL, $curl->url); $this->assertEquals(Test::TEST_URL, $curl->effectiveUrl); $this->assertEquals('key=value', $curl->response); } public function testPutDataString() { $data = str_repeat('-', 100); $curl = new Curl(); $curl->setHeader('X-DEBUG-TEST', 'put'); $curl->put(Test::TEST_URL, $data); $this->assertEquals('PUT / HTTP/1.1', $curl->requestHeaders['Request-Line']); $this->assertEquals(Test::TEST_URL, $curl->url); $this->assertEquals(Test::TEST_URL, $curl->effectiveUrl); } public function testSearchDataArray() { $data = ['key' => 'value']; $curl = new Curl(); $curl->setHeader('X-DEBUG-TEST', 'search'); $curl->search(Test::TEST_URL, $data); $this->assertEquals('SEARCH / HTTP/1.1', $curl->requestHeaders['Request-Line']); $this->assertEquals(Test::TEST_URL, $curl->url); $this->assertEquals(Test::TEST_URL, $curl->effectiveUrl); } public function testSearchDataString() { $data = str_repeat('-', 100); $curl = new Curl(); $curl->setHeader('X-DEBUG-TEST', 'search'); $curl->search(Test::TEST_URL, $data); $this->assertEquals('SEARCH / HTTP/1.1', $curl->requestHeaders['Request-Line']); $this->assertEquals(Test::TEST_URL, $curl->url); $this->assertEquals(Test::TEST_URL, $curl->effectiveUrl); } public function testBeforeSendEachRequest() { // Ensure Curl::beforeSend() is called before each request including retries. $test = new Test(); $test->curl->setOpt(CURLOPT_COOKIEJAR, '/dev/null'); $test->curl->setRetry(5); $before_send_call_count = 0; $test->curl->beforeSend(function ($instance) use (&$before_send_call_count) { $before_send_call_count += 1; }); $test->server('retry', 'GET', ['failures' => 5]); $this->assertEquals(6, $before_send_call_count); $this->assertEquals(6, $test->curl->attempts); $this->assertEquals(5, $test->curl->retries); $this->assertFalse($test->curl->error); } public function testGzipDecoding() { $test = new Test(); $test->server('json_response', 'POST', [ 'key' => 'content-encoding', 'value' => 'gzip', 'body' => gzencode('hello'), ]); $this->assertEquals('hello', $test->curl->response); } public function testGzipDecodingFailureWithoutWarning() { $test = new Test(); $test->server('json_response', 'POST', [ 'headers' => [ 'content-type: text/html; charset=utf-8', 'content-encoding: gzip', ], 'body' => 'not gzip-encoded', ]); $this->assertEquals('text/html; charset=utf-8', $test->curl->responseHeaders['content-type']); $this->assertEquals('gzip', $test->curl->responseHeaders['content-encoding']); $this->assertEquals('not gzip-encoded', $test->curl->response); } public function testGzipDecodingNonStringResponseWithoutError() { $test = new Test(); $test->curl->setDefaultDecoder(function () { $response = new \stdClass(); $response->{'abc'} = 'foo'; $response->{'123'} = 'bar'; return $response; }); $test->server('json_response', 'POST', [ 'headers' => [ 'content-type: text/html; charset=utf-8', 'content-encoding: gzip', ], ]); $this->assertEquals('text/html; charset=utf-8', $test->curl->responseHeaders['content-type']); $this->assertEquals('gzip', $test->curl->responseHeaders['content-encoding']); $this->assertEquals('foo', $test->curl->response->{'abc'}); $this->assertEquals('bar', $test->curl->response->{'123'}); } }