sabre-io / dav

sabre/dav is a CalDAV, CardDAV and WebDAV framework for PHP
http://sabre.io
BSD 3-Clause "New" or "Revised" License
1.52k stars 345 forks source link

Unauthenticated REPORT requests with no body produce an HTTP 500 response #932

Open jorgelzpz opened 7 years ago

jorgelzpz commented 7 years ago

Many HTTP clients, such as cURL, always sends the first request with Content-Length: 0 when digest authentication is used, even when a body is specified, likely to save some bandwidth until digest parameters are negotiated.

I don't know if the standard requires the whole body sent on the first request (haven't been able to find anything on this topic), but sabre/dav 3.2.0 answers with a 500 error on REPORT requests with no body. A 401 is expected, so most clients just stop after receiving a 5xx response.

Older releases (<3.2.0) did not show this behavior.

Other HTTP verbs don't have this issue, take a look at this PROPFIND request:

% curl --digest --user admin -X PROPFIND \
 -H "Content-Type: application/xml" --data @propfind.xml \
 http://localhost:8082/calendarserver.php/ -v
Enter host password for user 'admin':
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8082 (#0)
* Server auth using Digest with user 'admin'
> PROPFIND /calendarserver.php/ HTTP/1.1
> Host: localhost:8082
> User-Agent: curl/7.43.0
> Accept: */*
> Content-Type: application/xml
> Content-Length: 0
>
< HTTP/1.1 401 Unauthorized
< Date: Wed, 07 Dec 2016 18:21:34 GMT
< Server: Apache/2.4.7 (Ubuntu)
< X-Powered-By: PHP/5.6.24-1+deb.sury.org~trusty+1
< X-Sabre-Version: 3.2.0
< WWW-Authenticate: Digest realm="SabreDAV",qop="auth",nonce="5848532ee79e4",opaque="df58bdff8cf60599c939187d0b5c54de"
< Content-Length: 403
< Content-Type: application/xml; charset=utf-8
<
* Ignoring the response-body
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://localhost:8082/calendarserver.php/'
* Found bundle for host localhost: 0x7f8251c0f210
* Re-using existing connection! (#0) with host localhost
* Connected to localhost (127.0.0.1) port 8082 (#0)
* Server auth using Digest with user 'admin'
> PROPFIND /calendarserver.php/ HTTP/1.1
> Host: localhost:8082
> Authorization: Digest username="admin", realm="SabreDAV", nonce="5848532ee79e4", uri="/calendarserver.php/", cnonce="MDk3ODI0YjUwNTZkMGNiZDMwYTY4YzU3NDJiNDk3NGM=", nc=00000001, qop=auth, response="f40755e5078d0fd578a3d813ab205261", opaque="df58bdff8cf60599c939187d0b5c54de"
> User-Agent: curl/7.43.0
> Accept: */*
> Content-Type: application/xml
> Content-Length: 347
>
* upload completely sent off: 347 out of 347 bytes
< HTTP/1.1 207 Multi-Status
< Date: Wed, 07 Dec 2016 18:21:34 GMT
< Server: Apache/2.4.7 (Ubuntu)
< X-Powered-By: PHP/5.6.24-1+deb.sury.org~trusty+1
< X-Sabre-Version: 3.2.0
< Vary: Brief,Prefer
< DAV: 1, 3, extended-mkcol, access-control, calendarserver-principal-property-search, calendar-access, calendar-proxy, calendarserver-subscribed, calendar-auto-schedule, calendar-availability, resource-sharing, calendarserver-sharing
< Content-Length: 1824
< Content-Type: application/xml; charset=utf-8
<
<?xml version="1.0"?>
[...]

However, a REPORT request gets a 500 response (note the Content-Length: 0):

% curl --digest --user admin -v -X REPORT \
  -H "Content-Type: application/xml" --data @report.xml \
  http://localhost:8082/calendarserver.php/calendars/admin/micalendario/ -v
Enter host password for user 'admin':
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8082 (#0)
* Server auth using Digest with user 'admin'
> REPORT /calendarserver.php/calendars/admin/micalendario/ HTTP/1.1
> Host: localhost:8082
> User-Agent: curl/7.43.0
> Accept: */*
> Content-Type: application/xml
> Content-Length: 0
>
< HTTP/1.1 500 Internal Server Error
< Date: Wed, 07 Dec 2016 18:25:29 GMT
< Server: Apache/2.4.7 (Ubuntu)
< X-Powered-By: PHP/5.6.24-1+deb.sury.org~trusty+1
< X-Sabre-Version: 3.2.0
< Content-Length: 275
< Connection: close
< Content-Type: application/xml; charset=utf-8
<
<?xml version="1.0" encoding="utf-8"?>
<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns">
  <s:sabredav-version>3.2.0</s:sabredav-version>
  <s:exception>ErrorException</s:exception>
  <s:message>XMLReader::XML(): Empty string supplied as input</s:message>
</d:error>
* Closing connection 0
staabm commented 7 years ago

We recently changed handling regarding content-length in https://github.com/fruux/sabre-http/commit/9cf0c48264176b34da485833ec9e4aca36d49d54

Could you check this broke your app?

evert commented 7 years ago

The reason this is happening is actually because of a change that happened a bit earlier. This change is documented here: http://sabre.io/dav/upgrade/3.1-to-3.2/

What the change here really entails is that we now try to do authentication as late as possible in the process. If you do a REPORT and you hit all calendar objects that have been made public, you will effectively never get asked for auth at all.

Only after we retrieve the iCalendar objects and find out that one of the objects is not publicly accessible and you didn't provide credentials, we force a login.

This basically allows us to build a number of features that weren't possible otherwise, such as public calendars and freebusy.

Since you're sending an invalid request, we reject it as early as possible, long before auth kicks in.

I understand curl's behavior, but I wonder if there's a way to work around it? Are you only doing this type of request for the first request to a server, or is it done for every single HTTP session?

Ideally your client should behave a bit like a browser, that is:

But if there is a different way you know of I can on my side see that I'm receiving a Curl 'auth preflight request', I would also be more than happy to implement a workaround for that. It would be helpful in that case to see a full HTTP request. I hope there's maybe a special header in there or something...

It might also be worth asking the curl authors what they think?

jorgelzpz commented 7 years ago

@evert found this message from cURL author (Daniel Stenberg). He wrote it some years ago, so looks like cURL has been acting this way for a long time.

Found this problem while using Guzzle with the cURL handler. The workaround would be to use cURL's any authentication method instead of digest, but Guzzle doesn't seem to support it right now out of the box, so every REPORT request gets a 500 response.

I clearly see the benefits of sabre/dav processing authentication as late as possible, but unfortunately this breaks some cURL-based HTTP clients, at least if they know they have to use Digest authentication beforehand.

evert commented 7 years ago

I don't really have a good answer than to just work around it at the moment though... the feature can be disabled in sabre/dav too, but I don't intend to remove it.

I think maybe the logical next step might be to open up a new bug report in curl and see what the author says about it.

nunohorta commented 6 years ago

What would be the solution if I need to use sabre 3.2 and REPORT requests?

evert commented 6 years ago

This is a client bug, so ideally you fix the client.