Created
August 31, 2009 20:56
-
-
Save tav/178705 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
| # -*- 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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment