Skip to content

fix: resolve extension resources for the target platform VS Code asks for - #2183

Open
netomi wants to merge 2 commits into
mainfrom
fix/unpkg-target-platform
Open

netomi wants to merge 2 commits into
mainfrom
fix/unpkg-target-platform

Conversation

@netomi

@netomi netomi commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Stacked on #2181 — base is fix/target-platform-validation, so this diff shows only the unpkg change. #2181 supplies the @Validated/@Pattern wiring on VSCodeAPI that this reuses; without it the two PRs would both add those lines and conflict. Merge that first, or say so and I will retarget this at main.

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>, web for a web extension (microsoft/vscode#180525, microsoft/vscode#182072). The unpkg endpoint never learned to read it:

/vscode/unpkg/BroadcomMFD/hlasm-language-support/1.23.0/extension/package.json       → 200
/vscode/unpkg/BroadcomMFD/hlasm-language-support/1.23.0+web/extension/package.json   → 404
/vscode/unpkg/.../1.23.0%2Bweb/extension/package.json                                → 404

That extension really does publish 1.23.0 for both universal and web, 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 null down 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) and browseExtensionPackage(...) both have the parameter, and browseExtensionPackage was 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:

var separator = version.lastIndexOf('+');
if (separator >= 0 && separator + 1 < version.length()) {
    var candidate = version.substring(separator + 1);
    if (TargetPlatform.isValid(candidate)) { ... }
}

A version may legitimately carry semver build metadata — 1.2.3+build.5 — which SemanticVersion.VERSION_PATH_PARAM_REGEX explicitly permits. "Text after the last +" would read build.5 as 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. browseExtensionPackage built 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. universal is left off, since it is the absence of a target rather than a target.

Upstream forwarding. UpstreamVSCodeService passes 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

  • testBrowseTopDirForATargetPlatformInTheVersion0.16.6+web resolves, and the returned listing URLs keep +web
  • testBrowseTopDirForATargetPlatformParameter — the same via ?target=web
  • testBrowseKeepsSemverBuildMetadataInTheVersion1.2.3+build.5 is looked up whole, with a null target, verified on the repository call

The 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); spotlessCheck and format.sh clean.

On #758

It cannot be rebased: it is written against ResponseEntity<byte[]> and a codebase with no WebResourceService, across 8 files and 2½ years of drift. Its repository addition is also redundant now — findFileByType already 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.

@netomi
netomi force-pushed the fix/unpkg-target-platform branch from 8f987a5 to c3a4a4c Compare September 9, 2026 12:27
Base automatically changed from fix/target-platform-validation to main September 9, 2026 12:27
@netomi
netomi requested a lite review from Copilot September 10, 2026 19:33
netomi and others added 2 commits September 10, 2026 21:34
… 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>
@netomi
netomi force-pushed the fix/unpkg-target-platform branch from c3a4a4c to 8273ae9 Compare September 10, 2026 19:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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 targetPlatform allowable 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_REGEX intentionally allows an empty string, but on this endpoint an empty ?targetPlatform= overrides the defaultValue and then propagates as "" into repository lookups (via LocalVSCodeService), 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 against NAMES_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_REGEX allows an empty string, but on this endpoint an empty ?targetPlatform= overrides the defaultValue and then propagates as "" into repository lookups (via LocalVSCodeService), 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 against NAMES_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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants