First commit
Made a first proof of concept with the ability to override things
This commit is contained in:
commit
90e3d2335b
|
@ -0,0 +1,50 @@
|
||||||
|
This allows to override things.
|
||||||
|
|
||||||
|
Some examples:
|
||||||
|
```python
|
||||||
|
$ cat target.py
|
||||||
|
import sys
|
||||||
|
|
||||||
|
print(sys.executable)
|
||||||
|
$ python target.py
|
||||||
|
/usr/bin/python
|
||||||
|
$ cat patch.py
|
||||||
|
from hijack import BinLaden
|
||||||
|
|
||||||
|
patcher = BinLaden()
|
||||||
|
patcher.override("sys.executable", "nope")
|
||||||
|
patcher.run("target.py")
|
||||||
|
$ python patch.py
|
||||||
|
nope
|
||||||
|
$
|
||||||
|
```
|
||||||
|
|
||||||
|
This could be useful to add some functionality or act as a decorator:
|
||||||
|
```python
|
||||||
|
$ cat target.py
|
||||||
|
import requests
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.get("fsf.org")
|
||||||
|
print(r.status_code, r.reason)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
$ python target.py
|
||||||
|
Invalid URL 'fsf.org': No schema supplied. Perhaps you meant http://fsf.org?
|
||||||
|
$ cat patch.py
|
||||||
|
from hijack import BinLaden
|
||||||
|
|
||||||
|
patcher = BinLaden()
|
||||||
|
old_get = patcher.import_("requests").get
|
||||||
|
|
||||||
|
@patcher.override("requests.get")
|
||||||
|
def get(url, *a, **kw):
|
||||||
|
if not url.startswith("http"):
|
||||||
|
url = "https://" + url
|
||||||
|
return old_get(url, *a, **kw)
|
||||||
|
|
||||||
|
patcher.run("target.py")
|
||||||
|
$ python patch.py
|
||||||
|
200 OK
|
||||||
|
$
|
||||||
|
```
|
|
@ -0,0 +1,63 @@
|
||||||
|
import sys
|
||||||
|
import runpy
|
||||||
|
import types
|
||||||
|
# TIL: module.__builtins__ is just an implementation detail
|
||||||
|
import builtins
|
||||||
|
|
||||||
|
# If you don't know what the qualified name is:
|
||||||
|
# https://docs.python.org/3/glossary.html#term-qualified-name
|
||||||
|
|
||||||
|
class BinLaden:
|
||||||
|
def __init__(self):
|
||||||
|
# "Things" yet to override
|
||||||
|
# Once they have been overriden thre is no need to keep em around
|
||||||
|
# since sys.modules is a thing.
|
||||||
|
# If you want to have "selective" override keeping the original module
|
||||||
|
# might be necessary to avoid reimporting every time
|
||||||
|
# This dict maps qualified name -> "thing"
|
||||||
|
self.to_override = {}
|
||||||
|
self.hijack()
|
||||||
|
|
||||||
|
def __import__(self, name, globals_=None, locals_=None, fromlist=(), level=0):
|
||||||
|
module = self.import_(name, globals_, locals_, fromlist, level)
|
||||||
|
|
||||||
|
for qualname, obj in self.to_override.copy().items():
|
||||||
|
if not qualname[0] == module.__name__:
|
||||||
|
continue
|
||||||
|
|
||||||
|
foo = module
|
||||||
|
for name in qualname[1:-1]:
|
||||||
|
if not hasattr(foo, name):
|
||||||
|
break
|
||||||
|
foo = getattr(module, name)
|
||||||
|
else:
|
||||||
|
if hasattr(foo, qualname[-1]):
|
||||||
|
setattr(foo, qualname[-1], obj)
|
||||||
|
self.to_override.pop(qualname)
|
||||||
|
|
||||||
|
return module
|
||||||
|
|
||||||
|
def hijack(self):
|
||||||
|
self.import_ = builtins.__import__
|
||||||
|
builtins.__import__ = self.__import__
|
||||||
|
|
||||||
|
def restore(self):
|
||||||
|
builtins.__import__ = self.import_
|
||||||
|
|
||||||
|
def run(self, file):
|
||||||
|
runpy.run_path(file, run_name="__main__")
|
||||||
|
|
||||||
|
def runmodule(self, path):
|
||||||
|
sys.modules.pop("__main__")
|
||||||
|
runpy.run_module(path, run_name="__main__")
|
||||||
|
|
||||||
|
def override(self, qualname, obj=None):
|
||||||
|
if obj:
|
||||||
|
self.to_override[tuple(qualname.split("."))] = obj
|
||||||
|
return
|
||||||
|
|
||||||
|
def _(fun):
|
||||||
|
self.to_override[tuple(qualname.split("."))] = fun
|
||||||
|
return fun
|
||||||
|
|
||||||
|
return _
|
Loading…
Reference in New Issue