Skip to content
Snippets Groups Projects
Commit 092b4e7b authored by Thomas Tanghus's avatar Thomas Tanghus
Browse files

Implement PUT an PATCH support

parent 2115db38
No related branches found
No related tags found
No related merge requests found
......@@ -31,10 +31,12 @@ use OCP\IRequest;
class Request implements \ArrayAccess, \Countable, IRequest {
protected $content;
protected $items = array();
protected $allowedKeys = array(
'get',
'post',
'patch',
'files',
'server',
'env',
......@@ -50,7 +52,7 @@ class Request implements \ArrayAccess, \Countable, IRequest {
* @param array 'params' the parsed json array
* @param array 'urlParams' the parameters which were matched from the URL
* @param array 'get' the $_GET array
* @param array 'post' the $_POST array
* @param array|string 'post' the $_POST array or JSON string
* @param array 'files' the $_FILES array
* @param array 'server' the $_SERVER array
* @param array 'env' the $_ENV array
......@@ -62,11 +64,19 @@ class Request implements \ArrayAccess, \Countable, IRequest {
public function __construct(array $vars=array()) {
foreach($this->allowedKeys as $name) {
$this->items[$name] = isset($vars[$name])
$this->items[$name] = isset($vars[$name])
? $vars[$name]
: array();
}
// Only 'application/x-www-form-urlencoded' requests are automatically
// transformed by PHP, 'application/json' must be decoded manually.
if (isset($this->items['post'])
&& strpos($this->getHeader('Content-Type'), 'application/json') !== false
&& is_string($this->items['post'])) {
$this->items['post'] = json_decode($this->items['post'], true);
}
$this->items['parameters'] = array_merge(
$this->items['params'],
$this->items['get'],
......@@ -141,19 +151,21 @@ class Request implements \ArrayAccess, \Countable, IRequest {
* $request->myvar; or $request->{'myvar'}; or $request->{$myvar}
* Looks in the combined GET, POST and urlParams array.
*
* if($request->method !== 'POST') {
* throw new Exception('This function can only be invoked using POST');
* }
* If you access e.g. ->post but the current HTTP request method
* is GET a \LogicException will be thrown.
*
* @param string $name The key to look for.
* @throws \LogicException
* @return mixed|null
*/
public function __get($name) {
switch($name) {
case 'put':
case 'patch':
case 'get':
case 'post':
if($this->method !== strtoupper($name)) {
throw new \BadMethodCallException(sprintf('%s cannot be accessed in a %s request.', $name, $this->method));
throw new \LogicException(sprintf('%s cannot be accessed in a %s request.', $name, $this->method));
}
case 'files':
case 'server':
......@@ -162,9 +174,13 @@ class Request implements \ArrayAccess, \Countable, IRequest {
case 'parameters':
case 'params':
case 'urlParams':
return isset($this->items[$name])
? $this->items[$name]
: null;
if(in_array($name, array('put', 'patch'))) {
return $this->getContent($name);
} else {
return isset($this->items[$name])
? $this->items[$name]
: null;
}
break;
case 'method':
return $this->items['method'];
......@@ -283,28 +299,57 @@ class Request implements \ArrayAccess, \Countable, IRequest {
/**
* Returns the request body content.
*
* @param Boolean $asResource If true, a resource will be returned
* If the HTTP request method is PUT a stream resource is returned, otherwise an
* array or a string depending on the Content-Type. For "normal" use an array
* will be returned.
*
* @return string|resource The request body content or a resource to read the body stream.
* @return array|string|resource The request body content or a resource to read the body stream.
*
* @throws \LogicException
*/
function getContent($asResource = false) {
return null;
// if (false === $this->content || (true === $asResource && null !== $this->content)) {
// throw new \LogicException('getContent() can only be called once when using the resource return type.');
// }
//
// if (true === $asResource) {
// $this->content = false;
//
// return fopen('php://input', 'rb');
// }
//
// if (null === $this->content) {
// $this->content = file_get_contents('php://input');
// }
//
// return $this->content;
protected function getContent() {
if ($this->content === false && $this->method === 'PUT') {
throw new \LogicException('"put" can only be accessed once.');
}
if (defined('PHPUNIT_RUN') && PHPUNIT_RUN
&& in_array('fakeinput', stream_get_wrappers())) {
$stream = 'fakeinput://data';
} else {
$stream = 'php://input';
}
if ($this->method === 'PUT') {
$this->content = false;
return fopen($stream, 'rb');
}
if (is_null($this->content)) {
$this->content = file_get_contents($stream);
if ($this->method === 'PATCH') {
/*
* Normal jquery ajax requests are sent as application/x-www-form-urlencoded
* and in $_GET and $_POST PHP transformes the data into an array.
* The first condition mimics this.
* The second condition allows for sending raw application/json data while
* still getting the result as an array.
*
*/
if (strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') !== false) {
parse_str($this->content, $content);
if(is_array($content)) {
$this->content = $content;
}
} elseif (strpos($this->getHeader('Content-Type'), 'application/json') !== false) {
$content = json_decode($this->content, true);
if(is_array($content)) {
$this->content = $content;
}
}
}
}
return $this->content;
}
}
......@@ -114,5 +114,5 @@ interface IRequest {
* @return string|resource The request body content or a resource to read the body stream.
* @throws \LogicException
*/
function getContent($asResource = false);
//function getContent($asResource = false);
}
......@@ -8,6 +8,7 @@
namespace OC\AppFramework\Http;
global $data;
class RequestTest extends \PHPUnit_Framework_TestCase {
......@@ -32,6 +33,8 @@ class RequestTest extends \PHPUnit_Framework_TestCase {
$this->assertEquals('Joey', $request->get['nickname']);
// Always returns null if variable not set.
$this->assertEquals(null, $request->{'flickname'});
require_once __DIR__ . '/requeststream.php';
}
// urlParams has precedence over POST which has precedence over GET
......@@ -75,7 +78,7 @@ class RequestTest extends \PHPUnit_Framework_TestCase {
}
/**
* @expectedException BadMethodCallException
* @expectedException LogicException
*/
public function testGetTheMethodRight() {
$vars = array(
......@@ -100,4 +103,100 @@ class RequestTest extends \PHPUnit_Framework_TestCase {
$this->assertEquals('Joey', $result['nickname']);
}
public function testJsonPost() {
$vars = array(
'post' => '{"name": "John Q. Public", "nickname": "Joey"}',
'method' => 'POST',
'server' => array('CONTENT_TYPE' => 'application/json; utf-8'),
);
$request = new Request($vars);
$this->assertEquals('POST', $request->method);
$result = $request->post;
$this->assertEquals('John Q. Public', $result['name']);
$this->assertEquals('Joey', $result['nickname']);
}
public function testPatch() {
global $data;
$data = http_build_query(array('name' => 'John Q. Public', 'nickname' => 'Joey'), '', '&');
if (in_array('fakeinput', stream_get_wrappers())) {
stream_wrapper_unregister('fakeinput');
}
stream_wrapper_register('fakeinput', 'RequestStream');
$vars = array(
'patch' => $data,
'method' => 'PATCH',
'server' => array('CONTENT_TYPE' => 'application/x-www-form-urlencoded'),
);
$request = new Request($vars);
$this->assertEquals('PATCH', $request->method);
$result = $request->patch;
$this->assertEquals('John Q. Public', $result['name']);
$this->assertEquals('Joey', $result['nickname']);
stream_wrapper_unregister('fakeinput');
}
public function testJsonPatch() {
global $data;
$data = '{"name": "John Q. Public", "nickname": null}';
if (in_array('fakeinput', stream_get_wrappers())) {
stream_wrapper_unregister('fakeinput');
}
stream_wrapper_register('fakeinput', 'RequestStream');
$vars = array(
'patch' => $data,
'method' => 'PATCH',
'server' => array('CONTENT_TYPE' => 'application/json; utf-8'),
);
$request = new Request($vars);
$this->assertEquals('PATCH', $request->method);
$result = $request->patch;
$this->assertEquals('John Q. Public', $result['name']);
$this->assertEquals(null, $result['nickname']);
stream_wrapper_unregister('fakeinput');
}
public function testPutSteam() {
global $data;
$data = file_get_contents(__DIR__ . '/../../../data/testimage.png');
if (in_array('fakeinput', stream_get_wrappers())) {
stream_wrapper_unregister('fakeinput');
}
stream_wrapper_register('fakeinput', 'RequestStream');
$vars = array(
'put' => $data,
'method' => 'PUT',
'server' => array('CONTENT_TYPE' => 'image/png'),
);
$request = new Request($vars);
$this->assertEquals('PUT', $request->method);
$resource = $request->put;
$contents = stream_get_contents($resource);
$this->assertEquals($data, $contents);
try {
$resource = $request->put;
} catch(\LogicException $e) {
stream_wrapper_unregister('fakeinput');
return;
}
$this->fail('Expected LogicException.');
}
}
<?php
/**
* Copy of http://dk1.php.net/manual/en/stream.streamwrapper.example-1.php
* Used to simulate php://input for Request tests
*/
class RequestStream {
protected $position;
protected $varname;
function stream_open($path, $mode, $options, &$opened_path) {
$url = parse_url($path);
$this->varname = $url["host"];
$this->position = 0;
return true;
}
function stream_read($count) {
$ret = substr($GLOBALS[$this->varname], $this->position, $count);
$this->position += strlen($ret);
return $ret;
}
function stream_write($data) {
$left = substr($GLOBALS[$this->varname], 0, $this->position);
$right = substr($GLOBALS[$this->varname], $this->position + strlen($data));
$GLOBALS[$this->varname] = $left . $data . $right;
$this->position += strlen($data);
return strlen($data);
}
function stream_tell() {
return $this->position;
}
function stream_eof() {
return $this->position >= strlen($GLOBALS[$this->varname]);
}
function stream_seek($offset, $whence) {
switch ($whence) {
case SEEK_SET:
if ($offset < strlen($GLOBALS[$this->varname]) && $offset >= 0) {
$this->position = $offset;
return true;
} else {
return false;
}
break;
case SEEK_CUR:
if ($offset >= 0) {
$this->position += $offset;
return true;
} else {
return false;
}
break;
case SEEK_END:
if (strlen($GLOBALS[$this->varname]) + $offset >= 0) {
$this->position = strlen($GLOBALS[$this->varname]) + $offset;
return true;
} else {
return false;
}
break;
default:
return false;
}
}
public function stream_stat() {
$size = strlen($GLOBALS[$this->varname]);
$time = time();
$data = array(
'dev' => 0,
'ino' => 0,
'mode' => 0777,
'nlink' => 1,
'uid' => 0,
'gid' => 0,
'rdev' => '',
'size' => $size,
'atime' => $time,
'mtime' => $time,
'ctime' => $time,
'blksize' => -1,
'blocks' => -1,
);
return array_values($data) + $data;
//return false;
}
function stream_metadata($path, $option, $var) {
if($option == STREAM_META_TOUCH) {
$url = parse_url($path);
$varname = $url["host"];
if(!isset($GLOBALS[$varname])) {
$GLOBALS[$varname] = '';
}
return true;
}
return false;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment