Last active
August 29, 2015 14:05
-
-
Save mattrobenolt/02d0a1a28e12a74c61bf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
import json | |
from django.core.serializers.json import DjangoJSONEncoder | |
from django.http import HttpResponse, HttpResponseBadRequest | |
# Reserved words list from http://javascript.about.com/library/blreserved.htm | |
JAVASCRIPT_RESERVED_WORDS = frozenset(( | |
'abstract', 'as', 'boolean', 'break', 'byte', 'case', 'catch', 'char', | |
'class', 'continue', 'const', 'debugger', 'default', 'delete', 'do', | |
'double', 'else', 'enum', 'export', 'extends', 'false', 'final', 'finally', | |
'float', 'for', 'function', 'goto', 'if', 'implements', 'import', 'in', | |
'instanceof', 'int', 'interface', 'is', 'long', 'namespace', 'native', | |
'new', 'null', 'package', 'private', 'protected', 'public', 'return', | |
'short', 'static', 'super', 'switch', 'synchronized', 'this', 'throw', | |
'throws', 'transient', 'true', 'try', 'typeof', 'use', 'var', 'void', | |
'volatile', 'while', 'with', | |
)) | |
VALID_CALLBACK_RE = re.compile(r'^[$a-z_][0-9a-z_\.\[\]]*$', re.I) | |
def is_valid_callback_name(callback_name): | |
if not callback_name: | |
return False | |
# Callbacks longer than 50 characters are suspicious. | |
# There isn't a legit reason for a callback longer. | |
# The length is arbitrary too. | |
# It's technically possible to construct malicious payloads using | |
# only ascii characters, so we just block this. | |
if len(callback_name) > 50: | |
return False | |
if callback_name in JAVASCRIPT_RESERVED_WORDS: | |
return False | |
if not VALID_CALLBACK_RE.match(callback_name): | |
return False | |
return True | |
class JsonResponse(HttpResponse): | |
def __init__(self, data, encoder=DjangoJSONEncoder, safe=True, callback=None, **kwargs): | |
if safe and not isinstance(data, dict): | |
raise TypeError('In order to allow non-dict objects to be ' | |
'serialized set the safe parameter to False') | |
self.context_data = data | |
data = json.dumps(data, cls=encoder) | |
if callback is not None: | |
if not is_valid_callback_name(callback): | |
return HttpResponseBadRequest() | |
kwargs.setdefault('content_type', 'application/javascript') | |
# We prefix JSONP responses with a dummy comment to prevent people from | |
# injecting malicious content. | |
# See: http://graph.facebook.com/?callback=foo | |
data = '/**/ %s(%s);' % (callback, data) | |
response = super(JsonResponse, self).__init__(content=data, **kwargs) | |
# Apply some headers to avoid SWF injection | |
# See: http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/ | |
response['Content-Disposition'] = 'attachment; filename=f.txt' | |
response['X-Content-Type-Options'] = 'nosniff' | |
return response | |
kwargs.setdefault('content_type', 'application/json') | |
super(JsonResponse, self).__init__(content=data, **kwargs) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment