mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-25 10:07:44 +00:00
fix(downloads): harden release-critical transfer paths
This commit is contained in:
@@ -49,9 +49,25 @@ const normalizeComparableUrl = (rawUrl: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const urlsHaveDifferentHosts = (sourceUrl: string, targetUrl: string) => {
|
||||
try {
|
||||
return new URL(sourceUrl).hostname.toLowerCase() !== new URL(targetUrl).hostname.toLowerCase();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const extensionHeaders = (context: PendingAddRequestContext | undefined) => [
|
||||
context?.referer ? `Referer: ${context.referer.replace(/[\r\n]/g, '')}` : '',
|
||||
context?.headers
|
||||
context?.media
|
||||
? (context.headers || '')
|
||||
.split(/\r?\n/)
|
||||
.filter(line => {
|
||||
const separator = line.indexOf(':');
|
||||
return separator < 0 || line.slice(0, separator).trim().toLowerCase() !== 'cookie';
|
||||
})
|
||||
.join('\n')
|
||||
: context?.headers
|
||||
].filter(Boolean).join('\n');
|
||||
|
||||
export const AddDownloadsModal = () => {
|
||||
@@ -121,9 +137,12 @@ export const AddDownloadsModal = () => {
|
||||
if (context) return extensionHeaders(context).trim();
|
||||
return hasExtensionRequestContext ? '' : headers.trim();
|
||||
};
|
||||
const cookiesForRow = (sourceUrl: string) => {
|
||||
const cookiesForRow = (sourceUrl: string, targetUrl = sourceUrl) => {
|
||||
if (cookiesManuallyEditedRef.current) return cookies.trim();
|
||||
const context = requestContextForUrl(sourceUrl);
|
||||
if (context && context.cookies && urlsHaveDifferentHosts(sourceUrl, targetUrl)) {
|
||||
return '';
|
||||
}
|
||||
if (context) return context.cookies.trim();
|
||||
return hasExtensionRequestContext ? '' : cookies.trim();
|
||||
};
|
||||
@@ -723,7 +742,7 @@ export const AddDownloadsModal = () => {
|
||||
checksum: checksumEnabled && checksumValue.trim()
|
||||
? `${checksumAlgo}=${checksumValue.trim()}`
|
||||
: undefined,
|
||||
cookies: cookiesForRow(item.sourceUrl) || undefined,
|
||||
cookies: cookiesForRow(item.sourceUrl, item.downloadUrl) || undefined,
|
||||
mirrors: mirrors.trim() || undefined,
|
||||
destination: useSharedDestination
|
||||
? finalLocation
|
||||
|
||||
@@ -845,7 +845,7 @@ describe('useDownloadStore', () => {
|
||||
referer: 'https://adult.example/watch/123',
|
||||
silent: false,
|
||||
filename: null,
|
||||
headers: 'User-Agent: Firefox Test',
|
||||
headers: `Cookie: stale=${'x'.repeat(64 * 1024)}\nUser-Agent: Firefox Test`,
|
||||
cookies: `oversized=${'x'.repeat(64 * 1024)}`,
|
||||
media: true
|
||||
});
|
||||
@@ -855,6 +855,7 @@ describe('useDownloadStore', () => {
|
||||
expect(state.pendingAddUrls).toBe('https://adult.example/watch/123');
|
||||
expect(state.pendingAddMediaUrls).toEqual(['https://adult.example/watch/123']);
|
||||
expect(state.pendingAddCookies).toBe('');
|
||||
expect(state.pendingAddHeaders).toBe('User-Agent: Firefox Test');
|
||||
});
|
||||
|
||||
it('preserves extension cookies for ordinary captured downloads', async () => {
|
||||
|
||||
@@ -67,6 +67,16 @@ const removeStaleBackendDispatch = async (id: string): Promise<void> => {
|
||||
const errorMessage = (error: unknown): string =>
|
||||
error instanceof Error ? error.message : String(error);
|
||||
|
||||
const stripCookieHeaders = (value: string | null | undefined): string =>
|
||||
(value || '')
|
||||
.split(/\r?\n/)
|
||||
.filter(line => {
|
||||
const separator = line.indexOf(':');
|
||||
return separator < 0 || line.slice(0, separator).trim().toLowerCase() !== 'cookie';
|
||||
})
|
||||
.join('\n')
|
||||
.trim();
|
||||
|
||||
const speedLimitForDispatch = (itemSpeedLimit: string | undefined, globalSpeedLimit: string): string | null => {
|
||||
const explicitLimit = itemSpeedLimit?.trim();
|
||||
if (explicitLimit) {
|
||||
@@ -558,12 +568,15 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
|
||||
// cookie source. Keep this frontend guard for events from older desktop or
|
||||
// extension builds; ordinary captured downloads retain their cookies.
|
||||
const cookies = request.media === true ? null : request.cookies;
|
||||
const headers = request.media === true
|
||||
? stripCookieHeaders(request.headers) || null
|
||||
: request.headers;
|
||||
|
||||
get().openAddModalWithUrls(
|
||||
urls.join('\n'),
|
||||
request.referer,
|
||||
urls.length === 1 ? request.filename : null,
|
||||
request.headers,
|
||||
headers,
|
||||
cookies,
|
||||
request.media === true
|
||||
);
|
||||
|
||||
@@ -13,12 +13,13 @@ describe('clipboard URL extraction', () => {
|
||||
|
||||
it('reads only supported, unique download URLs from clipboard text', async () => {
|
||||
vi.mocked(readText).mockResolvedValue(
|
||||
'https://example.com/file.zip\nhttps://example.com/file.zip ftp://example.com/file.bin mailto:user@example.com'
|
||||
'https://example.com/file.zip\nhttps://example.com/file.zip ftp://example.com/file.bin sftp://example.com/file.iso mailto:user@example.com'
|
||||
);
|
||||
|
||||
await expect(readClipboardDownloadUrls()).resolves.toEqual([
|
||||
'https://example.com/file.zip',
|
||||
'ftp://example.com/file.bin',
|
||||
'sftp://example.com/file.iso',
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ export function extractValidDownloadUrls(text: string): string[] {
|
||||
for (const part of parts) {
|
||||
try {
|
||||
const url = new URL(part);
|
||||
if (url.protocol === 'http:' || url.protocol === 'https:' || url.protocol === 'ftp:') {
|
||||
if (url.protocol === 'http:' || url.protocol === 'https:' || url.protocol === 'ftp:' || url.protocol === 'sftp:') {
|
||||
urls.push(url.toString());
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user