forked from mxx1111/mdlook
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathupload-local.ts
More file actions
92 lines (79 loc) · 2.7 KB
/
Copy pathupload-local.ts
File metadata and controls
92 lines (79 loc) · 2.7 KB
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
import type { Context } from 'hono'
import type { Env } from './types'
import { Buffer } from 'node:buffer'
import fs from 'node:fs/promises'
import path from 'node:path'
import { buildDatedObjectKey } from './upload-filename'
type LocalUploadContext = Context<{ Bindings: Env }>
const DEFAULT_UPLOAD_DIR = `/data/uploads`
const DEFAULT_PUBLIC_URL = `/uploads`
function getDir(): string {
const date = new Date()
const year = date.getUTCFullYear()
const month = String(date.getUTCMonth() + 1).padStart(2, `0`)
const day = String(date.getUTCDate()).padStart(2, `0`)
return `${year}/${month}/${day}`
}
function uploadRoot(env: Env): string {
return env.UPLOAD_LOCAL_DIR?.trim() || DEFAULT_UPLOAD_DIR
}
function publicUrlPrefix(env: Env, origin: string): string {
const configured = env.UPLOAD_LOCAL_PUBLIC_URL?.trim() || DEFAULT_PUBLIC_URL
if (/^https?:\/\//i.test(configured))
return configured.replace(/\/$/, ``)
return `${origin}${configured.startsWith(`/`) ? configured : `/${configured}`}`.replace(/\/$/, ``)
}
function contentTypeFromName(filename: string): string {
const ext = path.extname(filename).toLowerCase()
switch (ext) {
case `.jpg`:
case `.jpeg`:
return `image/jpeg`
case `.png`:
return `image/png`
case `.gif`:
return `image/gif`
case `.webp`:
return `image/webp`
case `.svg`:
return `image/svg+xml`
case `.avif`:
return `image/avif`
default:
return `application/octet-stream`
}
}
export async function uploadToLocal(env: Env, file: File, origin: string): Promise<string> {
const dir = getDir()
const filename = buildDatedObjectKey(file.name, file.type)
const key = `${dir}/${filename}`
const root = uploadRoot(env)
const outputDir = path.join(root, dir)
const outputPath = path.join(root, key)
await fs.mkdir(outputDir, { recursive: true })
await fs.writeFile(outputPath, Buffer.from(await file.arrayBuffer()))
return `${publicUrlPrefix(env, origin)}/${key}`
}
export async function localUploadFileHandler(c: LocalUploadContext): Promise<Response> {
if (c.env.UPLOAD_BACKEND !== `local`)
return c.notFound()
const key = decodeURIComponent(c.req.path.replace(/^\/uploads\/?/, ``))
if (!key || key.includes(`..`) || path.isAbsolute(key))
return c.notFound()
const root = path.resolve(uploadRoot(c.env))
const filePath = path.resolve(root, key)
if (!filePath.startsWith(`${root}${path.sep}`))
return c.notFound()
try {
const file = await fs.readFile(filePath)
return new Response(file, {
headers: {
'content-type': contentTypeFromName(filePath),
'cache-control': `public, max-age=31536000, immutable`,
},
})
}
catch {
return c.notFound()
}
}