133 lines
5.1 KiB
C#
133 lines
5.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace Paca.Browser.Content;
|
|
|
|
public sealed class MhtmlWebArchivePackager
|
|
{
|
|
public string Package(WebArchivePackageRequest request)
|
|
{
|
|
if (request == null)
|
|
throw new ArgumentNullException(nameof(request));
|
|
|
|
var boundary = "----=_NextPart_" + Guid.NewGuid().ToString("N");
|
|
var sb = new StringBuilder();
|
|
|
|
sb.AppendLine("From: <Saved by PACA Browser>");
|
|
sb.AppendLine($"Snapshot-Content-Location: {request.Url ?? string.Empty}");
|
|
sb.AppendLine($"Subject: {request.Title ?? "Untitled"}");
|
|
sb.AppendLine($"Date: {DateTime.UtcNow:R}");
|
|
sb.AppendLine("MIME-Version: 1.0");
|
|
sb.AppendLine($"Content-Type: multipart/related; boundary=\"{boundary}\"; type=\"text/html\"");
|
|
sb.AppendLine();
|
|
|
|
// HTML Part
|
|
sb.AppendLine($"--{boundary}");
|
|
sb.AppendLine("Content-Type: text/html; charset=\"utf-8\"");
|
|
sb.AppendLine("Content-Transfer-Encoding: 8bit");
|
|
sb.AppendLine($"Content-Location: {request.Url ?? string.Empty}");
|
|
sb.AppendLine();
|
|
sb.AppendLine(request.HtmlContent ?? string.Empty);
|
|
sb.AppendLine();
|
|
|
|
// Resources
|
|
if (request.Resources != null)
|
|
{
|
|
foreach (var res in request.Resources)
|
|
{
|
|
if (res == null) continue;
|
|
var data = res.Data ?? Array.Empty<byte>();
|
|
sb.AppendLine($"--{boundary}");
|
|
sb.AppendLine($"Content-Type: {res.MimeType ?? "application/octet-stream"}");
|
|
sb.AppendLine("Content-Transfer-Encoding: base64");
|
|
sb.AppendLine($"Content-Location: {res.Url ?? string.Empty}");
|
|
sb.AppendLine();
|
|
sb.AppendLine(Convert.ToBase64String(data));
|
|
sb.AppendLine();
|
|
}
|
|
}
|
|
|
|
sb.AppendLine($"--{boundary}--");
|
|
return sb.ToString();
|
|
}
|
|
|
|
public WebArchiveUnpackResult Unpack(string mhtmlContent)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(mhtmlContent))
|
|
{
|
|
return new WebArchiveUnpackResult("Untitled", string.Empty, string.Empty, Array.Empty<WebArchiveResource>());
|
|
}
|
|
|
|
var locMatch = Regex.Match(mhtmlContent, @"Snapshot-Content-Location:\s*([^\r\n]+)", RegexOptions.IgnoreCase);
|
|
var locationUrl = locMatch.Success ? locMatch.Groups[1].Value.Trim() : string.Empty;
|
|
|
|
var subjMatch = Regex.Match(mhtmlContent, @"Subject:\s*([^\r\n]+)", RegexOptions.IgnoreCase);
|
|
var title = subjMatch.Success ? subjMatch.Groups[1].Value.Trim() : "Untitled";
|
|
|
|
var boundMatch = Regex.Match(mhtmlContent, @"boundary=""?([^"";\r\n]+)""?", RegexOptions.IgnoreCase);
|
|
if (!boundMatch.Success)
|
|
{
|
|
return new WebArchiveUnpackResult(title, locationUrl, mhtmlContent, Array.Empty<WebArchiveResource>());
|
|
}
|
|
|
|
var boundary = boundMatch.Groups[1].Value;
|
|
var parts = mhtmlContent.Split(new[] { "--" + boundary }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
string htmlContent = string.Empty;
|
|
var resources = new List<WebArchiveResource>();
|
|
|
|
foreach (var part in parts)
|
|
{
|
|
if (part.StartsWith("--") || string.IsNullOrWhiteSpace(part)) continue;
|
|
|
|
var doubleLine = part.IndexOf("\r\n\r\n", StringComparison.Ordinal);
|
|
if (doubleLine < 0) doubleLine = part.IndexOf("\n\n", StringComparison.Ordinal);
|
|
if (doubleLine < 0) continue;
|
|
|
|
var headers = part.Substring(0, doubleLine);
|
|
var body = part.Substring(doubleLine).Trim();
|
|
|
|
var typeMatch = Regex.Match(headers, @"Content-Type:\s*([^;\r\n]+)", RegexOptions.IgnoreCase);
|
|
var mimeType = typeMatch.Success ? typeMatch.Groups[1].Value.Trim() : "text/html";
|
|
|
|
if (mimeType.Contains("multipart/related", StringComparison.OrdinalIgnoreCase) ||
|
|
headers.Contains("boundary=", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var urlMatch = Regex.Match(headers, @"Content-Location:\s*([^\r\n]+)", RegexOptions.IgnoreCase);
|
|
var resUrl = urlMatch.Success ? urlMatch.Groups[1].Value.Trim() : string.Empty;
|
|
|
|
if (mimeType.Contains("text/html", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
htmlContent = body;
|
|
}
|
|
else
|
|
{
|
|
byte[] data;
|
|
if (headers.Contains("base64", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
try
|
|
{
|
|
data = Convert.FromBase64String(body);
|
|
}
|
|
catch
|
|
{
|
|
data = Encoding.UTF8.GetBytes(body);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
data = Encoding.UTF8.GetBytes(body);
|
|
}
|
|
|
|
resources.Add(new WebArchiveResource(resUrl, mimeType, data));
|
|
}
|
|
}
|
|
|
|
return new WebArchiveUnpackResult(title, locationUrl, htmlContent, resources.AsReadOnly());
|
|
}
|
|
}
|