mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-22 17:06:40 +00:00
fix(aria2): harden protocol and torrent transfers
This commit is contained in:
@@ -284,6 +284,7 @@ export const AddDownloadsModal = () => {
|
||||
const [useAuth, setUseAuth] = useState(false);
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [sftpHostKeyMd, setSftpHostKeyMd] = useState('');
|
||||
|
||||
const [advancedExpanded, setAdvancedExpanded] = useState(false);
|
||||
const [playlistQualityExpanded, setPlaylistQualityExpanded] = useState(true);
|
||||
@@ -411,6 +412,7 @@ export const AddDownloadsModal = () => {
|
||||
setUseAuth(false);
|
||||
setUsername('');
|
||||
setPassword('');
|
||||
setSftpHostKeyMd('');
|
||||
setAdvancedExpanded(false);
|
||||
setChecksumEnabled(false);
|
||||
setChecksumAlgo('SHA-256');
|
||||
@@ -1515,6 +1517,9 @@ export const AddDownloadsModal = () => {
|
||||
speedLimit: speedLimitEnabled ? `${speedLimit}K` : undefined,
|
||||
username: useAuth ? username.trim() : undefined,
|
||||
password: useAuth ? password.trim() : undefined,
|
||||
sftpHostKeyMd: !item.isTorrent && item.sourceUrl.trim().toLowerCase().startsWith('sftp:')
|
||||
? sftpHostKeyMd.trim() || undefined
|
||||
: undefined,
|
||||
headers: headersForRow(contextUrl) || undefined,
|
||||
checksum: checksumEnabled && checksumValue.trim()
|
||||
? `${checksumAlgo}=${checksumValue.trim()}`
|
||||
@@ -1706,6 +1711,9 @@ export const AddDownloadsModal = () => {
|
||||
return Boolean(selected && selected.length > 0 && selected.length < item.torrentFiles.length);
|
||||
};
|
||||
const selectedItem = selectedItemIndex === null ? undefined : parsedItems[selectedItemIndex];
|
||||
const hasSftpRows = parsedItems.some(item => item.selected !== false
|
||||
&& !item.isTorrent
|
||||
&& item.sourceUrl.trim().toLowerCase().startsWith('sftp:'));
|
||||
const selectedPlaylistSourceUrl = selectedItem?.playlistSourceUrl;
|
||||
const selectedPlaylistRows = selectedPlaylistSourceUrl
|
||||
? parsedItems.filter(item => item.playlistSourceUrl === selectedPlaylistSourceUrl && item.selected !== false)
|
||||
@@ -2854,6 +2862,23 @@ export const AddDownloadsModal = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasSftpRows && (
|
||||
<div>
|
||||
<label className="block text-[10px] uppercase font-bold tracking-wider text-text-muted mb-1">
|
||||
{t($ => $.addDownloads.sftpHostKeyMd)}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={sftpHostKeyMd}
|
||||
onChange={event => setSftpHostKeyMd(event.target.value)}
|
||||
placeholder={t($ => $.addDownloads.sftpHostKeyMdHint)}
|
||||
className="add-download-control w-full px-3 py-1.5 text-xs font-mono"
|
||||
autoComplete="off"
|
||||
/>
|
||||
<p className="mt-1 text-[11px] text-text-muted">{t($ => $.addDownloads.sftpHostKeyMdDescription)}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-[10px] uppercase font-bold tracking-wider text-text-muted mb-1">{t($ => $.addDownloads.headers)}</label>
|
||||
<textarea
|
||||
|
||||
@@ -214,6 +214,7 @@ export const PropertiesWindowApp = () => {
|
||||
const [fileName, setFileName] = useState('');
|
||||
const [destination, setDestination] = useState('');
|
||||
const [connections, setConnections] = useState('');
|
||||
const [sftpHostKeyMd, setSftpHostKeyMd] = useState('');
|
||||
const [trackers, setTrackers] = useState('');
|
||||
const [excludedTrackers, setExcludedTrackers] = useState('');
|
||||
const [downloadLimit, setDownloadLimit] = useState('');
|
||||
@@ -278,6 +279,7 @@ export const PropertiesWindowApp = () => {
|
||||
detailsRef.current = details;
|
||||
|
||||
const isTorrent = snapshot?.isTorrent === true;
|
||||
const isSftp = Boolean(snapshot?.url.trim().toLowerCase().startsWith('sftp:'));
|
||||
const tabs = useMemo(() => getPropertiesTabs(isTorrent), [isTorrent]);
|
||||
const peerDiagnosticState = getPropertiesPeerDiagnosticState(peers, diagnosticsLoading, peerDiagnosticPhase);
|
||||
const availabilityDiagnosticState = getPropertiesAvailabilityDiagnosticState(availability, diagnosticsLoading, availabilityDiagnosticPhase);
|
||||
@@ -394,6 +396,7 @@ export const PropertiesWindowApp = () => {
|
||||
setFileName(next.fileName);
|
||||
setDestination(next.destination ?? '');
|
||||
setConnections(next.connections === undefined ? '' : String(next.connections));
|
||||
setSftpHostKeyMd(next.sftpHostKeyMd ?? '');
|
||||
setTrackers(next.torrentTrackers ?? '');
|
||||
setExcludedTrackers(next.torrentExcludeTrackers ?? '');
|
||||
setSelectedFiles(next.torrentFileIndices ? [...next.torrentFileIndices] : null);
|
||||
@@ -973,6 +976,9 @@ export const PropertiesWindowApp = () => {
|
||||
if (nextFileAllocation !== snapshot.torrentFileAllocation) patch.torrentFileAllocation = encodePropertiesPatchValue(nextFileAllocation);
|
||||
}
|
||||
} else if (activeTab === 'advanced') {
|
||||
if (isSftp && sftpHostKeyMd !== (snapshot.sftpHostKeyMd ?? '')) {
|
||||
patch.sftpHostKeyMd = encodePropertiesPatchValue(sftpHostKeyMd.trim() || undefined);
|
||||
}
|
||||
for (const name of SECRET_NAMES) {
|
||||
const draft = secretDrafts[name];
|
||||
if (!draft.touched) continue;
|
||||
@@ -980,7 +986,7 @@ export const PropertiesWindowApp = () => {
|
||||
}
|
||||
}
|
||||
await requestAction('apply-properties', patch);
|
||||
}, [activeTab, checkIntegrity, connections, destination, downloadLimit, encryptionPolicy, excludedTrackers, fileAllocation, fileName, fileProgress, isTorrent, maxPeers, peerSpeedLimit, prioritizePiece, removeUnselectedFile, requestAction, secretDrafts, seedRatio, seedTime, selectedFiles, snapshot, stopTimeout, trackerConnectTimeout, trackerInterval, trackerTimeout, trackers, t, uploadLimit]);
|
||||
}, [activeTab, checkIntegrity, connections, destination, downloadLimit, encryptionPolicy, excludedTrackers, fileAllocation, fileName, fileProgress, isSftp, isTorrent, maxPeers, peerSpeedLimit, prioritizePiece, removeUnselectedFile, requestAction, sftpHostKeyMd, secretDrafts, seedRatio, seedTime, selectedFiles, snapshot, stopTimeout, trackerConnectTimeout, trackerInterval, trackerTimeout, trackers, t, uploadLimit]);
|
||||
|
||||
const chooseTab = (tab: PropertiesTab) => {
|
||||
if (tab === activeTab) {
|
||||
@@ -1538,6 +1544,8 @@ export const PropertiesWindowApp = () => {
|
||||
|
||||
{activeTab === 'advanced' && <div className="space-y-4">
|
||||
<p className="text-xs text-text-muted">{t($ => $.properties.advancedTransfer)}</p>
|
||||
{snapshot.credentialsRequired === true && <p className="rounded-lg border border-amber-500/40 bg-amber-500/10 p-3 text-xs text-amber-200" role="alert">{t($ => $.properties.credentialsRequired)}</p>}
|
||||
{isSftp && <label className="block max-w-2xl text-xs text-text-muted">{t($ => $.properties.sftpHostKeyMd)}<input className="app-control mt-1 w-full font-mono" value={sftpHostKeyMd} onChange={event => { setSftpHostKeyMd(event.target.value); setDraftTab('advanced'); }} placeholder={t($ => $.properties.sftpHostKeyMdHint)} disabled={!editingEnabled} autoComplete="off" /><span className="mt-1 block text-[11px]">{t($ => $.properties.sftpHostKeyMdDescription)}</span></label>}
|
||||
<div className="grid max-w-2xl gap-3 rounded-lg border border-border-modal bg-bg-input/30 p-3 text-xs sm:grid-cols-2">
|
||||
<div><span className="text-text-muted">{t($ => $.properties.connections)}</span><p className="mt-1">{snapshot.isMedia === true ? `${snapshot.connections ?? '—'} ${t($ => $.properties.configuredConcurrency)}` : `${snapshot.activeConnections ?? '—'} / ${snapshot.requestedConnections ?? snapshot.connections ?? '—'}`}</p></div>
|
||||
<div><span className="text-text-muted">{t($ => $.properties.speedCap)}</span><p className="mt-1">{snapshot.speedLimit || '—'}</p></div>
|
||||
|
||||
@@ -101,6 +101,15 @@ const copyEditablePatch = (rawPatch: PropertiesPatch): Partial<DownloadItem> =>
|
||||
if (safePatch.destination !== undefined && typeof safePatch.destination !== 'string') {
|
||||
throw new Error('Invalid destination');
|
||||
}
|
||||
if (safePatch.sftpHostKeyMd !== undefined) {
|
||||
if (typeof safePatch.sftpHostKeyMd !== 'string') throw new Error('Invalid SFTP host-key fingerprint');
|
||||
const fingerprint = safePatch.sftpHostKeyMd.trim().toLowerCase();
|
||||
const valid = /^(md5|sha-1)=[0-9a-f]+$/.test(fingerprint)
|
||||
&& ((fingerprint.startsWith('md5=') && fingerprint.length === 36)
|
||||
|| (fingerprint.startsWith('sha-1=') && fingerprint.length === 45));
|
||||
if (!valid) throw new Error('Invalid SFTP host-key fingerprint');
|
||||
safePatch.sftpHostKeyMd = fingerprint;
|
||||
}
|
||||
|
||||
if (safePatch.connections !== undefined
|
||||
&& (!Number.isInteger(safePatch.connections) || safePatch.connections < 1 || safePatch.connections > 16)) {
|
||||
|
||||
Reference in New Issue
Block a user