From ee31f1b1d81ddcd12bbbedf9a4049f4a6a6b2436 Mon Sep 17 00:00:00 2001 From: Lucas FICHEUX Date: Wed, 13 Oct 2021 17:11:28 +0200 Subject: [PATCH] Make scripts in config.yml relative to config.yml Make the paths to scripts found in config.yml relative to the directory where config.yml is instead of where the command is ran. --- CHANGELOG.md | 1 + mitmproxy/optmanager.py | 26 ++++++++- test/mitmproxy/test_optmanager.py | 96 +++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cd8766c80..4747160225 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * New content view which handles gRPC/protobuf. Allows to apply custom definitions to visualize different field decodings. Includes example addon which applies custom definitions for selected gRPC traffic (@mame82) * Fix a crash caused when editing string option (#4852, @rbdixon) +* Scripts with relative paths are now loaded relative to the config file and not where the command is ran ## 28 September 2021: mitmproxy 7.0.4 diff --git a/mitmproxy/optmanager.py b/mitmproxy/optmanager.py index 00374c4ea4..c821a40740 100644 --- a/mitmproxy/optmanager.py +++ b/mitmproxy/optmanager.py @@ -505,12 +505,17 @@ def parse(text): return data -def load(opts: OptManager, text: str) -> None: +def load(opts: OptManager, text: str, config_path: str=None) -> None: """ Load configuration from text, over-writing options already set in this object. May raise OptionsError if the config file is invalid. """ data = parse(text) + + scripts = data.get('scripts') + if scripts is not None and config_path is not None: + data['scripts'] = [compute_path(path, config_path) for path in scripts] + opts.update_defer(**data) @@ -531,7 +536,7 @@ def load_paths(opts: OptManager, *paths: str) -> None: f"Error reading {p}: {e}" ) try: - load(opts, txt) + load(opts, txt, config_path=p) except exceptions.OptionsError as e: raise exceptions.OptionsError( f"Error reading {p}: {e}" @@ -580,3 +585,20 @@ def save(opts: OptManager, path: str, defaults: bool =False) -> None: with open(path, "wt", encoding="utf8") as f: serialize(opts, f, data, defaults) + + +def compute_path(script_path: str, config_path: str) -> str: + """ + Make relative paths found in config files relative to said config file, + instead of relative to where the command is ran. + """ + if os.path.isabs(script_path): + return script_path + if script_path != os.path.expanduser(script_path): + return script_path + return os.path.abspath( + os.path.join( + os.path.dirname(config_path), + script_path + ) + ) diff --git a/test/mitmproxy/test_optmanager.py b/test/mitmproxy/test_optmanager.py index d0f05678f0..d33ec756d9 100644 --- a/test/mitmproxy/test_optmanager.py +++ b/test/mitmproxy/test_optmanager.py @@ -1,9 +1,12 @@ import copy import io +import os import pytest import typing import argparse +import ruamel.yaml + from mitmproxy import options from mitmproxy import optmanager from mitmproxy import exceptions @@ -39,6 +42,13 @@ def __init__(self): self.add_option("one", typing.Optional[str], None, "help") +class TS(optmanager.OptManager): + def __init__(self): + super().__init__() + self.add_option("scripts", typing.Sequence[str], [], "help") + self.add_option("not_scripts", typing.Sequence[str], [], "help") + + def test_defaults(): o = TD2() defaults = { @@ -459,3 +469,89 @@ def test_set(): opts.process_deferred() assert "deferredsequenceoption" not in opts.deferred assert opts.deferredsequenceoption == ["a", "b"] + + +def test_relative_script_path(tmpdir): + opts = TS() + conf_path = os.path.join(tmpdir, 'config.yaml') + + conf = {'not_scripts': ['abc', ]} + with open(conf_path, 'w') as f: + ruamel.yaml.YAML().dump(conf, f) + optmanager.load_paths(opts, conf_path) + assert opts.not_scripts == conf['not_scripts'] + + conf = {'not_scripts': ['~/abc', ]} + with open(conf_path, 'w') as f: + ruamel.yaml.YAML().dump(conf, f) + optmanager.load_paths(opts, conf_path) + assert opts.not_scripts == conf['not_scripts'] + + conf = {'not_scripts': ['/abc', ]} + with open(conf_path, 'w') as f: + ruamel.yaml.YAML().dump(conf, f) + optmanager.load_paths(opts, conf_path) + assert opts.not_scripts == conf['not_scripts'] + + conf = {'not_scripts': ['./abc', ]} + with open(conf_path, 'w') as f: + ruamel.yaml.YAML().dump(conf, f) + optmanager.load_paths(opts, conf_path) + assert opts.not_scripts == conf['not_scripts'] + + conf = { + 'not_scripts': ['abc', ], + 'scripts': ['abc', ], + } + with open(conf_path, 'w') as f: + ruamel.yaml.YAML().dump(conf, f) + optmanager.load_paths(opts, conf_path) + assert opts.not_scripts == conf['not_scripts'] + assert opts.scripts[0] == os.path.abspath(os.path.join(tmpdir, conf['scripts'][0])) + + conf = { + 'not_scripts': ['~/abc', ], + 'scripts': ['~/abc', ], + } + with open(conf_path, 'w') as f: + ruamel.yaml.YAML().dump(conf, f) + optmanager.load_paths(opts, conf_path) + assert opts.not_scripts == conf['not_scripts'] + assert opts.scripts == conf['scripts'] + + conf = { + 'not_scripts': ['/abc', ], + 'scripts': ['/abc', ], + } + + with open(conf_path, 'w') as f: + ruamel.yaml.YAML().dump(conf, f) + optmanager.load_paths(opts, conf_path) + assert opts.not_scripts == conf['not_scripts'] + assert opts.scripts == conf['scripts'] + + conf = { + 'not_scripts': ['./abc', ], + 'scripts': ['./abc'], + } + with open(conf_path, 'w') as f: + ruamel.yaml.YAML().dump(conf, f) + optmanager.load_paths(opts, conf_path) + assert opts.not_scripts == conf['not_scripts'] + assert opts.scripts[0] == os.path.abspath(os.path.join(tmpdir, conf['scripts'][0])) + + conf = { + 'scripts': [ + 'abc', + '~/abc', + '/abc', + './abc', + ] + } + with open(conf_path, 'w') as f: + ruamel.yaml.YAML().dump(conf, f) + optmanager.load_paths(opts, conf_path) + assert opts.scripts[0] == os.path.abspath(os.path.join(tmpdir, conf['scripts'][0])) + assert opts.scripts[1] == conf['scripts'][1] + assert opts.scripts[2] == conf['scripts'][2] + assert opts.scripts[3] == os.path.abspath(os.path.join(tmpdir, conf['scripts'][3]))