CORVO-AI commited on
Commit
ff178eb
·
verified ·
1 Parent(s): f0c48a6

Update file-manager/app.py

Browse files
Files changed (1) hide show
  1. file-manager/app.py +87 -31
file-manager/app.py CHANGED
@@ -1,4 +1,4 @@
1
- import os, shutil, html, urllib.parse
2
  from flask import Flask, request, redirect, send_file, Response
3
  from werkzeug.middleware.dispatcher import DispatcherMiddleware
4
  from werkzeug.serving import run_simple
@@ -6,44 +6,107 @@ from werkzeug.serving import run_simple
6
  app = Flask(__name__)
7
  app.config["APPLICATION_ROOT"] = "/fm"
8
 
9
- # Guide rule 10: mask secret-bearing values so a stray read can't leak credentials.
10
  SECRET_HINTS = ("TOKEN", "KEY", "SECRET", "PASSWORD", "AUTH", "COOKIE")
11
 
12
  def is_sensitive(name):
13
  up = name.upper()
14
  return any(h in up for h in SECRET_HINTS)
15
 
16
- PAGE = """<!doctype html><html><head><title>File Manager</title>
 
 
 
 
 
 
 
 
 
 
17
  <style>
18
- body{{font-family:monospace;margin:20px;background:#111;color:#eee}}
19
  a{{color:#29BEFD;text-decoration:none}} a:hover{{text-decoration:underline}}
20
- table{{border-collapse:collapse;width:100%}} td,th{{padding:4px 8px;border-bottom:1px solid #333;text-align:left}}
21
- .bar{{background:#222;padding:10px;margin-bottom:10px;border-radius:6px}}
22
- textarea{{width:100%;height:60vh;background:#000;color:#0f0;border:1px solid #333}}
23
- button,input[type=submit]{{background:#F46821;color:#fff;border:0;padding:6px 12px;border-radius:4px;cursor:pointer}}
24
- </style></head><body>{body}</body></html>"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
 
26
  @app.route("/")
27
  def browse():
28
- path = request.args.get("path", "/")
29
- path = os.path.abspath(path)
30
  if not os.path.exists(path):
31
  return Response(PAGE.format(body=f"<p>Not found: {html.escape(path)}</p>"), status=404)
32
-
33
  if os.path.isfile(path):
34
  return redirect(f"/fm/view?path={urllib.parse.quote(path)}")
35
 
36
- rows = ""
37
  parent = os.path.dirname(path.rstrip("/")) or "/"
38
- rows += f'<tr><td><a href="/fm/?path={urllib.parse.quote(parent)}">.. (up)</a></td><td></td><td></td></tr>'
39
  try:
40
  entries = sorted(os.listdir(path))
41
  except PermissionError:
42
  return Response(PAGE.format(body=f"<p>Permission denied: {html.escape(path)}</p>"), status=403)
43
 
44
  for name in entries:
45
- full = os.path.join(path, name)
46
- q = urllib.parse.quote(full)
47
  try:
48
  size = os.path.getsize(full) if os.path.isfile(full) else ""
49
  except OSError:
@@ -63,18 +126,14 @@ def browse():
63
  <div class="bar">
64
  <form action="/fm/mkdir" method="post" style="display:inline">
65
  <input type="hidden" name="path" value="{html.escape(path)}">
66
- <input name="name" placeholder="new folder name">
67
- <input type="submit" value="Create folder">
68
  </form>
69
  <form action="/fm/upload" method="post" enctype="multipart/form-data" style="display:inline">
70
  <input type="hidden" name="path" value="{html.escape(path)}">
71
- <input type="file" name="file">
72
- <input type="submit" value="Upload">
73
  </form>
74
  </div>
75
- <table><tr><th>Name</th><th>Size</th><th>Actions</th></tr>{rows}</table>
76
- <p style="color:#777">Test mode: no auth. Storage is ephemeral — edits reset on rebuild.</p>
77
- """
78
  return Response(PAGE.format(body=body))
79
 
80
  @app.route("/view")
@@ -87,7 +146,7 @@ def view():
87
  else:
88
  try:
89
  with open(path, "r", errors="replace") as f:
90
- content = f.read(200000)
91
  except Exception as e:
92
  content = f"[cannot read as text: {e}]"
93
  q = urllib.parse.quote(path)
@@ -98,7 +157,7 @@ def view():
98
  f"<pre>{html.escape(content)}</pre>")
99
  return Response(PAGE.format(body=body))
100
 
101
- @app.route("/edit", methods=["GET"])
102
  def edit():
103
  path = os.path.abspath(request.args.get("path", ""))
104
  try:
@@ -110,8 +169,8 @@ def edit():
110
  <div class="bar"><b>Editing:</b> {html.escape(path)}</div>
