commit be2ef0637427f6725739c494107d8e7a36c3452d Author: alyssadev Date: Sun Sep 17 04:50:55 2023 +1000 Initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86fddd4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +/target +/dist +**/*.rs.bk +Cargo.lock +bin/ +pkg/ +wasm-pack.log +worker/ +node_modules/ +.cargo-ok +/__target__ +/env +/.dev.vars +/.editorconfig +/.prettierrc +/.wrangler/ +package-lock.json diff --git a/package.json b/package.json new file mode 100644 index 0000000..604d0e7 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "linkie", + "version": "0.0.0", + "private": true, + "scripts": { + "deploy": "wrangler deploy", + "start": "wrangler dev" + }, + "devDependencies": { + "wrangler": "^3.0.0" + } +} \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..847bdd1 --- /dev/null +++ b/src/index.js @@ -0,0 +1,97 @@ +// vim: tabstop=4 shiftwidth=4 expandtab + +function makeid(length) { + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + const charactersLength = characters.length; + let counter = 0; + while (counter < length) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + counter += 1; + } + return result; +} + +function checkAuth(request) { + const auth = request.headers.get("Authorization"); + return auth === AUTH_KEY; +} + +function getHost(request) { + return request.headers.get("Host") +} + +async function add(host,path,request) { + if (!request.headers.get("content-type")) + return new Response("No URL provided", {status:400}) + const data = await request.formData() + const dest = data.get("u") + if (!dest) return new Response("No URL provided",{status:400}) + try { + new URL(dest) + } catch (e) { + if (e instanceof TypeError) + return new Response("No valid URL provided",{status:400}); + else throw e; + }; + if (!path) return new Response("No path provided",{status:400}) + if (path === "_") { + var x = 0 + while (true) { + path = makeid(4) + const lookup = await KV.get(path) + if (!lookup) break + if (x >= 5) return new Response("Failed to generate a unique ID in 5 attempts", {status:500}); + x += 1 + } + } + await KV.put(path, dest) + return new Response(`https://${host}/${path}`, {status:201}) +} + +async function remove(host,path) { + if (!path) return new Response("No path provided",{status:400}) + await KV.delete(path) + return new Response(`DELETE https://${host}/${path}`, {status:200}) +} + +async function get(host,path,auth) { + if (!path && auth) { + const { keys } = await KV.list() + let paths = "" + keys.forEach(element => paths += `${element.name}\n`); + return new Response(paths,{status:200}) + } + if (!path) return Response.redirect("https://aly-smith.carrd.co",301) + const dest = await KV.get(path) + if (dest) return Response.redirect(dest, 302) + return new Response("Path not found", {status:404}) +} + +async function handleRequest(request) { + const host = getHost(request) + const url = new URL(request.url) + var path = url.pathname.split("/")[1] + switch (request.method) { + case "PUT": + case "POST": + case "PATCH": + if (!checkAuth(request)) + return new Response("Only GET requests allowed to unauthed users", {status:403}); + return add(host,path,request) + case "DELETE": + if (!checkAuth(request)) + return new Response("Only GET requests allowed to unauthed users", {status:403}); + return remove(host,path) + case "HEAD": + case "GET": + return get(host,path,checkAuth(request)) + default: + return new Response("Method not allowed", {status:405}) + } +} + +addEventListener('fetch', event => { + const { request } = event; + return event.respondWith(handleRequest(request)); +}); diff --git a/test.py b/test.py new file mode 100644 index 0000000..c48208c --- /dev/null +++ b/test.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +from requests import get,put,post,delete,patch,request +from os import environ + +host = environ.get("HOST", "https://linkie.alyssa-dev-smith.workers.dev") +auth = {"Authorization": environ.get("AUTH", "")} +path = environ.get("TEST_PATH", "/devtestpath") + +try: + # unauth get / + req = get(host, allow_redirects=False) + assert req.status_code == 301 and "http" in req.headers["location"] # unauth get / + + # auth get / + req = get(host,headers=auth) + assert req.status_code == 200 # auth get / + + # unsupported method + req = request("OPTIONS", host) + assert req.status_code == 405 # unsupported method + + # unauth requests to auth methods + reqs = put(host),post(host),delete(host) + assert all(req.status_code == 403 for req in reqs) # unauth requests to auth methods + + # auth put wo data + req = put(host + path,data={},headers=auth) + assert req.status_code == 400 # auth put wo data + req = post(host + path,data={},headers=auth) + assert req.status_code == 400 # auth post wo data + req = patch(host + path,data={},headers=auth) + assert req.status_code == 400 # auth patch wo data + + # auth put invalid url + req = put(host + path,data={"u": "golf sale"},headers=auth) + assert req.status_code == 400 # auth put invalid url + req = post(host + path,data={"u": "golf sale"},headers=auth) + assert req.status_code == 400 # auth post invalid url + req = patch(host + path,data={"u": "golf sale"},headers=auth) + assert req.status_code == 400 # auth patch invalid url + + # auth put wo path + req = put(host,data={"u": "http://www.example.com"},headers=auth) + assert req.status_code == 400 # auth put wo path + req = post(host,data={"u": "http://www.example.com"},headers=auth) + assert req.status_code == 400 # auth post wo path + req = post(host,data={"u": "http://www.example.com"},headers=auth) + assert req.status_code == 400 # auth patch wo path + + # auth put valid + req = put(host + path,data={"u": "http://www.example.com/?put"},headers=auth) + assert req.status_code == 201 # auth put valid + req = get(host + path, allow_redirects=False) + assert req.status_code == 302 and req.headers["location"] == "http://www.example.com/?put" + + req = post(host + path + "post",data={"u": "http://www.example.com/?post"},headers=auth) + assert req.status_code == 201 # auth post valid + req = get(host + path + "post", allow_redirects=False) + assert req.status_code == 302 and req.headers["location"] == "http://www.example.com/?post" + + req = patch(host + path + "patch",data={"u": "http://www.example.com/?patch"},headers=auth) + assert req.status_code == 201 # auth patch valid + req = get(host + path + "patch", allow_redirects=False) + assert req.status_code == 302 and req.headers["location"] == "http://www.example.com/?patch" + + # auth delete wo path + req = delete(host,headers=auth) + assert req.status_code == 400 # auth delete wo path + + # auth delete valid + req = delete(host + path,headers=auth) + assert req.status_code == 200 # auth delete valid +# req = get(host + path, allow_redirects=False) +# assert req.status_code == 404 +except AssertionError: + print(req,req.headers,req.text) + raise diff --git a/wrangler.toml b/wrangler.toml new file mode 100644 index 0000000..5a449b3 --- /dev/null +++ b/wrangler.toml @@ -0,0 +1,6 @@ +name = "linkie" +main = "src/index.js" +compatibility_date = "2023-09-14" +kv_namespaces = [ + { binding = "KV", id = "7214e776a3da4ccd9f9fe5b6c3d3f781" } +]