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)
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
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
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
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)
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)
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