hfs 0.42.2 → 0.43.0
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/README.md +9 -12
- package/admin/assets/index-5cd667a5.js +511 -0
- package/admin/assets/index-8ff39373.css +1 -0
- package/{frontend/assets/sha512-bce9fb1c.js → admin/assets/sha512-55ff2fa3.js} +1 -1
- package/admin/index.html +2 -2
- package/frontend/assets/index-27488fde.js +94 -0
- package/frontend/assets/index-54a5c76f.css +1 -0
- package/{admin/assets/sha512-af87c1bd.js → frontend/assets/sha512-8ebf6e2a.js} +1 -1
- package/frontend/fontello.css +9 -3
- package/frontend/fontello.woff2 +0 -0
- package/frontend/index.html +2 -2
- package/package.json +2 -2
- package/plugins/antibrute/plugin.js +1 -1
- package/plugins/download-counter/plugin.js +10 -3
- package/plugins/download-counter/public/main.js +12 -2
- package/src/adminApis.js +3 -3
- package/src/api.accounts.js +2 -1
- package/src/api.file_list.js +15 -2
- package/src/api.lang.js +8 -11
- package/src/api.vfs.js +11 -5
- package/src/block.js +6 -20
- package/src/config.js +6 -2
- package/src/const.js +2 -2
- package/src/customHtml.js +1 -1
- package/src/frontEndApis.js +1 -26
- package/src/lang.js +77 -0
- package/src/langs/hfs-lang-it.json +100 -0
- package/src/langs/hfs-lang-ko.json +103 -0
- package/src/langs/hfs-lang-ru.json +106 -0
- package/src/langs/hfs-lang-sr.json +108 -0
- package/src/langs/hfs-lang-zh.json +98 -0
- package/src/listen.js +8 -3
- package/src/middlewares.js +10 -0
- package/src/misc.js +13 -9
- package/src/perm.js +1 -1
- package/src/serveFile.js +2 -2
- package/src/serveGuiFiles.js +21 -8
- package/src/upload.js +1 -1
- package/src/vfs.js +27 -33
- package/admin/assets/index-1db4299e.js +0 -511
- package/admin/assets/index-94bbe0be.css +0 -1
- package/frontend/assets/index-297a3f3d.js +0 -94
- package/frontend/assets/index-a09cacfd.css +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@charset "UTF-8";:root{height:100dvh;--bg: #fff;--text: #555;--ghost-contrast: #8882;--faint-contrast: #8884;--mild-contrast: #8886;--good-contrast: #000a;--button-bg: #68a;--button-text: #eaeaea;--focus-color: #468;--separator: " \2013 "}:root .highlightedText,:root .file-menu a:hover{color:#0006;text-shadow:0 0 3px rgba(0,0,0,.4)}:root .theme-dark{--bg: #000;--text: #999;--good-contrast: #fffa;--button-bg: #345;--button-text: #999;color-scheme:dark}:root .theme-dark .highlightedText,:root .theme-dark .file-menu a:hover,:root .file-menu .theme-dark a:hover{color:#fff;text-shadow:0 0 3px #fff}:root .theme-dark a{color:#8ac}:root .theme-dark .dialog-closer{background:#633}:root .theme-dark .dialog-icon{color:#ccc}:root .theme-dark .dialog-icon .icon{color:#aaa;margin-left:-1px;font-size:95%}:root .theme-dark .dialog-backdrop{background:rgba(51,51,51,.7333333333)}:root .theme-dark .error-msg{color:#b88;background-color:#623}:root .theme-dark button.toggled{color:#eee}body{background:var(--bg);margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,button,select,input{font-size:12pt}#root{max-width:50em;margin:auto;min-height:100vh;display:flex;flex-direction:column}body,input{color:var(--text)}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}input:not([type=checkbox]),select{padding:.3em .4em;border-radius:.5em;background:var(--bg);border-color:var(--mild-contrast);color:var(--good-contrast);max-width:100%;box-sizing:border-box;width:100%}input[type=checkbox]{transform:scale(1.7);accent-color:var(--button-bg)}label input[type=checkbox]{margin-right:.8em}select{text-align:center}.hidden{display:none!important}.icon{font-size:1.2em}.emoji-icon,.file-icon{display:inline-block;width:1.4em;text-align:center}.file-icon{height:1em;background-size:contain;background-repeat:no-repeat;background-position:center;vertical-align:text-bottom}.icon.mirror:before{transform:scaleX(-1)}a{text-decoration:none;color:#57a}button{background-color:var(--button-bg);color:var(--button-text);padding:.5em 1em;border:transparent;text-decoration:none;border-radius:.3em;vertical-align:middle;cursor:pointer}button:hover{outline:1px solid var(--mild-contrast)}button.toggled{color:#fff;text-shadow:0 0 3px #fff}button:focus-visible,.breadcrumb:focus-visible{outline:3px solid var(--focus-color)}a>button{width:100%}input:focus-visible,select:focus-visible,ul a:focus-visible{border-radius:.3em;border-color:transparent;outline:2px solid var(--focus-color)}.icon-button,ul.dir li .entry-panel .file-menu-button{font-size:.7em;padding:.2em .4em;margin-left:.4em;vertical-align:bottom}.error-msg{background-color:#faa;color:#833;padding:.5em 1em}header{position:sticky;top:0;background:var(--bg);padding:.2em;z-index:1}.before-sliding{width:0!important;flex:0!important;margin:0!important;height:0!important;padding:0!important;overflow:hidden!important;transition:flex .5s}.show-sliding{transition:flex .5s;overflow:clip;flex:1;white-space:nowrap}.ani-working{animation:1s blink infinite}@keyframes blink{0%{opacity:1}50%{opacity:.2}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.spinner,.icon.spinner:before{animation:1.5s spin infinite linear;display:inline-flex;justify-content:center;align-items:center;width:min-content}.icon.emoji.spinner{display:inline-block}.breadcrumb{padding:.1em .6em .2em;line-height:1.8em;border-radius:.7em;background-color:var(--button-bg);color:var(--button-text);border-top:1px solid #666;margin-right:-.1em}.breadcrumb:nth-child(-n+3) .icon{padding:0 .2em}#folder-stats,#filter-bar>span{font-size:90%}#folder-stats{margin:.5em 0 0 .5em;float:right}#folder-stats .icon{margin-right:.3em}#filter{flex:1;box-sizing:border-box}#filter-bar{display:flex;align-items:center;gap:.8em;margin:.2em 0 0;padding:2px 0 1px 11px;height:1.8em}#filter-bar input[type=checkbox]{margin-top:.5em}#filter-bar span:empty{display:none}ul.dir{flex:1;padding:0;margin:0;clear:both}ul.dir>p{text-align:center}ul.dir li{display:block;list-style-type:none;padding:.3em .3em .4em;border-bottom:1px solid var(--faint-contrast)}ul.dir li:nth-of-type(odd){background-color:var(--ghost-contrast)}ul.dir li input[type=checkbox]{margin:0 .8em}ul.dir li a:last-of-type{word-break:break-word;padding-right:.3em}ul.dir li a .icon{margin-right:.3em}ul.dir li a:hover{text-decoration:underline}ul.dir li .entry-panel{float:right;margin-top:.3em;display:flex;align-items:center}ul.dir li .entry-panel .file-menu-button{margin:-3px 0 -3px .4em}ul.dir li .entry-panel .entry-props{font-size:90%;margin-left:4px;font-variant-numeric:tabular-nums}ul.dir li .entry-panel .entry-props .entry-size-unit{margin-left:.3em}ul.dir li>div:last-of-type{clear:both}ul.dir li.page-separator{margin-top:1em;position:relative}ul.dir li.page-separator:before{content:attr(label);position:absolute;top:-1.8em;font-size:smaller;margin-left:calc(50% - 1em);opacity:.9}#menu-bar{display:flex;justify-content:space-evenly;flex-wrap:wrap}#menu-bar>*{flex:1;margin:.1em}#menu-bar button{padding-left:0;padding-right:0}#searched{margin:.2em}#user-panel{display:flex;flex-direction:column;gap:1em}#user-panel a>button{width:100%}button label{cursor:inherit;margin-left:.5em}.dialog-backdrop.working{font-size:5em;animation:1s fade-in}.dialog-content{padding:.2em}.dialog-alert .dialog-content{text-align:center}.dialog-alert .dialog-content p{text-align:left;display:inline-block}.dialog{min-width:11em;--color: var(--button-bg)}#paging{position:sticky;bottom:0;display:flex;gap:.1em;background:var(--bg);padding:0 .2em .2em}#paging>button{z-index:1}#paging button{box-shadow:0 0 .3em .3em #0003}#paging #paging-middle{padding:0 .5em;margin:0 -.3em;display:flex;gap:.5em;flex:1;overflow-x:auto}#paging #paging-middle>button{flex:1;padding-top:0;padding-bottom:0}#paging button{background:var(--button-bg);text-align:center;white-space:nowrap;padding:.5em}.upload-progress:before{content:var(--separator)}.entry-size:after{content:var(--separator)}.upload-progress{min-width:4em;display:inline-block;margin-left:.5em}.upload-list td:nth-child(1){width:0}.upload-list td:nth-child(2){text-align:right;width:0;white-space:nowrap;padding-left:.5em}.upload-list td:nth-child(3){padding:.2em .5em;word-break:break-word}.dialog-login form{display:flex;flex-direction:column;gap:1.2em}.dialog-login label{display:block;margin-bottom:.5em;margin-left:.1em}.miss-perm{margin:.3em}.popup-menu-button{font-size:.8em;padding:.2em .3em;position:absolute;opacity:.8}.popup-menu-button:hover{opacity:1}.file-dialog .dialog-content{min-width:calc(100% - 1em)}.file-dialog .dialog{min-width:13em}.file-dialog-properties{word-break:break-word;line-height:1.5em}.file-dialog-properties dt{font-weight:700}.file-dialog-properties dd{margin-left:1.5em}.file-menu{margin-top:1em;display:flex;flex-direction:column}.file-menu a{padding:.5em 0}.file-menu a:first-child{padding-top:1em;border-top:1px solid var(--faint-contrast)}.file-menu a .icon{margin-right:.5em}@media (min-width: 42em){body{scrollbar-width:thin;scrollbar-color:var(--button-bg) var(--ghost-contrast)}body::-webkit-scrollbar{width:12px}body::-webkit-scrollbar-track{background:var(--ghost-contrast)}body::-webkit-scrollbar-thumb{background-color:var(--button-bg);border-radius:20px;border:1px solid var(--ghost-contrast)}}@media (max-width: 42em){:root{--ghost-contrast: #8883}body,button,select{font-size:14pt}#menu-bar button label,#filter-bar button label{display:none}#filter-bar{margin-top:.4em}#filter-bar button{width:17.6vw;height:2.3em}.breadcrumb{word-break:break-all}.breadcrumb .icon{font-size:24px}}.dialog-backdrop{position:fixed;inset:0;background:#888a;display:flex;justify-content:center;align-items:center;z-index:1000}.dialog{background:#fff;background:var(--bg);padding:max(.5em,1vw);border-radius:1em;position:relative;margin:0 3vw;overflow:hidden;max-height:calc(100vh - 2em);display:flex;flex-direction:column;justify-content:center}.dialog-icon{color:#fff;background-color:var(--color);position:absolute;top:0;width:2em;height:1.8em;text-align:center;border-radius:.8em 0}.dialog-title{font-size:120%;margin-top:-.4em;padding:0 .5em}.dialog-closer~.dialog-title{margin-right:2em}.dialog-type~.dialog-title{margin-left:2em}.dialog-icon~.dialog-title{text-align:center}.dialog-closer{border-radius:0 .8em;right:0;padding:0;background-color:#c88}.dialog-icon~.dialog-content{margin-top:2em}.dialog-type{left:0;top:0;overflow:hidden;line-height:1.8em;opacity:.8}.dialog-content{overflow:auto;max-height:calc(100vh - 4.5em)}.dialog-content p{white-space:pre-wrap;margin:.5em 0}.dialog-confirm .dialog-content button{margin-top:1em}.dialog-alert-info{--color: #282 }.dialog-alert-warning{--color: #c91 }.dialog-alert-error{--color: #822}@media (max-width: 42em){.dialog-icon{font-size:120%}.dialog-icon~.dialog-content{margin-top:2.5em}.dialog-title{margin-top:-.2em}}.dialog-prompt label{display:block;margin-bottom:.5em;margin-left:.1em}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{c as SF}from"./index-
|
|
1
|
+
import{c as SF}from"./index-27488fde.js";function OF(iF,hF){for(var eF=0;eF<hF.length;eF++){const tF=hF[eF];if(typeof tF!="string"&&!Array.isArray(tF)){for(const w in tF)if(w!=="default"&&!(w in iF)){const lF=Object.getOwnPropertyDescriptor(tF,w);lF&&Object.defineProperty(iF,w,lF.get?lF:{enumerable:!0,get:()=>tF[w]})}}}return Object.freeze(Object.defineProperty(iF,Symbol.toStringTag,{value:"Module"}))}var EF={},UF={get exports(){return EF},set exports(iF){EF=iF}};/*
|
|
2
2
|
* [js-sha512]{@link https://github.com/emn178/js-sha512}
|
|
3
3
|
*
|
|
4
4
|
* @version 0.8.0
|
package/frontend/fontello.css
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
@font-face {
|
|
2
2
|
font-family: 'fontello';
|
|
3
|
-
src: url('fontello.woff2?
|
|
3
|
+
src: url('fontello.woff2?19258766') format('woff2'); font-weight: normal;
|
|
4
|
+
font-weight: normal;
|
|
4
5
|
font-style: normal;
|
|
5
6
|
}
|
|
6
7
|
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
|
|
@@ -9,7 +10,7 @@
|
|
|
9
10
|
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
|
10
11
|
@font-face {
|
|
11
12
|
font-family: 'fontello';
|
|
12
|
-
src: url('../font/fontello.svg?
|
|
13
|
+
src: url('../font/fontello.svg?19258766#fontello') format('svg');
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
16
|
*/
|
|
@@ -49,9 +50,11 @@
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
.fa-cog:before { content: '\e800'; } /* '' */
|
|
53
|
+
.fa-audio:before { content: '\e801'; } /* '' */
|
|
52
54
|
.fa-doc:before { content: '\e802'; } /* '' */
|
|
53
55
|
.fa-stop:before { content: '\e803'; } /* '' */
|
|
54
56
|
.fa-play:before { content: '\e804'; } /* '' */
|
|
57
|
+
.fa-music:before { content: '\e805'; } /* '' */
|
|
55
58
|
.fa-cancel:before { content: '\e806'; } /* '' */
|
|
56
59
|
.fa-edit:before { content: '\e807'; } /* '' */
|
|
57
60
|
.fa-check:before { content: '\e808'; } /* '' */
|
|
@@ -63,8 +66,12 @@
|
|
|
63
66
|
.fa-to_start:before { content: '\e80e'; } /* '' */
|
|
64
67
|
.fa-retweet:before { content: '\e80f'; } /* '' */
|
|
65
68
|
.fa-to_end:before { content: '\e810'; } /* '' */
|
|
69
|
+
.fa-picture:before { content: '\e811'; } /* '' */
|
|
70
|
+
.fa-camera:before { content: '\e812'; } /* '' */
|
|
66
71
|
.fa-search:before { content: '\e813'; } /* '' */
|
|
67
72
|
.fa-logout:before { content: '\e814'; } /* '' */
|
|
73
|
+
.fa-video:before { content: '\e815'; } /* '' */
|
|
74
|
+
.fa-left:before { content: '\e816'; } /* '' */
|
|
68
75
|
.fa-spin6:before { content: '\e839'; } /* '' */
|
|
69
76
|
.fa-crown:before { content: '\e844'; } /* '' */
|
|
70
77
|
.fa-download:before { content: '\f02e'; } /* '' */
|
|
@@ -73,5 +80,4 @@
|
|
|
73
80
|
.fa-menu:before { content: '\f0c9'; } /* '' */
|
|
74
81
|
.fa-quote:before { content: '\f10d'; } /* '' */
|
|
75
82
|
.fa-unlink:before { content: '\f127'; } /* '' */
|
|
76
|
-
.fa-level-up:before { content: '\f148'; } /* '' */
|
|
77
83
|
.fa-archive:before { content: '\f1c6'; } /* '' */
|
package/frontend/fontello.woff2
CHANGED
|
Binary file
|
package/frontend/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0" />
|
|
6
6
|
<link href="/fontello.css" rel="stylesheet" />
|
|
7
7
|
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-27488fde.js"></script>
|
|
9
|
+
<link rel="stylesheet" href="/assets/index-54a5c76f.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hfs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"description": "HTTP File Server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"file server",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"build-server": "rm -rf dist/src dist/plugins && tsc --target es2018 && touch package.json && cp -v -r package.json README* LICENSE* plugins dist && find dist -name .DS_Store -delete && node afterbuild.js",
|
|
20
20
|
"build-frontend": "npm run build --workspace=frontend",
|
|
21
21
|
"build-admin": "npm run build --workspace=admin",
|
|
22
|
-
"server-for-test": "node dist/src --cwd . --config tests",
|
|
22
|
+
"server-for-test": "node dist/src --cwd . --config tests && rm custom.html",
|
|
23
23
|
"server-for-test-dev": "cross-env DEV=1 nodemon --ignore tests/ --watch src -e ts,tsx --exec ts-node src -- --cwd . --config tests",
|
|
24
24
|
"test": "mocha -r ts-node/register 'tests/**/*.ts'",
|
|
25
25
|
"pub": "cd dist && npm publish",
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
exports.description = "Counts downloads for each file, and displays the total in the list"
|
|
2
|
-
exports.version =
|
|
3
|
-
exports.apiRequired =
|
|
1
|
+
exports.description = "Counts downloads for each file, and displays the total in the list or file menu"
|
|
2
|
+
exports.version = 4 // config.where
|
|
3
|
+
exports.apiRequired = 8
|
|
4
|
+
|
|
5
|
+
exports.config = {
|
|
6
|
+
where: { frontend: true, type: 'select', options: ['list', 'menu'] }
|
|
7
|
+
}
|
|
8
|
+
exports.configDialog = {
|
|
9
|
+
sx: { maxWidth: '20em' },
|
|
10
|
+
}
|
|
4
11
|
|
|
5
12
|
exports.init = async api => {
|
|
6
13
|
const _ = api.require('lodash')
|
|
@@ -1,2 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
(() => { // this wrapper avoids name clashing of outer variables and functions
|
|
2
|
+
const config = HFS.getPluginConfig()
|
|
3
|
+
|
|
4
|
+
const inMenu = config.where === 'menu'
|
|
5
|
+
HFS.onEvent('additionalEntryProps', ({ entry: { hits } }, { t }) =>
|
|
6
|
+
hits && !inMenu && `<span class="download-counter" title="${t`download counter`}">${hits}</span>`)
|
|
7
|
+
|
|
8
|
+
HFS.onEvent('fileMenu', ({ entry, props }) => {
|
|
9
|
+
if (inMenu && !entry.isFolder)
|
|
10
|
+
props.push(["Downloads", entry.hits || 0])
|
|
11
|
+
})
|
|
12
|
+
})()
|
package/src/adminApis.js
CHANGED
|
@@ -167,8 +167,8 @@ for (const [k, was] of Object.entries(exports.adminApis))
|
|
|
167
167
|
: new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED, props);
|
|
168
168
|
};
|
|
169
169
|
exports.localhostAdmin = (0, config_1.defineConfig)('localhost_admin', true);
|
|
170
|
-
exports.adminNet = (0, config_1.defineConfig)('admin_net', '');
|
|
171
|
-
exports.favicon = (0, config_1.defineConfig)('favicon');
|
|
170
|
+
exports.adminNet = (0, config_1.defineConfig)('admin_net', '', v => (0, misc_1.makeNetMatcher)(v, true));
|
|
171
|
+
exports.favicon = (0, config_1.defineConfig)('favicon', '');
|
|
172
172
|
exports.title = (0, config_1.defineConfig)('title', "File server");
|
|
173
173
|
function ctxAdminAccess(ctx) {
|
|
174
174
|
return !ctx.state.proxiedFor // we consider localhost_admin only if no proxy is detected
|
|
@@ -187,6 +187,6 @@ function anyAccountCanLoginAdmin() {
|
|
|
187
187
|
}
|
|
188
188
|
exports.anyAccountCanLoginAdmin = anyAccountCanLoginAdmin;
|
|
189
189
|
function allowAdmin(ctx) {
|
|
190
|
-
return
|
|
190
|
+
return exports.adminNet.compiled()(ctx.ip);
|
|
191
191
|
}
|
|
192
192
|
exports.allowAdmin = allowAdmin;
|
package/src/api.accounts.js
CHANGED
|
@@ -32,6 +32,7 @@ const apis = {
|
|
|
32
32
|
return { list: lodash_1.default.filter(perm_1.accountsConfig.get(), perm_1.accountCanLoginAdmin).map(ac => ac.username) };
|
|
33
33
|
},
|
|
34
34
|
set_account({ username, changes }, ctx) {
|
|
35
|
+
var _a;
|
|
35
36
|
const { admin } = changes;
|
|
36
37
|
if (admin === null)
|
|
37
38
|
changes.admin = undefined;
|
|
@@ -41,7 +42,7 @@ const apis = {
|
|
|
41
42
|
if (!acc)
|
|
42
43
|
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
|
|
43
44
|
(0, perm_1.setAccount)(acc, changes);
|
|
44
|
-
if (changes.username && ctx.session)
|
|
45
|
+
if (changes.username && ((_a = ctx.session) === null || _a === void 0 ? void 0 : _a.username) === username)
|
|
45
46
|
ctx.session.username = changes.username;
|
|
46
47
|
return lodash_1.default.pick(acc, 'username');
|
|
47
48
|
},
|
package/src/api.file_list.js
CHANGED
|
@@ -58,7 +58,7 @@ const file_list = async ({ path, offset, limit, search, omit, sse }, ctx) => {
|
|
|
58
58
|
break;
|
|
59
59
|
if (!filter((0, vfs_1.getNodeName)(sub)))
|
|
60
60
|
continue;
|
|
61
|
-
const entry = await nodeToDirEntry(sub);
|
|
61
|
+
const entry = await nodeToDirEntry(ctx, sub);
|
|
62
62
|
if (!entry)
|
|
63
63
|
continue;
|
|
64
64
|
const cbParams = { entry, ctx, listPath: path, node: sub };
|
|
@@ -87,7 +87,7 @@ const file_list = async ({ path, offset, limit, search, omit, sse }, ctx) => {
|
|
|
87
87
|
}
|
|
88
88
|
};
|
|
89
89
|
exports.file_list = file_list;
|
|
90
|
-
async function nodeToDirEntry(node) {
|
|
90
|
+
async function nodeToDirEntry(ctx, node) {
|
|
91
91
|
let { source, default: def } = node;
|
|
92
92
|
const name = (0, vfs_1.getNodeName)(node);
|
|
93
93
|
if (!source)
|
|
@@ -98,14 +98,27 @@ async function nodeToDirEntry(node) {
|
|
|
98
98
|
const st = await (0, promises_1.stat)(source);
|
|
99
99
|
const folder = st.isDirectory();
|
|
100
100
|
const { ctime, mtime } = st;
|
|
101
|
+
const pl = node.can_list === vfs_1.WHO_NO_ONE ? 'l'
|
|
102
|
+
: !(0, vfs_1.hasPermission)(node, 'can_list', ctx) ? 'L'
|
|
103
|
+
: '';
|
|
104
|
+
// no download here, but maybe inside?
|
|
105
|
+
const pr = node.can_read === vfs_1.WHO_NO_ONE && !(folder && filesInsideCould()) ? 'r'
|
|
106
|
+
: !(0, vfs_1.hasPermission)(node, 'can_read', ctx) ? 'R'
|
|
107
|
+
: '';
|
|
101
108
|
return {
|
|
102
109
|
n: name + (folder ? '/' : ''),
|
|
103
110
|
c: ctime,
|
|
104
111
|
m: Math.abs(+mtime - +ctime) < 1000 ? undefined : mtime,
|
|
105
112
|
s: folder ? undefined : st.size,
|
|
113
|
+
p: (pr + pl) || undefined
|
|
106
114
|
};
|
|
107
115
|
}
|
|
108
116
|
catch (_a) {
|
|
109
117
|
return null;
|
|
110
118
|
}
|
|
119
|
+
function filesInsideCould(n = node) {
|
|
120
|
+
var _a;
|
|
121
|
+
return (0, vfs_1.masksCouldGivePermission)(n.masks, 'can_read')
|
|
122
|
+
|| ((_a = n.children) === null || _a === void 0 ? void 0 : _a.some(c => c.can_read || filesInsideCould(c))); // we count on the boolean-compliant nature of the permission type here
|
|
123
|
+
}
|
|
111
124
|
}
|
package/src/api.lang.js
CHANGED
|
@@ -10,21 +10,22 @@ const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
|
10
10
|
const promises_1 = require("fs/promises");
|
|
11
11
|
const const_1 = require("./const");
|
|
12
12
|
const misc_1 = require("./misc");
|
|
13
|
-
const
|
|
14
|
-
const SUFFIX = '.json';
|
|
13
|
+
const lang_1 = require("./lang");
|
|
15
14
|
const apis = {
|
|
16
15
|
list_langs() {
|
|
17
16
|
return new apiMiddleware_1.SendListReadable({
|
|
18
17
|
doAtStart: async (list) => {
|
|
19
|
-
for await (let name of fast_glob_1.default.stream(code2file('*'))) {
|
|
18
|
+
for await (let name of fast_glob_1.default.stream((0, lang_1.code2file)('*'))) {
|
|
20
19
|
name = String(name);
|
|
21
|
-
const code =
|
|
20
|
+
const code = (0, lang_1.file2code)(name);
|
|
22
21
|
try {
|
|
23
22
|
const data = JSON.parse(await (0, promises_1.readFile)(name, 'utf8'));
|
|
24
23
|
list.add({ code, ...lodash_1.default.omit(data, 'translate') });
|
|
25
24
|
}
|
|
26
25
|
catch (_a) { }
|
|
27
26
|
}
|
|
27
|
+
for (const [code, data] of Object.entries(lang_1.EMBEDDED_TRANSLATIONS))
|
|
28
|
+
list.add({ code, embedded: true, ...lodash_1.default.omit(data, 'translate') });
|
|
28
29
|
list.close();
|
|
29
30
|
}
|
|
30
31
|
});
|
|
@@ -32,7 +33,7 @@ const apis = {
|
|
|
32
33
|
async del_lang({ code }) {
|
|
33
34
|
validateCode(code);
|
|
34
35
|
try {
|
|
35
|
-
await (0, promises_1.rm)(code2file(code));
|
|
36
|
+
await (0, promises_1.rm)((0, lang_1.code2file)(code));
|
|
36
37
|
return {};
|
|
37
38
|
}
|
|
38
39
|
catch (e) {
|
|
@@ -41,10 +42,9 @@ const apis = {
|
|
|
41
42
|
},
|
|
42
43
|
async add_langs({ langs }) {
|
|
43
44
|
for (let [code, content] of Object.entries(langs)) {
|
|
44
|
-
|
|
45
|
-
code = code.slice(PREFIX.length, -SUFFIX.length);
|
|
45
|
+
code = (0, lang_1.file2code)(code);
|
|
46
46
|
validateCode(code);
|
|
47
|
-
const fn = code2file(code);
|
|
47
|
+
const fn = (0, lang_1.code2file)(code);
|
|
48
48
|
const s = content = String(content);
|
|
49
49
|
if (!(0, misc_1.tryJson)(s))
|
|
50
50
|
return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_ACCEPTABLE, "bad content for file " + fn);
|
|
@@ -54,9 +54,6 @@ const apis = {
|
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
56
|
exports.default = apis;
|
|
57
|
-
function code2file(code) {
|
|
58
|
-
return PREFIX + code.toLowerCase() + SUFFIX;
|
|
59
|
-
}
|
|
60
57
|
function validateCode(code) {
|
|
61
58
|
if (!/^(\w\w)(-\w\w)*$/.test(code))
|
|
62
59
|
throw new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad code/filename');
|
package/src/api.vfs.js
CHANGED
|
@@ -86,6 +86,7 @@ const apis = {
|
|
|
86
86
|
return n;
|
|
87
87
|
},
|
|
88
88
|
async add_vfs({ parent, source, name }) {
|
|
89
|
+
var _a;
|
|
89
90
|
const n = parent ? await urlToNodeOriginal(parent) : vfs_1.vfs;
|
|
90
91
|
if (!n)
|
|
91
92
|
return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'invalid parent');
|
|
@@ -93,13 +94,17 @@ const apis = {
|
|
|
93
94
|
return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_ACCEPTABLE, 'invalid parent');
|
|
94
95
|
if ((0, misc_1.isWindowsDrive)(source))
|
|
95
96
|
source += '\\'; // slash must be included, otherwise it will refer to the cwd of that drive
|
|
97
|
+
let tryName = (0, vfs_1.getNodeName)({ name, source });
|
|
98
|
+
const ext = (0, path_1.extname)(tryName);
|
|
99
|
+
const noExt = ext ? tryName.slice(0, -ext.length) : tryName;
|
|
100
|
+
let idx = 2;
|
|
101
|
+
while ((_a = n.children) === null || _a === void 0 ? void 0 : _a.find((0, vfs_1.isSameFilenameAs)(tryName)))
|
|
102
|
+
tryName = `${noExt} ${idx++}${ext}`;
|
|
103
|
+
name = tryName;
|
|
96
104
|
n.children || (n.children = []);
|
|
97
|
-
const sameName = name && (0, vfs_1.isSameFilenameAs)(name);
|
|
98
|
-
if (n.children.find(x => source && source === x.source || (sameName === null || sameName === void 0 ? void 0 : sameName(x))))
|
|
99
|
-
return new apiMiddleware_1.ApiError(const_1.HTTP_CONFLICT, 'already present');
|
|
100
105
|
n.children.unshift({ source, name });
|
|
101
106
|
await (0, vfs_1.saveVfs)();
|
|
102
|
-
return {};
|
|
107
|
+
return { name };
|
|
103
108
|
},
|
|
104
109
|
async del_vfs({ uris }) {
|
|
105
110
|
if (!uris || !Array.isArray(uris))
|
|
@@ -149,13 +154,14 @@ const apis = {
|
|
|
149
154
|
return;
|
|
150
155
|
}
|
|
151
156
|
try {
|
|
157
|
+
const matching = (0, misc_1.makeMatcher)(fileMask);
|
|
152
158
|
path = (0, misc_1.isWindowsDrive)(path) ? path + '\\' : (0, path_1.resolve)(path || '/');
|
|
153
159
|
for await (const [name, isDir] of (0, misc_1.dirStream)(path)) {
|
|
154
160
|
if (ctx.req.aborted)
|
|
155
161
|
return;
|
|
156
162
|
try {
|
|
157
163
|
if (!isDir)
|
|
158
|
-
if (!files || fileMask && !(
|
|
164
|
+
if (!files || fileMask && !matching(name))
|
|
159
165
|
continue;
|
|
160
166
|
const stats = await (0, promises_1.stat)((0, path_1.join)(path, name));
|
|
161
167
|
yield {
|
package/src/block.js
CHANGED
|
@@ -1,34 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
-
};
|
|
6
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
4
|
exports.applyBlock = void 0;
|
|
8
5
|
const config_1 = require("./config");
|
|
9
6
|
const connections_1 = require("./connections");
|
|
10
7
|
const misc_1 = require("./misc");
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
compileBlock(rules);
|
|
8
|
+
const block = (0, config_1.defineConfig)('block', [], rules => {
|
|
9
|
+
const ret = !Array.isArray(rules) ? []
|
|
10
|
+
: (0, misc_1.onlyTruthy)(rules.map(rule => (0, misc_1.makeNetMatcher)(rule.ip, true)));
|
|
11
|
+
// reapply new block to existing connections
|
|
16
12
|
for (const { socket, ip } of (0, connections_1.getConnections)())
|
|
17
13
|
applyBlock(socket, ip);
|
|
14
|
+
return ret;
|
|
18
15
|
});
|
|
19
|
-
function compileBlock(rules) {
|
|
20
|
-
blockFunctions = !Array.isArray(rules) ? []
|
|
21
|
-
: (0, misc_1.onlyTruthy)(rules.map(rule => !rule ? null
|
|
22
|
-
: (0, misc_1.with_)(rule.ip, ip => typeof ip !== 'string' ? null
|
|
23
|
-
: ip.includes('/') ? x => cidr_tools_1.default.contains(ip, x)
|
|
24
|
-
: ip.includes('*') ? (0, misc_1.with_)(ipMask2regExp(ip), re => x => re.test(x))
|
|
25
|
-
: x => x === ip)));
|
|
26
|
-
function ipMask2regExp(ipMask) {
|
|
27
|
-
return new RegExp(lodash_1.default.escapeRegExp(ipMask).replace(/\\\*/g, '.*'));
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
16
|
function applyBlock(socket, ip = (0, connections_1.normalizeIp)(socket.remoteAddress || '')) {
|
|
31
|
-
if (ip &&
|
|
17
|
+
if (ip && block.compiled().find(rule => rule(ip)))
|
|
32
18
|
return socket.destroy();
|
|
33
19
|
}
|
|
34
20
|
exports.applyBlock = applyBlock;
|
package/src/config.js
CHANGED
|
@@ -51,8 +51,11 @@ const { save } = (0, watchLoad_1.watchLoad)(path, values => setConfig(values ||
|
|
|
51
51
|
setConfig({}, false);
|
|
52
52
|
}
|
|
53
53
|
});
|
|
54
|
-
function defineConfig(k, defaultValue) {
|
|
54
|
+
function defineConfig(k, defaultValue, compiler = lodash_1.default.identity) {
|
|
55
55
|
configProps[k] = { defaultValue };
|
|
56
|
+
let compiled = compiler(defaultValue);
|
|
57
|
+
if (compiler)
|
|
58
|
+
subscribeConfig(k, (v) => compiled = compiler(v));
|
|
56
59
|
return {
|
|
57
60
|
key() {
|
|
58
61
|
return k;
|
|
@@ -68,7 +71,8 @@ function defineConfig(k, defaultValue) {
|
|
|
68
71
|
this.set(v(this.get()));
|
|
69
72
|
else
|
|
70
73
|
setConfig1(k, v);
|
|
71
|
-
}
|
|
74
|
+
},
|
|
75
|
+
compiled: () => compiled
|
|
72
76
|
};
|
|
73
77
|
}
|
|
74
78
|
exports.defineConfig = defineConfig;
|
package/src/const.js
CHANGED
|
@@ -38,12 +38,12 @@ exports.DEV = process.env.DEV || exports.argv.dev ? 'DEV' : '';
|
|
|
38
38
|
exports.ORIGINAL_CWD = process.cwd();
|
|
39
39
|
exports.HFS_STARTED = new Date();
|
|
40
40
|
const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
|
|
41
|
-
exports.BUILD_TIMESTAMP = "2023-
|
|
41
|
+
exports.BUILD_TIMESTAMP = "2023-04-08T13:40:53.294Z";
|
|
42
42
|
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
|
|
43
43
|
exports.VERSION = pkg.version;
|
|
44
44
|
exports.DAY = 86400000;
|
|
45
45
|
exports.SESSION_DURATION = exports.DAY;
|
|
46
|
-
exports.API_VERSION =
|
|
46
|
+
exports.API_VERSION = 8; // entry.uri + script.plugin
|
|
47
47
|
exports.COMPATIBLE_API_VERSION = 1; // while changes in the api are not breaking, this number stays the same, otherwise is made equal to API_VERSION
|
|
48
48
|
exports.SPECIAL_URI = '/~/';
|
|
49
49
|
exports.FRONTEND_URI = exports.SPECIAL_URI + 'frontend/';
|
package/src/customHtml.js
CHANGED
|
@@ -21,7 +21,7 @@ if (!(0, fs_1.existsSync)(FILE))
|
|
|
21
21
|
events_1.default.once('config ready', () => {
|
|
22
22
|
const legacy = (0, misc_1.prefix)('[beforeHeader]\n', frontEndApis_1.customHeader.get());
|
|
23
23
|
(0, fs_1.writeFileSync)(FILE, legacy);
|
|
24
|
-
frontEndApis_1.customHeader.set(
|
|
24
|
+
frontEndApis_1.customHeader.set(''); // get rid of it
|
|
25
25
|
});
|
|
26
26
|
(0, watchLoad_1.watchLoad)(FILE, data => {
|
|
27
27
|
var _a;
|
package/src/frontEndApis.js
CHANGED
|
@@ -38,8 +38,7 @@ const const_1 = require("./const");
|
|
|
38
38
|
const vfs_1 = require("./vfs");
|
|
39
39
|
const promises_1 = require("fs/promises");
|
|
40
40
|
const path_1 = require("path");
|
|
41
|
-
|
|
42
|
-
exports.customHeader = (0, config_1.defineConfig)('custom_header');
|
|
41
|
+
exports.customHeader = (0, config_1.defineConfig)('custom_header', '');
|
|
43
42
|
exports.frontEndApis = {
|
|
44
43
|
file_list: api_file_list_1.file_list,
|
|
45
44
|
...api_auth,
|
|
@@ -87,30 +86,6 @@ exports.frontEndApis = {
|
|
|
87
86
|
throw new apiMiddleware_1.ApiError(e.code || const_1.HTTP_SERVER_ERROR, e);
|
|
88
87
|
}
|
|
89
88
|
},
|
|
90
|
-
async load_lang({ lang, embedded }) {
|
|
91
|
-
const ret = {};
|
|
92
|
-
const langs = (0, misc_1.wantArray)(lang).map(x => x.toLowerCase());
|
|
93
|
-
let i = 0;
|
|
94
|
-
while (i < langs.length) {
|
|
95
|
-
let x = langs[i];
|
|
96
|
-
if (x === embedded)
|
|
97
|
-
break;
|
|
98
|
-
try {
|
|
99
|
-
ret[x] = JSON.parse(await (0, promises_1.readFile)(`hfs-lang-${x}.json`, 'utf8'));
|
|
100
|
-
}
|
|
101
|
-
catch (_a) {
|
|
102
|
-
do {
|
|
103
|
-
x = x.substring(0, x.lastIndexOf('-'));
|
|
104
|
-
} while (x && langs.includes(x));
|
|
105
|
-
if (x) {
|
|
106
|
-
langs[i] = x; // overwrite and retry
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
i++;
|
|
111
|
-
}
|
|
112
|
-
return ret;
|
|
113
|
-
}
|
|
114
89
|
};
|
|
115
90
|
function notifyClient(ctx, name, data) {
|
|
116
91
|
const { notificationChannel } = ctx.query;
|
package/src/lang.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.EMBEDDED_TRANSLATIONS = exports.getLangData = exports.file2code = exports.code2file = void 0;
|
|
7
|
+
const misc_1 = require("./misc");
|
|
8
|
+
const promises_1 = require("fs/promises");
|
|
9
|
+
const config_1 = require("./config");
|
|
10
|
+
const watchLoad_1 = require("./watchLoad");
|
|
11
|
+
const PREFIX = 'hfs-lang-';
|
|
12
|
+
const SUFFIX = '.json';
|
|
13
|
+
const EMBEDDED_LANGUAGE = 'en';
|
|
14
|
+
function code2file(code) {
|
|
15
|
+
return PREFIX + code.toLowerCase() + SUFFIX;
|
|
16
|
+
}
|
|
17
|
+
exports.code2file = code2file;
|
|
18
|
+
function file2code(fn) {
|
|
19
|
+
return fn.slice(PREFIX.length, -SUFFIX.length);
|
|
20
|
+
}
|
|
21
|
+
exports.file2code = file2code;
|
|
22
|
+
async function getLangData(ctx) {
|
|
23
|
+
if (forceLangData)
|
|
24
|
+
return forceLangData;
|
|
25
|
+
const ret = {};
|
|
26
|
+
const csv = String(ctx.query.lang || '') || ctx.get('Accept-Language') || '';
|
|
27
|
+
const langs = (0, misc_1.wantArray)(csv.split(',').map(x => x.toLowerCase()));
|
|
28
|
+
let i = 0;
|
|
29
|
+
while (i < langs.length) {
|
|
30
|
+
let k = langs[i] || ''; // shut up ts
|
|
31
|
+
if (!k || k === EMBEDDED_LANGUAGE)
|
|
32
|
+
break;
|
|
33
|
+
try {
|
|
34
|
+
ret[k] = JSON.parse(await (0, promises_1.readFile)(`hfs-lang-${k}.json`, 'utf8'));
|
|
35
|
+
}
|
|
36
|
+
catch (_a) {
|
|
37
|
+
if (k in exports.EMBEDDED_TRANSLATIONS)
|
|
38
|
+
ret[k] = exports.EMBEDDED_TRANSLATIONS[k];
|
|
39
|
+
else {
|
|
40
|
+
do {
|
|
41
|
+
k = k.substring(0, k.lastIndexOf('-'));
|
|
42
|
+
} while (k && langs.includes(k));
|
|
43
|
+
if (k) {
|
|
44
|
+
langs[i] = k; // overwrite and retry
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
i++;
|
|
50
|
+
}
|
|
51
|
+
return ret;
|
|
52
|
+
}
|
|
53
|
+
exports.getLangData = getLangData;
|
|
54
|
+
let forceLangData;
|
|
55
|
+
let undo;
|
|
56
|
+
(0, config_1.defineConfig)('force_lang', '', v => {
|
|
57
|
+
undo === null || undo === void 0 ? void 0 : undo();
|
|
58
|
+
forceLangData = undefined;
|
|
59
|
+
if (!v)
|
|
60
|
+
return;
|
|
61
|
+
const res = (0, watchLoad_1.watchLoad)(code2file(v), data => {
|
|
62
|
+
forceLangData = { [v]: JSON.parse(data) };
|
|
63
|
+
});
|
|
64
|
+
undo = res.unwatch;
|
|
65
|
+
});
|
|
66
|
+
const hfs_lang_it_json_1 = __importDefault(require("./langs/hfs-lang-it.json"));
|
|
67
|
+
const hfs_lang_zh_json_1 = __importDefault(require("./langs/hfs-lang-zh.json"));
|
|
68
|
+
const hfs_lang_ru_json_1 = __importDefault(require("./langs/hfs-lang-ru.json"));
|
|
69
|
+
const hfs_lang_sr_json_1 = __importDefault(require("./langs/hfs-lang-sr.json"));
|
|
70
|
+
const hfs_lang_ko_json_1 = __importDefault(require("./langs/hfs-lang-ko.json"));
|
|
71
|
+
exports.EMBEDDED_TRANSLATIONS = {
|
|
72
|
+
it: hfs_lang_it_json_1.default,
|
|
73
|
+
zh: hfs_lang_zh_json_1.default,
|
|
74
|
+
ru: hfs_lang_ru_json_1.default,
|
|
75
|
+
sr: hfs_lang_sr_json_1.default,
|
|
76
|
+
ko: hfs_lang_ko_json_1.default,
|
|
77
|
+
};
|