#!/usr/bin/env bash # Aufgabe 4: Hoch- und Herunterladen gegen die Test-Nextcloud — Umlautdatei, Ueberschreibschutz, # 30 MB in 8-MiB-Stuecken DURCH den Next.js-Proxy (/api-proxy), Download byteidentisch mit # Range, Kopfzeilen, 413, 411, 404-Abbildung, ZIP (Ordner und Auswahl). Setzt einen laufenden # Stack und nc-test-setup.sh voraus. Nur Testwerte; liest keine .env-Dateien. set -euo pipefail source "$(dirname "${BASH_SOURCE[0]}")/e2e-lib.sh" trap 'rm -rf "$E2E_TMP"' EXIT FILES="$API/modules/nextcloud-files" VIA_WEB="$WEB/modules/nextcloud-files" NC_DAV="http://localhost:18080/remote.php/dav" ANNA="anna:User1-Pass-12345" UMLAUT_NAME='Ärger & Ölpreis 100%.txt' CHUNK=8388608 # enc — vollstaendig prozentcodiert (jedes Zeichen ausser A-Za-z0-9._~-) enc() { python3 -I -c 'import sys,urllib.parse;print(urllib.parse.quote(sys.argv[1], safe=""))' "$1"; } # jget — Wert aus einer JSON-Datei, z. B. 'd["mode"]' jget() { python3 -I -c 'import json,sys;d=json.load(open(sys.argv[1]));print(eval(sys.argv[2]))' "$1" "$2"; } ADMIN="$E2E_TMP/admin.jar" e2e_wait_health e2e_login "$ADMIN" e2e_activate "$ADMIN" e2e_set_address "$ADMIN" code=$(e2e_connect_anna "$ADMIN") e2e_expect 200 "$code" "anna verbinden" F="/Tessera-E2E-T-$(date +%s)" FNAME="${F#/}" code=$(e2e_status "$ADMIN" POST "$FILES/folders" "{\"path\":\"$F\"}") case "$code" in 200|201) ;; *) e2e_fail "Testordner anlegen -> $code" ;; esac # --- 1. Kleine Datei mit Umlauten, Sonderzeichen und Prozent im Namen ------------------------------ printf 'Hallo Aerger\n' > "$E2E_TMP/small.txt" SMALL_SIZE=$(wc -c < "$E2E_TMP/small.txt") UPATH="$F/$UMLAUT_NAME" code=$(e2e_status "$ADMIN" POST "$FILES/uploads" "{\"path\":\"$UPATH\",\"size\":$SMALL_SIZE}") case "$code" in 200|201) ;; *) e2e_fail "Upload starten -> $code" ;; esac e2e_contains "$E2E_TMP/body.out" '"mode":"single"' "kleine Datei: single" code=$(curl -s -o "$E2E_TMP/body.out" -w '%{http_code}' -b "$ADMIN" -X PUT -H 'Content-Type: application/octet-stream' \ --data-binary "@$E2E_TMP/small.txt" "$FILES/uploads/file?path=$(enc "$UPATH")&size=$SMALL_SIZE&mtime=1700000000") e2e_expect 200 "$code" "kleine Datei hochladen" code=$(curl -s -o "$E2E_TMP/body.out" -w '%{http_code}' -b "$ADMIN" -G --data-urlencode "path=$F" "$FILES/files") e2e_expect 200 "$code" "Testordner auflisten" e2e_contains "$E2E_TMP/body.out" "\"name\":\"$UMLAUT_NAME\"" "Umlautname exakt in der Liste" ETAG=$(python3 -I -c 'import json,sys for e in json.load(open(sys.argv[1]))["entries"]: if e["name"]==sys.argv[2]: print(e["etag"]); print(e["mtime"], file=sys.stderr)' "$E2E_TMP/body.out" "$UMLAUT_NAME" 2> "$E2E_TMP/mtime.out") [ "$(cat "$E2E_TMP/mtime.out")" = "2023-11-14T22:13:20.000Z" ] || e2e_fail "Aenderungszeit uebernommen: $(cat "$E2E_TMP/mtime.out")" # --- 2. Ueberschreibschutz ------------------------------------------------------------------------------ code=$(e2e_status "$ADMIN" POST "$FILES/uploads" "{\"path\":\"$UPATH\",\"size\":$SMALL_SIZE}") e2e_expect 409 "$code" "gleicher Name noch einmal" e2e_contains "$E2E_TMP/body.out" '"code":"nameTaken"' "Namenskonflikt: nameTaken" e2e_contains "$E2E_TMP/body.out" '"existing"' "Namenskonflikt: existing" # direkter PUT ohne vorherigen Start darf ebenfalls nichts ueberschreiben (If-None-Match) code=$(curl -s -o "$E2E_TMP/body.out" -w '%{http_code}' -b "$ADMIN" -X PUT -H 'Content-Type: application/octet-stream' \ --data-binary "@$E2E_TMP/small.txt" "$FILES/uploads/file?path=$(enc "$UPATH")&size=$SMALL_SIZE") e2e_expect 409 "$code" "PUT auf vorhandenen Namen" e2e_contains "$E2E_TMP/body.out" '"code":"nameTaken"' "PUT: nameTaken" # Ersetzen mit der richtigen Version klappt, mit einer falschen nicht printf 'Neuer Inhalt\n' > "$E2E_TMP/small2.txt" SIZE2=$(wc -c < "$E2E_TMP/small2.txt") code=$(e2e_status "$ADMIN" POST "$FILES/uploads" "{\"path\":\"$UPATH\",\"size\":$SIZE2,\"replaceEtag\":\"\\\"falsch\\\"\"}") e2e_expect 409 "$code" "Ersetzen mit falscher Version" e2e_contains "$E2E_TMP/body.out" '"code":"changedMeanwhile"' "falsche Version: changedMeanwhile" ETAG_JSON=$(python3 -I -c 'import json,sys;print(json.dumps(sys.argv[1]))' "$ETAG") code=$(e2e_status "$ADMIN" POST "$FILES/uploads" "{\"path\":\"$UPATH\",\"size\":$SIZE2,\"replaceEtag\":$ETAG_JSON}") case "$code" in 200|201) ;; *) e2e_fail "Ersetzen mit richtiger Version -> $code" ;; esac code=$(curl -s -o "$E2E_TMP/body.out" -w '%{http_code}' -b "$ADMIN" -X PUT -H 'Content-Type: application/octet-stream' \ --data-binary "@$E2E_TMP/small2.txt" "$FILES/uploads/file?path=$(enc "$UPATH")&size=$SIZE2&replaceEtag=$(enc "$ETAG")") e2e_expect 200 "$code" "Ersetzen" code=$(curl -s -o "$E2E_TMP/dl.out" -w '%{http_code}' -b "$ADMIN" "$FILES/download?path=$(enc "$UPATH")") e2e_expect 200 "$code" "Umlautdatei laden" cmp "$E2E_TMP/dl.out" "$E2E_TMP/small2.txt" || e2e_fail "ersetzte Datei hat nicht den neuen Inhalt" # --- 3. 30 MB in 8-MiB-Stuecken durch den Next.js-Proxy --------------------------------------------- head -c 30000000 /dev/urandom > "$E2E_TMP/big.bin" BIG="$F/gross.bin" BIG_SIZE=30000000 code=$(e2e_status "$ADMIN" POST "$VIA_WEB/uploads" "{\"path\":\"$BIG\",\"size\":$BIG_SIZE}") case "$code" in 200|201) ;; *) e2e_fail "grosse Datei: Start -> $code" ;; esac e2e_contains "$E2E_TMP/body.out" '"mode":"chunked"' "grosse Datei: chunked" UP=$(jget "$E2E_TMP/body.out" 'd["uploadId"]') [ "$(jget "$E2E_TMP/body.out" 'd["chunkSize"]')" = "$CHUNK" ] || e2e_fail "chunkSize ist nicht 8388608" echo "$UP" | grep -Eq '^tessera-[0-9a-f-]{36}$' || e2e_fail "uploadId hat falsche Form: $UP" mkdir "$E2E_TMP/chunks" split -b "$CHUNK" -d -a 3 "$E2E_TMP/big.bin" "$E2E_TMP/chunks/c" n=0 for part in "$E2E_TMP"/chunks/c*; do n=$((n + 1)) code=$(curl -s -o "$E2E_TMP/body.out" -w '%{http_code}' -b "$ADMIN" -X PUT -H 'Content-Type: application/octet-stream' \ --data-binary "@$part" "$VIA_WEB/uploads/$UP/chunks/$n?path=$(enc "$BIG")&size=$BIG_SIZE") e2e_expect 200 "$code" "Stueck $n durch den Proxy" [ "$(jget "$E2E_TMP/body.out" 'd["received"]')" = "$(wc -c < "$part")" ] || e2e_fail "Stueck $n: received stimmt nicht" done [ "$n" = "4" ] || e2e_fail "30 MB sollten 4 Stuecke sein, waren $n" code=$(e2e_status "$ADMIN" POST "$VIA_WEB/uploads/$UP/complete" "{\"path\":\"$BIG\",\"size\":$BIG_SIZE,\"mtime\":1700000000}") case "$code" in 200) e2e_contains "$E2E_TMP/body.out" '"state":"done"' "complete: done" ;; 202) for _ in $(seq 1 90); do sleep 2 code=$(e2e_status "$ADMIN" GET "$VIA_WEB/uploads/$UP/state") e2e_expect 200 "$code" "Zustand abfragen" grep -q '"state":"done"' "$E2E_TMP/body.out" && break grep -q '"state":"failed"' "$E2E_TMP/body.out" && e2e_fail "Zusammenbau fehlgeschlagen: $(cat "$E2E_TMP/body.out")" done grep -q '"state":"done"' "$E2E_TMP/body.out" || e2e_fail "Zusammenbau wurde nicht fertig" ;; *) e2e_fail "complete -> $code" ;; esac code=$(e2e_status "$ADMIN" GET "$VIA_WEB/uploads/$UP/state") e2e_expect 200 "$code" "Zustand nach dem Ende" e2e_contains "$E2E_TMP/body.out" '"state":"done"' "Zustand done" # Groesse und Mtime bei der Nextcloud selbst pruefen nc_size=$(curl -s -u "$ANNA" -I "$NC_DAV/files/anna$F/gross.bin" | tr -d '\r' | awk -F': ' 'tolower($1)=="content-length"{print $2}') [ "$nc_size" = "$BIG_SIZE" ] || e2e_fail "Nextcloud meldet $nc_size Byte statt $BIG_SIZE" # Download durch den Proxy, byteidentisch; Kopfzeilen code=$(curl -s -D "$E2E_TMP/dl.hdr" -o "$E2E_TMP/big.dl" -w '%{http_code}' -b "$ADMIN" "$VIA_WEB/download?path=$(enc "$BIG")") e2e_expect 200 "$code" "grosse Datei laden (Proxy)" cmp "$E2E_TMP/big.bin" "$E2E_TMP/big.dl" || e2e_fail "heruntergeladene Datei weicht ab" grep -qi '^content-disposition: attachment; filename="gross.bin"' "$E2E_TMP/dl.hdr" || e2e_fail "content-disposition fehlt/falsch: $(grep -i content-disposition "$E2E_TMP/dl.hdr")" grep -qi '^x-content-type-options: nosniff' "$E2E_TMP/dl.hdr" || e2e_fail "nosniff fehlt" grep -qi '^content-security-policy: default-src .none.; sandbox' "$E2E_TMP/dl.hdr" || e2e_fail "CSP sandbox fehlt" if grep -qi '^set-cookie' "$E2E_TMP/dl.hdr"; then e2e_fail "set-cookie erreichte den Browser"; fi # Umlautname im Content-Disposition curl -s -D "$E2E_TMP/dl2.hdr" -o /dev/null -b "$ADMIN" "$FILES/download?path=$(enc "$UPATH")" grep -qi "^content-disposition: attachment; filename=\"_rger & _lpreis 100_.txt\"; filename\\*=UTF-8''%C3%84rger%20%26%20%C3%96lpreis%20100%25.txt" "$E2E_TMP/dl2.hdr" \ || e2e_fail "content-disposition mit Umlauten: $(grep -i content-disposition "$E2E_TMP/dl2.hdr")" # Range code=$(curl -s -D "$E2E_TMP/range.hdr" -o "$E2E_TMP/range.out" -w '%{http_code}' -b "$ADMIN" -H 'Range: bytes=0-99' "$VIA_WEB/download?path=$(enc "$BIG")") e2e_expect 206 "$code" "Range" [ "$(wc -c < "$E2E_TMP/range.out")" = "100" ] || e2e_fail "Range: nicht 100 Byte" head -c 100 "$E2E_TMP/big.bin" | cmp - "$E2E_TMP/range.out" || e2e_fail "Range: Inhalt weicht ab" grep -qi "^content-range: bytes 0-99/$BIG_SIZE" "$E2E_TMP/range.hdr" || e2e_fail "content-range fehlt: $(grep -i content-range "$E2E_TMP/range.hdr")" code=$(curl -s -o "$E2E_TMP/range2.out" -w '%{http_code}' -b "$ADMIN" -H "Range: bytes=$((BIG_SIZE - 10))-" "$FILES/download?path=$(enc "$BIG")") e2e_expect 206 "$code" "Range bis zum Ende" tail -c 10 "$E2E_TMP/big.bin" | cmp - "$E2E_TMP/range2.out" || e2e_fail "Range bis zum Ende: Inhalt weicht ab" # --- 4. Grenzen: 413, 411 -------------------------------------------------------------------------------- head -c 9000000 /dev/zero > "$E2E_TMP/nine.bin" FAKE_UP="tessera-00000000-0000-0000-0000-000000000000" code=$(curl -s -o "$E2E_TMP/body.out" -w '%{http_code}' -b "$ADMIN" -X PUT -H 'Content-Type: application/octet-stream' \ --data-binary "@$E2E_TMP/nine.bin" "$VIA_WEB/uploads/$FAKE_UP/chunks/1?path=$(enc "$F/x.bin")&size=30000000") e2e_expect 413 "$code" "9-MB-Stueck" e2e_contains "$E2E_TMP/body.out" '"code":"chunkTooLarge"' "9-MB-Stueck: chunkTooLarge" code=$(curl -s -o "$E2E_TMP/body.out" -w '%{http_code}' -b "$ADMIN" -X PUT -H 'Transfer-Encoding: chunked' -H 'Content-Type: application/octet-stream' \ --data-binary "@$E2E_TMP/small.txt" "$FILES/uploads/$FAKE_UP/chunks/1?path=$(enc "$F/x.bin")&size=30000000") e2e_expect 411 "$code" "PUT ohne content-length" e2e_contains "$E2E_TMP/body.out" '"code":"lengthRequired"' "ohne content-length: lengthRequired" code=$(e2e_status "$ADMIN" POST "$FILES/uploads" "{\"path\":\"$F/riesig.bin\",\"size\":99999999999999}") e2e_expect 413 "$code" "zu grosse Datei" e2e_contains "$E2E_TMP/body.out" '"code":"fileTooLarge"' "zu gross: fileTooLarge" code=$(curl -s -o "$E2E_TMP/body.out" -w '%{http_code}' -b "$ADMIN" -X PUT -H 'Content-Type: application/octet-stream' \ --data-binary "@$E2E_TMP/small.txt" "$FILES/uploads/tessera-..%2Fx/chunks/1?path=$(enc "$F/x.bin")&size=30000000") e2e_expect 400 "$code" "ungueltige Upload-Kennung" # --- 5. Fehlerabbildung: nie 401/403 ----------------------------------------------------------------------- code=$(curl -s -o "$E2E_TMP/body.out" -w '%{http_code}' -b "$ADMIN" "$FILES/download?path=$(enc "$F/gibt-es-nicht.bin")") e2e_expect 404 "$code" "Download einer fehlenden Datei" e2e_contains "$E2E_TMP/body.out" '"code":"notFound"' "fehlende Datei: notFound" # --- 6. ZIP ----------------------------------------------------------------------------------------------------- code=$(curl -s -D "$E2E_TMP/zip.hdr" -o "$E2E_TMP/folder.zip" -w '%{http_code}' -b "$ADMIN" "$VIA_WEB/download?path=$(enc "$F")&zip=1") e2e_expect 200 "$code" "Ordner als ZIP" [ "$(head -c 2 "$E2E_TMP/folder.zip")" = "PK" ] || e2e_fail "Ordner-ZIP beginnt nicht mit PK" grep -qi "^content-disposition: attachment; filename=\"$FNAME.zip\"" "$E2E_TMP/zip.hdr" || e2e_fail "ZIP-Name: $(grep -i content-disposition "$E2E_TMP/zip.hdr")" unzip -l "$E2E_TMP/folder.zip" | grep -q 'gross.bin' || e2e_fail "Ordner-ZIP enthaelt gross.bin nicht" # Auswahl-ZIP per POST (WR-08): die Namen im Koerper, als JSON ... jlist() { python3 -I -c 'import json,sys;print(json.dumps({"dir":sys.argv[1],"names":sys.argv[2:]}))' "$@"; } code=$(curl -s -D "$E2E_TMP/zip2.hdr" -o "$E2E_TMP/sel.zip" -w '%{http_code}' -b "$ADMIN" -X POST -H 'Content-Type: application/json' \ --data-binary "$(jlist "$F" "$UMLAUT_NAME")" "$FILES/download/zip") e2e_expect 200 "$code" "Auswahl als ZIP (JSON)" [ "$(head -c 2 "$E2E_TMP/sel.zip")" = "PK" ] || e2e_fail "Auswahl-ZIP beginnt nicht mit PK" grep -qi "^content-disposition: attachment; filename=\"$FNAME.zip\"" "$E2E_TMP/zip2.hdr" || e2e_fail "Auswahl-ZIP-Name: $(grep -i content-disposition "$E2E_TMP/zip2.hdr")" unzip -Z1 "$E2E_TMP/sel.zip" > "$E2E_TMP/sel.list" [ "$(wc -l < "$E2E_TMP/sel.list")" = "1" ] || e2e_fail "Auswahl-ZIP enthaelt nicht genau einen Eintrag: $(cat "$E2E_TMP/sel.list")" [ "$(cat "$E2E_TMP/sel.list")" = "$UMLAUT_NAME" ] || e2e_fail "Auswahl-ZIP: falscher Eintrag: $(cat "$E2E_TMP/sel.list")" # ... und wie das unsichtbare Formular der Weboberflaeche (urlencoded, names als JSON-Text), ueber /api-proxy NAMES_JSON=$(python3 -I -c 'import json,sys;print(json.dumps(sys.argv[1:]))' "$UMLAUT_NAME" gross.bin) code=$(curl -s -o "$E2E_TMP/sel2.zip" -w '%{http_code}' -b "$ADMIN" -X POST --data-urlencode "dir=$F" --data-urlencode "names=$NAMES_JSON" "$VIA_WEB/download/zip") e2e_expect 200 "$code" "Auswahl mit zwei Namen (Formular, via Web)" [ "$(unzip -Z1 "$E2E_TMP/sel2.zip" | wc -l)" = "2" ] || e2e_fail "Auswahl-ZIP mit zwei Namen hat nicht zwei Eintraege" code=$(curl -s -o "$E2E_TMP/body.out" -w '%{http_code}' -b "$ADMIN" -X POST -H 'Content-Type: application/json' \ --data-binary "$(jlist "$F" "..")" "$FILES/download/zip") e2e_expect 400 "$code" "ZIP mit Name .." e2e_contains "$E2E_TMP/body.out" '"code":"invalidPath"' "ZIP-Name ..: invalidPath" # 300 lange Namen kommen im Koerper durch Proxy und API (frueher 431/414 an Tessera). Nextcloud nimmt # die Auswahl aber nur in ihrer Adresse an (Apache: hoechstens 8190 Zeichen) -> klare 413 selectionTooLarge # von Tessera, bevor Nextcloud gefragt wird. MANY=$(python3 -I -c 'import json,sys;print(json.dumps({"dir":sys.argv[1],"names":[sys.argv[2]]+["fehlt-%03d-%s"%(i,"x"*60) for i in range(299)]}))' "$F" "$UMLAUT_NAME") code=$(curl -s -o "$E2E_TMP/body.out" -w '%{http_code}' -b "$ADMIN" -X POST -H 'Content-Type: application/json' --data-binary "$MANY" "$VIA_WEB/download/zip") e2e_expect 413 "$code" "300 lange Namen" e2e_contains "$E2E_TMP/body.out" '"code":"selectionTooLarge"' "300 lange Namen: selectionTooLarge" # Die alte Adressform gibt es nicht mehr code=$(curl -s -o /dev/null -w '%{http_code}' -b "$ADMIN" -G --data-urlencode "dir=$F" --data-urlencode "name=gross.bin" "$FILES/download/zip") e2e_expect 404 "$code" "GET download/zip gibt es nicht mehr" # Vorabpruefung (IN-06): { ok: true } oder der Fehler, ohne Datenstrom code=$(e2e_status "$ADMIN" GET "$FILES/download?path=$(enc "$F/gross.bin")&check=1") e2e_expect 200 "$code" "Vorabpruefung Datei" e2e_contains "$E2E_TMP/body.out" '"ok":true' "Vorabpruefung Datei ok" code=$(e2e_status "$ADMIN" GET "$FILES/download?path=$(enc "$F/gibt-es-nicht.bin")&check=1") e2e_expect 404 "$code" "Vorabpruefung fehlende Datei" code=$(e2e_status "$ADMIN" POST "$FILES/download/zip" "$(jlist "$F" gross.bin | python3 -I -c 'import json,sys;d=json.load(sys.stdin);d["check"]=True;print(json.dumps(d))')") e2e_expect 200 "$code" "Vorabpruefung Auswahl-ZIP" e2e_contains "$E2E_TMP/body.out" '"ok":true' "Vorabpruefung Auswahl-ZIP ok" # --- 7. Abbrechen raeumt auf ----------------------------------------------------------------------------------------- code=$(e2e_status "$ADMIN" POST "$FILES/uploads" "{\"path\":\"$F/abbruch.bin\",\"size\":$BIG_SIZE}") case "$code" in 200|201) ;; *) e2e_fail "Abbruch-Upload starten -> $code" ;; esac UP2=$(jget "$E2E_TMP/body.out" 'd["uploadId"]') code=$(curl -s -o /dev/null -w '%{http_code}' -u "$ANNA" -X PROPFIND -H 'Depth: 0' "$NC_DAV/uploads/anna/$UP2") e2e_expect 207 "$code" "Upload-Ordner liegt bei Nextcloud" code=$(e2e_status "$ADMIN" DELETE "$FILES/uploads/$UP2") e2e_expect 200 "$code" "Upload abbrechen" code=$(curl -s -o /dev/null -w '%{http_code}' -u "$ANNA" -X PROPFIND -H 'Depth: 0' "$NC_DAV/uploads/anna/$UP2") e2e_expect 404 "$code" "Upload-Ordner nach dem Abbrechen" code=$(e2e_status "$ADMIN" GET "$FILES/uploads/$UP2/state") e2e_expect 404 "$code" "Zustand eines unbekannten Uploads" # --- 8. Zwei Benutzer teilen keinen Zustand: ohne Anmeldung gar nichts ------------------------------------------------ code=$(curl -s -o /dev/null -w '%{http_code}' "$FILES/download?path=$(enc "$UPATH")") e2e_expect 401 "$code" "Download ohne Tessera-Anmeldung" # --- Aufraeumen --------------------------------------------------------------------------------------------------------------- code=$(curl -s -o "$E2E_TMP/body.out" -w '%{http_code}' -b "$ADMIN" -X DELETE -G --data-urlencode "path=$F" "$FILES/files") e2e_expect 200 "$code" "Testordner loeschen" code=$(e2e_status "$ADMIN" DELETE "$FILES/connect") e2e_expect 200 "$code" "anna trennen" echo "e2e transfer ok"