Files
stream-cinema/CinemaLib/Webshare/LinkGenerator.cs
Roman Vaníček 5d1906abfa
Some checks failed
continuous-integration/drone/push Build is failing
IlRepack and obfuscation
2024-12-12 01:10:11 +01:00

103 lines
4.5 KiB
C#

using System;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
namespace CinemaLib.Webshare;
public static class LinkGenerator
{
internal static readonly Uri WebshareApiUri = new Uri("https://webshare.cz/api/");
/// <summary>
/// Generates a download link for the given Webshare download.
/// </summary>
/// <param name="fileId">Webshare file identifier.</param>
/// <param name="fileName">Webshare file name.</param>
/// <param name="session">Optional session to get a VIP link.</param>
/// <param name="cancel">Asynchronous cancellation.</param>
/// <returns>Time-limited download link.</returns>
public async static Task<Uri> GenerateDownloadLinkAsync(string fileId, string fileName, Session? session, CancellationToken cancel)
{
// Obtain the download password salt
// Example response: <?xml version="1.0" encoding="UTF-8"?><response><status>OK</status><salt>UX8y8Fpa</salt><app_version>30</app_version></response>
Uri saltUri = new Uri(WebshareApiUri, "file_password_salt/");
HttpResponseMessage saltRes = await Program._http.PostAsync(saltUri, new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("ident", fileId) }), cancel);
if (!saltRes.IsSuccessStatusCode)
throw new IOException("Failed to get password salt from Webshare API.");
XmlReader saltR = XmlReader.Create(saltRes.Content.ReadAsStream());
saltR.ReadStartElement("response");
Exception? e = saltR.GetExceptionIfStatusNotOK(out string? code);
string password;
if (e != null)
{
if (code == "FILE_PASSWORD_SALT_FATAL_2")
{
// No password is set
password = "";
}
else
throw e;
}
else
{
// Calculate the download password
string salt = saltR.ReadElementContentAsString("salt", "");
saltR.ReadElementContentAsString("app_version", "");
saltR.ReadEndElement(); // response
password = CalculatePassword(fileId, fileName, salt);
}
// Obtain the download link
// Example response: <?xml version="1.0" encoding="UTF-8"?><response><status>OK</status><link>https://free.19.dl.wsfiles.cz/9209/...</link><app_version>30</app_version></response>
Uri linkUri = new Uri(WebshareApiUri, "file_link/");
HttpResponseMessage linkRes = await Program._http.PostAsync(linkUri, new FormUrlEncodedContent(new[] {
new KeyValuePair<string, string>("ident", fileId),
new KeyValuePair<string, string>("download_type", "video_stream"),
new KeyValuePair<string, string>("device_uuid", Guid.NewGuid().ToString("X")),
new KeyValuePair<string, string>("device_res_x", "3840"),
new KeyValuePair<string, string>("device_res_y", "2160"),
new KeyValuePair<string, string>("password", password),
new KeyValuePair<string, string>(Session.TokenKeyName, (session != null ? await session.GetTokenAsync(cancel) : null) ?? ""),
}), cancel
);
if (!linkRes.IsSuccessStatusCode)
throw new IOException("Failed to get download link from Webshare API.");
XmlReader linkR = XmlReader.Create(linkRes.Content.ReadAsStream());
linkR.ReadStartElement("response");
linkR.ThrowIfStatusNotOK();
string link = linkR.ReadElementContentAsString("link", "");
linkR.ReadElementContentAsString("app_version", "");
linkR.ReadEndElement(); // response
return new Uri(link);
}
/// <summary>
/// Calculates the Webshare download password from file identifier, name and salt.
/// </summary>
/// <param name="fileId">Webshare file identifier.</param>
/// <param name="fileName">Webshare file name.</param>
/// <param name="salt">Download salt obtained from https://webshare.cz/api/file_password_salt/.</param>
/// <returns>Password to use to obtain a download link using https://webshare.cz/api/file_link/.</returns>
private static string CalculatePassword(string fileId, string fileName, string salt)
{
if (fileId == null || fileName == null || salt == null)
throw new ArgumentNullException();
// Example
// name = "bIaFGdkvKiiTBo2_S3E"
// id = "12kRBqAYQS2"
// password = sha256(name + id) = "1e98873b57b7660cba7267c191f2d1fb7d198d062eb2fe0686502883042c4105"
// crypt = md5crypt(password, salt: "UX8y8Fpa") = "$1$UX8y8Fpa$stY.bUYGBsmAlc6IIxnHU0"
// result = sha1(crypt) = "8ceff8e5abf9aa375a8a5b05871b6cd6fb7fd185")
string toHash = fileName + fileId;
byte[] password = SHA256.HashData(Encoding.UTF8.GetBytes(toHash));
string passwordS = Convert.ToHexString(password).ToLowerInvariant();
return passwordS.HashPassword(salt);
}
}