Conversation
8f987a5 to
c3a4a4c
Compare
… for VS Code encodes the target platform into the version segment when it resolves an extension's resources - `<version>+<target>`, `web` for a web extension (microsoft/vscode#180525). The unpkg endpoint never learned to read it, so such a request 404s: /vscode/unpkg/BroadcomMFD/hlasm-language-support/1.23.0/... 200 /vscode/unpkg/BroadcomMFD/hlasm-language-support/1.23.0+web/... 404 That extension publishes 1.23.0 for both `universal` and `web`, and the second URL is the one a web VS Code sends, so its resources do not load at all. Without a target the endpoint also could not have distinguished them: it passed null down to the download lookup, which then matched whichever version came first. The plumbing already took a target platform - WebResourceService's getExtensionDownload and browseExtensionPackage both have the parameter, and browseExtensionPackage used it only to compose an error message - so this is mostly a matter of resolving one at the edge and passing it along. The target comes from `?target=`, or from a `+<target>` suffix on the version when that is absent. The suffix is only taken when it names a platform: a version may legitimately carry semver build metadata (`1.2.3+build.5`), which SemanticVersion's own regex permits, and that belongs to the version. Directory listings re-append the target to the version in the URLs they hand back. Without that, walking into a subdirectory silently drops to whichever version matches first, which is the same bug one level down. UpstreamVSCodeService forwards the target as a query parameter, so a mirror passes it on rather than resolving it against its own registry. Reimplements #758, which reported and fixed this in June 2023 - credit to @amvanbaren. That PR cannot be rebased: it is written against ResponseEntity<byte[]> and a codebase without WebResourceService, and the repository method it adds is now redundant since findFileByType already resolves a target-specific version. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
c3a4a4c to
8273ae9
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
VSCodeAPI validates targetPlatform with a regex that allows empty strings on endpoints with a default value, which can allow ?targetPlatform= to pass validation but then incorrectly 404 due to downstream lookups using "" as the platform.
Pull request overview
This PR updates the VS Code compatibility layer so /vscode/unpkg/** resolves and browses extension resources for the target platform VS Code requests (either via ?target= or a +<target> suffix on the version), and adds request-side validation for targetPlatform where it’s supplied as a query/body parameter.
Changes:
- Parse target platform for unpkg browsing from
?target=or a validated+<target>version suffix and pass it through to the browse services. - Preserve the target platform when generating directory listing URLs so subsequent navigation doesn’t “fall back” to an arbitrary matching version.
- Enforce
targetPlatformallowable values via Bean Validation annotations across registry and VS Code adapter endpoints, with tests covering rejections/acceptance.
File summaries
| File | Description |
|---|---|
| server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java | Adds tests asserting unknown targetPlatform query/body values are rejected. |
| server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java | Adds tests for unpkg browse behavior with +web and ?target=web, plus semver build-metadata guard. |
| server/src/test/java/org/eclipse/openvsx/adapter/UpstreamVSCodeServiceTest.java | Updates tests for the new browse(..., targetPlatform, ...) signature. |
| server/src/main/java/org/eclipse/openvsx/util/TargetPlatform.java | Introduces NAMES_PARAM_REGEX for validating query/body params (allowing empty/absent). |
| server/src/main/java/org/eclipse/openvsx/RegistryAPI.java | Adds @Pattern validation to targetPlatform query params and @Valid for POST body validation. |
| server/src/main/java/org/eclipse/openvsx/json/QueryParamJson.java | Adds @Pattern validation for request-body targetPlatform. |
| server/src/main/java/org/eclipse/openvsx/adapter/WebResourceService.java | Re-appends target to directory listing URLs (except universal). |
| server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAPI.java | Adds method parameter validation; resolves unpkg target from query or version suffix and passes it downstream. |
| server/src/main/java/org/eclipse/openvsx/adapter/UpstreamVSCodeService.java | Forwards browse target upstream via ?target=. |
| server/src/main/java/org/eclipse/openvsx/adapter/LocalVSCodeService.java | Threads targetPlatform through unpkg browsing to ensure correct download lookup and browsing. |
| server/src/main/java/org/eclipse/openvsx/adapter/IVSCodeService.java | Extends browse contract to include targetPlatform. |
Review details
Suppressed comments (2)
server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAPI.java:228
TargetPlatform.NAMES_PARAM_REGEXintentionally allows an empty string, but on this endpoint an empty?targetPlatform=overrides thedefaultValueand then propagates as""into repository lookups (viaLocalVSCodeService), which can lead to an incorrect 404. Since this endpoint already has a default (universal), it’s safer to reject an explicit empty value by validating againstNAMES_PATH_PARAM_REGEX(non-empty) instead.
This issue also appears on line 350 of the same file.
@RequestParam(defaultValue = TargetPlatform.NAME_UNIVERSAL)
@Pattern(
regexp = TargetPlatform.NAMES_PARAM_REGEX,
message = "parameter must be a supported target platform"
)
server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAPI.java:354
TargetPlatform.NAMES_PARAM_REGEXallows an empty string, but on this endpoint an empty?targetPlatform=overrides thedefaultValueand then propagates as""into repository lookups (viaLocalVSCodeService), which can lead to an incorrect 404. Since this endpoint already has a default (universal), it’s safer to reject an explicit empty value by validating againstNAMES_PATH_PARAM_REGEX(non-empty) instead.
@RequestParam(defaultValue = TargetPlatform.NAME_UNIVERSAL)
@Pattern(
regexp = TargetPlatform.NAMES_PARAM_REGEX,
message = "parameter must be a supported target platform"
)
- Files reviewed: 7/7 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Reimplements #758 (June 2023) on current
main. Fixes #744.What is broken
VS Code encodes the target platform into the version segment when it resolves an extension's resources —
<version>+<target>,webfor a web extension (microsoft/vscode#180525, microsoft/vscode#182072). The unpkg endpoint never learned to read it:That extension really does publish
1.23.0for bothuniversalandweb, and the second URL is what a web VS Code sends — so its resources do not load at all in vscode.dev or github.dev.Even without the suffix the endpoint could not have told the two apart: it passed
nulldown to the download lookup, which matched whichever version came first.The change
Smaller than #758, because the plumbing already takes a target platform —
WebResourceService.getExtensionDownload(namespace, extension, targetPlatform, version)andbrowseExtensionPackage(...)both have the parameter, andbrowseExtensionPackagewas using it only to compose an error message. So this is mostly a matter of resolving a target at the edge and passing it down.Where the target comes from.
?target=if given, otherwise a+<target>suffix on the version. The suffix is taken only when it names a platform:A version may legitimately carry semver build metadata —
1.2.3+build.5— whichSemanticVersion.VERSION_PATH_PARAM_REGEXexplicitly permits. "Text after the last+" would readbuild.5as a target, so the validation is what makes the heuristic safe rather than merely convenient. There is a test for it.Directory listings re-append the target.
browseExtensionPackagebuilt its child URLs from the bare version, so without this, walking into a subdirectory silently drops back to whichever version matches first — the same bug one level down.universalis left off, since it is the absence of a target rather than a target.Upstream forwarding.
UpstreamVSCodeServicepasses the target on as?target=, so a mirror forwards it rather than resolving it against its own registry. Query-parameter form rather than the+suffix because both are accepted and it needs no escaping decisions.Tests
testBrowseTopDirForATargetPlatformInTheVersion—0.16.6+webresolves, and the returned listing URLs keep+webtestBrowseTopDirForATargetPlatformParameter— the same via?target=webtestBrowseKeepsSemverBuildMetadataInTheVersion—1.2.3+build.5is looked up whole, with a null target, verified on the repository callThe first two fail without the change. The third passes either way, which is right — it guards against over-eager parsing, so it should hold both before and after.
Full server suite green (1214 tests);
spotlessCheckandformat.shclean.On #758
It cannot be rebased: it is written against
ResponseEntity<byte[]>and a codebase with noWebResourceService, across 8 files and 2½ years of drift. Its repository addition is also redundant now —findFileByTypealready resolves a target-specific version. The design is amvanbaren's and this follows it, including the two details easiest to miss: validating the suffix before stripping it, and putting the target back on the listing URLs.Suggest closing #758 in favour of this.