simpleserver improvements.

- Reference self.server.password instead of fowarding the password
  to the request handler.
- Add --enable_zip.
- Remove unused stuff.
master
voussoir 2021-01-23 18:37:22 -08:00
parent 11660a2dcd
commit 4b599e38d3
No known key found for this signature in database
GPG Key ID: 5F7554F8C26DACCB
1 changed files with 30 additions and 56 deletions

View File

@ -3,21 +3,16 @@ import base64
import cgi import cgi
import http.cookies import http.cookies
import http.server import http.server
import math
import mimetypes import mimetypes
import os import os
import pathlib
import random
import socketserver
import sys import sys
import threading
import types import types
import urllib.parse import urllib.parse
import zipstream import zipstream
# pip install voussoirkit
from voussoirkit import betterhelp from voussoirkit import betterhelp
from voussoirkit import bytestring from voussoirkit import bytestring
from voussoirkit import passwordy
from voussoirkit import pathclass from voussoirkit import pathclass
from voussoirkit import ratelimiter from voussoirkit import ratelimiter
@ -61,10 +56,7 @@ TOKEN_COOKIE_NAME = 'simpleserver_token'
# SERVER ########################################################################################### # SERVER ###########################################################################################
class RequestHandler(http.server.BaseHTTPRequestHandler): class RequestHandler(http.server.BaseHTTPRequestHandler):
def __init__(self, *args, password=None, accepted_tokens=None, accepted_ips=None, **kwargs): def __init__(self, *args, **kwargs):
self.accepted_tokens = accepted_tokens
self.accepted_ips = accepted_ips
self.password = password
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@property @property
@ -99,25 +91,25 @@ class RequestHandler(http.server.BaseHTTPRequestHandler):
return self.request.getpeername()[0] return self.request.getpeername()[0]
def check_password(self, attempt): def check_password(self, attempt):
if self.password is None: if self.server.password is None:
return True return True
if attempt == self.password: if attempt == self.server.password:
return True return True
return False return False
def check_has_password(self): def check_has_password(self):
if self.password is None: if self.server.password is None:
return True return True
if self.auth_header == self.password: if self.auth_header == self.server.password:
return True return True
if self.accepted_tokens is not None and self.auth_cookie in self.accepted_tokens: if self.server.accepted_tokens is not None and self.auth_cookie in self.server.accepted_tokens:
return True return True
if self.accepted_ips is not None and self.remote_addr in self.accepted_ips: if self.server.accepted_ips is not None and self.remote_addr in self.server.accepted_ips:
return True return True
return False return False
@ -159,7 +151,6 @@ class RequestHandler(http.server.BaseHTTPRequestHandler):
helper = lambda x: int(x) if x and x.isdigit() else None helper = lambda x: int(x) if x and x.isdigit() else None
if '-' in desired_range: if '-' in desired_range:
(desired_min, desired_max) = desired_range.split('-') (desired_min, desired_max) = desired_range.split('-')
#print('desire', desired_min, desired_max)
range_min = helper(desired_min) range_min = helper(desired_min)
range_max = helper(desired_max) range_max = helper(desired_max)
else: else:
@ -195,10 +186,10 @@ class RequestHandler(http.server.BaseHTTPRequestHandler):
elif path.is_dir: elif path.is_dir:
headers['Content-type'] = 'text/html' headers['Content-type'] = 'text/html'
response = generate_opendir(path) response = generate_opendir(path, enable_zip=self.server.enable_zip)
response = response.encode('utf-8') response = response.encode('utf-8')
elif self.path.endswith('.zip'): elif self.path.endswith('.zip') and self.server.enable_zip:
path = url_to_path(self.path.rsplit('.zip', 1)[0]) path = url_to_path(self.path.rsplit('.zip', 1)[0])
headers['Content-type'] = 'application/octet-stream' headers['Content-type'] = 'application/octet-stream'
download_as = urllib.parse.quote(path.basename) download_as = urllib.parse.quote(path.basename)
@ -260,14 +251,14 @@ class RequestHandler(http.server.BaseHTTPRequestHandler):
if self.check_password(attempt): if self.check_password(attempt):
self.send_response(302) self.send_response(302)
if self.accepted_tokens is not None: if self.server.accepted_tokens is not None:
cookie = http.cookies.SimpleCookie() cookie = http.cookies.SimpleCookie()
token = random_hex(32) token = passwordy.random_hex(32)
cookie[TOKEN_COOKIE_NAME] = token cookie[TOKEN_COOKIE_NAME] = token
self.accepted_tokens.add(token) self.server.accepted_tokens.add(token)
self.send_header('Set-Cookie', cookie.output(header='', sep='')) self.send_header('Set-Cookie', cookie.output(header='', sep=''))
if self.accepted_ips is not None: if self.server.accepted_ips is not None:
self.accepted_ips.add(self.remote_addr) self.server.accepted_ips.add(self.remote_addr)
self.send_header('Location', goto) self.send_header('Location', goto)
else: else:
@ -287,10 +278,12 @@ class RequestHandler(http.server.BaseHTTPRequestHandler):
return False return False
class SimpleServer: class SimpleServer:
def __init__(self, port, password, authorize_by_ip): def __init__(self, port, password, authorize_by_ip, enable_zip):
self.port = port self.port = port
self.password = password self.password = password
self.authorize_by_ip = authorize_by_ip self.authorize_by_ip = authorize_by_ip
self.enable_zip = enable_zip
if authorize_by_ip: if authorize_by_ip:
self.accepted_ips = set() self.accepted_ips = set()
self.accepted_tokens = None self.accepted_tokens = None
@ -298,14 +291,8 @@ class SimpleServer:
self.accepted_tokens = set() self.accepted_tokens = set()
self.accepted_ips = None self.accepted_ips = None
def make_request_handler(self, *args, **kwargs): def make_request_handler(self, request, client_info, _server):
return RequestHandler( return RequestHandler(request, client_info, self)
password=self.password,
accepted_tokens=self.accepted_tokens,
accepted_ips=self.accepted_ips,
*args,
**kwargs,
)
def start(self): def start(self):
server = http.server.ThreadingHTTPServer(('0.0.0.0', self.port), self.make_request_handler) server = http.server.ThreadingHTTPServer(('0.0.0.0', self.port), self.make_request_handler)
@ -330,12 +317,9 @@ def atag(path, display_name=None):
# Folder emoji # Folder emoji
icon = '\U0001F4C1' icon = '\U0001F4C1'
else: else:
# Diamond emoji
#icon = '\U0001F48E'
# Gift emoji # Gift emoji
icon = '\U0001F381' icon = '\U0001F381'
#print('anchor', path)
if display_name.endswith('.placeholder'): if display_name.endswith('.placeholder'):
a = '<a>{icon} {display}</a>' a = '<a>{icon} {display}</a>'
else: else:
@ -347,7 +331,7 @@ def atag(path, display_name=None):
) )
return a return a
def generate_opendir(path): def generate_opendir(path, enable_zip):
try: try:
path.correct_case() path.correct_case()
items = path.listdir() items = path.listdir()
@ -387,7 +371,7 @@ def generate_opendir(path):
table_rows.append(entry) table_rows.append(entry)
shaded = not shaded shaded = not shaded
if len(items) > 0: if len(items) > 0 and enable_zip:
entry = table_row(path.replace_extension('.zip'), display_name='zip', shaded=shaded) entry = table_row(path.replace_extension('.zip'), display_name='zip', shaded=shaded)
shaded = not shaded shaded = not shaded
table_rows.append(entry) table_rows.append(entry)
@ -396,21 +380,7 @@ def generate_opendir(path):
text = OPENDIR_TEMPLATE.format(table_rows=table_rows) text = OPENDIR_TEMPLATE.format(table_rows=table_rows)
return text return text
def generate_random_filename(original_filename='', length=8):
import random
bits = length * 44
bits = random.getrandbits(bits)
identifier = '{:x}'.format(bits).rjust(length, '0')
return identifier
def random_hex(length=12):
randbytes = os.urandom(math.ceil(length / 2))
token = ''.join('{:02x}'.format(x) for x in randbytes)
token = token[:length]
return token
def read_filebytes(path, range_min=None, range_max=None): def read_filebytes(path, range_min=None, range_max=None):
#print(path)
if range_min is None: if range_min is None:
range_min = 0 range_min = 0
@ -419,7 +389,6 @@ def read_filebytes(path, range_min=None, range_max=None):
range_span = range_max - range_min range_span = range_max - range_min
#print('read span', range_min, range_max, range_span)
f = path.open('rb') f = path.open('rb')
f.seek(range_min) f.seek(range_min)
sent_amount = 0 sent_amount = 0
@ -431,7 +400,6 @@ def read_filebytes(path, range_min=None, range_max=None):
yield chunk yield chunk
sent_amount += len(chunk) sent_amount += len(chunk)
#print('I read', len(fr))
f.close() f.close()
def table_row(path, display_name=None, shaded=False): def table_row(path, display_name=None, shaded=False):
@ -449,7 +417,7 @@ def table_row(path, display_name=None, shaded=False):
bg=bg, bg=bg,
anchor=anchor, anchor=anchor,
size=size, size=size,
) )
return row return row
def path_to_url(path): def path_to_url(path):
@ -475,7 +443,7 @@ def zip_directory(path):
return zipfile return zipfile
# COMMAND LINE ################################################################################################### # COMMAND LINE #####################################################################################
DOCSTRING = ''' DOCSTRING = '''
simpleserver simpleserver
@ -498,6 +466,10 @@ flags:
all future requests. This reduces security, because a single IP can be home all future requests. This reduces security, because a single IP can be home
to many different people, but increases convenience, because the user can to many different people, but increases convenience, because the user can
use download managers / scripts where adding auth is not convenient. use download managers / scripts where adding auth is not convenient.
--enable_zip:
Add a 'zip' link to every directory and allow the user to download the
entire directory as a zip file.
''' '''
def simpleserver_argparse(args): def simpleserver_argparse(args):
@ -505,6 +477,7 @@ def simpleserver_argparse(args):
port=args.port, port=args.port,
password=args.password, password=args.password,
authorize_by_ip=args.authorize_by_ip, authorize_by_ip=args.authorize_by_ip,
enable_zip=args.enable_zip,
) )
server.start() server.start()
@ -514,6 +487,7 @@ def main(argv):
parser.add_argument('port', nargs='?', type=int, default=40000) parser.add_argument('port', nargs='?', type=int, default=40000)
parser.add_argument('--password', dest='password', default=None) parser.add_argument('--password', dest='password', default=None)
parser.add_argument('--authorize_by_ip', '--authorize-by-ip', dest='authorize_by_ip', action='store_true') parser.add_argument('--authorize_by_ip', '--authorize-by-ip', dest='authorize_by_ip', action='store_true')
parser.add_argument('--enable_zip', '--enable-zip', dest='enable_zip', action='store_true')
parser.set_defaults(func=simpleserver_argparse) parser.set_defaults(func=simpleserver_argparse)
return betterhelp.single_main(argv, parser, DOCSTRING) return betterhelp.single_main(argv, parser, DOCSTRING)