def execute_script(hass, name, data=None): """Execute a script.""" filename = f"{name}.py" raise_if_invalid_filename(filename) with open(hass.config.path(FOLDER, filename)) as fil: source = fil.read() execute(hass, filename, source, data)
async def post(self, request: web.Request) -> web.Response: """Handle upload.""" if not request["hass_user"].is_admin: raise Unauthorized() # Increase max payload request._client_max_size = MAX_UPLOAD_SIZE # pylint: disable=protected-access try: data = self.schema(dict(await request.post())) except vol.Invalid as err: LOGGER.error("Received invalid upload data: %s", err) raise web.HTTPBadRequest() from err try: item = MediaSourceItem.from_uri(self.hass, data["media_content_id"]) except ValueError as err: LOGGER.error("Received invalid upload data: %s", err) raise web.HTTPBadRequest() from err try: source_dir_id, location = self.source.async_parse_identifier(item) except Unresolvable as err: LOGGER.error("Invalid local source ID") raise web.HTTPBadRequest() from err uploaded_file: FileField = data["file"] if not uploaded_file.content_type.startswith( ("image/", "video/", "audio/")): LOGGER.error("Content type not allowed") raise vol.Invalid("Only images and video are allowed") try: raise_if_invalid_filename(uploaded_file.filename) except ValueError as err: LOGGER.error("Invalid filename") raise web.HTTPBadRequest() from err try: await self.hass.async_add_executor_job( self._move_file, self.source.async_full_path(source_dir_id, location), uploaded_file, ) except ValueError as err: LOGGER.error("Moving upload failed: %s", err) raise web.HTTPBadRequest() from err return self.json({ "media_content_id": f"{data['media_content_id']}/{uploaded_file.filename}" })
def get_media_key(self, device_id: str, event: ImageEventBase) -> str: """Return the filename to use for a new event.""" # Convert a nest device id to a home assistant device id device_id_str = (self._devices.get(device_id, f"{device_id}-unknown_device") if self._devices else "unknown_device") event_id_str = event.event_session_id try: raise_if_invalid_filename(event_id_str) except ValueError: event_id_str = "" time_str = str(int(event.timestamp.timestamp())) event_type_str = EVENT_NAME_MAP.get(event.event_type, "event") suffix = "jpg" if event.event_image_type == EventImageType.IMAGE else "mp4" return f"{device_id_str}/{time_str}-{event_id_str}-{event_type_str}.{suffix}"
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_filename(location) except ValueError as err: raise Unresolvable("Invalid path.") from err return source_dir_id, location
def test_raise_if_invalid_filename(): """Test raise_if_invalid_filename.""" assert util.raise_if_invalid_filename("test") is None with pytest.raises(ValueError): util.raise_if_invalid_filename("/test") with pytest.raises(ValueError): util.raise_if_invalid_filename("..test") with pytest.raises(ValueError): util.raise_if_invalid_filename("\\test") with pytest.raises(ValueError): util.raise_if_invalid_filename("\\../test")
async def _upload_file(self, request: web.Request) -> web.Response: """Handle uploaded file.""" # Increase max payload request._client_max_size = MAX_SIZE # pylint: disable=protected-access data = await request.post() file_field = data.get("file") if not isinstance(file_field, web.FileField): raise vol.Invalid("Expected a file") try: raise_if_invalid_filename(file_field.filename) except ValueError as err: raise web.HTTPBadRequest from err hass: HomeAssistant = request.app["hass"] file_id = ulid_hex() if DOMAIN not in hass.data: hass.data[DOMAIN] = await FileUploadData.create(hass) file_upload_data: FileUploadData = hass.data[DOMAIN] file_dir = file_upload_data.file_dir(file_id) def _sync_work() -> None: file_dir.mkdir() # MyPy forgets about the isinstance check because we're in a function scope assert isinstance(file_field, web.FileField) with (file_dir / file_field.filename).open("wb") as target_fileobj: shutil.copyfileobj(file_field.file, target_fileobj) await hass.async_add_executor_job(_sync_work) file_upload_data.files[file_id] = file_field.filename return self.json({"file_id": file_id})
async def get(self, request: web.Request, source_dir_id: str, location: str) -> web.FileResponse: """Start a GET request.""" try: raise_if_invalid_filename(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)