1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
Author: Vagrant Cascadian <vagrant@reproducible-builds.org>
tony mancill <tmancill@debian.org>
Description: Use SOURCE_DATE_EPOCH environment variable when set instead of
current system time to enable reproducible generation of PDF documents.
If you desire the previous behavior, either unset SOURCE_DATE_EPOCH or
overwrite it to a non-integer value.
Also see: https://reproducible-builds.org/docs/source-date-epoch/
Forwarded: not-needed
Last-Update: 2024-02-18
Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=978499
--- a/fop-core/src/main/java/org/apache/fop/pdf/PDFInfo.java
+++ b/fop-core/src/main/java/org/apache/fop/pdf/PDFInfo.java
@@ -21,6 +21,8 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -305,9 +307,40 @@
* @return the requested String representation
*/
protected static String formatDateTime(final Date time) {
+ final Date sourceDateEpoch = getSourceDateEpoch();
+ if (sourceDateEpoch != null) {
+ return formatDateTime(sourceDateEpoch, TimeZone.getTimeZone("Etc/UTC"));
+ }
return formatDateTime(time, TimeZone.getDefault());
}
+ /** @return a Date initialized from SOURCE_DATE_EPOCH or null if not set */
+ public static Date getSourceDateEpoch() {
+ // https://reproducible-builds.org/docs/source-date-epoch/
+ // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=978499
+ final String sourceDateEpochString = getEnvVar("SOURCE_DATE_EPOCH", null);
+ if (sourceDateEpochString != null) {
+ try {
+ final Long sourcedate = (1000 * Long.parseLong(sourceDateEpochString));
+ return new Date(sourcedate);
+ } catch (NumberFormatException ignored) {
+ // ignored
+ }
+ }
+ return null;
+ }
+
+ private static String getEnvVar(String envVar, String defaultVal) {
+ try {
+ return AccessController.doPrivileged((PrivilegedAction<String>)
+ () -> System.getenv(envVar)
+ );
+ } catch(SecurityException ignored) {
+ // do nothing
+ }
+ return defaultVal;
+ }
+
/**
* Adds a custom property to this Info dictionary.
*/
--- a/fop-core/src/main/java/org/apache/fop/pdf/PDFMetadata.java
+++ b/fop-core/src/main/java/org/apache/fop/pdf/PDFMetadata.java
@@ -135,8 +135,12 @@
//Set creation date if not available, yet
if (info.getCreationDate() == null) {
- Date d = new Date();
- info.setCreationDate(d);
+ final Date sourceDateEpoch = PDFInfo.getSourceDateEpoch();
+ if (sourceDateEpoch != null) {
+ info.setCreationDate(sourceDateEpoch);
+ } else {
+ info.setCreationDate(new Date());
+ }
}
//Important: Acrobat 7's preflight check for PDF/A-1b wants the creation date in the Info
--- a/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
+++ b/fop-core/src/main/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
@@ -261,8 +261,14 @@
}
fopXMP.mergeInto(docXMP, exclude);
XMPBasicAdapter xmpBasic = XMPBasicSchema.getAdapter(docXMP);
- //Metadata was changed so update metadata date
- xmpBasic.setMetadataDate(new java.util.Date());
+ //Metadata was changed so potentially update metadata date
+ final Date sourceDateEpoch = PDFInfo.getSourceDateEpoch();
+ if (sourceDateEpoch != null) {
+ xmpBasic.setMetadataDate(sourceDateEpoch);
+ } else {
+ xmpBasic.setMetadataDate(new Date());
+ }
+
PDFMetadata.updateInfoFromMetadata(docXMP, pdfDoc.getInfo());
PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata(
@@ -481,7 +487,13 @@
augmentDictionary((PDFDictionary)currentPage.get("DPart"), extension);
}
} else if (type == PDFDictionaryType.PagePiece) {
- String date = DateFormatUtil.formatPDFDate(new Date(), TimeZone.getDefault());
+ final Date sourceDateEpoch = PDFInfo.getSourceDateEpoch();
+ final String date;
+ if (sourceDateEpoch != null) {
+ date = DateFormatUtil.formatPDFDate(sourceDateEpoch, TimeZone.getTimeZone("Etc/UTC"));
+ } else {
+ date = DateFormatUtil.formatPDFDate(new Date(), TimeZone.getDefault());
+ }
if (currentPage.get("PieceInfo") == null) {
currentPage.put("PieceInfo", new PDFDictionary());
currentPage.put("LastModified", date);
--- a/fop-core/src/main/java/org/apache/fop/pdf/FileIDGenerator.java
+++ b/fop-core/src/main/java/org/apache/fop/pdf/FileIDGenerator.java
@@ -24,8 +24,11 @@
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
+import java.util.TimeZone;
import java.util.Random;
+import org.apache.xmlgraphics.util.DateFormatUtil;
+
/**
* A class to generate the File Identifier of a PDF document (the ID entry of the file
* trailer dictionary).
@@ -85,8 +88,14 @@
}
private void generateFileID() {
- DateFormat df = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS");
- digest.update(PDFDocument.encode(df.format(new Date())));
+ final Date sourceDateEpoch = PDFInfo.getSourceDateEpoch();
+ if (sourceDateEpoch != null) {
+ digest.update(PDFDocument.encode(
+ DateFormatUtil.formatPDFDate(sourceDateEpoch, TimeZone.getTimeZone("Etc/UTC"))));
+ } else {
+ DateFormat df = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS");
+ digest.update(PDFDocument.encode(df.format(new Date())));
+ }
// Ignoring the filename here for simplicity even though it's recommended
// by the PDF spec
digest.update(PDFDocument.encode(String.valueOf(document.getCurrentFileSize())));
|