using Sunfish.Http;
using Sunfish.Mac;
namespace Sunfish.Web;
/// <summary>
/// Mac clasico y nombres cortos en la web (DOC/MAC-CLASICO.md, DOC/CLIENTES.md): las subidas en
/// MacBinary o BinHex se separan solas; a un Mac clasico se le entrega en MacBinary lo que tiene
/// resource fork; ?as=macbin y ?as=hqx lo piden explicitamente; DOS y Windows 3.1 reciben el
/// nombre 8.3 en la descarga y Mac clasico uno de 31 caracteres como mucho.
/// </summary>
public sealed partial class WebApp
{
/// <summary>Tras una subida: si es MacBinary/BinHex y el share tiene Mac, lo separa. Devuelve el nombre final.</summary>
private async Task<string> UnpackMacAsync(ShareRequest sr, string path)
{
if (!sr.Share.Mac)
return Path.GetFileName(path);
try
{
var (result, name) = await MacFiles.TryUnpackAsync(path, sr.Share.Overwrite, sr.Profile.Kind == ClientKind.Mac, sr.Ctx.Aborted);
if (result == MacFiles.Unpacked.Unpacked)
log.Info($"{sr.Ctx.Request.RemoteEndPoint?.Address} mac {Path.GetFileName(path)} -> {name}");
else if (result == MacFiles.Unpacked.ExistsKept)
log.Warning($"{sr.Ctx.Request.RemoteEndPoint?.Address} mac {Path.GetFileName(path)}: name inside already exists, kept encoded");
return name;
}
catch (Exception e) when (e is IOException or UnauthorizedAccessException or EndOfStreamException)
{
log.Warning($"{sr.Ctx.Request.RemoteEndPoint?.Address} mac {Path.GetFileName(path)}: {e.Message}");
return Path.GetFileName(path);
}
}
/// <summary>Descarga de un fichero de un share, con las adaptaciones del perfil del cliente.</summary>
private async Task SendShareFileAsync(ShareRequest sr, string physical)
{
var (ctx, share, profile) = (sr.Ctx, sr.Share, sr.Profile);
string name = Path.GetFileName(physical);
string? encoding = ctx.Request.GetQuery("as");
bool macFork = share.Mac && MacFiles.HasCompanion(physical);
if (share.Mac && (encoding is "macbin" or "hqx" || (encoding is null && profile.Kind == ClientKind.Mac && macFork)))
{
await SendMacEncodedAsync(ctx, physical, encoding == "hqx", profile);
return;
}
// Nombre de la descarga segun el cliente: 8.3 para DOS y Windows 3.1, 31 caracteres para Mac.
string? downloadName = null;
if (profile.UsesShortNames && share.ShortNames)
{
var dir = Path.GetDirectoryName(physical)!;
downloadName = Shares.ShortNames.Map(new DirectoryInfo(dir).EnumerateFileSystemInfos().Select(i => i.Name)).GetValueOrDefault(name);
}
else if (profile.Kind == ClientKind.Mac && name.Length > 31)
{
downloadName = MacNames.Shorten(name, 31);
}
if (downloadName is not null && downloadName != name)
ctx.Response.Headers["Content-Disposition"] = (ctx.Request.HasQuery("dl") ? "attachment" : "inline") +
$"; filename=\"{AsciiOnly(downloadName)}\"";
await SendFileAsync(ctx, physical);
}
/// <summary>MacBinary III (con Content-Length) o BinHex 4.0 del fichero con su AppleDouble.</summary>
private static async Task SendMacEncodedAsync(HttpContext ctx, string physical, bool binHex, ClientProfile profile)
{
var file = MacFiles.Describe(physical);
long dataLength = new FileInfo(physical).Length;
string baseName = profile.Kind == ClientKind.Mac ? MacNames.Shorten(file.Name, 27) : file.Name;
if (binHex)
{
if (dataLength > MacFiles.MaxBinHex)
{
await SendTextAsync(ctx, 413, Strings.ErrorTitle(413));
return;
}
var text = BinHex.Encode(file, await File.ReadAllBytesAsync(physical, ctx.Aborted));
ctx.Response.Headers["Content-Disposition"] = ContentDisposition(baseName + ".hqx");
await ctx.Response.SendAsync(text, "application/mac-binhex40", ctx.Aborted);
return;
}
ctx.Response.Headers["Content-Type"] = "application/x-macbinary";
ctx.Response.Headers["Content-Disposition"] = ContentDisposition(baseName + ".bin");
var body = await ctx.Response.StartAsync(MacBinary.EncodedLength(dataLength, file.ResourceFork.Length), ctx.Aborted);
if (ctx.Request.IsHead)
return;
await using var data = new FileStream(physical, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, 1, useAsync: true);
await MacBinary.EncodeAsync(file, data, dataLength, body, ctx.Aborted);
}
}