Skip to content
This repository has been archived by the owner on Sep 19, 2022. It is now read-only.

AJAX's empty POST dictionary conflicts with csrf middleware #1

Open
seddonym opened this issue Oct 19, 2011 · 12 comments
Open

AJAX's empty POST dictionary conflicts with csrf middleware #1

seddonym opened this issue Oct 19, 2011 · 12 comments

Comments

@seddonym
Copy link

I'm unable to get the delete buttons working with csrf middleware turned on.

Here's my analysis of the situation, I can't work out whether the bug might be seen to lie with Jquery fileupload, Django, or your implementation:

jquery.fileupload-ui.js uses the following code to post the data (l.88). The data variable passed $.ajax() does not contain any POST data but just does it with a query string at the end of the URL. (http://api.jquery.com/jQuery.ajax/ - see the 'data' argument.)

// Callback for file deletion:
            destroy: function (e, data) {
                var that = $(this).data('fileupload');
                if (data.url) {
                    $.ajax(data)
                        .success(function () {
                            that._adjustMaxNumberOfFiles(1);
                            $(this).fadeOut(function () {
                                $(this).remove();
                            });
                        });
                } else {
                    data.context.fadeOut(function () {
                        $(this).remove();
                    });
                }
            }

This would work fine but for Django's csrf middleware here (django.middleware.csrf, l. 199):

# check incoming token
            request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')
            if request_csrf_token == "":
                # Fall back to X-CSRFToken, to make things easier for AJAX
                request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '')

That looks like it's going to work, but request.POST.get() chokes with the following exception, because the POST data couldn't be parsed. (On django/http/init.py in _load_post_and_files line 269).

'NoneType' object has no attribute 'startswith'

Hope that makes sense.

@miki725
Copy link
Owner

miki725 commented Oct 19, 2011

Hi. Which version of Django are you using?

Staring with Django 1.2.5 (release notes), it was changed how Django validates CSRF.

Here is an exerpt from the release notes:

Additionally, Django will now accept the CSRF token in the custom HTTP header _X-CSRFTOKEN,
as well as in the form submission itself, for ease of use with popular JavaScript toolkits which allow
insertion of custom headers into all AJAX requests._

So if you are using Django >= 1.2.5, it can validate csrf either by validating csrfmiddlewaretoken field from POST data, or by validating the value of the X-CSRFTOKEN HTTP header. In my implementation, I include a fix (file) for jQuery which adds this custom HTTP header to each AJAX request.

Hope this helps.

@seddonym
Copy link
Author

Hi, it's Django 1.3 I'm afraid. And your jQuery fix is included and running, and the token is being included in the headers. The trouble is that POST is failing to be parsed in the first place, I think. Could this be a bug with Django core?

Traceback from the bug at http://dpaste.com/637781/.

@seddonym
Copy link
Author

Actually maybe the issue is not to do with the post being empty, but the Content Type: This is the dump of self at the point of the AttributeError (self.META['CONTENT_TYPE'] is None).

path:/upload/,
GET:<QueryDict: {u'f': [u'1f2d5695-08ff-471a-b5bd-cdd31b94d008/a5de68eb-1a06-4a41-a969-e22d0006cdcdaverage_male.jpg']}>,
POST:<could not parse>,
COOKIES:{'csrftoken': 'aac77d9246b00ea905a4afbbe4ac8971'},
META:{'AUTH_TYPE': None,
 'CONTENT_LENGTH': '0',
 'CONTENT_TYPE': None,
 'CSRF_COOKIE': 'aac77d9246b00ea905a4afbbe4ac8971',
 'GATEWAY_INTERFACE': 'CGI/1.1',
 'HTTP_ACCEPT': 'application/json, text/javascript, */*; q=0.01',
 'HTTP_ACCEPT_CHARSET': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
 'HTTP_ACCEPT_ENCODING': 'gzip, deflate',
 'HTTP_ACCEPT_LANGUAGE': 'en-gb,en;q=0.5',
 'HTTP_CACHE_CONTROL': 'no-cache',
 'HTTP_CONNECTION': 'keep-alive',
 'HTTP_CONTENT_LENGTH': '0',
 'HTTP_COOKIE': 'csrftoken=aac77d9246b00ea905a4afbbe4ac8971',
 'HTTP_HOST': 'demo.localhost',
 'HTTP_PRAGMA': 'no-cache',
 'HTTP_REFERER': 'http://demo.localhost/upload/',
 'HTTP_USER_AGENT': 'Mozilla/5.0 (X11; Linux i686; rv:7.0.1) Gecko/20100101 Firefox/7.0.1',
 'HTTP_X_CSRFTOKEN': 'aac77d9246b00ea905a4afbbe4ac8971',
 'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest',
 'PATH_INFO': u'/upload/',
 'PATH_TRANSLATED': None,
 'QUERY_STRING': 'f=1f2d5695-08ff-471a-b5bd-cdd31b94d008%2Fa5de68eb-1a06-4a41-a969-e22d0006cdcdaverage_male.jpg',
 'REMOTE_ADDR': '127.0.0.1',
 'REMOTE_HOST': None,
 'REMOTE_IDENT': None,
 'REMOTE_USER': None,
 'REQUEST_METHOD': 'POST',
 'SCRIPT_NAME': '',
 'SERVER_NAME': 'demo.localhost',
 'SERVER_PORT': 80,
 'SERVER_PROTOCOL': 'HTTP/1.1',
 'SERVER_SOFTWARE': 'mod_python'}>

@miki725
Copy link
Owner

miki725 commented Oct 20, 2011

Very interesting. I am starting to think it's a bug in Django. Here is an exert from Django Docs (here):

HttpRequest.method
A string representing the HTTP method used in the request. This is guaranteed to be uppercase. Example:

if request.method == 'GET':
    do_something()
elif request.method == 'POST':
    do_something_else()

So the request.method cannot be a None because that is not a string.

@miki725
Copy link
Owner

miki725 commented Oct 20, 2011

I asked on StackOverflow regarding request.method is None: http://stackoverflow.com/questions/7836834/how-can-request-method-none-in-django

Judging from the response, it might have to do something with the client side. What is your system config? Maybe the problem is there somewhere (e.g. browser)

@seddonym
Copy link
Author

Using Firefox 7.0.1 on Ubuntu.

I've posted details about the ajax call on Stack Overflow, if that helps.

@miki725
Copy link
Owner

miki725 commented Oct 21, 2011

Can you post the dump of the request for when you try to do file deletion and when you upload a file. Thanx.

@seddonym
Copy link
Author

request on deletion (which doesn't work): http://dpaste.com/641300/ - taken from django debug screen
request on uploading file (which does): http://dpaste.com/641302/ - just the request headers from firebug console

I reckon it might be the fact that the content-type is not set.

@miki725
Copy link
Owner

miki725 commented Oct 28, 2011

I don't see any META values in the file upload dump here. Can you please add this line in your view:

def Upload(request):
    print request
    # rest of view here

And then post the dump of the print statement in dpaste. Thank you.

@seddonym
Copy link
Author

seddonym commented Nov 8, 2011

Sorry about the delay. http://dpaste.com/hold/649324/

@miki725
Copy link
Owner

miki725 commented Nov 8, 2011

That's the dump for delete. Can you post sump for upload.

I wan to see the value of CONTENT_TYPE when you upload files because I still have no clue what might be causing your delete request to have a CONTENT_TYPE of None.

@seddonym
Copy link
Author

Ok, this is the dump of the request when I upload files: http://dpaste.com/hold/650551/ Note that the upload part does actually work, it's just the delete buttons.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants