diff --git a/homeassistant/components/notify/file.py b/homeassistant/components/notify/file.py index 3e2f20707ad..ec79cab59ea 100644 --- a/homeassistant/components/notify/file.py +++ b/homeassistant/components/notify/file.py @@ -54,7 +54,6 @@ class FileNotificationService(BaseNotificationService): if self.add_timestamp: text = '{} {}\n'.format(dt_util.utcnow().isoformat(), message) - file.write(text) else: text = '{}\n'.format(message) - file.write(text) + file.write(text) diff --git a/homeassistant/components/notify/smtp.py b/homeassistant/components/notify/smtp.py index 9ac73a49e3d..60fccc0c510 100644 --- a/homeassistant/components/notify/smtp.py +++ b/homeassistant/components/notify/smtp.py @@ -28,10 +28,10 @@ CONF_DEBUG = 'debug' CONF_SERVER = 'server' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_RECIPIENT): cv.string, + vol.Required(CONF_RECIPIENT): vol.Email, vol.Optional(CONF_SERVER, default='localhost'): cv.string, vol.Optional(CONF_PORT, default=25): cv.port, - vol.Optional(CONF_SENDER): cv.string, + vol.Optional(CONF_SENDER): vol.Email, vol.Optional(CONF_STARTTLS, default=False): cv.boolean, vol.Optional(CONF_USERNAME): cv.string, vol.Optional(CONF_PASSWORD): cv.string, diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index d9c761832dc..c9a6917bb01 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1,5 +1,6 @@ """Helpers for config validation using voluptuous.""" from datetime import timedelta +import os from urllib.parse import urlparse from typing import Any, Union, TypeVar, Callable, Sequence, List, Dict @@ -65,9 +66,17 @@ def boolean(value: Any) -> bool: return bool(value) -def isfile(value): +def isfile(value: Any) -> str: """Validate that the value is an existing file.""" - return vol.IsFile('not a file')(value) + if value is None: + raise vol.Invalid('None is not file') + file_in = str(value) + + if not os.path.isfile(file_in): + raise vol.Invalid('not a file') + if not os.access(file_in, os.R_OK): + raise vol.Invalid('file not readable') + return file_in def ensure_list(value: Union[T, Sequence[T]]) -> List[T]: diff --git a/tests/components/camera/test_local_file.py b/tests/components/camera/test_local_file.py index c30f59968e8..546152b0d8a 100644 --- a/tests/components/camera/test_local_file.py +++ b/tests/components/camera/test_local_file.py @@ -59,7 +59,7 @@ class TestLocalCamera(unittest.TestCase): fp.flush() with mock.patch('os.access', return_value=False): - assert setup_component(self.hass, 'camera', { + assert not setup_component(self.hass, 'camera', { 'camera': { 'name': 'config_test', 'platform': 'local_file', diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 14d80d9104d..637d5ead0b7 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -1,4 +1,6 @@ from datetime import timedelta +import os +import tempfile import pytest import voluptuous as vol @@ -59,6 +61,24 @@ def test_port(): schema(value) +def test_isfile(): + """Validate that the value is an existing file.""" + schema = vol.Schema(cv.isfile) + + with tempfile.NamedTemporaryFile() as fp: + pass + + for value in ('invalid', None, -1, 0, 80000, fp.name): + with pytest.raises(vol.Invalid): + schema(value) + + with tempfile.TemporaryDirectory() as tmp_path: + tmp_file = os.path.join(tmp_path, "test.txt") + with open(tmp_file, "w") as tmp_handl: + tmp_handl.write("test file") + schema(tmp_file) + + def test_url(): """Test URL.""" schema = vol.Schema(cv.url)