index : worker-pastebin

A toy pastebin on Cloudflare Workers with E2EE, React frontend and S3-compatible storage backend

diff options
context:
space:
mode:
authorPeter Cai <[email protected]>2020-02-23 20:49:30 +0800
committerPeter Cai <[email protected]>2020-02-23 20:49:30 +0800
commit25f04b0c7e9f311679fc3ffd29811ce279d34497 (patch)
treeb0de7ed538a481e23b824aec1a33213568a9b705
parentaad3454f8d3ee8ee74f14e7dc30c31be46800abe (diff)
downloadworker-pastebin-25f04b0c7e9f311679fc3ffd29811ce279d34497.tar.gz
backend: implement remote url fetching
-rw-r--r--config.insecure.json.example3
-rw-r--r--src/index.coffee80
2 files changed, 80 insertions, 3 deletions
diff --git a/config.insecure.json.example b/config.insecure.json.example
index 1306ec8..2ab0756 100644
--- a/config.insecure.json.example
+++ b/config.insecure.json.example
@@ -1,5 +1,6 @@
{
"comments": "This file contains configuration that will be load into the browser frontend. See src/util.coffee for details.",
"max_upload_size": 10485760,
- "file_lifetime": "7 days"
+ "file_lifetime": "7 days",
+ "remote_fetch_magic": "some_not_common_string"
} \ No newline at end of file
diff --git a/src/index.coffee b/src/index.coffee
index 12de1dc..0402732 100644
--- a/src/index.coffee
+++ b/src/index.coffee
@@ -2,6 +2,7 @@ import * as util from './util'
import * as crypto from './crypto'
import S3 from './aws/s3'
import config from '../config.json'
+import configInsec from '../config.insecure.json'
import indexHtml from '../worker/index.html'
FRONTEND_PATHS = [
@@ -66,8 +67,14 @@ handlePUT = (req, file) ->
return buildInvalidResponse "Maximum upload size: " + util.MAX_UPLOAD_SIZE
if file.length > util.MAX_FILENAME_LENGTH
return buildInvalidResponse "File name too long (max #{util.MAX_FILENAME_LENGTH})"
-
- # Generate a valid ID first
+
+ if file != configInsec.remote_fetch_magic
+ handleClientUpload req, file
+ else
+ # If the file name is magic, we fetch content from remote
+ handleRemoteFetch req
+
+generateID = ->
id = null
path = null
loop
@@ -76,6 +83,11 @@ handlePUT = (req, file) ->
files = await s3.listObjects
prefix: path
break if !files.Contents or files.Contents.length == 0
+ [id, path]
+
+handleClientUpload = (req, file) ->
+ # Generate a valid ID first
+ [id, path] = await generateID()
path = path + "/" + file
len = req.headers.get "content-length"
@@ -154,5 +166,69 @@ handleGET = (req, file) ->
status: resp.status
headers: headers
+handleRemoteFetch = (req) ->
+ # We support fetching files from a remote URL
+ # given that the length is within a separate constraint
+ # Determine if we are actually getting an URL first
+ if req.headers.get("content-type") != "text/plain"
+ return buildInvalidResponse "Invalid URL"
+
+ if !req.headers.has("content-length") or req.headers.get("content-length") >= 1024
+ return buildInvalidResponse "Invalid URL"
+
+ # Get the URL
+ url = await req.text()
+
+ # Validate URL first
+ try
+ url = new URL url
+ throw "WTF" if url.protocol != "http:" and url.protocol != "https:"
+ catch err
+ return buildInvalidResponse "Invalid URL"
+
+ # Request the remote content
+ remote_res = await fetch url,
+ redirect: "follow"
+ if remote_res.status != 200
+ return buildInvalidResponse "Remote server returned error status"
+
+ # Check the length first
+ # TODO: Maybe we need to limit another length for remote fetch pastes
+ if !remote_res.headers.has("content-length") or remote_res.headers.get("content-length") > util.MAX_UPLOAD_SIZE
+ return buildInvalidResponse "Remote file too large"
+
+ # Try to determine the file name
+ path_splitted = url.pathname.split '/'
+ fileName = path_splitted[path_splitted.length - 1]
+ if remote_res.headers.has "content-disposition"
+ dispos = remote_res.headers.get "content-disposition"
+ for item of dispos.split ';'
+ item = item.trim()
+ if item.startsWith("filename=") or item.startsWith("filename*=")
+ fileName = item.replace "filename=", ""
+ .replace "filename*=", ""
+ .replace "\"", ""
+ break
+ console.log fileName
+
+ if fileName.length > util.MAX_FILENAME_LENGTH
+ return buildInvalidResponse "Remote file name too long"
+
+ # Begin uploading
+ [id, path] = await generateID()
+ path = path + "/" + fileName
+ len = remote_res.headers.get "content-length"
+
+ try
+ await s3.putObject path, remote_res.body,
+ ContentType: remote_res.headers.get "content-type"
+ ContentLength: len
+ catch err
+ console.log err
+ return buildInvalidResponse err
+
+ # Simply return the path in body
+ new Response "/paste/" + id,
+ status: 200
export default main \ No newline at end of file