forked from cloudflare/worker-template-router
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathfile_download.js
120 lines (104 loc) · 4.17 KB
/
file_download.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/**
* Responsible for handling the actual downloading of a requested file from B2.
*/
import { rewriteErrorResponse } from './error_handling'
import {
CACHE_AGE_SECONDS,
PLAIN_TEXT_CONTENT_TYPE,
} from './constants'
/**
* Fetch and return a file from Backblaze B2 by supplying the download authorization
* token in the Authorization header.
* Errors are rewritten to not give away that this is a B2 bucket.
* Headers starting with "x-bz" are removed before being returned to the user.
* Adds cache and security-related headers.
*
* @param request the request from a client that will be rewritten to fetch from
* a Backblaze B2 bucket
* @param {object} b2 the b2config object
* @returns {Promise<Response>} the Response to the client that will either be
* the requested file or an error page
*/
async function getB2File(request, b2) {
let requestedUrl = new URL(request.url)
console.log(`requestedUrl = ${requestedUrl.toString()}`)
if(requestedUrl.hostname === DIR_DOMAIN) {
requestedUrl.hostname = MAIN_DOMAIN
return Response.redirect(requestedUrl.toString(), 301)
}
let url = new URL(b2.data.downloadUrl)
url.pathname = `/file/${B2BUCKET}/${requestedUrl.pathname}`
const response = await fetch(url.toString(), {
cf: {
cacheTtl: 60,
cacheEverything: true,
},
headers: {
"Authorization": b2.data.authorizationToken,
}
})
if(response.ok) {
return modifiedB2Response(request, response)
}
else {
return rewriteErrorResponse(request, response)
}
}
/**
* Adds cache headers, converts some Backblaze B2-specific headers to standard
* headers, and deletes the headers that start with "x-bz"
*
* @param request a request for a file on B2
* @param response the successful response from B2 that will be copied and modified
* @param convertHeaders if true, convert x-bz headers to standard headers then delete them
* @returns {Promise<Response>} the modified response
*/
async function modifiedB2Response(request, response, convertHeaders=true) {
console.log("modifiedB2Response...")
const newResponse = new Response(response.body, response)
// cache for a week
newResponse.headers.set("Cache-Control", `public, immutable, max-age=${CACHE_AGE_SECONDS}`)
newResponse.headers.set("Expires", new Date(Date.now() + CACHE_AGE_SECONDS * 1000).toUTCString())
if(convertHeaders) {
convertB2Headers(request, newResponse)
}
return newResponse
}
/**
* Converts the x-bz-content-sha1 header to an ETag header.
* Converts the x-bz-upload-timestamp header to a Last-Modified header.
* By default also deletes all headers that start with "x-bz"
*
* @param request the request from the client for a B2 file
* @param response the response from B2 that will be modified in-place
* @param deleteHeaders if true, delete the x-bz headers in the response
*/
function convertB2Headers(request, response, deleteHeaders=true) {
console.log("convertB2Headers...")
// get a Last-Modified header from x-bz-upload-timestamp
let bzts = response.headers.get("x-bz-upload-timestamp")
bzts = parseInt(bzts)
let d = new Date(bzts)
let lastModified = d.toUTCString()
response.headers.set("Last-Modified", lastModified)
// get an ETag header from x-bz-content-sha1
let bzsha = response.headers.get("x-bz-content-sha1")
bzsha = bzsha.replace(/^unverified:/, "") // in case it was uploaded without a checksum
bzsha = bzsha.substring(0, 16) // just get the first 16 characters of it
bzsha = `"${bzsha}"` // CloudFlare wants the ETag wrapped in quotes
response.headers.set("ETag", bzsha)
if(deleteHeaders) {
// remove the 'x-bz-' Backblaze headers
for(const header of response.headers.keys()) {
if(header.match(/^x-bz/i)) {
response.headers.delete(header)
}
}
}
// these file extensions we want to show up as plain text
let url = new URL(request.url)
if(/\.(pub|boot|cfg)$/.test(url.pathname)) {
response.headers.set("Content-Type", PLAIN_TEXT_CONTENT_TYPE)
}
}
export default getB2File