aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomasz Kramkowski <tomasz@kramkow.ski>2023-03-27 20:11:37 +0100
committerTomasz Kramkowski <tomasz@kramkow.ski>2023-03-27 20:11:37 +0100
commit3a9629e49b4c7e1d10c89bbffa04d18e96948116 (patch)
treed7e3fe35141fd56f6e2446de3546a052a7c197cf
parent5af2720086adc5337150259c7a6a37ade3770344 (diff)
downloadpaste-3a9629e49b4c7e1d10c89bbffa04d18e96948116.tar.gz
paste-3a9629e49b4c7e1d10c89bbffa04d18e96948116.tar.xz
paste-3a9629e49b4c7e1d10c89bbffa04d18e96948116.zip
POST request support
-rw-r--r--paste/__init__.py21
-rw-r--r--paste/store.py26
-rw-r--r--tests/test_application.py17
3 files changed, 62 insertions, 2 deletions
diff --git a/paste/__init__.py b/paste/__init__.py
index 3ee4468..36ce0cb 100644
--- a/paste/__init__.py
+++ b/paste/__init__.py
@@ -1,12 +1,13 @@
import binascii
import sys
import traceback
+import urllib.parse
from base64 import b64decode, b64encode
from collections.abc import Callable, Iterable
from functools import wraps
from sqlite3 import Connection
from typing import Any, Optional, Protocol, runtime_checkable
-from wsgiref.util import request_uri
+from wsgiref.util import application_uri, request_uri
from . import db, store
@@ -239,6 +240,24 @@ def application(environ: Env, start_response: StartResponse) -> Response:
],
)
return []
+ elif environ["REQUEST_METHOD"] == "POST":
+ content_type = environ.get("CONTENT_TYPE", "text/plain")
+ content_length = int(environ["CONTENT_LENGTH"])
+ content = environ["wsgi.input"].read(content_length)
+ path, content_hash = store.post(conn, name, content, content_type)
+ uri = application_uri(environ)
+ path = urllib.parse.quote(path)
+ if uri[-1] == "/" and path[:1] == "/":
+ uri += path[1:]
+ else:
+ uri += path
+ if environ.get("QUERY_STRING"):
+ uri += "?" + environ["QUERY_STRING"]
+ start_response(
+ "201 Created",
+ [("Location", uri), ("ETag", f'"{b64encode(content_hash).decode()}"')],
+ )
+ return []
elif environ["REQUEST_METHOD"] == "DELETE":
if store.delete(conn, name):
start_response("204 No Content", [])
diff --git a/paste/store.py b/paste/store.py
index ed81560..dd00edd 100644
--- a/paste/store.py
+++ b/paste/store.py
@@ -1,4 +1,5 @@
-from sqlite3 import Connection
+from secrets import token_urlsafe
+from sqlite3 import Connection, IntegrityError
def put(conn: Connection, name: str, content: bytes, content_type: str):
@@ -25,6 +26,29 @@ def put(conn: Connection, name: str, content: bytes, content_type: str):
return True, content_hash
+def post(conn: Connection, prefix: str, content: bytes, content_type: str):
+ with conn:
+ conn.execute(
+ "INSERT OR IGNORE INTO file (content) VALUES (?)",
+ (content,),
+ )
+ (content_hash,) = conn.execute("SELECT DATA_HASH(?)", (content,)).fetchone()
+ for _ in range(16):
+ name = prefix + token_urlsafe(5)
+ try:
+ conn.execute(
+ """INSERT INTO link (name, content_type, file_hash)
+ VALUES (?, ?, ?)""",
+ (name, content_type, content_hash),
+ )
+ except IntegrityError:
+ continue
+ break
+ else:
+ raise RuntimeError("Could not insert a link in 16 attempts")
+ return name, content_hash
+
+
def get(conn: Connection, name: str):
row = conn.execute(
"""SELECT link.content_type, file.hash, file.content
diff --git a/tests/test_application.py b/tests/test_application.py
index a7d6861..5a7847e 100644
--- a/tests/test_application.py
+++ b/tests/test_application.py
@@ -238,3 +238,20 @@ def test_delete(app, token):
"/test_key", headers={"Authorization": f"APIKey {token}"}, expect_errors=True
)
assert res.status == "404 Not Found"
+
+
+def test_post(app, token):
+ res = app.post(
+ "/test_key",
+ "Hello, World!",
+ headers={"Content-Type": "text/plain", "Authorization": f"APIKey {token}"},
+ )
+ assert res.status == "201 Created"
+ assert res.headers["Location"] != res.request.url
+ print(res.headers["Location"])
+ print(res.request.url)
+ assert res.headers["Location"].startswith(res.request.url)
+ etag = res.headers["ETag"]
+ res = app.get(res.headers["Location"])
+ assert res.status == "200 OK"
+ assert res.headers["ETag"] == etag