dsh-disk-cleaner 1.6.7 → 1.6.8
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 +11 -9
- package/lib/client.js +3 -3
- package/lib/index.js +34 -15
- package/package.json +1 -1
- package/smoke.mjs +6 -4
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<!-- badges -->
|
|
6
6
|
| | |
|
|
7
7
|
| --- | --- |
|
|
8
|
-
| **version** | `1.6.
|
|
8
|
+
| **version** | `1.6.8` |
|
|
9
9
|
| **DSH** | `0.2.1-rc.5`(开发与实测版本;用到 `plugins.bundle.config`、`settings.section`) |
|
|
10
10
|
| **license** | [MIT](LICENSE) |
|
|
11
11
|
| **Node** | `>= 22`(仅运行冒烟测试需要) |
|
|
@@ -166,16 +166,16 @@ These are enforced in `lib/catalog.js` and must not be relaxed:
|
|
|
166
166
|
|
|
167
167
|
Listed in the curated [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) catalog, which powers **dshmarket → Discover** ([PR #12970](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/12970), merged): search **dsh-disk-cleaner** there and install it in one click. The direct installs below do the same thing by hand.
|
|
168
168
|
|
|
169
|
-
Published on npm as [`dsh-disk-cleaner`](https://www.npmjs.com/package/dsh-disk-cleaner) — latest `1.6.
|
|
169
|
+
Published on npm as [`dsh-disk-cleaner`](https://www.npmjs.com/package/dsh-disk-cleaner) — latest `1.6.8`:
|
|
170
170
|
|
|
171
171
|
```bash
|
|
172
172
|
npm i dsh-disk-cleaner
|
|
173
173
|
```
|
|
174
174
|
|
|
175
|
-
A prebuilt tarball is attached to the [v1.6.
|
|
175
|
+
A prebuilt tarball is attached to the [v1.6.8 Release](https://github.com/stallenlu/dsh-disk-cleaner/releases/tag/v1.6.8); installing from it skips DSH's build-approval step:
|
|
176
176
|
|
|
177
177
|
```bash
|
|
178
|
-
dsh plugin --profile <profile> add https://github.com/stallenlu/dsh-disk-cleaner/releases/download/v1.6.
|
|
178
|
+
dsh plugin --profile <profile> add https://github.com/stallenlu/dsh-disk-cleaner/releases/download/v1.6.8/dsh-disk-cleaner-1.6.8.tgz
|
|
179
179
|
```
|
|
180
180
|
|
|
181
181
|
Then enable it in the profile — in `$DSH_PROFILE_DIR/package.json`:
|
|
@@ -183,7 +183,7 @@ Then enable it in the profile — in `$DSH_PROFILE_DIR/package.json`:
|
|
|
183
183
|
```json
|
|
184
184
|
{
|
|
185
185
|
"dependencies": {
|
|
186
|
-
"dsh-disk-cleaner": "^1.6.
|
|
186
|
+
"dsh-disk-cleaner": "^1.6.8"
|
|
187
187
|
},
|
|
188
188
|
"dsh": {
|
|
189
189
|
"profile": {
|
|
@@ -275,6 +275,7 @@ Client-half slots: `sidebar.panellist` (order 50, disposed in settings-only mode
|
|
|
275
275
|
|
|
276
276
|
### What's new
|
|
277
277
|
|
|
278
|
+
- **1.6.8** — Fixes the other half of the 1.6.7 story: the panel still answered "origin rejected" when the desktop window sent `Origin: http://localhost:<port>` while the request itself arrived at `127.0.0.1`, because the check compared those strings literally. The gate now decides on the **peer address** of the connection — something a remote page cannot influence — and only then looks at `Origin`: the app's own scheme, no `Origin` at all, or any loopback host name is accepted, everything else is still refused. An `https://evil.example` origin is rejected even from a loopback socket. The three assessment error paths (evaluate, reveal, run-a-plan-step) now go through the same translated-code path as the rest of the panel, so a Chinese user sees "请求来源不被接受" rather than the raw `origin rejected` string.
|
|
278
279
|
- **1.6.7** — Fixes a regression 1.6.6 shipped: refusing every request without an `Origin` header locked the plugin's own panel out of its API ("请求来源不被接受"), because the desktop window posts from `dsh-app://app`, not from `http://127.0.0.1:<port>`. The check now accepts the desktop scheme and a header the desktop forwarder stripped, but only when the request already arrived on a loopback address; a plain web page still cannot reach a loopback port without sending an `Origin`, so the door stays shut. No other behaviour changed.
|
|
279
280
|
- **1.6.6** — The results the plugin already computed are now shown. A **Dry run** button next to *Clean* asks the exact same question without `confirm`, so you see what a delete would free before anything goes; the result banner then survives the rescan that follows it (it used to be erased a line later). Plan steps name targets in words instead of printing `npm-cache, chrome-cache, pip-cache`, an empty plan says "nothing worth doing" instead of rendering a hole, and route errors arrive as a stable `code` the panel translates, so a Chinese user never reads `origin rejected` or `unknown target: x`. Every real clean now appends one line to `$DSH_HOME/dsh-disk-cleaner/history.jsonl`, and a **Clean history** panel reads it back with a *Copy as CSV* button. Under the hood: scan walks four targets at a time instead of all 32 at once, `SystemRoot` decides where the Windows targets live instead of a hardcoded `C:`, a request without an `Origin` header is refused unless it arrived on loopback (see 1.6.7), and a refused directory removal is counted separately from a legitimately full one.
|
|
280
281
|
- **1.6.5** — Documentation only: the install section now points at the Release it names (the prebuilt-tarball link is labelled v1.6.5 and resolves to the v1.6.5 tag), and the catalog paragraph says the awesome-dsh-plugin entry is merged rather than pending review. No code change.
|
|
@@ -440,16 +441,16 @@ Client-half slots: `sidebar.panellist` (order 50, disposed in settings-only mode
|
|
|
440
441
|
|
|
441
442
|
已收录进精选目录 [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin),它是 **dshmarket → 发现** 的数据源([PR #12970](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/12970),已合并):在 dshmarket 里搜索 **dsh-disk-cleaner** 即可一键安装。下面的直接安装方式就是手动做同一件事。
|
|
442
443
|
|
|
443
|
-
已发布到 npm:[`dsh-disk-cleaner`](https://www.npmjs.com/package/dsh-disk-cleaner),最新版 `1.6.
|
|
444
|
+
已发布到 npm:[`dsh-disk-cleaner`](https://www.npmjs.com/package/dsh-disk-cleaner),最新版 `1.6.8`。
|
|
444
445
|
|
|
445
446
|
```bash
|
|
446
447
|
npm i dsh-disk-cleaner
|
|
447
448
|
```
|
|
448
449
|
|
|
449
|
-
[v1.6.
|
|
450
|
+
[v1.6.8 Release](https://github.com/stallenlu/dsh-disk-cleaner/releases/tag/v1.6.8) 还附带一个预构建 tarball,从它安装可跳过 DSH 的构建授权步骤:
|
|
450
451
|
|
|
451
452
|
```bash
|
|
452
|
-
dsh plugin --profile <profile> add https://github.com/stallenlu/dsh-disk-cleaner/releases/download/v1.6.
|
|
453
|
+
dsh plugin --profile <profile> add https://github.com/stallenlu/dsh-disk-cleaner/releases/download/v1.6.8/dsh-disk-cleaner-1.6.8.tgz
|
|
453
454
|
```
|
|
454
455
|
|
|
455
456
|
然后在 profile 里启用 —— 在 `$DSH_PROFILE_DIR/package.json`:
|
|
@@ -457,7 +458,7 @@ dsh plugin --profile <profile> add https://github.com/stallenlu/dsh-disk-cleaner
|
|
|
457
458
|
```json
|
|
458
459
|
{
|
|
459
460
|
"dependencies": {
|
|
460
|
-
"dsh-disk-cleaner": "^1.6.
|
|
461
|
+
"dsh-disk-cleaner": "^1.6.8"
|
|
461
462
|
},
|
|
462
463
|
"dsh": {
|
|
463
464
|
"profile": {
|
|
@@ -549,6 +550,7 @@ Client 半面注册的 slot:`sidebar.panellist`(order 50,仅设置页模
|
|
|
549
550
|
|
|
550
551
|
### 更新记录
|
|
551
552
|
|
|
553
|
+
- **1.6.8** — 补上 1.6.7 的另一半:桌面窗口如果发的是 `Origin: http://localhost:<端口>`、而请求实际到达 `127.0.0.1`,1.6.7 仍然会报「origin rejected」——因为它逐字比较这两个字符串。现在判定改为看**连接的来源地址**(远端网页无法影响这个值),再看 `Origin`:桌面自有协议、压根没有 `Origin`、或任何 loopback 主机名都放行,其余照旧拒绝;即便是 loopback 连接,`https://evil.example` 也进不来。空间评估的三条错误路径(评估、打开位置、跑计划步骤)统一走面板已有的「稳定 code → 中文文案」通道,不会再直接显示原始英文串。
|
|
552
554
|
- **1.6.7** — 修掉 1.6.6 带进来的一个回归:把「没有 Origin 头的请求」一律拒绝,连插件自己的面板都进不了接口(界面报「请求来源不被接受」)——桌面窗口发请求时 Origin 是 `dsh-app://app`,不是 `http://127.0.0.1:<端口>`。现在的判定接受桌面协议,也接受被桌面转发层剥掉的头,但前提是请求本身已经到达 loopback 地址;普通网页不发 Origin 就到不了 loopback 端口,所以门仍然是关的。其余行为没变。
|
|
553
555
|
- **1.6.6** — 把插件本来就算出来的结果真正显示出来。*清理* 旁边多了 **预演** 按钮:发同一个请求但不带 `confirm`,删之前就能看到会释放多少;清理结果横幅不再被紧随其后的重新扫描抹掉(以前下一行就被清空)。计划步骤改用目标名称,不再直出 `npm-cache, chrome-cache, pip-cache`;没有可做的事时说明「现在没有值得动手的地方」,而不是留一片空白;接口错误改带稳定 `code`,由面板翻成中文,不会再出现 `origin rejected` 或 `unknown target: x`。每次真正清理都会往 `$DSH_HOME/dsh-disk-cleaner/history.jsonl` 追加一行,新增的 **清理记录** 面板可读回并按 *复制为 CSV* 导出。底层:扫描从「32 个目标同时跑」改为每次 4 个,Windows 目标路径改由 `SystemRoot` 决定而不再硬编码 `C:`,缺少 `Origin` 头的请求会被拒绝(这项判定在 1.6.7 收紧到只放行 loopback,见下),目录删除被拒与非空目录被跳过分别计数。
|
|
554
556
|
- **1.6.5** — 仅文档修正:安装段现在指向它所写的那个 Release(预构建 tarball 的链接标为 v1.6.5,且指向 v1.6.5 的 tag);目录段落也从「审核中」改为「已合并」。代码无变化。
|
package/lib/client.js
CHANGED
|
@@ -1398,7 +1398,7 @@ window.__ModuleLoader__.load({
|
|
|
1398
1398
|
const result = await call('audit', force === true ? { force: true } : {});
|
|
1399
1399
|
setData(result);
|
|
1400
1400
|
} catch (err) {
|
|
1401
|
-
setError(`${t('auditError')}: ${
|
|
1401
|
+
setError(`${t('auditError')}: ${describeError(t, err)}`);
|
|
1402
1402
|
} finally {
|
|
1403
1403
|
setBusy(false);
|
|
1404
1404
|
}
|
|
@@ -1427,7 +1427,7 @@ window.__ModuleLoader__.load({
|
|
|
1427
1427
|
const result = await call('reveal', payload);
|
|
1428
1428
|
setOpened(result && result.revealed ? result.revealed : '');
|
|
1429
1429
|
} catch (err) {
|
|
1430
|
-
setError(`${t('auditError')}: ${
|
|
1430
|
+
setError(`${t('auditError')}: ${describeError(t, err)}`);
|
|
1431
1431
|
}
|
|
1432
1432
|
};
|
|
1433
1433
|
|
|
@@ -1472,7 +1472,7 @@ window.__ModuleLoader__.load({
|
|
|
1472
1472
|
const result = await call('audit');
|
|
1473
1473
|
setData(result);
|
|
1474
1474
|
} catch (err) {
|
|
1475
|
-
setError(`${t('auditError')}: ${
|
|
1475
|
+
setError(`${t('auditError')}: ${describeError(t, err)}`);
|
|
1476
1476
|
} finally {
|
|
1477
1477
|
setBusy(false);
|
|
1478
1478
|
}
|
package/lib/index.js
CHANGED
|
@@ -41,13 +41,11 @@ const sendJson = (res, status, value) => {
|
|
|
41
41
|
res.end(body);
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '[::1]', '::1']);
|
|
44
|
+
const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '[::1]', '::1', '::ffff:127.0.0.1']);
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* The desktop app renders the panel under its own privileged protocol (`dsh-app://app`), so a
|
|
48
|
-
* fetch from inside that window carries an Origin no `http://` comparison can ever match.
|
|
49
|
-
* page cannot forge this: Origin is set by the user agent, and only the harness serves that
|
|
50
|
-
* scheme. Everything is still gated on a loopback Host below.
|
|
48
|
+
* fetch from inside that window carries an Origin no `http://` comparison can ever match.
|
|
51
49
|
*/
|
|
52
50
|
const APP_SCHEME = 'dsh-app://';
|
|
53
51
|
|
|
@@ -62,21 +60,42 @@ function hostName(host) {
|
|
|
62
60
|
}
|
|
63
61
|
|
|
64
62
|
/**
|
|
65
|
-
*
|
|
63
|
+
* Whether the connection itself came in over loopback. This is the check that actually decides,
|
|
64
|
+
* because it is the one a remote page cannot influence: the kernel reports the peer address.
|
|
65
|
+
* Comparing Origin to `http://` + Host was the earlier approach and it broke the panel twice —
|
|
66
|
+
* the desktop window sends `dsh-app://app` (or no Origin at all), and when it does send an
|
|
67
|
+
* http origin the Host header may spell the address differently (`localhost` against
|
|
68
|
+
* `127.0.0.1`, or a rewritten host from the forwarder), which the string comparison rejects.
|
|
69
|
+
*/
|
|
70
|
+
function fromLoopback(req) {
|
|
71
|
+
const address = req.socket && typeof req.socket.remoteAddress === 'string' ? req.socket.remoteAddress : '';
|
|
72
|
+
if (address !== '' && LOOPBACK_HOSTS.has(address)) return true;
|
|
73
|
+
// No usable peer address (a test double, or a proxy that consumed the socket): fall back to
|
|
74
|
+
// the Host header, which at least keeps the old protection for callers that set it honestly.
|
|
75
|
+
const host = req.headers.host;
|
|
76
|
+
if (typeof host !== 'string' || host === '') return false;
|
|
77
|
+
return LOOPBACK_HOSTS.has(hostName(host));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Only the panel may drive a state-changing route, and only from this machine.
|
|
66
82
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* door it looks like it opens. Any other absent-Origin caller must first reach loopback anyway.
|
|
83
|
+
* The gate is the peer address, not the Origin string: a web page on another origin cannot reach
|
|
84
|
+
* a loopback socket, and one that reaches it through some other path still has to send an Origin
|
|
85
|
+
* that we then refuse unless it is the app's own scheme or a loopback name.
|
|
71
86
|
*/
|
|
72
87
|
const sameOrigin = (req) => {
|
|
88
|
+
if (!fromLoopback(req)) return false;
|
|
73
89
|
const origin = req.headers.origin;
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
90
|
+
if (typeof origin !== 'string' || origin === '' || origin === 'null') return true;
|
|
91
|
+
if (origin.startsWith(APP_SCHEME)) return true;
|
|
92
|
+
let host = '';
|
|
93
|
+
try {
|
|
94
|
+
host = new URL(origin).hostname;
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return LOOPBACK_HOSTS.has(host) || LOOPBACK_HOSTS.has(`[${host}]`);
|
|
80
99
|
};
|
|
81
100
|
|
|
82
101
|
const readBody = (req) =>
|
package/package.json
CHANGED
package/smoke.mjs
CHANGED
|
@@ -413,7 +413,7 @@ check(
|
|
|
413
413
|
source.includes('QQ') ,
|
|
414
414
|
'it only ever measures the QQGuild folder',
|
|
415
415
|
);
|
|
416
|
-
check('the version badge and package agree', pkg.version === '1.6.
|
|
416
|
+
check('the version badge and package agree', pkg.version === '1.6.8');
|
|
417
417
|
|
|
418
418
|
check(
|
|
419
419
|
'a dry run is one click away and never sends confirm',
|
|
@@ -512,9 +512,11 @@ check(
|
|
|
512
512
|
'a request without an Origin is refused unless it arrived on loopback',
|
|
513
513
|
/const LOOPBACK_HOSTS = new Set\(/.test(index) &&
|
|
514
514
|
/const APP_SCHEME = 'dsh-app:\/\/';/.test(index) &&
|
|
515
|
-
/
|
|
516
|
-
/
|
|
517
|
-
|
|
515
|
+
/function fromLoopback\(req\)/.test(index) &&
|
|
516
|
+
/req\.socket\.remoteAddress/.test(index) &&
|
|
517
|
+
/if \(!fromLoopback\(req\)\) return false;/.test(index) &&
|
|
518
|
+
/if \(origin\.startsWith\(APP_SCHEME\)\) return true;/.test(index) &&
|
|
519
|
+
!/return origin === `http:\/\/${host}` \|\| origin === `https:\/\/${host}`;/.test(index),
|
|
518
520
|
'the desktop shell posts from dsh-app://, and a stripped Origin can still be the panel itself',
|
|
519
521
|
);
|
|
520
522
|
|