Skip to content

Commit

Permalink
make scripts in config.yml relative to config.yml (mitmproxy#4860)
Browse files Browse the repository at this point in the history
Co-authored-by: Lucas FICHEUX <lficheux@corp.free.fr>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed Dec 4, 2023
1 parent 148ebad commit ba84b6b
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 13 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -9,6 +9,8 @@

* Fix empty cookie attributes being set to `Key=` instead of `Key`
([#5084](https://github.com/mitmproxy/mitmproxy/pull/5084), @Speedlulu)
* Scripts with relative paths are now loaded relative to the config file and not where the command is ran
([#4860](https://github.com/mitmproxy/mitmproxy/pull/4860), @Speedlulu)


## 14 November 2023: mitmproxy 10.1.5
Expand Down
43 changes: 31 additions & 12 deletions mitmproxy/optmanager.py
Expand Up @@ -2,14 +2,14 @@

import contextlib
import copy
import os
import pprint
import textwrap
import weakref
from collections.abc import Callable
from collections.abc import Iterable
from collections.abc import Sequence
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from typing import Optional
from typing import TextIO
Expand Down Expand Up @@ -541,31 +541,38 @@ def parse(text):
return data


def load(opts: OptManager, text: str) -> None:
def load(opts: OptManager, text: str, cwd: Path | str | None = 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 cwd is not None:
data["scripts"] = [
str(relative_path(Path(path), relative_to=Path(cwd))) for path in scripts
]

opts.update_defer(**data)


def load_paths(opts: OptManager, *paths: str) -> None:
def load_paths(opts: OptManager, *paths: Path | str) -> None:
"""
Load paths in order. Each path takes precedence over the previous
path. Paths that don't exist are ignored, errors raise an
OptionsError.
"""
for p in paths:
p = os.path.expanduser(p)
if os.path.exists(p) and os.path.isfile(p):
with open(p, encoding="utf8") as f:
p = Path(p).expanduser()
if p.exists() and p.is_file():
with p.open(encoding="utf8") as f:
try:
txt = f.read()
except UnicodeDecodeError as e:
raise exceptions.OptionsError(f"Error reading {p}: {e}")
try:
load(opts, txt)
load(opts, txt, cwd=p.absolute().parent)
except exceptions.OptionsError as e:
raise exceptions.OptionsError(f"Error reading {p}: {e}")

Expand Down Expand Up @@ -594,21 +601,33 @@ def serialize(
ruamel.yaml.YAML().dump(data, file)


def save(opts: OptManager, path: str, defaults: bool = False) -> None:
def save(opts: OptManager, path: Path | str, defaults: bool = False) -> None:
"""
Save to path. If the destination file exists, modify it in-place.
Raises OptionsError if the existing data is corrupt.
"""
path = os.path.expanduser(path)
if os.path.exists(path) and os.path.isfile(path):
with open(path, encoding="utf8") as f:
path = Path(path).expanduser()
if path.exists() and path.is_file():
with path.open(encoding="utf8") as f:
try:
data = f.read()
except UnicodeDecodeError as e:
raise exceptions.OptionsError(f"Error trying to modify {path}: {e}")
else:
data = ""

with open(path, "w", encoding="utf8") as f:
with path.open("w", encoding="utf8") as f:
serialize(opts, f, data, defaults)


def relative_path(script_path: Path | str, *, relative_to: Path | str) -> Path:
"""
Make relative paths found in config files relative to said config file,
instead of relative to where the command is ran.
"""
script_path = Path(script_path)
# Edge case when $HOME is not an absolute path
if script_path.expanduser() != script_path and not script_path.is_absolute():
script_path = script_path.expanduser().absolute()
return (relative_to / script_path.expanduser()).absolute()
3 changes: 3 additions & 0 deletions test/mitmproxy/data/test_config.yml
@@ -0,0 +1,3 @@
scripts: ['~/abc', 'abc', '../abc', '/abc']

not_scripts: ['~/abc', 'abc', '../abc', '/abc']
50 changes: 49 additions & 1 deletion test/mitmproxy/test_optmanager.py
Expand Up @@ -2,6 +2,7 @@
import copy
import io
from collections.abc import Sequence
from pathlib import Path
from typing import Optional

import pytest
Expand Down Expand Up @@ -41,6 +42,13 @@ def __init__(self):
self.add_option("one", Optional[str], None, "help")


class TS(optmanager.OptManager):
def __init__(self):
super().__init__()
self.add_option("scripts", Sequence[str], [], "help")
self.add_option("not_scripts", Sequence[str], [], "help")


def test_defaults():
o = TD2()
defaults = {
Expand Down Expand Up @@ -302,7 +310,7 @@ def test_serialize_defaults():
def test_saving(tmpdir):
o = TD2()
o.three = "set"
dst = str(tmpdir.join("conf"))
dst = Path(tmpdir.join("conf"))
optmanager.save(o, dst, defaults=True)

o2 = TD2()
Expand Down Expand Up @@ -469,3 +477,43 @@ def test_set():
opts.process_deferred()
assert "deferredsequenceoption" not in opts.deferred
assert opts.deferredsequenceoption == ["a", "b"]


def test_load_paths(tdata):
opts = TS()
conf_path = tdata.path("mitmproxy/data/test_config.yml")
optmanager.load_paths(opts, conf_path)
assert opts.scripts == [
str(Path.home().absolute().joinpath("abc")),
str(Path(conf_path).parent.joinpath("abc")),
str(Path(conf_path).parent.joinpath("../abc")),
str(Path("/abc").absolute()),
]
assert opts.not_scripts == ["~/abc", "abc", "../abc", "/abc"]


@pytest.mark.parametrize(
"script_path, relative_to, expected",
(
("~/abc", ".", Path.home().joinpath("abc")),
("/abc", ".", Path("/abc")),
("abc", ".", Path(".").joinpath("abc")),
("../abc", ".", Path(".").joinpath("../abc")),
("~/abc", "/tmp", Path.home().joinpath("abc")),
("/abc", "/tmp", Path("/abc")),
("abc", "/tmp", Path("/tmp").joinpath("abc")),
("../abc", "/tmp", Path("/tmp").joinpath("../abc")),
("~/abc", "foo", Path.home().joinpath("abc")),
("/abc", "foo", Path("/abc")),
("abc", "foo", Path("foo").joinpath("abc")),
("../abc", "foo", Path("foo").joinpath("../abc")),
),
)
def test_relative_path(script_path, relative_to, expected):
assert (
optmanager.relative_path(
script_path,
relative_to=relative_to,
)
== expected.absolute()
)

0 comments on commit ba84b6b

Please sign in to comment.