mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 01:37:08 +00:00
Dont substitute user/pass for relative stream urls on generic camera (#74201)
Co-authored-by: Dave T <davet2001@users.noreply.github.com>
This commit is contained in:
parent
57114c1a55
commit
737a1fd9fa
@ -228,7 +228,13 @@ class GenericCamera(Camera):
|
|||||||
try:
|
try:
|
||||||
stream_url = self._stream_source.async_render(parse_result=False)
|
stream_url = self._stream_source.async_render(parse_result=False)
|
||||||
url = yarl.URL(stream_url)
|
url = yarl.URL(stream_url)
|
||||||
if not url.user and not url.password and self._username and self._password:
|
if (
|
||||||
|
not url.user
|
||||||
|
and not url.password
|
||||||
|
and self._username
|
||||||
|
and self._password
|
||||||
|
and url.is_absolute()
|
||||||
|
):
|
||||||
url = url.with_user(self._username).with_password(self._password)
|
url = url.with_user(self._username).with_password(self._password)
|
||||||
return str(url)
|
return str(url)
|
||||||
except TemplateError as err:
|
except TemplateError as err:
|
||||||
|
@ -150,6 +150,12 @@ async def async_test_still(
|
|||||||
except TemplateError as err:
|
except TemplateError as err:
|
||||||
_LOGGER.warning("Problem rendering template %s: %s", url, err)
|
_LOGGER.warning("Problem rendering template %s: %s", url, err)
|
||||||
return {CONF_STILL_IMAGE_URL: "template_error"}, None
|
return {CONF_STILL_IMAGE_URL: "template_error"}, None
|
||||||
|
try:
|
||||||
|
yarl_url = yarl.URL(url)
|
||||||
|
except ValueError:
|
||||||
|
return {CONF_STILL_IMAGE_URL: "malformed_url"}, None
|
||||||
|
if not yarl_url.is_absolute():
|
||||||
|
return {CONF_STILL_IMAGE_URL: "relative_url"}, None
|
||||||
verify_ssl = info[CONF_VERIFY_SSL]
|
verify_ssl = info[CONF_VERIFY_SSL]
|
||||||
auth = generate_auth(info)
|
auth = generate_auth(info)
|
||||||
try:
|
try:
|
||||||
@ -222,7 +228,12 @@ async def async_test_stream(
|
|||||||
if info.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS):
|
if info.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS):
|
||||||
stream_options[CONF_USE_WALLCLOCK_AS_TIMESTAMPS] = True
|
stream_options[CONF_USE_WALLCLOCK_AS_TIMESTAMPS] = True
|
||||||
|
|
||||||
url = yarl.URL(stream_source)
|
try:
|
||||||
|
url = yarl.URL(stream_source)
|
||||||
|
except ValueError:
|
||||||
|
return {CONF_STREAM_SOURCE: "malformed_url"}
|
||||||
|
if not url.is_absolute():
|
||||||
|
return {CONF_STREAM_SOURCE: "relative_url"}
|
||||||
if not url.user and not url.password:
|
if not url.user and not url.password:
|
||||||
username = info.get(CONF_USERNAME)
|
username = info.get(CONF_USERNAME)
|
||||||
password = info.get(CONF_PASSWORD)
|
password = info.get(CONF_PASSWORD)
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
"unable_still_load": "Unable to load valid image from still image URL (e.g. invalid host, URL or authentication failure). Review log for more info.",
|
"unable_still_load": "Unable to load valid image from still image URL (e.g. invalid host, URL or authentication failure). Review log for more info.",
|
||||||
"no_still_image_or_stream_url": "You must specify at least a still image or stream URL",
|
"no_still_image_or_stream_url": "You must specify at least a still image or stream URL",
|
||||||
"invalid_still_image": "URL did not return a valid still image",
|
"invalid_still_image": "URL did not return a valid still image",
|
||||||
|
"malformed_url": "Malformed URL",
|
||||||
|
"relative_url": "Relative URLs are not allowed",
|
||||||
"stream_file_not_found": "File not found while trying to connect to stream (is ffmpeg installed?)",
|
"stream_file_not_found": "File not found while trying to connect to stream (is ffmpeg installed?)",
|
||||||
"stream_http_not_found": "HTTP 404 Not found while trying to connect to stream",
|
"stream_http_not_found": "HTTP 404 Not found while trying to connect to stream",
|
||||||
"template_error": "Error rendering template. Review log for more info.",
|
"template_error": "Error rendering template. Review log for more info.",
|
||||||
@ -75,6 +77,8 @@
|
|||||||
"unable_still_load": "[%key:component::generic::config::error::unable_still_load%]",
|
"unable_still_load": "[%key:component::generic::config::error::unable_still_load%]",
|
||||||
"no_still_image_or_stream_url": "[%key:component::generic::config::error::no_still_image_or_stream_url%]",
|
"no_still_image_or_stream_url": "[%key:component::generic::config::error::no_still_image_or_stream_url%]",
|
||||||
"invalid_still_image": "[%key:component::generic::config::error::invalid_still_image%]",
|
"invalid_still_image": "[%key:component::generic::config::error::invalid_still_image%]",
|
||||||
|
"malformed_url": "[%key:component::generic::config::error::malformed_url%]",
|
||||||
|
"relative_url": "[%key:component::generic::config::error::relative_url%]",
|
||||||
"template_error": "[%key:component::generic::config::error::template_error%]",
|
"template_error": "[%key:component::generic::config::error::template_error%]",
|
||||||
"timeout": "[%key:component::generic::config::error::timeout%]",
|
"timeout": "[%key:component::generic::config::error::timeout%]",
|
||||||
"stream_no_route_to_host": "[%key:component::generic::config::error::stream_no_route_to_host%]",
|
"stream_no_route_to_host": "[%key:component::generic::config::error::stream_no_route_to_host%]",
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"no_devices_found": "No devices found on the network",
|
|
||||||
"single_instance_allowed": "Already configured. Only a single configuration possible."
|
"single_instance_allowed": "Already configured. Only a single configuration possible."
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"already_exists": "A camera with these URL settings already exists.",
|
"already_exists": "A camera with these URL settings already exists.",
|
||||||
"invalid_still_image": "URL did not return a valid still image",
|
"invalid_still_image": "URL did not return a valid still image",
|
||||||
|
"malformed_url": "Malformed URL",
|
||||||
"no_still_image_or_stream_url": "You must specify at least a still image or stream URL",
|
"no_still_image_or_stream_url": "You must specify at least a still image or stream URL",
|
||||||
|
"relative_url": "Relative URLs are not allowed",
|
||||||
"stream_file_not_found": "File not found while trying to connect to stream (is ffmpeg installed?)",
|
"stream_file_not_found": "File not found while trying to connect to stream (is ffmpeg installed?)",
|
||||||
"stream_http_not_found": "HTTP 404 Not found while trying to connect to stream",
|
"stream_http_not_found": "HTTP 404 Not found while trying to connect to stream",
|
||||||
"stream_io_error": "Input/Output error while trying to connect to stream. Wrong RTSP transport protocol?",
|
"stream_io_error": "Input/Output error while trying to connect to stream. Wrong RTSP transport protocol?",
|
||||||
"stream_no_route_to_host": "Could not find host while trying to connect to stream",
|
"stream_no_route_to_host": "Could not find host while trying to connect to stream",
|
||||||
"stream_no_video": "Stream has no video",
|
|
||||||
"stream_not_permitted": "Operation not permitted while trying to connect to stream. Wrong RTSP transport protocol?",
|
"stream_not_permitted": "Operation not permitted while trying to connect to stream. Wrong RTSP transport protocol?",
|
||||||
"stream_unauthorised": "Authorisation failed while trying to connect to stream",
|
|
||||||
"template_error": "Error rendering template. Review log for more info.",
|
"template_error": "Error rendering template. Review log for more info.",
|
||||||
"timeout": "Timeout while loading URL",
|
"timeout": "Timeout while loading URL",
|
||||||
"unable_still_load": "Unable to load valid image from still image URL (e.g. invalid host, URL or authentication failure). Review log for more info.",
|
"unable_still_load": "Unable to load valid image from still image URL (e.g. invalid host, URL or authentication failure). Review log for more info.",
|
||||||
@ -50,14 +49,12 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"already_exists": "A camera with these URL settings already exists.",
|
"already_exists": "A camera with these URL settings already exists.",
|
||||||
"invalid_still_image": "URL did not return a valid still image",
|
"invalid_still_image": "URL did not return a valid still image",
|
||||||
|
"malformed_url": "Malformed URL",
|
||||||
"no_still_image_or_stream_url": "You must specify at least a still image or stream URL",
|
"no_still_image_or_stream_url": "You must specify at least a still image or stream URL",
|
||||||
"stream_file_not_found": "File not found while trying to connect to stream (is ffmpeg installed?)",
|
"relative_url": "Relative URLs are not allowed",
|
||||||
"stream_http_not_found": "HTTP 404 Not found while trying to connect to stream",
|
|
||||||
"stream_io_error": "Input/Output error while trying to connect to stream. Wrong RTSP transport protocol?",
|
"stream_io_error": "Input/Output error while trying to connect to stream. Wrong RTSP transport protocol?",
|
||||||
"stream_no_route_to_host": "Could not find host while trying to connect to stream",
|
"stream_no_route_to_host": "Could not find host while trying to connect to stream",
|
||||||
"stream_no_video": "Stream has no video",
|
|
||||||
"stream_not_permitted": "Operation not permitted while trying to connect to stream. Wrong RTSP transport protocol?",
|
"stream_not_permitted": "Operation not permitted while trying to connect to stream. Wrong RTSP transport protocol?",
|
||||||
"stream_unauthorised": "Authorisation failed while trying to connect to stream",
|
|
||||||
"template_error": "Error rendering template. Review log for more info.",
|
"template_error": "Error rendering template. Review log for more info.",
|
||||||
"timeout": "Timeout while loading URL",
|
"timeout": "Timeout while loading URL",
|
||||||
"unable_still_load": "Unable to load valid image from still image URL (e.g. invalid host, URL or authentication failure). Review log for more info.",
|
"unable_still_load": "Unable to load valid image from still image URL (e.g. invalid host, URL or authentication failure). Review log for more info.",
|
||||||
|
@ -180,33 +180,43 @@ async def test_form_only_still_sample(hass, user_flow, image_file):
|
|||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("template", "url", "expected_result"),
|
("template", "url", "expected_result", "expected_errors"),
|
||||||
[
|
[
|
||||||
# Test we can handle templates in strange parts of the url, #70961.
|
# Test we can handle templates in strange parts of the url, #70961.
|
||||||
(
|
(
|
||||||
"http://localhost:812{{3}}/static/icons/favicon-apple-180x180.png",
|
"http://localhost:812{{3}}/static/icons/favicon-apple-180x180.png",
|
||||||
"http://localhost:8123/static/icons/favicon-apple-180x180.png",
|
"http://localhost:8123/static/icons/favicon-apple-180x180.png",
|
||||||
data_entry_flow.RESULT_TYPE_CREATE_ENTRY,
|
data_entry_flow.RESULT_TYPE_CREATE_ENTRY,
|
||||||
|
None,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"{% if 1 %}https://bla{% else %}https://yo{% endif %}",
|
"{% if 1 %}https://bla{% else %}https://yo{% endif %}",
|
||||||
"https://bla/",
|
"https://bla/",
|
||||||
data_entry_flow.RESULT_TYPE_CREATE_ENTRY,
|
data_entry_flow.RESULT_TYPE_CREATE_ENTRY,
|
||||||
|
None,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"http://{{example.org",
|
"http://{{example.org",
|
||||||
"http://example.org",
|
"http://example.org",
|
||||||
data_entry_flow.RESULT_TYPE_FORM,
|
data_entry_flow.RESULT_TYPE_FORM,
|
||||||
|
{"still_image_url": "template_error"},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"invalid1://invalid:4\\1",
|
"invalid1://invalid:4\\1",
|
||||||
"invalid1://invalid:4%5c1",
|
"invalid1://invalid:4%5c1",
|
||||||
data_entry_flow.RESULT_TYPE_CREATE_ENTRY,
|
data_entry_flow.RESULT_TYPE_FORM,
|
||||||
|
{"still_image_url": "malformed_url"},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"relative/urls/are/not/allowed.jpg",
|
||||||
|
"relative/urls/are/not/allowed.jpg",
|
||||||
|
data_entry_flow.RESULT_TYPE_FORM,
|
||||||
|
{"still_image_url": "relative_url"},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_still_template(
|
async def test_still_template(
|
||||||
hass, user_flow, fakeimgbytes_png, template, url, expected_result
|
hass, user_flow, fakeimgbytes_png, template, url, expected_result, expected_errors
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test we can handle various templates."""
|
"""Test we can handle various templates."""
|
||||||
respx.get(url).respond(stream=fakeimgbytes_png)
|
respx.get(url).respond(stream=fakeimgbytes_png)
|
||||||
@ -220,6 +230,7 @@ async def test_still_template(
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert result2["type"] == expected_result
|
assert result2["type"] == expected_result
|
||||||
|
assert result2.get("errors") == expected_errors
|
||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
@ -514,8 +525,29 @@ async def test_options_template_error(hass, fakeimgbytes_png, mock_create_stream
|
|||||||
result4["flow_id"],
|
result4["flow_id"],
|
||||||
user_input=data,
|
user_input=data,
|
||||||
)
|
)
|
||||||
assert result5.get("type") == data_entry_flow.RESULT_TYPE_FORM
|
|
||||||
assert result5["errors"] == {"stream_source": "template_error"}
|
assert result5.get("type") == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result5["errors"] == {"stream_source": "template_error"}
|
||||||
|
|
||||||
|
# verify that an relative stream url is rejected.
|
||||||
|
data[CONF_STILL_IMAGE_URL] = "http://127.0.0.1/testurl/1"
|
||||||
|
data[CONF_STREAM_SOURCE] = "relative/stream.mjpeg"
|
||||||
|
result6 = await hass.config_entries.options.async_configure(
|
||||||
|
result5["flow_id"],
|
||||||
|
user_input=data,
|
||||||
|
)
|
||||||
|
assert result6.get("type") == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result6["errors"] == {"stream_source": "relative_url"}
|
||||||
|
|
||||||
|
# verify that an malformed stream url is rejected.
|
||||||
|
data[CONF_STILL_IMAGE_URL] = "http://127.0.0.1/testurl/1"
|
||||||
|
data[CONF_STREAM_SOURCE] = "http://example.com:45:56"
|
||||||
|
result7 = await hass.config_entries.options.async_configure(
|
||||||
|
result6["flow_id"],
|
||||||
|
user_input=data,
|
||||||
|
)
|
||||||
|
assert result7.get("type") == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result7["errors"] == {"stream_source": "malformed_url"}
|
||||||
|
|
||||||
|
|
||||||
async def test_slug(hass, caplog):
|
async def test_slug(hass, caplog):
|
||||||
@ -528,6 +560,10 @@ async def test_slug(hass, caplog):
|
|||||||
assert result is None
|
assert result is None
|
||||||
assert "Syntax error in" in caplog.text
|
assert "Syntax error in" in caplog.text
|
||||||
|
|
||||||
|
result = slug(hass, "http://example.com:999999999999/stream")
|
||||||
|
assert result is None
|
||||||
|
assert "Syntax error in" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_options_only_stream(hass, fakeimgbytes_png, mock_create_stream):
|
async def test_options_only_stream(hass, fakeimgbytes_png, mock_create_stream):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user