hfs 3.3.2 → 3.4.0-alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/assets/AccountsPage-C6J2hRcY.js +4 -0
- package/admin/assets/ConfigFilePage-D5TZZyMR.js +4 -0
- package/admin/assets/CustomHtmlPage-DV2-ufWZ.js +2 -0
- package/admin/assets/InternetPage-DT3wSQYQ.js +6 -0
- package/admin/assets/LangPage-DbXCv7wR.js +2 -0
- package/admin/assets/LogsPage-DmAxUZpu.js +3 -0
- package/admin/assets/MonitorPage-D7ydOeq_.js +2 -0
- package/admin/assets/OptionsPage-DpN3ajRm.js +3 -0
- package/admin/assets/PluginsPage-BYzfwuvg.js +1 -0
- package/admin/assets/icons-CfBG9vvB.js +1 -0
- package/admin/assets/index-CVCnYMoB.js +755 -0
- package/admin/assets/index-mI9kZlpb.css +1 -0
- package/admin/assets/routing-C4l8rnk_.js +1 -0
- package/admin/index.html +2 -2
- package/central.json +100 -1
- package/frontend/assets/{dist-legacy-zQdkj0Z9.js → dist-legacy-CKczq_fx.js} +1 -1
- package/frontend/assets/index-legacy-ZYae30AY.js +1 -0
- package/frontend/assets/{lib-legacy-CRrFEEqm.js → lib-legacy-D2tyILlj.js} +1 -1
- package/frontend/assets/{sha512-legacy-BB7Mdt0y.js → sha512-legacy-CFcDeiiP.js} +1 -1
- package/frontend/index.html +1 -1
- package/npm-shrinkwrap.json +60 -60
- package/package.json +3 -3
- package/src/acme.js +143 -50
- package/src/acmeConfig.js +43 -0
- package/src/acmeDns.js +285 -0
- package/src/admin-langs/embedded.js +33 -0
- package/src/admin-langs/hfs-admin-lang-ar.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-bg.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-de.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-el.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-en.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-es.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-fi.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-fr.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-hu.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-it.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-ja.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-ko.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-lt.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-ms.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-nl.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-pl.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-pt-br.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-ro.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-ru.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-sr-latn.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-sr.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-th.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-tr.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-uk.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-vi.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-zh-tw.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-zh.json +1141 -0
- package/src/adminApis.js +2 -2
- package/src/adminLang.js +50 -0
- package/src/api.lang.js +22 -13
- package/src/api.log.js +5 -3
- package/src/api.net.js +22 -4
- package/src/api.plugins.js +53 -19
- package/src/api.vfs.js +2 -1
- package/src/argv.js +4 -0
- package/src/auth.js +0 -2
- package/src/basicWeb.js +6 -2
- package/src/block.js +1 -1
- package/src/config.js +1 -0
- package/src/connections.js +4 -7
- package/src/const.js +1 -1
- package/src/cross-const.js +7 -5
- package/src/cross.js +16 -3
- package/src/frontEndApis.js +44 -7
- package/src/github.js +6 -37
- package/src/i18n.js +16 -4
- package/src/index.js +4 -2
- package/src/lang.js +15 -8
- package/src/langs/embedded.js +2 -1
- package/src/langs/hfs-lang-ar.json +5 -1
- package/src/langs/hfs-lang-bg.json +5 -1
- package/src/langs/hfs-lang-de.json +5 -1
- package/src/langs/hfs-lang-el.json +5 -1
- package/src/langs/hfs-lang-en.json +188 -0
- package/src/langs/hfs-lang-es.json +7 -3
- package/src/langs/hfs-lang-fi.json +6 -2
- package/src/langs/hfs-lang-fr.json +10 -6
- package/src/langs/hfs-lang-hu.json +5 -1
- package/src/langs/hfs-lang-it.json +9 -4
- package/src/langs/hfs-lang-ja.json +5 -1
- package/src/langs/hfs-lang-ko.json +5 -1
- package/src/langs/hfs-lang-lt.json +5 -1
- package/src/langs/hfs-lang-ms.json +7 -1
- package/src/langs/hfs-lang-nl.json +5 -1
- package/src/langs/hfs-lang-pl.json +5 -1
- package/src/langs/hfs-lang-pt-br.json +7 -3
- package/src/langs/hfs-lang-ro.json +5 -1
- package/src/langs/hfs-lang-ru.json +7 -3
- package/src/langs/hfs-lang-sr-latn.json +5 -1
- package/src/langs/hfs-lang-sr.json +5 -1
- package/src/langs/hfs-lang-th.json +6 -2
- package/src/langs/hfs-lang-tr.json +6 -2
- package/src/langs/hfs-lang-uk.json +14 -10
- package/src/langs/hfs-lang-vi.json +5 -1
- package/src/langs/hfs-lang-zh-tw.json +5 -1
- package/src/langs/hfs-lang-zh.json +5 -1
- package/src/log.js +7 -1
- package/src/middlewares.js +85 -14
- package/src/multipartUpload.js +21 -0
- package/src/nat.js +10 -7
- package/src/outboundProxy.js +1 -1
- package/src/perm.js +38 -20
- package/src/pluginCatalog.js +66 -0
- package/src/pluginUsage.js +64 -0
- package/src/plugins.js +12 -0
- package/src/roots.js +22 -13
- package/src/serveFile.js +26 -5
- package/src/serveGuiFiles.js +5 -2
- package/src/update.js +51 -18
- package/src/upload.js +49 -8
- package/src/util-http.js +31 -5
- package/src/vfs.js +84 -4
- package/src/webdav.js +12 -6
- package/admin/assets/AccountsPage-Cvi9DSc1.js +0 -3
- package/admin/assets/ArrayField-ChS9pNra.js +0 -2
- package/admin/assets/ConfigFilePage-6jCYVkbr.js +0 -4
- package/admin/assets/CustomHtmlPage-DaEXsEcQ.js +0 -2
- package/admin/assets/DataTable-Cf5S5yWr.js +0 -119
- package/admin/assets/DateTimeField--mTsSB1l.js +0 -1
- package/admin/assets/EventManager-BJWba-fL.js +0 -1
- package/admin/assets/FileField-B0WhrAuk.js +0 -117
- package/admin/assets/InternetPage-BICQDG_K.js +0 -8
- package/admin/assets/LangPage-D42UPSxC.js +0 -2
- package/admin/assets/LogsPage-DQTrf5GE.js +0 -4
- package/admin/assets/MenuButton-DDfB3119.js +0 -1
- package/admin/assets/MonitorPage-CH06c8aW.js +0 -2
- package/admin/assets/OptionsPage-BvXLA_R0.js +0 -3
- package/admin/assets/PluginsPage-CyaVJZkM.js +0 -1
- package/admin/assets/Tabs-CGfV3BK0.js +0 -1
- package/admin/assets/TextEditor-BeS-mR2I.css +0 -1
- package/admin/assets/TextEditor-Df9LSzm6.js +0 -34
- package/admin/assets/VfsPathField-6Kyy5vuQ.js +0 -1
- package/admin/assets/agentIcons-DwT9U-lh.js +0 -1
- package/admin/assets/baseUrl-D7S0KsqZ.js +0 -2
- package/admin/assets/icons-DWkdo8hc.js +0 -1
- package/admin/assets/index-11n5z0Ez.css +0 -1
- package/admin/assets/index-BDdPG215.js +0 -477
- package/admin/assets/routing-CxxvyWME.js +0 -1
- package/frontend/assets/index-legacy-B8EvbIOB.js +0 -1
package/src/serveFile.js
CHANGED
|
@@ -8,6 +8,9 @@ exports.forceDownload = forceDownload;
|
|
|
8
8
|
exports.disposition = disposition;
|
|
9
9
|
exports.serveFileNode = serveFileNode;
|
|
10
10
|
exports.enforceDownloadLimits = enforceDownloadLimits;
|
|
11
|
+
exports.resolveFileMime = resolveFileMime;
|
|
12
|
+
exports.getNodeMime = getNodeMime;
|
|
13
|
+
exports.isActiveContentMime = isActiveContentMime;
|
|
11
14
|
exports.serveFile = serveFile;
|
|
12
15
|
exports.monitorAsDownload = monitorAsDownload;
|
|
13
16
|
exports.applyRange = applyRange;
|
|
@@ -26,6 +29,7 @@ const errorPages_1 = require("./errorPages");
|
|
|
26
29
|
const stream_1 = require("stream");
|
|
27
30
|
const crypto_1 = require("crypto");
|
|
28
31
|
const iconv_lite_1 = __importDefault(require("iconv-lite"));
|
|
32
|
+
const upload_1 = require("./upload");
|
|
29
33
|
const allowedReferer = (0, config_1.defineConfig)(misc_1.CFG.allowed_referer, '');
|
|
30
34
|
const maxDownloads = downloadLimiter((0, config_1.defineConfig)(misc_1.CFG.max_downloads, 0), () => true);
|
|
31
35
|
const maxDownloadsPerIp = downloadLimiter((0, config_1.defineConfig)(misc_1.CFG.max_downloads_per_ip, 0), ctx => ctx.ip);
|
|
@@ -49,6 +53,7 @@ async function serveFileNode(ctx, node) {
|
|
|
49
53
|
const name = (0, vfs_1.getNodeName)(node);
|
|
50
54
|
const mimeString = typeof mime === 'string' ? mime
|
|
51
55
|
: lodash_1.default.find(mime, (_val, mask) => (0, misc_1.matches)(name, mask));
|
|
56
|
+
const effectiveMime = resolveFileMime(source || '', mimeString);
|
|
52
57
|
if (allowedReferer.get()) {
|
|
53
58
|
const ref = (0, misc_1.try_)(() => new URL(ctx.get('referer') || '').host);
|
|
54
59
|
if (ref && ref !== ctx.host // automatically accept if the referer is basically the hosting domain
|
|
@@ -58,12 +63,17 @@ async function serveFileNode(ctx, node) {
|
|
|
58
63
|
ctx.state.vfsNode = node; // useful to tell service files from files shared by the user
|
|
59
64
|
const download = 'dl' in ctx.query;
|
|
60
65
|
disposition(ctx, name, download);
|
|
66
|
+
if (!download && source && isActiveContentMime(effectiveMime)) {
|
|
67
|
+
const upload = await (0, upload_1.getUploadMeta)(source);
|
|
68
|
+
if ((0, upload_1.isUploading)(source) || upload && upload.approved !== true)
|
|
69
|
+
return (0, errorPages_1.sendErrorPage)(ctx, const_1.HTTP_FORBIDDEN);
|
|
70
|
+
}
|
|
61
71
|
const fetchDest = ctx.get('sec-fetch-dest');
|
|
62
72
|
ctx.state.considerAsGui ??= !download && ctx.get('referer')?.endsWith('/')
|
|
63
73
|
&& (fetchDest ? fetchDest !== 'document' && fetchDest !== 'empty' // modern clients
|
|
64
74
|
// legacy clients often send Accept: */* for archive downloads, so the served mime is a safer signal than request headers here
|
|
65
|
-
: GUI_ASSET_MIME.test(
|
|
66
|
-
await serveFile(ctx, source || '',
|
|
75
|
+
: GUI_ASSET_MIME.test(effectiveMime));
|
|
76
|
+
await serveFile(ctx, source || '', effectiveMime);
|
|
67
77
|
await enforceDownloadLimits(ctx);
|
|
68
78
|
}
|
|
69
79
|
async function enforceDownloadLimits(ctx) {
|
|
@@ -75,14 +85,25 @@ const mimeCfg = (0, config_1.defineConfig)(misc_1.CFG.mime, {}, obj => {
|
|
|
75
85
|
const values = Object.values(obj);
|
|
76
86
|
return (name) => values[matchers.findIndex(matcher => matcher(name))];
|
|
77
87
|
});
|
|
88
|
+
function resolveFileMime(filePath, mime) {
|
|
89
|
+
mime ??= mimeCfg.compiled()((0, path_1.basename)(filePath));
|
|
90
|
+
return mime === undefined || mime === const_1.MIME_AUTO ? mime_types_1.default.lookup(filePath) || '' : mime;
|
|
91
|
+
}
|
|
92
|
+
function getNodeMime(node) {
|
|
93
|
+
const name = (0, vfs_1.getNodeName)(node);
|
|
94
|
+
const mime = typeof node.mime === 'string' ? node.mime
|
|
95
|
+
: lodash_1.default.find(node.mime, (_val, mask) => (0, misc_1.matches)(name, mask));
|
|
96
|
+
return resolveFileMime(node.source || '', mime);
|
|
97
|
+
}
|
|
98
|
+
function isActiveContentMime(mime) {
|
|
99
|
+
return /^(?:text\/html|application\/xhtml\+xml|image\/svg\+xml)(?:;|$)/i.test(mime);
|
|
100
|
+
}
|
|
78
101
|
// after this number of seconds, the browser should check the server to see if there's a newer version of the file
|
|
79
102
|
const cacheControlDiskFiles = (0, config_1.defineConfig)(misc_1.CFG.cache_control_disk_files, 5);
|
|
80
103
|
async function serveFile(ctx, filePath, mime, cached) {
|
|
81
104
|
if (!filePath)
|
|
82
105
|
return;
|
|
83
|
-
mime
|
|
84
|
-
if (mime === undefined || mime === const_1.MIME_AUTO)
|
|
85
|
-
mime = mime_types_1.default.lookup(filePath) || '';
|
|
106
|
+
mime = resolveFileMime(filePath, mime);
|
|
86
107
|
if (mime)
|
|
87
108
|
ctx.type = mime;
|
|
88
109
|
if (ctx.method === 'OPTIONS') {
|
package/src/serveGuiFiles.js
CHANGED
|
@@ -19,6 +19,7 @@ const customHtml_1 = require("./customHtml");
|
|
|
19
19
|
const lodash_1 = __importDefault(require("lodash"));
|
|
20
20
|
const config_1 = require("./config");
|
|
21
21
|
const lang_1 = require("./lang");
|
|
22
|
+
const adminLang_1 = require("./adminLang");
|
|
22
23
|
const upload_1 = require("./upload");
|
|
23
24
|
const icons_1 = require("./icons");
|
|
24
25
|
const middlewares_1 = require("./middlewares");
|
|
@@ -107,7 +108,8 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
107
108
|
return !lodash_1.default.isEmpty(configs) && [name, configs];
|
|
108
109
|
})));
|
|
109
110
|
const timestamp = await getFaviconTimestamp();
|
|
110
|
-
const
|
|
111
|
+
const adminLangs = isFrontend ? [] : await (0, adminLang_1.getAdminLangs)();
|
|
112
|
+
const lang = isFrontend ? await (0, lang_1.getLangData)(ctx) : await (0, adminLang_1.getAdminLangData)(ctx, adminLangs);
|
|
111
113
|
let group = 0; // this represents the loading-group: all plugins with the same group can be loaded concurrently
|
|
112
114
|
const loadScripts = (0, misc_1.onlyTruthy)((0, plugins_1.mapPlugins)((p, id) => {
|
|
113
115
|
const js = p.frontend_js?.map(f => f.includes('//') ? f : pub + id + '/' + f);
|
|
@@ -148,7 +150,8 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
148
150
|
customHtml: lodash_1.default.omit((0, customHtml_1.getAllSections)(), ['top', 'bottom', 'htmlHead', 'style']), // exclude the sections we already apply in this phase
|
|
149
151
|
...(0, misc_1.newObj)(misc_1.FRONTEND_OPTIONS, (v, k) => (0, config_1.getConfig)(k)),
|
|
150
152
|
icons: Object.assign({}, ...(0, plugins_1.mapPlugins)(p => iconsToObj(p.icons, p.id + '/')), iconsToObj(icons_1.customizedIcons)), // name-to-uri
|
|
151
|
-
lang
|
|
153
|
+
lang,
|
|
154
|
+
...(isFrontend ? {} : { adminLangs, adminLang: adminLang_1.adminLang.get() })
|
|
152
155
|
}, null, 4).replace(/<(\/script)/g, '<"+"$1') /*avoid breaking our script container*/}
|
|
153
156
|
document.documentElement.setAttribute('ver', HFS.VERSION.split('-')[0])
|
|
154
157
|
document.documentElement.setAttribute('browser', ${JSON.stringify((0, misc_1.shortenAgent)(ctx.get('user-agent')))})
|
package/src/update.js
CHANGED
|
@@ -10,7 +10,7 @@ exports.getVersions = getVersions;
|
|
|
10
10
|
exports.getUpdates = getUpdates;
|
|
11
11
|
exports.localUpdateAvailable = localUpdateAvailable;
|
|
12
12
|
exports.previousAvailable = previousAvailable;
|
|
13
|
-
exports.
|
|
13
|
+
exports.updateNotSupported = updateNotSupported;
|
|
14
14
|
exports.update = update;
|
|
15
15
|
const github_1 = require("./github");
|
|
16
16
|
const const_1 = require("./const");
|
|
@@ -29,6 +29,7 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
|
29
29
|
const argv_1 = require("./argv");
|
|
30
30
|
const promises_2 = require("stream/promises");
|
|
31
31
|
const updateChangelog_1 = require("./updateChangelog");
|
|
32
|
+
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
32
33
|
const updateToBeta = (0, config_1.defineConfig)(misc_1.CFG.update_to_beta, false);
|
|
33
34
|
const autoCheckUpdate = (0, config_1.defineConfig)(misc_1.CFG.auto_check_update, true);
|
|
34
35
|
const lastCheckUpdate = persistence_1.storedMap.singleSync('lastCheckUpdate', 0);
|
|
@@ -59,15 +60,16 @@ exports.checkForUpdates = (0, misc_1.debounceAsync)(async () => {
|
|
|
59
60
|
async function getBestUpdate() {
|
|
60
61
|
return (await getUpdates(true))[0];
|
|
61
62
|
}
|
|
62
|
-
const
|
|
63
|
-
const
|
|
63
|
+
const releaseKeys = ['prerelease', 'tag_name', 'name', 'body', 'assets', 'isNewer', 'versionScalar'];
|
|
64
|
+
const releaseAssetKeys = ['name', 'browser_download_url'];
|
|
65
|
+
const assetPrefix = `hfs-${(0, misc_1.xlate)(process.platform, { win32: 'windows', darwin: 'mac' })}-${process.arch}`;
|
|
64
66
|
const curV = config_1.currentVersion.scalar;
|
|
65
67
|
function prepareRelease(r) {
|
|
66
68
|
const v = (0, config_1.versionToScalar)(r.name);
|
|
67
|
-
return Object.assign(lodash_1.default.pick(r,
|
|
69
|
+
return Object.assign(lodash_1.default.pick(r, releaseKeys), {
|
|
68
70
|
versionScalar: v,
|
|
69
71
|
isNewer: v > curV, // make easy to know what's newer
|
|
70
|
-
assets: r.assets.map((a) => lodash_1.default.pick(a,
|
|
72
|
+
assets: r.assets.map((a) => lodash_1.default.pick(a, releaseAssetKeys))
|
|
71
73
|
});
|
|
72
74
|
}
|
|
73
75
|
async function getVersions(filter, max = 30, stopAtCurrent = false) {
|
|
@@ -91,10 +93,19 @@ async function getVersions(filter, max = 30, stopAtCurrent = false) {
|
|
|
91
93
|
async function getUpdates(strict = false) {
|
|
92
94
|
console.log("Checking for updates");
|
|
93
95
|
void (0, github_1.getProjectInfo)(); // also check for alerts and print them asap in the console
|
|
94
|
-
const stable = prepareRelease(await (0, github_1.getRepoInfo)(const_1.HFS_REPO + '/releases/latest'));
|
|
95
96
|
const includeBetas = updateToBeta.get() || const_1.RUNNING_BETA;
|
|
96
|
-
|
|
97
|
-
const
|
|
97
|
+
const latest = await (0, misc_1.httpStream)(`https://github.com/${const_1.HFS_REPO}/releases/latest`, { method: 'HEAD', noRedirect: true });
|
|
98
|
+
const stableTag = latest.headers.location?.split('/').at(-1);
|
|
99
|
+
if (!stableTag)
|
|
100
|
+
throw Error('Cannot determine latest stable version');
|
|
101
|
+
if ((0, config_1.versionToScalar)(stableTag) === curV && !includeBetas)
|
|
102
|
+
return [];
|
|
103
|
+
let stable = prepareRelease({ prerelease: false, tag_name: stableTag, name: stableTag, body: '', assets: [probableAsset(stableTag)], isNewer: false, versionScalar: 0 });
|
|
104
|
+
if (stable.isNewer || const_1.RUNNING_BETA && !strict)
|
|
105
|
+
stable = await (0, github_1.getRepoInfo)(const_1.HFS_REPO + '/releases/tags/' + stableTag).then(prepareRelease).catch(() => stable);
|
|
106
|
+
const betas = !includeBetas ? [] : await getVersions(x => x.prerelease && x.versionScalar > stable.versionScalar && (!strict || x.isNewer))
|
|
107
|
+
// preserve update detection when GitHub's REST quota is exhausted
|
|
108
|
+
.catch(() => getVersionsFromAtom(x => x.prerelease && x.versionScalar > stable.versionScalar && (!strict || x.isNewer)));
|
|
98
109
|
if (stable.isNewer || const_1.RUNNING_BETA && !strict)
|
|
99
110
|
betas.push(stable);
|
|
100
111
|
if (stable.isNewer && stable.body) {
|
|
@@ -102,7 +113,28 @@ async function getUpdates(strict = false) {
|
|
|
102
113
|
.catch(() => []); // missing history must not prevent installing an available update
|
|
103
114
|
stable.body = (0, updateChangelog_1.updateChangelog)(stable, history, curV);
|
|
104
115
|
}
|
|
105
|
-
return betas;
|
|
116
|
+
return lodash_1.default.sortBy(betas, x => -x.versionScalar);
|
|
117
|
+
async function getVersionsFromAtom(filter) {
|
|
118
|
+
const feed = new fast_xml_parser_1.XMLParser({ ignoreAttributes: false })
|
|
119
|
+
.parse(await (0, misc_1.httpString)(`https://github.com/${const_1.HFS_REPO}/releases.atom`))?.feed;
|
|
120
|
+
return lodash_1.default.sortBy(lodash_1.default.castArray(feed?.entry)
|
|
121
|
+
.map((x) => prepareRelease({
|
|
122
|
+
prerelease: /^v?\d+\.\d+\.\d+-/.test(x.title),
|
|
123
|
+
tag_name: x.id.split('/').at(-1),
|
|
124
|
+
name: x.title,
|
|
125
|
+
body: x.content?.['#text'] || '',
|
|
126
|
+
assets: [probableAsset(x.id.split('/').at(-1))],
|
|
127
|
+
isNewer: false,
|
|
128
|
+
versionScalar: 0,
|
|
129
|
+
}))
|
|
130
|
+
.filter(x => Number.isFinite(x.versionScalar) && !x.name.endsWith('-ignore')
|
|
131
|
+
&& x.versionScalar !== curV && filter(x)), x => -x.versionScalar);
|
|
132
|
+
}
|
|
133
|
+
function probableAsset(tag) {
|
|
134
|
+
const name = `${assetPrefix}-${tag.replace(/^v/, '')}.zip`;
|
|
135
|
+
// release-assets.yml uses this predictable name, keeping degraded checks installable
|
|
136
|
+
return { name, browser_download_url: `https://github.com/${const_1.HFS_REPO}/releases/download/${tag}/${name}` };
|
|
137
|
+
}
|
|
106
138
|
}
|
|
107
139
|
const LOCAL_UPDATE = 'hfs-update.zip'; // update from file takes precedence over net
|
|
108
140
|
const INSTALLED_FN = 'hfs-installed.zip';
|
|
@@ -113,13 +145,16 @@ function localUpdateAvailable() {
|
|
|
113
145
|
function previousAvailable() {
|
|
114
146
|
return (0, misc_1.exists)(PREVIOUS_FN);
|
|
115
147
|
}
|
|
116
|
-
function
|
|
117
|
-
return
|
|
148
|
+
function updateNotSupported() {
|
|
149
|
+
return process.env.DISABLE_UPDATE ? "Automatic updates are disabled"
|
|
150
|
+
: !(argv_1.argv.forceupdate || const_1.IS_BINARY) ? "Only binary versions support automatic updates"
|
|
151
|
+
: (0, promises_1.access)((0, path_1.dirname)(process.execPath), fs_1.constants.W_OK | fs_1.constants.X_OK)
|
|
152
|
+
.then(() => '', () => `Executable directory is not writable: ${(0, path_1.dirname)(process.execPath)}`);
|
|
118
153
|
}
|
|
119
154
|
async function update(tagOrUrl = '') {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
155
|
+
const error = await updateNotSupported();
|
|
156
|
+
if (error)
|
|
157
|
+
throw error;
|
|
123
158
|
let url = tagOrUrl.includes('://') && tagOrUrl;
|
|
124
159
|
if (tagOrUrl === const_1.PREVIOUS_TAG)
|
|
125
160
|
await (0, promises_1.rename)(PREVIOUS_FN, LOCAL_UPDATE);
|
|
@@ -135,11 +170,9 @@ async function update(tagOrUrl = '') {
|
|
|
135
170
|
});
|
|
136
171
|
if (!update)
|
|
137
172
|
throw "No update has been found";
|
|
138
|
-
const
|
|
139
|
-
const assetSearch = `${plat}-${process.arch}`;
|
|
140
|
-
const asset = update.assets.find((x) => x.name.includes(assetSearch) && x.name.endsWith('.zip'));
|
|
173
|
+
const asset = update.assets.find((x) => x.name.includes(assetPrefix) && x.name.endsWith('.zip'));
|
|
141
174
|
if (!asset)
|
|
142
|
-
throw `Asset not found: ${
|
|
175
|
+
throw `Asset not found: ${assetPrefix}`;
|
|
143
176
|
url = asset.browser_download_url;
|
|
144
177
|
}
|
|
145
178
|
if (url) {
|
package/src/upload.js
CHANGED
|
@@ -5,7 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.dontOverwriteUploading = exports.minAvailableMb = exports.deleteUnfinishedUploadsAfter = void 0;
|
|
7
7
|
exports.getUploadMeta = getUploadMeta;
|
|
8
|
+
exports.saveUploadMeta = saveUploadMeta;
|
|
9
|
+
exports.setUploadApproved = setUploadApproved;
|
|
8
10
|
exports.getUploadTempFor = getUploadTempFor;
|
|
11
|
+
exports.isUploading = isUploading;
|
|
12
|
+
exports.whileUploadMetaPending = whileUploadMetaPending;
|
|
9
13
|
exports.uploadWriter = uploadWriter;
|
|
10
14
|
const vfs_1 = require("./vfs");
|
|
11
15
|
const const_1 = require("./const");
|
|
@@ -25,6 +29,7 @@ const expiringCache_1 = require("./expiringCache");
|
|
|
25
29
|
const first_1 = require("./first");
|
|
26
30
|
const uploadOwners_1 = require("./uploadOwners");
|
|
27
31
|
const webdav_1 = require("./webdav");
|
|
32
|
+
const adminApis_1 = require("./adminApis");
|
|
28
33
|
exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)(misc_1.CFG.delete_unfinished_uploads_after, 86_400);
|
|
29
34
|
exports.minAvailableMb = (0, config_1.defineConfig)(misc_1.CFG.min_available_mb, 100);
|
|
30
35
|
exports.dontOverwriteUploading = (0, config_1.defineConfig)(misc_1.CFG.dont_overwrite_uploading, true);
|
|
@@ -56,10 +61,18 @@ const ATTR_UPLOADER = 'uploader';
|
|
|
56
61
|
function getUploadMeta(path) {
|
|
57
62
|
return (0, misc_1.loadFileAttr)(path, ATTR_UPLOADER);
|
|
58
63
|
}
|
|
59
|
-
function
|
|
60
|
-
return (0, misc_1.storeFileAttr)(path, ATTR_UPLOADER,
|
|
64
|
+
function saveUploadMeta(path, meta) {
|
|
65
|
+
return (0, misc_1.storeFileAttr)(path, ATTR_UPLOADER, meta);
|
|
66
|
+
}
|
|
67
|
+
async function setUploadApproved(path, approved) {
|
|
68
|
+
const meta = await getUploadMeta(path);
|
|
69
|
+
return Boolean(meta && await saveUploadMeta(path, { ...meta, approved }));
|
|
70
|
+
}
|
|
71
|
+
function fillUploadMeta(path, ctx) {
|
|
72
|
+
return saveUploadMeta(path, {
|
|
61
73
|
username: (0, auth_1.getCurrentUsername)(ctx) || undefined,
|
|
62
74
|
ip: ctx.ip,
|
|
75
|
+
approved: (0, adminApis_1.ctxAdminAccess)(ctx) || undefined,
|
|
63
76
|
});
|
|
64
77
|
}
|
|
65
78
|
function getUploadTempFor(fullPath) {
|
|
@@ -67,6 +80,24 @@ function getUploadTempFor(fullPath) {
|
|
|
67
80
|
}
|
|
68
81
|
const diskSpaceCache = (0, expiringCache_1.expiringCache)(3_000); // invalidate shortly
|
|
69
82
|
const uploadingFiles = new Map();
|
|
83
|
+
const uploadMetaPending = new Set();
|
|
84
|
+
function isUploading(path) {
|
|
85
|
+
const key = (0, vfs_1.normalizeFilename)(path);
|
|
86
|
+
return (0, path_1.basename)(path).startsWith(const_1.UPLOAD_TEMP_PREFIX)
|
|
87
|
+
|| uploadingFiles.has(key)
|
|
88
|
+
|| uploadingFiles.has((0, vfs_1.normalizeFilename)(getUploadTempFor(path))) // active uploads are keyed by their temporary file
|
|
89
|
+
|| uploadMetaPending.has(key);
|
|
90
|
+
}
|
|
91
|
+
async function whileUploadMetaPending(path, cb) {
|
|
92
|
+
const key = (0, vfs_1.normalizeFilename)(path);
|
|
93
|
+
uploadMetaPending.add(key);
|
|
94
|
+
try {
|
|
95
|
+
return await cb();
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
uploadMetaPending.delete(key);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
70
101
|
// initially sync for formidable; still sync to avoid async races and PUT piping gaps
|
|
71
102
|
function uploadWriter(base, baseUri, filename, ctx) {
|
|
72
103
|
if (!filename || !(0, misc_1.isValidFileName)(filename) || !filename)
|
|
@@ -123,7 +154,7 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
123
154
|
const createdFolders = [];
|
|
124
155
|
const firstCreated = !dir.endsWith(':\\') && fs_1.default.mkdirSync(dir, { recursive: true });
|
|
125
156
|
if (firstCreated) {
|
|
126
|
-
|
|
157
|
+
fillUploadMeta(dir, ctx);
|
|
127
158
|
// recursive mkdir returns the first created directory; existing ancestors must not receive ownership
|
|
128
159
|
const firstCreatedPath = (0, path_1.toNamespacedPath)((0, path_1.resolve)(firstCreated)); // Node 24 mkdir can return a namespace-prefixed Windows path
|
|
129
160
|
let source = (0, path_1.resolve)(dir);
|
|
@@ -219,12 +250,16 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
219
250
|
await new Promise(res => fileStream.close(res)); // this only seems necessary on Windows
|
|
220
251
|
for (const { uri, source } of createdFolders)
|
|
221
252
|
await (0, uploadOwners_1.setUploadOwner)(uri, ctx, source);
|
|
222
|
-
if (errored)
|
|
223
|
-
return;
|
|
224
253
|
if (simulate)
|
|
225
254
|
return (0, promises_1.rm)(tempName).catch(() => { });
|
|
255
|
+
if (!await fillUploadMeta(tempName, ctx)) {
|
|
256
|
+
await (0, promises_1.rm)(tempName).catch(() => { });
|
|
257
|
+
return fail(const_1.HTTP_SERVER_ERROR);
|
|
258
|
+
}
|
|
259
|
+
if (errored)
|
|
260
|
+
return;
|
|
226
261
|
if (ctx.isAborted()) { // in the very unlikely case the connection is interrupted between last-byte and here, we still consider it unfinished, as the client had no way to know, and will resume, but it would get an error if we finish the process
|
|
227
|
-
const sec = exports.deleteUnfinishedUploadsAfter.get();
|
|
262
|
+
const sec = first_1.quitting ? undefined : exports.deleteUnfinishedUploadsAfter.get(); // preserve uploads interrupted by shutdown for resuming
|
|
228
263
|
await (0, uploadOwners_1.setUploadOwner)(tempOwnerUri, ctx, tempName, lodash_1.default.isNumber(sec) ? Date.now() + sec * 1000 : null);
|
|
229
264
|
return lodash_1.default.isNumber(sec) && delayedDelete(tempName, sec, tempOwnerUri);
|
|
230
265
|
}
|
|
@@ -254,11 +289,16 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
254
289
|
() => (0, promises_1.rename)(tempName, dest).then(() => true, e => e?.code !== 'EBUSY' && Promise.reject(e)), { timeout: 10_000 });
|
|
255
290
|
if (!done)
|
|
256
291
|
throw 'EBUSY';
|
|
292
|
+
await (0, misc_1.moveStoredFileAttrs)(tempName, dest);
|
|
257
293
|
(0, uploadOwners_1.deleteUploadOwner)(tempOwnerUri); // the temp URI no longer exists after rename; final ownership is recorded below
|
|
258
294
|
if (mtime) // so we use it to touch the file
|
|
259
295
|
await (0, promises_1.utimes)(dest, Date.now() / 1000, mtime / 1000);
|
|
260
296
|
obj.fullPath = ctx.state.uploadDestinationPath = dest;
|
|
261
|
-
|
|
297
|
+
if (!await fillUploadMeta(dest, ctx)) {
|
|
298
|
+
await (0, promises_1.rename)(dest, tempName);
|
|
299
|
+
await (0, misc_1.moveStoredFileAttrs)(dest, tempName);
|
|
300
|
+
throw Error("couldn't store upload metadata");
|
|
301
|
+
}
|
|
262
302
|
if (ctx.query.comment)
|
|
263
303
|
void (0, comments_1.setCommentFor)(dest, String(ctx.query.comment));
|
|
264
304
|
obj.uri = (0, misc_1.enforceFinal)('/', baseUri) + (0, misc_1.pathEncode)((0, path_1.basename)(dest));
|
|
@@ -272,8 +312,9 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
272
312
|
cb(obj);
|
|
273
313
|
}
|
|
274
314
|
catch (err) {
|
|
275
|
-
void
|
|
315
|
+
void fillUploadMeta(tempName, ctx);
|
|
276
316
|
console.error("Couldn't rename temp to", dest, String(err));
|
|
317
|
+
fail(const_1.HTTP_SERVER_ERROR);
|
|
277
318
|
}
|
|
278
319
|
}
|
|
279
320
|
finally {
|
package/src/util-http.js
CHANGED
|
@@ -38,6 +38,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
};
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
40
|
exports.stream2string = void 0;
|
|
41
|
+
exports.readTextLimited = readTextLimited;
|
|
42
|
+
exports.readRequestBodyLimited = readRequestBodyLimited;
|
|
41
43
|
exports.httpString = httpString;
|
|
42
44
|
exports.httpWithBody = httpWithBody;
|
|
43
45
|
exports.httpStream = httpStream;
|
|
@@ -51,6 +53,29 @@ const consumers_1 = require("node:stream/consumers");
|
|
|
51
53
|
Object.defineProperty(exports, "stream2string", { enumerable: true, get: function () { return consumers_1.text; } });
|
|
52
54
|
const tls = __importStar(require("node:tls"));
|
|
53
55
|
const cross_1 = require("./cross");
|
|
56
|
+
const cross_const_1 = require("./cross-const");
|
|
57
|
+
async function readTextLimited(stream, maxBytes) {
|
|
58
|
+
if (Number(stream.headers['content-length']) > maxBytes)
|
|
59
|
+
return { error: 'too_large' };
|
|
60
|
+
const chunks = [];
|
|
61
|
+
let size = 0;
|
|
62
|
+
// keep the socket alive so callers can send the rejection before closing an undrained request
|
|
63
|
+
for await (const chunk of stream.iterator({ destroyOnReturn: false })) {
|
|
64
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
65
|
+
size += buffer.length;
|
|
66
|
+
if (size > maxBytes)
|
|
67
|
+
return { error: 'too_large' };
|
|
68
|
+
chunks.push(buffer);
|
|
69
|
+
}
|
|
70
|
+
return { text: new TextDecoder().decode(Buffer.concat(chunks, size)) };
|
|
71
|
+
}
|
|
72
|
+
async function readRequestBodyLimited(ctx, maxBytes) {
|
|
73
|
+
const body = await readTextLimited(ctx.req, maxBytes);
|
|
74
|
+
if (!('error' in body))
|
|
75
|
+
return body;
|
|
76
|
+
ctx.set('Connection', 'close'); // an unread rejected body makes this connection unsafe to reuse
|
|
77
|
+
ctx.status = cross_const_1.HTTP_PAYLOAD_TOO_LARGE;
|
|
78
|
+
}
|
|
54
79
|
async function httpString(url, { maxBytes = 10 * cross_1.MB, ...options } = {}) {
|
|
55
80
|
const stream = await httpStream(url, options);
|
|
56
81
|
if (maxBytes === Infinity)
|
|
@@ -85,12 +110,13 @@ function httpStream(url, { body, proxy, jar, noRedirect, httpThrow = true, ...op
|
|
|
85
110
|
options.headers['user-agent'] = httpStream.defaultUA;
|
|
86
111
|
if (body) {
|
|
87
112
|
options.method ||= 'POST';
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
113
|
+
if (!(body instanceof node_stream_1.Readable)) {
|
|
114
|
+
if (typeof body === 'object' && !(body instanceof Buffer)) {
|
|
115
|
+
options.headers['content-type'] ??= 'application/json';
|
|
116
|
+
body = JSON.stringify(body);
|
|
117
|
+
}
|
|
93
118
|
options.headers['content-length'] ??= Buffer.byteLength(body);
|
|
119
|
+
}
|
|
94
120
|
}
|
|
95
121
|
const { auth, ...parsed } = parseHttpUrl(url);
|
|
96
122
|
const hostJar = jar && (jar[parsed.hostname || ''] ||= {});
|
package/src/vfs.js
CHANGED
|
@@ -42,6 +42,7 @@ const walkDir_1 = require("./walkDir");
|
|
|
42
42
|
const node_stream_1 = require("node:stream");
|
|
43
43
|
const adminApis_1 = require("./adminApis");
|
|
44
44
|
const showHiddenFiles = (0, config_1.defineConfig)(misc_1.CFG.show_hidden_files, false);
|
|
45
|
+
const sourceNames = new Map();
|
|
45
46
|
function setVfsPath(node, vfsPath, parent) {
|
|
46
47
|
return (0, misc_1.setHidden)(node, {
|
|
47
48
|
vfsPath: (0, misc_1.enforceFinal)('/', parent?.vfsPath) + (0, misc_1.pathEncode)(vfsPath)
|
|
@@ -189,6 +190,27 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
|
|
|
189
190
|
// does the tree node have a child that goes by this name, otherwise attempt disk
|
|
190
191
|
let virtualName = name;
|
|
191
192
|
let child = parent.children?.find(isSameFilenameAs(name));
|
|
193
|
+
const sameName = isSameFilenameAs(name);
|
|
194
|
+
const sourceName = Object.entries(parent.rename || {}).find(([, to]) => sameName(to))?.[0] || name;
|
|
195
|
+
if (!child) {
|
|
196
|
+
child = findSourceChild();
|
|
197
|
+
if (!child && const_1.IS_WINDOWS && parent.source) {
|
|
198
|
+
const aliases = await windowsPathNames((0, path_1.join)(parent.source, sourceName), parent.source);
|
|
199
|
+
if (!aliases)
|
|
200
|
+
return;
|
|
201
|
+
child = findSourceChild(aliases);
|
|
202
|
+
if (!child) {
|
|
203
|
+
const normalized = new Set(aliases.map(normalizeFilename));
|
|
204
|
+
const candidates = parent.children?.filter(candidate => candidate.source
|
|
205
|
+
&& normalized.has(normalizeFilename((0, path_1.basename)(candidate.source)))) || [];
|
|
206
|
+
if (candidates.length > 1)
|
|
207
|
+
return; // an unverified physical alias must identify one VFS child
|
|
208
|
+
const [candidate] = candidates;
|
|
209
|
+
if (candidate?.source && await isSameFilePath((0, path_1.dirname)(candidate.source), parent.source))
|
|
210
|
+
child = candidate;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
192
214
|
if (child) { // found as vfs node
|
|
193
215
|
virtualName = getNodeName(child);
|
|
194
216
|
await setIsFolder(child); // in case it's pointing to a folder that didn't exist at loading time
|
|
@@ -202,7 +224,6 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
|
|
|
202
224
|
const ret = {};
|
|
203
225
|
let onDisk = name;
|
|
204
226
|
if (parent.rename) { // reverse the mapping
|
|
205
|
-
const sameName = isSameFilenameAs(name);
|
|
206
227
|
const entries = Object.entries(parent.rename);
|
|
207
228
|
// search display names first: with { A: B, C: A }, A must resolve to C rather than be hidden
|
|
208
229
|
const asDisplayName = entries.find(([, to]) => sameName(to));
|
|
@@ -211,12 +232,17 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
|
|
|
211
232
|
virtualName = asDisplayName[1];
|
|
212
233
|
}
|
|
213
234
|
else {
|
|
214
|
-
|
|
235
|
+
if (!await canonicalizeWindowsAlias())
|
|
236
|
+
return;
|
|
237
|
+
const sameSourceName = isSameFilenameAs(onDisk);
|
|
238
|
+
const asPhysicalName = entries.find(([from]) => sameSourceName(from));
|
|
215
239
|
if (asPhysicalName)
|
|
216
240
|
return; // a VFS rename replaces the original public name
|
|
217
241
|
}
|
|
218
242
|
ret.rename = renameUnderPath(parent.rename, onDisk);
|
|
219
243
|
}
|
|
244
|
+
else if (!await canonicalizeWindowsAlias())
|
|
245
|
+
return;
|
|
220
246
|
if (!(0, misc_1.isValidFileName)(onDisk))
|
|
221
247
|
return;
|
|
222
248
|
ret.source = (0, path_1.join)(parent.source, onDisk);
|
|
@@ -225,6 +251,22 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
|
|
|
225
251
|
if (assumeMissingToBeFolder)
|
|
226
252
|
ret.isFolder ??= true;
|
|
227
253
|
return ret;
|
|
254
|
+
async function canonicalizeWindowsAlias() {
|
|
255
|
+
if (!const_1.IS_WINDOWS || !name.includes('~'))
|
|
256
|
+
return true;
|
|
257
|
+
// Windows exposes 8.3 aliases that must keep the long name's VFS identity
|
|
258
|
+
const longPath = await convertWindowsPath((0, path_1.join)(parent.source, name), true);
|
|
259
|
+
if (!longPath)
|
|
260
|
+
return true; // new names containing '~' are still valid
|
|
261
|
+
if (!await isSameFilePath((0, path_1.dirname)(longPath), parent.source))
|
|
262
|
+
return false;
|
|
263
|
+
onDisk = virtualName = (0, path_1.basename)(longPath);
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function findSourceChild(names = [sourceName]) {
|
|
268
|
+
const normalized = new Set(names.map(normalizeFilename));
|
|
269
|
+
return parent.children?.find(x => getSourceNames(x, parent).some(source => normalized.has(normalizeFilename(source))));
|
|
228
270
|
}
|
|
229
271
|
}
|
|
230
272
|
const smartUncFolderDetection = (0, config_1.defineConfig)(misc_1.CFG.smart_unc_folder_detection, false);
|
|
@@ -261,8 +303,42 @@ async function reviewVfs() {
|
|
|
261
303
|
usedNames.add(normalizeFilename(getNodeName(child)));
|
|
262
304
|
}
|
|
263
305
|
await Promise.allSettled(node.children.map(recur));
|
|
306
|
+
await Promise.allSettled(node.children.map(child => setSourceNames(child, node)));
|
|
264
307
|
})(exports.vfs.compiled());
|
|
265
308
|
}
|
|
309
|
+
async function setSourceNames(node, parent) {
|
|
310
|
+
if (!node.source || !parent.source)
|
|
311
|
+
return;
|
|
312
|
+
const sameParent = normalizeFilename((0, path_1.dirname)((0, path_1.resolve)(node.source))) === normalizeFilename((0, path_1.resolve)(parent.source));
|
|
313
|
+
if (!sameParent && (!const_1.IS_WINDOWS || !await isSameFilePath((0, path_1.dirname)(node.source), parent.source)))
|
|
314
|
+
return;
|
|
315
|
+
const names = [(0, path_1.basename)(node.source)];
|
|
316
|
+
const key = sourceNamesKey(node, parent);
|
|
317
|
+
if (const_1.IS_WINDOWS) {
|
|
318
|
+
const converted = await Promise.all([convertWindowsPath(node.source), convertWindowsPath(node.source, true)]);
|
|
319
|
+
names.push(...converted.map(x => x && (0, path_1.basename)(x)).filter(Boolean));
|
|
320
|
+
}
|
|
321
|
+
sourceNames.set(key, lodash_1.default.uniq(names));
|
|
322
|
+
}
|
|
323
|
+
function convertWindowsPath(path, isLong = false) {
|
|
324
|
+
return new Promise(resolvePromise => {
|
|
325
|
+
if (!fswin_1.default.convertPath(path, resolvePromise, isLong))
|
|
326
|
+
resolvePromise(undefined);
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
async function windowsPathNames(path, parent) {
|
|
330
|
+
const converted = await Promise.all([convertWindowsPath(path), convertWindowsPath(path, true)]);
|
|
331
|
+
const existing = converted.find(Boolean);
|
|
332
|
+
if (existing && !await isSameFilePath((0, path_1.dirname)(existing), parent))
|
|
333
|
+
return;
|
|
334
|
+
return lodash_1.default.uniq([(0, path_1.basename)(path), ...converted.map(x => x && (0, path_1.basename)(x)).filter(Boolean)]);
|
|
335
|
+
}
|
|
336
|
+
function getSourceNames(node, parent) {
|
|
337
|
+
return node.source && parent.source ? sourceNames.get(sourceNamesKey(node, parent)) || [] : [];
|
|
338
|
+
}
|
|
339
|
+
function sourceNamesKey(node, parent) {
|
|
340
|
+
return normalizeFilename((0, path_1.resolve)(parent.source)) + '\0' + normalizeFilename((0, path_1.resolve)(node.source));
|
|
341
|
+
}
|
|
266
342
|
exports.saveVfs = (0, misc_1.debounceAsync)(async () => {
|
|
267
343
|
await reviewVfs(); // refresh runtime-derived folder flags before saving mutated VFS state
|
|
268
344
|
exports.vfs.set(exports.vfs.compiled()); // sync the mutable runtime tree back to config state before persisting it
|
|
@@ -385,6 +461,7 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
|
|
|
385
461
|
started = true;
|
|
386
462
|
const { source } = parent;
|
|
387
463
|
const taken = new Set();
|
|
464
|
+
const takenSources = new Set();
|
|
388
465
|
const maskApplier = parentMaskApplier(parent);
|
|
389
466
|
const visitLater = [];
|
|
390
467
|
const childrenWorking = parent.children?.length && Promise.all(parent.children.map(async (child) => {
|
|
@@ -392,7 +469,9 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
|
|
|
392
469
|
return;
|
|
393
470
|
const nodeName = getNodeName(child);
|
|
394
471
|
const name = prefixPath + nodeName;
|
|
395
|
-
taken
|
|
472
|
+
taken.add(normalizeFilename(name));
|
|
473
|
+
for (const sourceName of getSourceNames(child, parent))
|
|
474
|
+
takenSources.add(normalizeFilename(prefixPath + sourceName));
|
|
396
475
|
const item = setVfsPath({ ...child, original: child, name, parent }, name, parent);
|
|
397
476
|
if (await cantSee(item))
|
|
398
477
|
return;
|
|
@@ -434,7 +513,8 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
|
|
|
434
513
|
renamed = dir + '/' + renamed;
|
|
435
514
|
}
|
|
436
515
|
const name = prefixPath + (renamed || path);
|
|
437
|
-
if (taken
|
|
516
|
+
if (taken.has(normalizeFilename(name))
|
|
517
|
+
|| takenSources.has(normalizeFilename(prefixPath + path))) // taken by vfs node above
|
|
438
518
|
return false; // false just in case it's a folder
|
|
439
519
|
const item = setVfsPath({ name, isFolder, source: (0, path_1.join)(source, path), parent, stats: entry.stats }, name, parent);
|
|
440
520
|
// masks containing '/' must be matched against the relative path while keeping walkDir recursion enabled
|
package/src/webdav.js
CHANGED
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.webdav = void 0;
|
|
7
7
|
exports.releaseWebdavLock = releaseWebdavLock;
|
|
8
8
|
exports.isWebdavLocked = isWebdavLocked;
|
|
9
|
-
const
|
|
9
|
+
const util_http_1 = require("./util-http");
|
|
10
10
|
const vfs_1 = require("./vfs");
|
|
11
11
|
const cross_1 = require("./cross");
|
|
12
12
|
const stream_1 = require("stream");
|
|
@@ -315,11 +315,14 @@ const webdav = async (ctx, next) => {
|
|
|
315
315
|
setWebdavHeaders(true);
|
|
316
316
|
return;
|
|
317
317
|
}
|
|
318
|
-
const body =
|
|
318
|
+
const body = await (0, util_http_1.readRequestBodyLimited)(ctx, 1024 * 1024);
|
|
319
|
+
if (!body)
|
|
320
|
+
return;
|
|
321
|
+
const bodyText = body.text;
|
|
319
322
|
const token = getProvidedLockToken();
|
|
320
323
|
let seconds = Number(ctx.get('timeout').split(',').find(x => /^Second-\d+$/i.test(x.trim()))?.trim().split('-', 2)[1]);
|
|
321
324
|
seconds = lodash_1.default.clamp(seconds || LOCK_DEFAULT_SECONDS, 1, LOCK_MAX_SECONDS);
|
|
322
|
-
if (!
|
|
325
|
+
if (!bodyText) {
|
|
323
326
|
// Finder and similar clients refresh an existing lock by sending LOCK without a body
|
|
324
327
|
if (!token)
|
|
325
328
|
return ctx.status = cross_1.HTTP_BAD_REQUEST;
|
|
@@ -338,7 +341,7 @@ const webdav = async (ctx, next) => {
|
|
|
338
341
|
ctx.body = renderLockResponse(lock.token, lock.seconds);
|
|
339
342
|
return;
|
|
340
343
|
}
|
|
341
|
-
const lockinfo = (0, cross_1.try_)(() => xmlParser.parse(
|
|
344
|
+
const lockinfo = (0, cross_1.try_)(() => xmlParser.parse(bodyText).lockinfo);
|
|
342
345
|
const scope = lodash_1.default.keys(lockinfo?.lockscope)[0];
|
|
343
346
|
const type = lodash_1.default.keys(lockinfo?.locktype)[0];
|
|
344
347
|
if (!scope || !type)
|
|
@@ -416,8 +419,11 @@ const webdav = async (ctx, next) => {
|
|
|
416
419
|
setWebdavHeaders(true);
|
|
417
420
|
return;
|
|
418
421
|
}
|
|
419
|
-
const body =
|
|
420
|
-
|
|
422
|
+
const body = await (0, util_http_1.readRequestBodyLimited)(ctx, 1024 * 1024);
|
|
423
|
+
if (!body)
|
|
424
|
+
return;
|
|
425
|
+
const bodyText = body.text;
|
|
426
|
+
const props = (0, cross_1.try_)(() => parseProppatchProps(bodyText)) || [];
|
|
421
427
|
if (!props.length)
|
|
422
428
|
return ctx.status = cross_1.HTTP_BAD_REQUEST;
|
|
423
429
|
const statuses = [];
|