-
Notifications
You must be signed in to change notification settings - Fork 0
/
rweb.py
258 lines (220 loc) · 10.4 KB
/
rweb.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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import argparse
import yaml
import sys
from flask import Flask, render_template_string, send_from_directory, abort, request
# Fallback-Values for the Flask App
FALLBACK_ALLOWED_IPS = ['127.0.0.1'] # Default: Only allow localhost
FALLBACK_HTML_PATH = 'index.html' # Default: Display index.html
FALLBACK_PORT = 8080 # Default: Run on port 5000 (HTTP)
FALLBACK_LISTEN = '0.0.0.0' # Default: Listen on all interfaces
FALLBACK_DIRECTORY = '/' # Default: Serve files from root directory
FALLBACK_STATIC_DIR = 'static' # Default: Serve static files from 'static' directory
FALLBACK_MESSAGE = 'Hello World!' # Default message to display if no HTML file is provided
USER_CONFIG_PATH = os.path.expanduser('~/.rweb/config.yaml') # Default path in user's home directory
VERSION = "1.1.10"
# available colors
colors = {
"green": "\033[32m",
"red": "\033[31m",
"blue": "\033[34m",
"yellow": "\033[33m",
"magenta": "\033[35m",
"cyan": "\033[36m",
"white": "\033[37m",
"light_grey": "\033[90m",
"dark_grey": "\033[30m",
"light_red": "\033[91m",
"light_green": "\033[92m",
"light_yellow": "\033[93m",
"light_blue": "\033[94m",
"light_magenta": "\033[95m",
"light_cyan": "\033[96m",
"black": "\033[30m",
}
reset = "\033[0m" # reset to the default color
default_color="{colors['light_blue']}"
# Function to print the banner
def print_banner():
print(f"{colors['light_red']}")
print(r'''
________ ________ ___ ________ ________
|\ __ \ |\ ___ \ |\ \ |\ _____\|\ _____\
\ \ \|\ \\ \ \_|\ \\ \ \\ \ \__/ \ \ \__/
\ \ ____\\ \ \ \\ \\ \ \\ \ __\ \ \ __\
\ \ \___| \ \ \_\\ \\ \ \\ \ \_| \ \ \_|
\ \__\ \ \_______\\ \__\\ \__\ \ \__\
\|__| \|_______| \|__| \|__| \|__|
''')
print(f"{colors['blue']} Robert Tulke [[email protected]] pdiff {VERSION}{reset}")
print()
# Function to parse command line arguments
def parse_args():
formatter = lambda prog: argparse.HelpFormatter(prog, max_help_position=60)
parser = argparse.ArgumentParser(formatter_class=formatter, description='Flask App to display an HTML file')
parser.add_argument('-p', '--path', type=str, help='Path to the HTML file to display')
parser.add_argument('-P', '--port', type=int, help='Port to run the Flask server on')
parser.add_argument('-i', '--ips', nargs='+', help='List of allowed IP addresses')
parser.add_argument('-L', '--listen', type=str, help='IP address to listen on (default: 0.0.0.0)')
parser.add_argument('-c', '--config', type=str, default=None, help='Path to the config file')
parser.add_argument('-G', '--generate-config', action='store_true', help='Generate a config.yaml file with the current settings')
parser.add_argument('-D', '--directory', type=str, help='Default directory to serve files from')
parser.add_argument('-S', '--static-dir', type=str, help='Directory to serve static files from (e.g., images)')
parser.add_argument('-l', '--list-config', action='store_true', help='List the current config.yaml file content and exit')
parser.add_argument('-v', '--version', action='store_true', help='Show version')
return parser.parse_args()
# Function to load the config.yaml file
def load_config(config_file):
if os.path.exists(config_file):
with open(config_file, 'r') as file:
return yaml.safe_load(file)
return {}
# Function to check if the config file exists in user directory or current working directory
def get_config_path():
# Check user directory first
if os.path.exists(USER_CONFIG_PATH):
return USER_CONFIG_PATH
# Check current working directory for .rweb/config.yaml
current_dir_config_path = os.path.join(os.getcwd(), '.rweb/config.yaml')
if os.path.exists(current_dir_config_path):
return current_dir_config_path
return USER_CONFIG_PATH # Fallback to user directory if none found
# Function to generate the config.yaml file in the current working directory
def generate_config_file(ips, path, port, listen, directory, static_dir):
current_dir_config_dir = os.path.join(os.getcwd(), '.rweb')
# Create the .rweb directory if it doesn't exist
if not os.path.exists(current_dir_config_dir):
os.makedirs(current_dir_config_dir)
config_file = os.path.join(current_dir_config_dir, 'config.yaml')
# Check if the config file already exists and ask the user if they want to overwrite it
if os.path.exists(config_file):
overwrite = input(f"The file '{config_file}' already exists. Do you want to overwrite it? (Y/n): ")
if overwrite.lower() not in ['y', 'yes', '']:
print("Operation cancelled. The config file was not overwritten.")
return
config_data = {
'allowed_ips': ips,
'html_path': path,
'port': port,
'listen': listen,
'default_message': 'Hello World!',
'default_directory': directory,
'static_directory': static_dir,
'logging': False,
'logfile': ''
}
with open(config_file, 'w') as file:
yaml.dump(config_data, file)
print(f"Config file '{config_file}' generated successfully.")
# Function to list the current config.yaml file content
def list_config(config_file):
if os.path.exists(config_file):
with open(config_file, 'r') as file:
config_data = yaml.safe_load(file)
print("Current configuration:")
print(yaml.dump(config_data, default_flow_style=False))
else:
print(f"No config file found at {config_file}")
# Function to initialize logging
def init_logging(config):
logging_enabled = config.get('logging', False)
logfile = config.get('logfile', '')
if logging_enabled and logfile:
logging.basicConfig(
filename=logfile,
level=logging.INFO,
format='%(asctime)s - %(message)s'
)
print(f"Logging enabled. Writing logs to {logfile}")
else:
print("Logging not enabled or logfile not provided.")
# Limit the remote address to the allowed IPs from the config file or command line
def limit_remote_addr():
if allowed_ips:
client_ip = request.remote_addr
if client_ip not in allowed_ips:
abort(403) # Verboten
if logging.getLogger().hasHandlers():
logging.info(f"Incoming connection from {client_ip}")
# User-defined error page for 403 Forbidden
def forbidden(e):
client_ip = request.remote_addr
return render_template_string(f'''
<h1>Forbidden</h1>
<p>Your IP address {client_ip} is not allowed to access this resource.</p>
<p>Please contact the server administrator to add your IP address to the allowed list in the config.yaml file.</p>
'''), 403
# User-defined error page for 404 Not Found
def not_found(e):
return render_template_string(f'''
<h1>File Not Found</h1>
<p>The file you requested could not be found on the server.</p>
<p>Requested file: {html_path}</p>
<p>Please check the path and try again.</p>
'''), 404
# Main function to run the Flask app
if __name__ == '__main__':
# Parse the command line arguments
args = parse_args()
# Print the banner and exit
if args.version:
print_banner()
sys.exit(0)
# Determine the correct config file path
config_file = args.config if args.config else get_config_path()
# config_file = args.config if args.config else FALLBACK_CONFIG
# Load the configuration from the config file
config = load_config(config_file)
# Show the current config file content and exit
if args.list_config:
list_config(config_file)
sys.exit(0)
# Initialize logging
init_logging(config)
# Set the configuration values from the command line arguments or the config file
allowed_ips = args.ips if args.ips else config.get('allowed_ips', FALLBACK_ALLOWED_IPS)
html_path = args.path if args.path else config.get('html_path', FALLBACK_HTML_PATH)
port = args.port if args.port else config.get('port', FALLBACK_PORT)
listen = args.listen if args.listen else config.get('listen', FALLBACK_LISTEN)
default_directory = args.directory if args.directory else config.get('default_directory', FALLBACK_DIRECTORY)
static_dir = args.static_dir if args.static_dir else config.get('static_directory', FALLBACK_STATIC_DIR)
default_message = config.get('default_message', FALLBACK_MESSAGE)
# If the user wants to generate a config file, do it and exit
if not args.generate_config:
if os.path.exists(config_file):
print(f"Loaded configuration from: {config_file}")
else:
print("No configuration file loaded. Using fallback values:")
print(f"Allowed IPs: {allowed_ips}")
print(f"HTML Path: {html_path}")
print(f"Port: {port}")
print(f"Listen: {listen}")
print(f"Default Directory: {default_directory}")
print(f"Static Directory: {static_dir}")
print(f"Default Message: {default_message}")
# Check if the user is trying to bind to a port below 1024 without root privileges
if port < 1024 and os.geteuid() != 0:
print("Error: You must run this script as root to bind to ports below 1024.")
sys.exit(1)
# Start the Flask app
if args.generate_config:
generate_config_file(allowed_ips, html_path, port, listen, default_directory, static_dir)
else:
app = Flask(__name__, static_folder=static_dir)
app.before_request(limit_remote_addr)
app.errorhandler(403)(forbidden)
app.errorhandler(404)(not_found)
# Serve static files from the 'static' directory
@app.route('/')
def show_page():
if html_path:
directory, filename = os.path.split(html_path)
if os.path.exists(html_path):
return send_from_directory(directory, filename)
else:
abort(404)
else:
return render_template_string(default_message)
app.run(debug=True, port=port, host=listen)