-
Notifications
You must be signed in to change notification settings - Fork 257
Hello,
I would like to remove or at least change the producer in PDF generation for security reasons.
Is there a possibility to do this? The property PdfDocumentRenderer.PdfDocument.Info.Producer is readonly.
Regards
Karl
All reactions
Replies: 3 comments
Hi,
I was also requested to remove the producer from all PDF file after a security audit. Could you please add a setter on the Producer property ?
Kind regards,
Michaël
All reactions
We had the same requirement, and ended up forking the PdfSharp version to allow this.
But this is kind of sad, and in a way, not allowing this in PDFSharp is not preventing users to actually remove this metadata.
To ease things for users, would it be possible to either:
- have control about the "default" Creator, with the ability to set it to null
- have full control on the Creator property
- have an option to not override the Creator at save-time, and to rely on the value in the pdf.Info.Elements (that could be set through the Elements collection ?)
Regards,
Arnaud
All reactions
I'd like to also have this be supported. We would like to have the ability to use my own metadata entirely since we need to support PDF/UA which is not really posssible with PDFSharp right now. To be able to use our own metadata we have resorted to some byte manipulation but we would be happy to send a PR to address this if the team is open to it.
In case someone is interested here is what we are doing. After saving the pdf to a byte[] we do this:
/// <summary> /// Handles XMP metadata injection for PDF/UA-1 compliance. /// </summary> public static class Metadata { public static byte[] ReplaceMetadataBytes(byte[] fileData, string title, string producer) { var template = LoadMetadataTemplate(); var targetXml = template .Replace("$$TITLE$$", EncodeForXml(title)) .Replace("$$PRODUCER$$", EncodeForXml(producer)); byte[] startTag = Encoding.UTF8.GetBytes("<?xpacket begin="); byte[] endTag = Encoding.UTF8.GetBytes("<?xpacket end=\"w\"?>"); // Locate the metadata block int startIndex = FindBytes(fileData, startTag); int endIndex = FindBytes(fileData, endTag); if (startIndex != -1 && endIndex != -1) { // Determine total available space in the file int originalBlockLength = (endIndex + endTag.Length) - startIndex; byte[] newXmlBytes = Encoding.UTF8.GetBytes(targetXml); // Ensure we fit if (newXmlBytes.Length <= originalBlockLength) { // A. Overwrite with your XML Array.Copy(newXmlBytes, 0, fileData, startIndex, newXmlBytes.Length); // B. Fill the remaining space with blank spaces (Padding) // This keeps the PDF valid by preserving the byte count. int paddingStart = startIndex + newXmlBytes.Length; int paddingLength = originalBlockLength - newXmlBytes.Length; for (int i = 0; i < paddingLength; i++) { fileData[paddingStart + i] = (byte)' '; } } } return fileData; } /// <summary> /// Encodes non-ASCII characters as XML numeric character references. /// </summary> private static string EncodeNonAscii(string text) { var sb = new StringBuilder(); foreach (var c in text) { if (c > 127) { sb.Append($"&#x{(int)c:X};"); } else { sb.Append(c); } } return sb.ToString(); } /// <summary> /// Encodes text for safe XML inclusion. /// </summary> private static string EncodeForXml(string text) { var result = text .Replace("&", "&") .Replace("<", "<") .Replace(">", ">") .Replace("\"", """) .Replace("'", "'"); return EncodeNonAscii(result); } /// <summary> /// Loads the XMP metadata template from embedded resources. /// </summary> private static string LoadMetadataTemplate() { var assembly = Assembly.GetExecutingAssembly(); var resourceName = "metadata_template.xml"; using var stream = assembly.GetManifestResourceStream(resourceName); if (stream == null) { throw new InvalidOperationException($"Embedded resource '{resourceName}' not found."); } using var reader = new StreamReader(stream); return reader.ReadToEnd(); } private static int FindBytes(byte[] haystack, byte[] needle) { for (int i = 0; i <= haystack.Length - needle.Length; i++) { bool match = true; for (int j = 0; j < needle.Length; j++) { if (haystack[i + j] != needle[j]) { match = false; break; } } if (match) { return i; } } return -1; } }