Skip to content

Navigation Menu

Sign in
Sign up

Remove metadata in PDF generation #172

Unanswered
karlipl asked this question in Q&A
Discussion options

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

You must be logged in to vote

Replies: 3 comments

Comment options

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

You must be logged in to vote
0 replies
Comment options

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:

  1. have control about the "default" Creator, with the ability to set it to null
  2. have full control on the Creator property
  3. 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

You must be logged in to vote
0 replies
Comment options

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("&", "&amp;")
 .Replace("<", "&lt;")
 .Replace(">", "&gt;")
 .Replace("\"", "&quot;")
 .Replace("'", "&apos;");
 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;
 }
}
You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

AltStyle によって変換されたページ (->オリジナル) /