-
Notifications
You must be signed in to change notification settings - Fork 5k
install: fix yarn.lock migration panic on tarball URLs with "/-/" right after the host #38803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -159,6 +159,18 @@ | |
| strings::without_trailing_slash(path) | ||
| } | ||
|
|
||
| /// `https://registry.npmjs.org/@scope/name/-/name-1.0.0.tgz` -> `@scope/name` | ||
| pub(crate) fn get_package_name_from_default_registry_url(url: &[u8]) -> Option<&[u8]> { | ||
| let host_and_path = url | ||
| .strip_prefix(b"https://") | ||
| .or_else(|| url.strip_prefix(b"http://"))?; | ||
| let path = host_and_path | ||
| .strip_prefix(b"registry.npmjs.org/") | ||
| .or_else(|| host_and_path.strip_prefix(b"registry.yarnpkg.com/"))?; | ||
| let name = &path[..strings::index_of(path, b"/-/")?]; | ||
| (!name.is_empty()).then_some(name) | ||
|
Check warning on line 171 in src/install/yarn.rs
|
||
| } | ||
|
Check failure on line 172 in src/install/yarn.rs
|
||
|
Comment on lines
+162
to
+172
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 The PR description and the 06:53/09:52 timeline comments describe a rework at 12002d9 (modifying Extended reasoning...What the mismatch isThe PR description (updated for 12002d9 per the 01:39 status comment) and the author's 06:53 timeline comment both describe a specific reworked shape:
The 09:52 comment even refers to "the None of this is in ef0d36c — the commit CodeRabbit and the robobun build comment both name as the current head. Step-by-step check against ef0d36c(1) let path = &url[..strings::index_of(url, b"/-/")?];
let (prefix, name) = strings::rsplit_once_char(path, b'/')?;For (2) No (3) The description says the (4) The test has exactly nine rows, all direct URL dependencies in the Why this mattersThis is not a case of an imprecise description. The 06:53 comment is the author's own record of having reworked the implementation at 12002d9, and the description was updated to match. The 09:52 comment (posted after ef0d36c was already building at 8:52 per the robobun status) references a test row that doesn't exist. The commit at HEAD has the earlier shape the 06:53 comment says was replaced. This is the signature of a rebase/squash that dropped the reworked hunks. If merged as-is:
How to fixOne of:
|
||
|
|
||
| pub(crate) fn parse_git_url( | ||
| _yarn_lock: &YarnLock<'a>, | ||
| version: &'a [u8], | ||
|
|
@@ -947,23 +959,10 @@ | |
| || Entry::is_remote_tarball(resolved) | ||
| || resolved.ends_with(b".tgz") | ||
| { | ||
| // https://registry.npmjs.org/package/-/package-version.tgz | ||
| if strings::index_of(resolved, b"registry.npmjs.org/").is_some() | ||
| || strings::index_of(resolved, b"registry.yarnpkg.com/").is_some() | ||
| if let Some(name) = Entry::get_package_name_from_default_registry_url(resolved) | ||
| { | ||
| if let Some(separator_idx) = strings::index_of(resolved, b"/-/") { | ||
| if let Some(registry_idx) = strings::index_of(resolved, b"registry.") { | ||
| let after_registry = &resolved[registry_idx..]; | ||
| if let Some(domain_slash) = strings::index_of(after_registry, b"/") | ||
| { | ||
| let package_start = registry_idx + domain_slash + 1; | ||
| let extracted_name = &resolved[package_start..separator_idx]; | ||
| break 'blk extracted_name; | ||
| } | ||
| } | ||
| } | ||
| break 'blk name; | ||
| } | ||
| break 'blk base_name; | ||
| } | ||
| } | ||
| break 'blk base_name; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 The scoped sibling of the
dash-packagerow isn't handled: forhttps://registry.npmjs.org/@scope/-/-/--0.0.1.tgz, the host-stripped path is@scope/-/-/--0.0.1.tgzandindex_of(path, b"/-/")matches at byte 6 (inside the name), so the extracted name is@scopeinstead of@scope/-. Fix: whenpathstarts with@, skip past the first/before searching for/-/. Extreme edge case (npm permits@scope/-by the same rule that permits the unscoped-package this PR already tests), no crash — just noting it since the PR explicitly enumerates this input class.Extended reasoning...
What the bug is
Entry::get_package_name_from_default_registry_url(src/install/yarn.rs:170) strips the scheme and host, then takes everything before the first/-/in the remaining path as the package name:For a URL dependency on a scoped package literally named
-, the tarball URL ishttps://registry.npmjs.org/@scope/-/-/--0.0.1.tgz. After strippinghttps://registry.npmjs.org/,path = "@scope/-/-/--0.0.1.tgz". The bytes/-/first appear at index 6 — the slash between@scopeand its bare-name segment-, followed by that-, followed by the actual separator's leading slash — soname = path[..6] = "@scope", a bare scope with no package part.Step-by-step trace
path = "@scope/-/-/--0.0.1.tgz":@scope/-/-/--0.0.1.tgzstrings::index_of(path, b"/-/")scans left-to-right and matches bytes 6–8 (/,-,/).name = &path[..6] = b"@scope".&path[..8] = b"@scope/-".The unscoped
-package (thedash-packagetest row) works because its path is-/-/--0.0.1.tgz, where the first/-/at index 1 correctly yields-— there is no scope segment for the search to land inside.Why nothing else prevents it
The caller at yarn.rs:962 uses the returned name directly (
break 'blk name) with no further validation, so@scopebecomes the package's recorded name inbun.lock("@scope@https://…").get_package_name_from_resolved_url(the sibling parser used for aliases) has the identical limitation for this input — it also searches for the first/-/before splitting on the last slash — so nothing downstream corrects it.Why it's the same input class this PR enumerates
The PR description explicitly calls out "the package literally named
-" as "the one real package whose URL starts with/-/", adds it as thedash-packagetest row, and hardens the parser against it. npm's naming rules permit@scope/-by exactly the same rule that permits unscoped-(a name may not start with.or_; a lone-is fine). Per REVIEW.md's "Cover the variant matrix, not just the repro" and "Fix the whole class in the same PR", the scoped variant is the direct sibling of a case the PR chose to enumerate.Impact
No crash — the slice bounds are ordered, so the PR's primary goal (never panic) is preserved. The migrated
bun.lockrecords the entry as@scope@<url>instead of@scope/-@<url>. For aRemoteTarballresolution the name is largely a display label (installation fetches the URL directly), so the practical fallout is a wrong name in the lockfile rather than a failed install. Reachable only via a URL dependency on such a package or a hand-craftedyarn.lock— the same adversarial-input surface the rest of this PR hardens.How to fix
When
pathstarts with@, skip past the first/(the scope/name boundary) before searching for/-/, then include the scope prefix in the returned slice:and add a
"scoped-dash-package": "https://registry.npmjs.org/@scope/-/-/--0.0.1.tgz"row alongsidedash-packagein the new test, expecting"@scope/-@…".