worker-pastebin/src/index.coffee

138 lines
3.8 KiB
CoffeeScript

import * as util from './util'
import _ from './prelude'
import S3 from './aws/s3'
import config from '../config.json'
import indexHtml from '../worker/index.html'
FRONTEND_PATHS = [
'/', '/paste/text', '/paste/binary',
'/paste/text/', '/paste/binary/'
]
s3 = new S3 config
main = ->
addEventListener 'fetch', (event) =>
event.respondWith handleRequest event
buildInvalidResponse = (msg) ->
if not msg
msg = "Invalid Request"
new Response msg,
status: 400
buildFrontendResponse = ->
new Response indexHtml,
status: 200
headers:
'content-type': 'text/html'
handleRequest = (event) ->
# Handle request for static home page first
if event.request.method == "GET"
parsedURL = new URL event.request.url
if parsedURL.pathname in FRONTEND_PATHS
return buildFrontendResponse _
# Validate file name first, since this is shared logic
file = util.getFileName event.request.url
if not file
return buildInvalidResponse _
# Handle PUT and GET separately
if event.request.method == "PUT"
handlePUT event.request, file
else if event.request.method == "GET"
handleGET event.request, file
else
buildInvalidResponse _
handlePUT = (req, file) ->
if not util.validateLength req
return buildInvalidResponse "Maximum upload size: " + util.MAX_UPLOAD_SIZE
# Generate a valid ID first
id = null
path = null
loop
id = util.randomID _
path = util.idToPath id
files = await s3.listObjects
prefix: path
break if !files.Contents or files.Contents.length == 0
path = path + "/" + file
len = req.headers.get "content-length"
# Upload the file to S3
try
await s3.putObject path, req.body, # Expiration should be configured on S3 side
ContentType: req.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
handleGET = (req, file) ->
path = util.idToPath file
# Find the file first, because ID is only the path part
# We still need the real file name
files = await s3.listObjects
prefix: path
if not files.Contents or files.Contents.length == 0
return new Response "Not Found",
status: 404
# The full path to the original file
fullPath = files.Contents[0].Key
fileName = fullPath.split '/'
.pop _
# Build options and downlaod the file from origin
options = {}
# Handle range header
if req.headers.has "range"
options["range"] = req.headers.get "range"
resp = await s3.getObject fullPath, options
if not resp.ok
return new Response "Something went wrong",
status: resp.status
# If the content is text, and the user is using a browser
# show frontend code viewer
if not req.url.endsWith 'original'
isText = util.isText resp.headers.get 'content-type'
isBrowser = util.isBrowser req
if isText and isBrowser
return buildFrontendResponse _
# Build response headers
headers =
'content-length': resp.headers.get 'content-length'
'accept-ranges': 'bytes'
# TODO: handle text/* with a code viewer of some sort
'content-type': resp.headers.get 'content-type'
# Prevent executing random HTML / XML by treating all text as `text/plain`
if headers['content-type'].startsWith 'text/'
headers['content-type'] = 'text/plain'
# Add content-disposition header to indicate file name
inline = util.shouldShowInline headers['content-type']
headers['content-disposition'] =
(if inline then 'inline;' else 'attachment;') + ' filename*=' + encodeURIComponent fileName
# Handle ranged resposes
if resp.headers.has 'content-range'
headers['content-range'] = resp.headers.get 'content-range'
new Response resp.body,
status: resp.status
headers: headers
export default main