Last active
October 31, 2020 04:54
-
-
Save jjdelc/4ce30477d6ab5a2711c272d4269e5066 to your computer and use it in GitHub Desktop.
Direct upload/download between devices
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Run this to serve a temporary upload/download page from your computer. | |
I use this to transfer files directly between my phone and any of my PCs. The | |
benefit over Dropbox or Syncthings is that this is direct and immediate. | |
No fuzz, no installation, no usernames. Just directly deposit the file. I | |
haven't tested this for very large files. | |
""" | |
import os | |
import ssl | |
import sys | |
import cgi | |
import base64 | |
import os.path | |
from io import BytesIO | |
from urllib.parse import unquote | |
from http.server import HTTPServer, BaseHTTPRequestHandler | |
port = sys.argv[1] if len(sys.argv) > 2 else 4444 | |
serving_path = sys.argv[2] if len(sys.argv) == 3 else os.getcwd() | |
listen = ("0.0.0.0", int(port)) | |
FAVICON = b"""<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96"><g transform="translate(0 -956.362)" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><path stroke-width="25" d="M.977 957.343h96v96h-96z"/><rect width="39.724" height="39.724" x="28.138" y="984.5" rx="39.724" stroke-width="15"/></g></svg>""" | |
HTML_HEAD = b"""<!doctype html> | |
<html> | |
<head> | |
<title>To PC</title> | |
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1" /> | |
<style> | |
html {font-family: sans-serif;} | |
ol {list-style: none; margin: 0;padding: 0; border: 1px solid #DDD;} | |
li:nth-child(2n) {background: #EEE;} | |
a {display: block; line-height: 200%; padding-left: 0.5rem;} | |
h1 {font-size: 1.5rem;} | |
</style> | |
</head> | |
<body> | |
<ol> | |
""" | |
HTML_FOOT = b""" | |
</ol> | |
<h1>Upload</h1> | |
<form method="post" action="/upload/" enctype="multipart/form-data"> | |
<input type="file" name="file"/><input type="submit" value="Upload"/> | |
</form> | |
</body> | |
</html> | |
""" | |
def read_files(): | |
this_script = __file__.replace("./", "") | |
files = [f for f in os.listdir(serving_path) if f != this_script] | |
files = [f for f in files if os.path.isfile(f)] | |
files = sorted(files) | |
return files | |
def index(path): | |
out = BytesIO() | |
out.write(HTML_HEAD) | |
mask = """<li><a href="/download/{fn}" download="{fn}">{fn}</a></li>""" | |
files = read_files() | |
lines = "\n".join([mask.format(fn=fn) for fn in files]) | |
out.write(lines.encode("utf-8")) | |
out.write(HTML_FOOT) | |
out.seek(0) | |
return out, [("Content-type", "text/html")] | |
def download(path): | |
out = BytesIO() | |
fn = path.split("/")[-1] | |
fn = unquote(fn) | |
fn = fn.replace("..", "") # Securitay? | |
filename = os.path.join(serving_path, fn) | |
size = os.path.getsize(filename) | |
out = open(filename, "rb") | |
base_fn = os.path.basename(filename) | |
headers = [ | |
("Content-type", "octet/stream"), | |
("Content-length", str(size)), | |
("Content-disposition", "attachment; filename={}".format(base_fn)) | |
] | |
return out, headers | |
def favicon(path): | |
out = BytesIO(FAVICON) | |
#return out, [("Content-Type", "image/x-icon")] | |
return out, [("Content-type", "image/svg+xml")] | |
def manifest(path): | |
raise NotImplementedError | |
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): | |
paths = { | |
"": index, | |
"manifest.json": manifest, | |
"download": download, | |
"favicon.ico": favicon, | |
} | |
def do_GET(self): | |
path = self.path.split("/")[1] | |
out, headers = self.paths[path](self.path) | |
self.send_response(200) | |
for h, v in headers: | |
self.send_header(h, v) | |
self.end_headers() | |
self.wfile.write(out.read()) | |
def do_POST(self): | |
env = { | |
'REQUEST_METHOD':'POST', | |
'CONTENT_TYPE':self.headers['Content-Type'], | |
} | |
form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ=env) | |
uploaded_file = form["file"] | |
filename = uploaded_file.filename | |
with open(os.path.join(serving_path, filename), "wb") as fh: | |
fh.write(uploaded_file.file.read()) | |
self.send_response(302) | |
self.send_header("Location", "/") | |
self.end_headers() | |
if __name__ == "__main__": | |
httpd = HTTPServer(listen, SimpleHTTPRequestHandler) | |
print("Serving on {}:{}".format(*listen)) | |
httpd.serve_forever() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment