I was running into an issue with using the firefox addon "Poster" to test the services module as suggested here: http://drupal.org/node/790416

The RESTServer.inc file contains at line #252

  private function parseRequest($method, $controller) {
    switch ($method) {
      case 'POST':
        if ($_SERVER['CONTENT_TYPE'] == 'application/x-www-form-urlencoded') {
          return $_POST;
        }

Through debugging I found that actual value in $_SERVER['CONTENT_TYPE'] when I am testing was "application/x-www-form-urlencoded; charset=UTF-8"

Of course the above code was assuming this was not a "POST" request. I did little more research to see if this was valid value for $_SERVER['CONTENT_TYPE']. Looking in the documentation for $_SERVER pointed me to theCGI/1.1 specification.

On this page you can find

4.1.3. CONTENT_TYPE

If the request includes a message-body, the CONTENT_TYPE variable is
set to the Internet Media Type [6] of the message-body.

CONTENT_TYPE = "" | media-type
media-type = type "/" subtype *( ";" parameter )
type = token
subtype = token
parameter = attribute "=" value
attribute = token
value = token | quoted-string

The type, subtype and parameter attribute names are not
case-sensitive. Parameter values may be case sensitive. Media types
and their use in HTTP are described section 3.7 of the HTTP/1.1
specification [4].

There is no default value for this variable. If and only if it is
unset, then the script MAY attempt to determine the media type from
the data received. If the type remains unknown, then the script MAY
choose to assume a type of application/octet-stream or it may reject
the request with an error (as described in section 6.3.3).

Each media-type defines a set of optional and mandatory parameters.
This may include a charset parameter with a case-insensitive value
defining the coded character set for the message-body.

My reading of this is that the value contains the media-type(divided into type and subtype) as the current code is expecting but also can contain other parameters after a semi colon. So "application/x-www-form-urlencoded; charset=UTF-8" appears to be a valid value that contains the extra parameter charset.

I am attaching a patches which changes the code to:

  private function parseRequest($method, $controller) {
    switch ($method) {
      case 'POST':
        $content_type_args = explode(';', $_SERVER['CONTENT_TYPE']);
        $media_type = $content_type_args[0];
        if ($media_type == 'application/x-www-form-urlencoded') {
          return $_POST;
        }
        return array();

This checks against just media-type portion of the string and would ignore other paramters. It should work whether the parameters are there or not.

Also I have added a "return array();" so that logic doesn't continue to fall through to the "PUT" switch case. This was happening my case b/c it didn't have expected $_SERVER['CONTENT_TYPE'] value. The program was then assuming that this was "PUT". I think this "return array();" or a break should be added to the code whether my other changes are expected or not.

Comments

tedbow’s picture

Status: Active » Needs review
StatusFileSize
new840 bytes

attaching patch for review

voxpelli’s picture

Status: Needs review » Needs work

This should already work. If not then it is the PUT part of parseRequest() that should be patched and not the POST part.

+++ b/servers/rest_server/includes/RESTServer.inc
@@ -244,9 +244,12 @@ class RESTServer {
+        $content_type_args = explode(';', $_SERVER['CONTENT_TYPE']);

This parsing should use self::parseContentHeader() - but since the PUT-case already does that it is probably unnecessary to do it here as well. Only reason would be to avoid the performance impact of having to parse the request content twice. requestParsers() will otherwise make the request content be parsed by RESTServer::parseURLEncoded().

+++ b/servers/rest_server/includes/RESTServer.inc
@@ -244,9 +244,12 @@ class RESTServer {
+        return array();

This makes it impossible to do POST:s with any content-type but application/x-www-form-urlencoded. POST-requests should be possible to make with any type of content body.

Powered by Dreditor.

tedbow’s picture

@voxpelli thanks I am still getting to know the workings of this module.

Am I correct though in thinking that the "POST" case should not be testing the value of $_SERVER['CONTENT_TYPE'] directly?

I have new patch that does the "self::parseContentHeader" call before the switch statement. This way the "CASE" can test the $type['value'] returned.

I also removed the return statement that I added.

voxpelli’s picture

Status: Needs work » Active

That will probably result in a slight performance improvement, but doesn't fix any bug. If there's a bug then it should be fixed prior to doing this performance improvement. Can you explain further what doesn't work when you're using an unpatched version of Services? If it does work with an unpatched version then this should be changed into a feature request and retitled to reflect that it is about performance.

+++ b/servers/rest_server/includes/RESTServer.inc
@@ -242,15 +242,17 @@ class RESTServer {
+        if ($type['value'] == 'application/x-www-form-urlencoded') {

You need to check if $type have any value.

Powered by Dreditor.

tedbow’s picture

@voxpelli, ok I figured out why my current situation is not working. I think it is combination of some special code I am running and Services code having the problem I specified in number #1.

I handling a service request from an outside app that is trying to create Users. The app is going to using a POST request but is not going to be sending the info exactly how the Services Rest Server is expecting. In a hook_init function I change the $_POST variable to what the Rest Server expects(rename the parameter "account") .

So after my hook_init the $_POST variable is exactly as the Rest Server wants it. The problem is that when it gets to "parseRequest" the current code doesn't recognise it as a POST request b/c of the reason I specified in #1.

It then falls through the switch case and gets the input by reading the raw input stream. Of course my hook_init method didn't affect the this input like it did the $_POST variable.

The problem I see with the current code is that it appears when first looking at the code that if a POST's content type is "application/x-www-form-urlencoded" then it will use the $_POST variable for $data. In fact though if $_SERVER['CONTENT_TYPE'] has any valid parameters it will not use the the $_POST variable but rather the raw input stream.

The core of the problem is when I rely on what I believe to be the intention of the code, i.e., If the post is a "form-urlencoded" then it will use the $_POST this doesn't work.

One way around this would be adding the ability to alter what is parsed from the request. Something like this:

  private function getControllerArguments($controller, $path, $method, &$sources=array()) {
    // Get argument sources
    $parameters = $_GET;
    if (isset($parameters['_method'])) {
      unset($parameters['_method']);
    }

    $data = $this->parseRequest($method, $controller);
    drupal_alter('rest_parsed_request',$data);

Sorry to be so long winded.

voxpelli’s picture

I see - I agree that an alter ('rest_server_request_parsed'?) in that place would make sense and that it would be good if $_POST would be used more expectable. The reason it sometimes needs to be parsed separate is because PHP doesn't parse the form-data on PUT:s if I remember correctly.

Can you add an alter to your previous patch? Change the status to "need review" once you have and I'll check it.

tedbow’s picture

Status: Active » Needs review
StatusFileSize
new1.22 KB

@voxpelli et all,

I have attached the patch with change requested in #6.

Here is the example of how I am using the alter in my case

/**
 * Implements hook_rest_server_request_parsed_alter().
 */
function modulename_rest_server_request_parsed_alter(&$data, $context){
  $callback = $context['controller']['callback'];
  if($callback == '_user_resource_create'){
    $data['account'] = $data;
  }
}

I don't like the fact that I have to check the callback to determine where I am at. It would nice if I could check for the resource, "user" and the operation, "create" but this not available in the controller object at that point, correct? I also don't think there is anything else that should be sent in the context but maybe there is.

Reading the docs for drupal_alter here: http://api.drupal.org/api/drupal/includes--module.inc/function/drupal_al...
It says:

If it is absolutely required that no implementation alters a passed object in $context, then an object needs to be cloned

I was wondering if I should clone the $controller variable so it cannot get altered but don't know if it is worth the performance hit that the clone call would add. Any thoughts?

tedbow’s picture

Sorry attached incomplete patch file. Please regard #7's patch.

Also this patch I changed the way the $controller is being passed so the implementation would be

/**
* Implements hook_rest_server_request_parsed_alter().
*/
function modulename_rest_server_request_parsed_alter(&$data, $controller){
  $callback = $controller['callback'];
  if($callback == '_user_resource_create'){
    $data['account'] = $data;
  }
}
kylebrowning’s picture

Version: 7.x-3.x-dev » 6.x-3.x-dev
Status: Needs review » Patch (to be ported)

Committed, thanks.

voxpelli’s picture

Version: 6.x-3.x-dev » 7.x-3.x-dev
Status: Patch (to be ported) » Needs work
+++ b/servers/rest_server/includes/RESTServer.inc
@@ -242,15 +243,17 @@ class RESTServer {
+        if ($type['value'] == 'application/x-www-form-urlencoded') {

Still missing an isset() here?

Powered by Dreditor.

kylebrowning’s picture

Version: 7.x-3.x-dev » 6.x-3.x-dev
Status: Needs work » Patch (to be ported)

thx voxpelli, amended to previous commit and fixed in 7.x

tedbow’s picture

@voxpelli thanks for catching that

kylebrowning’s picture

Status: Patch (to be ported) » Fixed

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.