-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsharedir.py
317 lines (275 loc) · 9.52 KB
/
sharedir.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
import os
import socket
import qrcode
from flask import (
Flask,
send_file,
send_from_directory,
request,
abort,
jsonify,
render_template_string,
)
import diceware
from argparse import ArgumentParser, Namespace
from time import time
# Dictionary to store failed attempts
failed_attempts = {}
blacklist = {}
# Thresholds
MAX_FAILED_ATTEMPTS = 10
TIME_WINDOW = 60 # 1 minute
BLACKLIST_DURATION = 4 * 60 * 60 # 4 hours in seconds
def generate_passphrase(num_words):
# Create an options object
options = Namespace()
options.num = num_words
options.delimiter = "-"
options.specials = False
options.caps = False
options.randomsource = "system"
options.infile = None
options.wordlist = ["en"]
# Generate the passphrase
passphrase = diceware.get_passphrase(options)
return passphrase
def get_lan_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
# Connect to an external IP address to get the LAN IP
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
except Exception:
ip = "127.0.0.1"
finally:
s.close()
return ip
# checking if IP is blacklisted and removing old entries
def is_blacklisted(ip):
if ip in blacklist:
if time() < blacklist[ip]:
return True
else:
del blacklist[ip]
return False
def log_failed_attempt(ip):
# Log failed attempt with a timestamp
now = time()
if ip not in failed_attempts:
failed_attempts[ip] = []
# Removing old entries
failed_attempts[ip] = [
attempt for attempt in failed_attempts[ip] if now - attempt < TIME_WINDOW
]
# Add current attempt
failed_attempts[ip].append(now)
# Check if it exceeds the max allowed attempts
if len(failed_attempts[ip]) >= MAX_FAILED_ATTEMPTS:
blacklist[ip] = now + BLACKLIST_DURATION
del failed_attempts[ip]
def create_http_server(path, passphrase):
app = Flask(__name__)
is_file = os.path.isfile(path)
@app.route("/", defaults={"req_path": ""})
@app.route("/<path:req_path>")
def dir_listing(req_path):
ip = request.remote_addr
if is_blacklisted(ip):
print(f"IP {ip} is blacklisted.")
return jsonify({
"error": "Too many failed attempts. You are blacklisted for 4 hours."
}), 403
if request.args.get("passphrase") != passphrase:
if request.args.get("passphrase") is not None:
log_failed_attempt(ip)
print(
"Incorrect passphrase. The correct passphrase is:",
passphrase,
"AND NOT",
request.args.get("passphrase"),
)
abort(403)
if is_file:
# Serve the single file
if req_path and req_path != os.path.basename(path):
return jsonify({"error": "Path not found"}), 404
return send_file(path)
else:
# Serve the directory listing
abs_path = os.path.join(path, req_path)
if not os.path.exists(abs_path):
return jsonify({"error": "Path not found"}), 404
if os.path.isfile(abs_path):
return send_from_directory(path, req_path)
# Split files and directories into separate lists
items = os.listdir(abs_path)
directories = sorted([
item for item in items if os.path.isdir(os.path.join(abs_path, item))
])
files = sorted([
item for item in items if os.path.isfile(os.path.join(abs_path, item))
])
return render_template_string(
"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Directory Listing</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
color: #333;
margin: 0;
padding: 20px;
transition: background-color 0.3s, color 0.3s;
}
h1 {
background-color: #3498db;
color: white;
padding: 10px;
border-radius: 5px;
}
ul {
list-style-type: none;
padding-left: 0;
}
li {
margin: 10px 0;
font-size: 18px;
}
.file {
color: #2980b9;
}
.folder {
color: #27ae60;
font-weight: bold;
}
.icon {
margin-right: 10px;
}
a {
text-decoration: none;
color: #2980b9; /* Default blue color for links */
}
a:hover {
text-decoration: underline;
}
.dark-mode {
background-color: #333;
color: #f4f4f4;
}
.dark-mode h1 {
background-color: #555;
}
.dark-mode a {
color: #1abc9c; /* Light blue-green for dark mode */
}
#dark-mode-toggle {
position: fixed;
top: 20px;
right: 20px;
background-color: #000000;
color: white;
border: none;
padding: 10px;
border-radius: 5px;
cursor: pointer;
}
</style>
</head>
<body>
<!-- Dark Mode Toggle Button -->
<button id="dark-mode-toggle">🌒</button>
<!-- Header -->
<h1>Directory listing {% if req_path %} for {{ req_path }} {% endif %}</h1>
<!-- Directory Listing -->
<ul>
<!-- Display directories first -->
{% for directory in directories %}
<li>
<span class="icon">📁</span>
<a class="folder" href="{{ '/' + req_path + '/' + directory if req_path else '/' + directory }}?passphrase={{ passphrase }}">{{ directory }}</a>
</li>
{% endfor %}
<!-- Display files after directories -->
{% for file in files %}
<li>
<span class="icon">📄</span>
<a class="file" href="{{ '/' + req_path + '/' + file if req_path else '/' + file }}?passphrase={{ passphrase }}">{{ file }}</a>
</li>
{% endfor %}
</ul>
<!-- JavaScript for Dark Mode Toggle -->
<script>
const toggleButton = document.getElementById('dark-mode-toggle');
const body = document.body;
// Check if dark mode is already enabled in local storage
if (localStorage.getItem('dark-mode') === 'enabled') {
body.classList.add('dark-mode');
}
// Toggle dark mode
toggleButton.addEventListener('click', function() {
body.classList.toggle('dark-mode');
// Store the dark mode setting in local storage
if (body.classList.contains('dark-mode')) {
localStorage.setItem('dark-mode', 'enabled');
} else {
localStorage.removeItem('dark-mode');
}
});
</script>
</body>
</html>
""",
req_path=req_path,
directories=directories,
files=files,
passphrase=passphrase,
)
return app
def display_qr_code(url):
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(url)
qr.make(fit=True)
# img = qr.make_image(fill="black", back_color="white")
qr.print_ascii()
def main():
parser = ArgumentParser(description="Share a directory or file over HTTP.")
parser.add_argument(
"path",
help="Path to the directory or file to be shared (relative or absolute).",
)
parser.add_argument(
"-p",
"--passphrase-length",
type=int,
default=4,
help="Number of words in the passphrase (default: 4).",
)
args = parser.parse_args()
# Resolving absolute path
shared_path = os.path.abspath(args.path)
if not os.path.exists(shared_path):
print(f"The provided path '{shared_path}' does not exist.")
exit(1)
passphrase = generate_passphrase(args.passphrase_length)
print(f"Generated passphrase: {passphrase}")
ip_address = get_lan_ip() # Detect LAN IP address
http_port = 44447
url = f"http://{ip_address}:{http_port}/?passphrase={passphrase}"
print(f"Access URL: {url}")
# Display the QR codel
display_qr_code(url)
# Create and start the HTTP server
http_server = create_http_server(shared_path, passphrase)
http_server.run(host="0.0.0.0", port=http_port)
if __name__ == "__main__":
main()