using System.Linq;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
namespace VideoDownloader.Server;
///
/// M0 원격 접근 서버: 데스크톱 앱에 내장되는 Kestrel.
/// QR 페어링 토큰(Bearer) 인증 + 큐 API + 라이브러리/Range 스트리밍 + 제로 설치 웹 컴패니언.
/// LAN 직결(계정/클라우드 없음) — MOBILE_PLAN §3 아키텍처.
///
public sealed class ServerHost : IAsyncDisposable
{
private readonly WebApplication _app;
private readonly string _token;
private readonly ServerOptions _options;
public int Port { get; }
/// QR 에 담을 페어링 URL 형식: http://<lan-ip>:<port>/#t=<token>
public string Token => _token;
private ServerHost(WebApplication app, int port, string token, ServerOptions options)
{
_app = app;
Port = port;
_token = token;
_options = options;
}
public static async Task StartAsync(ServerOptions options)
{
var token = string.IsNullOrEmpty(options.Token)
? WebEncoders.Base64UrlEncode(RandomNumberGenerator.GetBytes(32))
: options.Token;
var builder = WebApplication.CreateBuilder(new WebApplicationOptions { Args = new[] { "--urls", $"http://0.0.0.0:{(options.Port == 0 ? GetFreePort() : options.Port)}" } });
foreach (var prov in builder.Services.Where(sd => sd.ServiceType.FullName!.Contains("LoggerProvider")).ToList())
builder.Services.Remove(prov);
var app = builder.Build();
// Bearer 토큰 인증(상수시간 비교) — / 와 /api/* 전체 적용
app.Use(async (ctx, next) =>
{
if (!ctx.Request.Path.StartsWithSegments("/api/pair") &&
(ctx.Request.Path.StartsWithSegments("/api") || ctx.Request.Path.StartsWithSegments("/stream") || ctx.Request.Path.StartsWithSegments("/file")))
{
var auth = ctx.Request.Headers.Authorization.ToString();
var provided = auth.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase) ? auth[7..].Trim() : "";
//