mirror of
https://github.com/mollersuite/monofile.git
synced 2024-11-21 13:36:25 -08:00
Finally fix /:fileId
This commit is contained in:
parent
a062a98996
commit
e7779b21e8
|
@ -87,7 +87,16 @@ apiRouter.loadAPIMethods().then(() => {
|
|||
console.log("API OK!")
|
||||
|
||||
// moved here to ensure it's matched last
|
||||
app.get("/:fileId", async (ctx) => app.fetch(ctx.req.raw, ctx.env))
|
||||
app.get("/:fileId", async (ctx) =>
|
||||
app.fetch(
|
||||
new Request(
|
||||
(new URL(
|
||||
`/api/v1/file/${ctx.req.param("fileId")}`, ctx.req.raw.url)).href,
|
||||
ctx.req.raw
|
||||
),
|
||||
ctx.env
|
||||
)
|
||||
)
|
||||
|
||||
// listen on 3000 or MONOFILE_PORT
|
||||
// moved here to prevent a crash if someone manages to access monofile before api routes are mounted
|
||||
|
|
|
@ -21,91 +21,15 @@ export let primaryApi = new Hono<{
|
|||
primaryApi.all("*", getAccount)
|
||||
|
||||
export default function (files: Files) {
|
||||
primaryApi.get(
|
||||
"/file/:fileId",
|
||||
async (ctx): Promise<Response> => {
|
||||
const fileId = (ctx.req.param() as {fileId: string}).fileId
|
||||
|
||||
let acc = ctx.get("account") as Accounts.Account
|
||||
|
||||
let file = files.files[fileId]
|
||||
ctx.header("Access-Control-Allow-Origin", "*")
|
||||
ctx.header("Content-Security-Policy", "sandbox allow-scripts")
|
||||
ctx.header("Content-Disposition", `${ctx.req.query("attachment") == "1" ? "attachment" : "inline"}; filename="${encodeURI(file.filename.replaceAll("\n","\\n"))}"`)
|
||||
ctx.header("ETag", file.md5)
|
||||
//if (file.lastModified) ctx.header("Last-Modified", new Date(file.lastModified).toTimeString())
|
||||
|
||||
if (file) {
|
||||
if (file.visibility == "private") {
|
||||
if (acc?.id != file.owner) {
|
||||
return ServeError(ctx, 403, "you do not own this file")
|
||||
}
|
||||
|
||||
if (
|
||||
auth.getType(auth.tokenFor(ctx)!) == "App" &&
|
||||
auth
|
||||
.getPermissions(auth.tokenFor(ctx)!)
|
||||
?.includes("private")
|
||||
) {
|
||||
return ServeError(ctx, 403, "insufficient permissions")
|
||||
}
|
||||
}
|
||||
|
||||
let range: Range | undefined
|
||||
|
||||
ctx.header("Content-Type", file.mime)
|
||||
if (file.sizeInBytes) {
|
||||
ctx.header("Content-Length", file.sizeInBytes.toString())
|
||||
|
||||
if (file.chunkSize && ctx.req.header("Range")) {
|
||||
let ranges = RangeParser(file.sizeInBytes, ctx.req.header("Range") || "")
|
||||
|
||||
if (ranges) {
|
||||
if (typeof ranges == "number")
|
||||
return ServeError(ctx, ranges == -1 ? 416 : 400, ranges == -1 ? "unsatisfiable ranges" : "invalid ranges")
|
||||
if (ranges.length > 1) return ServeError(ctx, 400, "multiple ranges not supported")
|
||||
range = ranges[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (range) {
|
||||
ctx.status(206)
|
||||
ctx.header(
|
||||
"Content-Length",
|
||||
(range.end - range.start + 1).toString()
|
||||
)
|
||||
ctx.header(
|
||||
"Content-Range",
|
||||
`bytes ${range.start}-${range.end}/${file.sizeInBytes}`
|
||||
)
|
||||
}
|
||||
|
||||
if (ctx.req.method == "HEAD")
|
||||
return ctx.body(null)
|
||||
|
||||
return files
|
||||
.readFileStream(fileId, range)
|
||||
.then(async (stream) => {
|
||||
let rs = new ReadableStream({
|
||||
start(controller) {
|
||||
stream.once("end", () => controller.close())
|
||||
stream.once("error", (err) => controller.error(err))
|
||||
},
|
||||
cancel(reason) {
|
||||
stream.destroy(reason instanceof Error ? reason : new Error(reason))
|
||||
}
|
||||
})
|
||||
stream.pipe(ctx.env.outgoing)
|
||||
return new Response(rs, ctx.body(null))
|
||||
})
|
||||
.catch((err) => {
|
||||
return ServeError(ctx, err.status, err.message)
|
||||
})
|
||||
} else {
|
||||
return ServeError(ctx, 404, "file not found")
|
||||
}
|
||||
}
|
||||
primaryApi.get("/:fileId", async (ctx) =>
|
||||
primaryApi.fetch(
|
||||
new Request(
|
||||
(new URL(
|
||||
`/api/v1/file/${ctx.req.param("fileId")}`, ctx.req.raw.url)).href,
|
||||
ctx.req.raw
|
||||
),
|
||||
ctx.env
|
||||
)
|
||||
)
|
||||
|
||||
primaryApi.post(
|
||||
|
|
|
@ -3,7 +3,14 @@
|
|||
"baseURL": "/api/v1",
|
||||
"mount": [
|
||||
"account",
|
||||
"file",
|
||||
"session"
|
||||
"session",
|
||||
{
|
||||
"file": "file/index",
|
||||
"to": "/file"
|
||||
},
|
||||
{
|
||||
"file": "file/individual",
|
||||
"to": "/file"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import { Hono } from "hono";
|
||||
import Files from "../../../lib/files.js";
|
||||
|
||||
const router = new Hono()
|
||||
|
||||
export default function(files: Files) {
|
||||
return router
|
||||
}
|
13
src/server/routes/api/v1/file/index.ts
Normal file
13
src/server/routes/api/v1/file/index.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { Hono } from "hono";
|
||||
import Files from "../../../../lib/files.js";
|
||||
import { getAccount } from "../../../../lib/middleware.js";
|
||||
|
||||
const router = new Hono()
|
||||
router.all("*", getAccount)
|
||||
|
||||
export default function(files: Files) {
|
||||
|
||||
|
||||
|
||||
return router
|
||||
}
|
109
src/server/routes/api/v1/file/individual.ts
Normal file
109
src/server/routes/api/v1/file/individual.ts
Normal file
|
@ -0,0 +1,109 @@
|
|||
import { Hono } from "hono"
|
||||
import * as Accounts from "../../../../lib/accounts.js"
|
||||
import * as auth from "../../../../lib/auth.js"
|
||||
import RangeParser, { type Range } from "range-parser"
|
||||
import ServeError from "../../../../lib/errors.js"
|
||||
import Files, { WebError } from "../../../../lib/files.js"
|
||||
import { getAccount, requiresPermissions } from "../../../../lib/middleware.js"
|
||||
import {Readable} from "node:stream"
|
||||
import type {ReadableStream as StreamWebReadable} from "node:stream/web"
|
||||
import formidable from "formidable"
|
||||
import { HttpBindings } from "@hono/node-server"
|
||||
import pkg from "../../../../../../package.json" assert {type: "json"}
|
||||
import { type StatusCode } from "hono/utils/http-status"
|
||||
|
||||
const router = new Hono<{
|
||||
Variables: {
|
||||
account: Accounts.Account
|
||||
},
|
||||
Bindings: HttpBindings
|
||||
}>()
|
||||
router.all("*", getAccount)
|
||||
|
||||
export default function(files: Files) {
|
||||
|
||||
router.get("/:id", async (ctx) => {
|
||||
const fileId = ctx.req.param("id")
|
||||
|
||||
let acc = ctx.get("account") as Accounts.Account
|
||||
|
||||
let file = files.files[fileId]
|
||||
ctx.header("Accept-Ranges", "bytes")
|
||||
ctx.header("Access-Control-Allow-Origin", "*")
|
||||
ctx.header("Content-Security-Policy", "sandbox allow-scripts")
|
||||
ctx.header("Content-Disposition", `${ctx.req.query("attachment") == "1" ? "attachment" : "inline"}; filename="${encodeURI(file.filename.replaceAll("\n","\\n"))}"`)
|
||||
ctx.header("ETag", file.md5)
|
||||
//if (file.lastModified) ctx.header("Last-Modified", new Date(file.lastModified).toTimeString())
|
||||
|
||||
if (file) {
|
||||
if (file.visibility == "private") {
|
||||
if (acc?.id != file.owner) {
|
||||
return ServeError(ctx, 403, "you do not own this file")
|
||||
}
|
||||
|
||||
if (
|
||||
auth.getType(auth.tokenFor(ctx)!) == "App" &&
|
||||
auth
|
||||
.getPermissions(auth.tokenFor(ctx)!)
|
||||
?.includes("private")
|
||||
) {
|
||||
return ServeError(ctx, 403, "insufficient permissions")
|
||||
}
|
||||
}
|
||||
|
||||
let range: Range | undefined
|
||||
|
||||
ctx.header("Content-Type", file.mime)
|
||||
if (file.sizeInBytes) {
|
||||
ctx.header("Content-Length", file.sizeInBytes.toString())
|
||||
|
||||
if (file.chunkSize && ctx.req.header("Range")) {
|
||||
let ranges = RangeParser(file.sizeInBytes, ctx.req.header("Range") || "")
|
||||
|
||||
if (ranges) {
|
||||
if (typeof ranges == "number")
|
||||
return ServeError(ctx, ranges == -1 ? 416 : 400, ranges == -1 ? "unsatisfiable ranges" : "invalid ranges")
|
||||
if (ranges.length > 1) return ServeError(ctx, 400, "multiple ranges not supported")
|
||||
range = ranges[0]
|
||||
|
||||
ctx.status(206)
|
||||
ctx.header(
|
||||
"Content-Length",
|
||||
(range.end - range.start + 1).toString()
|
||||
)
|
||||
ctx.header(
|
||||
"Content-Range",
|
||||
`bytes ${range.start}-${range.end}/${file.sizeInBytes}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.req.method == "HEAD")
|
||||
return ctx.body(null)
|
||||
|
||||
return files
|
||||
.readFileStream(fileId, range)
|
||||
.then(async (stream) => {
|
||||
let rs = new ReadableStream({
|
||||
start(controller) {
|
||||
stream.once("end", () => controller.close())
|
||||
stream.once("error", (err) => controller.error(err))
|
||||
},
|
||||
cancel(reason) {
|
||||
stream.destroy(reason instanceof Error ? reason : new Error(reason))
|
||||
}
|
||||
})
|
||||
stream.pipe(ctx.env.outgoing)
|
||||
return new Response(rs, ctx.body(null))
|
||||
})
|
||||
.catch((err) => {
|
||||
return ServeError(ctx, err.status, err.message)
|
||||
})
|
||||
} else {
|
||||
return ServeError(ctx, 404, "file not found")
|
||||
}
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
Loading…
Reference in a new issue