Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

212 lines (146 sloc) 6.223 kb
# -*- coding: utf-8 -*-
# Placed into the Public Domain by tav <tav@espians.com>
"""Validate Javascript Identifiers for use as JSON-P callback parameters."""
import re
from unicodedata import category
# ------------------------------------------------------------------------------
# javascript identifier unicode categories and "exceptional" chars
# ------------------------------------------------------------------------------
valid_jsid_categories_start = frozenset([
'Lu', 'Ll', 'Lt', 'Lm', 'Lo', 'Nl'
])
valid_jsid_categories = frozenset([
'Lu', 'Ll', 'Lt', 'Lm', 'Lo', 'Nl', 'Mn', 'Mc', 'Nd', 'Pc'
])
valid_jsid_chars = ('$', '_')
# ------------------------------------------------------------------------------
# regex to find array[index] patterns
# ------------------------------------------------------------------------------
array_index_regex = re.compile(r'\[[0-9]+\]$')
has_valid_array_index = array_index_regex.search
replace_array_index = array_index_regex.sub
# ------------------------------------------------------------------------------
# javascript reserved words -- including keywords and null/boolean literals
# ------------------------------------------------------------------------------
is_reserved_js_word = frozenset([
'abstract', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class',
'const', 'continue', 'debugger', 'default', 'delete', 'do', 'double',
'else', 'enum', 'export', 'extends', 'false', 'final', 'finally', 'float',
'for', 'function', 'goto', 'if', 'implements', 'import', 'in', 'instanceof',
'int', 'interface', 'long', 'native', 'new', 'null', 'package', 'private',
'protected', 'public', 'return', 'short', 'static', 'super', 'switch',
'synchronized', 'this', 'throw', 'throws', 'transient', 'true', 'try',
'typeof', 'var', 'void', 'volatile', 'while', 'with',
# potentially reserved in a future version of the ES5 standard
# 'let', 'yield'
]).__contains__
# ------------------------------------------------------------------------------
# the core validation functions
# ------------------------------------------------------------------------------
def is_valid_javascript_identifier(identifier, escape=r'\u', ucd_cat=category):
"""Return whether the given ``id`` is a valid Javascript identifier."""
if not identifier:
return False
if not isinstance(identifier, unicode):
try:
identifier = unicode(identifier, 'utf-8')
except UnicodeDecodeError:
return False
if escape in identifier:
new = []; add_char = new.append
split_id = identifier.split(escape)
add_char(split_id.pop(0))
for segment in split_id:
if len(segment) < 4:
return False
try:
add_char(unichr(int('0x' + segment[:4], 16)))
except Exception:
return False
add_char(segment[4:])
identifier = u''.join(new)
if is_reserved_js_word(identifier):
return False
first_char = identifier[0]
if not ((first_char in valid_jsid_chars) or
(ucd_cat(first_char) in valid_jsid_categories_start)):
return False
for char in identifier[1:]:
if not ((char in valid_jsid_chars) or
(ucd_cat(char) in valid_jsid_categories)):
return False
return True
def is_valid_jsonp_callback_value(value):
"""Return whether the given ``value`` can be used as a JSON-P callback."""
for identifier in value.split(u'.'):
while '[' in identifier:
if not has_valid_array_index(identifier):
return False
identifier = replace_array_index(u'', identifier)
if not is_valid_javascript_identifier(identifier):
return False
return True
# ------------------------------------------------------------------------------
# test
# ------------------------------------------------------------------------------
def test():
"""
The function ``is_valid_javascript_identifier`` validates a given identifier
according to the latest draft of the ECMAScript 5 Specification:
>>> is_valid_javascript_identifier('hello')
True
>>> is_valid_javascript_identifier('alert()')
False
>>> is_valid_javascript_identifier('a-b')
False
>>> is_valid_javascript_identifier('23foo')
False
>>> is_valid_javascript_identifier('foo23')
True
>>> is_valid_javascript_identifier('$210')
True
>>> is_valid_javascript_identifier(u'Stra\u00dfe')
True
>>> is_valid_javascript_identifier(r'\u0062') # u'b'
True
>>> is_valid_javascript_identifier(r'\u62')
False
>>> is_valid_javascript_identifier(r'\u0020')
False
>>> is_valid_javascript_identifier('_bar')
True
>>> is_valid_javascript_identifier('some_var')
True
>>> is_valid_javascript_identifier('$')
True
But ``is_valid_jsonp_callback_value`` is the function you want to use for
validating JSON-P callback parameter values:
>>> is_valid_jsonp_callback_value('somevar')
True
>>> is_valid_jsonp_callback_value('function')
False
>>> is_valid_jsonp_callback_value(' somevar')
False
It supports the possibility of '.' being present in the callback name, e.g.
>>> is_valid_jsonp_callback_value('$.ajaxHandler')
True
>>> is_valid_jsonp_callback_value('$.23')
False
As well as the pattern of providing an array index lookup, e.g.
>>> is_valid_jsonp_callback_value('array_of_functions[42]')
True
>>> is_valid_jsonp_callback_value('array_of_functions[42][1]')
True
>>> is_valid_jsonp_callback_value('$.ajaxHandler[42][1].foo')
True
>>> is_valid_jsonp_callback_value('array_of_functions[42]foo[1]')
False
>>> is_valid_jsonp_callback_value('array_of_functions[]')
False
>>> is_valid_jsonp_callback_value('array_of_functions["key"]')
False
Enjoy!
"""
if __name__ == '__main__':
import doctest
doctest.testmod()
Jump to Line
Something went wrong with that request. Please try again.