hfs 0.42.3 → 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.
Files changed (41) hide show
  1. package/README.md +6 -8
  2. package/admin/assets/{index-08017e15.js → index-5cd667a5.js} +134 -134
  3. package/admin/assets/index-8ff39373.css +1 -0
  4. package/{frontend/assets/sha512-f6798733.js → admin/assets/sha512-55ff2fa3.js} +1 -1
  5. package/admin/index.html +2 -2
  6. package/frontend/assets/index-27488fde.js +94 -0
  7. package/frontend/assets/index-54a5c76f.css +1 -0
  8. package/{admin/assets/sha512-69b26793.js → frontend/assets/sha512-8ebf6e2a.js} +1 -1
  9. package/frontend/fontello.css +9 -3
  10. package/frontend/fontello.woff2 +0 -0
  11. package/frontend/index.html +2 -2
  12. package/package.json +1 -1
  13. package/plugins/antibrute/plugin.js +1 -1
  14. package/plugins/download-counter/plugin.js +10 -3
  15. package/plugins/download-counter/public/main.js +12 -2
  16. package/src/adminApis.js +3 -3
  17. package/src/api.file_list.js +15 -2
  18. package/src/api.lang.js +8 -11
  19. package/src/api.vfs.js +2 -1
  20. package/src/block.js +6 -20
  21. package/src/config.js +6 -2
  22. package/src/const.js +2 -2
  23. package/src/customHtml.js +1 -1
  24. package/src/frontEndApis.js +1 -26
  25. package/src/lang.js +77 -0
  26. package/src/langs/hfs-lang-it.json +100 -0
  27. package/src/langs/hfs-lang-ko.json +103 -0
  28. package/src/langs/hfs-lang-ru.json +106 -0
  29. package/src/langs/hfs-lang-sr.json +108 -0
  30. package/src/langs/hfs-lang-zh.json +98 -0
  31. package/src/listen.js +8 -3
  32. package/src/middlewares.js +10 -0
  33. package/src/misc.js +13 -9
  34. package/src/perm.js +1 -1
  35. package/src/serveFile.js +2 -2
  36. package/src/serveGuiFiles.js +21 -8
  37. package/src/upload.js +1 -1
  38. package/src/vfs.js +19 -27
  39. package/admin/assets/index-94bbe0be.css +0 -1
  40. package/frontend/assets/index-5f125477.js +0 -94
  41. 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-08017e15.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}};/*
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
@@ -1,6 +1,7 @@
1
1
  @font-face {
2
2
  font-family: 'fontello';
3
- src: url('fontello.woff2?66723712') format('woff2'); font-weight: normal;
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?66723712#fontello') format('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'; } /* '' */
Binary file
@@ -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-5f125477.js"></script>
9
- <link rel="stylesheet" href="/assets/index-a09cacfd.css">
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.42.3",
3
+ "version": "0.43.0",
4
4
  "description": "HTTP File Server",
5
5
  "keywords": [
6
6
  "file server",
@@ -7,7 +7,7 @@ exports.config = {
7
7
  max: { type: 'number', min: 1, defaultValue: 60, helperText: "Max seconds to delay before next login is allowed" },
8
8
  }
9
9
  exports.configDialog = {
10
- maxWidth: '25em',
10
+ maxWidth: 'xs',
11
11
  }
12
12
 
13
13
  const byIp = {}
@@ -1,6 +1,13 @@
1
- exports.description = "Counts downloads for each file, and displays the total in the list"
2
- exports.version = 3.1 // removed "hits" word
3
- exports.apiRequired = 3
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
- HFS.onEvent('additionalEntryProps', ({ entry: { hits } }, { t }) =>
2
- hits && `<span class="download-counter" title="${t`download counter`}">${hits}</span>`)
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 (0, misc_1.matchesNet)(ctx, exports.adminNet.get(), true);
190
+ return exports.adminNet.compiled()(ctx.ip);
191
191
  }
192
192
  exports.allowAdmin = allowAdmin;
@@ -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 PREFIX = 'hfs-lang-';
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 = name.slice(PREFIX.length, -SUFFIX.length);
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
- if (code.endsWith(SUFFIX)) // filename, actually
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
@@ -154,13 +154,14 @@ const apis = {
154
154
  return;
155
155
  }
