Skip to content

feat: smart-link resolve, direct downloads, and Bandcamp album track pick - #90

Merged
D3SOX merged 6 commits into
masterfrom
direct-download-gate-resolve
Aug 1, 2026
Merged

feat: smart-link resolve, direct downloads, and Bandcamp album track pick#90
D3SOX merged 6 commits into
masterfrom
direct-download-gate-resolve

Conversation

@D3SOX

@D3SOX D3SOX commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Summary

  • Follow unknown SoundCloud purchase/description links (e.g. lnk.to) via HTTP redirects and HTML scanning to known gates (Hypeddit, Droploud, etc.)
  • Accept direct file URLs (Dropbox dl=1, Google Drive, raw audio/archive links) as a browserless download path when pasting a gate URL
  • When a Bandcamp album URL cannot be auto-matched to the SoundCloud title, let the user pick a track (CLI select + Web UI list) instead of failing
  • Wire the new direct provider through CLI, server, and Web UI copy

Test plan

  • Paste a Dropbox share link with dl=1 as the manual gate URL and confirm the file lands in ./downloads
  • Use a SoundCloud track whose purchase_url is an lnk.to/smart-link that lands on Hypeddit and confirm auto-resolution
  • Use a Bandcamp album link that does not fuzzy-match the SoundCloud title and confirm CLI/Web UI offer a track picker, then download the chosen track
  • Confirm Bandcamp track URLs / Hypeddit / Droploud paths are unchanged
  • bun test ./src/utils.test.ts and bun run lint

Summary by CodeRabbit

  • New Features

    • Added direct downloads from Dropbox, Google Drive, and common audio/archive links.
    • Downloads follow redirects, report progress, enforce size limits, and save files automatically.
    • Added browserless direct-download flows and broader gate detection.
    • Added Bandcamp album track selection when multiple matches are found.
  • Documentation

    • Documented direct file-link downloads and supported URL types.
  • Bug Fixes

    • Improved browser launch handling, gate resolution, URL validation, and outbound safety checks.

Follow unknown purchase/description links to known gates, and allow pasting Dropbox/Drive/raw file URLs as a browserless download path.
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3ed5638e-94b7-46a1-b878-ef30407f35fd

📥 Commits

Reviewing files that changed from the base of the PR and between 7e10bc8 and 2b81714.

📒 Files selected for processing (3)
  • src/directLinkRules.ts
  • src/safeOutboundUrl.ts
  • src/utils.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/directLinkRules.ts
  • src/safeOutboundUrl.ts
  • src/utils.test.ts

📝 Walkthrough

Walkthrough

The change adds direct file links as gate sources and interactive Bandcamp album-track selection. It updates URL resolution, download routing, job state, browserless handling, progress reporting, the web UI, tests, and documentation.

Changes

Gate and download flows

Layer / File(s) Summary
Gate detection and asynchronous resolution
src/directLinkRules.ts, src/safeOutboundUrl.ts, src/utils.ts, src/utils.test.ts
Gate utilities detect and normalize direct links, validate outbound destinations, scan HTML and meta-refresh targets, follow unresolved HTTP(S) links, and extract resolved candidates.
Direct file downloader
src/directDownload.ts
DirectDownloader fetches normalized URLs, validates responses and redirects, derives sanitized filenames, reports progress, and saves files under ./downloads.
Bandcamp album-track selection
src/types.ts, src/ytdlp.ts, src/jobStore.ts, src/server.ts, webui/src/components/App.tsx, webui/src/components/App.css, src/jobStore.test.ts
Bandcamp album matching exposes track choices, pauses jobs for selection, validates the selected URL, and resumes or cancels the download.
Application routing and documentation
src/index.ts, src/server.ts, src/soundcloud.ts, src/browserLaunch.ts, README.md, webui/src/components/App.tsx
CLI and server flows use asynchronous gate resolution. Direct providers use browserless downloads. Browser arguments merge defaults with user arguments. README and UI text document direct links.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GateInput
  participant GateResolver
  participant DownloadServer
  participant DirectDownloader
  participant DownloadsDirectory
  GateInput->>GateResolver: Submit gate URL
  GateResolver->>DownloadServer: Return resolved provider and URL
  DownloadServer->>DirectDownloader: Download direct provider URL
  DirectDownloader->>DownloadsDirectory: Write downloaded file
  DirectDownloader-->>DownloadServer: Return filename and progress
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the pull request's three primary changes: smart-link resolution, direct downloads, and Bandcamp album track selection.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch direct-download-gate-resolve

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
src/soundcloud.ts (1)

