feat: Dateien aus der Nextcloud uebernehmen
tools/uebernehmen.ps1 holt einen Nextcloud-Ordner in den Dienst. Unterordner
bleiben erhalten, -Probe zeigt erst nur, was passieren wuerde.
UEBER DAS DASHBOARD UND NICHT UEBER DIE TOKEN-API. Nextcloud-Dateien heissen
"Brand Logo (final).PNG" -- checkPath lehnt das mit Recht ab. Der
Dashboard-Weg biegt den Namen bereits vorhersagbar zurecht. Dieselbe Regel an
einer Stelle ist besser als dieselbe Regel an zweien, von denen eine irgendwann
abweicht.
WIEDERHOLBAR ueber overwrite=false: was schon da ist, meldet der Dienst je
Datei als "Pfad ist belegt". Nachgemessen mit 57 Marken -- zweiter Lauf: 0
uebernommen, 57 waren schon da.
DREI FEHLER BEIM BAUEN, alle durch Messen gefunden:
1. Ein kaputter Formular-Rumpf ergab einen NACKTEN 500, aufgefangen nur von der
Auffanglinie. Jetzt 400 mit Grund.
2. PowerShells `Invoke-RestMethod -Form` scheitert am Dienst. Ich hatte die
Ursache zuerst falsch: nicht (nur) die Grenzmarkierung in
Anfuehrungszeichen, die RFC 2046 erlaubt und die wir jetzt vertragen --
sondern der Rumpf selbst. Mitgeschnitten:
Content-Disposition: form-data; name=file; filename=a.png
RFC 7578 verlangt name="file" MIT Anfuehrungszeichen, .NET laesst sie weg,
und Nodes Parser besteht darauf. Das steht im RUMPF; dafuer braeuchte es
einen eigenen Multipart-Parser, und der waere unverhaeltnismaessig. Die
Werkzeuge nehmen deshalb curl.exe -F.
3. PowerShell-Falle: curl.exe gibt zeilenweise ein ARRAY zurueck, und
-match/-notmatch darauf FILTERT, statt zu pruefen. Ein nicht-leeres Ergebnis
gilt als wahr -- damit meldete das Skript "Kein Verzeichnis gefunden" bei
einer tadellosen Antwort mit HTTP 207. Jetzt wird vorher zusammengefuegt,
auch in sichern.ps1, wo dieselbe Falle nur deshalb nicht zuschlug, weil die
Ausgabe eine Zeile hat.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -570,17 +570,61 @@ dashRoutes.post('/upload', async (c) => {
|
||||
return c.json({ error: 'Erwartet wird multipart/form-data' }, 400)
|
||||
}
|
||||
|
||||
// { all: true } ist noetig: ohne das liefert Hono bei mehreren Feldern
|
||||
// gleichen Namens nur das letzte, und aus fuenf Dateien wuerde eine.
|
||||
const form = await c.req.parseBody({ all: true })
|
||||
// Das Formular lesen -- und dabei eine Strenge ausbuegeln, die uns fast
|
||||
// eine Anbindung gekostet haette.
|
||||
//
|
||||
// Manche Aufrufer setzen die Grenzmarkierung in ANFUEHRUNGSZEICHEN:
|
||||
//
|
||||
// multipart/form-data; boundary="3c5a305b-cb7d-..."
|
||||
//
|
||||
// Nodes Parser lehnt das ab, obwohl RFC 2046 es ausdruecklich erlaubt.
|
||||
// Also nehmen wir sie weg und lesen selbst.
|
||||
//
|
||||
// ACHTUNG, EINE HALBE WAHRHEIT WAERE HIER TEURER ALS KEINE: das allein
|
||||
// rettet PowerShells `Invoke-RestMethod -Form` NICHT. Mitgeschnitten hat
|
||||
// dessen Rumpf naemlich noch eine zweite Abweichung:
|
||||
//
|
||||
// Content-Disposition: form-data; name=file; filename=a.png
|
||||
//
|
||||
// RFC 7578 verlangt name="file" MIT Anfuehrungszeichen, .NET laesst sie
|
||||
// weg. Das steht im RUMPF, nicht im Kopf -- dafuer braeuchte es einen
|
||||
// eigenen Multipart-Parser, und der waere fuer diesen Dienst
|
||||
// unverhaeltnismaessig. Werkzeuge auf dieser Maschine nehmen deshalb
|
||||
// `curl.exe -F` (siehe tools/uebernehmen.ps1); curl setzt beides korrekt.
|
||||
let form: FormData
|
||||
try {
|
||||
const typ = c.req.header('content-type') ?? ''
|
||||
if (/boundary="/.test(typ)) {
|
||||
const gerade = typ.replace(/boundary="([^"]+)"/, 'boundary=$1')
|
||||
const rumpf = await c.req.arrayBuffer()
|
||||
form = await new Request('http://formular.invalid/', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': gerade },
|
||||
body: rumpf,
|
||||
}).formData()
|
||||
} else {
|
||||
form = await c.req.raw.formData()
|
||||
}
|
||||
} catch (err) {
|
||||
// Ein kaputter Rumpf ergab bisher einen 500, aufgefangen nur von der
|
||||
// Auffanglinie. Wer ein Formular falsch zusammenbaut, soll 400 samt
|
||||
// Grund bekommen und nicht "Serverfehler".
|
||||
return c.json(
|
||||
{
|
||||
error: `Der Formular-Rumpf liess sich nicht lesen: ${err instanceof Error ? err.message : 'unbekannt'}`,
|
||||
},
|
||||
400,
|
||||
)
|
||||
}
|
||||
|
||||
const roheDateien = form['file']
|
||||
const dateien = (Array.isArray(roheDateien) ? roheDateien : [roheDateien]).filter(
|
||||
(f): f is File => f instanceof File,
|
||||
)
|
||||
// getAll und nicht get: sonst kaeme von fuenf Dateien nur die letzte an.
|
||||
// Ohne DOM-Typen kennt TypeScript hier nur FormDataEntryValue; die
|
||||
// Einengung auf File muss deshalb ausgesprochen werden. Der Test bleibt
|
||||
// derselbe -- was kein Text ist, ist eine Datei.
|
||||
const dateien = form.getAll('file').filter((f) => typeof f !== 'string') as unknown as File[]
|
||||
if (dateien.length === 0) return c.json({ error: 'Keine Datei dabei' }, 400)
|
||||
|
||||
const ordnerRoh = typeof form['folder'] === 'string' ? form['folder'].trim() : ''
|
||||
const ordnerRoh = (form.get('folder') as string | null)?.trim() ?? ''
|
||||
let ordner = ''
|
||||
if (ordnerRoh) {
|
||||
try {
|
||||
@@ -593,7 +637,7 @@ dashRoutes.post('/upload', async (c) => {
|
||||
}
|
||||
}
|
||||
|
||||
const ueberschreiben = form['overwrite'] !== 'false'
|
||||
const ueberschreiben = form.get('overwrite') !== 'false'
|
||||
const ergebnisse: {
|
||||
name: string
|
||||
ok: boolean
|
||||
|
||||
Reference in New Issue
Block a user