156
156
  try {
157
+ const matching = (0, misc_1.makeMatcher)(fileMask);
157
158
  path = (0, misc_1.isWindowsDrive)(path) ? path + '\\' : (0, path_1.resolve)(path || '/');
158
159
  for await (const [name, isDir] of (0, misc_1.dirStream)(path)) {
159
160
  if (ctx.req.aborted)
160
161
  return;
161
162
  try {
162
163
  if (!isDir)
163
- if (!files || fileMask && !(0, misc_1.matches)(name, fileMask))
164
+ if (!files || fileMask && !matching(name))
164
165
  continue;
165
166
  const stats = await (0, promises_1.stat)((0, path_1.join)(path, name));
166
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 cidr_tools_1 = __importDefault(require("cidr-tools"));
12
- const lodash_1 = __importDefault(require("lodash"));
13
- let blockFunctions = []; // "compiled" versions of the rules in config.block
14
- (0, config_1.defineConfig)('block', []).sub(rules => {
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 && blockFunctions.find(rule => rule(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-03-29T21:48:40.615Z";
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 = 7; // fileMenu event
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(undefined); // get rid of it
24
+ frontEndApis_1.customHeader.set(''); // get rid of it
25
25
  });
26
26
  (0, watchLoad_1.watchLoad)(FILE, data => {
27
27
  var _a;
@@ -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
- const misc_1 = require("./misc");
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
+ };
@@ -0,0 +1,100 @@
1
+ {
2
+ "author": "Massimo Melina",
3
+ "version": 1.5,
4
+ "hfs_version": "0.43.0",
5
+ "translate": {
6
+ "Select": "Seleziona",
7
+ "n_files": "{n} file",
8
+ "n_folders": "{n,plural, one{# cartella} other{# cartelle}}",
9
+ "filter_count": "{n,plural, one{# filtrato} other{# filtrati}}",
10
+ "select_count": "{n,plural, one{# selezionato} other{# selezionati}}",
11
+ "filter_placeholder": "Digita qui per filtrare la lista dei file",
12
+ "Select some files": "Solo alcuni file",
13
+ "zip_checkboxes": "Spunta le caselle per selezionare i file desiderati, e poi clicca di nuovo su Zip",
14
+ "zip_tooltip_selected": "Scarica gli elementi selezionati come singolo file zip",
15
+ "zip_tooltip_whole": "Scarica intera lista (non considera il filtro) come singolo file zip. Se selezioni alcuni elementi, solo quelli saranno inclusi.",
16
+ "zip_confirm_search": "Scarica TUTTI i risultati di questa ricerca come file zip?",
17
+ "zip_confirm_folder": "Scarica l'INTERA artella come file zip?",
18
+ "select_tooltip": "Selezionare serve con la funziona Zip e per cancellare (dove disponibile), ma puoi anche filtrare la lista",
19
+ "delete_hint": "Per poter cancellare bisogna prima cliccare Seleziona",
20
+ "delete_confirm": "Cancellare {n,plural, one{l'elemento} =8 =11 {gli # elementi} other{i # elementi}}?",
21
+ "delete_completed": "Cancellazione: {n} riusciti",
22
+ "delete_failed": ", {n,plural, one{# fallito} other{# falliti}}",
23
+ "delete_select": "Seleziona qualcosa per poter cancellare",
24
+ "Delete": "Cancella",
25
+ "Options": "Opzioni",
26
+ "Search": "Cerca",
27
+ "search_msg": "Cerca in questa cartella e cartelle sottostanti",
28
+ "Searching": "Cercando",
29
+ "Searched": "Risultati ricerca",
30
+ "Clear search": "Annulla ricerca",
31
+ "Interrupted": "interrotto",
32
+ "stopped_before": "Ricerca interrotta prima di trovare risultati",
33
+ "empty_list": "Non c'è niente qui",
34
+ "filter_none": "Nessun elemento corrisponde al filtro impostato",
35
+ "Login": "Entra",
36
+ "Username": "Nome utente",
37
+ "Continue": "Avanti",
38
+ "login_untrusted": "Login interrotto: server non affidabile",
39
+ "login_bad_credentials": "Nome utente o password non validi",
40
+ "login_bad_cookies": "Login interrotto: non funzionano i cookies",
41
+ "User panel": "Pannello utente",
42
+ "Change password": "Cambia password",
43
+ "enter_pass": "Inserisci nuova password",
44
+ "enter_pass2": "Ripeti la stessa password",
45
+ "pass2_mismatch": "Hai scritto 2 volte la password ma non sono uguali.",
46
+ "password_changed": "La password è stata cambiata",
47
+ "Logout": "Esci",
48
+ "connection error": "errore di connessione",
49
+ "Full timestamp:": "Data e ora:",
50
+ "Search was interrupted": "Ricerca interrotta",
51
+ "Stop list": "Interrompi",
52
+ "upload_starting": "Ora dovrebbe iniziare il download",
53
+ "wrong_account": "L'account {u} non ha accesso a questa cartella, prova con un altro",
54
+ "no_upload_here": "Non hai il permesso di uplodare in questa cartella",
55
+ "Create folder": "Crea cartella",
56
+ "Pick files": "Scegli file",
57
+ "Pick folder": "Scegli una cartella",
58
+ "send_files": "Invia {n} file, {size}",
59
+ "Clear": "Azzera",
60
+ "failed_upload": "Upload fallito per {name}",
61
+ "confirm_resume": "Vuoi riprendere questo upload?",
62
+ "file too large": "file troppo grande",
63
+ "Enter folder name": "Inserisci nome cartella",
64
+ "Successfully created": "Creazione riuscita",
65
+ "enter_folder": "Accedi alla nuova cartella",
66
+ "folder_exists": "Questo nome esiste già",
67
+ "Sort by": "Ordinamento",
68
+ "name": "nome",
69
+ "extension": "estensione",
70
+ "size": "dimensioni",
71
+ "time": "data/ora",
72
+ "Invert order": "Inverti ordine",
73
+ "Folders first": "Prima le cartelle",
74
+ "Numeric names": "Rispetta numeri all'inizio",
75
+ "theme:": "tema:",
76
+ "auto": "automatico",
77
+ "light": "chiaro",
78
+ "dark": "scuro",
79
+ "parent folder": "cartella superiore",
80
+ "Confirm": "Conferma",
81
+ "Don't": "Annulla",
82
+ "Warning": "Attenzione",
83
+ "Error": "Errore",
84
+ "Unauthorized": "Non autorizzato",
85
+ "Forbidden": "Vietato",
86
+ "Not found": "Non trovato",
87
+ "Server error": "Errore del server",
88
+ "upload_concluded": "Upload concluso",
89
+ "upload_finished": "{n,plural, one{# riuscito} other{# riusciti}} ({size})",
90
+ "upload_errors": "{n,plural, one{# fallito} other{# falliti}}",
91
+ "upload_file_rejected": "Alcuni file non sono stati accettati",
92
+ "download counter": "download conteggiati",
93
+ "File menu": "Menu file",
94
+ "Folder menu": "Menu cartella",
95
+ "Name": "Nome",
96
+ "file_open": "Apri",
97
+ "Download": "Download",
98
+ "Missing permission": "Permesso mancante"
99
+ }
100
+ }