rdt-client/server/RdtClient.Service/Helpers/DownloadHelper.cs
Claude 582bbd7a78
fix: address review feedback on filename sanitizer
- Revert GetFileName to return unsanitized name. AllDebridDebridClient.
  GetSymlinkPath matches torrent.Files[].Path (original debrid data)
  against GetFileName, so sanitizing it broke AllDebrid Symlink lookups
  for any torrent with bracketed filenames. Sanitization now happens
  inside GetDownloadPath at the point the filesystem path is assembled,
  while matching still uses the unsanitized name.

- Make SanitizeFilename / SanitizePath accept String? and return
  String.Empty for null/empty input so the declared non-null return
  type is honoured.

- SanitizePath splits on both '/' and '\\' regardless of host OS, so
  paths arriving from the debrid provider with foreign separators are
  still segmented correctly on Linux containers.

- Rewrite the control-character tests to use explicit \u escape
  sequences (, , , €, Ÿ) so the test
  inputs are visible in diffs and survive editor/CI round-trips.
  Replace the single-row empty [Theory] with a [Fact] and add a null
  case. Add tests asserting SanitizeFilenameIfEnabled and
  SanitizePathIfEnabled return input unchanged when the toggle is
  disabled, and sanitize when enabled.
2026-04-23 06:55:34 +00:00

163 lines
4.6 KiB
C#

using System.IO.Abstractions;
using System.Web;
using RdtClient.Data.Models.Data;
namespace RdtClient.Service.Helpers;
public static class DownloadHelper
{
public static String? GetDownloadPath(String downloadPath, Torrent torrent, Download download, IFileSystem? fileSystem = null)
{
var fileUrl = download.Link;
if (String.IsNullOrWhiteSpace(fileUrl) || torrent.RdName == null)
{
return null;
}
var originalDirectory = RemoveInvalidPathChars(torrent.RdName);
var directory = FilenameSanitizer.SanitizeFilenameIfEnabled(originalDirectory);
var torrentPath = Path.Combine(downloadPath, directory);
var originalFileName = GetFileName(download);
if (originalFileName == null)
{
return null;
}
var matchingTorrentFiles = torrent.Files.Where(m => m.Path.EndsWith(originalFileName)).Where(m => !String.IsNullOrWhiteSpace(m.Path)).ToList();
if (matchingTorrentFiles.Count > 0)
{
var matchingTorrentFile = matchingTorrentFiles[0];
var subPath = Path.GetDirectoryName(matchingTorrentFile.Path);
if (!String.IsNullOrWhiteSpace(subPath))
{
subPath = subPath.Trim('/').Trim('\\');
subPath = StripTorrentNamePrefix(subPath, originalDirectory);
if (!String.IsNullOrWhiteSpace(subPath))
{
subPath = FilenameSanitizer.SanitizePathIfEnabled(subPath);
torrentPath = Path.Combine(torrentPath, subPath);
}
}
}
fileSystem ??= new FileSystem();
if (!fileSystem.Directory.Exists(torrentPath))
{
fileSystem.Directory.CreateDirectory(torrentPath);
}
var fileName = FilenameSanitizer.SanitizeFilenameIfEnabled(originalFileName);
var filePath = Path.Combine(torrentPath, fileName);
return filePath;
}
public static String? GetDownloadPath(Torrent torrent, Download download)
{
var fileUrl = download.Link;
if (String.IsNullOrWhiteSpace(fileUrl) || torrent.RdName == null)
{
return null;
}
var uri = new Uri(fileUrl);
var torrentPath = RemoveInvalidPathChars(torrent.RdName);
var fileName = download.FileName;
if (String.IsNullOrWhiteSpace(fileName))
{
fileName = uri.Segments.Last();
fileName = HttpUtility.UrlDecode(fileName);
}
var matchingTorrentFiles = torrent.Files.Where(m => m.Path.EndsWith(fileName)).Where(m => !String.IsNullOrWhiteSpace(m.Path)).ToList();
if (matchingTorrentFiles.Count > 0)
{
var matchingTorrentFile = matchingTorrentFiles[0];
var subPath = Path.GetDirectoryName(matchingTorrentFile.Path);
if (!String.IsNullOrWhiteSpace(subPath))
{
subPath = subPath.Trim('/').Trim('\\');
subPath = StripTorrentNamePrefix(subPath, torrentPath);
if (!String.IsNullOrWhiteSpace(subPath))
{
torrentPath = Path.Combine(torrentPath, subPath);
}
}
}
var filePath = Path.Combine(torrentPath, fileName);
return filePath;
}
public static String? GetFileName(Download download)
{
if (String.IsNullOrWhiteSpace(download.Link))
{
return null;
}
var fileName = download.FileName;
if (String.IsNullOrWhiteSpace(fileName))
{
fileName = HttpUtility.UrlDecode(new Uri(download.Link).Segments.Last());
}
return FileHelper.RemoveInvalidFileNameChars(fileName);
}
public static String RemoveInvalidPathChars(String path)
{
return String.Concat(path.Split(Path.GetInvalidPathChars()));
}
private static String StripTorrentNamePrefix(String subPath, String torrentName)
{
var separatorIndex = subPath.IndexOfAny(['/', '\\']);
String firstComponent;
String remainder;
if (separatorIndex < 0)
{
firstComponent = subPath;
remainder = String.Empty;
}
else
{
firstComponent = subPath[..separatorIndex];
remainder = subPath[(separatorIndex + 1)..];
}
var normalizedFirst = RemoveInvalidPathChars(firstComponent);
if (normalizedFirst.Equals(torrentName.TrimEnd('/', '\\'), StringComparison.OrdinalIgnoreCase))
{
return remainder;
}
return subPath;
}
}