Code

Opened 3 years ago

Closed 16 months ago

#17474 closed Bug (wontfix)

Problem when the request doesn't have Content-Type.

Reported by: Marcelo Salhab Brogliato <msbrogli@…> Owned by: nobody
Component: HTTP handling Version: 1.4
Severity: Normal Keywords: content-type
Cc: olau@…, ivan_virabyan, purohit@… Triage Stage: Design decision needed
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by aaugustin)

I don't know exactly why, but some ajax requests don't have Content-Type header.
When this happens, when I try to get request.POST, I got the following exception: 'NoneType' object has no attribute 'startswith',
raised by method _load_post_and_files at line 270 of django/http/__init__.py.

The problem is that when the request doesn't have Content-Type, self.META.get('CONTENT_TYPE', '') returns None.
To fix the problem I just did it:

content_type = self.META.get('CONTENT_TYPE', None) or ''
if content_type.startswith('multipart'):

Attachments (0)

Change History (14)

comment:1 Changed 3 years ago by aaugustin

  • Description modified (diff)
  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset

Improved formatting (please use preview).

comment:2 Changed 3 years ago by aaugustin

  • Resolution set to needsinfo
  • Status changed from new to closed

The error you're reporting means that self.META['CONTENT_TYPE'] == None — if it were undefined, self.META.get('CONTENT_TYPE', '') would return '', not None.


request.META['CONTENT_TYPE'] is defined in WSGIRequestHandler.get_environ like this:

        if self.headers.typeheader is None:
            env['CONTENT_TYPE'] = self.headers.type
        else:
            env['CONTENT_TYPE'] = self.headers.typeheader

This code is copy-pasted from wsgiref.simple_server (line 90), and self.headers comes from BaseHTTPRequestHandler.headers.

self.headers.type is computed in Message.parse_type, which sets self.type = '/'.join(fields). This means that self.headers.type can never be None.


So, by inspecting the code, I can't figure out how the bug you're reporting happened. I'd prefer to fix its root cause rather than its symptom. For this reason, I'm not eager to blindly commit your suggestion.

Could you check with your browser's inspector which AJAX requests trigger the bug, and which headers they contain exactly ? If we can reproduce the problem, it will be easier to understand its cause.

Per our standard procedures, I'm going to close this ticket as "needing more information", since I don't have enough data to reproduce the problem. Could you add more information about the problematic requests and reopen it? Thank you!


On a related note, MultiPartParser uses this code:

        content_type = META.get('HTTP_CONTENT_TYPE', META.get('CONTENT_TYPE', ''))
        if not content_type.startswith('multipart/'):
            ...

We may have a small inconsistency here.

comment:3 follow-up: Changed 2 years ago by olau

  • Cc olau@… added
  • Resolution needsinfo deleted
  • Status changed from closed to reopened

Hi!

We had the same problem with a Django instance running inside mod_python (yes, I know mod_python is bad, it's a client's server). I think it happens with a custom iPhone client. Here's the traceback:

Traceback:
File "/var/sites/bcl/PYTHON_ENV/lib/python2.5/site-packages/django/core/handlers/base.py" in get_response
  111.                         response = callback(request, *callback_args, **callback_kwargs)
File "/var/sites/bcl/main/api.py" in inner
  17.         token = request.GET.get("authtoken", request.POST.get("authtoken"))
File "/var/sites/bcl/PYTHON_ENV/lib/python2.5/site-packages/django/core/handlers/modpython.py" in _get_post
  101.             self._load_post_and_files()
File "/var/sites/bcl/PYTHON_ENV/lib/python2.5/site-packages/django/http/__init__.py" in _load_post_and_files
  270.         if self.META.get('CONTENT_TYPE', '').startswith('multipart'):

Exception Type: AttributeError at /api/events/log/
Exception Value: 'NoneType' object has no attribute 'startswith'

If you want to test it, you could try telnetting a POST response, something like "telnet localhost 8000", then copy-paste

POST / HTTP/1.0
Content-length: 17

query=1234

comment:4 in reply to: ↑ 3 Changed 2 years ago by anonymous

Replying to olau:

Hi!

We had the same problem with a Django instance running inside mod_python

What versions of Django and Python are you using?

comment:5 Changed 2 years ago by olau

Django 1.3 and Python 2.5.

comment:6 Changed 2 years ago by lrekucki

  • Triage Stage changed from Unreviewed to Design decision needed

PEP 333 (WSGI spec):

CONTENT_TYPE
    The contents of any Content-Type fields in the HTTP request. May be empty or absent.

So this is definitively a bug in mod_python. We are supposed to officially support it until 1.4, but I don't think this is worth it.

comment:7 Changed 2 years ago by olau

Huh? Is mod_python implementing WSGI? I thought it was using its own protocol.

Why don't you think a one-line bug fix is worth it? Some people may be stuck on mod_python for some years to come.

comment:8 Changed 2 years ago by ivan_virabyan

  • Cc ivan_virabyan added

comment:9 Changed 2 years ago by jroweboy@…

Just had a similar problem myself, and I suspect it might have the same root problem. I hate to suggest a workaround for things, but for anyone else who has a similar problem, you might be able to directly specify the CONTENT-TYPE in your ajax request. I don't know why it wasn't sending it by default as 'application/x-www-form-urlencoded' as the documentation says it does (http://api.jquery.com/jQuery.ajax/) so I ended up explicitly adding it in the ajax function call. Example:

$.ajax({
     type: 'POST',
     url: 'your url',
     success: function(data){
         // function to call on success
     },
     dataType: 'text',
     contentType: 'application/x-www-form-urlencoded',
});

comment:10 Changed 21 months ago by Ashwin Purohit <purohit@…>

  • Cc purohit@… added
  • Version changed from 1.3 to 1.4

This same thing happens in 1.4:

File "/usr/local/lib/Django1.4/django/core/handlers/modpython.py", line 56, in _get_request
self._request = datastructures.MergeDict(self.POST, self.GET)

File "/usr/local/lib/Django1.4/django/core/handlers/modpython.py", line 69, in _get_post
self._load_post_and_files()

File "/usr/local/lib/Django1.4/django/http/_init_.py", line 353, in _load_post_and_files
if self.META.get('CONTENT_TYPE', '').startswith('multipart'):

AttributeError: 'NoneType' object has no attribute 'startswith'

Request repr() unavailable.

You can easily test it by writing a test that sends a POST without the CONTENT_TYPE header:

def test_missing_content_type(self):                                            
    // Will throw an exception:           
    response = self.client.post('/url/', {}, HTTP_CONTENT_TYPE=None, follow=True)                                 


comment:11 Changed 21 months ago by claudep

For master, the question is solved by the removal of mod_python. On 1.4, the correct fix would be to change ModPythonRequest._get_meta and replace self._req.headers_in.get('content-type') by self._req.headers_in.get('content-type', ''). I think that this is a safe bug fix, but won't decide until other committers approve it.

comment:12 Changed 21 months ago by ptone

I agree that this is only a 1.4 issue - however if modpython is in fact passing in a defined content-type of None, then what we actually need is: self._req.headers_in.get('content-type', '') or '' since the key exists in this case.

Last edited 21 months ago by ptone (previous) (diff)

comment:13 Changed 16 months ago by aaugustin

  • Status changed from reopened to new

comment:14 Changed 16 months ago by aaugustin

  • Resolution set to wontfix
  • Status changed from new to closed

Since 1.4 is now is security-fixes-only mode, this is moot.

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as closed
as The resolution will be set. Next status will be 'closed'
The resolution will be deleted. Next status will be 'new'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.