525-538: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Nested ternary for provider labels is growing hard to read.

The provider-to-label mapping is now six levels of nested ternary. Each new GateProvider value adds another level. Replace it with a lookup table for readability and to make future additions (new providers) a one-line change.

♻️ Proposed refactor
+const GATE_PROVIDER_LABELS: Record<GateProvider, string> = {
+	droploud: 'Droploud',
+	gaterush: 'GateRush',
+	downloadgater: 'DownloadGater',
+	direct: 'direct download',
+	bandcamp: 'Bandcamp',
+	soundcloud: 'SoundCloud',
+	hypeddit: 'Hypeddit',
+};
+
 	async getGateURL(track: SoundcloudTrack) {
 		const gate = await extractAndResolveGateUrl(track);
 		if (!gate) {
 			return null;
 		}
-		const providerLabel =
-			gate.provider === 'droploud'
-				? 'Droploud'
-				: gate.provider === 'gaterush'
-					? 'GateRush'
-					: gate.provider === 'downloadgater'
-						? 'DownloadGater'
-						: gate.provider === 'direct'
-							? 'direct download'
-							: gate.provider === 'bandcamp'
-								? 'Bandcamp'
-								: gate.provider === 'soundcloud'
-									? 'SoundCloud'
-									: 'Hypeddit';
+		const providerLabel = GATE_PROVIDER_LABELS[gate.provider];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/soundcloud.ts` around lines 525 - 538, Replace the nested ternary
assigned to providerLabel in the surrounding provider-handling flow with a
provider-to-label lookup table keyed by gate.provider. Preserve all existing
labels and the fallback for unknown providers, while making future provider
additions a single mapping entry.
src/utils.test.ts (1)

1-13: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Missing test coverage for resolveUnknownGateUrl.

This file adds tests for resolveGateProviderUrl (direct-URL matching) and findKnownGateInHtml, but resolveUnknownGateUrl — the function that follows redirects and fetches HTML for unrecognized URLs — has no test coverage here. This is the function with the highest risk (network fetch, redirect following, HTML scanning, SoundCloud short-circuit). Add tests that mock fetch to cover: a redirect landing on a known gate, a page whose HTML embeds a known gate, a SoundCloud final URL (should return null), and a network failure (should return null).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils.test.ts` around lines 1 - 13, Add tests in src/utils.test.ts for
resolveUnknownGateUrl, mocking fetch to cover redirects to a known gate, HTML
containing a known gate, SoundCloud final URLs returning null, and network
failures returning null; import the function under test and preserve the
existing test conventions and cleanup behavior.
src/index.ts (1)

81-90: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Duplicate network resolution for the same manually entered URL.

resolveGateUrlOrFollow(value) runs once inside the validate callback (Line 83) and again immediately after the prompt resolves (Line 90), on the identical raw input. For an unrecognized URL, both calls trigger a full redirect-following fetch plus HTML scan (resolveUnknownGateUrl), doubling latency and outbound requests for the same input. Cache the resolution from validate and reuse it instead of calling resolveGateUrlOrFollow a second time.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/index.ts` around lines 81 - 90, Cache the result of
resolveGateUrlOrFollow inside the prompt validate callback and reuse that cached
resolution after the prompt completes, removing the second call for the same
gateUrl. Preserve the existing validation rejection for missing results or
soundcloud providers, and ensure the cached value is updated for each validation
attempt.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/directDownload.ts`:
- Around line 77-119: Update downloadAudio to pass an AbortSignal.timeout(...)
signal to the fetch call, and enforce a maximum download size before or while
obtaining response.arrayBuffer(). Use the response Content-Length when available
and reject oversized files before buffering; preserve the existing successful
download and filename handling.
- Around line 60-119: Sanitize the Content-Disposition filename before
constructing the output path in DirectDownloader.downloadAudio. Apply basename()
or the existing sanitizeFilenamePart utility to fromHeader, while preserving the
current URL-derived fallback and default filename behavior, so
join('./downloads', filename) cannot escape the downloads directory.

In `@src/utils.ts`:
- Around line 395-435: Extract the shared direct-download host checks, extension
checks, and normalization logic used by matchDirectDownloadUrl,
isDirectDownloadUrl, and normalizeDirectDownloadUrl into a dependency-free
module such as directLinkRules.ts. Update both utils.ts and directDownload.ts to
import and reuse those shared symbols, removing the duplicated inline rules
while preserving existing URL matching and normalization behavior.
- Around line 462-500: Update resolveUnknownGateUrl to validate the initial
trimmed URL and each redirect destination before any network request, rejecting
non-public or otherwise unsafe addresses while preserving known-gate and
SoundCloud checks. Configure the fetch flow to inspect redirects and apply the
same validation to every destination before following it, so neither
extractAndResolveGateUrl nor the hypeddit route can trigger blind SSRF.

In `@webui/src/components/App.tsx`:
- Line 948: Update the placeholder in the relevant App component input to show
the Dropbox example as a complete valid URL, using the https:// scheme and ?dl=1
as the first query separator instead of &dl=1; leave the other supported URL
examples unchanged.

---

Nitpick comments:
In `@src/index.ts`:
- Around line 81-90: Cache the result of resolveGateUrlOrFollow inside the
prompt validate callback and reuse that cached resolution after the prompt
completes, removing the second call for the same gateUrl. Preserve the existing
validation rejection for missing results or soundcloud providers, and ensure the
cached value is updated for each validation attempt.

In `@src/soundcloud.ts`:
- Around line 525-538: Replace the nested ternary assigned to providerLabel in
the surrounding provider-handling flow with a provider-to-label lookup table
keyed by gate.provider. Preserve all existing labels and the fallback for
unknown providers, while making future provider additions a single mapping
entry.

In `@src/utils.test.ts`:
- Around line 1-13: Add tests in src/utils.test.ts for resolveUnknownGateUrl,
mocking fetch to cover redirects to a known gate, HTML containing a known gate,
SoundCloud final URLs returning null, and network failures returning null;
import the function under test and preserve the existing test conventions and
cleanup behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5d6fcea4-b738-40e0-807c-46f8e971f3a0

📥 Commits

Reviewing files that changed from the base of the PR and between e657b11 and c8758e7.

📒 Files selected for processing (9)
  • README.md
  • src/browserLaunch.ts
  • src/directDownload.ts
  • src/index.ts
  • src/server.ts
  • src/soundcloud.ts
  • src/utils.test.ts
  • src/utils.ts
  • webui/src/components/App.tsx

Comment thread src/directDownload.ts
Comment thread src/directDownload.ts
Comment thread src/utils.ts
Comment thread src/utils.ts
Comment thread webui/src/components/App.tsx Outdated
@D3SOX D3SOX changed the title feat: resolve smart-links and support direct download URLs feat: smart-link resolve, direct downloads, and Bandcamp album track pick Jul 31, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
webui/src/components/App.tsx (1)

990-990: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix the Dropbox query separator in the placeholder.

The example does not show an earlier query parameter, so use ?dl=1 instead of &dl=1. Include the https:// scheme so the example is a valid URL. This was already raised on a previous commit of this PR and remains unfixed.

Proposed fix
-									placeholder="https://hypeddit.com/... / droploud.com/gate/... / gaterush.me/... / downloadgater.com/g/... / artist.bandcamp.com/track/... / dropbox.com/...&dl=1"
+									placeholder="https://hypeddit.com/... / droploud.com/gate/... / gaterush.me/... / downloadgater.com/g/... / artist.bandcamp.com/track/... / https://www.dropbox.com/...?dl=1"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webui/src/components/App.tsx` at line 990, Update the placeholder in the App
component to show the Dropbox example as a valid URL with the https:// scheme
and ?dl=1 query separator, replacing the current &dl=1 form while leaving the
other URL examples unchanged.
🧹 Nitpick comments (1)
src/jobStore.ts (1)

161-181: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add unit tests for the Bandcamp selection pause/resume/cancel interactions.

The resolver map introduces new concurrency-sensitive behavior: superseding an existing waiter, resolving on cancel, and resolving on delete. Add tests that cover: a pending wait resolved by resolveBandcampTrackSelection, a pending wait resolved by cancel(), a pending wait resolved by delete(), and calling waitForBandcampTrackSelection twice for the same job.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/jobStore.ts` around lines 161 - 181, Add unit tests covering the Bandcamp
resolver lifecycle through waitForBandcampTrackSelection and
resolveBandcampTrackSelection: verify a pending wait receives its track URL,
cancel() resolves it with null, delete() resolves it with null, and a second
wait for the same job resolves the first waiter with null while leaving the
replacement waiter pending.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@webui/src/components/App.tsx`:
- Line 990: Update the placeholder in the App component to show the Dropbox
example as a valid URL with the https:// scheme and ?dl=1 query separator,
replacing the current &dl=1 form while leaving the other URL examples unchanged.

---

Nitpick comments:
In `@src/jobStore.ts`:
- Around line 161-181: Add unit tests covering the Bandcamp resolver lifecycle
through waitForBandcampTrackSelection and resolveBandcampTrackSelection: verify
a pending wait receives its track URL, cancel() resolves it with null, delete()
resolves it with null, and a second wait for the same job resolves the first
waiter with null while leaving the replacement waiter pending.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a164c4e4-4992-442c-8331-7d996e4e96c0

📥 Commits

Reviewing files that changed from the base of the PR and between c8758e7 and c4b878d.

📒 Files selected for processing (8)
  • README.md
  • src/index.ts
  • src/jobStore.ts
  • src/server.ts
  • src/types.ts
  • src/ytdlp.ts
  • webui/src/components/App.css
  • webui/src/components/App.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • README.md
  • src/index.ts

Sanitize download filenames, bound fetch size/timeout, share link rules, block private resolve hops, rewrite Dropbox dl=0→dl=1, and test Bandcamp selection lifecycle.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/directDownload.ts (1)

64-94: 🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift

Bind SSRF validation to the connection used for every request.

  • In src/directDownload.ts#L73-L87, replace automatic redirects with a manual loop. Validate each Location URL before fetching it.
  • In src/safeOutboundUrl.ts#L52-L82, do not validate one DNS result and then call hostname-based Bun fetch, which resolves separately. Use a client or connection path that uses the validated address while preserving the original hostname for TLS/SNI. Apply this to src/utils.ts#L468-L483 as well; its manual redirect loop still has the DNS TOCTOU gap. Do not rely on an unsupported Bun custom dispatcher or connector.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/directDownload.ts` around lines 64 - 94, Bind SSRF validation to the
actual connection for every request: in src/directDownload.ts lines 64-94,
replace fetch’s automatic redirects with a manual redirect loop that validates
each Location URL before the next fetch; in src/safeOutboundUrl.ts lines 52-82,
change the validated request path to connect through the validated DNS address
while preserving the original hostname for TLS/SNI, without using an unsupported
Bun dispatcher or connector; also apply the same DNS-TOCTOU fix to the manual
redirect loop in src/utils.ts lines 468-483.
🧹 Nitpick comments (2)
src/directLinkRules.ts (2)

34-45: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

docs.google.com is a "known" host but has no normalization path.

isKnownDirectDownloadHost (lines 9-17) recognizes docs.google.com as a direct-download host, but normalizeDirectDownloadParsedUrl only rewrites dropbox.com and drive.google.com URLs. A plain Google Docs share link (e.g. .../document/d/ID/edit) passed through unchanged will resolve to an HTML edit page, which DirectDownloader correctly rejects with an HTML error — so the failure is graceful, but the feature silently doesn't do what the host allow-list implies it supports.

Add an export-URL rewrite for docs.google.com (mirroring the Drive uc?export=download pattern) or drop it from isKnownDirectDownloadHost until normalization exists.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/directLinkRules.ts` around lines 34 - 45, Update
normalizeDirectDownloadParsedUrl to handle docs.google.com URLs consistently
with isKnownDirectDownloadHost: rewrite Google Docs document links containing a
document ID to the appropriate export-download URL using the existing Drive
normalization pattern, or remove docs.google.com from the allow-list if
normalization is not being added. Preserve existing Dropbox and Drive behavior.

20-28: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Tighten the dl/raw query-param heuristic.

urlLooksLikeDirectDownload returns true for any https?:// URL that has a dl or raw query parameter (lines 25-26), regardless of host. Many unrelated sites use raw or dl for other purposes (CDN cache-busting, image sizing, tracking). When such a URL is actually a legitimate gate page, this heuristic misclassifies it as direct, bypasses resolveUnknownGateUrl's redirect/HTML-scanning logic, and forces the user through a hard failure in DirectDownloader ("returned HTML instead of a file") instead of correctly finding the real gate.

Consider requiring the extension match or a known host in combination with the dl/raw signal, rather than accepting either alone on an arbitrary host.

♻️ Suggested tightening
 export function urlLooksLikeDirectDownload(url: URL): boolean {
 	if (!/^https?:$/i.test(url.protocol)) return false;
 	if (isKnownDirectDownloadHost(url.hostname)) return true;
 	if (AUDIO_OR_ARCHIVE_EXT_RE.test(url.pathname)) return true;
-	// Dropbox preview links often use dl=0; treat any dl= as a download intent.
-	if (url.searchParams.has('dl')) return true;
-	if (url.searchParams.has('raw')) return true;
+	// Dropbox preview links often use dl=0; only treat dl=/raw= as a download
+	// intent when the path also looks like a file (avoids false positives on
+	// unrelated HTML pages that happen to use these common query names).
+	if (
+		(url.searchParams.has('dl') || url.searchParams.has('raw')) &&
+		/\.[a-z0-9]{2,4}$/i.test(url.pathname)
+	) {
+		return true;
+	}
 	return false;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/directLinkRules.ts` around lines 20 - 28, Update
urlLooksLikeDirectDownload so dl/raw query parameters only qualify a URL when
combined with an AUDIO_OR_ARCHIVE_EXT_RE pathname match or a known
direct-download host. Preserve the existing protocol, host, and extension
checks, but remove the unconditional dl/raw acceptance for arbitrary hosts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/directDownload.ts`:
- Around line 96-122: Update the download flow around response.arrayBuffer() to
stream response.body through a byte-counting limit enforced during writes,
aborting when the accumulated bytes exceed MAX_DOWNLOAD_BYTES. Preserve the
existing filename selection and size-error behavior, and avoid relying on
content-length or post-buffer validation as the primary limit.

---

Outside diff comments:
In `@src/directDownload.ts`:
- Around line 64-94: Bind SSRF validation to the actual connection for every
request: in src/directDownload.ts lines 64-94, replace fetch’s automatic
redirects with a manual redirect loop that validates each Location URL before
the next fetch; in src/safeOutboundUrl.ts lines 52-82, change the validated
request path to connect through the validated DNS address while preserving the
original hostname for TLS/SNI, without using an unsupported Bun dispatcher or
connector; also apply the same DNS-TOCTOU fix to the manual redirect loop in
src/utils.ts lines 468-483.

---

Nitpick comments:
In `@src/directLinkRules.ts`:
- Around line 34-45: Update normalizeDirectDownloadParsedUrl to handle
docs.google.com URLs consistently with isKnownDirectDownloadHost: rewrite Google
Docs document links containing a document ID to the appropriate export-download
URL using the existing Drive normalization pattern, or remove docs.google.com
from the allow-list if normalization is not being added. Preserve existing
Dropbox and Drive behavior.
- Around line 20-28: Update urlLooksLikeDirectDownload so dl/raw query
parameters only qualify a URL when combined with an AUDIO_OR_ARCHIVE_EXT_RE
pathname match or a known direct-download host. Preserve the existing protocol,
host, and extension checks, but remove the unconditional dl/raw acceptance for
arbitrary hosts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b96f643d-2eab-4058-bcd7-712e9a1baf48

📥 Commits

Reviewing files that changed from the base of the PR and between c4b878d and af3fee8.

📒 Files selected for processing (7)
  • src/directDownload.ts
  • src/directLinkRules.ts
  • src/jobStore.test.ts
  • src/safeOutboundUrl.ts
  • src/utils.test.ts
  • src/utils.ts
  • webui/src/components/App.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • webui/src/components/App.tsx
  • src/utils.ts

Comment thread src/directDownload.ts Outdated
Connect via DNS-validated public addresses with Host/SNI, follow redirects manually, enforce size limits while streaming, and tighten direct-link heuristics.
@D3SOX

D3SOX commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

Addressed the latest CodeRabbit round in 7e10bc8:

  • Direct downloads + gate resolve use safeFetch (validated public IP + Host/SNI; manual redirects)
  • Streamed downloads with a hard byte cap (no full arrayBuffer)
  • Dropped docs.google.com from the direct-host list; tightened dl/raw heuristics
  • Provider labels use a lookup table

@D3SOX

D3SOX commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/safeOutboundUrl.ts (1)

73-83: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Normalize IPv6 literals before validation.

url.hostname includes brackets, while net.isIP() returns 0 for bracketed IPv6. The code then calls dns.lookup() and rejects every IPv6 literal with ENOTFOUND. Strip the brackets once and use the unbracketed value for isBlockedHostname(), isIP(), isPrivateOrLocalIp(), and address.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/safeOutboundUrl.ts` around lines 73 - 83, Normalize url.hostname by
removing IPv6 brackets once before the validation flow, then use the normalized
host for isBlockedHostname(), isIP(), isPrivateOrLocalIp(), and the returned
address. Preserve existing rejection behavior for blocked or private/local hosts
while allowing valid IPv6 literals to bypass DNS lookup.
src/directLinkRules.ts (1)

18-41: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Reject malformed Google Drive share links before direct download. Valid /file/d/ID/view links convert correctly, and Dropbox uses URLSearchParams.set('dl', '1'). However, malformed Drive paths are classified as direct and may pass through unchanged. Validate the file ID before normalization.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/directLinkRules.ts` around lines 18 - 41, Update
normalizeDirectDownloadParsedUrl to validate the Google Drive file ID extracted
by the driveFile pattern before converting it to the uc download URL. Reject
malformed Drive share paths rather than treating them as direct downloads, while
preserving conversion for valid /file/d/ID/view links and existing Dropbox
normalization.
🧹 Nitpick comments (1)
src/safeOutboundUrl.ts (1)

59-95: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Normalize IPv6 brackets in isBlockedHostname. URL.hostname retains brackets for IPv6 literals, so unbracketed blocklist entries do not match. isPrivateOrLocalIp catches these values later, but the blocklist check should normalize brackets directly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/safeOutboundUrl.ts` around lines 59 - 95, Normalize IPv6 brackets from
the host value before passing it to isBlockedHostname in
resolveSafeConnectTarget, while preserving the original host for subsequent IP
validation and error reporting as needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/directLinkRules.ts`:
- Around line 18-41: Update normalizeDirectDownloadParsedUrl to validate the
Google Drive file ID extracted by the driveFile pattern before converting it to
the uc download URL. Reject malformed Drive share paths rather than treating
them as direct downloads, while preserving conversion for valid /file/d/ID/view
links and existing Dropbox normalization.

In `@src/safeOutboundUrl.ts`:
- Around line 73-83: Normalize url.hostname by removing IPv6 brackets once
before the validation flow, then use the normalized host for
isBlockedHostname(), isIP(), isPrivateOrLocalIp(), and the returned address.
Preserve existing rejection behavior for blocked or private/local hosts while
allowing valid IPv6 literals to bypass DNS lookup.

---

Nitpick comments:
In `@src/safeOutboundUrl.ts`:
- Around line 59-95: Normalize IPv6 brackets from the host value before passing
it to isBlockedHostname in resolveSafeConnectTarget, while preserving the
original host for subsequent IP validation and error reporting as needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f1c66e80-eadb-4c24-8aff-2e1e281df2d0

📥 Commits

Reviewing files that changed from the base of the PR and between af3fee8 and 7e10bc8.

📒 Files selected for processing (5)
  • src/directDownload.ts
  • src/directLinkRules.ts
  • src/safeOutboundUrl.ts
  • src/soundcloud.ts
  • src/utils.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/soundcloud.ts
  • src/directDownload.ts
  • src/utils.ts

Strip IPv6 brackets before IP/blocklist checks, and only treat Google Drive URLs with a valid file id as direct downloads.
@D3SOX

D3SOX commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

Addressed the latest CodeRabbit outside-diff notes in this push:

  • Bracketed IPv6 hostnames are normalized before blocklist/isIP/connect
  • Google Drive links require a valid file id (malformed Drive paths are no longer classified as direct)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/directLinkRules.ts`:
- Around line 58-65: Update the Google Drive normalization branch around
extractDriveFileId to read any input resourcekey via url.searchParams and
preserve it in the returned download URL’s query parameters. Keep the existing
URL format and validation unchanged, and add a regression test covering a link
containing resourcekey.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 046694f1-2f6d-40ed-bc57-3fc2b80d3a55

📥 Commits

Reviewing files that changed from the base of the PR and between 7e10bc8 and 726d6c3.

📒 Files selected for processing (3)
  • src/directLinkRules.ts
  • src/safeOutboundUrl.ts
  • src/utils.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/utils.test.ts
  • src/safeOutboundUrl.ts

Comment thread src/directLinkRules.ts Outdated
Link-shared Drive files can require resourcekey; keep it when rewriting to uc?export=download.
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.

1 participant