Compare commits

..

4 commits

Author SHA1 Message Date
a389c18e0e Applying boop changes 2022-11-20 23:44:41 +02:00
21ece7707f Merge remote-tracking branch 'mihaigalos/feat/put/add-support' into boop 2022-11-20 23:41:32 +02:00
Mihai Galos
1abe43f4c0 Update index.html 2022-09-26 18:13:28 +02:00
Mihai Galos
b3763acee4 PUT: Add initial support 2022-09-26 17:52:49 +02:00
7 changed files with 260 additions and 4 deletions

View file

@ -330,9 +330,9 @@ def get(path):
abort(404)
@app.route("/", methods=["GET", "POST"])
@app.route("/", methods=["GET", "POST", "PUT"])
def fhost():
if request.method == "POST":
if request.method == "POST" or request.method == "PUT":
sf = None
if "file" in request.files:
@ -344,7 +344,7 @@ def fhost():
abort(400)
else:
return render_template("index.html")
return render_template("boop.html")
@app.route("/robots.txt")
def robots():

View file

@ -8,3 +8,4 @@ Flask_SQLAlchemy
validators
flask_migrate
python_magic
uwsgi

37
services/boop.icu.nginx Normal file
View file

@ -0,0 +1,37 @@
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name boop.icu www.boop.icu;
ssl_certificate /etc/letsencrypt/live/boop.icu/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/boop.icu/privkey.pem;
include common/acme.conf;
include common/statuscodes.conf;
include common/ssl.conf;
# Upload size limit
client_max_body_size 256M;
location / {
include uwsgi_params;
uwsgi_pass unix:/opt/boop/boop.icu.sock;
uwsgi_param UWSGI_SCHEME https;
}
location /css {
root /opt/boop/css;
}
location /up {
root /opt/boop;
internal;
}
}
server {
listen 80;
listen [::]:80;
server_name boop.icu www.boop.icu;
return 301 https://boop.icu$request_uri;
}

12
services/boop.icu.service Normal file
View file

@ -0,0 +1,12 @@
[Unit]
Description=boop.icu instance
After=network.target
[Service]
User=boop
Group=http
WorkingDirectory=/opt/boop
ExecStart=/opt/boop/.local/bin/uwsgi --ini /opt/boop/boop.icu.ini --enable-threads
[Install]
WantedBy=multi-user.target

142
templates/boop.html Normal file
View file