111
  <form action="/fm/save" method="post">
112
  <input type="hidden" name="path" value="{html.escape(path)}">
113
- <textarea name="content">{html.escape(content)}</textarea>
114
- <br><input type="submit" value="Save (live, no refresh)">
115
  <a href="/fm/?path={urllib.parse.quote(os.path.dirname(path))}">cancel</a>
116
  </form>"""
117
  return Response(PAGE.format(body=body))
@@ -149,10 +208,7 @@ def delete():
149
  path = os.path.abspath(request.args.get("path", ""))
150
  parent = os.path.dirname(path)
151
  try:
152
- if os.path.isdir(path):
153
- shutil.rmtree(path)
154
- else:
155
- os.remove(path)
156
  except Exception as e:
157
  return Response(PAGE.format(body=f"<p>Delete failed: {e}</p>"), status=500)
158
  return redirect(f"/fm/?path={urllib.parse.quote(parent)}")
 
1
+ import os, shutil, html, urllib.parse, subprocess
2
  from flask import Flask, request, redirect, send_file, Response
3
  from werkzeug.middleware.dispatcher import DispatcherMiddleware
4
  from werkzeug.serving import run_simple
 
6
  app = Flask(__name__)
7
  app.config["APPLICATION_ROOT"] = "/fm"
8
 
 
9
  SECRET_HINTS = ("TOKEN", "KEY", "SECRET", "PASSWORD", "AUTH", "COOKIE")
10
 
11
  def is_sensitive(name):
12
  up = name.upper()
13
  return any(h in up for h in SECRET_HINTS)
14
 
15
+ def mask_secrets(text):
16
+ # Guide rule 10: never surface real credential values in output.
17
+ out = []
18
+ for line in text.splitlines():
19
+ if any(h in line.upper() for h in SECRET_HINTS) and ("=" in line or ":" in line):
20
+ out.append("*** line masked (possible secret) ***")
21
+ else:
22
+ out.append(line)
23
+ return "\n".join(out)
24
+
25
+ PAGE = """<!doctype html><html><head><title>Box</title>
26
  <style>
