fix: detect no-op document stores via OUTPUT rowset; sanitise regenerated filename
Follow-up to 99bf533 from code review:
- The AffectedRows == 0 guard in Store{Invoice,Reminder}DocumentFileAsync
was dead: fds__setInvoiceFile / fds__setReminderFile run SET NOCOUNT ON,
so ExecuteNonQuery returns -1 whether or not the UPDATE ... WHERE [file]
IS NULL matched. Both procs already return the updated Id through their
OUTPUT clause, so read that result set (getSQLDatatable_async) instead:
empty rowset = no-op store, logged, and the blob upload / FileCreated
event are skipped rather than reporting unpersisted bytes as success.
- inv/rdoc's regenerate path built the download name from the raw
DocumentName; a user-typed title with '/' truncated FileInfo.Name.
SafeDocumentFileName strips editor markup and every invalid filename
char, and the path no longer goes through FileInfo at all.
- Reminder counterpart tests for the store-file failure branches.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -293,7 +293,13 @@ public partial class IntranetController
|
||||
_logger.LogDebug("HandleReportDoc reportid={ReportId} typ={Typ} user={User}", reportid, Form("typ"), UserAccountID);
|
||||
byte[]? content = null;
|
||||
var file = _mfr.GetReportDoc(ref content, reportid);
|
||||
if (file == null || content == null || content.Length == 0)
|
||||
string fileName, mimeType;
|
||||
if (file != null && content is { Length: > 0 })
|
||||
{
|
||||
fileName = file.Name;
|
||||
mimeType = file.MimeType();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Regenerate-on-miss (ADR 0025). Finalised, non-external only: fds__setInvoiceFile
|
||||
// writes WHERE [file] IS NULL, so persisting a draft render here would permanently
|
||||
@@ -310,18 +316,32 @@ public partial class IntranetController
|
||||
_logger.LogWarning("HandleReportDoc: document not found reportid={ReportId} user={User}", reportid, UserAccountID);
|
||||
return StatusCode(404, new { error = "Dokument wurde nicht gefunden" });
|
||||
}
|
||||
string regeneratedName = fdInv.InvoiceRegistration?.getString("DocumentName")
|
||||
.ne($"Rechnung_{fdInv.Id}.pdf") ?? $"Rechnung_{reportid}.pdf";
|
||||
file = new FileInfo(Path.Combine(Path.GetTempPath(), regeneratedName));
|
||||
fileName = SafeDocumentFileName(
|
||||
fdInv.InvoiceRegistration?.getString("DocumentName") ?? "", $"Rechnung_{fdInv.Id.ne(reportid)}.pdf");
|
||||
mimeType = "application/pdf";
|
||||
}
|
||||
_logger.LogDebug("HandleReportDoc found reportid={ReportId} fileName={FileName} mimeType={MimeType} size={Size} user={User}",
|
||||
reportid, file.Name, file.MimeType(), content?.Length ?? 0, UserAccountID);
|
||||
reportid, fileName, mimeType, content.Length, UserAccountID);
|
||||
if (Form("typ") != "img")
|
||||
return await FileContentResultAsync(content!, file.MimeType(), file.Name);
|
||||
return await FileContentResultAsync(content, mimeType, fileName);
|
||||
return await PdfPreviewAsync("inv/rdoc", reportid, async () =>
|
||||
await JSONAsync(new { id = reportid, img = await BuildPdfImageArray(content!) }));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download filename for a regenerated document. DocumentName is derived from the user-typed
|
||||
/// invoice title, so it can carry path separators ("Wartung 2025/26") and stray editor markup;
|
||||
/// mirrors the cleanup <c>FdsMfr.GetReportDoc</c> applies to stored documents.
|
||||
/// </summary>
|
||||
internal static string SafeDocumentFileName(string documentName, string fallback)
|
||||
{
|
||||
string name = (documentName ?? "").Replace("<p>", "").Replace("</p>", "").Trim();
|
||||
if (name.Length == 0) name = fallback;
|
||||
foreach (char c in Path.GetInvalidFileNameChars())
|
||||
name = name.Replace(c, '_');
|
||||
return name.Length == 0 ? fallback : name;
|
||||
}
|
||||
|
||||
private async Task<IActionResult> HandleReportDocByName(string fn, string id, string code)
|
||||
{
|
||||
if (!HasForm("name")) { _logger.LogWarning("HandleReportDocByName: missing 'name' form field user={User}", UserAccountID); return BadRequest400(); }
|
||||
|
||||
Reference in New Issue
Block a user