hfs 0.55.0 → 0.55.2-beta3
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/{index-nTXwHDdR.js → index-BtyIH4Gp.js} +52 -52
- package/admin/assets/{sha512-BRULsGE4.js → sha512-CB8Tx1Fo.js} +1 -1
- package/admin/index.html +1 -1
- package/frontend/assets/{index-legacy-BoMy1vEB.js → index-legacy-BFUS5Ol_.js} +7 -7
- package/frontend/assets/{sha512-legacy-CP10jckG.js → sha512-legacy-HnxdKRIz.js} +1 -1
- package/frontend/index.html +1 -1
- package/package.json +2 -1
- package/src/config.js +0 -4
- package/src/const.js +4 -2
- package/src/cross-const.js +3 -1
- package/src/cross.js +0 -19
- package/src/i18n.js +2 -0
- package/src/langs/hfs-lang-vi.json +6 -4
- package/src/serveGuiAndSharedFiles.js +1 -0
- package/src/upload.js +32 -26
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
System.register(["./index-legacy-
|
|
1
|
+
System.register(["./index-legacy-BFUS5Ol_.js"],(function(h,t){"use strict";var i,s;return{setters:[function(h){i=h.g,s=h.c}],execute:function(){function t(h,t){for(var i=function(){var i=t[s];if("string"!=typeof i&&!Array.isArray(i)){var r=function(t){if("default"!==t&&!(t in h)){var s=Object.getOwnPropertyDescriptor(i,t);s&&Object.defineProperty(h,t,s.get?s:{enumerable:!0,get:function(){return i[t]}})}};for(var e in i)r(e)}},s=0;s<t.length;s++)i();return Object.freeze(Object.defineProperty(h,Symbol.toStringTag,{value:"Module"}))}var r={exports:{}};
|
|
2
2
|
/*
|
|
3
3
|
* [js-sha512]{@link https://github.com/emn178/js-sha512}
|
|
4
4
|
*
|
package/frontend/index.html
CHANGED
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
document.getElementById('root').innerHTML = 'Loading...'
|
|
23
23
|
</script>
|
|
24
24
|
<script crossorigin id="vite-legacy-polyfill" src="/assets/polyfills-legacy-DMrMt_pQ.js"></script>
|
|
25
|
-
<script crossorigin id="vite-legacy-entry" data-src="/assets/index-legacy-
|
|
25
|
+
<script crossorigin id="vite-legacy-entry" data-src="/assets/index-legacy-BFUS5Ol_.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
|
|
26
26
|
</body>
|
|
27
27
|
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hfs",
|
|
3
|
-
"version": "0.55.
|
|
3
|
+
"version": "0.55.2-beta3",
|
|
4
4
|
"description": "HTTP File Server",
|
|
5
5
|
"keywords": ["file server", "http server"],
|
|
6
6
|
"homepage": "https://rejetto.com/hfs",
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"fs-x-attributes": "^1.0.2",
|
|
80
80
|
"fswin": "^3.24.829",
|
|
81
81
|
"iconv-lite": "^0.6.3",
|
|
82
|
+
"immer": "^9.0.21",
|
|
82
83
|
"ip2location-nodejs": "^9.6.0",
|
|
83
84
|
"koa": "^2.15.3",
|
|
84
85
|
"koa-compress": "^5.1.0",
|
package/src/config.js
CHANGED
|
@@ -196,10 +196,6 @@ const saveDebounced = (0, misc_1.debounceAsync)(async () => {
|
|
|
196
196
|
const aWeekAgo = Date.now() - misc_1.DAY * 7;
|
|
197
197
|
if (await (0, promises_1.stat)(bak).then(x => aWeekAgo > Number(x.mtime || x.ctime), () => true))
|
|
198
198
|
await (0, promises_1.copyFile)(filePath, bak).catch(() => { }); // ignore errors
|
|
199
|
-
if (lodash_1.default.isEmpty(state)) {
|
|
200
|
-
console.error('saving empty config');
|
|
201
|
-
debugger;
|
|
202
|
-
}
|
|
203
199
|
await exports.configFile.save(stringify({ ...state, version: const_1.VERSION }))
|
|
204
200
|
.catch(err => console.error('Failed at saving config file, please ensure it is writable.', String(err)));
|
|
205
201
|
});
|
package/src/const.js
CHANGED
|
@@ -54,7 +54,7 @@ exports.DEV = process.env.DEV || exports.argv.dev ? 'DEV' : '';
|
|
|
54
54
|
exports.ORIGINAL_CWD = process.cwd();
|
|
55
55
|
exports.HFS_STARTED = new Date();
|
|
56
56
|
const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
|
|
57
|
-
exports.BUILD_TIMESTAMP = "
|
|
57
|
+
exports.BUILD_TIMESTAMP = "2025-01-03T13:39:42.709Z";
|
|
58
58
|
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
|
|
59
59
|
exports.VERSION = pkg.version;
|
|
60
60
|
exports.RUNNING_BETA = exports.VERSION.includes('-');
|
|
@@ -80,7 +80,7 @@ console.debug('arguments', exports.argv);
|
|
|
80
80
|
const dir = exports.argv.cwd || useHomeDir() && (0, path_1.join)((0, os_1.homedir)(), '.hfs');
|
|
81
81
|
if (dir) {
|
|
82
82
|
try {
|
|
83
|
-
fs.mkdirSync(dir);
|
|
83
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
84
84
|
}
|
|
85
85
|
catch (e) {
|
|
86
86
|
if (e.code !== 'EEXIST')
|
|
@@ -88,6 +88,8 @@ if (dir) {
|
|
|
88
88
|
}
|
|
89
89
|
process.chdir(dir);
|
|
90
90
|
}
|
|
91
|
+
else if (process.cwd().startsWith(process.env.windir + '\\')) // this happens if you run hfs from task scheduler
|
|
92
|
+
process.chdir(exports.APP_PATH);
|
|
91
93
|
console.log('working directory (cwd)', process.cwd());
|
|
92
94
|
if (exports.APP_PATH !== process.cwd())
|
|
93
95
|
console.log('app', exports.APP_PATH);
|
package/src/cross-const.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HTTP_MESSAGES = exports.HTTP_SERVICE_UNAVAILABLE = exports.HTTP_SERVER_ERROR = exports.HTTP_TOO_MANY_REQUESTS = exports.HTTP_FAILED_DEPENDENCY = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_PRECONDITION_FAILED = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_MOVED_PERMANENTLY = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_OK = exports.HFS_REPO = exports.PLUGIN_CUSTOM_REST_PREFIX = exports.NBSP = exports.PORT_DISABLED = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = void 0;
|
|
3
|
+
exports.HTTP_MESSAGES = exports.HTTP_SERVICE_UNAVAILABLE = exports.HTTP_SERVER_ERROR = exports.HTTP_TOO_MANY_REQUESTS = exports.HTTP_FAILED_DEPENDENCY = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_PRECONDITION_FAILED = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_MOVED_PERMANENTLY = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_OK = exports.UPLOAD_STATUS = exports.UPLOAD_RESUMABLE = exports.HFS_REPO = exports.PLUGIN_CUSTOM_REST_PREFIX = exports.NBSP = exports.PORT_DISABLED = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = void 0;
|
|
4
4
|
exports.SPECIAL_URI = '/~/';
|
|
5
5
|
exports.FRONTEND_URI = exports.SPECIAL_URI + 'frontend/';
|
|
6
6
|
exports.ADMIN_URI = exports.SPECIAL_URI + 'admin/';
|
|
@@ -10,6 +10,8 @@ exports.PORT_DISABLED = -1;
|
|
|
10
10
|
exports.NBSP = '\xA0';
|
|
11
11
|
exports.PLUGIN_CUSTOM_REST_PREFIX = '_';
|
|
12
12
|
exports.HFS_REPO = 'rejetto/hfs';
|
|
13
|
+
exports.UPLOAD_RESUMABLE = 'upload.resumable';
|
|
14
|
+
exports.UPLOAD_STATUS = 'upload.status';
|
|
13
15
|
exports.HTTP_OK = 200;
|
|
14
16
|
exports.HTTP_NO_CONTENT = 204;
|
|
15
17
|
exports.HTTP_PARTIAL_CONTENT = 206;
|
package/src/cross.js
CHANGED
|
@@ -534,22 +534,3 @@ const BROWSERS = {
|
|
|
534
534
|
Xbox: /xbox/i,
|
|
535
535
|
UC: /UCBrowser/i,
|
|
536
536
|
};
|
|
537
|
-
Object.defineProperties(Object.prototype, {
|
|
538
|
-
_L: {
|
|
539
|
-
enumerable: false,
|
|
540
|
-
writable: true,
|
|
541
|
-
value(msg = '') {
|
|
542
|
-
const val = this instanceof Number || this instanceof String || this instanceof Boolean ? this.valueOf() : this;
|
|
543
|
-
console.log('**', msg, val);
|
|
544
|
-
return val;
|
|
545
|
-
}
|
|
546
|
-
},
|
|
547
|
-
_D: {
|
|
548
|
-
enumerable: false,
|
|
549
|
-
writable: true,
|
|
550
|
-
value(msg = '') {
|
|
551
|
-
debugger;
|
|
552
|
-
return this._L(msg);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
});
|
package/src/i18n.js
CHANGED
|
@@ -40,6 +40,8 @@ function i18nFromTranslations(translations, embedded = 'en') {
|
|
|
40
40
|
for (const lang of searchLangs)
|
|
41
41
|
if (found = (_b = (_a = state.translations[selectedLang = lang]) === null || _a === void 0 ? void 0 : _a.translate) === null || _b === void 0 ? void 0 : _b[key])
|
|
42
42
|
break;
|
|
43
|
+
if (found)
|
|
44
|
+
break;
|
|
43
45
|
if (!warns.has(key) && langs.length && langs[0] !== embedded) {
|
|
44
46
|
warns.add(key);
|
|
45
47
|
console.debug("miss i18n:", key);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "thenoppy12 (Nguyễn Văn Ngu)",
|
|
3
|
-
"version": 2.
|
|
4
|
-
"hfs_version": "0.
|
|
3
|
+
"version": 2.6,
|
|
4
|
+
"hfs_version": "0.55.0",
|
|
5
5
|
"translate": {
|
|
6
6
|
"Select": "Chọn",
|
|
7
7
|
"n_files": "{n,plural,one{# tập tin} other{# tập tin}}",
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
"Close": "Đóng",
|
|
144
144
|
"Folder": "Thư mục",
|
|
145
145
|
"Web page": "Trang web",
|
|
146
|
-
"Link": "
|
|
146
|
+
"Link": "Đường dẫn",
|
|
147
147
|
"Auto-play": "Tự động phát",
|
|
148
148
|
"autoplay_seconds": "Số giây chờ ảnh",
|
|
149
149
|
"Select all": "Chọn hết",
|
|
@@ -166,6 +166,8 @@
|
|
|
166
166
|
"Logged in": "Đã đăng nhập!",
|
|
167
167
|
"Logged out": "Đã đăng xuất!",
|
|
168
168
|
"Cancel": "Huỷ bỏ",
|
|
169
|
-
"allow_session_ip_change": "Cho phép đổi IP trong phiên này"
|
|
169
|
+
"allow_session_ip_change": "Cho phép đổi IP trong phiên này",
|
|
170
|
+
"focus_hint": "Bằng cách nhập nhanh lên bàn phím, có thể tìm và xem các phần tử trong danh sách",
|
|
171
|
+
"copy_links": "Copy đường dẫn"
|
|
170
172
|
}
|
|
171
173
|
}
|
|
@@ -54,6 +54,7 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
54
54
|
ctx.status = cross_const_1.HTTP_SERVER_ERROR;
|
|
55
55
|
ctx.body = err.message || String(err);
|
|
56
56
|
});
|
|
57
|
+
ctx.req.on('close', () => dest.end());
|
|
57
58
|
const uri = await dest.lockMiddleware; // we need to wait more than just the stream
|
|
58
59
|
ctx.body = { uri };
|
|
59
60
|
}
|
package/src/upload.js
CHANGED
|
@@ -37,7 +37,7 @@ function setUploadMeta(path, ctx) {
|
|
|
37
37
|
}
|
|
38
38
|
// stay sync because we use this function with formidable()
|
|
39
39
|
const diskSpaceCache = (0, expiringCache_1.expiringCache)(3000); // invalidate shortly
|
|
40
|
-
const
|
|
40
|
+
const uploadingFiles = new Set();
|
|
41
41
|
function uploadWriter(base, baseUri, path, ctx) {
|
|
42
42
|
let fullPath = '';
|
|
43
43
|
if ((0, misc_1.dirTraversal)(path))
|
|
@@ -78,12 +78,12 @@ function uploadWriter(base, baseUri, path, ctx) {
|
|
|
78
78
|
catch (e) { // warn, but let it through
|
|
79
79
|
console.warn("can't check disk size:", e.message || String(e));
|
|
80
80
|
}
|
|
81
|
-
if (openFiles.has(fullPath))
|
|
82
|
-
return fail(const_1.HTTP_CONFLICT, 'uploading');
|
|
83
81
|
// optionally 'skip'
|
|
84
82
|
if (ctx.query.existing === 'skip' && fs_1.default.existsSync(fullPath))
|
|
85
83
|
return fail(const_1.HTTP_CONFLICT, 'exists');
|
|
86
|
-
|
|
84
|
+
if (uploadingFiles.has(fullPath))
|
|
85
|
+
return fail(const_1.HTTP_CONFLICT, 'uploading');
|
|
86
|
+
uploadingFiles.add(fullPath);
|
|
87
87
|
let overwriteRequestedButForbidden = false;
|
|
88
88
|
try {
|
|
89
89
|
// if upload creates a folder, then add meta to it too
|
|
@@ -91,38 +91,42 @@ function uploadWriter(base, baseUri, path, ctx) {
|
|
|
91
91
|
setUploadMeta(dir, ctx);
|
|
92
92
|
// use temporary name while uploading
|
|
93
93
|
const keepName = (0, path_1.basename)(fullPath).slice(-200);
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
const firstTempName = (0, path_1.join)(dir, 'hfs$upload-' + keepName);
|
|
95
|
+
const altTempName = (0, path_1.join)(dir, 'hfs$upload2-' + keepName);
|
|
96
|
+
const splitAndPreserving = ctx.query.preserveTempFile; // frontend knows about existing temp that can be resumed, but it is not resuming that, but instead it is continuing split-uploading on alternative temp file
|
|
97
|
+
let tempName = splitAndPreserving ? altTempName : firstTempName;
|
|
98
|
+
const stats = (0, misc_1.try_)(() => fs_1.default.statSync(tempName));
|
|
99
|
+
const resumableSize = (stats === null || stats === void 0 ? void 0 : stats.size) || 0; // we use size to even when user has not required resume, yet, to notify frontend of the possibility
|
|
100
|
+
const resumableTempName = resumableSize > 0 && tempName;
|
|
101
|
+
if (resumableTempName)
|
|
102
|
+
tempName = altTempName;
|
|
98
103
|
// checks for resume feature
|
|
99
104
|
let resume = Number(ctx.query.resume);
|
|
100
|
-
|
|
101
|
-
if (size === undefined) // stat failed
|
|
102
|
-
return fail(const_1.HTTP_SERVER_ERROR);
|
|
103
|
-
if (lodash_1.default.isNumber(size) && resume > size)
|
|
105
|
+
if (resume > resumableSize)
|
|
104
106
|
return fail(const_1.HTTP_RANGE_NOT_SATISFIABLE);
|
|
105
107
|
// warn frontend about resume possibility
|
|
106
108
|
let resumableLost = false;
|
|
107
|
-
if (!resume &&
|
|
109
|
+
if (!resume && !resumableTempName)
|
|
110
|
+
(0, frontEndApis_1.notifyClient)(ctx, const_1.UPLOAD_RESUMABLE, { [path]: 0 });
|
|
111
|
+
if (!resume && resumableTempName) {
|
|
108
112
|
const timeout = 30;
|
|
109
|
-
(0, frontEndApis_1.notifyClient)(ctx,
|
|
110
|
-
delayedDelete(
|
|
111
|
-
fs_1.default.rename(tempName,
|
|
113
|
+
(0, frontEndApis_1.notifyClient)(ctx, const_1.UPLOAD_RESUMABLE, { [path]: resumableSize, expires: Date.now() + timeout * 1000 });
|
|
114
|
+
delayedDelete(resumableTempName, timeout, () => // if user resumes, this upload is interrupted, and next upload will cancel this delayedDelete
|
|
115
|
+
fs_1.default.rename(tempName, resumableTempName, err => {
|
|
112
116
|
if (err)
|
|
113
117
|
return;
|
|
114
|
-
tempName =
|
|
118
|
+
tempName = resumableTempName;
|
|
115
119
|
resumableLost = true;
|
|
116
120
|
}));
|
|
117
121
|
}
|
|
118
122
|
// append if resuming
|
|
119
|
-
const resuming = resume &&
|
|
123
|
+
const resuming = resume && resumableTempName;
|
|
120
124
|
if (!resuming)
|
|
121
125
|
resume = 0;
|
|
122
126
|
const writeStream = (0, misc_1.createStreamLimiter)(reqSize !== null && reqSize !== void 0 ? reqSize : Infinity);
|
|
123
|
-
if (resuming) {
|
|
127
|
+
if (resuming && !splitAndPreserving) {
|
|
124
128
|
fs_1.default.rm(tempName, () => { });
|
|
125
|
-
tempName =
|
|
129
|
+
tempName = resumableTempName;
|
|
126
130
|
}
|
|
127
131
|
cancelDeletion(tempName);
|
|
128
132
|
ctx.state.uploadDestinationPath = tempName;
|
|
@@ -131,7 +135,7 @@ function uploadWriter(base, baseUri, path, ctx) {
|
|
|
131
135
|
const resEvent = events_1.default.emit('uploadStart', obj);
|
|
132
136
|
if (resEvent === null || resEvent === void 0 ? void 0 : resEvent.isDefaultPrevented())
|
|
133
137
|
return;
|
|
134
|
-
const fileStream = resuming ? fs_1.default.createWriteStream(
|
|
138
|
+
const fileStream = resuming ? fs_1.default.createWriteStream(resumableTempName, { flags: 'r+', start: resume })
|
|
135
139
|
: fs_1.default.createWriteStream(tempName);
|
|
136
140
|
writeStream.on('error', e => {
|
|
137
141
|
releaseFile();
|
|
@@ -145,7 +149,7 @@ function uploadWriter(base, baseUri, path, ctx) {
|
|
|
145
149
|
try {
|
|
146
150
|
await new Promise(res => fileStream.close(res)); // this only seem to be necessary on Windows
|
|
147
151
|
if (ctx.req.aborted) {
|
|
148
|
-
if (
|
|
152
|
+
if (resumableTempName && !resumableLost && !resuming) // we don't want to be left with 2 temp files
|
|
149
153
|
return (0, promises_1.rm)(tempName);
|
|
150
154
|
const sec = exports.deleteUnfinishedUploadsAfter.get();
|
|
151
155
|
return lodash_1.default.isNumber(sec) && delayedDelete(tempName, sec);
|
|
@@ -168,12 +172,14 @@ function uploadWriter(base, baseUri, path, ctx) {
|
|
|
168
172
|
try {
|
|
169
173
|
await (0, promises_1.rename)(tempName, dest);
|
|
170
174
|
cancelDeletion(tempName); // not necessary, as deletion's failure is silent, but still
|
|
175
|
+
if (splitAndPreserving) // we've been using altTempName, but now we're done, so we can delete firstTempName
|
|
176
|
+
delayedDelete(firstTempName, 0);
|
|
171
177
|
ctx.state.uploadDestinationPath = dest;
|
|
172
178
|
setUploadMeta(dest, ctx);
|
|
173
179
|
if (ctx.query.comment)
|
|
174
180
|
void (0, comments_1.setCommentFor)(dest, String(ctx.query.comment));
|
|
175
|
-
if (
|
|
176
|
-
(0, promises_1.rm)(
|
|
181
|
+
if (resumableTempName && !resuming) // this happens if user decided to not resume and the new upload finished before delayedDelete
|
|
182
|
+
(0, promises_1.rm)(resumableTempName).catch(console.warn);
|
|
177
183
|
obj.uri = (0, misc_1.enforceFinal)('/', baseUri) + (0, misc_1.pathEncode)((0, path_1.basename)(dest));
|
|
178
184
|
events_1.default.emit('uploadFinished', obj);
|
|
179
185
|
if (resEvent)
|
|
@@ -236,7 +242,7 @@ function uploadWriter(base, baseUri, path, ctx) {
|
|
|
236
242
|
delete waitingToBeDeleted[path];
|
|
237
243
|
}
|
|
238
244
|
function releaseFile() {
|
|
239
|
-
|
|
245
|
+
uploadingFiles.delete(fullPath);
|
|
240
246
|
}
|
|
241
247
|
function fail(status, msg) {
|
|
242
248
|
console.debug('upload failed', status, msg);
|
|
@@ -245,7 +251,7 @@ function uploadWriter(base, baseUri, path, ctx) {
|
|
|
245
251
|
ctx.status = status;
|
|
246
252
|
if (msg)
|
|
247
253
|
ctx.body = msg;
|
|
248
|
-
(0, frontEndApis_1.notifyClient)(ctx,
|
|
254
|
+
(0, frontEndApis_1.notifyClient)(ctx, const_1.UPLOAD_STATUS, { [path]: ctx.status }); // allow browsers to detect failure while still sending body
|
|
249
255
|
}
|
|
250
256
|
}
|
|
251
257
|
exports.uploadWriter = uploadWriter;
|