@samitouri / QOSami-HFS / commits / 8361dd26

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