-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for sending a json request with empty body #58
Conversation
Otherwise a malformed request is sent (body does not match content-length header)
Hey! This makes sense, and is clearly a serious bug, but I think the fix is a bit trickier than just this. First, I think changing the default from Second, if the default is |
@anlutro Yes, I should have given more info on this. Allow me to go into more detail, and then I'd ask you to please help find a non-breaking solution. The first and main issue is in the commit message - application/json requests with a post-like method MUST have a body. The server will expect it, and will not respond if a body is not found. This is usually no problem because json_encode ALWAYS returns a non-empty string for any valid input, for example '[]' for empty array. One solution was to simply make Request::hasData() always return true for json-encoded post-like requests (in fact, this worked before 1.4.4 and was broken in e65f127). But I thought a bit more, and I think it's reasonable to assume the same rule would apply for any theoretical encoding where the $data was empty but encodeData() was non-empty, so I changed the hasData() method and am quite happy with that choice. As for $data = null or $data = array(), you can see why I did that if you change it back and run phpunit. The problem arises on line 400 of Request.php, because http_build_query ONLY allows objects and arrays, and it is now receiving null when $data is null. Another possible fix is to instead change line 400 to something like
(which would still leave the question, what if somebody tries $curl->post('https://some.url', 'hello') because the scalar value 'hello' would produce the same error - what should this library do then?) Or alternatively, change hasData() again, to
So that encodeData() is never called for non-post-like requests. However, this would not stop users from simply calling $curl->post('https://some.url', null), and triggering the same bug as $data = null... And I can go around and around with this, but I'd rather have your input :) As for sending empty array or object in json, do not worry at all, it is a typical use case: json_encode(array()) produces '[]', json_encode(new \stdClass()) produces '{}'. |
@anlutro Sorry for such a long message, here's a TL;DR:
|
Hey, don't worry about being TLDR, I've just been occupied with other things so haven't had the chance to properly process this yet. Let me get back to you within a day or two, okay? |
Okay I think I get this. One thing you mention confuses me though - |
A few headers are set implicitly by the curl library, and I dug a bit further into this and found out that the most problematic combination is setting CURLOPT_POST=1 without setting CURLOPT_POSTFIELDS, here's what happens: CURLOPT_POST=1 does a few things at once - sets the request method to POST if no other if specified, sets content-type to 'application/x-www-form-urlencoded' if no other is specified, then sets content-length to -1 (some default value I guess) if no other is specified. When you set CURLOPT_POSTFIELDS also, the post body is generated and content-length is calculated and set to the correct value. A nasty side effect of NOT setting CURLOPT_POSTFIELDS is that content-length stays at -1, which is fully invalid, probably gets cast to unsigned integer and interpreted as MAX_INT, and the server won't even return a response. Another side-effect which I noticed but not sure how important it is, is that I guess content-length=-1 causes an "Expect: 100-continue" header to be added to the already malformed request. As you see, CURLOPT_POST should never be used without CURLOPT_POSTFIELDS. In fact, in this library I think it should not be used at all, since setting CURLOPT_POSTFIELDS in combination with always setting CURLOPT_CUSTOMREQUEST does all the necessary work for any request method, without any of the risk. I've updated the PR, reverting to $data=null, but also adding some more tests to cover more ground, and used what I've learned about the curl lib to improve cURL::prepareRequest(). |
Yikes, what a mess. |
@@ -397,7 +397,7 @@ public function encodeData() | |||
case static::ENCODING_JSON: | |||
return json_encode($this->data); | |||
case static::ENCODING_QUERY: | |||
return http_build_query($this->data); | |||
return (!is_null($this->data) ? http_build_query($this->data) : ''); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about moving this null check to the start of this method and return an empty string for every encoding if $data === null
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For raw, it would make sense. However for json, I expect null to encode as the string "null".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point.
public function queryRequestEmptyArrayBody() | ||
{ | ||
$r = $this->makeCurl()->post(static::URL.'/echo.php', array()); | ||
$this->assertEquals('', $r->body); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe check the content-length
header in all these new tests, to check for the bug you originally wanted to fix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just the fact that the request is executed successfully without any errors or exceptions means that content-length was correct, any more checks would be redundant.
In fact, you can see that a few of these tests fail if you revert the changes in the source code and try to run them, to assert that they do cover this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, okay, good to know. I don't have php installed locally anymore so I don't have the option to check myself :o
Thanks a bunch for your work! |
You're welcome, and I appreciate the input! I'm happier with how it turned out now than the first commit. Might I ask whether you're planning to bump the library version some time soon, or should I specify dev-master in my projects? |
Tagged and released 1.4.5 with your fix right after the merge :D |
Otherwise a malformed request is sent (body does not match content-length header)