diff --git a/SuperBeam/RequestHandlers.py b/SuperBeam/RequestHandlers.py new file mode 100644 index 0000000..72a01d7 --- /dev/null +++ b/SuperBeam/RequestHandlers.py @@ -0,0 +1,133 @@ +import os +import json + +import uuid + + +class BaseTextHandler(object): + mimetype = 'text/mime' + + @staticmethod + def handle(httpd): + raise Exception(f'Not implemented: Unable to handle {httpd} ' + '({BaseTextHandler.mimetype}') + + +class OctetStreamHandler(object): + mimetype = 'application/octet-stream' + + def handle(httpd): + raise Exception(f'Not implemented: Unable to handle {httpd} ' + '({StreamHandler.mimetype}') + + +class ChunkTemplateHandler(BaseTextHandler): + pass + + +class ThumbHandler(object): + mimetype = 'image/png' + + def handle(httpd): + raise Exception(f'Not implemented: Unable to handle {httpd} ' + '({ThumbHandler.mimetype}') + + +class LegacyListHandler(BaseTextHandler): + + def handle(httpd): + # files = filess['files'] + raise Exception(f'Not implemented: Unable to handle {httpd} ' + '({LegacyListHandler.mimetype}') + + +class JsonListHandler(object): + mimetype = 'application/json' + + def _make_jsonlist(files): + filestats = [] + totalsize = 0 + for file_ in files: + statinfo = os.stat(file_) + filename = os.path.basename(file_) + filepath = os.path.dirname(file_) + filestats.append({ + 'created': str(int(statinfo.st_ctime) * 1000), + 'modified': str(int(statinfo.st_mtime) * 1000), + 'name': filename, + 'path': filepath, + 'size': str(statinfo.st_size), + 'type': "Image" # TODO dynamic + }) + totalsize += statinfo.st_size + jsonlist = { + 'device': 'ANYONMOUS', + 'empty_dirs': [], + 'files': filestats, + 'uuid': str(uuid.uuid4()), + 'version': 4130 + } + return json.dumps(jsonlist), totalsize + + def handle(httpd): + jsonlist, totalsize = JsonListHandler._make_jsonlist(httpd.files) + httpd.send_response(200) + httpd.send_header('Content-Type', 'application/json') + httpd.end_headers() + + response = bytes(jsonlist, 'utf-8') + httpd.wfile.write(response) + httpd.total_file_size = totalsize + + +class SingleFileHandler(OctetStreamHandler): + pass + + +class ZipFileHandler(object): + mimetype = 'application/zip' + + def handle(httpd): + raise Exception('ZipFileHandler not implemeted.') + + +class SuperStreamHandler(OctetStreamHandler): + + @staticmethod + def _get_streamsize(files): + totalsize = 0 + for file_ in files: + statinfo = os.stat(file_) + totalsize += statinfo.st_size + return totalsize + + @staticmethod + def _stream_file_content(files): + for file_ in files: + with open(file_, 'rb') as f_handle: + yield f_handle.read() + + @staticmethod + def handle(httpd): + httpd.send_response(200) + httpd.send_header('ylfrettub', '1') + httpd.send_header('Content-Type', 'application/octet-stream') + httpd.send_header('Content-Length', + str(SuperStreamHandler._get_streamsize(httpd.files))) + httpd.send_header('Content-Disposition', 'attachment; ' + 'filename="RedHotChillyStream"') + httpd.end_headers() + for chunk in SuperStreamHandler._stream_file_content(httpd.files): + httpd.wfile.write(chunk) + + +class AssetHandler(OctetStreamHandler): + pass + + +class ApkRequestHandler(object): + mimetype = 'application/vnd.android.package-archive' + + def handle(httpd): + raise Exception(f'Not implemented: Unable to handle {httpd} ' + '({ApkRequestHandler.mimetype}') diff --git a/SuperBeam/__init__.py b/SuperBeam/__init__.py new file mode 100644 index 0000000..13db3ba --- /dev/null +++ b/SuperBeam/__init__.py @@ -0,0 +1,110 @@ +import os + +from http.server import BaseHTTPRequestHandler +import socketserver +import threading +import pyqrcode +import socket +import struct +import base64 + + +from SuperBeam.RequestHandlers import ( + ChunkTemplateHandler, ThumbHandler, ApkRequestHandler, LegacyListHandler, + SingleFileHandler, ZipFileHandler, SuperStreamHandler, AssetHandler, + JsonListHandler +) + + +class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): + pass + + +class SuperBeamServer(BaseHTTPRequestHandler): + + def do_GET(self): + # TODO Add globbing + print(threading.current_thread()) + paths = { + 'index': ChunkTemplateHandler, + '/': ChunkTemplateHandler, + '/index.htm': ChunkTemplateHandler, # TODO handler2 + '/getthumb': ThumbHandler, + '/light': ChunkTemplateHandler, # TODO handler2 + '/superlist': LegacyListHandler, + '/jsonlist': JsonListHandler, + '/get/': SingleFileHandler, + '/getapk': ApkRequestHandler, + '/getzip': ZipFileHandler, + '/getstream': SuperStreamHandler, + '*': AssetHandler, + } + + if self.path in paths: # TODO startswith key check + self.respond(paths[self.path]) + + def respond(self, handler): + print('='*30 + ' DEBUG ' + '='*30) + print('CONNECTION:', self.connection) + print('HEADERS :', self.headers) + print('REQUEST :', self.request) + print('REQUEST* :', self.requestline) + print('PATH :', self.path) + print('COMMAND :', self.command) + print('HANDLER :', handler) + print('='*30 + ' DEBUG ' + '='*30) + handler.handle(self) + + +def get_primary_ip(): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + # doesn't even have to be reachable + s.connect(('10.255.255.255', 1)) + IP = s.getsockname()[0] + except Exception as exc: + print(exc) + IP = '127.0.0.1' + finally: + s.close() + return IP + + +def show_qrcode(): + primary_ip = get_primary_ip() + octets = [1] + octets.extend([int(_) for _ in primary_ip.split('.')[::-1]]) + url = 'http://superbe.am/q?' + base64.b64encode( + struct.pack('BBBBB', *octets) + ).decode('utf-8') + qrcode = pyqrcode.create(url) + print('\033[1;37;37m████████' + '██'*(len(qrcode.code[0]))) + for row in qrcode.code: + print('\033[1;37;37m████', end='') + for block in row: + if block == 1: + print('\033[1;30;30m ', end='') + else: + print('\033[1;37;37m██', end='') + print('\033[1;37;37m████') + print('\033[1;37;37m████████' + '██'*(len(qrcode.code[0]))) + + +def build_filelist(paths, recursive=True): + filelist = [] + for path in paths: + if os.path.isdir(path) and recursive: + for root, dirs, files in os.walk(path): + for file_ in files: + filelist.append(os.path.join(root, file_)) + else: + filelist.append(path) + return filelist + + +def serve_forever(files): + Handler = SuperBeamServer + Handler.files = files + with socketserver.ThreadingTCPServer(('', 8080), Handler) as httpd: + show_qrcode() + httpd.serve_forever() diff --git a/sbeam.py b/sbeam.py old mode 100644 new mode 100755 index a99a01b..8843d73 --- a/sbeam.py +++ b/sbeam.py @@ -7,82 +7,70 @@ # Distributed under terms of the MIT license. """ +> GET /getstream HTTP/1.1 +> Host: 192.168.178.50:8080 +> User-Agent: sbeam +> Accept: */* +> +* Mark bundle as not supporting multiuse +< HTTP/1.1 200 OK +< ylfrettub: 1 +< Content-Disposition: attachment; filename="RedHotChillyStream" +< Date: Fri, 09 Aug 2019 12:57:20 GMT +< Content-Length: 8137967 +< Content-Type: application/octet-stream +Server QR Code = http://superbe.am/q?ATKyqMA= +Base64 = 01 32 b2 a8 c0 = 01 50 178 168 192 """ import os +import click import requests -import pyqrcode -import socket -import struct -import base64 - -# from http.server import HTTPServer, BaseHTTPRequestHandler -import http.server -import socketserver +import SuperBeam -def get_primary_ip(): - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - try: - # doesn't even have to be reachable - s.connect(('10.255.255.255', 1)) - IP = s.getsockname()[0] - except Exception as exc: - print(exc) - IP = '127.0.0.1' - finally: - s.close() - return IP +@click.group() +def cli(): + pass -def show_qrcode(): - primary_ip = get_primary_ip() - octets = [1] - octets.extend([int(_) for _ in primary_ip.split('.')[::-1]]) - url = 'http://superbe.am/q?' + base64.b64encode( - struct.pack('BBBBB', *octets) - ).decode('utf-8') - qrcode = pyqrcode.create(url) - print('\033[1;37;37m████████' + '██'*(len(qrcode.code[0]))) - for row in qrcode.code: - print('\033[1;37;37m████', end='') - for block in row: - if block == 1: - print('\033[1;30;30m ', end='') - else: - print('\033[1;37;37m██', end='') - print('\033[1;37;37m████') - print('\033[1;37;37m████████' + '██'*(len(qrcode.code[0]))) +@cli.command() +@click.option('--recursive', default=True, is_flag=True, + help='Recurse into sub directories') +@click.argument('filename', nargs=-1, type=click.Path(exists=True)) +def serve(recursive, filename): + filelist = SuperBeam.build_filelist(filename, recursive) + SuperBeam.serve_forever(filelist) -def serve_files(): - show_qrcode() - Handler = http.server.SimpleHTTPRequestHandler - with socketserver.TCPServer(('', 8081), Handler) as httpd: - httpd.serve_forever() - - -def get_superlist(): - with requests.get('http://192.168.178.50:8080/superlist', +@cli.command() +@click.argument('host', default='127.0.0.1') +def superlist(host): + with requests.get(f'http://{host}:8080/superlist', headers={'User-Agent': 'sbeam'}) as response: assert response.status_code == 200 return response.text.split('\n') -def save_stream(filename): - with requests.get('http://192.168.178.50:8080/getstream', stream=True, +@cli.command() +@click.argument('host', default='127.0.0.1') +@click.argument('filename', default='received.bin', type=click.File('w')) +def receive(host, filename): + with requests.get(f'http://{host}:8080/getstream', stream=True, headers={'User-Agent': 'sbeam'}) as response: assert response.status_code == 200 with open(filename, 'wb') as handle: - # length = response.headers.get('Content-length') - # print(length) for chunk in response.iter_content(chunk_size=8196): handle.write(chunk) -def split_stream(data, directory, superlist): +@cli.command() +@click.argument('filename', default='received.bin', type=click.File('r')) +@click.argument('superlist', default='superlist', type=click.File('r')) +@click.argument('directory', default='received', type=click.File('w')) +def split_stream(data, superlist, directory): with open(data, 'rb') as blob: for item in superlist: if not item: @@ -96,7 +84,7 @@ def split_stream(data, directory, superlist): if __name__ == '__main__': - serve_files() + cli() # to_download = 0 # superlist = get_superlist() # try: