def _update_or_add_directives(directives: List[Any], insert_at_top: bool, block: UnspacedList) -> None: """Adds or replaces directives in a config block.""" for directive in directives: _update_or_add_directive(block, directive, insert_at_top) if block and '\n' not in block[-1]: # could be " \n " or ["\n"] ! block.append(nginxparser.UnspacedList('\n'))
def setUp(self): self.a = ["\n ", "things", " ", "quirk"] self.b = ["y", " "] self.l = self.a[:] self.l2 = self.b[:] self.ul = UnspacedList(self.l) self.ul2 = UnspacedList(self.l2)
def test_double_redirect(self): # Test that we add one redirect for each domain example_conf = self.config.parser.abs_path('sites-enabled/example.com') self.config.enhance("example.com", "redirect") self.config.enhance("example.org", "redirect") expected1 = UnspacedList(_redirect_block_for_domain("example.com"))[0] expected2 = UnspacedList(_redirect_block_for_domain("example.org"))[0] generated_conf = self.config.parser.parsed[example_conf] self.assertTrue(util.contains_at_depth(generated_conf, expected1, 2)) self.assertTrue(util.contains_at_depth(generated_conf, expected2, 2))
def test_is_dirty(self): self.assertEqual(False, self.ul2.is_dirty()) ul3 = UnspacedList([]) ul3.append(self.ul) self.assertEqual(False, self.ul.is_dirty()) self.assertEqual(True, ul3.is_dirty()) ul4 = UnspacedList([[1], [2, 3, 4]]) self.assertEqual(False, ul4.is_dirty()) ul4[1][2] = 5 self.assertEqual(True, ul4.is_dirty())
def test_is_dirty(self): self.assertIs(self.ul2.is_dirty(), False) ul3 = UnspacedList([]) ul3.append(self.ul) self.assertIs(self.ul.is_dirty(), False) self.assertIs(ul3.is_dirty(), True) ul4 = UnspacedList([[1], [2, 3, 4]]) self.assertIs(ul4.is_dirty(), False) ul4[1][2] = 5 self.assertIs(ul4.is_dirty(), True)
def test_deploy_no_match_add_redirect(self): default_conf = self.config.parser.abs_path('sites-enabled/default') foo_conf = self.config.parser.abs_path('foo.conf') del self.config.parser.parsed[foo_conf][2][1][0][1][ 0] # remove default_server self.config.version = (1, 3, 1) self.config.deploy_cert("www.nomatch.com", "example/cert.pem", "example/key.pem", "example/chain.pem", "example/fullchain.pem") self.config.deploy_cert("nomatch.com", "example/cert.pem", "example/key.pem", "example/chain.pem", "example/fullchain.pem") self.config.enhance("www.nomatch.com", "redirect") self.config.save() self.config.parser.load() expected = UnspacedList( _redirect_block_for_domain("www.nomatch.com"))[0] generated_conf = self.config.parser.parsed[default_conf] self.assertTrue(util.contains_at_depth(generated_conf, expected, 2))
def test_dump_as_string(self): dumped = dumps(UnspacedList([ ['user', ' ', 'www-data'], [['\n', 'server', ' '], [ ['\n ', 'listen', ' ', '80'], ['\n ', 'server_name', ' ', 'foo.com'], ['\n ', 'root', ' ', '/home/ubuntu/sites/foo/'], [['\n\n ', 'location', ' ', '/status', ' '], [ ['\n ', 'check_status', ''], [['\n\n ', 'types', ' '], [['\n ', 'image/jpeg', ' ', 'jpg']]], ]] ]]])) self.assertEqual(dumped.split('\n'), 'user www-data;\n' 'server {\n' ' listen 80;\n' ' server_name foo.com;\n' ' root /home/ubuntu/sites/foo/;\n' '\n' ' location /status {\n' ' check_status;\n' '\n' ' types {\n' ' image/jpeg jpg;}}}'.split('\n'))
def test_set(self): ul3 = copy.deepcopy(self.ul) ul3[0] = "zither" l = ["\n ", "zather", "zest"] ul3[1] = UnspacedList(l) self.assertEqual(ul3, ["zither", ["zather", "zest"]]) self.assertEqual(ul3.spaced, [self.a[0], "zither", " ", l])
def _update_or_add_directive(block: UnspacedList, directive: Sequence[Any], insert_at_top: bool) -> None: if not isinstance(directive, nginxparser.UnspacedList): directive = nginxparser.UnspacedList(directive) if _is_whitespace_or_comment(directive): # whitespace or comment block.append(directive) return location = _find_location(block, directive[0]) # we can update directive if location is not None: _update_directive(block, directive, location) return _add_directive(block, directive, insert_at_top)
def test_insert(self): x = UnspacedList( [['\n ', 'listen', ' ', '69.50.225.155:9000'], ['\n ', 'listen', ' ', '127.0.0.1'], ['\n ', 'server_name', ' ', '.example.com'], ['\n ', 'server_name', ' ', 'example.*'], '\n', ['listen', ' ', '5001', ' ', 'ssl']]) x.insert(5, "FROGZ") self.assertEqual( x, [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], ['server_name', 'example.*'], ['listen', '5001', 'ssl'], 'FROGZ']) self.assertEqual( x.spaced, [['\n ', 'listen', ' ', '69.50.225.155:9000'], ['\n ', 'listen', ' ', '127.0.0.1'], ['\n ', 'server_name', ' ', '.example.com'], ['\n ', 'server_name', ' ', 'example.*'], '\n', ['listen', ' ', '5001', ' ', 'ssl'], 'FROGZ'])
def comment_directive(block: UnspacedList, location: int) -> None: """Add a ``#managed by Certbot`` comment to the end of the line at location. :param list block: The block containing the directive to be commented :param int location: The location within ``block`` of the directive to be commented """ next_entry = block[location + 1] if location + 1 < len(block) else None if isinstance(next_entry, list) and next_entry: if len(next_entry ) >= 2 and next_entry[-2] == "#" and COMMENT in next_entry[-1]: return if isinstance(next_entry, nginxparser.UnspacedList): next_entry = next_entry.spaced[0] else: next_entry = next_entry[0] block.insert(location + 1, COMMENT_BLOCK[:]) if next_entry is not None and "\n" not in next_entry: block.insert(location + 2, '\n')
def test_redirect_enhance(self): # Test that we successfully add a redirect when there is # a listen directive expected = UnspacedList(_redirect_block_for_domain("www.example.com"))[0] example_conf = self.config.parser.abs_path('sites-enabled/example.com') self.config.enhance("www.example.com", "redirect") generated_conf = self.config.parser.parsed[example_conf] self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) # Test that we successfully add a redirect when there is # no listen directive migration_conf = self.config.parser.abs_path('sites-enabled/migration.com') self.config.enhance("migration.com", "redirect") expected = UnspacedList(_redirect_block_for_domain("migration.com"))[0] generated_conf = self.config.parser.parsed[migration_conf] self.assertTrue(util.contains_at_depth(generated_conf, expected, 2))
def _parse_ssl_options(ssl_options: Optional[str]) -> List[UnspacedList]: if ssl_options is not None: try: with io.open(ssl_options, "r", encoding="utf-8") as _file: return nginxparser.load(_file) except IOError: logger.warning("Missing NGINX TLS options file: %s", ssl_options) except UnicodeDecodeError: logger.warning( "Could not read file: %s due to invalid character. " "Only UTF-8 encoding is supported.", ssl_options) except pyparsing.ParseBaseException as err: logger.warning("Could not parse file: %s due to %s", ssl_options, err) return UnspacedList([])
def test_dump_as_file(self): with open(util.get_data_filename('nginx.conf')) as handle: parsed = load(handle) parsed[-1][-1].append(UnspacedList([['server'], [['listen', ' ', '443', ' ', 'ssl'], ['server_name', ' ', 'localhost'], ['ssl_certificate', ' ', 'cert.pem'], ['ssl_certificate_key', ' ', 'cert.key'], ['ssl_session_cache', ' ', 'shared:SSL:1m'], ['ssl_session_timeout', ' ', '5m'], ['ssl_ciphers', ' ', 'HIGH:!aNULL:!MD5'], [['location', ' ', '/'], [['root', ' ', 'html'], ['index', ' ', 'index.html', ' ', 'index.htm']]]]])) with tempfile.TemporaryFile(mode='w+t') as f: dump(parsed, f) f.seek(0) parsed_new = load(f) self.assertEqual(parsed, parsed_new)
class TestUnspacedList(unittest.TestCase): """Test the UnspacedList data structure""" def setUp(self): self.a = ["\n ", "things", " ", "quirk"] self.b = ["y", " "] self.l = self.a[:] self.l2 = self.b[:] self.ul = UnspacedList(self.l) self.ul2 = UnspacedList(self.l2) def test_construction(self): self.assertEqual(self.ul, ["things", "quirk"]) self.assertEqual(self.ul2, ["y"]) def test_append(self): ul3 = copy.deepcopy(self.ul) ul3.append("wise") self.assertEqual(ul3, ["things", "quirk", "wise"]) self.assertEqual(ul3.spaced, self.a + ["wise"]) def test_add(self): ul3 = self.ul + self.ul2 self.assertEqual(ul3, ["things", "quirk", "y"]) self.assertEqual(ul3.spaced, self.a + self.b) self.assertEqual(self.ul.spaced, self.a) ul3 = self.ul + self.l2 self.assertEqual(ul3, ["things", "quirk", "y"]) self.assertEqual(ul3.spaced, self.a + self.b) def test_extend(self): ul3 = copy.deepcopy(self.ul) ul3.extend(self.ul2) self.assertEqual(ul3, ["things", "quirk", "y"]) self.assertEqual(ul3.spaced, self.a + self.b) self.assertEqual(self.ul.spaced, self.a) def test_set(self): ul3 = copy.deepcopy(self.ul) ul3[0] = "zither" l = ["\n ", "zather", "zest"] ul3[1] = UnspacedList(l) self.assertEqual(ul3, ["zither", ["zather", "zest"]]) self.assertEqual(ul3.spaced, [self.a[0], "zither", " ", l]) def test_get(self): self.assertRaises(IndexError, self.ul2.__getitem__, 2) self.assertRaises(IndexError, self.ul2.__getitem__, -3) def test_insert(self): x = UnspacedList( [['\n ', 'listen', ' ', '69.50.225.155:9000'], ['\n ', 'listen', ' ', '127.0.0.1'], ['\n ', 'server_name', ' ', '.example.com'], ['\n ', 'server_name', ' ', 'example.*'], '\n', ['listen', ' ', '5001', ' ', 'ssl']]) x.insert(5, "FROGZ") self.assertEqual(x, [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], ['server_name', '.example.com'], ['server_name', 'example.*'], ['listen', '5001', 'ssl'], 'FROGZ']) self.assertEqual(x.spaced, [['\n ', 'listen', ' ', '69.50.225.155:9000'], ['\n ', 'listen', ' ', '127.0.0.1'], ['\n ', 'server_name', ' ', '.example.com'], ['\n ', 'server_name', ' ', 'example.*'], '\n', ['listen', ' ', '5001', ' ', 'ssl'], 'FROGZ']) def test_rawlists(self): ul3 = copy.deepcopy(self.ul) ul3.insert(0, "some") ul3.append("why") ul3.extend(["did", "whether"]) del ul3[2] self.assertEqual(ul3, ["some", "things", "why", "did", "whether"]) def test_is_dirty(self): self.assertEqual(False, self.ul2.is_dirty()) ul3 = UnspacedList([]) ul3.append(self.ul) self.assertEqual(False, self.ul.is_dirty()) self.assertEqual(True, ul3.is_dirty()) ul4 = UnspacedList([[1], [2, 3, 4]]) self.assertEqual(False, ul4.is_dirty()) ul4[1][2] = 5 self.assertEqual(True, ul4.is_dirty())
def _add_directive(block: UnspacedList, directive: Sequence[Any], insert_at_top: bool) -> None: if not isinstance(directive, nginxparser.UnspacedList): directive = nginxparser.UnspacedList(directive) if _is_whitespace_or_comment(directive): # whitespace or comment block.append(directive) return location = _find_location(block, directive[0]) # Append or prepend directive. Fail if the name is not a repeatable directive name, # and there is already a copy of that directive with a different value # in the config file. # handle flat include files directive_name = directive[0] def can_append(loc: Optional[int], dir_name: str) -> bool: """ Can we append this directive to the block? """ return loc is None or (isinstance(dir_name, str) and dir_name in REPEATABLE_DIRECTIVES) err_fmt = 'tried to insert directive "{0}" but found conflicting "{1}".' # Give a better error message about the specific directive than Nginx's "fail to restart" if directive_name == INCLUDE: # in theory, we might want to do this recursively, but in practice, that's really not # necessary because we know what file we're talking about (and if we don't recurse, we # just give a worse error message) included_directives = _parse_ssl_options(directive[1]) for included_directive in included_directives: included_dir_loc = _find_location(block, included_directive[0]) included_dir_name = included_directive[0] if (not _is_whitespace_or_comment(included_directive) and not can_append(included_dir_loc, included_dir_name)): # By construction of can_append(), included_dir_loc cannot be None at that point resolved_included_dir_loc = cast(int, included_dir_loc) if block[resolved_included_dir_loc] != included_directive: raise errors.MisconfigurationError( err_fmt.format(included_directive, block[resolved_included_dir_loc])) _comment_out_directive(block, resolved_included_dir_loc, directive[1]) if can_append(location, directive_name): if insert_at_top: # Add a newline so the comment doesn't comment # out existing directives block.insert(0, nginxparser.UnspacedList('\n')) block.insert(0, directive) comment_directive(block, 0) else: block.append(directive) comment_directive(block, len(block) - 1) return # By construction of can_append(), location cannot be None at that point resolved_location = cast(int, location) if block[resolved_location] != directive: raise errors.MisconfigurationError( err_fmt.format(directive, block[resolved_location]))