@ -0,0 +1,142 @@
{% set title = config["SITE_NAME"] %}
{% set fhost_url = url_for("fhost", _external=True).rstrip("/") %}
{% set max_size = config["MAX_CONTENT_LENGTH"]|filesizeformat(True) %}
{% set not_allowed = config["FHOST_MIME_BLACKLIST"]|join(", ") %}
{% set half = ((config["MAX_CONTENT_LENGTH"]/2)|filesizeformat(True)).split(" ")[0].rjust(27) %}
{% set max = max_size.split(" ")[0].rjust(27) %}
{% set unit = max_size.split(" ")[1].rjust(54) %}
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ title }}</title>
<style type="text/css">
@media (min-width: 768px) {
body { width: 80%; }
pre { font-size: 12px; }
}
@media (min-width: 992px) {
body { width: 70%; }
}
@media (min-width: 1200px) {
body { width: 65%; }
}
::selection {
background: #99F2B9;
color: #0E0E0E;
}
body {
background: #0E0E0E;
color: #3EE77B;
font-family: monospace;
font-size: 16px;
line-height: 1.43;
margin: 0 auto;
}
form {
margin-block: 0em;
margin-block-end: 1em;
}
form input, hr, pre {
border: 1px solid #414141;
}
form input {
background: #0E0E0E;
color: #3EE77B;
font-family: monospace;
font-size: 16px;
height: 36px;
padding: 6px 12px;
width: 100%;
}
h1 {
font-size: 48px;
}
h3 {
font-size: 32px;
}
h1, h3 {
font-weight: 500;
line-height: 1.2;
margin-top: 22px;
margin-bottom: 11px;
}
h1, h3, pre, a, a:visited {
color: #99F2B9;
}
p, pre {
margin: 0 0 11px;
}
pre {
background: #282828;
padding: 10.5px;
font-size: 15px;
}
.container {
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
</style>
</head>
<body>
<div class="container">
<h1>{{ title }}</h1>
<hr>
HTTP POST files here:
<pre>curl -F'file=@yourfile.png' {{ fhost_url }}</pre>
You can also POST remote URLs:
<pre>curl -F'url=http://example.com/image.jpg' {{ fhost_url }}</pre>
Or you can shorten URLs:
<pre>curl -F'shorten=http://example.com/some/long/url' {{ fhost_url }}</pre>
Alternatively, you can use PUT:
<pre>curl -X PUT -T 'yourfile.png' {{ fhost_url }}</pre>
<p>File URLs are valid for at least 30 days and up to a year (see below).<br/>
Shortened URLs do not expire.</p>
<p>Maximum file size: {{ max_size }}</p>
<p>Not allowed: {{ not_allowed }}</p>
<h3>FILE RETENTION PERIOD</h3>
<pre>
retention = min_age + (-max_age + min_age) * pow((file_size / max_size - 1), 3)
days
365 | \\
| \\
| \\
| \\
| \\
| \\
| ..
| \
197.5 | ----------..-------------------------------------------
| ..
| \
| ..
| ...
| ..
| ...
| ....
| ......
30 | ....................
0{{ half }}{{ max }}
{{ unit }}
</pre>
<h3>UPLOAD DIRECTLY</h3>
<hr>
<form action="{{ fhost_url }}" method="POST" enctype="multipart/form-data">
<label for="file">File:</label>
<input class="form-control" type="file" name="file"><br><br>
<input class="form-control" type="submit" value="Submit">
</form>
<p>Please report illegal content to <a href="mailto:fedi@criminallycute.fi?subject="boop.icu content">fedi@criminallycute.fi</a> with the subject "boop.icu content". Include a link to content to be removed.</p>
</div>
</body>
</html>

View file

@ -9,6 +9,9 @@ You can also POST remote URLs:
Or you can shorten URLs:
curl -F'shorten=http://example.com/some/long/url' {{ fhost_url }}
Alternatively, you can use PUT:
curl -X PUT -T 'yourfile.png' {{ fhost_url }}
File URLs are valid for at least 30 days and up to a year (see below).
Shortened URLs do not expire.
{% set max_size = config["MAX_CONTENT_LENGTH"]|filesizeformat(True) %}

View file

@ -18,7 +18,7 @@ def client():
db_upgrade()
yield client
def test_client(client):
def test_client_post(client):
payloads = [
({ "file" : (BytesIO(b"hello"), "hello.txt") }, 200, b"https://localhost/E.txt\n"),
({ "file" : (BytesIO(b"hello"), "hello.ignorethis") }, 200, b"https://localhost/E.txt\n"),
@ -79,3 +79,64 @@ def test_client(client):
rv = client.get(p)
assert rv.status_code == code
def test_client_put(client):
payloads = [
({ "file" : (BytesIO(b"hello"), "hello.txt") }, 200, b"https://localhost/E.txt\n"),
({ "file" : (BytesIO(b"hello"), "hello.ignorethis") }, 200, b"https://localhost/E.txt\n"),
({ "file" : (BytesIO(b"bye"), "bye.truncatethis") }, 200, b"https://localhost/Q.truncate\n"),
({ "file" : (BytesIO(b"hi"), "hi.tar.gz") }, 200, b"https://localhost/h.tar.gz\n"),
({ "file" : (BytesIO(b"lea!"), "lea!") }, 200, b"https://localhost/d.txt\n"),
({ "file" : (BytesIO(b"why?"), "balls", "application/x-dosexec") }, 415, None),
({ "shorten" : "https://0x0.st" }, 200, b"https://localhost/E\n"),
({ "shorten" : "https://localhost" }, 400, None),
({}, 400, None),
]
for p, s, r in payloads:
rv = client.put("/", buffered=True,
content_type="multipart/form-data",
data=p)
assert rv.status_code == s
if r:
assert rv.data == r
f = File.query.get(2)
f.removed = True
db.session.add(f)
db.session.commit()
rq = [
(200, [
"/",
"robots.txt",
"E.txt",
"E.txt/test",
"E.txt/test.py",
"d.txt",
"h.tar.gz",
]),
(302, [
"E",
"E/test",
"E/test.bin",
]),
(404, [
"test.bin",
"test.bin/test",
"test.bin/test.py",
"test",
"test/test",
"test.bin/test.py",
"E.bin",
]),
(451, [
"Q.truncate",
]),
]
for code, paths in rq:
for p in paths:
app.logger.info(f"GET {p}")
rv = client.get(p)
assert rv.status_code == code