diff --git a/letsencrypt/display/ops.py b/letsencrypt/display/ops.py index 102dbe3a0..5ceb7fcfc 100644 --- a/letsencrypt/display/ops.py +++ b/letsencrypt/display/ops.py @@ -123,6 +123,7 @@ def pick_configurator( config, default, plugins, question, (interfaces.IAuthenticator, interfaces.IInstaller)) + def get_email(more=False, invalid=False): """Prompt for valid email address. @@ -209,6 +210,7 @@ def choose_names(installer): else: return [] + def get_valid_domains(domains): """Helper method for choose_names that implements basic checks on domain names @@ -226,6 +228,7 @@ def get_valid_domains(domains): continue return valid_domains + def _filter_names(names): """Determine which names the user would like to select from a list. @@ -250,7 +253,41 @@ def _choose_names_manually(): "Please enter in your domain name(s) (comma and/or space separated) ") if code == display_util.OK: - return display_util.separate_list_input(input_) + invalid_domains = dict() + retry_message = "" + try: + domain_list = display_util.separate_list_input(input_) + except UnicodeEncodeError: + domain_list = [] + retry_message = ( + "Internationalized domain names are not presently " + "supported.{0}{0}Would you like to re-enter the " + "names?{0}").format(os.linesep) + + for domain in domain_list: + try: + le_util.check_domain_sanity(domain) + except errors.ConfigurationError as e: + invalid_domains[domain] = e.message + + if len(invalid_domains): + retry_message = ( + "One or more of the entered domain names was not valid:" + "{0}{0}").format(os.linesep) + for domain in invalid_domains: + retry_message = retry_message + "{1}: {2}{0}".format( + os.linesep, domain, invalid_domains[domain]) + retry_message = retry_message + ( + "{0}Would you like to re-enter the names?{0}").format( + os.linesep) + + if retry_message: + # We had error in input + retry = util(interfaces.IDisplay).yesno(retry_message) + if retry: + return _choose_names_manually() + else: + return domain_list return [] diff --git a/letsencrypt/tests/display/ops_test.py b/letsencrypt/tests/display/ops_test.py index 30183b955..31db47cce 100644 --- a/letsencrypt/tests/display/ops_test.py +++ b/letsencrypt/tests/display/ops_test.py @@ -397,6 +397,44 @@ class ChooseNamesTest(unittest.TestCase): self.assertEqual(get_valid_domains(all_invalid), []) self.assertEqual(len(get_valid_domains(two_valid)), 2) + @mock.patch("letsencrypt.display.ops.util") + def test_choose_manually(self, mock_util): + from letsencrypt.display.ops import _choose_names_manually + # No retry + mock_util().yesno.return_value = False + # IDN and no retry + mock_util().input.return_value = (display_util.OK, + "uniçodé.com") + self.assertEqual(_choose_names_manually(), []) + # IDN exception with previous mocks + with mock.patch("letsencrypt.display.util") as mock_sl: + uerror = UnicodeEncodeError('mock', u'', + 0, 1, 'mock') + mock_sl.separate_list_input.side_effect = uerror + self.assertEqual(_choose_names_manually(), []) + # Punycode and no retry + mock_util().input.return_value = (display_util.OK, + "xn--ls8h.tld") + self.assertEqual(_choose_names_manually(), []) + # non-FQDN and no retry + mock_util().input.return_value = (display_util.OK, + "notFQDN") + self.assertEqual(_choose_names_manually(), []) + # Two valid domains + mock_util().input.return_value = (display_util.OK, + ("example.com," + "valid.example.com")) + self.assertEqual(_choose_names_manually(), + ["example.com", "valid.example.com"]) + # Three iterations + mock_util().input.return_value = (display_util.OK, + "notFQDN") + yn = mock.MagicMock() + yn.side_effect = [True, True, False] + mock_util().yesno = yn + _choose_names_manually() + self.assertEqual(mock_util().yesno.call_count, 3) + class SuccessInstallationTest(unittest.TestCase): # pylint: disable=too-few-public-methods