Revert "Rewrite yaml loader (#13803)" (#13805)

This reverts commit 38ff46e45c.
This commit is contained in:
Josh Hawkins 2024-09-17 16:03:22 -05:00 committed by GitHub
parent 1ed8642010
commit ff9e1da1de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 28 additions and 29 deletions

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
.DS_Store .DS_Store
__pycache__ *.pyc
*.swp *.swp
debug debug
.vscode/* .vscode/*

View File

@ -9,7 +9,6 @@ from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple, Union from typing import Any, Dict, List, Optional, Tuple, Union
import numpy as np import numpy as np
import yaml
from pydantic import ( from pydantic import (
BaseModel, BaseModel,
ConfigDict, ConfigDict,
@ -42,11 +41,11 @@ from frigate.ffmpeg_presets import (
) )
from frigate.plus import PlusApi from frigate.plus import PlusApi
from frigate.util.builtin import ( from frigate.util.builtin import (
NoDuplicateKeysLoader,
deep_merge, deep_merge,
escape_special_characters, escape_special_characters,
generate_color_palette, generate_color_palette,
get_ffmpeg_arg_list, get_ffmpeg_arg_list,
load_config_with_no_duplicates,
) )
from frigate.util.config import StreamInfoRetriever, get_relative_coordinates from frigate.util.config import StreamInfoRetriever, get_relative_coordinates
from frigate.util.image import create_mask from frigate.util.image import create_mask
@ -1765,7 +1764,7 @@ class FrigateConfig(FrigateBaseModel):
raw_config = f.read() raw_config = f.read()
if config_file.endswith(YAML_EXT): if config_file.endswith(YAML_EXT):
config = yaml.load(raw_config, NoDuplicateKeysLoader) config = load_config_with_no_duplicates(raw_config)
elif config_file.endswith(".json"): elif config_file.endswith(".json"):
config = json.loads(raw_config) config = json.loads(raw_config)
@ -1773,5 +1772,5 @@ class FrigateConfig(FrigateBaseModel):
@classmethod @classmethod
def parse_raw(cls, raw_config): def parse_raw(cls, raw_config):
config = yaml.load(raw_config, NoDuplicateKeysLoader) config = load_config_with_no_duplicates(raw_config)
return cls.model_validate(config) return cls.model_validate(config)

View File

@ -4,14 +4,13 @@ import unittest
from unittest.mock import patch from unittest.mock import patch
import numpy as np import numpy as np
import yaml
from pydantic import ValidationError from pydantic import ValidationError
from frigate.config import BirdseyeModeEnum, FrigateConfig from frigate.config import BirdseyeModeEnum, FrigateConfig
from frigate.const import MODEL_CACHE_DIR from frigate.const import MODEL_CACHE_DIR
from frigate.detectors import DetectorTypeEnum from frigate.detectors import DetectorTypeEnum
from frigate.plus import PlusApi from frigate.plus import PlusApi
from frigate.util.builtin import NoDuplicateKeysLoader, deep_merge from frigate.util.builtin import deep_merge, load_config_with_no_duplicates
class TestConfig(unittest.TestCase): class TestConfig(unittest.TestCase):
@ -1538,7 +1537,7 @@ class TestConfig(unittest.TestCase):
""" """
self.assertRaises( self.assertRaises(
ValueError, lambda: yaml.load(raw_config, NoDuplicateKeysLoader) ValueError, lambda: load_config_with_no_duplicates(raw_config)
) )
def test_object_filter_ratios_work(self): def test_object_filter_ratios_work(self):

View File

@ -89,31 +89,32 @@ def deep_merge(dct1: dict, dct2: dict, override=False, merge_lists=False) -> dic
return merged return merged
class NoDuplicateKeysLoader(yaml.loader.SafeLoader): def load_config_with_no_duplicates(raw_config) -> dict:
"""A yaml SafeLoader that disallows duplicate keys""" """Get config ensuring duplicate keys are not allowed."""
def construct_mapping(self, node, deep=False): # https://stackoverflow.com/a/71751051
mapping = super().construct_mapping(node, deep=deep) # important to use SafeLoader here to avoid RCE
class PreserveDuplicatesLoader(yaml.loader.SafeLoader):
pass
if len(node.value) != len(mapping): def map_constructor(loader, node, deep=False):
# There's a duplicate key somewhere. Find it. keys = [loader.construct_object(node, deep=deep) for node, _ in node.value]
duplicate_keys = [ vals = [loader.construct_object(node, deep=deep) for _, node in node.value]
key key_count = Counter(keys)
for key, count in Counter( data = {}
self.construct_object(key, deep=deep) for key, _ in node.value for key, val in zip(keys, vals):
if key_count[key] > 1:
raise ValueError(
f"Config input {key} is defined multiple times for the same field, this is not allowed."
) )
if count > 1 else:
] data[key] = val
return data
# This might be possible if PyYAML's construct_mapping() changes the node PreserveDuplicatesLoader.add_constructor(
# afterwards for some reason? I don't see why, but better safe than sorry. yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, map_constructor
assert len(duplicate_keys) > 0 )
return yaml.load(raw_config, PreserveDuplicatesLoader)
raise ValueError(
f"Config field duplicates are not allowed, the following fields are duplicated in the config: {', '.join(duplicate_keys)}"
)
return mapping
def clean_camera_user_pass(line: str) -> str: def clean_camera_user_pass(line: str) -> str: