This commit is contained in:
@@ -103,6 +103,7 @@ async function handleRequest(request: NextRequest, method: 'GET' | 'HEAD') {
|
||||
return new NextResponse(null, {
|
||||
status: 200,
|
||||
headers: {
|
||||
...Object.fromEntries(response.headers.entries()),
|
||||
'Content-Type': contentType,
|
||||
'Content-Length': contentLength || '',
|
||||
'Cache-Control': 'public, max-age=300',
|
||||
|
||||
@@ -29,7 +29,13 @@ import {
|
||||
parseMediaType,
|
||||
resolveURL,
|
||||
} from './utils/opdsUtils';
|
||||
import { getProxiedURL, fetchWithAuth, probeAuth, needsProxy } from './utils/opdsReq';
|
||||
import {
|
||||
getProxiedURL,
|
||||
fetchWithAuth,
|
||||
probeAuth,
|
||||
needsProxy,
|
||||
probeFilename,
|
||||
} from './utils/opdsReq';
|
||||
import { READEST_OPDS_USER_AGENT } from '@/services/constants';
|
||||
import { FeedView } from './components/FeedView';
|
||||
import { PublicationView } from './components/PublicationView';
|
||||
@@ -414,49 +420,49 @@ export default function BrowserPage() {
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
const username = usernameRef.current || '';
|
||||
const password = passwordRef.current || '';
|
||||
const useProxy = needsProxy(url);
|
||||
let downloadUrl = useProxy ? getProxiedURL(url, '', true) : url;
|
||||
const headers: Record<string, string> = {
|
||||
'User-Agent': READEST_OPDS_USER_AGENT,
|
||||
Accept: '*/*',
|
||||
};
|
||||
if (username || password) {
|
||||
const authHeader = await probeAuth(url, username, password, useProxy);
|
||||
if (authHeader) {
|
||||
headers['Authorization'] = authHeader;
|
||||
downloadUrl = useProxy ? getProxiedURL(url, authHeader, true) : url;
|
||||
}
|
||||
}
|
||||
|
||||
const probedFilename = await probeFilename(url, useProxy, headers);
|
||||
const pathname = decodeURIComponent(new URL(url).pathname);
|
||||
const ext = getFileExtFromMimeType(parsed?.mediaType) || getFileExtFromPath(pathname);
|
||||
const basename = pathname.replaceAll('/', '_');
|
||||
const filename = ext ? `${basename}.${ext}` : basename;
|
||||
const filename = probedFilename ? probedFilename : ext ? `${basename}.${ext}` : basename;
|
||||
const dstFilePath = await appService?.resolveFilePath(filename, 'Cache');
|
||||
if (dstFilePath) {
|
||||
const username = usernameRef.current || '';
|
||||
const password = passwordRef.current || '';
|
||||
const useProxy = needsProxy(url);
|
||||
let downloadUrl = useProxy ? getProxiedURL(url, '', true) : url;
|
||||
const headers: Record<string, string> = {
|
||||
'User-Agent': READEST_OPDS_USER_AGENT,
|
||||
Accept: '*/*',
|
||||
};
|
||||
if (username || password) {
|
||||
const authHeader = await probeAuth(url, username, password, useProxy);
|
||||
if (authHeader) {
|
||||
headers['Authorization'] = authHeader;
|
||||
downloadUrl = useProxy ? getProxiedURL(url, authHeader, true) : url;
|
||||
}
|
||||
}
|
||||
|
||||
await downloadFile({
|
||||
appService,
|
||||
dst: dstFilePath,
|
||||
cfp: '',
|
||||
url: downloadUrl,
|
||||
headers,
|
||||
singleThreaded: true,
|
||||
skipSslVerification: true,
|
||||
onProgress,
|
||||
});
|
||||
const { library, setLibrary } = useLibraryStore.getState();
|
||||
const book = await appService.importBook(dstFilePath, library);
|
||||
if (user && book && !book.uploadedAt && settings.autoUpload) {
|
||||
setTimeout(() => {
|
||||
transferManager.queueUpload(book);
|
||||
}, 3000);
|
||||
}
|
||||
setLibrary(library);
|
||||
appService.saveLibraryBooks(library);
|
||||
return book;
|
||||
console.log('Downloading to:', dstFilePath);
|
||||
await downloadFile({
|
||||
appService,
|
||||
dst: dstFilePath,
|
||||
cfp: '',
|
||||
url: downloadUrl,
|
||||
headers,
|
||||
singleThreaded: true,
|
||||
skipSslVerification: true,
|
||||
onProgress,
|
||||
});
|
||||
const { library, setLibrary } = useLibraryStore.getState();
|
||||
const book = await appService.importBook(dstFilePath, library);
|
||||
if (user && book && !book.uploadedAt && settings.autoUpload) {
|
||||
setTimeout(() => {
|
||||
transferManager.queueUpload(book);
|
||||
}, 3000);
|
||||
}
|
||||
setLibrary(library);
|
||||
appService.saveLibraryBooks(library);
|
||||
return book;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Download error:', e);
|
||||
|
||||
@@ -245,6 +245,46 @@ export const probeAuth = async (
|
||||
return createBasicAuth(finalUsername, finalPassword);
|
||||
};
|
||||
|
||||
export const probeFilename = async (
|
||||
url: string,
|
||||
useProxy = false,
|
||||
headers?: Record<string, string>,
|
||||
) => {
|
||||
const { url: cleanUrl } = extractCredentialsFromURL(url);
|
||||
|
||||
const fetchURL = useProxy ? getProxiedURL(cleanUrl) : cleanUrl;
|
||||
const baseHeaders: Record<string, string> = {
|
||||
'User-Agent': READEST_OPDS_USER_AGENT,
|
||||
Accept: 'application/atom+xml, application/xml, text/xml, */*',
|
||||
};
|
||||
|
||||
const fetch = isTauriAppPlatform() ? tauriFetch : window.fetch;
|
||||
const res = await fetch(fetchURL, {
|
||||
method: 'HEAD',
|
||||
headers: { ...baseHeaders, ...(headers || {}) },
|
||||
danger: { acceptInvalidCerts: true, acceptInvalidHostnames: true },
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const contentDisposition = res.headers.get('content-disposition');
|
||||
if (contentDisposition) {
|
||||
const extendedMatch = contentDisposition.match(
|
||||
/filename\*\s*=\s*(?:utf-8|UTF-8)'[^']*'([^;\s]+)/i,
|
||||
);
|
||||
if (extendedMatch?.[1]) {
|
||||
return decodeURIComponent(extendedMatch[1]);
|
||||
}
|
||||
|
||||
const plainMatch = contentDisposition.match(/filename\s*=\s*["']?([^"';\s]+)["']?/i);
|
||||
if (plainMatch?.[1]) {
|
||||
return decodeURIComponent(plainMatch[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Perform authenticated HTTP request with retry logic for Digest/Basic auth
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user