Compare commits

..
22 Commits
Author SHA1 Message Date
cdrci ee31cdbe02 Update Code to 1.144.0 (#8087) 2026-10-28 17:38:07 -08:00
Andy Piper 27808460b1 Fix --socket-fd when code-server is socket-activated (#8083)
The --socket-fd flag never worked. The wrapper forks the process that
listens, but only the IPC channel was inherited as fd 3, so the socket
passed by the caller was not available to the child. Node then tried to
listen on the IPC pipe and failed with "listen EEXIST".

Pass the socket to the child as stdio entry 4 and tell the child to use
that fd in the handshake.

Node also reports no address for a Unix socket adopted by fd, which made
startup fail with "Server has no address". Log the listener as fd:<n>
in that case.

Add an integration test that starts the code-server binary with a Unix
or TCP socket as fd 3 and checks that /healthz answers over it.
2026-10-28 15:11:06 -08:00
Harsh Bhanushali 70fc911358 Override basic-ftp to ^6.2.1 (#8086)
get-uri still depends on basic-ftp ^5.x, which has no patched release.
2026-10-28 14:25:45 -08:00
Raphael Fakhri 761b0d2a19 Fix cookie domain not being set against root host (#8076) 2026-10-27 13:05:56 -08:00
Vincent Coffey 09012065c9 Support load balancer service in Helm chart (#8081) 2026-10-27 11:58:13 -08:00
dependabot[bot] e999549684 Bump follow-redirects from 1.16.0 to 1.16.2 (#8065) 2026-10-27 11:51:39 -08:00
dependabot[bot] 9d0eee156e Bump ip-address from 10.7.2 to 10.7.5 (#8080) 2026-10-26 17:01:48 -08:00
dependabot[bot] b87ae88bc6 Bump body-parser from 2.3.0 to 2.4.0 (#8079) 2026-10-26 17:01:24 -08:00
Harsh Bhanushali 89cba83cb4 Normalize default ports and use X-Forwarded-Port in the origin check (#8059)
The origin check compared the host header to the origin's host as plain
strings, so `example.com:443` did not match `https://example.com`, and a
proxy forwarding `$host` (no port) together with X-Forwarded-Port failed
on any non-default port.

Default ports (80 for http, 443 for https) are now treated as no port,
and when X-Forwarded-Host has no port the port from X-Forwarded-Port is
used.  Everything else is still an exact match.  getHost() is unchanged
for the domain proxy.
2026-10-26 16:42:24 -08:00
dependabot[bot] b45f29491d Bump js-yaml from 4.3.1 to 5.4.3 (#8054) 2026-10-26 15:47:26 -08:00
cdrci 4677c9d865 Update Helm chart and changelog with 4.143.1 (#8072) 2026-10-23 15:32:11 -08:00
cdrci 22ddf60dbc Update Code to 1.143.1 (#8069)
* Remove Node patch
* Update patches
Just one conflict, they use user-data-dir now which is something we
already have been doing for a long time.
2026-10-23 22:15:00 +00:00
cdrci e777c1f963 Update Helm chart and changelog with 4.141.1 (#8071) 2026-10-23 13:49:22 -08:00
Dean Sheather 279b5702aa Merge pull request #8070 from coder/upgrade-node-checksums
Fix Node 24.22.1 checksums
2026-10-24 04:50:00 +11:00
Dean Sheather 27baccf843 Fix Node 24.22.1 checksums 2026-10-23 17:35:56 +00:00
Dean Sheather 8aab1b0d72 Merge pull request #8068 from coder/upgrade-node
Upgrade Node to 24.22.1
2026-10-24 02:35:35 +11:00
Dean Sheather 777c493a9b Upgrade Node to 24.22.1 2026-10-23 15:06:41 +00:00
Harsh Bhanushali f07405cba3 Expand ~ in path options from the config file (#8055)
The shell expands ~ in command-line arguments, but values read from
config.yaml were passed to path.resolve as-is, so
`user-data-dir: ~/.config/code-server-data` resolved to
`<cwd>/~/.config/code-server-data`.
2026-10-14 13:47:08 -08:00
dependabot[bot] c5fee2d2f9 Bump brace-expansion from 1.1.18 to 1.1.21 (#8044) 2026-10-13 17:02:46 -08:00
Harsh Bhanushali 83670e50c6 Fix false booleans in config file being parsed as true (#8053)
Options like `enable-remote-storage: false` in config.yaml are converted to
`--enable-remote-storage=false`, but the parser set any boolean option to
true regardless of its value. Booleans set to false are now ignored, the
same way optional strings set to false already are.
2026-10-13 17:01:10 -08:00
KeuqS 41cf6f7eaf Add Turkish locale (#8050) 2026-10-09 22:50:17 +00:00
cdrci 78aad47ce6 Update Helm chart and changelog with 4.141.0 (#8045) 2026-10-08 02:35:52 -08:00
39 changed files with 547 additions and 86 deletions
+1 -1
View File
@@ -1 +1 @@
24.21.0
24.22.1
+52
View File
@@ -22,6 +22,58 @@ Code v99.99.999
## Unreleased
Code v1.144.0
### Changed
- Update to Code 1.144.0
### Fixed
- `--socket-fd` now works: the inherited socket is passed on to the process
that listens, and the address is logged as `fd:<n>`. Previously code-server
failed with `listen EEXIST` or `Server has no address`.
- The cookie domain was not being set against the shared root host of proxy
domains when proxy domains contained template variables like `{{port}}`,
resulting in having to sign in multiple times per domain instead of only once
as originally intended.
- When doing the web socket origin check, if the host does not already contain a
port, fall back to `X-Forwarded-Port`.
- Fix default ports not matching in the web socket origin check (for example
`https://coder.com` will now match `https://coder.com:443`).
## [4.143.1](https://github.com/coder/code-server/releases/tag/v4.143.1) - 2026-10-23
Code v1.143.1
### Changed
- Update to Code 1.143.1
## [4.141.1](https://github.com/coder/code-server/releases/tag/v4.141.1) - 2026-10-23
### Security
- Update bundled Node.js to 24.22.1 to fix a remote code execution bug in the
Brotli decoder (CVE-2026-111497).
- Reject compressed request bodies (`Content-Encoding: br`, `gzip`, or
`deflate`) instead of decompressing them.
### Fixed
- Boolean flags in the `--flag=false` form and in the config file now respect
being set to false.
### Added
- Turkish locale.
### Changed
- Expand values from path options in the config file that start with a tilde.
## [4.141.0](https://github.com/coder/code-server/releases/tag/v4.141.0) - 2026-10-08
Code v1.141.0
### Added
+8
View File
@@ -101,6 +101,14 @@ function update_csp() {
echo "Calculated hash as $hash"
# Use octothorpe as a delimiter since the hash may contain a slash.
sed -i.bak "s#'sha256-[^']\+'#'sha256-$hash'#" "$file"
# For the web worker extension host iframe, there is a matching sha in the
# web server.
if [[ $file == */webWorkerExtensionHostIframe.html ]] ; then
quilt add ./lib/vscode/src/vs/server/node/webClientServer.ts
sed -i.bak \
"s#webWorkerExtensionHostIframeScriptSHA = 'sha256-[^']\+'#webWorkerExtensionHostIframeScriptSHA = 'sha256-$hash'#" \
./lib/vscode/src/vs/server/node/webClientServer.ts
fi
done
quilt refresh
+2 -2
View File
@@ -15,9 +15,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 3.55.0
version: 3.57.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
appVersion: 4.140.0
appVersion: 4.143.1
+5
View File
@@ -4,8 +4,13 @@ metadata:
name: {{ include "code-server.fullname" . }}
labels:
{{- include "code-server.labels" . | nindent 4 }}
annotations:
{{- with .Values.service.annotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
loadBalancerClass: {{ .Values.service.loadBalancerClass }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
+3 -1
View File
@@ -6,7 +6,7 @@ replicaCount: 1
image:
repository: codercom/code-server
tag: '4.140.0'
tag: '4.143.1'
pullPolicy: Always
# Specifies one or more secrets to be used when pulling images from a
@@ -48,6 +48,8 @@ priorityClassName: ""
service:
type: ClusterIP
port: 8080
loadBalancerClass: null
annotations: {}
ingress:
enabled: false
+36 -36
View File
@@ -269,9 +269,9 @@
}
},
"node_modules/@eslint/eslintrc/node_modules/js-yaml": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
"integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
"integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
"dev": true,
"funding": [
{
@@ -1055,9 +1055,9 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "5.0.9",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
"integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1726,27 +1726,27 @@
"license": "MIT"
},
"node_modules/basic-ftp": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz",
"integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==",
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-6.2.3.tgz",
"integrity": "sha512-T7GYtQ2eKJUN5YORG7A8u2WfB9JMN0a4/wl3MI64qq2qTRfa3FXzoNq0DDojZDkDyStcL+TkfKsz4JHEZx5kGQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/body-parser": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
"integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.4.0.tgz",
"integrity": "sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^2.0.0",
"content-type": "^2.1.0",
"debug": "^4.4.3",
"http-errors": "^2.0.1",
"iconv-lite": "^0.7.2",
"iconv-lite": "^0.7.3",
"on-finished": "^2.4.1",
"qs": "^6.15.2",
"qs": "^6.16.1",
"raw-body": "^3.0.2",
"type-is": "^2.1.0"
},
@@ -1759,9 +1759,9 @@
}
},
"node_modules/body-parser/node_modules/content-type": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
"integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
"integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
"license": "MIT",
"engines": {
"node": ">=18"
@@ -1779,9 +1779,9 @@
"license": "BSD-2-Clause"
},
"node_modules/brace-expansion": {
"version": "1.1.18",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"version": "1.1.21",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz",
"integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3245,9 +3245,9 @@
"license": "ISC"
},
"node_modules/follow-redirects": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.2.tgz",
"integrity": "sha512-/kVP2U8z596Ia0gwFrwyIeo74ta5aVmSeU1FfAUMAmcEP5EP7zydmag3SLiN64NaRKef3lxsq7i/g7ltJQDGow==",
"funding": [
{
"type": "individual",
@@ -3703,9 +3703,9 @@
}
},
"node_modules/iconv-lite": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
"integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -3777,9 +3777,9 @@
}
},
"node_modules/ip-address": {
"version": "10.7.2",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz",
"integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==",
"version": "10.7.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.5.tgz",
"integrity": "sha512-t2v0rkILYv9WXmSL7TpAaU3HXgaFYOPUcH3QHgwQ++gpJuH3bqyMy4Fvt/DJCTKwzH3Cf+bAp5W/mFL39KPLzA==",
"license": "MIT",
"engines": {
"node": ">= 12"
@@ -4270,9 +4270,9 @@
"license": "ISC"
},
"node_modules/js-yaml": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.1.tgz",
"integrity": "sha512-28R/k+NAjeuf7+CKlTxWZVExJGwVVLwY06DgEnOMz2gEpfNkDcD7QvyiVPT0xy0XXhU8vHsd4Ot42OOPdJG7dQ==",
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.3.tgz",
"integrity": "sha512-yzzNtgczwgoVHNigCyKTXcQJU4Y8t/ikU4UxXW0BvrLfHTGWZhsNcOT2s/a7vn81/4KqVnNmHjaUEPlvWnNwVw==",
"funding": [
{
"type": "github",
@@ -5366,9 +5366,9 @@
}
},
"node_modules/qs": {
"version": "6.16.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
"integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
"version": "6.16.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.16.1.tgz",
"integrity": "sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==",
"license": "BSD-3-Clause",
"dependencies": {
"es-define-property": "^1.0.1",
+3
View File
@@ -90,6 +90,9 @@
"resolutions": {
"@types/node": "24.x"
},
"overrides": {
"basic-ftp": "^6.2.1"
},
"bin": {
"code-server": "out/node/entry.js"
},
+1 -1
View File
@@ -10,7 +10,7 @@ Index: code-server/lib/vscode/src/vs/base/common/network.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/base/common/network.ts
+++ code-server/lib/vscode/src/vs/base/common/network.ts
@@ -256,7 +256,9 @@ class RemoteAuthoritiesImpl {
@@ -262,7 +262,9 @@ class RemoteAuthoritiesImpl {
return URI.from({
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
authority: `${host}:${port}`,
+1 -1
View File
@@ -2,7 +2,7 @@ Index: code-server/lib/vscode/build/gulpfile.extensions.ts
===================================================================
--- code-server.orig/lib/vscode/build/gulpfile.extensions.ts
+++ code-server/lib/vscode/build/gulpfile.extensions.ts
@@ -291,6 +291,29 @@ export const compileCopilotExtensionBuil
@@ -283,6 +283,29 @@ export const compileCopilotExtensionBuil
task.task(compileCopilotExtensionBuildTask);
/**
+15 -2
View File
@@ -6,8 +6,8 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy"
- content="default-src 'none'; script-src 'sha256-FFQoOVVa2tOE3uqUvirwaMNT20TZmrHcL2aaOjJ8BUo=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
+ content="default-src 'none'; script-src 'sha256-24QqbpzqcqSyIGvg/ZwFRy5pPlhmVBa8CkUREEIUnBw=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
- content="default-src 'none'; script-src 'sha256-Q3Tfcw0dGu/YD4dGBrRqBeOnmRMAH8HbKxArK4YSXBc=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
+ content="default-src 'none'; script-src 'sha256-BmcTt6YZr75RujSQ3gF/UQL77e1RrOx62jNeikpwtxc=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
<!-- Disable pinch zooming -->
<meta name="viewport"
@@ -24,3 +24,16 @@ Index: code-server/lib/vscode/src/vs/workbench/services/extensions/worker/webWor
connect-src 'self' https: wss: http://localhost:* http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:*;"/>
</head>
<body>
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -114,7 +114,7 @@ const APP_ROOT = dirname(FileAccess.asFi
const STATIC_PATH = `/static`;
const CALLBACK_PATH = `/callback`;
const WEB_EXTENSION_PATH = `/web-extension-resource`;
-const webWorkerExtensionHostIframeScriptSHA = 'sha256-daEgfo2VIXpx2Np71KqCCbkeQwv+68vPrx54XRcbdcs=';
+const webWorkerExtensionHostIframeScriptSHA = 'sha256-gfjiOVjAlxqrivZAfTq2d83mOaz0fnAxbGaFpj5IjD8=';
/**
* Substitutes the `{{...}}` placeholders of a workbench template. Placeholders must only ever
+1 -1
View File
@@ -7,7 +7,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
@@ -343,6 +343,10 @@ export class Extension implements IExten
@@ -344,6 +344,10 @@ export class Extension implements IExten
if (this.type === ExtensionType.System && this.productService.quality === 'stable' && !this.productService.builtInExtensionsEnabledWithAutoUpdates?.some(id => id.toLowerCase() === this.identifier.id.toLowerCase())) {
return false;
}
+1 -1
View File
@@ -18,7 +18,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
import { ProtocolConstants } from '../../base/parts/ipc/common/ipc.net.js';
import { createRandomIPCHandle } from '../../base/parts/ipc/node/ipc.net.js';
import { IConfigurationService } from '../../platform/configuration/common/configuration.js';
@@ -418,6 +418,9 @@ export async function setupServerService
@@ -444,6 +444,9 @@ export async function setupServerService
socketServer.registerChannel('mcpManagement', new McpManagementChannel(mcpManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority)));
+3 -3
View File
@@ -282,7 +282,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/files/browser/views/explo
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
@@ -65,6 +65,7 @@ import { timeout } from '../../../../../
@@ -62,6 +62,7 @@ import { defaultCountBadgeStyles } from
import { IFilesConfigurationService } from '../../../../services/filesConfiguration/common/filesConfigurationService.js';
import { mainWindow } from '../../../../../base/browser/window.js';
import { IExplorerFileContribution, explorerFileContribRegistry } from '../explorerFileContrib.js';
@@ -290,7 +290,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/files/browser/views/explo
import { WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js';
import { ISearchService, QueryType, getExcludes, ISearchConfiguration, ISearchComplete, IFileQuery } from '../../../../services/search/common/search.js';
import { CancellationToken } from '../../../../../base/common/cancellation.js';
@@ -1587,7 +1588,8 @@ export class FileDragAndDrop implements
@@ -1515,7 +1516,8 @@ export class FileDragAndDrop implements
@IConfigurationService private configurationService: IConfigurationService,
@IInstantiationService private instantiationService: IInstantiationService,
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService,
@@ -300,7 +300,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/files/browser/views/explo
) {
const updateDropEnablement = (e: IConfigurationChangeEvent | undefined) => {
if (!e || e.affectsConfiguration('explorer.enableDragAndDrop')) {
@@ -1812,15 +1814,17 @@ export class FileDragAndDrop implements
@@ -1740,15 +1742,17 @@ export class FileDragAndDrop implements
// External file DND (Import/Upload file)
if (data instanceof NativeDragAndDropData) {
+4 -4
View File
@@ -19,7 +19,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/bro
import { renderFormattedText } from '../../../../base/browser/formattedTextRenderer.js';
import { status } from '../../../../base/browser/ui/aria/aria.js';
import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';
@@ -55,7 +55,7 @@ import { IRecentFolder, IRecentWorkspace
@@ -56,7 +56,7 @@ import { IRecentFolder, IRecentWorkspace
import { OpenRecentAction } from '../../../browser/actions/windowActions.js';
import { OpenFileFolderAction, OpenFolderAction, OpenFolderViaWorkspaceAction } from '../../../browser/actions/workspaceActions.js';
import { EditorPane } from '../../../browser/parts/editor/editorPane.js';
@@ -28,7 +28,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/bro
import { IEditorOpenContext, IEditorSerializer } from '../../../common/editor.js';
import { IWebviewElement, IWebviewService } from '../../webview/browser/webview.js';
import './gettingStartedColors.js';
@@ -930,6 +930,72 @@ export class GettingStartedPage extends
@@ -944,6 +944,72 @@ export class GettingStartedPage extends
$('p.subtitle.description', {}, localize({ key: 'gettingStarted.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved"))
);
@@ -101,7 +101,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/bro
const leftColumn = $('.categories-column.categories-column-left', {},);
const rightColumn = $('.categories-column.categories-column-right', {},);
@@ -980,6 +1046,9 @@ export class GettingStartedPage extends
@@ -1000,6 +1066,9 @@ export class GettingStartedPage extends
recentList.setLimit(5);
reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
}
@@ -115,7 +115,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/bro
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css
+++ code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css
@@ -60,6 +60,15 @@
@@ -63,6 +63,15 @@
display: block;
}
+3 -3
View File
@@ -33,7 +33,7 @@ Index: code-server/lib/vscode/src/vs/server/node/server.main.ts
-delete process.env[agentHostBridgeConnectionTokenEnvironmentVariable];
-
-const REMOTE_DATA_FOLDER = args['server-data-dir'] || process.env['VSCODE_AGENT_FOLDER'] || join(os.homedir(), product.serverDataFolderName || '.vscode-remote');
-const USER_DATA_PATH = join(REMOTE_DATA_FOLDER, 'data');
-const USER_DATA_PATH = args['user-data-dir'] || join(REMOTE_DATA_FOLDER, 'data');
-const APP_SETTINGS_HOME = join(USER_DATA_PATH, 'User');
-const GLOBAL_STORAGE_HOME = join(APP_SETTINGS_HOME, 'globalStorage');
-const LOCAL_HISTORY_HOME = join(APP_SETTINGS_HOME, 'History');
@@ -169,7 +169,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.main.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
@@ -64,6 +64,7 @@ import { IOpenerService } from '../../pl
@@ -65,6 +65,7 @@ import { IOpenerService } from '../../pl
import { mixin, safeStringify } from '../../base/common/objects.js';
import { IndexedDB } from '../../base/browser/indexedDB.js';
import { WebFileSystemAccess } from '../../platform/files/browser/webFileSystemAccess.js';
@@ -177,7 +177,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
import { IProgressService } from '../../platform/progress/common/progress.js';
import { DelayedLogChannel } from '../services/output/common/delayedLogChannel.js';
import { dirname, joinPath } from '../../base/common/resources.js';
@@ -140,6 +141,9 @@ export class BrowserMain extends Disposa
@@ -141,6 +142,9 @@ export class BrowserMain extends Disposa
// Startup
const instantiationService = workbench.startup();
+1 -1
View File
@@ -77,7 +77,7 @@ Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/ext
import { TargetPlatform } from '../../extensions/common/extensions.js';
import { ExtensionGalleryResourceType, getExtensionGalleryManifestResourceUri, IExtensionGalleryManifest, IExtensionGalleryManifestService } from '../../extensionManagement/common/extensionGalleryManifest.js';
import { ILogService } from '../../log/common/log.js';
@@ -163,9 +162,9 @@ export abstract class AbstractExtensionR
@@ -167,9 +166,9 @@ export abstract class AbstractExtensionR
}
protected _isWebExtensionResourceEndPoint(uri: URI): boolean {
+1 -1
View File
@@ -10,7 +10,7 @@ Index: code-server/lib/vscode/src/vs/workbench/services/extensions/common/extens
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/extensions/common/extensions.ts
+++ code-server/lib/vscode/src/vs/workbench/services/extensions/common/extensions.ts
@@ -321,6 +321,10 @@ function extensionDescriptionArrayToMap(
@@ -324,6 +324,10 @@ function extensionDescriptionArrayToMap(
}
export function isProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): boolean {
+6 -6
View File
@@ -104,7 +104,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/serverServices.ts
+++ code-server/lib/vscode/src/vs/server/node/serverServices.ts
@@ -105,6 +105,10 @@ import { McpManagementChannel } from '..
@@ -106,6 +106,10 @@ import { McpManagementChannel } from '..
import { AllowedMcpServersService } from '../../platform/mcp/common/allowedMcpServersService.js';
import { IMcpGalleryManifestService } from '../../platform/mcp/common/mcpGalleryManifest.js';
import { McpGalleryManifestIPCService } from '../../platform/mcp/common/mcpGalleryManifestServiceIpc.js';
@@ -115,7 +115,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
import { SANDBOX_HELPER_CHANNEL_NAME, SandboxHelperChannel } from '../../platform/sandbox/common/sandboxHelperIpc.js';
import { SandboxHelperService } from '../../platform/sandbox/node/sandboxHelper.js';
@@ -238,6 +242,21 @@ export async function setupServerService
@@ -244,6 +248,21 @@ export async function setupServerService
services.set(IMcpGatewayService, new SyncDescriptor(McpGatewayService));
const instantiationService: IInstantiationService = new InstantiationService(services);
@@ -137,7 +137,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
services.set(ILanguagePackService, instantiationService.createInstance(NativeLanguagePackService));
const ptyHostStarter = instantiationService.createInstance(
@@ -420,6 +439,7 @@ export async function setupServerService
@@ -446,6 +465,7 @@ export async function setupServerService
const languagePackChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(ILanguagePackService), disposables);
socketServer.registerChannel('languagePacks', languagePackChannel);
@@ -179,7 +179,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.main.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
@@ -33,6 +33,7 @@ import { ISignService } from '../../plat
@@ -34,6 +34,7 @@ import { ISignService } from '../../plat
import { SignService } from '../../platform/sign/browser/signService.js';
import { IWorkbenchConstructionOptions, IWorkbench, IWorkspace, ITunnel } from './web.api.js';
import { BrowserStorageService } from '../services/storage/browser/storageService.js';
@@ -187,7 +187,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
import { IStorageService } from '../../platform/storage/common/storage.js';
import { toLocalISOString } from '../../base/common/date.js';
import { isWorkspaceToOpen, isFolderToOpen } from '../../platform/window/common/window.js';
@@ -482,7 +483,7 @@ export class BrowserMain extends Disposa
@@ -488,7 +489,7 @@ export class BrowserMain extends Disposa
serviceCollection.set(IWorkbenchConfigurationService, service);
return service;
}),
@@ -196,7 +196,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
serviceCollection.set(IStorageService, service);
return service;
})
@@ -604,7 +605,21 @@ export class BrowserMain extends Disposa
@@ -610,7 +611,21 @@ export class BrowserMain extends Disposa
}));
}
+1 -1
View File
@@ -20,7 +20,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
import { NullPolicyService } from '../../platform/policy/common/policy.js';
import { OneDataSystemAppender } from '../../platform/telemetry/node/1dsAppender.js';
import { LoggerService } from '../../platform/log/node/loggerService.js';
@@ -179,11 +180,23 @@ export async function setupServerService
@@ -184,11 +185,23 @@ export async function setupServerService
const requestService = new RequestService('remote', configurationService, environmentService, logService);
services.set(IRequestService, requestService);
+1 -1
View File
@@ -21,7 +21,7 @@ Index: code-server/lib/vscode/src/vs/workbench/services/storage/browser/storageS
export class BrowserStorageService extends AbstractStorageService {
@@ -354,7 +355,11 @@ export class IndexedDBStorageDatabase ex
@@ -365,7 +366,11 @@ export class IndexedDBStorageDatabase ex
}
static async createWorkspaceStorage(workspaceId: string, logService: ILogService): Promise<IIndexedDBStorageDatabase> {
+2 -2
View File
@@ -53,7 +53,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index.html
+++ code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index.html
@@ -253,7 +253,7 @@
@@ -266,7 +266,7 @@
}
const swPath = encodeURI(`service-worker.js?v=${expectedWorkerVersion}&vscode-resource-base-authority=${searchParams.get('vscode-resource-base-authority')}&remoteAuthority=${searchParams.get('remoteAuthority') ?? ''}&platform=${searchParams.get('platform')}`);
@@ -62,7 +62,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index
.then(async registration => {
if (navigator.serviceWorker.controller) {
// A previous SW is already controlling. Force an update
@@ -332,6 +332,12 @@
@@ -345,6 +345,12 @@
const hostname = location.hostname;
+6 -1
View File
@@ -111,9 +111,14 @@ export const createApp = async (args: DefaultedArgs): Promise<App> => {
*
* The address might be a URL or it might be a pipe or socket path.
*/
export const ensureAddress = (server: http.Server, protocol: string): URL | string => {
export const ensureAddress = (server: http.Server, protocol: string, socketFd?: number): URL | string => {
const addr = server.address()
// Node reports no address for a Unix socket adopted by fd.
if (!addr && typeof socketFd === "number") {
return `fd:${socketFd}`
}
if (!addr) {
throw new Error("Server has no address")
}
+7 -3
View File
@@ -3,7 +3,7 @@ import * as crypto from "crypto"
import { promises as fs } from "fs"
import { load } from "js-yaml"
import * as path from "path"
import { generateCertificate, generatePassword, paths, splitOnFirstEquals } from "./util"
import { expandTilde, generateCertificate, generatePassword, paths, splitOnFirstEquals } from "./util"
import { EditorSessionManagerClient } from "./vscodeSocket"
export enum Feature {
@@ -436,7 +436,11 @@ export const parse = (
const option = options[key]
if (option.type === "boolean") {
;(args[key] as boolean) = true
// Booleans set to false (for example `--flag=false`, which is also how
// false values from the config file are passed in) are ignored.
if (value !== "false") {
;(args[key] as boolean) = true
}
continue
}
@@ -462,7 +466,7 @@ export const parse = (
}
if (option.path) {
value = path.resolve(value)
value = path.resolve(expandTilde(value))
}
switch (option.type) {
+67 -7
View File
@@ -235,7 +235,16 @@ export const getCookieDomain = (host: string, proxyDomains: string[]): string |
return undefined
}
proxyDomains.forEach((domain) => {
proxyDomains.forEach((proxyDomain) => {
// Proxy domains are patterns like `{{port}}.domain.tld` or
// `prefix-{{port}}-suffix.domain.tld` so use the part after the label that
// contains the port.
const portIndex = proxyDomain.indexOf("{{port}}")
const domain = portIndex === -1 ? proxyDomain : proxyDomain.substring(proxyDomain.indexOf(".", portIndex) + 1)
// Skip patterns we cannot turn into a domain, like `{{port}}.{{host}}`.
if (!domain || domain.includes("{{")) {
return
}
if (host.endsWith(domain) && domain.length < host.length) {
host = domain
}
@@ -381,19 +390,20 @@ export function authenticateOrigin(req: express.Request): void {
return
}
let origin: string
let originUrl: URL
try {
origin = new URL(originRaw).host.trim().toLowerCase()
originUrl = new URL(originRaw)
} catch (error) {
throw new Error(`unable to parse malformed origin "${originRaw}"`)
}
const origin = originUrl.host.trim().toLowerCase()
const trustedOrigins = req.args["trusted-origins"] || []
if (isTrustedOrigin(origin, trustedOrigins)) {
return
}
const host = getHost(req)
const host = getHost(req, true)
if (typeof host === "undefined") {
// A missing host likely means the reverse proxy has not been configured to
// forward the host which means we cannot perform the check. Emit an error
@@ -403,15 +413,64 @@ export function authenticateOrigin(req: express.Request): void {
throw new Error("no host headers found")
}
if (host !== origin) {
if (!hostMatchesOrigin(host, originUrl)) {
throw new Error(`host "${host}" does not match origin "${origin}"`)
}
}
const defaultPorts: { [protocol: string]: string } = {
"http:": "80",
"https:": "443",
}
/**
* Parse a host like `example.com:8080` or `[::1]:8080`. A non-special scheme
* keeps the port as written instead of dropping default ports.
*/
function parseHost(host: string): URL | undefined {
try {
return new URL(`host://${host}`)
} catch (error) {
return undefined
}
}
/**
* Check whether the host from the request headers matches the origin.
*
* This is an exact match, except that a default port (80 for http, 443 for
* https) is the same as no port, since `example.com:443` and
* `https://example.com` are the same origin.
*/
export function hostMatchesOrigin(host: string, origin: URL): boolean {
const parsed = parseHost(host)
if (!parsed) {
return false
}
const port = parsed.port === defaultPorts[origin.protocol] ? "" : parsed.port
return parsed.hostname === origin.hostname.toLowerCase() && port === origin.port
}
/**
* Add the port from X-Forwarded-Port to a host that does not have one. Some
* reverse proxies (for example nginx with `$host`) forward the host without
* the port and send the port separately.
*/
function addForwardedPort(req: express.Request, host: string): string {
const port = getFirstHeader(req, "x-forwarded-port")?.split(",")[0].trim()
if (!port || !/^\d+$/.test(port) || parseHost(host)?.port !== "") {
return host
}
return `${host}:${port}`
}
/**
* Get the host from headers. It will be trimmed and lowercased.
*
* If `withForwardedPort` is set and the host comes from X-Forwarded-Host
* without a port, the port from X-Forwarded-Port is added.
*/
export function getHost(req: express.Request): string | undefined {
export function getHost(req: express.Request, withForwardedPort = false): string | undefined {
// Honor Forwarded if present.
const forwardedRaw = getFirstHeader(req, "forwarded")
if (forwardedRaw) {
@@ -430,7 +489,8 @@ export function getHost(req: express.Request): string | undefined {
if (xHost) {
const firstXHost = xHost.split(",")[0]
if (firstXHost) {
return firstXHost.trim().toLowerCase()
const host = firstXHost.trim().toLowerCase()
return withForwardedPort ? addForwardedPort(req, host) : host
}
}
+4
View File
@@ -3,6 +3,7 @@ import i18next, { init } from "i18next"
import * as en from "./locales/en.json"
import * as ja from "./locales/ja.json"
import * as th from "./locales/th.json"
import * as tr from "./locales/tr.json"
import * as ur from "./locales/ur.json"
import * as zhCn from "./locales/zh-cn.json"
@@ -16,6 +17,9 @@ const defaultResources = {
th: {
translation: th,
},
tr: {
translation: tr,
},
ja: {
translation: ja,
},
+13
View File
@@ -0,0 +1,13 @@
{
"LOGIN_TITLE": "{{app}} girişi",
"LOGIN_BELOW": "Lütfen aşağıdan giriş yapın.",
"WELCOME": "{{app}} uygulamasına hoş geldiniz",
"LOGIN_PASSWORD": "Parola için yapılandırma dosyasını kontrol edin.",
"LOGIN_USING_ENV_PASSWORD": "Parola $PASSWORD ortam değişkeninden ayarlandı.",
"LOGIN_USING_HASHED_PASSWORD": "Parola $HASHED_PASSWORD ortam değişkeninden ayarlandı.",
"SUBMIT": "GÖNDER",
"PASSWORD_PLACEHOLDER": "PAROLA",
"LOGIN_RATE_LIMIT": "Giriş denemesi sınırına ulaşıldı!",
"MISS_PASSWORD": "Parola eksik",
"INCORRECT_PASSWORD": "Parola yanlış."
}
+1 -1
View File
@@ -141,7 +141,7 @@ export const runCodeServer = async (
const app = await createApp(args)
const protocol = args.cert ? "https" : "http"
const serverAddress = ensureAddress(app.server, protocol)
const serverAddress = ensureAddress(app.server, protocol, args["socket-fd"])
const { disposeRoutes, heart } = await register(app, args)
logger.info(`Using config file ${args.config}`)
+4 -2
View File
@@ -132,8 +132,10 @@ export const register = async (
})
})
app.router.use(express.json())
app.router.use(express.urlencoded({ extended: true }))
// Reject compressed request bodies so unauthenticated requests cannot reach
// the zlib/Brotli decoders. Browsers never compress request bodies.
app.router.use(express.json({ inflate: false }))
app.router.use(express.urlencoded({ extended: true, inflate: false }))
app.router.use(
"/_static",
+15
View File
@@ -511,3 +511,18 @@ export function splitOnFirstEquals(str: string): [string, string | undefined] {
const split = str.split(/=(.+)?/, 2)
return [split[0], split[1]]
}
/**
* Expand a leading `~` to the home directory. The shell does this for
* command-line arguments but values from the config file are not expanded.
* `~user` forms are left alone.
*/
export function expandTilde(p: string): string {
if (p === "~") {
return os.homedir()
}
if (p.startsWith("~/") || p.startsWith(`~${path.sep}`)) {
return path.join(os.homedir(), p.slice(2))
}
return p
}
+1 -1
View File
@@ -38,7 +38,7 @@ export async function makeEditorSessionManagerServer(
): Promise<http.Server> {
const router = express()
router.use(express.json())
router.use(express.json({ inflate: false }))
router.get<{}, GetSessionResponse | string | unknown, undefined, { filePath?: string }>(
"/session",
+7 -2
View File
@@ -8,6 +8,9 @@ import { paths } from "./util"
const timeoutInterval = 10000 // 10s, matches VS Code's timeouts.
// Fd 3 in the child is the IPC channel, so the inherited socket goes to fd 4.
const CHILD_SOCKET_FD = 4
/**
* Listen to a single message from a process. Reject if the process errors,
* exits, or times out.
@@ -334,13 +337,14 @@ export class ParentProcess extends Process {
}
private spawn(): cp.ChildProcess {
const socketFd = this.args?.["socket-fd"]
return cp.fork(path.join(__dirname, "entry"), {
env: {
...process.env,
CODE_SERVER_PARENT_PID: process.pid.toString(),
NODE_EXEC_PATH: process.execPath,
},
stdio: ["pipe", "pipe", "pipe", "ipc"],
stdio: typeof socketFd === "number" ? ["pipe", "pipe", "pipe", "ipc", socketFd] : ["pipe", "pipe", "pipe", "ipc"],
})
}
@@ -359,7 +363,8 @@ export class ParentProcess extends Process {
this.logger,
)
this.logger.debug("got message", field("message", message))
this.send(child, { type: "handshake", args: this.args })
const args = typeof this.args["socket-fd"] === "number" ? { ...this.args, "socket-fd": CHILD_SOCKET_FD } : this.args
this.send(child, { type: "handshake", args })
}
/**
+80
View File
@@ -0,0 +1,80 @@
import { ChildProcess, spawn } from "child_process"
import { promises as fs } from "fs"
import * as http from "http"
import * as net from "net"
import * as path from "path"
import { clean, tmpdir } from "../utils/helpers"
describe("--socket-fd", () => {
const testName = "socketFd"
const codeServerPath = process.env.CODE_SERVER_PATH || path.resolve("../../release-standalone/bin/code-server")
let tempDir: string
let socketDir: string | undefined
let child: ChildProcess | undefined
beforeEach(async () => {
await clean(testName)
tempDir = await tmpdir(testName)
})
afterEach(async () => {
if (child?.pid && child.exitCode === null) {
// Kill the process group so that the wrapper's inner process also stops.
process.kill(-child.pid, "SIGKILL")
}
if (socketDir) {
await fs.rm(socketDir, { force: true, recursive: true })
socketDir = undefined
}
})
// Give the listener to code-server as fd 3, as systemd socket activation does.
const getHealthzThroughFd = async (server: net.Server, target: http.RequestOptions): Promise<number> => {
const fd: number = (server as any)._handle.fd
let output = ""
child = spawn(
codeServerPath,
[
"--socket-fd=3",
"--auth=none",
`--user-data-dir=${path.join(tempDir, "data")}`,
`--config=${path.join(tempDir, "config.yaml")}`,
],
{ detached: true, stdio: ["ignore", "pipe", "pipe", fd] },
)
child.stdout?.on("data", (chunk) => (output += chunk))
child.stderr?.on("data", (chunk) => (output += chunk))
await new Promise((resolve) => server.close(resolve))
const exited = new Promise<never>((_, reject) => {
child?.once("exit", (code) => reject(new Error(`code-server exited with code ${code}:\n${output}`)))
})
const status = new Promise<number>((resolve, reject) => {
http
.get({ ...target, path: "/healthz" }, (res) => {
res.resume()
resolve(res.statusCode ?? 0)
})
.once("error", (error) => reject(new Error(`${error.message}:\n${output}`)))
})
return Promise.race([status, exited])
}
it("should serve requests on a Unix socket passed as a file descriptor", async () => {
// macOS limits socket paths to 104 bytes, and os.tmpdir() can be too long.
socketDir = await fs.mkdtemp("/tmp/cs-fd-")
const server = net.createServer()
await new Promise<void>((resolve) => server.listen(path.join(socketDir!, "listen.sock"), resolve))
// server.close() unlinks the socket path, so connect through a hard link.
const socketPath = path.join(socketDir, "connect.sock")
await fs.link(path.join(socketDir, "listen.sock"), socketPath)
expect(await getHealthzThroughFd(server, { socketPath })).toBe(200)
}, 60000)
it("should serve requests on a TCP socket passed as a file descriptor", async () => {
const server = net.createServer()
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve))
const { port } = server.address() as net.AddressInfo
expect(await getHealthzThroughFd(server, { host: "127.0.0.1", port })).toBe(200)
}, 60000)
})
+3
View File
@@ -152,6 +152,9 @@ describe("ensureAddress", () => {
it("should throw and error if no address", () => {
expect(() => ensureAddress(mockServer, "http")).toThrow("Server has no address")
})
it("should describe a --socket-fd listener by its fd when node reports no address", () => {
expect(ensureAddress(mockServer, "http", 4)).toBe("fd:4")
})
it("should return the address if it's a string", async () => {
mockServer.address = () => "/path/to/unix.sock"
const address = ensureAddress(mockServer, "http")
+25
View File
@@ -1,5 +1,6 @@
import { Level, logger } from "@coder/logger"
import { promises as fs } from "fs"
import * as os from "os"
import * as path from "path"
import {
UserProvidedArgs,
@@ -575,9 +576,33 @@ describe("parser", () => {
"install-extension": ["extension.number1", "extension.number2"],
})
})
it("should expand ~ in path options from the config file", () => {
const configContents = `
user-data-dir: ~/.config/code-server-data
extensions-dir: ~/extensions
`
expect(parseConfigFile(configContents, "/fake-config-path")).toEqual({
config: "/fake-config-path",
"user-data-dir": path.join(os.homedir(), ".config/code-server-data"),
"extensions-dir": path.join(os.homedir(), "extensions"),
})
})
it("should ignore optional strings set to false", async () => {
expect(parse(["--cert=false"])).toEqual({})
})
it("should ignore booleans set to false", async () => {
expect(parse(["--disable-telemetry=false"])).toEqual({})
})
it("should parse booleans set to false in the config file", () => {
const configContents = `
disable-telemetry: false
disable-update-check: true
`
expect(parseConfigFile(configContents, "/fake-config-path")).toEqual({
config: "/fake-config-path",
"disable-update-check": true,
})
})
it("should use last flag", async () => {
expect(parse(["--port", "8081", "--port", "8082"])).toEqual({
port: 8082,
+135
View File
@@ -68,6 +68,43 @@ describe("http", () => {
host: "localhost:8081",
expected: "does not match",
},
{
origin: "https://example.com",
host: "example.com:443", // Explicit default port.
},
{
origin: "http://example.com",
host: "example.com:80",
},
{
origin: "https://example.com",
host: "example.com:80", // Default port of the other protocol.
expected: "does not match",
},
{
origin: "https://example.com:8080",
host: "example.com", // Missing port is not ignored.
expected: "does not match",
},
{
origin: "https://example.com",
host: "example.com:8443",
expected: "does not match",
},
{
origin: "http://example.com:8080",
host: "other.com",
expected: "does not match",
},
{
origin: "http://[::1]:8080",
host: "[::1]:8080",
},
{
origin: "http://[::1]:8080",
host: "[::1]:8081",
expected: "does not match",
},
{
origin: "localhost:8080",
host: "localhost:8080",
@@ -122,6 +159,104 @@ describe("http", () => {
})
})
})
describe("x-forwarded-port", () => {
;[
{
origin: "https://example.com:8080",
headers: { "x-forwarded-host": "example.com", "x-forwarded-port": "8080" },
},
{
origin: "https://example.com",
headers: { "x-forwarded-host": "example.com", "x-forwarded-port": "443" },
},
{
origin: "http://[::1]:8080",
headers: { "x-forwarded-host": "[::1]", "x-forwarded-port": "8080" },
},
{
origin: "https://example.com:8080",
headers: { "x-forwarded-host": "example.com", "x-forwarded-port": "8080, 443" },
},
{
origin: "https://example.com:8080",
headers: { "x-forwarded-host": "example.com", "x-forwarded-port": "8081" },
expected: "does not match",
},
{
origin: "https://example.com:8080",
headers: { "x-forwarded-host": "example.com:8081", "x-forwarded-port": "8080" },
expected: "does not match", // The port in the host wins.
},
{
origin: "https://example.com:8080",
headers: { "x-forwarded-host": "example.com", "x-forwarded-port": "foo" },
expected: "does not match",
},
{
origin: "https://example.com:8080",
headers: { host: "example.com", "x-forwarded-port": "8080" },
expected: "does not match", // Only used with X-Forwarded-Host.
},
{
origin: "https://example.com:8080",
headers: { forwarded: "host=example.com", "x-forwarded-port": "8080" },
expected: "does not match",
},
].forEach((test) => {
it(`${test.origin} -> ${JSON.stringify(test.headers)}`, () => {
const req = getMockReq({
originalUrl: "localhost:8080",
headers: {
origin: test.origin,
...test.headers,
},
args: {},
})
if (typeof test.expected === "string") {
expect(() => http.authenticateOrigin(req)).toThrow(test.expected)
} else {
expect(() => http.authenticateOrigin(req)).not.toThrow()
}
})
})
it("should not add the port to the host for other uses", () => {
const req = getMockReq({
headers: { "x-forwarded-host": "code-8080.example.com", "x-forwarded-port": "443" },
})
expect(http.getHost(req)).toBe("code-8080.example.com")
})
})
})
describe("getCookieDomain", () => {
it("should use the proxy domain for the cookie", () => {
const proxyDomains = ["{{port}}.example.com", "code-{{port}}.test.com"]
expect(http.getCookieDomain("example.com", proxyDomains)).toBe("example.com")
expect(http.getCookieDomain("8080.example.com", proxyDomains)).toBe("example.com")
expect(http.getCookieDomain("8080.example.com:443", proxyDomains)).toBe("example.com")
expect(http.getCookieDomain("code-8080.test.com", proxyDomains)).toBe("test.com")
})
it("should use the highest level proxy domain", () => {
const proxyDomains = ["{{port}}.test.example.com", "{{port}}.example.com"]
expect(http.getCookieDomain("test.example.com", proxyDomains)).toBe("example.com")
expect(http.getCookieDomain("8080.test.example.com", proxyDomains)).toBe("example.com")
})
it("should use the host when no proxy domain matches", () => {
expect(http.getCookieDomain("code.other.com", ["{{port}}.example.com"])).toBe("code.other.com")
expect(http.getCookieDomain("8080.example.com", ["{{port}}.{{host}}"])).toBe("8080.example.com")
expect(http.getCookieDomain("8080.example.com", [])).toBe("8080.example.com")
})
it("should not set a domain for localhost or IP addresses", () => {
const proxyDomains = ["{{port}}.example.com"]
expect(http.getCookieDomain("localhost:8080", proxyDomains)).toBe(undefined)
expect(http.getCookieDomain("127.0.0.1:8080", proxyDomains)).toBe(undefined)
expect(http.getCookieDomain("8080.dev.localhost", proxyDomains)).toBe(undefined)
})
})
describe("constructRedirectPath", () => {
+11
View File
@@ -75,6 +75,17 @@ describe("login", () => {
expect(htmlContent).toContain("Missing password")
})
it("should reject compressed request bodies", async () => {
for (const encoding of ["br", "gzip", "deflate"]) {
const resp = await codeServer().fetch("/login", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded", "Content-Encoding": encoding },
body: "password=test",
})
expect(resp.status).toBe(415)
}
})
it("should return HTML with 'Incorrect password' message", async () => {
const params = new URLSearchParams()
params.append("password", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+16
View File
@@ -1,5 +1,6 @@
import * as cp from "child_process"
import { promises as fs } from "fs"
import * as os from "os"
import * as path from "path"
import { generateUuid } from "../../../src/common/util"
import { tmpdir } from "../../../src/node/constants"
@@ -623,3 +624,18 @@ describe("splitOnFirstEquals", () => {
})
})
})
describe("expandTilde", () => {
it("should expand a bare ~", () => {
expect(util.expandTilde("~")).toBe(os.homedir())
})
it("should expand a leading ~/", () => {
expect(util.expandTilde("~/.config/code-server")).toBe(path.join(os.homedir(), ".config/code-server"))
})
it("should leave other paths alone", () => {
expect(util.expandTilde("/tmp/~/data")).toBe("/tmp/~/data")
expect(util.expandTilde("relative/~")).toBe("relative/~")
expect(util.expandTilde("~user/data")).toBe("~user/data")
expect(util.expandTilde("")).toBe("")
})
})