Fix invoice-image code-review findings: aspect ratio, whitelist config, stale refs

- MigraDocExtensions: fix a bug where capping an oversized explicit
  width+height image re-derived the other dimension from the image's
  native pixel ratio instead of scaling both sides uniformly, silently
  distorting a deliberately non-native-ratio (e.g. stretched) image.
- InvoiceService: derive the mfr-portal image whitelist prefix from the
  existing Fds:MFR_host config setting (via MFRClientConfig.RestRoot)
  instead of a second hardcoded literal, so it can't drift from the
  actual configured host; parse <img> sources with HtmlAgilityPack
  (already used by the PDF's own HTML converter) instead of a regex, so
  a decoy attribute like data-src can't be picked up in place of the
  real src; dedup fetch URLs with a HashSet instead of O(n^2) List
  scanning; reuse FuchsPdf.MigraDocFilenameFromByteArray instead of
  re-spelling the "base64:" prefix.
- Fix a stale log message and comment left over from the
  GenerateInvoicePdf -> GenerateInvoicePdfAsync rename, and update
  INVOICE_LIFECYCLE.md's references to the old sync method name.
- Add tests for the config-derived whitelist, a decoy-attribute src,
  and the 20-image-per-invoice fetch cap boundary.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 21:03:09 +02:00
co-authored by Claude Sonnet 5
parent c182d4fd69
commit 406c6f2860
6 changed files with 146 additions and 47 deletions
+2 -1
View File
@@ -1166,7 +1166,8 @@ public static class FuchsPdf
img.Top = ShapePosition.Top;
}
private static string MigraDocFilenameFromByteArray(byte[] image) =>
/// <summary>Formats bytes as a MigraDoc-recognized inline image source ("base64:...").</summary>
internal static string MigraDocFilenameFromByteArray(byte[] image) =>
"base64:" + Convert.ToBase64String(image);
private static byte[] ImageToByteArray(System.Drawing.Image img)
+20 -2
View File
@@ -129,8 +129,26 @@ namespace MigraDoc.Extensions.Html
double? wPx = ReadPxDimension(n, "width");
double? hPx = ReadPxDimension(n, "height");
if (wPx is > 0 && hPx is > 0) { img.LockAspectRatio = false; img.Width = PxToUnit(wPx.Value); img.Height = PxToUnit(hPx.Value); }
else if (wPx is > 0) img.Width = PxToUnit(wPx.Value);
if (wPx is > 0 && hPx is > 0)
{
// Both explicit: honour the HTML's (possibly non-native, e.g. a stretched banner)
// aspect ratio. Enforcing the cap must scale both dimensions by the same factor —
// re-enabling LockAspectRatio here would make MigraDoc recompute the other side from
// the image's *native* pixel ratio instead, silently discarding the explicit value.
img.LockAspectRatio = false;
img.Width = PxToUnit(wPx.Value);
img.Height = PxToUnit(hPx.Value);
double scale = Math.Min(1.0, Math.Min(
MaxInlineImageWidth.Centimeter / img.Width.Centimeter,
MaxInlineImageHeight.Centimeter / img.Height.Centimeter));
if (scale < 1.0)
{
img.Width = Unit.FromCentimeter(img.Width.Centimeter * scale);
img.Height = Unit.FromCentimeter(img.Height.Centimeter * scale);
}
return para;
}
if (wPx is > 0) img.Width = PxToUnit(wPx.Value);
else if (hPx is > 0) img.Height = PxToUnit(hPx.Value);
if (img.Width > MaxInlineImageWidth) { img.LockAspectRatio = true; img.Width = MaxInlineImageWidth; }