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.
Files changed (43) hide show
  1. package/README.md +9 -12
  2. package/admin/assets/index-5cd667a5.js +511 -0
  3. package/admin/assets/index-8ff39373.css +1 -0
  4. package/{frontend/assets/sha512-bce9fb1c.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-af87c1bd.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 +2 -2
  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.accounts.js +2 -1
  18. package/src/api.file_list.js +15 -2
  19. package/src/api.lang.js +8 -11
  20. package/src/api.vfs.js +11 -5
  21. package/src/block.js +6 -20
  22. package/src/config.js +6 -2
  23. package/src/const.js +2 -2
  24. package/src/customHtml.js +1 -1
  25. package/src/frontEndApis.js +1 -26
  26. package/src/lang.js +77 -0
  27. package/src/langs/hfs-lang-it.json +100 -0
  28. package/src/langs/hfs-lang-ko.json +103 -0
  29. package/src/langs/hfs-lang-ru.json +106 -0
  30. package/src/langs/hfs-lang-sr.json +108 -0
  31. package/src/langs/hfs-lang-zh.json +98 -0
  32. package/src/listen.js +8 -3
  33. package/src/middlewares.js +10 -0
  34. package/src/misc.js +13 -9
  35. package/src/perm.js +1 -1
  36. package/src/serveFile.js +2 -2
  37. package/src/serveGuiFiles.js +21 -8
  38. package/src/upload.js +1 -1
  39. package/src/vfs.js +27 -33
  40. package/admin/assets/index-1db4299e.js +0 -511
  41. package/admin/assets/index-94bbe0be.css +0 -1
  42. package/frontend/assets/index-297a3f3d.js +0 -94
  43. 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-1db4299e.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-297a3f3d.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.2",
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",
@@ -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;
@@ -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
  },
@@ -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
@@ -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 && !(0, misc_1.matches)(name, 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 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-28T13:25:45.798Z";
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
+ };