78f6cf358d
- Pfade, Ziele und Empfaengerkennungen der Freigaben bleiben woertlich (nur Anzeigetexte werden bereinigt) - Weitergaben als eigene Freigaben mit eigenem Pfad; accessOf nach Eintragsart - Begrenzung: 10 neue Freigaben je 10 Minuten, 40 Versuche je 10 Minuten vor den Abfragen an die Nextcloud, leere Zaehler werden entfernt - Freigaberegeln nennen das Serverdatum, die Ordnerliste die Berechtigungsbuchstaben des Ordners selbst - Live-Test: Ablehnen offener Freigaben, Weitergabe, Buchstaben des Ordners Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
525 lines
20 KiB
TypeScript
525 lines
20 KiB
TypeScript
import type { NextcloudCallGate } from './nextcloud-call-gate';
|
|
import type { NcSession } from './nextcloud-files.types';
|
|
import {
|
|
discardBody,
|
|
type NcFailure,
|
|
type NextcloudTransport,
|
|
ncRequest,
|
|
readCappedText,
|
|
} from './nextcloud-http';
|
|
|
|
/**
|
|
* Freigaben-Schicht des Moduls "Nextcloud-Dateien" (quick-261009-dkv, D-11/D-12):
|
|
* die OCS-Schnittstelle fuer Freigaben, Empfaengersuche und die Freigaberegeln
|
|
* (`cloud/capabilities`) auf Basis von `ncRequest`. Es gelten unveraendert die
|
|
* Regeln der Etappe 1: fester Pfadanfang `/ocs/v2.php/`, Segmente einzeln codiert,
|
|
* keine Weiterleitungen, keine Cookies, und es wird NIE eine Adresse aus einer
|
|
* Nextcloud-Antwort aufgerufen (weder `url` einer Freigabe noch `api.generate`
|
|
* aus den Faehigkeiten).
|
|
*
|
|
* Warum nicht `ocsRequest` aus `nextcloud-auth-client.ts`: der Anmeldecode
|
|
* verlaesst sich darauf, dass ein 403 als `app-password-given` gilt und der Text
|
|
* einer Fehlerantwort verworfen wird. Bei Freigaben steht genau dort die
|
|
* Begruendung (`ocs.meta.message`), und ein 403 heisst "nicht erlaubt". Darum ein
|
|
* eigener Aufruf, der den Koerper bei JEDEM Status liest (Erfolg: bis 8 MiB,
|
|
* Fehler: bis 64 KiB, Faehigkeiten: bis 1 MiB).
|
|
*
|
|
* Nie loggen oder weitergeben: Passwoerter, Kennungen (`token`) und Link-Adressen.
|
|
* Die Parser bauen kleine eigene Ansichten; unbekannte Felder (Speicherkennungen,
|
|
* `attributes`, `mail_send` ...) werden nie kopiert.
|
|
*/
|
|
|
|
export type NcShareKind = 'user' | 'group' | 'link';
|
|
export type NcShareAccess = 'view' | 'edit' | 'upload' | 'custom';
|
|
export type NcItemType = 'file' | 'folder';
|
|
|
|
/** Eine Freigabe, wie der Browser sie sieht. */
|
|
export interface NcShareView {
|
|
id: string;
|
|
kind: NcShareKind;
|
|
/**
|
|
* Pfad im Baum des Aufrufers, woertlich wie von der Nextcloud geliefert (nie bereinigt: er geht
|
|
* zurueck an die Nextcloud, ein zusammengefasstes Leerzeichen waere ein anderer Eintrag).
|
|
*/
|
|
path: string;
|
|
name: string;
|
|
itemType: NcItemType;
|
|
mime: string | null;
|
|
/** Der Eintrag selbst erlaubt Aendern/Anlegen (Berechtigungsbits 2 oder 4). */
|
|
itemWritable: boolean;
|
|
permissions: number;
|
|
access: NcShareAccess;
|
|
/** Kennung von Person/Gruppe; bei Links null. */
|
|
shareWith: string | null;
|
|
shareWithName: string | null;
|
|
/**
|
|
* Wer die Freigabe erstellt hat (`uid_owner` der Nextcloud heisst dort "Freigebender", nicht
|
|
* "Eigentuemer der Datei"; gemessen). Bei eigenen Freigaben ist das der Benutzer selbst.
|
|
*/
|
|
ownerId: string | null;
|
|
ownerName: string | null;
|
|
/** Anzeigename des Dateieigentuemers, nur wenn er ein anderer ist (Weitergabe, siehe `parseShare`). */
|
|
fileOwnerName: string | null;
|
|
canEdit: boolean;
|
|
canDelete: boolean;
|
|
/** `YYYY-MM-DD` oder null. */
|
|
expiration: string | null;
|
|
label: string;
|
|
/** Nur bei eigenen Links, nur http/https. */
|
|
url: string | null;
|
|
hasPassword: boolean;
|
|
/** Pfad im eigenen Baum des Aufrufers (bei eigenen Freigaben und Weitergaben gleich `path`), woertlich. */
|
|
target: string;
|
|
sharedAt: string | null;
|
|
/** Noch nicht angenommen (nur eingehende). */
|
|
pending?: boolean;
|
|
}
|
|
|
|
export interface NcSharee {
|
|
kind: 'user' | 'group';
|
|
id: string;
|
|
label: string;
|
|
detail: string | null;
|
|
}
|
|
|
|
export interface NcSharePolicy {
|
|
/** Heutiges Datum (`YYYY-MM-DD`) nach der Uhr des Tessera-Servers: Grundlage fuer Ablaufgrenzen. */
|
|
today: string;
|
|
/** Freigabe-Schnittstelle der Nextcloud an. */
|
|
enabled: boolean;
|
|
groupsEnabled: boolean;
|
|
links: {
|
|
enabled: boolean;
|
|
passwordRequired: boolean;
|
|
passwordSuggested: boolean;
|
|
/** Tage des Standard-Ablaufs (nur wenn die Nextcloud einen vorgibt). */
|
|
expiryDefaultDays: number | null;
|
|
expiryEnforced: boolean;
|
|
uploadAllowed: boolean;
|
|
multipleLinks: boolean;
|
|
};
|
|
/** Regeln fuer Personen- und Gruppenfreigaben (nur zur Anzeige). */
|
|
internalExpiry: { defaultDays: number | null; enforced: boolean };
|
|
minSearchLength: number;
|
|
passwordMinLength: number | null;
|
|
}
|
|
|
|
// --- Grenzen -------------------------------------------------------------------------------
|
|
|
|
export const SHARES_BASE_SEGMENTS = ['apps', 'files_sharing', 'api', 'v1', 'shares'] as const;
|
|
export const SHAREE_SEGMENTS = ['apps', 'files_sharing', 'api', 'v1', 'sharees'] as const;
|
|
export const CAPABILITIES_SEGMENTS = ['cloud', 'capabilities'] as const;
|
|
|
|
export const OCS_OK_MAX_BYTES = 8 * 1024 * 1024;
|
|
export const OCS_ERROR_MAX_BYTES = 64 * 1024;
|
|
export const OCS_CAPABILITIES_MAX_BYTES = 1024 * 1024;
|
|
export const MAX_SHARES = 2000;
|
|
export const MAX_SHAREES = 25;
|
|
const SHARE_TIMEOUT_MS = 15_000;
|
|
const MESSAGE_MAX = 300;
|
|
const DISPLAY_MAX = 255;
|
|
const URL_MAX = 2048;
|
|
const PATH_MAX = 4096;
|
|
|
|
const SHARE_ID_RE = /^\d{1,20}$/;
|
|
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
|
|
// --- Hilfen ----------------------------------------------------------------------------------
|
|
|
|
type Dict = Record<string, unknown>;
|
|
|
|
function isDict(value: unknown): value is Dict {
|
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
}
|
|
|
|
/** Steuerzeichen entfernen, Leerraum zusammenfassen, kuerzen — fuer alles, was angezeigt wird. */
|
|
export function cleanText(value: unknown, max: number): string {
|
|
if (typeof value !== 'string') return '';
|
|
return (
|
|
value
|
|
// biome-ignore lint/suspicious/noControlCharactersInRegex: Steuerzeichen sind hier gerade der Pruefstoff
|
|
.replace(/[\u0000-\u001f\u007f]/g, ' ')
|
|
.replace(/\s+/g, ' ')
|
|
.trim()
|
|
.slice(0, max)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Pfade und Kennungen (Freigabe-Pfad, Ziel, Empfaenger) gehen unveraendert an die Nextcloud
|
|
* zurueck. Sie werden darum NIE zusammengefasst oder gekuerzt wie Anzeigetexte: ein doppeltes
|
|
* oder nachgestelltes Leerzeichen gehoert zum Namen. Enthalten sie Steuerzeichen oder sind sie
|
|
* zu lang, gelten sie als unbrauchbar (leere Zeichenkette).
|
|
*/
|
|
export function verbatimId(value: unknown, max: number): string {
|
|
if (typeof value !== 'string' || value.length > max) return '';
|
|
// biome-ignore lint/suspicious/noControlCharactersInRegex: Steuerzeichen sind hier gerade der Pruefstoff
|
|
return /[\u0000-\u001f\u007f]/.test(value) ? '' : value;
|
|
}
|
|
|
|
function intOf(value: unknown): number | null {
|
|
if (typeof value === 'number' && Number.isFinite(value)) return Math.trunc(value);
|
|
if (typeof value === 'string' && /^-?\d{1,15}$/.test(value.trim())) return Number(value.trim());
|
|
return null;
|
|
}
|
|
|
|
export function isShareId(value: unknown): value is string {
|
|
return typeof value === 'string' && SHARE_ID_RE.test(value);
|
|
}
|
|
|
|
// --- Aufruf -----------------------------------------------------------------------------------
|
|
|
|
export interface OcsShareOptions {
|
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
segments: readonly string[];
|
|
query?: Record<string, string>;
|
|
/** JSON-Koerper (POST/PUT); Passwoerter stehen nie in einer Adresse. */
|
|
json?: Record<string, unknown>;
|
|
/** Obergrenze fuer eine erfolgreiche Antwort (Standard 8 MiB). */
|
|
maxBytes?: number;
|
|
}
|
|
|
|
export type OcsShareResult =
|
|
| { ok: true; status: number; message: string | null; data: unknown }
|
|
| NcFailure;
|
|
|
|
/**
|
|
* OCS-Aufruf mit dem Zugang der Sitzung. Liefert fuer JEDEN HTTP-Status
|
|
* `{ ok: true, status, message, data }` (auch 4xx/5xx); Transportfehler, 401
|
|
* (`credential-dead`) und 429 (Sperre) kommen wie bei `ncRequest` als Fehlerergebnis.
|
|
*/
|
|
export async function ocsShareRequest(
|
|
transport: NextcloudTransport,
|
|
gate: NextcloudCallGate,
|
|
session: NcSession,
|
|
opts: OcsShareOptions,
|
|
): Promise<OcsShareResult> {
|
|
const res = await ncRequest(transport, gate, {
|
|
baseUrl: session.baseUrl,
|
|
prefix: '/ocs/v2.php/',
|
|
segments: opts.segments,
|
|
query: opts.query,
|
|
method: opts.method,
|
|
ocs: true,
|
|
authorization: session.authorization,
|
|
credentialKey: session.credentialKey,
|
|
...(opts.json !== undefined
|
|
? { headers: { 'content-type': 'application/json' }, body: JSON.stringify(opts.json) }
|
|
: {}),
|
|
headersTimeoutMs: SHARE_TIMEOUT_MS,
|
|
bodyTimeoutMs: SHARE_TIMEOUT_MS,
|
|
});
|
|
if (!res.ok) return res;
|
|
|
|
const success = res.status >= 200 && res.status < 300;
|
|
const text = await readCappedText(
|
|
res.body,
|
|
success ? (opts.maxBytes ?? OCS_OK_MAX_BYTES) : OCS_ERROR_MAX_BYTES,
|
|
);
|
|
if (!text.ok) {
|
|
discardBody(res.body);
|
|
// Eine zu grosse Fehlerantwort bleibt ein Fehlerstatus, nur ohne Text.
|
|
if (!success && text.kind === 'too-large') {
|
|
return { ok: true, status: res.status, message: null, data: null };
|
|
}
|
|
return { ok: false, kind: text.kind, detail: text.detail };
|
|
}
|
|
|
|
let ocs: Dict | null = null;
|
|
try {
|
|
const parsed: unknown = JSON.parse(text.text);
|
|
if (isDict(parsed) && isDict(parsed.ocs)) ocs = parsed.ocs;
|
|
} catch {
|
|
// kein JSON
|
|
}
|
|
if (ocs === null) {
|
|
if (success) return { ok: false, kind: 'invalid-response' };
|
|
return { ok: true, status: res.status, message: null, data: null };
|
|
}
|
|
const meta = isDict(ocs.meta) ? ocs.meta : {};
|
|
const message = cleanText(meta.message, MESSAGE_MAX);
|
|
return { ok: true, status: res.status, message: message === '' ? null : message, data: ocs.data };
|
|
}
|
|
|
|
// --- Berechtigungen -----------------------------------------------------------------------------
|
|
|
|
const READ = 1;
|
|
const UPDATE = 2;
|
|
const CREATE = 4;
|
|
const DELETE = 8;
|
|
|
|
/**
|
|
* Berechtigungsmaske fuer die einfache Auswahl (D-11): Ansehen 1; Bearbeiten Ordner 15 /
|
|
* Datei 3; Nur hochladen 4 (nur Ordner, sonst null). Das Teilen-Bit (16) wird nie gesendet.
|
|
*/
|
|
export function permissionsFor(
|
|
access: 'view' | 'edit' | 'upload',
|
|
itemType: NcItemType,
|
|
): number | null {
|
|
if (access === 'view') return READ;
|
|
if (access === 'edit')
|
|
return itemType === 'folder' ? READ | UPDATE | CREATE | DELETE : READ | UPDATE;
|
|
return itemType === 'folder' ? CREATE : null;
|
|
}
|
|
|
|
/**
|
|
* Umkehrung: aus der Maske (Bit 16 wird ignoriert) die einfache Auswahl oder `custom`. Nur die
|
|
* Masken, die `permissionsFor` selbst vergibt, gelten als Auswahl: Ansehen 1; Bearbeiten 15
|
|
* (Ordner) bzw. 3 (Datei); Nur hochladen 4 (nur Ordner). Alles andere (z. B. Lesen plus Loeschen
|
|
* oder Lesen plus Anlegen) ist eine eigene Berechtigung und wird so angezeigt, nie als
|
|
* Bearbeiten ausgegeben.
|
|
*/
|
|
export function accessOf(permissions: number, itemType: NcItemType): NcShareAccess {
|
|
const mask = permissions & 15;
|
|
if (mask === READ) return 'view';
|
|
if (itemType === 'folder' && mask === CREATE) return 'upload';
|
|
if (mask === (itemType === 'folder' ? READ | UPDATE | CREATE | DELETE : READ | UPDATE)) {
|
|
return 'edit';
|
|
}
|
|
return 'custom';
|
|
}
|
|
|
|
// --- Parser -------------------------------------------------------------------------------------
|
|
|
|
function lastSegment(path: string): string {
|
|
const parts = path.split('/').filter((p) => p !== '');
|
|
return parts.length > 0 ? parts[parts.length - 1] : '';
|
|
}
|
|
|
|
export function isRealDate(value: string): boolean {
|
|
if (!DATE_RE.test(value)) return false;
|
|
const d = new Date(`${value}T00:00:00Z`);
|
|
return !Number.isNaN(d.getTime()) && d.toISOString().slice(0, 10) === value;
|
|
}
|
|
|
|
/** Datum `YYYY-MM-DD` nach der Uhr dieses Servers (nicht des Browsers): Grundlage der Ablaufgrenzen. */
|
|
export function serverDate(now: Date = new Date()): string {
|
|
const pad = (n: number, width = 2) => String(n).padStart(width, '0');
|
|
return `${pad(now.getFullYear(), 4)}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
|
}
|
|
|
|
function publicUrl(value: unknown): string | null {
|
|
if (typeof value !== 'string' || value.length === 0 || value.length > URL_MAX) return null;
|
|
try {
|
|
const u = new URL(value);
|
|
return u.protocol === 'http:' || u.protocol === 'https:' ? value : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* `null` fuer nicht unterstuetzte Arten (E-Mail, Server, Talk ...) und fehlerhafte Eintraege.
|
|
*
|
|
* Eingehend/eigen (gemessen, Nextcloud 34): `uid_owner` ist der FREIGEBENDE, `uid_file_owner`
|
|
* der Eigentuemer der Datei. Eine Weitergabe (Ben gibt einen Ordner weiter, den Anna ihm
|
|
* geteilt hat) hat `uid_owner = ben`, `uid_file_owner = anna`; `path` ist immer der Pfad im
|
|
* Baum des Aufrufers, `file_target` der im Baum des Empfaengers. Weitergaben gelten daher als
|
|
* eigene Freigaben mit eigenem Pfad (`target` = `path`), nicht als eingehende.
|
|
*/
|
|
export function parseShare(raw: unknown, selfId: string): NcShareView | null {
|
|
if (!isDict(raw)) return null;
|
|
const type = intOf(raw.share_type);
|
|
const kind: NcShareKind | null =
|
|
type === 0 ? 'user' : type === 1 ? 'group' : type === 3 ? 'link' : null;
|
|
if (kind === null) return null;
|
|
const id = typeof raw.id === 'number' ? String(raw.id) : raw.id;
|
|
if (!isShareId(id)) return null;
|
|
|
|
const ownerId = cleanText(raw.uid_owner, DISPLAY_MAX) || null;
|
|
const received = ownerId !== null && ownerId !== selfId;
|
|
const rawTarget = verbatimId(raw.file_target, PATH_MAX);
|
|
const rawPath = verbatimId(raw.path, PATH_MAX);
|
|
// Ohne brauchbaren Pfad laesst sich nichts damit tun: wie eine unbekannte Art nur gezaehlt.
|
|
if (rawPath === '' && rawTarget === '') return null;
|
|
const path = rawPath || rawTarget;
|
|
const target = received ? rawTarget || rawPath : path;
|
|
const itemType: NcItemType =
|
|
raw.item_type === 'folder' || raw.mimetype === 'httpd/unix-directory' ? 'folder' : 'file';
|
|
const name = lastSegment(received ? target : path);
|
|
const permissions = intOf(raw.permissions) ?? 0;
|
|
const itemPermissions = intOf(raw.item_permissions) ?? permissions;
|
|
const expirationRaw = typeof raw.expiration === 'string' ? raw.expiration.slice(0, 10) : '';
|
|
const stime = intOf(raw.stime);
|
|
const sharedAt =
|
|
stime !== null && stime > 0 && Number.isFinite(new Date(stime * 1000).getTime())
|
|
? new Date(stime * 1000).toISOString()
|
|
: null;
|
|
const mimeRaw = cleanText(raw.mimetype, DISPLAY_MAX);
|
|
const fileOwnerId = cleanText(raw.uid_file_owner, DISPLAY_MAX);
|
|
const reshare = !received && fileOwnerId !== '' && fileOwnerId !== selfId;
|
|
const shareWith = kind === 'link' ? '' : verbatimId(raw.share_with, DISPLAY_MAX);
|
|
|
|
return {
|
|
id,
|
|
kind,
|
|
path,
|
|
name,
|
|
itemType,
|
|
mime: itemType === 'file' && mimeRaw !== '' ? mimeRaw : null,
|
|
itemWritable: (itemPermissions & (UPDATE | CREATE)) !== 0,
|
|
permissions,
|
|
access: accessOf(permissions, itemType),
|
|
shareWith: shareWith === '' ? null : shareWith,
|
|
shareWithName:
|
|
kind === 'link' ? null : cleanText(raw.share_with_displayname, DISPLAY_MAX) || null,
|
|
ownerId,
|
|
ownerName: cleanText(raw.displayname_owner, DISPLAY_MAX) || null,
|
|
fileOwnerName: reshare
|
|
? cleanText(raw.displayname_file_owner, DISPLAY_MAX) || fileOwnerId
|
|
: null,
|
|
canEdit: raw.can_edit === true,
|
|
canDelete: raw.can_delete === true,
|
|
expiration: isRealDate(expirationRaw) ? expirationRaw : null,
|
|
label: cleanText(raw.label, DISPLAY_MAX),
|
|
url: kind === 'link' && ownerId === selfId ? publicUrl(raw.url) : null,
|
|
// Nextcloud sendet beim Passwort nie den Wert, nur den Text "redacted" — hier zaehlt nur, DASS eins gesetzt ist.
|
|
hasPassword: kind === 'link' && typeof raw.password === 'string' && raw.password !== '',
|
|
target,
|
|
sharedAt,
|
|
};
|
|
}
|
|
|
|
export interface ParsedShares {
|
|
shares: NcShareView[];
|
|
/** Eintraege anderer Arten (E-Mail, Server ...), nur als Zahl. */
|
|
hidden: number;
|
|
truncated: boolean;
|
|
}
|
|
|
|
/** `data` ist bei Listen ein Feld, bei POST/PUT ein einzelnes Objekt. */
|
|
export function parseShareList(data: unknown, selfId: string): ParsedShares {
|
|
const items = Array.isArray(data) ? data : isDict(data) ? [data] : [];
|
|
const shares: NcShareView[] = [];
|
|
let hidden = 0;
|
|
let truncated = false;
|
|
for (const item of items) {
|
|
const share = parseShare(item, selfId);
|
|
if (share === null) {
|
|
hidden += 1;
|
|
} else if (shares.length >= MAX_SHARES) {
|
|
truncated = true;
|
|
} else {
|
|
shares.push(share);
|
|
}
|
|
}
|
|
return { shares, hidden, truncated };
|
|
}
|
|
|
|
function shareeList(value: unknown, wanted: 0 | 1): NcSharee[] {
|
|
if (!Array.isArray(value)) return [];
|
|
const out: NcSharee[] = [];
|
|
for (const item of value) {
|
|
if (!isDict(item) || !isDict(item.value)) continue;
|
|
if (intOf(item.value.shareType) !== wanted) continue;
|
|
// Die Kennung geht unveraendert zurueck an die Nextcloud (Anlegen): nie bereinigen.
|
|
const id = verbatimId(item.value.shareWith, DISPLAY_MAX);
|
|
if (id === '') continue;
|
|
const label = cleanText(item.label, DISPLAY_MAX) || id;
|
|
const unique = cleanText(item.shareWithDisplayNameUnique, DISPLAY_MAX);
|
|
out.push({
|
|
kind: wanted === 0 ? 'user' : 'group',
|
|
id,
|
|
label,
|
|
detail: wanted === 0 ? unique || id : null,
|
|
});
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** Treffer der Empfaengersuche: nur Personen und Gruppen, genaue und weitere Treffer zusammen, ohne Doppelte. */
|
|
export function parseSharees(data: unknown): NcSharee[] {
|
|
if (!isDict(data)) return [];
|
|
const exact = isDict(data.exact) ? data.exact : {};
|
|
const all = [
|
|
...shareeList(exact.users, 0),
|
|
...shareeList(data.users, 0),
|
|
...shareeList(exact.groups, 1),
|
|
...shareeList(data.groups, 1),
|
|
];
|
|
const seen = new Set<string>();
|
|
const out: NcSharee[] = [];
|
|
for (const s of all) {
|
|
const key = `${s.kind}:${s.id}`;
|
|
if (seen.has(key)) continue;
|
|
seen.add(key);
|
|
out.push(s);
|
|
if (out.length >= MAX_SHAREES) break;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function days(value: unknown): number | null {
|
|
const n = intOf(value);
|
|
return n !== null && n >= 1 ? Math.min(n, 3650) : null;
|
|
}
|
|
|
|
/**
|
|
* Freigaberegeln aus `cloud/capabilities` (Antwort-`data` oder dessen `capabilities`); `today`
|
|
* ist das Serverdatum (`serverDate`) und wird unveraendert in die Regeln uebernommen.
|
|
* Fehlende Schluessel heissen "keine Regel", kein Fehler: ohne `expire_date.enabled` gibt
|
|
* es keine Tage; sind Links aus, steht unter `public` nur `enabled: false`.
|
|
*/
|
|
export function parseSharePolicy(data: unknown, today: string): NcSharePolicy {
|
|
const caps = isDict(data) && isDict(data.capabilities) ? data.capabilities : data;
|
|
const files = isDict(caps) && isDict(caps.files_sharing) ? caps.files_sharing : null;
|
|
const pwPolicy = isDict(caps) && isDict(caps.password_policy) ? caps.password_policy : null;
|
|
const minLen = pwPolicy ? intOf(pwPolicy.minLength) : null;
|
|
const passwordMinLength = minLen !== null && minLen >= 1 && minLen <= 256 ? minLen : null;
|
|
|
|
const off: NcSharePolicy = {
|
|
today,
|
|
enabled: false,
|
|
groupsEnabled: false,
|
|
links: {
|
|
enabled: false,
|
|
passwordRequired: false,
|
|
passwordSuggested: false,
|
|
expiryDefaultDays: null,
|
|
expiryEnforced: false,
|
|
uploadAllowed: false,
|
|
multipleLinks: false,
|
|
},
|
|
internalExpiry: { defaultDays: null, enforced: false },
|
|
minSearchLength: 0,
|
|
passwordMinLength,
|
|
};
|
|
if (files === null || files.api_enabled === false) return off;
|
|
|
|
const sharee = isDict(files.sharee) ? files.sharee : {};
|
|
const min = intOf(sharee.minSearchStringLength);
|
|
const minSearchLength = min !== null ? Math.min(Math.max(min, 0), 32) : 0;
|
|
|
|
const pub = isDict(files.public) ? files.public : {};
|
|
const linksEnabled = pub.enabled === true;
|
|
const expiry = (value: unknown): { defaultDays: number | null; enforced: boolean } => {
|
|
const e = isDict(value) ? value : {};
|
|
if (e.enabled !== true) return { defaultDays: null, enforced: false };
|
|
return { defaultDays: days(e.days), enforced: e.enforced === true };
|
|
};
|
|
const password = isDict(pub.password) ? pub.password : {};
|
|
const passwordRequired = linksEnabled && password.enforced === true;
|
|
const link = expiry(pub.expire_date);
|
|
const internal = expiry(pub.expire_date_internal);
|
|
|
|
return {
|
|
today,
|
|
enabled: true,
|
|
groupsEnabled: files.group_sharing !== false,
|
|
links: linksEnabled
|
|
? {
|
|
enabled: true,
|
|
passwordRequired,
|
|
passwordSuggested: !passwordRequired && password.askForOptionalPassword === true,
|
|
expiryDefaultDays: link.defaultDays,
|
|
expiryEnforced: link.enforced,
|
|
uploadAllowed: pub.upload === true,
|
|
multipleLinks: pub.multiple_links !== false,
|
|
}
|
|
: off.links,
|
|
internalExpiry: internal,
|
|
minSearchLength,
|
|
passwordMinLength,
|
|
};
|
|
}
|