File transfer is funny.
Usually people shove things onto Google Drive/Dropbox, email themselves, text themselves, wait for their USB to finish writing, make a temporary file on YetAnotherAnonShareSite.info, or even go through the steps of recreating the file manually, by hand! (I admit to that one. In my defense, I had no internet and it was only 40 lines of HTML.)
Pfft. It’s a rare and minor hassle, right? Who cares? So what if the email service complains about your PDF being over 200MB, or your WiFi speed throws a hissy fit for 2 minutes before loading the Drive page, or your USB has a read/write speed in the millibytes? Let’s just exchange glances awkwardly while watching the progress bar update, then be done with it.
Nah. Let’s forget this forever.
Termux
is a free open-source Android
app that shoves a Linux terminal on your phone. It’s useful for a load of tiny
day-to-day utilities, such as ping
to lie-detect your “four bars of WiFi”,
yt-dlp
to borrow a couple YouTube playlists in the background, and ffmpeg
to turn said YouTube playlists into space-efficient audio files. It renders
services such as TotallyNotShadyYoutubeDownloader.xyz and Spotify Premium
useless and quaking in the face of Making Something Yourself.
Put this on your Android, and you’ll be a walking server.
Let’s speed in.
NOTE: This guide assumes you installed Termux 5 seconds ago, and can read
error messages. And if you really did install Termux 5 seconds ago, please
run pkg upgrade
.
1. Sending via python3 -m http.server
First, find your internal IP (or whatever you named your phone in your settings app). This is how devices connected to your router will talk to your phone.
$ ifconfig
lo: ... # Loopback interface, ignore.
wlan0: ... # Wireless interface. Look for "inet".
inet 192.168.1.2 netmask 255.255.255.0 broadcast 192.168.1.255
Next, cd
to storage/downloads
or wherever you downloaded your file, then
start a HTTP server. (Make sure you have the python package.)
$ python3 -m http.server
Serving HTTP on :: port 8000 (http://[::]:8000/) ...
# [::] just means "every network
# interface on your device"
Now use a client machine to visit your device in a browser, e.g. 192.168.1.2:8000 or epicandroidname:8000, ignoring warnings complaining we’re on HTTP instead of HTTPS.
Finally, CTRL+C will interrupt the program.
The python3 -m http.server
oneliner is enough for most people and most
situations. In fact, I even alias py=python3
in .bashrc
, since doing this
operation in Windows uses py -m http.server
(typing python
in PowerShell
redirects you to Microsoft Store). If you want, you can stop here.
2. Sending/receiving via netcat-openbsd
Screw you! Time to read the manual instead.
$ man nc # See CLIENT/SERVER MODEL and DATA TRANSFER
# press `/` to search within the manuals
Note: you may need to pkg install netcat-openbsd
.
Note: this requires 2 Linux-like machines (e.g. WSL, Termux, Linux).
3. Receiving over HTTPS via Flask
Some devices cannot download/upload from HTTP alone; they need to know the connection is encrypted.
Flask is a lightweight Python webserver package. Since we’re going to start programming, you’ll want to make a folder to store the following code (as well as a folder for all of your coding projects).
# "~" is the HOME directory. Avoid putting code
# projects in the storage/ folder we were in, for
# filesystem permissions reasons.
$ mkdir -p ~/git/file-transfer
$ cd ~/git/file-transfer
Note that you’ll need a text editor, like nano
or vim
, if you’re developing
from your phone.
How do I use vim?
vim
is an efficient modal text-editor for headless (non-GUI) environments,
but it has a skill floor (try vimtutor
). For the bare minimum:
Hit i to enter INSERT mode
Hit ESC then type :wq! to run the save and quit commands.
Step 1. Set up a Python project
It’s good practice that we set up a virtual Python environment, instead of
installing loads of pip
packages in the global environment that might conflict.
$ python3 -m venv env # Use the Python packages
$ source env/bin/activate # local to your project,
# rather than your global
# Python install
(env) $ pip install flask
(env) $ echo 'env' >> .gitignore
(env) $ git init # to save our work later
Stay in the (env) virtual environment for the rest of the project, until
we’re done; then, we can run deactivate
to return to global Python.
Step 2. Look up the docs for Flask file uploads
Anyone can read and write copy-pastable code into a tutorial. But the spirit of Termux is the spirit of Linux: we read manpages and debug errors. Let’s go.
Search up File uploading with Python Flask.
Look for anything that says documentation… for me, it was this PalletsProjects 2.3.x link. Let’s skim it real quick.
Looks like we have example code from the docs itself; the docs are the tutorial.
Let’s copy-paste this into a main.py
file, changing the necessary fillers and
foobars into what we’ll actually use.
# --- FIRST CODE BLOCK in site ---
import os
from flask import Flask, flash, request, redirect, url_for
from werkzeug.utils import secure_filename
UPLOAD_FOLDER = 'storage' # Make sure to `mkdir` a storage directory for this
# Comment the line below out. Who cares? Let's upload anything!
# ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
# --- SECOND CODE BLOCK in site ---
# Comment the ALLOWED_EXTENSIONS bit out
def allowed_file(filename):
return '.' in filename #and \
#filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
# check if the post request has the file part
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
# If the user does not select a file, the browser submits an
# empty file without a filename.
if file.filename == '':
flash('No selected file')
return redirect(request.url)
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
# Comment this line out. If we look at the 4th block,
# this will cause a redirect to the user-uploaded file
# once they upload. That's unnecessary.
# return redirect(url_for('download_file', name=filename))
return '''
<!doctype html>
<title>Upload new File</title>
<h1>Upload new File</h1>
<form method=post enctype=multipart/form-data>
<input type=file name=file>
<input type=submit value=Upload>
</form>
'''
# --- FOURTH CODE BLOCK in site ---
# Optimally we'd put this at the top but we're
# copy-pasting for now.
from flask import send_from_directory
# We can infer that <name> is for variable substitution
@app.route('/uploads/<name>')
def download_file(name):
return send_from_directory(app.config["UPLOAD_FOLDER"], name)
Step 3. Add SSL
When we run the app with python3 main.py
, nothing happens. Let’s search
up how to actually serve… while we’re at it, let’s
search up how to serve with HTTPS
specifically!
Looks like we’ve got no docs here. Next bet in the chain is StackOverflow, before we start moving to tutorial websites and blog posts.
According to the question, we forgot to add app.run()
at the end of our file.
Boohoo.
According to the first answer, we need to add the following to the end of our code:
context = ('local.crt', 'local.key') #certificate and key files
app.run(debug=True, ssl_context=context)
but, according to one of the comments under said answer, instead of pre-making
these local.crt
and local.key
files, we can actually generate the SSL
ad hoc instead, which means “on demand for a short time.”
To generate local.crt and local.key see Method 2 of: kracekumar.com/post/54437887454/ssl-for-flask-local-development
andyandy Jun 20, 2017 at 16:42
# Totally copy-pasted this.
app.run('0.0.0.0', debug=True, port=8100, ssl_context='adhoc')
Now let’s run it! Follow along with me.
$ mkdir storage # If you forgot to in step 2
$ python3 main.py
... blahblah, output ...
TypeError: Using ad-hoc certificates requires the cryptography library.
$ pip install cryptography # Fix that error
error: can\'t find Rust compiler
To update pip, run:
pip install --upgrade pip
and retry package installation.
This package requires Rust >=1.63.0.
$ pip install --upgrade pip
Requirement already satisfied
$ pkg install rust # Since it said we need a Rust compiler
$ pip install cryptography
error: Failed to find tool. Is `aarch64-linux-android-ar` installed?
$ pkg search aarch64 linux android
rust-std-aarch64-linux-android/stable [installed]
# Huh? Do we have it already? Well, we should search
# up our problem online now, then -- no clear way out.
DuckDuckGo
shows us a
GitHub link
that tells us to install binutils
.
$ pkg in binutils
$ pip install cryptography
... blah blah, it worked~ ...
$ python3 main.py
...
* Running on all addresses (0.0.0.0)
* Running on https://127.0.0.1:8100
* Running on https://192.168.1.2:8100
...
Whew… you better test it. Upload a file to https:// whatever.
Do it. Check if it was received in our storage dir with ls
. Do it.
Step 4. Version control!
Whenever you get something done, commit your source code!
$ git add --all
$ git commit # You'll be put into vim or nano.
Step 5. Add download functionality
We’re not chumps. We go the full mile. Since we already have URLs that can access uploaded content, all we need to do is add a way to conveniently list them in our HTML. If you don’t already know how to get these hyperlinks, search it up and skim the docs specifically.
@app.route('/', methods=['GET', 'POST'])
def upload_file():
# For each thing in the upload dir
# add a hyperlink to its URL accessor
files = ''
for name in os.listdir(app.config['UPLOAD_FOLDER']):
# Use string formatting to insert the name.s
# Make sure you use 'uploads' since it is
# the @app.route we're using.
files += f'<li><a href="/uploads/{name}">{name}</a></li>'
# ... code and stuff
return '''
<!doctype html>
<title>Upload new File</title>
<h1>Upload new File</h1>
<form method=post enctype=multipart/form-data>
<input type=file name=file>
<input type=submit value=Upload>
</form>
''' + '<br />' + files
# Add the links to the end. Also, a <br>
# linebreak will make it look nicer.
And now we have a cruddily cobbled file server. Commit and save.
file-transfer
├── App.py
├── env
│ └── ...
└── storage
└── test.txt
Yes, this duct-taped code uses bad practices.
We returned HTML as a string rather than using Flask’s Jinja templating engine.
We didn’t check if the files/directories we serve in storage/ are actually files, rather than folders. There are no size limits either.
We used ad hoc instead of researching HTTPS, realizing Linux boxes use the openssl command to make certificates, then running the first 3 commands from
curl cht.sh/openssl | less
, which would be arguably easier for both end users who will now hit “Ignore cert warning” less often, and new Termux users who will be familiarized with basic SSL management and the cheat.sh website.We didn’t add a
requirements.txt
viapip freeze > requirements.txt
, nor did we add aREADME.md
that explains how to use the project.
But it works, it’s tiny, and it isn’t meant to be robust.