Skip to content

Instantly share code, notes, and snippets.

@jjdelc
Last active October 31, 2020 04:54
Show Gist options
  • Save jjdelc/4ce30477d6ab5a2711c272d4269e5066 to your computer and use it in GitHub Desktop.
Save jjdelc/4ce30477d6ab5a2711c272d4269e5066 to your computer and use it in GitHub Desktop.
Direct upload/download between devices
#!/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