upload: support ?overwrite
Massimo Melina committed
Dec 16, 2023 at 17:51 UTC
8361dd268e0d71f75e9709f65f5fc4ae7a89dbb6
2 files changed
+34
-23
src/upload.ts
+8
-2
@@ -1,4 +1,4 @@
1
-import { statusCodeForMissingPerm, VfsNode } from './vfs'
1
+import { getNodeByName, hasPermission, statusCodeForMissingPerm, VfsNode } from './vfs'
2
import Koa from 'koa'
3
import {
4
HTTP_CONFLICT, HTTP_FOOL,
@@ -96,7 +96,7 @@ export function uploadWriter(base: VfsNode, path: string, ctx: Koa.Context) {
96
if (!ctx.req.aborted) {
97
let dest = fullPath
98
await setUploadMeta(tempName, ctx)
99
- if (dontOverwriteUploading.get() && fs.existsSync(dest)) {
99
+ if (dontOverwriteUploading.get() && fs.existsSync(dest) && !overwriteAnyway()) {
100
const ext = extname(dest)
101
const base = dest.slice(0, -ext.length)
102
let i = 1
@@ -120,6 +120,12 @@ export function uploadWriter(base: VfsNode, path: string, ctx: Koa.Context) {
120
})
121
return ret
122
123
+ function overwriteAnyway() {
124
+ if (ctx.query.overwrite === undefined) return
125
+ const n = getNodeByName(path, base)
126
+ return n && hasPermission(n, 'can_delete', ctx)
127
+ }
128
+
129
function trackProgress() {
130
let lastGot = 0
131
let lastGotTime = 0
src/vfs.ts
+26
-21
@@ -4,7 +4,7 @@ import fs from 'fs/promises'
4
import { basename, dirname, join, resolve } from 'path'
5
import {
6
dirStream, dirTraversal, enforceFinal, getOrSet, isDirectory, makeMatcher, setHidden, onlyTruthy,
7
- throw_, VfsPerms, Who, isWhoObject, WHO_ANY_ACCOUNT, defaultPerms, PERM_KEYS
7
+ throw_, VfsPerms, Who, isWhoObject, WHO_ANY_ACCOUNT, defaultPerms, PERM_KEYS, removeStarting
8
} from './misc'
9
import Koa from 'koa'
10
import _ from 'lodash'
@@ -94,18 +94,38 @@ export async function urlToNode(url: string, ctx?: Koa.Context, parent: VfsNode=
94
if (!name)
95
return parent
96
const rest = nextSlash < 0 ? '' : url.slice(nextSlash+1, url.endsWith('/') ? -1 : undefined)
97
- if (dirTraversal(name) || /[\\/]/.test(name)) {
98
- if (ctx)
99
- ctx.status = HTTP_FOOL
97
+ const ret = getNodeByName(name, parent)
98
+ if (!ret)
99
return
100
+ if (ret?.original)
101
+ return urlToNode(rest, ctx, ret, getRest)
102
+ if (parent.default)
103
+ inheritFromParent({ mime: { '*': MIME_AUTO } }, ret)
104
+ if (rest)
105
+ return urlToNode(rest, ctx, ret, getRest)
106
+ if (ret.source)
107
+ try {
108
+ const st = await fs.stat(ret.source) // check existence
109
+ ret.isFolder = st.isDirectory()
110
+ }
111
+ catch {
112
+ if (!getRest)
113
+ return
114
+ const rest = ret.source.slice(parent.source!.length) // parent has source, otherwise !ret.source || ret.original
115
+ getRest(removeStarting('/', rest))
116
+ return parent
117
}
118
+ return ret
119
+}
120
+
121
+export function getNodeByName(name: string, parent: VfsNode) {
122
+ if (dirTraversal(name) || /[\\/]/.test(name)) return
123
// does the tree node have a child that goes by this name?
124
const child = parent.children?.find(isSameFilenameAs(name))
125
if (!child && !parent.source) return // on tree or on disk, or it doesn't exist
105
-
126
const ret = applyParentToChild(child, parent, name)
127
if (child)
108
- return urlToNode(rest, ctx, ret, getRest)
128
+ return ret
129
let onDisk = name
130
if (parent.rename) { // reverse the mapping
131
for (const [from, to] of Object.entries(parent.rename))
@@ -116,21 +136,6 @@ export async function urlToNode(url: string, ctx?: Koa.Context, parent: VfsNode=
136
ret.rename = renameUnderPath(parent.rename, name)
137
}
138
ret.source = enforceFinal('/', parent.source!) + onDisk
119
- if (parent.default)
120
- inheritFromParent({ mime: { '*': MIME_AUTO } }, ret)
121
- if (rest)
122
- return urlToNode(rest, ctx, ret, getRest)
123
- if (ret.source)
124
- try {
125
- const st = await fs.stat(ret.source) // check existence
126
- ret.isFolder = st.isDirectory()
127
- }
128
- catch {
129
- if (!getRest)
130
- return
131
- getRest(onDisk)
132
- return parent
133
- }
139
return ret
140
}
141