Some checks failed
release / build-release (push) Has been cancelled
- IChannel: creators -> sub-channels -> video posts (public listing API, search mapped) - IRequiresMediaInfoCallback -> signed proxy URLs; stream controller rewrites HLS so the AES-128 key is fetched server-side with the session cookie, segments stay on the CDN - delivery/info cached per token lifetime and throttled (~2/min) - config page: paste sails.sid + validate, extra creators, quality, search test - scripts/package.sh -> plugin folder + zip + md5 + manifest.json; Gitea release workflow - research docs, HAR analysis (HAR itself gitignored), install-pi5.sh Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
64 lines
1.9 KiB
C#
64 lines
1.9 KiB
C#
using System;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
|
|
namespace Jellyfin.Plugin.Floatplane.Api;
|
|
|
|
/// <summary>
|
|
/// Signs the URLs the channel hands to Jellyfin's ffmpeg, so the anonymous stream
|
|
/// endpoints only serve what this plugin selected. The secret lives for the process:
|
|
/// media sources are handed out per playback, so a restart just means fresh signatures.
|
|
/// </summary>
|
|
public static class ProxySignature
|
|
{
|
|
public const string QueryParameter = "t";
|
|
|
|
private static readonly byte[] Secret = RandomNumberGenerator.GetBytes(32);
|
|
|
|
public static string Create(params string[] parts)
|
|
{
|
|
var payload = string.Join('\n', parts);
|
|
var hash = HMACSHA256.HashData(Secret, Encoding.UTF8.GetBytes(payload));
|
|
return Convert.ToHexString(hash).ToLowerInvariant();
|
|
}
|
|
|
|
public static bool Verify(string? signature, params string[] parts)
|
|
{
|
|
if (string.IsNullOrEmpty(signature))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var expected = Create(parts);
|
|
return CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(signature));
|
|
}
|
|
|
|
/// <summary>URL-safe base64 without padding (for embedding upstream URLs in proxy URLs).</summary>
|
|
public static string EncodeUrl(string value)
|
|
=> Convert.ToBase64String(Encoding.UTF8.GetBytes(value)).TrimEnd('=').Replace('+', '-').Replace('/', '_');
|
|
|
|
public static string? DecodeUrl(string? encoded)
|
|
{
|
|
if (string.IsNullOrEmpty(encoded))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var s = encoded.Replace('-', '+').Replace('_', '/');
|
|
switch (s.Length % 4)
|
|
{
|
|
case 2: s += "=="; break;
|
|
case 3: s += "="; break;
|
|
}
|
|
|
|
try
|
|
{
|
|
return Encoding.UTF8.GetString(Convert.FromBase64String(s));
|
|
}
|
|
catch (FormatException)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
}
|