Skip to content

Instantly share code, notes, and snippets.

@mattrobenolt
Last active August 29, 2015 14:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattrobenolt/02d0a1a28e12a74c61bf to your computer and use it in GitHub Desktop.
Save mattrobenolt/02d0a1a28e12a74c61bf to your computer and use it in GitHub Desktop.
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