mirror of
https://github.com/acon96/home-llm.git
synced 2026-01-10 06:07:58 -05:00
Fix a setup issue with invalid hostnames
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ main.log
|
||||
*.xlsx
|
||||
notes.txt
|
||||
runpod_bootstrap.sh
|
||||
*.code-workspace
|
||||
2
TODO.md
2
TODO.md
@@ -59,7 +59,7 @@
|
||||
- [x] generic openai responses backend
|
||||
- [ ] fix and re-upload all compatible old models (+ upload all original safetensors)
|
||||
- [x] config entry migration function
|
||||
- [ ] re-write setup guide
|
||||
- [x] re-write setup guide
|
||||
|
||||
## more complicated ideas
|
||||
- [ ] "context requests"
|
||||
|
||||
@@ -39,7 +39,7 @@ from homeassistant.helpers.selector import (
|
||||
BooleanSelectorConfig,
|
||||
)
|
||||
|
||||
from .utils import download_model_from_hf, get_llama_cpp_python_version, install_llama_cpp_python, format_url, MissingQuantizationException
|
||||
from .utils import download_model_from_hf, get_llama_cpp_python_version, install_llama_cpp_python, is_valid_hostname, MissingQuantizationException
|
||||
from .const import (
|
||||
CONF_CHAT_MODEL,
|
||||
CONF_MAX_TOKENS,
|
||||
@@ -324,35 +324,32 @@ class ConfigFlow(BaseConfigFlow, domain=DOMAIN):
|
||||
if user_input:
|
||||
self.client_config.update(user_input)
|
||||
|
||||
# validate remote connections
|
||||
connect_err = await BACKEND_TO_CLS[self.client_config[CONF_BACKEND_TYPE]].async_validate_connection(self.hass, self.client_config)
|
||||
|
||||
if not connect_err:
|
||||
return await self.async_step_finish()
|
||||
hostname = user_input.get(CONF_HOST, "")
|
||||
if not is_valid_hostname(hostname):
|
||||
errors["base"] = "invalid_hostname"
|
||||
else:
|
||||
errors["base"] = "failed_to_connect"
|
||||
description_placeholders["exception"] = str(connect_err)
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=remote_connection_schema(
|
||||
self.client_config[CONF_BACKEND_TYPE],
|
||||
host=user_input.get(CONF_HOST),
|
||||
port=user_input.get(CONF_PORT),
|
||||
ssl=user_input.get(CONF_SSL),
|
||||
selected_path=user_input.get(CONF_GENERIC_OPENAI_PATH)
|
||||
),
|
||||
errors=errors,
|
||||
description_placeholders=description_placeholders,
|
||||
last_step=True
|
||||
)
|
||||
else:
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=remote_connection_schema(self.client_config[CONF_BACKEND_TYPE],
|
||||
host=self.client_config.get(CONF_HOST),
|
||||
port=self.client_config.get(CONF_PORT),
|
||||
ssl=self.client_config.get(CONF_SSL),
|
||||
selected_path=self.client_config.get(CONF_GENERIC_OPENAI_PATH)
|
||||
), last_step=True)
|
||||
# validate remote connections
|
||||
connect_err = await BACKEND_TO_CLS[self.client_config[CONF_BACKEND_TYPE]].async_validate_connection(self.hass, self.client_config)
|
||||
|
||||
if connect_err:
|
||||
errors["base"] = "failed_to_connect"
|
||||
description_placeholders["exception"] = str(connect_err)
|
||||
else:
|
||||
return await self.async_step_finish()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=remote_connection_schema(
|
||||
self.client_config[CONF_BACKEND_TYPE],
|
||||
host=self.client_config.get(CONF_HOST),
|
||||
port=self.client_config.get(CONF_PORT),
|
||||
ssl=self.client_config.get(CONF_SSL),
|
||||
selected_path=self.client_config.get(CONF_GENERIC_OPENAI_PATH)
|
||||
),
|
||||
errors=errors,
|
||||
description_placeholders=description_placeholders,
|
||||
last_step=True
|
||||
)
|
||||
else:
|
||||
raise AbortFlow("Unknown internal step")
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"config": {
|
||||
"error": {
|
||||
"failed_to_connect": "Failed to connect to the remote API: {exception}",
|
||||
"invalid_hostname": "The provided hostname was invalid. Please ensure you only provide the domain or IP address and not the full API endpoint.",
|
||||
"unknown": "Unexpected error",
|
||||
"pip_wheel_error": "Pip returned an error while installing the wheel! Please check the Home Assistant logs for more details."
|
||||
},
|
||||
@@ -191,6 +192,7 @@
|
||||
},
|
||||
"error": {
|
||||
"failed_to_connect": "Failed to connect to the remote API: {exception}",
|
||||
"invalid_hostname": "The provided hostname was invalid. Please ensure you only provide the domain or IP address and not the full API endpoint.",
|
||||
"unknown": "Unexpected error"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import time
|
||||
import os
|
||||
import re
|
||||
import ipaddress
|
||||
import sys
|
||||
import platform
|
||||
import logging
|
||||
@@ -449,3 +450,44 @@ def parse_raw_tool_call(raw_block: str | dict, llm_api: llm.APIInstance, user_in
|
||||
)
|
||||
|
||||
return tool_input, to_say
|
||||
|
||||
def is_valid_hostname(host: str) -> bool:
|
||||
"""
|
||||
Validates whether a string is a valid hostname or IP address,
|
||||
rejecting URLs, paths, ports, query strings, etc.
|
||||
"""
|
||||
if not host or not isinstance(host, str):
|
||||
return False
|
||||
|
||||
# Normalize: strip whitespace
|
||||
host = host.strip().lower()
|
||||
|
||||
# Special case: localhost
|
||||
if host == "localhost":
|
||||
return True
|
||||
|
||||
# Try to parse as IPv4
|
||||
try:
|
||||
ipaddress.IPv4Address(host)
|
||||
return True
|
||||
except ipaddress.AddressValueError:
|
||||
pass
|
||||
|
||||
# Try to parse as IPv6
|
||||
try:
|
||||
ipaddress.IPv6Address(host)
|
||||
return True
|
||||
except ipaddress.AddressValueError:
|
||||
pass
|
||||
|
||||
# Validate as domain name (RFC 1034/1123)
|
||||
# Rules:
|
||||
# - Only a-z, 0-9, hyphens
|
||||
# - No leading/trailing hyphens
|
||||
# - Max 63 chars per label
|
||||
# - At least 2 chars in TLD
|
||||
# - No consecutive dots
|
||||
|
||||
domain_pattern = re.compile(r"^[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?)*\.[a-z]{2,}$")
|
||||
|
||||
return bool(domain_pattern.match(host))
|
||||
Reference in New Issue
Block a user