214 строки
6.1 KiB
Python
214 строки
6.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
# see also: http://github.com/tav/scripts/raw/master/validate_jsonp.py
|
|
# 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()
|