|
13 | 13 | import mimetypes
|
14 | 14 | import os
|
15 | 15 | import re
|
| 16 | +import secrets |
16 | 17 | import subprocess
|
17 | 18 | import tempfile
|
18 | 19 | from typing import Any, Final, TypedDict, final
|
@@ -540,6 +541,9 @@ def __init__(
|
540 | 541 | self.file_cache: dict[str, str] = {}
|
541 | 542 | self.mem_cache: dict[str, TTSCache] = {}
|
542 | 543 |
|
| 544 | + # filename <-> token |
| 545 | + self.filename_token: dict[str, str] = {} |
| 546 | + |
543 | 547 | def _init_cache(self) -> dict[str, str]:
|
544 | 548 | """Init cache folder and fetch files."""
|
545 | 549 | try:
|
@@ -656,7 +660,17 @@ async def async_get_url_path(
|
656 | 660 | engine_instance, cache_key, message, use_cache, language, options
|
657 | 661 | )
|
658 | 662 |
|
659 |
| - return f"/api/tts_proxy/{filename}" |
| 663 | + # Use a randomly generated token instead of exposing the filename |
| 664 | + token = self.filename_token.get(filename) |
| 665 | + if not token: |
| 666 | + # Keep extension (.mp3, etc.) |
| 667 | + token = secrets.token_urlsafe(16) + os.path.splitext(filename)[1] |
| 668 | + |
| 669 | + # Map token <-> filename |
| 670 | + self.filename_token[filename] = token |
| 671 | + self.filename_token[token] = filename |
| 672 | + |
| 673 | + return f"/api/tts_proxy/{token}" |
660 | 674 |
|
661 | 675 | async def async_get_tts_audio(
|
662 | 676 | self,
|
@@ -910,11 +924,15 @@ def async_remove_from_mem(_: datetime) -> None:
|
910 | 924 | ),
|
911 | 925 | )
|
912 | 926 |
|
913 |
| - async def async_read_tts(self, filename: str) -> tuple[str | None, bytes]: |
| 927 | + async def async_read_tts(self, token: str) -> tuple[str | None, bytes]: |
914 | 928 | """Read a voice file and return binary.
|
915 | 929 |
|
916 | 930 | This method is a coroutine.
|
917 | 931 | """
|
| 932 | + filename = self.filename_token.get(token) |
| 933 | + if not filename: |
| 934 | + raise HomeAssistantError(f"{token} was not recognized!") |
| 935 | + |
918 | 936 | if not (record := _RE_VOICE_FILE.match(filename.lower())) and not (
|
919 | 937 | record := _RE_LEGACY_VOICE_FILE.match(filename.lower())
|
920 | 938 | ):
|
@@ -1076,6 +1094,7 @@ def __init__(self, tts: SpeechManager) -> None:
|
1076 | 1094 | async def get(self, request: web.Request, filename: str) -> web.Response:
|
1077 | 1095 | """Start a get request."""
|
1078 | 1096 | try:
|
| 1097 | + # filename is actually token, but we keep its name for compatibility |
1079 | 1098 | content, data = await self.tts.async_read_tts(filename)
|
1080 | 1099 | except HomeAssistantError as err:
|
1081 | 1100 | _LOGGER.error("Error on load tts: %s", err)
|
|
0 commit comments