diff --git a/docs/usage.md b/docs/usage.md index 453ec9b59..def3e5685 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -96,6 +96,14 @@ $data = array('some' => 'data'); $response = WpOrg\Requests\Requests::post($url, $headers, json_encode($data)); ``` +To send a file as part of a post body: + +```php +$url = 'https://api.github.com/some/endpoint'; +$body = \WpOrg\Requests\Requests::add_files_to_body( array('mydata' => 'something'), $file_path, 'file1' ); +$response = WpOrg\Requests\Requests::post( $url, $headers, $body ); +``` + Note that if you don't manually specify a Content-Type header, Requests has undefined behaviour for the header. It may be set to various values depending on the internal execution path, so it's recommended to set this explicitly if diff --git a/examples/post.php b/examples/post.php index 47abd674d..51ce40267 100644 --- a/examples/post.php +++ b/examples/post.php @@ -11,3 +11,12 @@ // Check what we received var_dump($request); +// create temp file for example +file_put_contents( $tmpfile = tempnam(sys_get_temp_dir(), 'requests' ), 'hello'); + +// Now let's make a request! +$body = \WpOrg\Requests\Requests::add_files_to_body( array('mydata' => 'something'), $tmpfile, 'file1' ); +$request = WpOrg\Requests\Requests::post('http://httpbin.org/post', array(), $body ); + +// Check what we received +var_dump($request); diff --git a/src/Exception/RequestsExceptionFile.php b/src/Exception/RequestsExceptionFile.php new file mode 100644 index 000000000..7da429745 --- /dev/null +++ b/src/Exception/RequestsExceptionFile.php @@ -0,0 +1,6 @@ + $file_path ){ + $body[ $key ] = new RequestsFile( $file_path ); + } + } elseif( is_scalar( $file_key ) && ! empty( $key_files ) ) { + $body[ $file_key ] = new RequestsFile( $key_files ); + } + + return $body; + } + /**#@+ * @see \WpOrg\Requests\Requests::request() * @param string $url @@ -562,15 +590,16 @@ public static function set_certificate_path($path) { self::$certificate_path = $path; } + /** - * Set the default values + * @param $url + * @param $headers + * @param $data + * @param $type + * @param $options * - * @param string $url URL to request - * @param array $headers Extra headers to send with the request - * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests - * @param string $type HTTP request type - * @param array $options Options for the request - * @return array $options + * @return mixed + * @throws \WpOrg\Requests\Exception */ protected static function set_defaults(&$url, &$headers, &$data, &$type, &$options) { if (!preg_match('/^http(s)?:\/\//i', $url, $matches)) { @@ -622,6 +651,8 @@ protected static function set_defaults(&$url, &$headers, &$data, &$type, &$optio $options['data_format'] = 'body'; } } + + return $options; } /** diff --git a/src/Transport/Curl.php b/src/Transport/Curl.php index df98894a3..af39af6b8 100644 --- a/src/Transport/Curl.php +++ b/src/Transport/Curl.php @@ -359,15 +359,35 @@ private function setup_handle($url, $headers, $data, $options) { $headers = Requests::flatten($headers); + $files = array(); + if (!empty($data)) { $data_format = $options['data_format']; + if (is_array($data)) { + foreach($data as $key => $value) { + if ($value instanceof Requests_File) { + $files[$key] = $value; + } + } + } + if ($data_format === 'query') { $url = self::format_get($url, $data); - $data = ''; + $data = array(); } - elseif (!is_string($data)) { - $data = http_build_query($data, '', '&'); + } + + if (!empty($files)) { + if (function_exists('curl_file_create')) { + foreach($files as $key => $file) { + $data[$key] = curl_file_create($file->path, $file->type, $file->name); + } + } + else { + foreach($files as $key => $file) { + $data[$key] = "@{$file->path}"; + } } } diff --git a/src/Transport/Fsockopen.php b/src/Transport/Fsockopen.php index ac1527610..3adc55116 100644 --- a/src/Transport/Fsockopen.php +++ b/src/Transport/Fsockopen.php @@ -176,8 +176,18 @@ public function request($url, $headers = array(), $data = array(), $options = ar $out = sprintf("%s %s HTTP/%.1F\r\n", $options['type'], $path, $options['protocol_version']); if ($options['type'] !== Requests::TRACE) { + $files = array(); + if (is_array($data)) { - $request_body = http_build_query($data, '', '&'); + foreach($data as $key => $value) { + if ($value instanceof Requests_File) { + $files[$key] = $value; + } + } + } + + if (is_array($data) && empty($files)) { + $request_body = http_build_query($data, null, '&'); } else { $request_body = $data; @@ -194,6 +204,33 @@ public function request($url, $headers = array(), $data = array(), $options = ar $headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; } } + + if (!empty($files)) { + $boundary = sha1(time()); + $headers['Content-Type'] = "multipart/form-data; boundary=$boundary"; + + $request_body = ''; + + if (!empty($data)) { + foreach ($data as $key => $value) { + $request_body .= "--$boundary\r\n"; + + if ($value instanceof Requests_File) { + $request_body .= "Content-Disposition: form-data; name=\"$key\"; filename=\"$value->name\"\r\n"; + $request_body .= "Content-Type: $value->type"; + $request_body .= "\r\n\r\n" . $value->get_contents() . "\r\n"; + } + else { + $request_body .= "Content-Disposition: form-data; name=\"$key\""; + $request_body .= "\r\n\r\n" . $value . "\r\n"; + } + } + } + + $request_body .= "--$boundary--\r\n\r\n"; + + $headers['Content-Length'] = strlen($request_body); + } } if (!isset($case_insensitive_headers['Host'])) { diff --git a/src/Utility/RequestsFile.php b/src/Utility/RequestsFile.php new file mode 100644 index 000000000..66b43d304 --- /dev/null +++ b/src/Utility/RequestsFile.php @@ -0,0 +1,74 @@ +path = $path; + $this->type = $type ? $type : mime_content_type($path); + $this->name = $name ? $name : basename($path); + } + + /** + * Retrieve the contents into a string. + * + * Caution: large files will fill up the RAM. + * + * @return string The contents. + */ + public function get_contents() { + return file_get_contents($this->path); + } +} diff --git a/tests/Transport/Base.php b/tests/Transport/Base.php new file mode 100644 index 000000000..f3438f1f4 --- /dev/null +++ b/tests/Transport/Base.php @@ -0,0 +1,857 @@ +transport, 'test'); + $supported = call_user_func($callback); + + if (!$supported) { + $this->markTestSkipped($this->transport . ' is not available'); + return; + } + + $ssl_supported = call_user_func($callback, array('ssl' => true)); + if (!$ssl_supported) { + $this->skip_https = true; + } + } + protected $skip_https = false; + + /** + * PHPUnit 6+ compatibility shim. + * + * @param mixed $exception + * @param string $message + * @param int|string $code + */ + public function setExpectedException( $exception, $message = '', $code = null ) { + if ( method_exists( 'PHPUnit_Framework_TestCase', 'setExpectedException' ) ) { + parent::setExpectedException( $exception, $message, $code ); + } else { + $this->expectException( $exception ); + if ( '' !== $message ) { + $this->expectExceptionMessage( $message ); + } + if ( null !== $code ) { + $this->expectExceptionCode( $code ); + } + } + } + + + protected function getOptions($other = array()) { + $options = array( + 'transport' => $this->transport + ); + $options = array_merge($options, $other); + return $options; + } + + public function testResponseByteLimit() { + $limit = 104; + $options = array( + 'max_bytes' => $limit, + ); + $response = Requests::get(httpbin('/bytes/325'), array(), $this->getOptions($options)); + $this->assertEquals($limit, strlen($response->body)); + } + + public function testResponseByteLimitWithFile() { + $limit = 300; + $options = array( + 'max_bytes' => $limit, + 'filename' => tempnam(sys_get_temp_dir(), 'RLT') // RequestsLibraryTest + ); + $response = Requests::get(httpbin('/bytes/482'), array(), $this->getOptions($options)); + $this->assertEmpty($response->body); + $this->assertEquals($limit, filesize($options['filename'])); + unlink($options['filename']); + } + + public function testSimpleGET() { + $request = Requests::get(httpbin('/get'), array(), $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals(httpbin('/get'), $result['url']); + $this->assertEmpty($result['args']); + } + + public function testGETWithArgs() { + $request = Requests::get(httpbin('/get?test=true&test2=test'), array(), $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals(httpbin('/get?test=true&test2=test'), $result['url']); + $this->assertEquals(array('test' => 'true', 'test2' => 'test'), $result['args']); + } + + public function testGETWithData() { + $data = array( + 'test' => 'true', + 'test2' => 'test', + ); + $request = Requests::request(httpbin('/get'), array(), $data, Requests::GET, $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals(httpbin('/get?test=true&test2=test'), $result['url']); + $this->assertEquals(array('test' => 'true', 'test2' => 'test'), $result['args']); + } + + public function testGETWithNestedData() { + $data = array( + 'test' => 'true', + 'test2' => array( + 'test3' => 'test', + 'test4' => 'test-too', + ), + ); + $request = Requests::request(httpbin('/get'), array(), $data, Requests::GET, $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals(httpbin('/get?test=true&test2%5Btest3%5D=test&test2%5Btest4%5D=test-too'), $result['url']); + $this->assertEquals(array('test' => 'true', 'test2[test3]' => 'test', 'test2[test4]' => 'test-too'), $result['args']); + } + + public function testGETWithDataAndQuery() { + $data = array( + 'test2' => 'test', + ); + $request = Requests::request(httpbin('/get?test=true'), array(), $data, Requests::GET, $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals(httpbin('/get?test=true&test2=test'), $result['url']); + $this->assertEquals(array('test' => 'true', 'test2' => 'test'), $result['args']); + } + + public function testGETWithHeaders() { + $headers = array( + 'Requested-At' => time(), + ); + $request = Requests::get(httpbin('/get'), $headers, $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals($headers['Requested-At'], $result['headers']['Requested-At']); + } + + public function testChunked() { + $request = Requests::get(httpbin('/stream/1'), array(), $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals(httpbin('/stream/1'), $result['url']); + $this->assertEmpty($result['args']); + } + + public function testHEAD() { + $request = Requests::head(httpbin('/get'), array(), $this->getOptions()); + $this->assertEquals(200, $request->status_code); + $this->assertEquals('', $request->body); + } + + public function testTRACE() { + $request = Requests::trace(httpbin('/trace'), array(), $this->getOptions()); + $this->assertEquals(200, $request->status_code); + } + + public function testRawPOST() { + $data = 'test'; + $request = Requests::post(httpbin('/post'), array(), $data, $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals('test', $result['data']); + } + + public function testFormPost() { + $data = 'test=true&test2=test'; + $request = Requests::post(httpbin('/post'), array(), $data, $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals(array('test' => 'true', 'test2' => 'test'), $result['form']); + } + + public function testPOSTWithArray() { + $data = array( + 'test' => 'true', + 'test2' => 'test', + ); + $request = Requests::post(httpbin('/post'), array(), $data, $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals(array('test' => 'true', 'test2' => 'test'), $result['form']); + } + + public function testPOSTWithNestedData() { + $data = array( + 'test' => 'true', + 'test2' => array( + 'test3' => 'test', + 'test4' => 'test-too', + ), + ); + $request = Requests::post(httpbin('/post'), array(), $data, $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals(array('test' => 'true', 'test2[test3]' => 'test', 'test2[test4]' => 'test-too'), $result['form']); + } + + public function testRawPUT() { + $data = 'test'; + $request = Requests::put(httpbin('/put'), array(), $data, $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals('test', $result['data']); + } + + public function testFormPUT() { + $data = 'test=true&test2=test'; + $request = Requests::put(httpbin('/put'), array(), $data, $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals(array('test' => 'true', 'test2' => 'test'), $result['form']); + } + + public function testPUTWithArray() { + $data = array( + 'test' => 'true', + 'test2' => 'test', + ); + $request = Requests::put(httpbin('/put'), array(), $data, $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals(array('test' => 'true', 'test2' => 'test'), $result['form']); + } + + public function testRawPATCH() { + $data = 'test'; + $request = Requests::patch(httpbin('/patch'), array(), $data, $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals('test', $result['data']); + } + + public function testFormPATCH() { + $data = 'test=true&test2=test'; + $request = Requests::patch(httpbin('/patch'), array(), $data, $this->getOptions()); + $this->assertEquals(200, $request->status_code, $request->body); + + $result = json_decode($request->body, true); + $this->assertEquals(array('test' => 'true', 'test2' => 'test'), $result['form']); + } + + public function testPATCHWithArray() { + $data = array( + 'test' => 'true', + 'test2' => 'test', + ); + $request = Requests::patch(httpbin('/patch'), array(), $data, $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals(array('test' => 'true', 'test2' => 'test'), $result['form']); + } + + public function testOPTIONS() { + $request = Requests::options(httpbin('/options'), array(), array(), $this->getOptions()); + $this->assertEquals(200, $request->status_code); + } + + public function testDELETE() { + $request = Requests::delete(httpbin('/delete'), array(), $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals(httpbin('/delete'), $result['url']); + $this->assertEmpty($result['args']); + } + + public function testDELETEWithData() { + $data = array( + 'test' => 'true', + 'test2' => 'test', + ); + $request = Requests::request(httpbin('/delete'), array(), $data, Requests::DELETE, $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals(httpbin('/delete?test=true&test2=test'), $result['url']); + $this->assertEquals(array('test' => 'true', 'test2' => 'test'), $result['args']); + } + + public function testLOCK() { + $request = Requests::request(httpbin('/lock'), array(), array(), 'LOCK', $this->getOptions()); + $this->assertEquals(200, $request->status_code); + } + + public function testLOCKWithData() { + $data = array( + 'test' => 'true', + 'test2' => 'test', + ); + $request = Requests::request(httpbin('/lock'), array(), $data, 'LOCK', $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals(array('test' => 'true', 'test2' => 'test'), $result['form']); + } + + public function testRedirects() { + $request = Requests::get(httpbin('/redirect/6'), array(), $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $this->assertEquals(6, $request->redirects); + } + + public function testRelativeRedirects() { + $request = Requests::get(httpbin('/relative-redirect/6'), array(), $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $this->assertEquals(6, $request->redirects); + } + + /** + * @expectedException Requests_Exception + * @todo This should also check that the type is "toomanyredirects" + */ + public function testTooManyRedirects() { + $options = array( + 'redirects' => 10, // default, but force just in case + ); + $request = Requests::get(httpbin('/redirect/11'), array(), $this->getOptions($options)); + } + + public static function statusCodeSuccessProvider() { + return array( + array(200, true), + array(201, true), + array(202, true), + array(203, true), + array(204, true), + array(205, true), + array(206, true), + array(300, false), + array(301, false), + array(302, false), + array(303, false), + array(304, false), + array(305, false), + array(306, false), + array(307, false), + array(400, false), + array(401, false), + array(402, false), + array(403, false), + array(404, false), + array(405, false), + array(406, false), + array(407, false), + array(408, false), + array(409, false), + array(410, false), + array(411, false), + array(412, false), + array(413, false), + array(414, false), + array(415, false), + array(416, false), + array(417, false), + array(418, false), // RFC 2324 + array(428, false), // RFC 6585 + array(429, false), // RFC 6585 + array(431, false), // RFC 6585 + array(500, false), + array(501, false), + array(502, false), + array(503, false), + array(504, false), + array(505, false), + array(511, false), // RFC 6585 + ); + } + + /** + * @dataProvider statusCodeSuccessProvider + */ + public function testStatusCode($code, $success) { + $transport = new MockTransport(); + $transport->code = $code; + + $url = sprintf(httpbin('/status/%d'), $code); + + $options = array( + 'follow_redirects' => false, + 'transport' => $transport, + ); + $request = Requests::get($url, array(), $options); + $this->assertEquals($code, $request->status_code); + $this->assertEquals($success, $request->success); + } + + /** + * @dataProvider statusCodeSuccessProvider + */ + public function testStatusCodeThrow($code, $success) { + $transport = new MockTransport(); + $transport->code = $code; + + $url = sprintf(httpbin('/status/%d'), $code); + $options = array( + 'follow_redirects' => false, + 'transport' => $transport, + ); + + if (!$success) { + if ($code >= 400) { + $this->setExpectedException('Requests_Exception_HTTP_' . $code, '', $code); + } + elseif ($code >= 300 && $code < 400) { + $this->setExpectedException('Requests_Exception'); + } + } + $request = Requests::get($url, array(), $options); + $request->throw_for_status(false); + } + + /** + * @dataProvider statusCodeSuccessProvider + */ + public function testStatusCodeThrowAllowRedirects($code, $success) { + $transport = new MockTransport(); + $transport->code = $code; + + $url = sprintf(httpbin('/status/%d'), $code); + $options = array( + 'follow_redirects' => false, + 'transport' => $transport, + ); + + if (!$success) { + if ($code >= 400 || $code === 304 || $code === 305 || $code === 306) { + $this->setExpectedException('Requests_Exception_HTTP_' . $code, '', $code); + } + } + $request = Requests::get($url, array(), $options); + $request->throw_for_status(true); + } + + public function testStatusCodeUnknown(){ + $transport = new MockTransport(); + $transport->code = 599; + + $options = array( + 'transport' => $transport, + ); + + $request = Requests::get(httpbin('/status/599'), array(), $options); + $this->assertEquals(599, $request->status_code); + $this->assertEquals(false, $request->success); + } + + /** + * @expectedException Requests_Exception_HTTP_Unknown + */ + public function testStatusCodeThrowUnknown(){ + $transport = new MockTransport(); + $transport->code = 599; + + $options = array( + 'transport' => $transport, + ); + + $request = Requests::get(httpbin('/status/599'), array(), $options); + $request->throw_for_status(true); + } + + public function testGzipped() { + $request = Requests::get(httpbin('/gzip'), array(), $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body); + $this->assertEquals(true, $result->gzipped); + } + + public function testStreamToFile() { + $options = array( + 'filename' => tempnam(sys_get_temp_dir(), 'RLT') // RequestsLibraryTest + ); + $request = Requests::get(httpbin('/get'), array(), $this->getOptions($options)); + $this->assertEquals(200, $request->status_code); + $this->assertEmpty($request->body); + + $contents = file_get_contents($options['filename']); + $result = json_decode($contents, true); + $this->assertEquals(httpbin('/get'), $result['url']); + $this->assertEmpty($result['args']); + + unlink($options['filename']); + } + + public function testNonblocking() { + $options = array( + 'blocking' => false + ); + $request = Requests::get(httpbin('/get'), array(), $this->getOptions($options)); + $empty = new Requests_Response(); + $this->assertEquals($empty, $request); + } + + /** + * @expectedException Requests_Exception + */ + public function testBadIP() { + $request = Requests::get('http://256.256.256.0/', array(), $this->getOptions()); + } + + public function testHTTPS() { + if ($this->skip_https) { + $this->markTestSkipped('SSL support is not available.'); + return; + } + + $request = Requests::get(httpbin('/get', true), array(), $this->getOptions()); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + // Disable, since httpbin always returns http + // $this->assertEquals(httpbin('/get', true), $result['url']); + $this->assertEmpty($result['args']); + } + + /** + * @expectedException Requests_Exception + */ + public function testExpiredHTTPS() { + if ($this->skip_https) { + $this->markTestSkipped('SSL support is not available.'); + return; + } + + $request = Requests::get('https://testssl-expire.disig.sk/index.en.html', array(), $this->getOptions()); + } + + /** + * @expectedException Requests_Exception + */ + public function testRevokedHTTPS() { + if ($this->skip_https) { + $this->markTestSkipped('SSL support is not available.'); + return; + } + + $request = Requests::get('https://testssl-revoked.disig.sk/index.en.html', array(), $this->getOptions()); + } + + /** + * Test that SSL fails with a bad certificate + * + * @expectedException Requests_Exception + */ + public function testBadDomain() { + if ($this->skip_https) { + $this->markTestSkipped('SSL support is not available.'); + return; + } + + $request = Requests::head('https://wrong.host.badssl.com/', array(), $this->getOptions()); + } + + /** + * Test that the transport supports Server Name Indication with HTTPS + * + * badssl.com is used for SSL testing, and the common name is set to + * `*.badssl.com` as such. Without alternate name support, this will fail + * as `badssl.com` is only in the alternate name + */ + public function testAlternateNameSupport() { + if ($this->skip_https) { + $this->markTestSkipped('SSL support is not available.'); + return; + } + + $request = Requests::head('https://badssl.com/', array(), $this->getOptions()); + $this->assertEquals(200, $request->status_code); + } + + /** + * Test that the transport supports Server Name Indication with HTTPS + * + * feelingrestful.com (owned by hmn.md and used with permission) points to + * CloudFlare, and will fail if SNI isn't sent. + */ + public function testSNISupport() { + if ($this->skip_https) { + $this->markTestSkipped('SSL support is not available.'); + return; + } + + $request = Requests::head('https://feelingrestful.com/', array(), $this->getOptions()); + $this->assertEquals(200, $request->status_code); + } + + /** + * @expectedException Requests_Exception + */ + public function testTimeout() { + $options = array( + 'timeout' => 1, + ); + $request = Requests::get(httpbin('/delay/10'), array(), $this->getOptions($options)); + var_dump($request); + } + + public function testMultiple() { + $requests = array( + 'test1' => array( + 'url' => httpbin('/get') + ), + 'test2' => array( + 'url' => httpbin('/get') + ), + ); + $responses = Requests::request_multiple($requests, $this->getOptions()); + + // test1 + $this->assertNotEmpty($responses['test1']); + $this->assertInstanceOf('Requests_Response', $responses['test1']); + $this->assertEquals(200, $responses['test1']->status_code); + + $result = json_decode($responses['test1']->body, true); + $this->assertEquals(httpbin('/get'), $result['url']); + $this->assertEmpty($result['args']); + + // test2 + $this->assertNotEmpty($responses['test2']); + $this->assertInstanceOf('Requests_Response', $responses['test2']); + $this->assertEquals(200, $responses['test2']->status_code); + + $result = json_decode($responses['test2']->body, true); + $this->assertEquals(httpbin('/get'), $result['url']); + $this->assertEmpty($result['args']); + } + + public function testMultipleWithDifferingMethods() { + $requests = array( + 'get' => array( + 'url' => httpbin('/get'), + ), + 'post' => array( + 'url' => httpbin('/post'), + 'type' => Requests::POST, + 'data' => 'test', + ), + ); + $responses = Requests::request_multiple($requests, $this->getOptions()); + + // get + $this->assertEquals(200, $responses['get']->status_code); + + // post + $this->assertEquals(200, $responses['post']->status_code); + $result = json_decode($responses['post']->body, true); + $this->assertEquals('test', $result['data']); + } + + /** + * @depends testTimeout + */ + public function testMultipleWithFailure() { + $requests = array( + 'success' => array( + 'url' => httpbin('/get'), + ), + 'timeout' => array( + 'url' => httpbin('/delay/10'), + 'options' => array( + 'timeout' => 1, + ), + ), + ); + $responses = Requests::request_multiple($requests, $this->getOptions()); + $this->assertEquals(200, $responses['success']->status_code); + $this->assertInstanceOf('Requests_Exception', $responses['timeout']); + } + + public function testMultipleUsingCallback() { + $requests = array( + 'get' => array( + 'url' => httpbin('/get'), + ), + 'post' => array( + 'url' => httpbin('/post'), + 'type' => Requests::POST, + 'data' => 'test', + ), + ); + $this->completed = array(); + $options = array( + 'complete' => array($this, 'completeCallback'), + ); + $responses = Requests::request_multiple($requests, $this->getOptions($options)); + + $this->assertEquals($this->completed, $responses); + $this->completed = array(); + } + + public function testMultipleUsingCallbackAndFailure() { + $requests = array( + 'success' => array( + 'url' => httpbin('/get'), + ), + 'timeout' => array( + 'url' => httpbin('/delay/10'), + 'options' => array( + 'timeout' => 1, + ), + ), + ); + $this->completed = array(); + $options = array( + 'complete' => array($this, 'completeCallback'), + ); + $responses = Requests::request_multiple($requests, $this->getOptions($options)); + + $this->assertEquals($this->completed, $responses); + $this->completed = array(); + } + + public function completeCallback($response, $key) { + $this->completed[$key] = $response; + } + + public function testMultipleToFile() { + $requests = array( + 'get' => array( + 'url' => httpbin('/get'), + 'options' => array( + 'filename' => tempnam(sys_get_temp_dir(), 'RLT') // RequestsLibraryTest + ), + ), + 'post' => array( + 'url' => httpbin('/post'), + 'type' => Requests::POST, + 'data' => 'test', + 'options' => array( + 'filename' => tempnam(sys_get_temp_dir(), 'RLT') // RequestsLibraryTest + ), + ), + ); + $responses = Requests::request_multiple($requests, $this->getOptions()); + + // GET request + $contents = file_get_contents($requests['get']['options']['filename']); + $result = json_decode($contents, true); + $this->assertEquals(httpbin('/get'), $result['url']); + $this->assertEmpty($result['args']); + unlink($requests['get']['options']['filename']); + + // POST request + $contents = file_get_contents($requests['post']['options']['filename']); + $result = json_decode($contents, true); + $this->assertEquals(httpbin('/post'), $result['url']); + $this->assertEquals('test', $result['data']); + unlink($requests['post']['options']['filename']); + } + + public function testAlternatePort() { + $request = Requests::get('http://portquiz.net:8080/', array(), $this->getOptions()); + $this->assertEquals(200, $request->status_code); + $num = preg_match('#You have reached this page on port (\d+)#i', $request->body, $matches); + $this->assertEquals(1, $num, 'Response should contain the port number'); + $this->assertEquals(8080, $matches[1]); + } + + public function testProgressCallback() { + $mock = $this->getMockBuilder('stdClass')->setMethods(array('progress'))->getMock(); + $mock->expects($this->atLeastOnce())->method('progress'); + $hooks = new Requests_Hooks(); + $hooks->register('request.progress', array($mock, 'progress')); + $options = array( + 'hooks' => $hooks, + ); + $options = $this->getOptions($options); + + $response = Requests::get(httpbin('/get'), array(), $options); + } + + public function testAfterRequestCallback() { + $mock = $this->getMockBuilder('stdClass') + ->setMethods(array('after_request')) + ->getMock(); + + $mock->expects($this->atLeastOnce()) + ->method('after_request') + ->with( + $this->isType('string'), + $this->logicalAnd($this->isType('array'), $this->logicalNot($this->isEmpty())) + ); + $hooks = new Requests_Hooks(); + $hooks->register('curl.after_request', array($mock, 'after_request')); + $hooks->register('fsockopen.after_request', array($mock, 'after_request')); + $options = array( + 'hooks' => $hooks, + ); + $options = $this->getOptions($options); + + $response = Requests::get(httpbin('/get'), array(), $options); + } + + public function testReusableTransport() { + $options = $this->getOptions(array('transport' => new $this->transport())); + + $request1 = Requests::get(httpbin('/get'), array(), $options); + $request2 = Requests::get(httpbin('/get'), array(), $options); + + $this->assertEquals(200, $request1->status_code); + $this->assertEquals(200, $request2->status_code); + + $result1 = json_decode($request1->body, true); + $result2 = json_decode($request2->body, true); + + $this->assertEquals(httpbin('/get'), $result1['url']); + $this->assertEquals(httpbin('/get'), $result2['url']); + + $this->assertEmpty($result1['args']); + $this->assertEmpty($result2['args']); + } + + public function testQueryDataFormat() { + $data = array('test' => 'true', 'test2' => 'test'); + $request = Requests::post(httpbin('/post'), array(), $data, $this->getOptions(array('data_format' => 'query'))); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals(httpbin('/post').'?test=true&test2=test', $result['url']); + $this->assertEquals('', $result['data']); + } + + public function testBodyDataFormat() { + $data = array('test' => 'true', 'test2' => 'test'); + $request = Requests::post(httpbin('/post'), array(), $data, $this->getOptions(array('data_format' => 'body'))); + $this->assertEquals(200, $request->status_code); + + $result = json_decode($request->body, true); + $this->assertEquals(httpbin('/post'), $result['url']); + $this->assertEquals(array('test' => 'true', 'test2' => 'test'), $result['form']); + } + + public function testFileUploads() { + file_put_contents($tmpfile = tempnam(sys_get_temp_dir(), 'requests'), 'some secret bytes, yo'); + $request = Requests::post('http://httpbin.org/post', array(), array('foo' => 'bar', 'file1' => new Requests_File($tmpfile)), $this->getOptions()); + + $result = json_decode($request->body, true); + $this->assertEquals($result['files']['file1'], 'some secret bytes, yo'); + $this->assertEquals($result['form']['foo'], 'bar'); + } +} diff --git a/tests/utils/FileTest.php b/tests/utils/FileTest.php new file mode 100644 index 000000000..d92829159 --- /dev/null +++ b/tests/utils/FileTest.php @@ -0,0 +1,118 @@ +expectException( RequestsExceptionFile::class ); + new RequestsFile( sys_get_temp_dir() . 'null.exe'); + } + + /** + * @throws RequestsExceptionFile + * + * @covers \WpOrg\Requests\Utility\RequestsFile + */ + public function testBasic() { + file_put_contents( $tmpfile = tempnam( sys_get_temp_dir(), 'requests' ), ''); + $file = new RequestsFile( $tmpfile, 'text/plain', 'readme.txt' ); + + $this->assertEquals( $file->path, $tmpfile); + $this->assertEquals( $file->type, 'text/plain' ); + $this->assertEquals( $file->name, 'readme.txt' ); + + file_put_contents( $tmpfile = tempnam(sys_get_temp_dir(), 'requests' ), 'hello'); + $file = new RequestsFile($tmpfile); + $this->assertEquals($file->name, basename( $tmpfile ) ); + + $this->assertEquals( $file->get_contents(), 'hello' ); + } + + /** + * @throws RequestsExceptionFile + * + * @covers \WpOrg\Requests\Utility\RequestsFile + */ + public function testMime() { + file_put_contents( $tmpfile = tempnam( sys_get_temp_dir(), 'requests' ), 'hello'); + $file = new RequestsFile( $tmpfile ); + $this->assertEquals( $file->type, 'text/plain' ); + + file_put_contents( $tmpfile = tempnam(sys_get_temp_dir(), 'requests' ), "\xff\xd8\xff"); + $file = new RequestsFile($tmpfile); + $this->assertEquals( $file->type, 'image/jpeg' ); + + file_put_contents( $tmpfile = tempnam(sys_get_temp_dir(), 'requests' ), "\x78\x01"); + $file = new RequestsFile($tmpfile); + $this->assertEquals( $file->type, 'application/octet-stream' ); + } + + /** + * @covers \WpOrg\Requests\Requests::add_files_to_body + */ + public function test_add_file_to_empty_body(){ + $body = ''; + file_put_contents( $tmpfile = tempnam( sys_get_temp_dir(), 'requests' ), 'hello'); + $file = new RequestsFile( $tmpfile ); + $body = Requests::add_files_to_body( $body, $tmpfile, 'filename' ); + $this->assertEquals( $body, array( 'filename' => $file ) ); + } + + /** + * @covers \WpOrg\Requests\Requests::add_files_to_body + */ + public function test_add_file_to_body_with_string(){ + $body = 'dd'; + file_put_contents( $tmpfile = tempnam( sys_get_temp_dir(), 'requests' ), 'hello'); + $file = new RequestsFile( $tmpfile ); + $body = Requests::add_files_to_body( $body, $tmpfile, 'filename' ); + $this->assertEquals( $body, array( 0 => 'dd','filename' => $file ) ); + } + + /** + * @covers \WpOrg\Requests\Requests::add_files_to_body + */ + public function test_add_file_to_body_with_array(){ + $body = array( 'dd' => 'fff' ) ; + file_put_contents( $tmpfile = tempnam( sys_get_temp_dir(), 'requests' ), 'hello' ); + $file = new RequestsFile( $tmpfile ); + $body = Requests::add_files_to_body( $body, $tmpfile, 'filename' ); + $this->assertEquals( $body, array( 'dd' => 'fff','filename' => $file ) ); + } + /** + * @covers \WpOrg\Requests\Requests::add_files_to_body + */ + public function test_add_files_to_body_with_array(){ + $body = array( 'dd' => 'fff' ) ; + file_put_contents( $tmpfile1 = tempnam( sys_get_temp_dir(), 'requests' ), 'hello' ); + $file1 = new RequestsFile( $tmpfile1 ); + file_put_contents( $tmpfile2 = tempnam( sys_get_temp_dir(), 'requests' ), 'hello2' ); + $file2 = new RequestsFile( $tmpfile2 ); + $files = array( $tmpfile1, $tmpfile2 ); + $body = Requests::add_files_to_body( $body, $files ); + $this->assertEquals( $body, array( 'dd' => 'fff',0 => $file1, 1 => $file2 ) ); + } + /** + * @covers \WpOrg\Requests\Requests::add_files_to_body + */ + public function test_add_files_to_body_with_keyed_array(){ + $body = array( 'dd' => 'fff' ) ; + file_put_contents( $tmpfile1 = tempnam( sys_get_temp_dir(), 'requests' ), 'hello' ); + $file1 = new RequestsFile( $tmpfile1 ); + file_put_contents( $tmpfile2 = tempnam( sys_get_temp_dir(), 'requests' ), 'hello2' ); + $file2 = new RequestsFile( $tmpfile2 ); + $files = array( 'tmpfile1' => $tmpfile1,'tmpfile2' => $tmpfile2 ); + $body = Requests::add_files_to_body( $body, $files ); + $this->assertEquals( $body, array( 'dd' => 'fff','tmpfile1' => $file1, 'tmpfile2' => $file2 ) ); + } +}