fix(opds): workaround parsing of encoded params in proxied url in cloudflare (#2575)

This commit is contained in:
Huang Xin
2025-12-01 12:49:04 +08:00
committed by GitHub
parent 2ca3561093
commit 02e885fbb7
3 changed files with 18 additions and 4 deletions
@@ -1,7 +1,18 @@
import { NextRequest, NextResponse } from 'next/server';
async function handleRequest(request: NextRequest, method: 'GET' | 'HEAD') {
const url = request.nextUrl.searchParams.get('url');
// Cloudflare Workers incorrectly decodes %26 to & in the url parameter value,
// causing query parameters within the proxied URL (like &start_index=26) to be
// treated as separate top-level parameters instead of part of the url value.
// We work around this by manually extracting the url parameter - capturing everything
// from 'url=' until we hit our known parameters (&stream= or &auth=), then decoding it.
const fullUrl = request.url;
const urlParamStart = fullUrl.indexOf('url=') + 4;
const streamParam = fullUrl.lastIndexOf('&stream=');
const authParam = fullUrl.lastIndexOf('&auth=');
const urlParamEnd = Math.min(...[streamParam, authParam].filter((i) => i > 0), fullUrl.length);
const encodedUrl = fullUrl.substring(urlParamStart, urlParamEnd);
const url = decodeURIComponent(encodedUrl);
const auth = request.nextUrl.searchParams.get('auth');
const stream = request.nextUrl.searchParams.get('stream');
@@ -173,7 +173,7 @@ export function CatalogManager() {
<div className='min-w-0 flex-1'>
<div className='mb-1 flex items-center justify-between'>
<h3 className='card-title truncate text-sm'>
{catalog.icon && <span className='mr-2'>{catalog.icon}</span>}
{catalog.icon && <span className=''>{catalog.icon}</span>}
{catalog.name}
</h3>
<button
@@ -226,7 +226,7 @@ export function CatalogManager() {
>
<div className='card-body p-4'>
<h3 className='card-title mb-1 text-sm'>
{catalog.icon && <span className='mr-2'>{catalog.icon}</span>}
{catalog.icon && <span className=''>{catalog.icon}</span>}
{catalog.name}
</h3>
{catalog.description && (
@@ -4,6 +4,7 @@ import { useMemo } from 'react';
import { groupByArray } from './utils/opdsUtils';
import { CachedImage } from '@/components/CachedImage';
import { OPDSPublication, REL } from '@/types/opds';
import { useTranslation } from '@/hooks/useTranslation';
interface PublicationCardProps {
publication: OPDSPublication;
@@ -20,6 +21,7 @@ export function PublicationCard({
resolveURL,
onGenerateCachedImageUrl,
}: PublicationCardProps) {
const _ = useTranslation();
const linksByRel = useMemo(
() => groupByArray(publication.links, (link) => link.rel),
[publication.links],
@@ -55,9 +57,10 @@ export function PublicationCard({
return `${currency} ${value}`;
}
if (linksByRel.has(REL.ACQ + '/open-access')) {
return 'Open Access';
return _('Open Access');
}
return null;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [publication.links, linksByRel]);
return (