2025-10-03 08:41:37 -04:00
|
|
|
import { Request } from "express";
|
|
|
|
|
import { WebDAVClient } from "webdav";
|
|
|
|
|
import { DIRS } from "./server";
|
2025-10-11 11:29:11 -04:00
|
|
|
import fetch from 'node-fetch'
|
|
|
|
|
import { XMLParser } from 'fast-xml-parser';
|
2025-10-03 08:41:37 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function statOne(fpath: string, client: WebDAVClient) {
|
|
|
|
|
const parent = fpath.replace(/\/[^/]+$/, '') || '/',
|
|
|
|
|
base = fpath.split('/').filter(Boolean).pop(),
|
2025-10-11 11:29:11 -04:00
|
|
|
list = await client.getDirectoryContents(parent, { deep: false }).catch(err => {
|
|
|
|
|
console.error(`error for "${parent}":`);
|
|
|
|
|
console.error(err);
|
|
|
|
|
|
|
|
|
|
if (err.response?.status === 404) {
|
|
|
|
|
throw `Path "${fpath}" not found`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw err;
|
|
|
|
|
}),
|
2025-10-03 08:41:37 -04:00
|
|
|
entry = Array.isArray(list) ? list.find((e: any) => e.basename === base) : undefined;
|
|
|
|
|
|
2025-10-11 11:29:11 -04:00
|
|
|
if (!entry) throw `Path "${fpath}" not found`;
|
2025-10-03 08:41:37 -04:00
|
|
|
return { etag: entry.etag as string, size: Number(entry.size), mime: entry.mime as string | undefined };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const toRegExp = (spec: string): RegExp | null => {
|
|
|
|
|
// slash-delimited form: /pattern/flags
|
|
|
|
|
const m = spec.match(/^\/(.+)\/([a-z]*)$/i);
|
|
|
|
|
try {
|
|
|
|
|
if (m) {
|
|
|
|
|
const [, source, flags] = m;
|
|
|
|
|
return new RegExp(source, flags);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// raw pattern form: "pattern"
|
|
|
|
|
return new RegExp(spec);
|
|
|
|
|
} catch {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-11 11:29:11 -04:00
|
|
|
export function checkIfHasPerms(req: Request | string) {
|
|
|
|
|
try {
|
|
|
|
|
if (typeof req === 'string') {
|
|
|
|
|
if (req.includes('..')) return false;
|
|
|
|
|
|
|
|
|
|
return !DIRS || DIRS.find(r => req.match(r));
|
|
|
|
|
}
|
|
|
|
|
else if (!req.body) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { fpath, obj_type, deep, usecache } = req.body,
|
|
|
|
|
o = { fpath, obj_type, deep, usecache };
|
|
|
|
|
|
|
|
|
|
if (!fpath) return false;
|
|
|
|
|
if (!DIRS) return o;
|
|
|
|
|
if (fpath.includes('..')) return false;
|
|
|
|
|
|
|
|
|
|
return DIRS.find(r => fpath.match(r)) ? o : false;
|
|
|
|
|
}
|
|
|
|
|
catch (err) {
|
|
|
|
|
console.error(err);
|
2025-10-03 08:41:37 -04:00
|
|
|
return false;
|
|
|
|
|
}
|
2025-10-11 11:29:11 -04:00
|
|
|
}
|
2025-10-03 08:41:37 -04:00
|
|
|
|
2025-10-11 11:29:11 -04:00
|
|
|
function buildCountPropfindXml() {
|
|
|
|
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
<d:propfind xmlns:d="DAV:"
|
|
|
|
|
xmlns:oc="http://owncloud.org/ns"
|
|
|
|
|
xmlns:nc="http://nextcloud.org/ns">
|
|
|
|
|
<d:prop>
|
|
|
|
|
<nc:contained-file-count/>
|
|
|
|
|
<nc:contained-folder-count/>
|
|
|
|
|
<oc:contained-file-count/>
|
|
|
|
|
<oc:contained-folder-count/>
|
|
|
|
|
</d:prop>
|
|
|
|
|
</d:propfind>`;
|
2025-10-03 08:41:37 -04:00
|
|
|
}
|
|
|
|
|
|
2025-10-11 11:29:11 -04:00
|
|
|
const authHeaders = (() => {
|
|
|
|
|
const u = process.env.NEXTCLOUD_APP_ID!,
|
|
|
|
|
p = process.env.NEXTCLOUD_APP_PASS!,
|
|
|
|
|
token = Buffer.from(`${u}:${p}`).toString("base64");
|
|
|
|
|
return `Basic ${token}`;
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
export async function getDirectoryFileCount(dirPath: string, client: WebDAVClient) {
|
|
|
|
|
const url = client.getFileDownloadLink(dirPath),
|
|
|
|
|
xml = buildCountPropfindXml();
|
|
|
|
|
|
|
|
|
|
const resp = await fetch(url, {
|
|
|
|
|
method: "PROPFIND",
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "text/xml",
|
|
|
|
|
"Depth": "0",
|
|
|
|
|
"Authorization": authHeaders
|
|
|
|
|
},
|
|
|
|
|
body: xml
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!resp.ok) {
|
|
|
|
|
throw resp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// parse XML
|
|
|
|
|
const parser = new XMLParser({
|
|
|
|
|
ignoreAttributes: false,
|
|
|
|
|
attributeNamePrefix: "@_",
|
|
|
|
|
allowBooleanAttributes: true,
|
|
|
|
|
numberParseOptions: {
|
|
|
|
|
hex: false,
|
|
|
|
|
leadingZeros: false
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
text = await resp.text(),
|
|
|
|
|
doc = parser.parse(text),
|
|
|
|
|
objs = doc['d:multistatus']['d:response']['d:propstat'],
|
|
|
|
|
obj = objs.find((o: any) => o['d:status'].includes("200"))['d:prop'];
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
file_count: obj['nc:contained-file-count'],
|
|
|
|
|
folder: obj['nc:contained-folder-count']
|
|
|
|
|
};
|
|
|
|
|
}
|