sliccy 6.146.1 → 6.147.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/dist/node-server/fetch-proxy-gzip.d.ts +34 -0
- package/dist/node-server/fetch-proxy-gzip.js +123 -0
- package/dist/node-server/fetch-proxy-headers.js +7 -0
- package/dist/node-server/routes/fetch-proxy.js +21 -12
- package/dist/ui/.vite/manifest.json +176 -164
- package/dist/ui/assets/{SKILL-CkfptFag.js → SKILL-2Mi1pQ3F.js} +30 -4
- package/dist/ui/assets/SKILL-u5bVJ6oJ.js +1 -0
- package/dist/ui/assets/{account-store-BBbnbFqW.js → account-store-6fYzIPsO.js} +1 -1
- package/dist/ui/assets/{account-store-C5qLfcah.js → account-store-DSBPuJN7.js} +1 -1
- package/dist/ui/assets/{adobe-Cn9azha4.js → adobe--PkbJXyr.js} +1 -1
- package/dist/ui/assets/{adobe-QpTrgnX3.js → adobe-_ayrG9lb.js} +1 -1
- package/dist/ui/assets/{agent-bridge-BbB-zvEg.js → agent-bridge-9apk6tdC.js} +1 -1
- package/dist/ui/assets/{agent-panels-B04PdPHZ.js → agent-panels-wEXS6Snx.js} +1 -1
- package/dist/ui/assets/{apply-layout-BFIvuTO5.js → apply-layout-xbQW06jD.js} +1 -1
- package/dist/ui/assets/{apps-G_7cHen4.js → apps-DYcOunQl.js} +1 -1
- package/dist/ui/assets/{azure-openai-Cma7BYBE.js → azure-openai-BDaJeYjY.js} +1 -1
- package/dist/ui/assets/{azure-openai-CieMCfos.js → azure-openai-CPi1_JUV.js} +1 -1
- package/dist/ui/assets/{budget-usage-source-VcEehYzM.js → budget-usage-source-BygBihhd.js} +1 -1
- package/dist/ui/assets/{cdp-CDXFadBr.js → cdp-B164i2yd.js} +1 -1
- package/dist/ui/assets/{cerebras-LM3EpO_Y.js → cerebras-CCGSKZIo.js} +1 -1
- package/dist/ui/assets/{cerebras-BNK44caD.js → cerebras-soePCzEI.js} +1 -1
- package/dist/ui/assets/{composer-speech-DMBIfNL6.js → composer-speech-CONnJ9a8.js} +1 -1
- package/dist/ui/assets/{connect-surface-Cvu3FcoA.js → connect-surface-B5uadbu6.js} +1 -1
- package/dist/ui/assets/{directed-approval-Cphk5q7p.js → directed-approval-OYbO_iSN.js} +1 -1
- package/dist/ui/assets/es-BR4l6CGS.js +1 -0
- package/dist/ui/assets/feature-flags-COdcJUhl.js +1 -0
- package/dist/ui/assets/frozen-archive-format-B4efpvB9.js +1 -0
- package/dist/ui/assets/frozen-archive-writer-cEYcj5un.js +8 -0
- package/dist/ui/assets/{fs-Cl28LHqT.js → fs-BfgEIhY7.js} +1 -1
- package/dist/ui/assets/{fs-DegnVN45.js → fs-CFSwpfwS.js} +2 -2
- package/dist/ui/assets/{github-OzLM8z0h.js → github-CMy4oA0P.js} +2 -2
- package/dist/ui/assets/{github-copilot-BlpPj5te.js → github-copilot-BUQL8Mqn.js} +1 -1
- package/dist/ui/assets/{github-copilot-C9nP-cMP.js → github-copilot-CDj_b6sj.js} +1 -1
- package/dist/ui/assets/{github-install-CQL1hNPV.js → github-install-fFFxQiCL.js} +1 -1
- package/dist/ui/assets/{github-Bd4_ZfV_.js → github-yoNzFKe_.js} +1 -1
- package/dist/ui/assets/{github-zip-DNPRmHEX.js → github-zip-BuTKMi7b.js} +1 -1
- package/dist/ui/assets/{hear-EvmwGYs8.js → hear-CxIOumn9.js} +1 -1
- package/dist/ui/assets/{help-BWvTYTCP.js → help-BvFgSl6_.js} +1 -1
- package/dist/ui/assets/{js-realm-worker-jQFPF9k-.js → js-realm-worker-NHlCs_Fd.js} +1 -1
- package/dist/ui/assets/{kernel-worker-KKJmF03X.js → kernel-worker-sNqm6gqV.js} +644 -644
- package/dist/ui/assets/live-session-snapshot-BnA6Mq0G.js +1 -0
- package/dist/ui/assets/{local-llm-BQVgwvmJ.js → local-llm-BcAVBZee.js} +1 -1
- package/dist/ui/assets/{local-llm-Dp8soSq5.js → local-llm-DB7OUcFy.js} +1 -1
- package/dist/ui/assets/{main-cherry-Dh-0jVX1.js → main-cherry-C6-AV8cY.js} +1 -1
- package/dist/ui/assets/{main-DW5fQjDJ.js → main-h_6bHCyA.js} +3 -3
- package/dist/ui/assets/memory-v2-flag-C1duOEOb.js +1 -0
- package/dist/ui/assets/{mount-LWsJL2Al.js → mount-Cd8ExLLy.js} +1 -1
- package/dist/ui/assets/{mount-Dmsok9wh.js → mount-DTx-0yit.js} +3 -3
- package/dist/ui/assets/{mount-recovery-BnXow9sO.js → mount-recovery-CGP180NZ.js} +1 -1
- package/dist/ui/assets/{new-session-CY2Mwd1n.js → new-session-BlinQKKe.js} +2 -2
- package/dist/ui/assets/{oauth-bootstrap-pR8mW51R.js → oauth-bootstrap-COWUwhaQ.js} +2 -2
- package/dist/ui/assets/{onboarding-orchestrator-C0IPeYJI.js → onboarding-orchestrator-BVcVLfMV.js} +1 -1
- package/dist/ui/assets/{openai-codex-CnSSdlKq.js → openai-codex-Cvv_6v-i.js} +1 -1
- package/dist/ui/assets/{openai-codex-B46Cnk--.js → openai-codex-DG355kmX.js} +1 -1
- package/dist/ui/assets/{openrouter-DXzB5_8j.js → openrouter-DN6PN9Wr.js} +1 -1
- package/dist/ui/assets/{openrouter-DIrm7f7P.js → openrouter-Dbic9UcU.js} +1 -1
- package/dist/ui/assets/{openrouter-free-B5e7hpwO.js → openrouter-free-Can40S_x.js} +1 -1
- package/dist/ui/assets/{openrouter-free-PAByMMso.js → openrouter-free-DReNzrCj.js} +1 -1
- package/dist/ui/assets/{openrouter-oauth-B3Wo-QCr.js → openrouter-oauth-ChJf76qL.js} +1 -1
- package/dist/ui/assets/{openrouter-oauth-DgsEfxWU.js → openrouter-oauth-Cr53CrUL.js} +1 -1
- package/dist/ui/assets/{panel-rpc-handlers-BdkFuGpQ.js → panel-rpc-handlers-4OqnyAmK.js} +2 -2
- package/dist/ui/assets/{panelize-shell-D1zii6hm.js → panelize-shell-DF9ihI8M.js} +1 -1
- package/dist/ui/assets/{provider-jro_gWss.js → provider-1VMJDdZZ.js} +2 -2
- package/dist/ui/assets/{provider-DMprvn3a.js → provider-C6FaVer7.js} +1 -1
- package/dist/ui/assets/provider-settings-BoEydVUb.js +1 -0
- package/dist/ui/assets/provider-store-access-BgcRADIe.js +1 -0
- package/dist/ui/assets/provider-store-access-CFksaoYa.js +1 -0
- package/dist/ui/assets/{providers-BDJVkWwI.js → providers-BySKmZld.js} +2 -2
- package/dist/ui/assets/{providers-BeB7fPFO.js → providers-CwVqsqpF.js} +1 -1
- package/dist/ui/assets/{quick-llm-B7QveYCI.js → quick-llm-CfwVaiXr.js} +1 -1
- package/dist/ui/assets/{recovery-screen-Dgs2UIfJ.js → recovery-screen-lyTGe0lL.js} +1 -1
- package/dist/ui/assets/{run-CqBeg0ZD.js → run-BKnRgn-D.js} +2 -2
- package/dist/ui/assets/{run-NWUgneNl.js → run-BQh3vxRY.js} +1 -1
- package/dist/ui/assets/{run-DCQ3ASsK.js → run-BiqCIix1.js} +1 -1
- package/dist/ui/assets/session-command-memory-Bh_U73Lu.js +21 -0
- package/dist/ui/assets/session-jsonl-BxhPJRfn.js +3 -0
- package/dist/ui/assets/session-jsonl-DAaYGd3T.js +11 -0
- package/dist/ui/assets/session-search-index-CYtd-I68.js +3 -0
- package/dist/ui/assets/{setup-feature-flags-remote-CjcsF8zx.js → setup-feature-flags-remote-vkoKRY9P.js} +1 -1
- package/dist/ui/assets/sidecar-repair-DMTwInFS.js +1 -0
- package/dist/ui/assets/{silent-renew-backoff-T19HVlKV.js → silent-renew-backoff-Cu6ccMA8.js} +1 -1
- package/dist/ui/assets/{skills-CT6WMAK_.js → skills-CAcGvshD.js} +1 -1
- package/dist/ui/assets/slicc-version-DRMK7s55.js +1 -0
- package/dist/ui/assets/{snapshot-BGJ0Zfej.js → snapshot-BiQjSDr1.js} +1 -1
- package/dist/ui/assets/{snapshot-features-DzNanNSb.js → snapshot-features-jnkdn4FF.js} +1 -1
- package/dist/ui/assets/{soundscape-0keHbXwY.js → soundscape-Q62OViF9.js} +1 -1
- package/dist/ui/assets/{speak-CHiox6lR.js → speak-BPaRbMbD.js} +1 -1
- package/dist/ui/assets/{sprinkle-manager-DrNprYvC.js → sprinkle-manager-BRWbNbE_.js} +1 -1
- package/dist/ui/assets/{sprinkle-renderer-DPHPwLo9.js → sprinkle-renderer-D52IoNN9.js} +1 -1
- package/dist/ui/assets/{state-CdME0BCc.js → state-Dd7acB-m.js} +1 -1
- package/dist/ui/assets/{store-DLYHt4os.js → store-Cwtr3R07.js} +1 -1
- package/dist/ui/assets/{store-Camw_-5t.js → store-Lwp3bUNr.js} +1 -1
- package/dist/ui/assets/{sudo-BUVdlPTs.js → sudo-D46irfhN.js} +1 -1
- package/dist/ui/assets/{sync-dialog-DYLpVny9.js → sync-dialog-DJsA6fkU.js} +1 -1
- package/dist/ui/assets/telemetry-DJyRzh6X.js +3 -0
- package/dist/ui/assets/{transformers-env-BZAuKR5H.js → transformers-env-tgTHZJKu.js} +1 -1
- package/dist/ui/assets/{tray-sidecar-BVOOvnQ1.js → tray-sidecar-D2stckPR.js} +1 -1
- package/dist/ui/assets/{upgrade-detection-CSwS-mY6.js → upgrade-detection-BJHuh5PG.js} +1 -1
- package/dist/ui/assets/{voice-reply-B8amxE79.js → voice-reply-BOM0a2lE.js} +1 -1
- package/dist/ui/assets/{wc-attach-sGbwmqGH.js → wc-attach-DQ2wFLbP.js} +2 -2
- package/dist/ui/assets/{wc-extension-Ixq6p16J.js → wc-extension-C0DE4XGb.js} +2 -2
- package/dist/ui/assets/wc-follower-BaVLZOEY.js +17 -0
- package/dist/ui/assets/{wc-follower-oauth-Dvz6UYMw.js → wc-follower-oauth-DMMj3qmG.js} +1 -1
- package/dist/ui/assets/wc-live-Ep7nfskG.js +395 -0
- package/dist/ui/assets/{wc-nav-DUY6DnjF.js → wc-nav-8TQAWunr.js} +2 -2
- package/dist/ui/assets/{wc-onboarding-DMGxXdNN.js → wc-onboarding-7ug9Vb53.js} +2 -2
- package/dist/ui/assets/{wc-placeholder-BC41KH7-.js → wc-placeholder-ciXts94w.js} +3 -3
- package/dist/ui/assets/{wc-secret-request-DNhGkxtu.js → wc-secret-request-Bg0Z9yTb.js} +1 -1
- package/dist/ui/assets/{wc-settings-O5_espwl.js → wc-settings-DcEIZKCO.js} +3 -3
- package/dist/ui/assets/{wc-shell-QVgWC1iO.js → wc-shell-0IYPmKz5.js} +3 -3
- package/dist/ui/assets/{wc-shortcut-config-NsHO2K__.js → wc-shortcut-config-D-nERhOf.js} +1 -1
- package/dist/ui/assets/{wc-shortcuts-BhlyPw9O.js → wc-shortcuts-8X2r2czL.js} +1 -1
- package/dist/ui/assets/{wc-sprinkles-BOUieMUu.js → wc-sprinkles-_Fa4xEjd.js} +2 -2
- package/dist/ui/assets/{wc-transcript-export-C672Xz_9.js → wc-transcript-export-BTNFe3Rj.js} +2 -2
- package/dist/ui/assets/{wc-tray-Cv7TKKgN.js → wc-tray-Ng4IYOo7.js} +4 -4
- package/dist/ui/assets/welcome-detection-BqZPDl32.js +1 -0
- package/dist/ui/assets/{whisper-session-Bvq8APC_.js → whisper-session-iQYr-1HE.js} +2 -2
- package/dist/ui/assets/{xai-grok-DzE0hUca.js → xai-grok-B79MufzJ.js} +1 -1
- package/dist/ui/assets/{xai-grok-Dfk35Eqi.js → xai-grok-BkWgoDic.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/ui/packages/webapp/index.html +4 -4
- package/package.json +1 -1
- package/dist/ui/assets/SKILL-Br-bRfNe.js +0 -1
- package/dist/ui/assets/frozen-archive-format-CP0rleV0.js +0 -1
- package/dist/ui/assets/live-session-snapshot-Dw8Q7uwS.js +0 -8
- package/dist/ui/assets/provider-settings-BfLuGvLq.js +0 -1
- package/dist/ui/assets/provider-store-access-CuFPq1o_.js +0 -1
- package/dist/ui/assets/provider-store-access-DKRvCT0v.js +0 -1
- package/dist/ui/assets/sidecar-repair-BYk4QTYn.js +0 -1
- package/dist/ui/assets/slicc-version-xHiBp9_g.js +0 -1
- package/dist/ui/assets/telemetry-BWAgv6Hm.js +0 -3
- package/dist/ui/assets/wc-follower-BywpaV2X.js +0 -17
- package/dist/ui/assets/wc-live-CwoQmosr.js +0 -400
- package/dist/ui/assets/welcome-detection-Gm4VqZTR.js +0 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Transform } from 'node:stream';
|
|
2
|
+
/** gzip member magic (`1f 8b`). */
|
|
3
|
+
export declare const GZIP_MAGIC_0 = 31;
|
|
4
|
+
export declare const GZIP_MAGIC_1 = 139;
|
|
5
|
+
/**
|
|
6
|
+
* True when the upstream `content-encoding` does not claim a compressed
|
|
7
|
+
* coding. Empty and `identity` both mean "the bytes are the representation"
|
|
8
|
+
* — which is a lie when an edge returns cached gzip without the header
|
|
9
|
+
* (#3037, AEM/Fastly).
|
|
10
|
+
*/
|
|
11
|
+
export declare function contentEncodingLooksUncompressed(value: string | null | undefined): boolean;
|
|
12
|
+
export declare function bufferStartsWithGzipMagic(buf: Uint8Array): boolean;
|
|
13
|
+
export interface MaybeGunzipOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Fired once the first two bytes have been seen (or on empty EOF), before
|
|
16
|
+
* any decoded bytes are pushed. `inflating` is true when gzip magic was
|
|
17
|
+
* present and the rest of the body will go through zlib.
|
|
18
|
+
*/
|
|
19
|
+
onDecided?: (inflating: boolean) => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Peek-and-maybe-gunzip transform for `/api/fetch-proxy`.
|
|
23
|
+
*
|
|
24
|
+
* If the body starts with gzip magic, pipe through `zlib.createGunzip`.
|
|
25
|
+
* Otherwise pass bytes through. The route only inserts this transform for
|
|
26
|
+
* text content types so a real `application/gzip` / `.tar.gz` download is
|
|
27
|
+
* not silently inflated.
|
|
28
|
+
*
|
|
29
|
+
* Declared `gzip`/`br` is usually already inflated by undici; sniffing
|
|
30
|
+
* magic still catches the AEM/Fastly shape (gzip bytes, encoding
|
|
31
|
+
* absent/`identity`). The inner Gunzip is paused when `push` returns
|
|
32
|
+
* false so a highly compressible chunk cannot unbounded-buffer here.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createMaybeGunzipStream(options?: MaybeGunzipOptions): Transform;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Transform } from 'node:stream';
|
|
2
|
+
import { createGunzip } from 'node:zlib';
|
|
3
|
+
/** gzip member magic (`1f 8b`). */
|
|
4
|
+
export const GZIP_MAGIC_0 = 0x1f;
|
|
5
|
+
export const GZIP_MAGIC_1 = 0x8b;
|
|
6
|
+
/**
|
|
7
|
+
* True when the upstream `content-encoding` does not claim a compressed
|
|
8
|
+
* coding. Empty and `identity` both mean "the bytes are the representation"
|
|
9
|
+
* — which is a lie when an edge returns cached gzip without the header
|
|
10
|
+
* (#3037, AEM/Fastly).
|
|
11
|
+
*/
|
|
12
|
+
export function contentEncodingLooksUncompressed(value) {
|
|
13
|
+
const encoding = (value ?? '').trim().toLowerCase();
|
|
14
|
+
return encoding === '' || encoding === 'identity';
|
|
15
|
+
}
|
|
16
|
+
export function bufferStartsWithGzipMagic(buf) {
|
|
17
|
+
return buf.length >= 2 && buf[0] === GZIP_MAGIC_0 && buf[1] === GZIP_MAGIC_1;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Peek-and-maybe-gunzip transform for `/api/fetch-proxy`.
|
|
21
|
+
*
|
|
22
|
+
* If the body starts with gzip magic, pipe through `zlib.createGunzip`.
|
|
23
|
+
* Otherwise pass bytes through. The route only inserts this transform for
|
|
24
|
+
* text content types so a real `application/gzip` / `.tar.gz` download is
|
|
25
|
+
* not silently inflated.
|
|
26
|
+
*
|
|
27
|
+
* Declared `gzip`/`br` is usually already inflated by undici; sniffing
|
|
28
|
+
* magic still catches the AEM/Fastly shape (gzip bytes, encoding
|
|
29
|
+
* absent/`identity`). The inner Gunzip is paused when `push` returns
|
|
30
|
+
* false so a highly compressible chunk cannot unbounded-buffer here.
|
|
31
|
+
*/
|
|
32
|
+
export function createMaybeGunzipStream(options = {}) {
|
|
33
|
+
return new MaybeGunzipTransform(options.onDecided);
|
|
34
|
+
}
|
|
35
|
+
class MaybeGunzipTransform extends Transform {
|
|
36
|
+
#head = Buffer.alloc(0);
|
|
37
|
+
#decided = false;
|
|
38
|
+
#gunzip = null;
|
|
39
|
+
#onDecided;
|
|
40
|
+
constructor(onDecided) {
|
|
41
|
+
super();
|
|
42
|
+
this.#onDecided = onDecided;
|
|
43
|
+
// Destination drained — let zlib produce more. Attached once so a
|
|
44
|
+
// paused Gunzip cannot sit idle after `push` returned false.
|
|
45
|
+
this.on('resume', () => {
|
|
46
|
+
this.#gunzip?.resume();
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
_transform(chunk, _enc, cb) {
|
|
50
|
+
if (this.#gunzip) {
|
|
51
|
+
this.#gunzip.write(chunk, cb);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (this.#decided) {
|
|
55
|
+
cb(null, chunk);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.#head = Buffer.concat([this.#head, chunk]);
|
|
59
|
+
if (this.#head.length < 2) {
|
|
60
|
+
cb();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
this.#finishDecide(cb);
|
|
64
|
+
}
|
|
65
|
+
_flush(cb) {
|
|
66
|
+
if (!this.#decided) {
|
|
67
|
+
if (this.#head.length === 0) {
|
|
68
|
+
this.#onDecided?.(false);
|
|
69
|
+
cb();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
this.#finishDecide((err) => {
|
|
73
|
+
if (err) {
|
|
74
|
+
cb(err);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.#endGunzipOrFinish(cb);
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
this.#endGunzipOrFinish(cb);
|
|
82
|
+
}
|
|
83
|
+
#finishDecide(cb) {
|
|
84
|
+
this.#decided = true;
|
|
85
|
+
const head = this.#head;
|
|
86
|
+
this.#head = Buffer.alloc(0);
|
|
87
|
+
if (!bufferStartsWithGzipMagic(head)) {
|
|
88
|
+
this.#onDecided?.(false);
|
|
89
|
+
cb(null, head);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.#onDecided?.(true);
|
|
93
|
+
const gunzip = createGunzip();
|
|
94
|
+
this.#gunzip = gunzip;
|
|
95
|
+
gunzip.on('data', (decoded) => {
|
|
96
|
+
if (!this.push(decoded)) {
|
|
97
|
+
gunzip.pause();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
gunzip.on('error', (err) => {
|
|
101
|
+
this.destroy(err);
|
|
102
|
+
});
|
|
103
|
+
gunzip.write(head, cb);
|
|
104
|
+
}
|
|
105
|
+
#endGunzipOrFinish(cb) {
|
|
106
|
+
const gunzip = this.#gunzip;
|
|
107
|
+
if (!gunzip) {
|
|
108
|
+
cb();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const onEnd = () => {
|
|
112
|
+
gunzip.off('error', onError);
|
|
113
|
+
cb();
|
|
114
|
+
};
|
|
115
|
+
const onError = (err) => {
|
|
116
|
+
gunzip.off('end', onEnd);
|
|
117
|
+
cb(err);
|
|
118
|
+
};
|
|
119
|
+
gunzip.once('end', onEnd);
|
|
120
|
+
gunzip.once('error', onError);
|
|
121
|
+
gunzip.end();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -35,6 +35,9 @@ export const FETCH_PROXY_SKIP_HEADERS = new Set([
|
|
|
35
35
|
// @slicc/shared-ts secrets-pipeline.ts) — consumed by the route handler
|
|
36
36
|
// to compute and attach a real signature header; never forwarded as-is.
|
|
37
37
|
'x-slicc-hmac-sign',
|
|
38
|
+
// Let undici negotiate gzip/br. Forcing `identity` made AEM/Fastly
|
|
39
|
+
// return cached gzip bytes with no `content-encoding` (#3037).
|
|
40
|
+
'accept-encoding',
|
|
38
41
|
]);
|
|
39
42
|
/**
|
|
40
43
|
* Response-side header names the proxy must NOT copy from the upstream
|
|
@@ -53,6 +56,10 @@ export const FETCH_PROXY_SKIP_HEADERS = new Set([
|
|
|
53
56
|
*/
|
|
54
57
|
export const FETCH_PROXY_SKIP_RESPONSE_HEADERS = new Set([
|
|
55
58
|
'transfer-encoding',
|
|
59
|
+
// Stripped because this hop delivers decoded bytes: undici inflates a
|
|
60
|
+
// declared coding, and `streamUpstreamBody` gunzips a body that still
|
|
61
|
+
// starts with gzip magic when the coding is absent (#3037). A synthetic
|
|
62
|
+
// SW `Response` does not inflate `content-encoding`.
|
|
56
63
|
'content-encoding',
|
|
57
64
|
'content-length',
|
|
58
65
|
'www-authenticate',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Readable, Transform } from 'node:stream';
|
|
1
|
+
import { PassThrough, Readable, Transform } from 'node:stream';
|
|
2
2
|
import { StringDecoder } from 'node:string_decoder';
|
|
3
3
|
import { HMAC_SIGN_HEADER, isFormContentType, isLoopbackOrigin, isTextContentType, isTextRequestContentType, unmaskFormBody, } from '../_shared/index.js';
|
|
4
|
+
import { createMaybeGunzipStream } from '../fetch-proxy-gzip.js';
|
|
4
5
|
import { buildFetchProxyExposeHeaders, FETCH_PROXY_CONTENT_LENGTH_HEADER, FETCH_PROXY_SKIP_HEADERS, FETCH_PROXY_SKIP_RESPONSE_HEADERS, FETCH_PROXY_SKIP_RESPONSE_PREFIXES, } from '../fetch-proxy-headers.js';
|
|
5
6
|
import { AgentActivityTracker, registerAgentActivityRoute } from './agent-activity.js';
|
|
6
7
|
/** Pick the first value of a possibly-multi-valued request header. */
|
|
@@ -24,7 +25,8 @@ async function collectRawBody(req) {
|
|
|
24
25
|
/**
|
|
25
26
|
* Build the forwarded header set: copy non-hop-by-hop headers, then restore
|
|
26
27
|
* the forbidden-header transports (Cookie/Origin/Referer/Proxy-*) the browser
|
|
27
|
-
* could not send via fetch()
|
|
28
|
+
* could not send via fetch(). `accept-encoding` is skipped so undici
|
|
29
|
+
* negotiates gzip/br and decompresses transparently (#3037).
|
|
28
30
|
*/
|
|
29
31
|
function buildForwardHeaders(req, targetUrl) {
|
|
30
32
|
const headers = {};
|
|
@@ -72,10 +74,6 @@ function buildForwardHeaders(req, targetUrl) {
|
|
|
72
74
|
delete headers[key];
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
|
-
// Always request uncompressed responses — the proxy doesn't decompress and
|
|
76
|
-
// the browser→proxy hop is localhost, so compression has no benefit and
|
|
77
|
-
// would arrive as garbage once Content-Encoding is stripped below.
|
|
78
|
-
headers['accept-encoding'] = 'identity';
|
|
79
77
|
return headers;
|
|
80
78
|
}
|
|
81
79
|
/**
|
|
@@ -246,25 +244,36 @@ function createScrubStream(secretProxy, isText) {
|
|
|
246
244
|
function streamUpstreamBody(res, upstream, secretProxy, detachClientClose) {
|
|
247
245
|
const isText = isTextContentType(upstream.headers.get('content-type') ?? '');
|
|
248
246
|
const upstreamStream = Readable.fromWeb(upstream.body);
|
|
247
|
+
// Text only: a real `application/gzip` / `.tar.gz` body is a gzip member
|
|
248
|
+
// whose bytes ARE the representation. Sniffing it would silently inflate
|
|
249
|
+
// a download. JS/CSS/HTML (#3037) are all text.
|
|
250
|
+
const decoded = isText
|
|
251
|
+
? createMaybeGunzipStream({
|
|
252
|
+
onDecided: (inflating) => {
|
|
253
|
+
if (inflating)
|
|
254
|
+
res.removeHeader(FETCH_PROXY_CONTENT_LENGTH_HEADER);
|
|
255
|
+
},
|
|
256
|
+
})
|
|
257
|
+
: new PassThrough();
|
|
249
258
|
const scrubChunk = createScrubStream(secretProxy, isText);
|
|
250
|
-
|
|
259
|
+
const onStreamError = (err) => {
|
|
251
260
|
detachClientClose();
|
|
252
261
|
if (!res.headersSent) {
|
|
253
262
|
res.setHeader('X-Proxy-Error', '1');
|
|
254
|
-
res
|
|
255
|
-
.status(502)
|
|
256
|
-
.json({ error: `Proxy stream failed: ${err instanceof Error ? err.message : err}` });
|
|
263
|
+
res.status(502).json({ error: `Proxy stream failed: ${err.message}` });
|
|
257
264
|
}
|
|
258
265
|
else {
|
|
259
266
|
res.destroy(err);
|
|
260
267
|
}
|
|
261
|
-
}
|
|
268
|
+
};
|
|
269
|
+
upstreamStream.on('error', onStreamError);
|
|
270
|
+
decoded.on('error', onStreamError);
|
|
262
271
|
// Belt-and-braces cleanup: 'finish' fires once the response is fully flushed;
|
|
263
272
|
// 'close' fires regardless of how the response ended. Either way the abort
|
|
264
273
|
// listener should be gone.
|
|
265
274
|
res.on('finish', detachClientClose);
|
|
266
275
|
res.on('close', detachClientClose);
|
|
267
|
-
upstreamStream.pipe(scrubChunk).pipe(res);
|
|
276
|
+
upstreamStream.pipe(decoded).pipe(scrubChunk).pipe(res);
|
|
268
277
|
}
|
|
269
278
|
/**
|
|
270
279
|
* Fetch proxy — forwards cross-origin requests from the browser to bypass
|