fix(nextcloud-files): WR-04/05/06, IN-01 Ersetzen und Abschluss von Uploads abgesichert

- WR-04: die Entity-Tag-Pruefung beim Ersetzen laeuft im Zusammenbau unmittelbar vor dem
  MOVE (gemessen gegen Nextcloud 34: If-Match auf dem MOVE wird gegen .file geprueft und
  ergibt immer 412, ist also nicht nutzbar); weicht der Tag ab -> changedMeanwhile
- WR-05: completeUpload merkt den Zustand synchron vor dem ersten await; ein zweiter oder
  gleichzeitiger Abschluss liefert das gemerkte Ergebnis (auch denselben Fehler) und baut
  nie zweimal zusammen; nur Fehler vor dem MOVE duerfen erneut versucht werden
- WR-06: 412 mit replaceEtag (PUT oder Zusammenbau) ist changedMeanwhile, nicht nameTaken
- IN-01: replaceEtag muss die Form eines Entity-Tags haben (sonst 400)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-08 22:41:20 +02:00
parent 07474bd162
commit ee05131add
3 changed files with 227 additions and 31 deletions
@@ -8,6 +8,7 @@ import {
IsNotEmpty,
IsOptional,
IsString,
Matches,
Max,
MaxLength,
Min,
@@ -26,6 +27,13 @@ import {
/** Hoechster Unix-Zeitstempel (Sekunden), den eine Aenderungszeit haben darf (Jahr 2100). */
export const MAX_MTIME_SECONDS = 4102444800;
/**
* Form eines Entity-Tags (IN-01): optional `W/`, optional in Anfuehrungszeichen, sonst nur
* sichtbare ASCII-Zeichen. Der Wert geht als `If-Match`-Kopfzeile an Nextcloud; CR, LF oder
* andere Steuerzeichen liessen den Aufruf sonst scheitern und als "nicht erreichbar" enden.
*/
export const ETAG_PATTERN = /^(W\/)?"?[\x21\x23-\x7e]{1,128}"?$/;
export class StartUploadDto {
@IsString()
@IsNotEmpty()
@@ -40,6 +48,7 @@ export class StartUploadDto {
@IsOptional()
@IsString()
@MaxLength(200)
@Matches(ETAG_PATTERN)
replaceEtag?: string;
}
@@ -62,6 +71,7 @@ export class CompleteUploadDto {
@IsOptional()
@IsString()
@MaxLength(200)
@Matches(ETAG_PATTERN)
replaceEtag?: string;
}
@@ -88,6 +98,7 @@ export class UploadQueryDto {
@IsOptional()
@IsString()
@MaxLength(200)
@Matches(ETAG_PATTERN)
replaceEtag?: string;
}