27
+ body{{font-family:monospace;margin:20px;background:#0b0b0b;color:#eee}}
28
  a{{color:#29BEFD;text-decoration:none}} a:hover{{text-decoration:underline}}
29
+ table{{border-collapse:collapse;width:100%}} td,th{{padding:4px 8px;border-bottom:1px solid #222;text-align:left}}
30
+ .bar{{background:#1a1a1a;padding:10px;margin-bottom:10px;border-radius:6px}}
31
+ .tabs a{{margin-right:14px;font-weight:bold}}
32
+ textarea{{width:100%;background:#000;color:#47E6C1;border:1px solid #333;padding:8px}}
33
+ pre{{background:#000;color:#78D64B;padding:12px;border-radius:6px;overflow:auto;max-height:60vh}}
34
+ input[type=text],select{{background:#000;color:#eee;border:1px solid #333;padding:6px}}
35
+ button,input[type=submit]{{background:#F46821;color:#fff;border:0;padding:6px 14px;border-radius:4px;cursor:pointer}}
36
+ </style></head><body>
37
+ <div class="tabs bar"><a href="/fm/">📁 Files</a><a href="/fm/term">🖥️ Terminal</a>
38
+ <span style="color:#777">test mode · storage is ephemeral</span></div>
39
+ {body}</body></html>"""
40
+
41
+ # ---------------- TERMINAL ----------------
42
+ @app.route("/term", methods=["GET", "POST"])
43
+ def term():
44
+ cmd = ""
45
+ cwd = request.form.get("cwd", "/") if request.method == "POST" else "/"
46
+ output = ""
47
+ if request.method == "POST":
48
+ cmd = request.form.get("cmd", "")
49
+ # allow "cd" to persist working directory between commands
50
+ if cmd.strip().startswith("cd "):
51
+ target = cmd.strip()[3:].strip()
52
+ new = os.path.abspath(os.path.join(cwd, target)) if not target.startswith("/") else target
53
+ if os.path.isdir(new):
54
+ cwd, output = new, f"(cwd -> {new})"
55
+ else:
56
+ output = f"cd: no such directory: {new}"
57
+ elif cmd.strip():
58
+ try:
59
+ r = subprocess.run(cmd, shell=True, cwd=cwd, capture_output=True,
60
+ text=True, timeout=60)
61
+ output = (r.stdout or "") + (r.stderr or "")
62
+ if r.returncode != 0 and not output:
63
+ output = f"[exit code {r.returncode}]"
64
+ except subprocess.TimeoutExpired:
65
+ output = "[timed out after 60s]"
66
+ except Exception as e:
67
+ output = f"[error: {e}]"
68
+ output = mask_secrets(output)
69
+
70
+ body = f"""
71
+ <div class="bar"><b>cwd:</b> {html.escape(cwd)}</div>
72
+ <form method="post">
73
+ <input type="hidden" name="cwd" value="{html.escape(cwd)}">
74
+ <input type="text" name="cmd" placeholder="type a command e.g. ls -la, df -h, nproc"
75
+ style="width:80%" autofocus value="{html.escape(cmd)}">
76
+ <input type="submit" value="Run">
77
+ </form>
78
+ <div class="bar" style="margin-top:10px">Quick:
79
+ <a href="#" onclick="q('ls -la')">ls -la</a> ·
80
+ <a href="#" onclick="q('df -h')">df -h</a> ·
81
+ <a href="#" onclick="q('free -h')">free -h</a> ·
82
+ <a href="#" onclick="q('nproc')">nproc</a> ·
83
+ <a href="#" onclick="q('cat /etc/os-release')">os-release</a>
84
+ </div>
85
+ <pre>{html.escape(output)}</pre>
86
+ <script>
87
+ function q(c){{document.querySelector('[name=cmd]').value=c;
88
+ document.querySelector('form').submit();}}
89
+ </script>"""
90
+ return Response(PAGE.format(body=body))
91
 
92
+ # ---------------- FILE MANAGER ----------------
93
  @app.route("/")
94
  def browse():
95
+ path = os.path.abspath(request.args.get("path", "/"))
 
96
  if not os.path.exists(path):
97
  return Response(PAGE.format(body=f"<p>Not found: {html.escape(path)}</p>"), status=404)
 
98
  if os.path.isfile(path):
99
  return redirect(f"/fm/view?path={urllib.parse.quote(path)}")
100
 
 
101
  parent = os.path.dirname(path.rstrip("/")) or "/"
102
+ rows = f'<tr><td><a href="/fm/?path={urllib.parse.quote(parent)}">.. (up)</a></td><td></td><td></td></tr>'
103
  try:
104
  entries = sorted(os.listdir(path))
105
  except PermissionError:
106
  return Response(PAGE.format(body=f"<p>Permission denied: {html.escape(path)}</p>"), status=403)
107
 
108
  for name in entries:
109
+ full = os.path.join(path, name); q = urllib.parse.quote(full)
 
110
  try:
111
  size = os.path.getsize(full) if os.path.isfile(full) else ""
112
  except OSError:
 
126
  <div class="bar">
127
  <form action="/fm/mkdir" method="post" style="display:inline">
128
  <input type="hidden" name="path" value="{html.escape(path)}">
129
+ <input name="name" placeholder="new folder"><input type="submit" value="Create folder">
 
130
  </form>
131
  <form action="/fm/upload" method="post" enctype="multipart/form-data" style="display:inline">
132
  <input type="hidden" name="path" value="{html.escape(path)}">
133
+ <input type="file" name="file"><input type="submit" value="Upload">
 
134
  </form>
135
  </div>
136
+ <table><tr><th>Name</th><th>Size</th><th>Actions</th></tr>{rows}</table>"""
 
 
137
  return Response(PAGE.format(body=body))
138
 
139
  @app.route("/view")
 
146
  else:
147
  try:
148
  with open(path, "r", errors="replace") as f:
149
+ content = mask_secrets(f.read(200000))
150
  except Exception as e:
151
  content = f"[cannot read as text: {e}]"
152
  q = urllib.parse.quote(path)
 
157
  f"<pre>{html.escape(content)}</pre>")
158
  return Response(PAGE.format(body=body))
159
 
160
+ @app.route("/edit")
161
  def edit():
162
  path = os.path.abspath(request.args.get("path", ""))
163
  try:
 
169
  <div class="bar"><b>Editing:</b> {html.escape(path)}</div>
170
  <form action="/fm/save" method="post">
171
  <input type="hidden" name="path" value="{html.escape(path)}">
172
+ <textarea name="content" style="height:60vh">{html.escape(content)}</textarea>
173
+ <br><input type="submit" value="Save (live)">
174
  <a href="/fm/?path={urllib.parse.quote(os.path.dirname(path))}">cancel</a>
175
  </form>"""
176
  return Response(PAGE.format(body=body))
 
208
  path = os.path.abspath(request.args.get("path", ""))
209
  parent = os.path.dirname(path)
210
  try:
211
+ shutil.rmtree(path) if os.path.isdir(path) else os.remove(path)
 
 
 
212
  except Exception as e:
213
  return Response(PAGE.format(body=f"<p>Delete failed: {e}</p>"), status=500)
214
  return redirect(f"/fm/?path={urllib.parse.quote(parent)}")