diff --git a/library/Requests/Exception/File.php b/library/Requests/Exception/File.php new file mode 100644 index 000000000..e4a62e6b8 --- /dev/null +++ b/library/Requests/Exception/File.php @@ -0,0 +1,5 @@ +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/library/Requests/Transport/cURL.php b/library/Requests/Transport/cURL.php index 4429edb64..945f0efbf 100644 --- a/library/Requests/Transport/cURL.php +++ b/library/Requests/Transport/cURL.php @@ -316,15 +316,35 @@ protected 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(); + } + } + + 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); + } } - elseif (!is_string($data)) { - $data = http_build_query($data, null, '&'); + else { + foreach($files as $key => $file) { + $data[$key] = "@{$file->path}"; + } } } diff --git a/library/Requests/Transport/fsockopen.php b/library/Requests/Transport/fsockopen.php index 21cb56d5e..02b6e9239 100644 --- a/library/Requests/Transport/fsockopen.php +++ b/library/Requests/Transport/fsockopen.php @@ -153,14 +153,24 @@ 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)) { + 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; } - if (!empty($data)) { + if (!empty($data) && empty($files)) { if (!isset($case_insensitive_headers['Content-Length'])) { $headers['Content-Length'] = strlen($request_body); } @@ -169,6 +179,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/tests/File.php b/tests/File.php new file mode 100644 index 000000000..d2ea89ccf --- /dev/null +++ b/tests/File.php @@ -0,0 +1,39 @@ +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 Requests_File($tmpfile); + $this->assertEquals($file->name, basename($tmpfile)); + + $this->assertEquals($file->get_contents(), 'hello'); + } + + public function testMime() { + file_put_contents($tmpfile = tempnam(sys_get_temp_dir(), 'requests'), 'hello'); + $file = new Requests_File($tmpfile); + $this->assertEquals($file->type, 'text/plain'); + + file_put_contents($tmpfile = tempnam(sys_get_temp_dir(), 'requests'), "\xff\xd8\xff"); + $file = new Requests_File($tmpfile); + $this->assertEquals($file->type, 'image/jpeg'); + + file_put_contents($tmpfile = tempnam(sys_get_temp_dir(), 'requests'), "\x78\x01"); + $file = new Requests_File($tmpfile); + $this->assertEquals($file->type, 'application/octet-stream'); + } +} diff --git a/tests/Transport/Base.php b/tests/Transport/Base.php index 566e09fad..f3438f1f4 100644 --- a/tests/Transport/Base.php +++ b/tests/Transport/Base.php @@ -845,4 +845,13 @@ public function testBodyDataFormat() { $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/phpunit.xml.dist b/tests/phpunit.xml.dist index 64e0b8260..1e3602332 100644 --- a/tests/phpunit.xml.dist +++ b/tests/phpunit.xml.dist @@ -13,6 +13,7 @@ ChunkedEncoding.php Cookies.php + File.php IDNAEncoder.php IRI.php Requests.php