Example #1
0
def test_raise_if_invalid_path():
    """Test raise_if_invalid_path."""
    assert util.raise_if_invalid_path("test/path") is None

    with pytest.raises(ValueError):
        assert util.raise_if_invalid_path("~test/path")

    with pytest.raises(ValueError):
        assert util.raise_if_invalid_path("~/../test/path")
def path(value: Any) -> str:
    """Validate it's a safe path."""
    if not isinstance(value, str):
        raise vol.Invalid("Expected a string")

    try:
        raise_if_invalid_path(value)
    except ValueError as err:
        raise vol.Invalid("Invalid path") from err

    return value
    def _move_file(self, target_dir: Path, uploaded_file: FileField) -> None:
        """Move file to target."""
        if not target_dir.is_dir():
            raise ValueError("Target is not an existing directory")

        target_path = target_dir / uploaded_file.filename

        target_path.relative_to(target_dir)
        raise_if_invalid_path(str(target_path))

        with target_path.open("wb") as target_fp:
            shutil.copyfileobj(uploaded_file.file, target_fp)
Example #4
0
    def async_parse_identifier(self, item: MediaSourceItem) -> tuple[str, str]:
        """Parse identifier."""
        if item.domain != DOMAIN:
            raise Unresolvable("Unknown domain.")

        source_dir_id, _, location = item.identifier.partition("/")
        if source_dir_id not in self.hass.config.media_dirs:
            raise Unresolvable("Unknown source directory.")

        try:
            raise_if_invalid_path(location)
        except ValueError as err:
            raise Unresolvable("Invalid path.") from err

        return source_dir_id, location
Example #5
0
    def async_parse_identifier(self, item: MediaSourceItem) -> tuple[str, str]:
        """Parse identifier."""
        if not item.identifier:
            # Empty source_dir_id and location
            return "", ""

        source_dir_id, location = item.identifier.split("/", 1)
        if source_dir_id not in self.hass.config.media_dirs:
            raise Unresolvable("Unknown source directory.")

        try:
            raise_if_invalid_path(location)
        except ValueError as err:
            raise Unresolvable("Invalid path.") from err

        return source_dir_id, location
Example #6
0
    def async_parse_identifier(self, item: MediaSourceItem) -> ItemInfo:
        """Parse identifier."""
        if not item.identifier:
            raise Unresolvable("Invalid path.")

        split = item.identifier.split("/")

        entry_id = split[0]

        api = self.hass.data[DOMAIN].get(entry_id)
        if api is None:
            raise Unresolvable(f"Missing {DOMAIN} configuration entry.")

        iteminfo = ItemInfo(entry_id, api.client.target_dir,
                            split[1] if len(split) > 1 else None)

        try:
            raise_if_invalid_path(str(iteminfo.path))
        except ValueError as err:
            raise Unresolvable("Invalid path.") from err

        return iteminfo
Example #7
0
    async def get(self, request: web.Request, source_dir_id: str,
                  location: str) -> web.FileResponse:
        """Start a GET request."""
        try:
            raise_if_invalid_path(location)
        except ValueError as err:
            raise web.HTTPBadRequest() from err

        if source_dir_id not in self.hass.config.media_dirs:
            raise web.HTTPNotFound()

        media_path = self.source.async_full_path(source_dir_id, location)

        # Check that the file exists
        if not media_path.is_file():
            raise web.HTTPNotFound()

        # Check that it's a media file
        mime_type, _ = mimetypes.guess_type(str(media_path))
        if not mime_type or mime_type.split("/")[0] not in MEDIA_MIME_TYPES:
            raise web.HTTPNotFound()

        return web.FileResponse(media_path)
Example #8
0
        def do_download():
            """Download the file."""
            try:
                url = service.data[ATTR_URL]

                subdir = service.data.get(ATTR_SUBDIR)

                filename = service.data.get(ATTR_FILENAME)

                overwrite = service.data.get(ATTR_OVERWRITE)

                if subdir:
                    # Check the path
                    raise_if_invalid_path(subdir)

                final_path = None

                req = requests.get(url, stream=True, timeout=10)

                if req.status_code != HTTP_OK:
                    _LOGGER.warning(
                        "downloading '%s' failed, status_code=%d", url, req.status_code
                    )
                    hass.bus.fire(
                        f"{DOMAIN}_{DOWNLOAD_FAILED_EVENT}",
                        {"url": url, "filename": filename},
                    )

                else:
                    if filename is None and "content-disposition" in req.headers:
                        match = re.findall(
                            r"filename=(\S+)", req.headers["content-disposition"]
                        )

                        if match:
                            filename = match[0].strip("'\" ")

                    if not filename:
                        filename = os.path.basename(url).strip()

                    if not filename:
                        filename = "ha_download"

                    # Check the filename
                    raise_if_invalid_filename(filename)

                    # Do we want to download to subdir, create if needed
                    if subdir:
                        subdir_path = os.path.join(download_path, subdir)

                        # Ensure subdir exist
                        if not os.path.isdir(subdir_path):
                            os.makedirs(subdir_path)

                        final_path = os.path.join(subdir_path, filename)

                    else:
                        final_path = os.path.join(download_path, filename)

                    path, ext = os.path.splitext(final_path)

                    # If file exist append a number.
                    # We test filename, filename_2..
                    if not overwrite:
                        tries = 1
                        final_path = path + ext
                        while os.path.isfile(final_path):
                            tries += 1

                            final_path = f"{path}_{tries}.{ext}"

                    _LOGGER.debug("%s -> %s", url, final_path)

                    with open(final_path, "wb") as fil:
                        for chunk in req.iter_content(1024):
                            fil.write(chunk)

                    _LOGGER.debug("Downloading of %s done", url)
                    hass.bus.fire(
                        f"{DOMAIN}_{DOWNLOAD_COMPLETED_EVENT}",
                        {"url": url, "filename": filename},
                    )

            except requests.exceptions.ConnectionError:
                _LOGGER.exception("ConnectionError occurred for %s", url)
                hass.bus.fire(
                    f"{DOMAIN}_{DOWNLOAD_FAILED_EVENT}",
                    {"url": url, "filename": filename},
                )

                # Remove file if we started downloading but failed
                if final_path and os.path.isfile(final_path):
                    os.remove(final_path)
            except ValueError:
                _LOGGER.exception("Invalid value")
                hass.bus.fire(
                    f"{DOMAIN}_{DOWNLOAD_FAILED_EVENT}",
                    {"url": url, "filename": filename},
                )

                # Remove file if we started downloading but failed
                if final_path and os.path.isfile(final_path):
                    os.remove(final_path)
