fix(opds): auth negotiation and auto-download fixes for self-hosted catalogs (#5002)
* fix(opds): allow LAN catalogs through the proxy in development The SSRF host blocklist added in #4638 unconditionally rejected private addresses, so the dev proxy returned 400 for LAN OPDS catalogs even though next dev runs on the developer's own machine where reaching the LAN is the normal use case. Skip the blocklist when NODE_ENV is development, matching the existing CatalogManager gate that only forbids LAN URLs in production. Production and test behavior are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(opds): negotiate Digest auth when the server rejects preemptive Basic with 400 Calibre's content server in digest mode, or auto mode over http, answers a Basic Authorization header with 400 Unsupported authentication method instead of a 401 challenge. The preemptive Basic header introduced in #4206 therefore dead-ended the request, since the auth retry only fired on 401 or 403, and digest catalogs failed with Failed to load OPDS feed: 400 Bad Request on every platform. When a request that carried preemptive Basic comes back 400, re-issue it once without credentials to surface the WWW-Authenticate challenge and let the existing negotiation pick the scheme the server actually wants. Verified end to end against a live Calibre server on web and Android. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(opds): skip SSL verification in auto-download like the manual download path The native download_file command validates TLS with rustls, which ignores the OS trust store, so downloads from self-signed or private-CA OPDS servers fail in the TLS handshake before any request reaches the server. The manual download path has passed skipSslVerification since #2900 as the workaround for #2871, but the auto-download path never did, so subscribed shelves failed to sync while feed browsing and manual downloads of the same books worked. Pass the same flag in downloadAndImport. Closes #4988 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,11 @@ import { isBlockedHost } from '@/utils/network';
|
||||
// Cap redirect hops so the SSRF host check below can re-run on every one.
|
||||
const MAX_REDIRECTS = 5;
|
||||
|
||||
// In `next dev` the server runs on the developer's own machine, where a LAN
|
||||
// OPDS catalog is the normal use case (the CatalogManager UI only forbids
|
||||
// adding LAN URLs in production builds), so the host blocklist is skipped.
|
||||
const isPrivateHostAllowed = () => process.env.NODE_ENV === 'development';
|
||||
|
||||
/**
|
||||
* Fetch the target while running the SSRF host check on every redirect hop.
|
||||
* `fetch`'s default `redirect: 'follow'` would let a public URL 302 to an
|
||||
@@ -25,7 +30,7 @@ async function fetchFollowingRedirects(
|
||||
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
||||
throw new SsrfBlockedError('Only http(s) URLs are supported');
|
||||
}
|
||||
if (isBlockedHost(parsed.hostname)) {
|
||||
if (!isPrivateHostAllowed() && isBlockedHost(parsed.hostname)) {
|
||||
throw new SsrfBlockedError('This URL is not allowed');
|
||||
}
|
||||
const response = await fetch(currentUrl, { ...init, redirect: 'manual' });
|
||||
@@ -79,7 +84,7 @@ async function handleRequest(request: NextRequest, method: 'GET' | 'HEAD') {
|
||||
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
|
||||
return NextResponse.json({ error: 'Only http(s) URLs are supported' }, { status: 400 });
|
||||
}
|
||||
if (isBlockedHost(parsedUrl.hostname)) {
|
||||
if (!isPrivateHostAllowed() && isBlockedHost(parsedUrl.hostname)) {
|
||||
return NextResponse.json({ error: 'This URL is not allowed' }, { status: 400 });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user