def test_unsafe_html(err, filename, data): 'Tests for unsafe HTML tags in language pack files.' context = ContextGenerator(data) unsafe_pttrn = re.compile('<(script|embed|object)', re.I) match = unsafe_pttrn.search(data) if match: line = context.get_line(match.start()) err.warning( ('testcases_langpack', 'test_unsafe_html', 'unsafe_content_html'), 'Unsafe HTML found in language pack files.', 'Language packs are not allowed to contain scripts, ' 'embeds, or other executable code in the language ' 'definition files.', filename, line=line, context=context) remote_pttrn = re.compile(BAD_LINK, re.I) match = remote_pttrn.search(data) if match: line = context.get_line(match.start()) err.warning( ('testcases_langpack', 'test_unsafe_html', 'unsafe_content_link'), 'Unsafe remote resource found in language pack.', 'Language packs are not allowed to contain references to ' 'remote resources.', filename, line=line, context=context)
def test_unsafe_html(err, filename, data): "Tests for unsafe HTML tags in language pack files." context = ContextGenerator(data) unsafe_pttrn = re.compile('<(script|embed|object)', re.I) match = unsafe_pttrn.search(data) if match: line = context.get_line(match.start()) err.warning(("testcases_langpack", "test_unsafe_html", "unsafe_content_html"), "Unsafe HTML found in language pack files.", """Language packs are not allowed to contain scripts, embeds, or other executable code in the language definition files.""", filename, line=line, context=context) remote_pttrn = re.compile(BAD_LINK, re.I) match = remote_pttrn.search(data) if match: line = context.get_line(match.start()) err.warning(("testcases_langpack", "test_unsafe_html", "unsafe_content_link"), "Unsafe remote resource found in language pack.", """Language packs are not allowed to contain references to remote resources.""", filename, line=line, context=context)
def _regex_tests(err, data, filename): c = ContextGenerator(data) np_warning = "Network preferences may not be modified." errors = {"globalStorage\\[.*\\].password": "******", "network\\.http": np_warning, "extensions\\.blocklist\\.url": np_warning, "extensions\\.blocklist\\.level": np_warning, "extensions\\.blocklist\\.interval": np_warning, "general\\.useragent": np_warning} for regex, message in errors.items(): reg = re.compile(regex) match = reg.search(data) if match: line = c.get_line(match.start()) err.warning(("testcases_scripting", "regex_tests", "compiled_error"), "Potentially malicious JS", message, filename=filename, line=line, context=c)
def test_get_context(): """Test that contexts are generated properly.""" d = open('tests/resources/contextgenerator/data.txt').read() c = ContextGenerator(d) print c.data c_start = c.get_context(line=1, column=0) c_end = c.get_context(line=11, column=0) print c_start print c_end # Contexts are always length 3 assert len(c_start) == 3 assert c_start[0] == None assert len(c_end) == 3 assert c_end[2] == None assert c_start[1] == '0123456789' assert c_end[0] == '9012345678' assert c_end[1] == '' c_mid = c.get_context(line=5) assert len(c_mid) == 3 assert c_mid[0] == '3456789012' assert c_mid[2] == '5678901234' print c_mid
def test_get_context(): """Test that contexts are generated properly.""" d = open('tests/resources/contextgenerator/data.txt').read() c = ContextGenerator(d) print c.data c_start = c.get_context(line=1, column=0) c_end = c.get_context(line=11, column=0) print c_start print c_end # Contexts are always length 3 assert len(c_start) == 3 assert c_start[0] is None assert len(c_end) == 3 assert c_end[2] is None assert c_start[1] == '0123456789' assert c_end[0] == '9012345678' assert c_end[1] == '' c_mid = c.get_context(line=5) assert len(c_mid) == 3 assert c_mid[0] == '3456789012' assert c_mid[2] == '5678901234' print c_mid
def test_get_context(): """Test that contexts are generated properly.""" d = open("tests/resources/contextgenerator/data.txt").read() c = ContextGenerator(d) print c.data c_start = c.get_context(line=1, column=0) c_end = c.get_context(line=11, column=0) print c_start print c_end # Contexts are always length 3 assert len(c_start) == 3 assert c_start[0] == None assert len(c_end) == 3 assert c_end[2] == None assert c_start[1] == "0123456789" assert c_end[0] == "9012345678" assert c_end[1] == "" c_mid = c.get_context(line=5) assert len(c_mid) == 3 assert c_mid[0] == "3456789012" assert c_mid[2] == "5678901234" print c_mid
def test_get_line(): """Test that the context generator returns the proper line.""" d = open('tests/resources/contextgenerator/data.txt').read() c = ContextGenerator(d) assert c.get_line(30) == 3 assert c.get_line(11) == 2 assert c.get_line(10000) is None
def test_get_context_trimming(): "Tests that contexts are generated properly when lines are >140 characters" d = open("tests/resources/contextgenerator/longdata.txt").read() c = ContextGenerator(d) print c.data trimmed = c.get_context(line=2, column=89) proper_lengths = (140, 148, 140) print trimmed print [len(x) for x in trimmed]
def test_get_line(): """Test that the context generator returns the proper line.""" d = open("tests/resources/contextgenerator/data.txt").read() c = ContextGenerator(d) print c.data print c.get_line(30) assert c.get_line(30) == 3 print c.get_line(11) assert c.get_line(11) == 2 print c.get_line(10000) assert c.get_line(10000) == 11
def test_get_context_trimming_inverse(): """Tests that surrounding lines are trimmed properly; the error line is ignored if it is less than 140 characters.""" d = open("tests/resources/contextgenerator/longdata.txt").read() c = ContextGenerator(d) print c.data trimmed = c.get_context(line=6, column=0) print trimmed assert trimmed[1] == "This line should be entirely visible." assert trimmed[0][0] != "X" assert trimmed[2][-1] != "X"
def __init__(self, data, path): self.context = ContextGenerator(data) self.lines = data.split('\n') # Extract the data from the triples in the manifest triples = [] counter = 0 for line in self.lines: line = line.strip() counter += 1 # Skip weird lines. if line.startswith('#'): continue triple = line.split(None, 2) if not triple: continue elif len(triple) == 2: triple.append('') if len(triple) < 3: continue triples.append({ 'subject': triple[0], 'predicate': triple[1], 'object': triple[2], 'line': counter, 'filename': path, 'context': self.context }) self.triples = triples
def test_get_context_trimming(): """ Test that contexts are generated properly when lines are >140 characters. """ d = open('tests/resources/contextgenerator/longdata.txt').read() c = ContextGenerator(d) print c.data trimmed = c.get_context(line=2, column=89) proper_lengths = (140, 148, 140) print trimmed print [len(x) for x in trimmed] for i in range(3): eq_(len(trimmed[i]), proper_lengths[i])
def __init__(self, data, path): self.context = ContextGenerator(data) self.lines = data.split("\n") # Extract the data from the triples in the manifest triples = [] counter = 0 for line in self.lines: line = line.strip() counter += 1 # Skip weird lines. if line.startswith("#"): continue triple = line.split(None, 2) if not triple: continue elif len(triple) == 2: triple.append("") if len(triple) < 3: continue triples.append({ "subject": triple[0], "predicate": triple[1], "object": triple[2], "line": counter, "filename": path, "context": self.context }) self.triples = triples
def run_regex_tests(document, err, filename, context=None, is_js=False, explicit=False): """Run all of the regex-based JS tests.""" # When `explicit` is true, only run tests which explicitly # specify which files they're applicable to. if context is None: context = ContextGenerator(document) # Run all of the registered tests. for cls in registered_regex_tests: if not hasattr(cls, 'applicable'): if explicit: continue else: if not cls.applicable(err, filename, document): continue t = cls(err, document, filename, context) # Run standard tests. for test in t.tests(): test() # Run tests that would otherwise be guarded by `is_js`. if is_js: for test in t.js_tests(): test()
def test_get_context_trimming_inverse(): """ Tests that surrounding lines are trimmed properly; the error line is ignored if it is less than 140 characters. """ d = open('tests/resources/contextgenerator/longdata.txt').read() c = ContextGenerator(d) print c.data trimmed = c.get_context(line=6, column=0) print trimmed assert trimmed[1] == 'This line should be entirely visible.' assert trimmed[0][0] != 'X' assert trimmed[2][-1] != 'X'
def get_tree(code, err=None, filename=None, shell=None): """Retrieve the parse tree for a JS snippet.""" try: return JSShell.get_shell().get_tree(code) except JSReflectException as exc: str_exc = str(exc) if 'SyntaxError' in str_exc or 'ReferenceError' in str_exc: err.warning(('testcases_scripting', 'test_js_file', 'syntax_error'), 'JavaScript Compile-Time Error', ['A compile-time error in the JavaScript halted ' 'validation of that file.', 'Message: %s' % str_exc.split(':', 1)[-1].strip()], filename=filename, line=exc.line, context=ContextGenerator(code)) elif 'InternalError: too much recursion' in str_exc: err.notice(('testcases_scripting', 'test_js_file', 'recursion_error'), 'JS too deeply nested for validation', 'A JS file was encountered that could not be valiated ' 'due to limitations with Spidermonkey. It should be ' 'manually inspected.', filename=filename) else: err.error(('testcases_scripting', 'test_js_file', 'retrieving_tree'), 'JS reflection error prevented validation', ['An error in the JavaScript file prevented it from ' 'being properly read by the Spidermonkey JS engine.', str(exc)], filename=filename)
def test_css_file(err, filename, data, line_start=1): "Parse and test a whole CSS file." tokenizer = cssutils.tokenize2.Tokenizer() context = ContextGenerator(data) if data: data = "".join(c for c in data if 8 < ord(c) < 127) token_generator = tokenizer.tokenize(data) try: _run_css_tests(err, tokens=token_generator, filename=filename, line_start=line_start - 1, context=context) except Exception: # pragma: no cover # This happens because tokenize is a generator. # Bravo, Mr. Bond, Bravo. err.warning( ("testcases_markup_csstester", "test_css_file", "could_not_parse"), "Could not parse CSS file", "CSS file could not be parsed by the tokenizer.", filename) #raise return
def __init__(self, dtd): """ Properties parsers can initialized based on a file path (provided as a string to the path), or directly (in memory as a StringIO object). """ self.entities = {} self.items = [] if isinstance(dtd, types.StringTypes): data = open(dtd).read() elif isinstance(dtd, StringIO): data = dtd.getvalue() elif isinstance(dtd, file): data = dtd.read() # Create a context! self.context = ContextGenerator(data) split_data = data.split('\n') line_buffer = None line_number = 0 for line in split_data: # Increment the line number line_number += 1 # Clean things up clean_line = line.strip() if not clean_line: continue if clean_line.startswith('#'): continue # It's a line that wraps if clean_line.count('=') == 0: if line_buffer: line_buffer[-1] += clean_line else: continue else: if line_buffer: # This line terminates a wrapped line self.entities[line_buffer[0].strip()] = \ line_buffer[1].strip() self.items.append((line_buffer[0].strip(), line_buffer[1].strip(), line_number)) line_buffer = clean_line.split('=', 1) # Handle any left-over wrapped line data if line_buffer: self.entities[line_buffer[0].strip()] = \ line_buffer[1].strip() self.items.append( (line_buffer[0].strip(), line_buffer[1].strip(), line_number))
def test_json_constructs(): """This tests some of the internal JSON stuff so we don't break zamboni.""" e = ErrorBundle() e.detected_type = 1 e.error(("a", "b", "c"), "Test") e.error(("a", "b", "foo"), "Test") e.error(("a", "foo", "c"), "Test") e.error(("a", "foo", "c"), "Test") e.error(("b", "foo", "bar"), "Test") e.warning((), "Context test", context=("x", "y", "z")) e.warning((), "Context test", context=ContextGenerator("x\ny\nz\n"), line=2, column=0) e.notice((), "none") e.notice((), "line", line=1) e.notice((), "column", column=0) e.notice((), "line column", line=1, column=1) results = e.render_json() print results j = json.loads(results) assert "detected_type" in j assert j["detected_type"] == "extension" assert "message_tree" in j tree = j["message_tree"] assert "__errors" not in tree assert not tree["a"]["__messages"] assert tree["a"]["__errors"] == 4 assert not tree["a"]["b"]["__messages"] assert tree["a"]["b"]["__errors"] == 2 assert not tree["a"]["b"]["__messages"] assert tree["a"]["b"]["c"]["__errors"] == 1 assert tree["a"]["b"]["c"]["__messages"] assert "messages" in j for m in (m for m in j["messages"] if m["type"] == "warning"): assert m["context"] == ["x", "y", "z"] for m in (m for m in j["messages"] if m["type"] == "notice"): if "line" in m["message"]: assert m["line"] is not None assert isinstance(m["line"], int) assert m["line"] > 0 else: assert m["line"] is None if "column" in m["message"]: assert m["column"] is not None assert isinstance(m["column"], int) assert m["column"] > -1 else: assert m["column"] is None
def test_json_constructs(): """This tests some of the internal JSON stuff so we don't break zamboni.""" e = ErrorBundle() e.detected_type = 1 e.error(('a', 'b', 'c'), 'Test') e.error(('a', 'b', 'foo'), 'Test') e.error(('a', 'foo', 'c'), 'Test') e.error(('a', 'foo', 'c'), 'Test') e.error(('b', 'foo', 'bar'), 'Test') e.warning((), 'Context test', context=('x', 'y', 'z')) e.warning((), 'Context test', context=ContextGenerator('x\ny\nz\n'), line=2, column=0) e.notice((), 'none') e.notice((), 'line', line=1) e.notice((), 'column', column=0) e.notice((), 'line column', line=1, column=1) results = e.render_json() print results j = json.loads(results) assert 'detected_type' in j assert j['detected_type'] == 'extension' assert 'message_tree' in j tree = j['message_tree'] assert '__errors' not in tree assert not tree['a']['__messages'] assert tree['a']['__errors'] == 4 assert not tree['a']['b']['__messages'] assert tree['a']['b']['__errors'] == 2 assert not tree['a']['b']['__messages'] assert tree['a']['b']['c']['__errors'] == 1 assert tree['a']['b']['c']['__messages'] assert 'messages' in j for m in (m for m in j['messages'] if m['type'] == 'warning'): assert m['context'] == ['x', 'y', 'z'] for m in (m for m in j['messages'] if m['type'] == 'notice'): if 'line' in m['message']: assert m['line'] is not None assert isinstance(m['line'], int) assert m['line'] > 0 else: assert m['line'] is None if 'column' in m['message']: assert m['column'] is not None assert isinstance(m['column'], int) assert m['column'] > -1 else: assert m['column'] is None
def process(self, filename, data, extension="xul"): """Processes data by splitting it into individual lines, then incrementally feeding each line into the parser, increasing the value of the line number with each line.""" self.line = 0 self.filename = filename self.extension = extension.lower() self.reported = set() self.context = ContextGenerator(data) lines = data.split("\n") buffering = False pline = 0 for line in lines: self.line += 1 search_line = line while True: # If a CDATA element is found, push it and its contents to the # buffer. Push everything previous to it to the parser. if "<![CDATA[" in search_line and not buffering: # Find the CDATA element. cdatapos = search_line.find("<![CDATA[") # If the element isn't at the start of the line, pass # everything before it to the parser. if cdatapos: self._feed_parser(search_line[:cdatapos]) # Collect the rest of the line to send it to the buffer. search_line = search_line[cdatapos + 9:] buffering = True continue elif "]]>" in search_line and buffering: # If we find the end element on the line being scanned, # buffer everything up to the end of it, and let the rest # of the line pass through for further processing. end_cdatapos = search_line.find("]]>") self._save_to_buffer(search_line[:end_cdatapos]) search_line = search_line[end_cdatapos + 3:] buffering = False break if buffering: self._save_to_buffer(search_line + "\n") else: self._feed_parser(search_line)
def test_load_data(): """Test that data is loaded properly into the CG.""" d = """abc def ghi""" c = ContextGenerator(d) print c.data assert len(c.data) == 3 # Through inductive reasoning, we can assert that every other line # is imported properly assert c.data[0].strip() == 'abc' assert c.data[1].strip() == 'def'
def test_css_file(err, filename, data, line_start=1): 'Parse and test a whole CSS file.' tokenizer = cssutils.tokenize2.Tokenizer() context = ContextGenerator(data) if data: # Remove any characters which aren't printable, 7-bit ASCII. data = NON_ASCII_FILTER.sub('', data) token_generator = tokenizer.tokenize(data) _run_css_tests(err, tokens=token_generator, filename=filename, line_start=line_start - 1, context=context)
def test_js_file(err, filename, data, line=1, column=0, context=None, pollutable=False): 'Test a JS file by parsing and analyzing its tokens.' if err.detected_type == PACKAGE_THEME: err.warning(err_id=('testcases_scripting', 'test_js_file', 'theme_js'), warning='JS run from full theme', description='Themes should not contain executable code.', filename=filename, line=line) before_tier = None # Set the tier to 4 (Security Tests) if err is not None: before_tier = err.tier err.set_tier(3) tree = get_tree(data, filename=filename, err=err) if not tree: if before_tier: err.set_tier(before_tier) return # Generate a context if one is not available. if context is None: context = ContextGenerator(data) t = traverser.Traverser(err, filename, start_line=line, start_column=column, context=context, pollutable=pollutable, is_jsm=(filename.endswith('.jsm') or 'EXPORTED_SYMBOLS' in data)) t.run(tree) # Reset the tier so we don't break the world if err is not None: err.set_tier(before_tier)
def test_js_file(err, filename, data, line=0, context=None, pollutable=False): "Tests a JS file by parsing and analyzing its tokens" spidermonkey = err.get_resource("SPIDERMONKEY") if spidermonkey is None: # Default value is False return elif not spidermonkey: spidermonkey = SPIDERMONKEY_INSTALLATION if err.detected_type == PACKAGE_THEME: err.warning(err_id=("testcases_scripting", "test_js_file", "theme_js"), warning="JS run from full theme", description="Themes should not contain executable code.", filename=filename, line=line) before_tier = None # Set the tier to 4 (Security Tests) if err is not None: before_tier = err.tier err.set_tier(3) tree = get_tree(data, filename=filename, shell=spidermonkey, err=err) if not tree: if before_tier: err.set_tier(before_tier) return # Generate a context if one is not available. if context is None: context = ContextGenerator(data) t = traverser.Traverser(err, filename, line, context=context, is_jsm=filename.endswith(".jsm") or "EXPORTED_SYMBOLS" in data) t.pollutable = pollutable t.run(tree) # Reset the tier so we don't break the world if err is not None: err.set_tier(before_tier)
def get_tree(code, err=None, filename=None, shell=None): "Retrieves the parse tree for a JS snippet" if not code or not shell: return None try: return _get_tree(code, shell) except JSReflectException as exc: str_exc = str(exc).strip("'\"") if ("SyntaxError" in str_exc or "ReferenceError" in str_exc): err.warning(("testcases_scripting", "test_js_file", "syntax_error"), "JavaScript Compile-Time Error", ["A compile-time error in the JavaScript halted " "validation of that file.", "Message: %s" % str_exc.split(":", 1)[-1].strip()], filename=filename, line=exc.line, context=ContextGenerator(code)) elif "InternalError: too much recursion" in str_exc: err.notice(("testcases_scripting", "test_js_file", "recursion_error"), "JS too deeply nested for validation", "A JS file was encountered that could not be valiated " "due to limitations with Spidermonkey. It should be " "manually inspected.", filename=filename) else: err.error(("testcases_scripting", "test_js_file", "retrieving_tree"), "JS reflection error prevented validation", ["An error in the JavaScript file prevented it from " "being properly read by the Spidermonkey JS engine.", str(exc)], filename=filename)
def test(self, string, err, filename, context=None): extension = os.path.splitext(filename)[1] filters = { 'filename': filename, 'extension': extension, 'is_javascript': extension in self.JAVASCRIPT_EXTENSIONS, 'document': string } # Don't bother running tests unless some of our tests match the file. if any( self.check_filter(test, filters) for test in self.tests.itervalues()): if context is None: context = ContextGenerator(string) super(FileRegexTest, self).test(string, err=err, filters=filters, filename=filename, context=context)
def __init__(self, dtd): """ Creation of DTD parsers can be done based on a local file (provided as a string to the path), or directly (in memory as a StringIO object). """ self.entities = {} self.items = [] data = "" if isinstance(dtd, types.StringTypes): with open(dtd) as dtd_instance: data = dtd_instance.read() elif isinstance(dtd, file): data = dtd.read() elif isinstance(dtd, StringIO): data = dtd.getvalue() self._parse(data) # Create a context for the file self.context = ContextGenerator(data)
def run_regex_tests(document, err, filename, context=None, is_js=False): """Run all of the regex-based JS tests.""" if context is None: context = ContextGenerator(document) def _generic_test(pattern, title, message, metadata={}): """Run a single regex test.""" match = pattern.search(document) if match: line = context.get_line(match.start()) err.warning( err_id=("testcases_javascript_regex", "generic", "_generic_test"), warning=title, description=message, filename=filename, line=line, context=context) if metadata: err.metadata.update(metadata) def _substring_test(pattern, title, message): """Run a single substringest.""" match = re.compile(pattern).search(document) if match: line = context.get_line(match.start()) err.warning( err_id=("testcases_javascript_regex", "generic", "_generic_test"), warning=title, description=message, filename=filename, line=line, context=context) def _compat_test(pattern, title, message, compatibility_type, appversions=None, logFunc=err.notice): """Run a single regex test and return a compatibility message.""" match = pattern.search(document) if match: line = context.get_line(match.start()) logFunc( ("testcases_javascript_regex", "generic", "_compat_test"), title, description=message, filename=filename, line=line, context=context, compatibility_type=compatibility_type, for_appversions=appversions, tier=5) if not filename.startswith("defaults/preferences/"): from javascript.predefinedentities import (BANNED_PREF_BRANCHES, BANNED_PREF_REGEXPS) for pattern in BANNED_PREF_REGEXPS: _generic_test( re.compile("[\"']" + pattern), "Potentially unsafe preference branch referenced", "Extensions should not alter preferences matching /%s/" % pattern) for branch in BANNED_PREF_BRANCHES: _substring_test( branch.replace(r".", r"\."), "Potentially unsafe preference branch referenced", "Extensions should not alter preferences in the '%s' " "preference branch" % branch) for pattern, message in GENERIC_PATTERNS.items(): _generic_test( re.compile(pattern), "Potentially unsafe JS in use.", message) for pattern, title, message in CHROME_PATTERNS: _generic_test(re.compile(pattern), title, message, {'requires_chrome': True}) if is_js: for pattern in CATEGORY_REGEXES: _generic_test( pattern, "Potential JavaScript category registration", "Add-ons should not register JavaScript categories. It " "appears that a JavaScript category was registered via a " "script to attach properties to JavaScript globals. This " "is not allowed.") if fnmatch.fnmatch(filename, "defaults/preferences/*.js"): _generic_test( PASSWORD_REGEX, "Passwords may be stored in /defaults/preferences JS files.", "Storing passwords in the preferences is insecure and the " "Login Manager should be used instead.") is_jsm = filename.endswith(".jsm") or "EXPORTED_SYMBOLS" in document if not is_jsm: # Have a non-static/dynamic test for prototype extension. _generic_test( PROTOTYPE_REGEX, "JS Prototype extension", "It appears that an extension of a built-in JS type was " "made. This is not allowed for security and compatibility " "reasons.") for pattern in DOM_MUTATION_REGEXES: _generic_test( pattern, "DOM Mutation Events Prohibited", "DOM mutation events are flagged because of their " "deprecated status, as well as their extreme " "inefficiency. Consider using a different event.") # Firefox 5 Compatibility if err.supports_version(FX5_DEFINITION): _compat_test( re.compile(r"navigator\.language"), "navigator.language may not behave as expected", ("JavaScript code was found that references " "navigator.language, which will no longer indicate " "the language of Firefox's UI. To maintain existing " "functionality, general.useragent.locale should be " "used in place of `navigator.language`."), compatibility_type="error", appversions=FX5_DEFINITION) # Firefox 6 Compatibility if err.supports_version(FX6_DEFINITION): for pattern, bug in FX6_INTERFACES.items(): _compat_test( re.compile(pattern), "Unsupported interface in use", ("Your add-on uses interface %s, which has been removed " "from Firefox 6. Please refer to %s for possible " "alternatives.") % (pattern, BUGZILLA_BUG % bug), compatibility_type="error", appversions=FX6_DEFINITION, logFunc=err.warning) # app.update.timer _compat_test( re.compile(r"app\.update\.timer"), "app.update.timer is incompatible with Firefox 6", ("The 'app.update.timer' preference is being replaced by the " "'app.update.timerMinimumDelay' preference in Firefox 6. " "Please refer to %s for more details.") % (BUGZILLA_BUG % 614181), compatibility_type="error", appversions=FX6_DEFINITION) if is_js: # javascript/data: URI usage in the address bar _compat_test( re.compile(r"['\"](javascript|data):"), "javascript:/data: URIs may be incompatible with Firefox " "6.", ("Loading 'javascript:' and 'data:' URIs through the " "location bar may no longer work as expected in Firefox " "6. If you load these types of URIs, please test your " "add-on on the latest Firefox 6 builds, or refer to %s " "for more information.") % (BUGZILLA_BUG % 656433), compatibility_type="warning", appversions=FX6_DEFINITION) # Firefox 7 Compatibility if err.supports_version(FX7_DEFINITION): for pattern, bug in FX7_INTERFACES.items(): _compat_test( re.compile(pattern), "Unsupported interface in use", ("Your add-on uses interface %s, which has been removed " "from Firefox 7. Please refer to %s for possible " "alternatives.") % (pattern, BUGZILLA_BUG % bug), compatibility_type="error", appversions=FX7_DEFINITION, logFunc=err.warning) # nsINavHistoryObserver _compat_test( re.compile(r"nsINavHistoryObserver"), "nsINavHistoryObserver interface has changed in Firefox 7", ("The nsINavHistoryObserver interface has changed in Firefox " "7. Most function calls now required a GUID parameter, " "please refer to %s and %s for more information.") % (NSINHS_LINK, BUGZILLA_BUG % 633266), compatibility_type="error", appversions=FX7_DEFINITION) # nsIMarkupDocumentViewer_MOZILLA_2_0_BRANCH _compat_test( re.compile(r"nsIMarkupDocumentViewer_MOZILLA_2_0_BRANCH"), "MOZILLA_2_0 Namespace has been merged in Firefox 7", ("The '_MOZILLA_2_0_BRANCH' interfaces have been merged out. " "You should now use the namespace without the " "'_MOZILLA_2_0_BRANCH' suffix. Please refer to %s for more " "details.") % (BUGZILLA_BUG % 617539), compatibility_type="warning", appversions=FX7_DEFINITION) # Firefox 8 Compatibility if err.supports_version(FX8_DEFINITION): for pattern, bug in FX8_INTERFACES.items(): _compat_test( re.compile(pattern), "Removed, deprecated, or unsupported interface in use.", ("The nsISelection2 and nsISelection3 interfaces have " "been removed in Firefox 8. You can use the nsISelection " "interface instead. See %s for more details.") % (BUGZILLA_BUG % bug), compatibility_type="error", appversions=FX8_DEFINITION, logFunc=err.warning) # nsIDOMWindowInternal NSIDWI_MDN = ("https://developer.mozilla.org/en/" "XPCOM_Interface_Reference/nsIDOMWindow") _compat_test( re.compile(r"nsIDOMWindowInternal"), "nsIDOMWindowInternal has been deprecated in Firefox 8.", ("The nsIDOMWindowInternal interface has been deprecated in " "Firefox 8. You can use the nsIDOMWindow interface instead. " "See %s for more information.") % NSIDWI_MDN, compatibility_type="warning", appversions=FX8_DEFINITION) # ISO8601DateUtils # TODO(basta): Make this a string test instead once they're invented. ISO8601_MDC = ("https://developer.mozilla.org/en/JavaScript/Reference/" "Global_Objects/Date") _compat_test( re.compile(r"ISO8601DateUtils"), "ISO8601DateUtils.jsm was removed in Firefox 8.", ("The ISO8601DateUtils object is no longer available in " "Firefox 8. You can use the normal Date object instead. See " "%s for more information.") % ISO8601_MDC, compatibility_type="error", appversions=FX8_DEFINITION, logFunc=err.warning) # Firefox 9 Compatibility if err.supports_version(FX9_DEFINITION): TAINTENABLED_BUG = BUGZILLA_BUG % 679971 _compat_test( re.compile(r"navigator\.taintEnabled"), "navigator.taintEnabled was removed in Firefox 9.", ("The taintEnabled function is no longer available in" " Firefox 9. Since this function was only used for " "browser detection and this doesn't belong in extension" " code, you should remove it if possible. For more " "information, please see %s.") % TAINTENABLED_BUG, compatibility_type="warning", appversions=FX9_DEFINITION, logFunc=err.warning) XRAYPROPS_BUG = BUGZILLA_BUG % 660233 _compat_test( re.compile(r"\.nodePrincipal"), ("nodePrincipal only available in chrome context"), ("The nodePrincipal property is no longer accessible from " "untrusted scripts. For more information, please see %s." ) % XRAYPROPS_BUG, compatibility_type="warning", appversions=FX9_DEFINITION) _compat_test( re.compile(r"\.documentURIObject"), ("documentURIObject only available in chrome context"), ("The documentURIObject property is no longer accessible from " "untrusted scripts. For more information, please see %s." ) % XRAYPROPS_BUG, compatibility_type="warning", appversions=FX9_DEFINITION) _compat_test( re.compile(r"\.baseURIObject"), ("baseURIObject only available in chrome context"), ("The baseURIObject property is no longer accessible from " "untrusted scripts. For more information, please see %s." ) % XRAYPROPS_BUG, compatibility_type="warning", appversions=FX9_DEFINITION) _compat_test( re.compile(r"nsIGlobalHistory3"), "nsIGlobalHistory3 was removed in Firefox 9", ("The nsIGlobalHistory3 interface has been removed from Firefox." " For more information, please see %s." ) % (BUGZILLA_BUG % 568971), compatibility_type="warning", appversions=FX9_DEFINITION, logFunc=err.warning) # geo.wifi.* warnings geo_wifi_description = ( "The geo.wifi.* preferences are no longer created by default " "in Gecko 9. Reading them without testing for their presence " "can result in unexpected errors. See %s for more " "information." % BUGZILLA_BUG % 689252) _compat_test( re.compile(r"geo\.wifi\.uri"), "The preference 'geo.wifi.uri' was removed in Firefox 9", geo_wifi_description, compatibility_type="error", appversions=FX9_DEFINITION, logFunc=err.warning) _compat_test( re.compile(r"geo\.wifi\.protocol"), "The preference 'geo.wifi.protocol' was removed in Firefox 9", geo_wifi_description, compatibility_type="error", appversions=FX9_DEFINITION, logFunc=err.warning) # Firefox 11 Compatibility if err.supports_version(FX11_DEFINITION): for pattern, bug in FX11_INTERFACES.items(): _compat_test( re.compile(pattern), "Unsupported interface in use", "Your add-on uses interface %s, which has been removed " "from Firefox 11. Please refer to %s for possible " "alternatives." % (pattern, BUGZILLA_BUG % bug), compatibility_type="error", appversions=FX11_DEFINITION, logFunc=err.warning) # omni.jar renamed for instance in re.finditer(r"omni\.jar", document): err.warning( err_id=("testcases_regex", "regex_regex_tests", "omni.jar"), warning="'omni.jar' renamed to 'omni.ja'", description="This add-on references omni.jar, which was " "renamed to omni.ja. You should avoid referencing " "this file directly, and at least update this " "reference for any versions that support Firefox " "11 and above. See %s for more information." % BUGZILLA_BUG % 701875, filename=filename, line=context.get_line(instance.start()), context=context, for_appversions=FX11_DEFINITION, compatibility_type="error", tier=5) # Firefox 12 Compatibility if err.supports_version(FX12_DEFINITION): for pattern, bug in FX12_INTERFACES.items(): if isinstance(bug, tuple): bug, message = bug else: message = ("Your add-on uses interface %s, which has been " "removed from Gecko 12.") % pattern message = "%s See %s for more infomration." % (message, BUGZILLA_BUG % bug) _compat_test( re.compile(pattern), "Unsupported interface in use", message, compatibility_type="error", appversions=FX12_DEFINITION, logFunc=err.warning) # Test for `chromemargin` (bug 735876) for instance in re.finditer(r"chromemargin", document): err.notice( err_id=("testcases_regex", "regex_regex_tests", "chromemargin"), notice="`chromemargin` attribute changed in Gecko 12", description="This add-on uses the chromemargin attribute, " "which after Gecko 12 will not work in the same " "way with values other than 0 or -1. Please see " "%s for more information." % BUGZILLA_BUG % 735876, filename=filename, line=context.get_line(instance.start()), context=context, for_appversions=FX12_DEFINITION, compatibility_type="error", tier=5) # Thunderbird 7 Compatibility rdf:addressdirectory if err.supports_version(TB7_DEFINITION): # dictUtils.js removal _compat_test( re.compile(r"resource:///modules/dictUtils.js"), "dictUtils.js was removed in Thunderbird 7.", "The dictUtils.js file is no longer available in " "Thunderbird 7. You can use Dict.jsm instead. See" "%s for more information." % BUGZILLA_BUG % 621213, compatibility_type="error", appversions=TB7_DEFINITION, logFunc=err.warning) # de-RDF the addressbook _compat_test( re.compile(r"rdf:addressdirectory"), "The address book does not use RDF in Thunderbird 7.", "The address book was changed to use a look up table in " "Thunderbird 7. See %s and %s for more information." % (TB7_LINK, BUGZILLA_BUG % 621213), compatibility_type="error", appversions=TB7_DEFINITION) # Second test for de-RDFing the addressbook # r"GetResource(.*?)\s*\.\s*QueryInterface(.*?nsIAbDirectory);" _compat_test( re.compile(r"GetResource\(.*?\)\s*\.\s*" r"QueryInterface\(.*?nsIAbDirectory\)"), "The address book does not use RDF in Thunderbird 7.", "The address book was changed to use a look up table in " "Thunderbird 7. See %s and %s for more information." % (TB7_LINK, BUGZILLA_BUG % 621213), compatibility_type="error", appversions=TB7_DEFINITION) # Thunderbird 10 Compatibility if err.supports_version(TB10_DEFINITION): # gDownloadManagerStrings removal _compat_test( re.compile(r"gDownloadManagerStrings"), "gDownloadManagerStrings was removed in Thunderbird 10.", "This global is no longer available in " "Thunderbird 10. See %s for more information." % BUGZILLA_BUG % 700220, compatibility_type="error", appversions=TB10_DEFINITION, logFunc=err.warning) # nsTryToClose.js removal _compat_test( re.compile(r"nsTryToClose.js"), "nsTryToClose.js was removed in Thunderbird 10.", "The nsTryToClose.js file is no longer available in " "Thunderbird 10. See %s for more information." % BUGZILLA_BUG % 539997, compatibility_type="error", appversions=TB10_DEFINITION, logFunc=err.warning) # Thunderbird 11 Compatibility if err.supports_version(TB11_DEFINITION): # specialFoldersDeletionAllowed removal _compat_test( re.compile(r"specialFoldersDeletionAllowed"), "specialFoldersDeletionAllowed was removed in Thunderbird 11.", "This global is no longer available in " "Thunderbird 11. See %s for more information." % BUGZILLA_BUG % 39121, compatibility_type="error", appversions=TB11_DEFINITION, logFunc=err.notice) for pattern, bug in TB11_STRINGS.items(): _compat_test( re.compile(pattern), "Removed, renamed, or changed strings in use", "Your add-on uses string %s, which has been changed or " "removed from Thunderbird 11. Please refer to %s for " "possible alternatives." % (pattern, BUGZILLA_BUG % bug), compatibility_type="error", appversions=TB11_DEFINITION, logFunc=err.warning) for pattern, bug in TB11_JS.items(): _compat_test( re.compile(pattern), "Removed, renamed, or changed javascript in use", "Your add-on uses the javascript method or class %s, which " "has been changed or removed from Thunderbird 11. Please " "refer to %s for possible alternatives." % (pattern, BUGZILLA_BUG % bug), compatibility_type="error", appversions=TB11_DEFINITION, logFunc=err.notice) # Thunderbird 12 Compatibility if err.supports_version(TB12_DEFINITION): _compat_test( re.compile(r"EdImage(Map|MapHotSpot|MapShapes|Overlay)\.js"), "Removed javascript file EdImage*.js in use ", "EdImageMap.js, EdImageMapHotSpot.js, " "EdImageMapShapes.js, and EdImageMapOverlay.js " "were removed in Thunderbird 12. " "See %s for more information." % BUGZILLA_BUG % 717240, compatibility_type="error", appversions=TB12_DEFINITION, logFunc=err.notice) for pattern, bug in TB12_STRINGS.items(): _compat_test( re.compile(pattern), "Removed, renamed, or changed strings in use", "Your add-on uses string %s, which has been changed or " "removed from Thunderbird 11. Please refer to %s for " "possible alternatives." % (pattern, BUGZILLA_BUG % bug), compatibility_type="error", appversions=TB12_DEFINITION, logFunc=err.warning) for pattern, bug in TB12_JS.items(): _compat_test( re.compile(pattern), "Removed, renamed, or changed javascript in use", "Your add-on uses the javascript method or class %s, which " "has been changed or removed from Thunderbird 11. Please " "refer to %s for possible alternatives." % (pattern, BUGZILLA_BUG % bug), compatibility_type="error", appversions=TB12_DEFINITION, logFunc=err.notice)
def run(data, expectation, line=2): # Strip blank lines. data = '\n'.join(filter(None, data.split('\n'))) # Get the context and assert its equality. c = ContextGenerator(data) assert c.get_context(line) == expectation
def run(data, expectation, line=2): # Strip blank lines. data = '\n'.join(filter(None, data.split('\n'))) # Get the context and assert its equality. c = ContextGenerator(data) eq_(c.get_context(line), expectation)