Example #9
0
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):

    hass.data.setdefault(DOMAIN, {})

    data = config_entry.data

    api = MotionFrontendApi(hass, data)
    try:
        await api.update(updatecameras=True)
    except MotionHttpClientError as err:
        await api.close()
        raise ConfigEntryNotReady from err

    if not api.is_available:
        raise ConfigEntryNotReady

    api.unsub_entry_update_listener = config_entry.add_update_listener(api.entry_update_listener)

    hass.data[DOMAIN][config_entry.entry_id] = api

    webhook_mode = data.get(CONF_WEBHOOK_MODE)
    if webhook_mode != CONF_OPTION_NONE:
        # setup webhook to manage 'push' from motion server
        try:
            webhook_id = f"{DOMAIN}_{config_entry.entry_id}"
            hass.components.webhook.async_register(DOMAIN, DOMAIN, webhook_id, api.async_handle_webhook)
            api.webhook_id = webhook_id #set here after succesfully calling async_register
            webhook_address = data.get(CONF_WEBHOOK_ADDRESS, CONF_OPTION_DEFAULT)
            if webhook_address == CONF_OPTION_INTERNAL:
                api.webhook_url = hass.helpers.network.get_url(
                    allow_internal=True,
                    allow_external=False,
                    allow_cloud=False)
            elif webhook_address == CONF_OPTION_EXTERNAL:
                api.webhook_url = hass.helpers.network.get_url(
                    allow_internal=False,
                    allow_external=True,
                    allow_cloud=True, prefer_cloud=False)
            elif webhook_address == CONF_OPTION_CLOUD:
                api.webhook_url = hass.helpers.network.get_url(
                    allow_internal=False,
                    allow_external=False,
                    allow_cloud=True, prefer_cloud=True)
            else:
                api.webhook_url = hass.helpers.network.get_url()
            api.webhook_url += hass.components.webhook.async_generate_path(api.webhook_id)

            force = (webhook_mode == CONF_OPTION_FORCE)

            config = api.config
            for event in MANAGED_EVENTS:
                hookcommand = f"curl%20-d%20'event={event}'%20" \
                                "-d%20'camera_id=%t'%20" \
                                "-d%20'event_id=%v'%20" \
                                "-d%20'filename=%f'%20" \
                                "-d%20'filetype=%n'%20" \
                                f"{api.webhook_url}"

                command = config.get(event)
                if (command != hookcommand) and (force or (command is None)):
                    await api.async_config_set(event, hookcommand)

            LOGGER.info("Registered webhook for motion events")
        except Exception as exception:
            LOGGER.exception("exception (%s) setting up webhook", str(exception))
            if api.webhook_id:
                hass.components.webhook.async_unregister(api.webhook_id) # this is actually 'safe'
                api.webhook_id = None
                api.webhook_url = None


    # setup media_source entry to access server recordings if they're local
    if data.get(CONF_MEDIASOURCE):
        try:
            media_dir_id = f"{DOMAIN}_{api.unique_id}"
            if media_dir_id not in hass.config.media_dirs:
                target_dir = api.config.get(cs.TARGET_DIR)
                if target_dir:
                    raise_if_invalid_path(target_dir)
                    if os.access(target_dir, os.R_OK):
                        hass.config.media_dirs[media_dir_id] = target_dir
                        LOGGER.info("Registered media_dirs[%s] for motion server target_dir", media_dir_id)
                        api.media_dir_id = media_dir_id
                    else:
                        LOGGER.error("Missing read access for target recordings directory")

        except Exception as err:
            LOGGER.exception("exception (%s) setting up media_source directory", str(err))


    for p in PLATFORMS:
        hass.async_create_task(
            hass.config_entries.async_forward_entry_setup(config_entry, p)
        )

    return True