summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node')
-rw-r--r--packages/meshbay-node/src/meshbay_node/config.py3
-rw-r--r--packages/meshbay-node/src/meshbay_node/ops.py3
-rw-r--r--packages/meshbay-node/src/meshbay_node/roots.py6
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py55
4 files changed, 45 insertions, 22 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/config.py b/packages/meshbay-node/src/meshbay_node/config.py
index 4712312..7673a51 100644
--- a/packages/meshbay-node/src/meshbay_node/config.py
+++ b/packages/meshbay-node/src/meshbay_node/config.py
@@ -184,7 +184,6 @@ class RootSpec:
kind: str = "generic" # generic|video|audio|photo — a view hint, unused for now
writable: bool = False # RW roots accept uploads from group members
removable: bool = False # operator can eject this root before unplugging the device
- direct: bool = False # uploads land at root path, not in a subdirectory
@dataclass
@@ -225,7 +224,7 @@ class GroupConfig:
for r in self.roots:
r.writable = False
self.roots.append(RootSpec(
- path=self.upload_dir.strip(), writable=True, direct=True))
+ path=self.upload_dir.strip(), writable=True))
@dataclass
diff --git a/packages/meshbay-node/src/meshbay_node/ops.py b/packages/meshbay-node/src/meshbay_node/ops.py
index 5b10452..3dfdc23 100644
--- a/packages/meshbay-node/src/meshbay_node/ops.py
+++ b/packages/meshbay-node/src/meshbay_node/ops.py
@@ -679,8 +679,7 @@ async def add_root(state: dict, group_id: str, path: str, *,
from meshbay_node.config import RootSpec
cfg.roots.append(RootSpec(
path=str(added.path), name=added.name, kind=added.kind,
- writable=added.writable, removable=added.removable,
- direct=added.direct))
+ writable=added.writable, removable=added.removable))
# Deliberately *not* mutating the live RootSet in place.
#
diff --git a/packages/meshbay-node/src/meshbay_node/roots.py b/packages/meshbay-node/src/meshbay_node/roots.py
index 9d3f7cb..ffe801c 100644
--- a/packages/meshbay-node/src/meshbay_node/roots.py
+++ b/packages/meshbay-node/src/meshbay_node/roots.py
@@ -122,7 +122,6 @@ class Root:
kind: str = "generic"
writable: bool = False
removable: bool = False
- direct: bool = False
ejected: bool = False
available: bool = True
@@ -226,8 +225,7 @@ class RootSet:
writable=writable,
removable=bool(spec.get("removable", False)),
ejected=bool(spec.get("ejected", False)),
- available=not bool(spec.get("ejected", False)),
- direct=bool(spec.get("direct", False)))
+ available=not bool(spec.get("ejected", False)))
_refuse_nesting(root, roots)
roots.append(root)
by_folded[root.folded] = root
@@ -370,8 +368,6 @@ class RootSet:
"ejected": r.ejected,
# Backward compat for MNP 1.0 clients
"upload": r.writable}
- if r.direct:
- d["direct"] = True
out.append(d)
return out
diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
index d341d8c..61458f2 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
@@ -214,7 +214,6 @@ JOIN_FAILURE_WINDOW = 600 # seconds
# attachments from the chat alike. One visible directory the operator can look
# into, back up or empty — rather than a hidden tree of per-user uuids that
# nobody could read, or files scattered wherever someone happened to be looking.
-UPLOAD_DIR_NAME = "uploads"
def _extract_dtls_fingerprint(sdp: str) -> bytes:
@@ -4016,7 +4015,12 @@ class WebRTCPeerSession:
# later — the same reason the old single upload root was never guessed.
# A client that names nothing is an MNP 1.0 one, and there was exactly
# one destination in its world: the first writable root.
- target_root_name = str(msg.get("root") or "").strip()
+ # `dir` is the folder being browsed, as a virtual path
+ # (`Media/Films/1999`); `root` is the older, coarser form and is what
+ # its first segment means on its own.
+ target_rel = str(msg.get("dir") or "").strip().strip("/")
+ target_root_name = (target_rel.split("/")[0] if target_rel
+ else str(msg.get("root") or "").strip())
upload_root = None
if target_root_name:
upload_root = roots.by_name(target_root_name)
@@ -4052,18 +4056,43 @@ class WebRTCPeerSession:
"filename": filename})
return
- if upload_root.direct:
- rel_dir = upload_root.name
- target_dir = upload_root.path
+ # The folder the sender is looking at, and no subdirectory of the node's
+ # invention.
+ #
+ # Uploads used to be confined to `<root>/uploads/`, created on demand.
+ # That was the last of v5's quarantine (the per-user layer went on
+ # 2026-08-14, for the same reason): a shared directory nobody can
+ # organise is not a shared directory, and a folder appearing beside the
+ # operator's library because somebody sent a file is the node deciding
+ # how their disk is arranged.
+ #
+ # What made the quarantine worth having is not the subdirectory — it is
+ # the filename allowlist, the size cap, the chunk ordering, and the
+ # no-overwrite rule below. All four are unchanged.
+ #
+ # `resolve()` and not a join: it refuses `..`, absolute segments and
+ # anything whose resolved form escapes its root, symlinks included. The
+ # client names *where among the group's own folders*, never a path on
+ # the operator's filesystem.
+ if target_rel:
+ target_dir = roots.resolve(target_rel)
+ if target_dir is None or not target_dir.is_dir():
+ self._send({"type": "error",
+ "detail": "Not a directory in this group",
+ "code": "no_such_directory",
+ "filename": filename})
+ return
+ rel_dir = target_rel
else:
- rel_dir = f"{upload_root.name}/{UPLOAD_DIR_NAME}"
- target_dir = upload_root.path / UPLOAD_DIR_NAME
- try:
- target_dir.mkdir(parents=True, exist_ok=True)
- except OSError as e:
- log.warning("Cannot create upload folder in root %r: %s",
- upload_root.name, e)
- self._send({"type": "error", "detail": "Upload folder unavailable",
+ # An MNP 1.0 client names nothing; the root itself is where its one
+ # destination now is.
+ target_dir = upload_root.path
+ rel_dir = upload_root.name
+ if not target_dir.is_dir():
+ self._send({"type": "error",
+ "detail": f"Directory '{upload_root.name}' is "
+ f"currently unavailable",
+ "code": "root_unavailable",
"filename": filename})
return