Refactor .gitignore to streamline ignored files and enhance clarity. Update CLAUDE.md to improve unit test instructions and add coverage reporting details. Revise README.md for better project overview and deployment instructions. Refactor ChannelEndpoints and StreamingEndpoints to utilize SegmentFiles for file resolution, improving code maintainability. Remove unused JunctionHandlers and update DependencyInjection for cleaner service registration. Enhance media processing services for better job handling and error management. Update frontend API types for consistency and clarity.
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using TeleWave.Infrastructure.Media;
|
||||
|
||||
namespace TeleWave.Api.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Общая проверка файлов нарезки для всех эндпоинтов, отдающих HLS: эфир, превью заставок.
|
||||
/// Имя сегмента сверяется с allowlist, а путь резолвится через <see cref="MediaPathResolver"/>,
|
||||
/// который бросает <see cref="UnauthorizedAccessException"/> на попытку выйти за пределы каталога —
|
||||
/// наружу это должно выглядеть как обычный 404, а не как ошибка сервера.
|
||||
/// </summary>
|
||||
internal static partial class SegmentFiles
|
||||
{
|
||||
[GeneratedRegex(@"^seg\d{1,6}\.ts$")]
|
||||
private static partial Regex SegmentName();
|
||||
|
||||
public static bool IsSegmentName(string file) => SegmentName().IsMatch(file);
|
||||
|
||||
/// <summary>Путь к существующему файлу нарезки, либо null — если имя опасно или файла нет.</summary>
|
||||
public static string? TryResolveExisting(
|
||||
MediaPathResolver paths,
|
||||
Guid assetId,
|
||||
string fileName
|
||||
)
|
||||
{
|
||||
string path;
|
||||
try
|
||||
{
|
||||
path = paths.SegmentPath(assetId, fileName);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return File.Exists(path) ? path : null;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using LiteCqrs;
|
||||
using TeleWave.Api.Common;
|
||||
using TeleWave.Application.Broadcast;
|
||||
@@ -13,11 +12,6 @@ namespace TeleWave.Api.Endpoints;
|
||||
/// <summary>Эндпоинты ТВ-заставок канала: блоки (стиль/аудио/фон), подблоки и рендер превью.</summary>
|
||||
public static partial class ChannelEndpoints
|
||||
{
|
||||
private static readonly Regex BumperSegmentFileName = new(
|
||||
@"^seg\d{1,6}\.ts$",
|
||||
RegexOptions.Compiled
|
||||
);
|
||||
|
||||
private static async Task<IResult> AddBumperTemplate(
|
||||
Guid id,
|
||||
AddBumperTemplateBody body,
|
||||
@@ -229,16 +223,7 @@ public static partial class ChannelEndpoints
|
||||
)
|
||||
{
|
||||
var previewId = BumperPreview.AssetId(variantId);
|
||||
string indexPath;
|
||||
try
|
||||
{
|
||||
indexPath = paths.SegmentPath(previewId, "index.m3u8");
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
if (!File.Exists(indexPath))
|
||||
if (SegmentFiles.TryResolveExisting(paths, previewId, "index.m3u8") is not { } indexPath)
|
||||
return Results.NotFound();
|
||||
|
||||
var baseUrl =
|
||||
@@ -265,20 +250,11 @@ public static partial class ChannelEndpoints
|
||||
MediaPathResolver paths
|
||||
)
|
||||
{
|
||||
if (!BumperSegmentFileName.IsMatch(file))
|
||||
if (!SegmentFiles.IsSegmentName(file))
|
||||
return Results.NotFound();
|
||||
|
||||
var previewId = BumperPreview.AssetId(variantId);
|
||||
string path;
|
||||
try
|
||||
{
|
||||
path = paths.SegmentPath(previewId, file);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
if (!File.Exists(path))
|
||||
if (SegmentFiles.TryResolveExisting(paths, previewId, file) is not { } path)
|
||||
return Results.NotFound();
|
||||
|
||||
return Results.File(path, "video/mp2t", enableRangeProcessing: true);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using LiteCqrs;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using TeleWave.Api.Common;
|
||||
@@ -17,7 +16,6 @@ namespace TeleWave.Api.Endpoints;
|
||||
public static class StreamingEndpoints
|
||||
{
|
||||
private const string StreamCookieName = "tw_stream";
|
||||
private static readonly Regex SegmentFileName = new(@"^seg\d{1,6}\.ts$", RegexOptions.Compiled);
|
||||
|
||||
public static IEndpointRouteBuilder MapStreamingEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
@@ -143,20 +141,10 @@ public static class StreamingEndpoints
|
||||
{
|
||||
if (tokens.Validate(request.Cookies[StreamCookieName]) is null)
|
||||
return Results.Unauthorized();
|
||||
if (!SegmentFileName.IsMatch(file))
|
||||
if (!SegmentFiles.IsSegmentName(file))
|
||||
return Results.NotFound();
|
||||
|
||||
string path;
|
||||
try
|
||||
{
|
||||
path = paths.SegmentPath(assetId, file);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
if (!File.Exists(path))
|
||||
if (SegmentFiles.TryResolveExisting(paths, assetId, file) is not { } path)
|
||||
return Results.NotFound();
|
||||
|
||||
response.Headers.CacheControl = "public, max-age=31536000, immutable";
|
||||
|
||||
Reference in New Issue
Block a user