File Coverage

src/pdfmake_meta.c
Criterion Covered Total %
statement 68 116 58.6
branch 39 112 34.8
condition n/a
subroutine n/a
pod n/a
total 107 228 46.9


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_meta.c — Document Information Dictionary implementation.
3             */
4              
5             #include "pdfmake_meta.h"
6             #include "pdfmake_arena.h"
7             #include
8             #include
9             #include
10              
11             /*----------------------------------------------------------------------------
12             * PDF date formatting (§7.9.4)
13             *
14             * Format: D:YYYYMMDDHHmmSSOHH'mm'
15             * YYYY = 4-digit year
16             * MM = month (01-12)
17             * DD = day (01-31)
18             * HH = hour (00-23)
19             * mm = minute (00-59)
20             * SS = second (00-59)
21             * O = relationship to UTC: +, -, or Z
22             * HH'mm' = timezone offset (only if O is + or -)
23             *--------------------------------------------------------------------------*/
24              
25 348           char *pdfmake_format_date(time_t t, char *buf, size_t buflen) {
26             struct tm local_tm;
27             struct tm utc_tm;
28             time_t local_epoch;
29             time_t utc_epoch;
30             long offset_secs;
31             int offset_mins;
32             int n;
33             char sign;
34             int off_hours;
35             int off_mins;
36             int n2;
37              
38 348 50         if (!buf || buflen < 24) return NULL;
    50          
39              
40             #ifdef _WIN32
41             localtime_s(&local_tm, &t);
42             #else
43 348           localtime_r(&t, &local_tm);
44             #endif
45              
46             /* Get UTC time to compute offset */
47             #ifdef _WIN32
48             gmtime_s(&utc_tm, &t);
49             #else
50 348           gmtime_r(&t, &utc_tm);
51             #endif
52              
53             /* Compute timezone offset in minutes */
54 348           local_epoch = mktime(&local_tm);
55 348           utc_epoch = mktime(&utc_tm);
56 348           offset_secs = (long)difftime(local_epoch, utc_epoch);
57 348           offset_mins = (int)(offset_secs / 60);
58              
59             /* Format base date part */
60 348           n = snprintf(buf, buflen, "D:%04d%02d%02d%02d%02d%02d",
61 348           local_tm.tm_year + 1900,
62 348           local_tm.tm_mon + 1,
63             local_tm.tm_mday,
64             local_tm.tm_hour,
65             local_tm.tm_min,
66             local_tm.tm_sec);
67              
68 348 50         if (n < 0 || (size_t)n >= buflen) return NULL;
    50          
69              
70             /* Append timezone */
71 348 50         if (offset_mins == 0) {
72             /* UTC */
73 348 50         if ((size_t)n + 1 >= buflen) return NULL;
74 348           buf[n++] = 'Z';
75 348           buf[n] = '\0';
76             } else {
77             /* Offset: +HH'mm' or -HH'mm' */
78 0 0         sign = (offset_mins >= 0) ? '+' : '-';
79 0 0         if (offset_mins < 0) offset_mins = -offset_mins;
80 0           off_hours = offset_mins / 60;
81 0           off_mins = offset_mins % 60;
82              
83 0           n2 = snprintf(buf + n, buflen - (size_t)n, "%c%02d'%02d'",
84             sign, off_hours, off_mins);
85 0 0         if (n2 < 0 || (size_t)(n + n2) >= buflen) return NULL;
    0          
86             }
87              
88 348           return buf;
89             }
90              
91 0           char *pdfmake_format_date_utc(time_t t, char *buf, size_t buflen) {
92             struct tm utc_tm;
93             int n;
94              
95 0 0         if (!buf || buflen < 18) return NULL;
    0          
96              
97             #ifdef _WIN32
98             gmtime_s(&utc_tm, &t);
99             #else
100 0           gmtime_r(&t, &utc_tm);
101             #endif
102              
103 0           n = snprintf(buf, buflen, "D:%04d%02d%02d%02d%02d%02dZ",
104 0           utc_tm.tm_year + 1900,
105 0           utc_tm.tm_mon + 1,
106             utc_tm.tm_mday,
107             utc_tm.tm_hour,
108             utc_tm.tm_min,
109             utc_tm.tm_sec);
110              
111 0 0         if (n < 0 || (size_t)n >= buflen) return NULL;
    0          
112              
113 0           return buf;
114             }
115              
116             /*----------------------------------------------------------------------------
117             * Internal: ensure doc has an Info dictionary
118             *--------------------------------------------------------------------------*/
119              
120 705           static pdfmake_obj_t *ensure_info_dict(pdfmake_doc_t *doc) {
121             pdfmake_arena_t *arena;
122             pdfmake_obj_t info;
123             uint32_t num;
124 705 50         if (!doc) return NULL;
125              
126             /* If we already have an Info object, return its dict */
127 705 100         if (doc->info_num != 0) {
128 515           pdfmake_obj_t *obj = pdfmake_doc_get(doc, doc->info_num);
129 515 50         if (obj && obj->kind == PDFMAKE_DICT) {
    100          
130 514           return obj;
131             }
132             }
133              
134             /* Create a new Info dictionary */
135 191           arena = pdfmake_doc_arena(doc);
136 191           info = pdfmake_dict_new(arena);
137 191 50         if (info.kind != PDFMAKE_DICT) return NULL;
138              
139             /* Add to document as indirect object */
140 191           num = pdfmake_doc_add(doc, info);
141 191 50         if (num == 0) return NULL;
142              
143             /* Set as Info reference in trailer */
144 191           pdfmake_doc_set_info(doc, num, 0);
145              
146             /* Return pointer to the dict in the indirect object table */
147 191           return pdfmake_doc_get(doc, num);
148             }
149              
150             /*----------------------------------------------------------------------------
151             * Generic metadata set/get
152             *--------------------------------------------------------------------------*/
153              
154 705           pdfmake_err_t pdfmake_meta_set(pdfmake_doc_t *doc, const char *key, const char *value) {
155             pdfmake_obj_t *info;
156             pdfmake_arena_t *arena;
157             uint32_t key_id;
158             pdfmake_obj_t val;
159              
160 705 50         if (!doc || !key) return PDFMAKE_EINVAL;
    50          
161              
162 705           info = ensure_info_dict(doc);
163 705 50         if (!info) return PDFMAKE_ENOMEM;
164              
165 705           arena = pdfmake_doc_arena(doc);
166              
167             /* Intern the key name */
168 705           key_id = pdfmake_arena_intern_name(arena, key, strlen(key));
169 705 50         if (key_id == 0) return PDFMAKE_ENOMEM;
170              
171             /* Create string value (or null if value is NULL) */
172 705 50         if (value) {
173 705           val = pdfmake_str_cstr(arena, value);
174 705 50         if (val.kind != PDFMAKE_STR) return PDFMAKE_ENOMEM;
175             } else {
176 0           val = pdfmake_null();
177             }
178              
179             /* Set in dict */
180 705 50         if (!pdfmake_dict_set(arena, info, key_id, val)) {
181 0           return PDFMAKE_ENOMEM;
182             }
183              
184 705           return PDFMAKE_OK;
185             }
186              
187 589           const char *pdfmake_meta_get(pdfmake_doc_t *doc, const char *key) {
188             pdfmake_obj_t *info;
189             pdfmake_arena_t *arena;
190             uint32_t key_id;
191             pdfmake_obj_t *val;
192             size_t len;
193             const uint8_t *bytes;
194              
195 589 50         if (!doc || !key || doc->info_num == 0) return NULL;
    50          
    100          
196              
197 430           info = pdfmake_doc_get(doc, doc->info_num);
198 430 50         if (!info || info->kind != PDFMAKE_DICT) return NULL;
    100          
199              
200 429           arena = pdfmake_doc_arena(doc);
201              
202             /* Intern the key name to look it up */
203 429           key_id = pdfmake_arena_intern_name(arena, key, strlen(key));
204 429 50         if (key_id == 0) return NULL;
205              
206 429           val = pdfmake_dict_get(info, key_id);
207 429 100         if (!val || val->kind != PDFMAKE_STR) return NULL;
    50          
208              
209             /* Return the string bytes (null-terminated by str_cstr) */
210 64           bytes = pdfmake_get_str_bytes(val, &len);
211             (void)len;
212 64           return (const char *)bytes;
213             }
214              
215             /*----------------------------------------------------------------------------
216             * Date field setters
217             *--------------------------------------------------------------------------*/
218              
219 174           pdfmake_err_t pdfmake_meta_set_creation_date(pdfmake_doc_t *doc, time_t t) {
220             char buf[32];
221 174 50         if (!pdfmake_format_date(t, buf, sizeof(buf))) {
222 0           return PDFMAKE_EINVAL;
223             }
224 174           return pdfmake_meta_set(doc, PDFMAKE_META_CREATION_DATE, buf);
225             }
226              
227 174           pdfmake_err_t pdfmake_meta_set_mod_date(pdfmake_doc_t *doc, time_t t) {
228             char buf[32];
229 174 50         if (!pdfmake_format_date(t, buf, sizeof(buf))) {
230 0           return PDFMAKE_EINVAL;
231             }
232 174           return pdfmake_meta_set(doc, PDFMAKE_META_MOD_DATE, buf);
233             }
234              
235             /*----------------------------------------------------------------------------
236             * Trapped field (name value)
237             *--------------------------------------------------------------------------*/
238              
239 0           pdfmake_err_t pdfmake_meta_set_trapped(pdfmake_doc_t *doc, pdfmake_trapped_t trapped) {
240             pdfmake_obj_t *info;
241             pdfmake_arena_t *arena;
242             uint32_t key_id;
243             const char *name_str;
244             pdfmake_obj_t val;
245              
246 0 0         if (!doc) return PDFMAKE_EINVAL;
247              
248 0           info = ensure_info_dict(doc);
249 0 0         if (!info) return PDFMAKE_ENOMEM;
250              
251 0           arena = pdfmake_doc_arena(doc);
252              
253             /* Intern the Trapped key */
254 0           key_id = pdfmake_arena_intern_name(arena, PDFMAKE_META_TRAPPED, 7);
255 0 0         if (key_id == 0) return PDFMAKE_ENOMEM;
256              
257             /* Create name value */
258 0           switch (trapped) {
259 0           case PDFMAKE_TRAPPED_TRUE: name_str = "True"; break;
260 0           case PDFMAKE_TRAPPED_FALSE: name_str = "False"; break;
261 0           default: name_str = "Unknown"; break;
262             }
263              
264 0           val = pdfmake_name_cstr(arena, name_str);
265 0 0         if (val.kind != PDFMAKE_NAME) return PDFMAKE_ENOMEM;
266              
267 0 0         if (!pdfmake_dict_set(arena, info, key_id, val)) {
268 0           return PDFMAKE_ENOMEM;
269             }
270              
271 0           return PDFMAKE_OK;
272             }
273              
274 0           pdfmake_trapped_t pdfmake_meta_get_trapped(pdfmake_doc_t *doc) {
275             pdfmake_obj_t *info;
276             pdfmake_arena_t *arena;
277             uint32_t key_id;
278             pdfmake_obj_t *val;
279             const char *name;
280              
281 0 0         if (!doc || doc->info_num == 0) return PDFMAKE_TRAPPED_UNKNOWN;
    0          
282              
283 0           info = pdfmake_doc_get(doc, doc->info_num);
284 0 0         if (!info || info->kind != PDFMAKE_DICT) return PDFMAKE_TRAPPED_UNKNOWN;
    0          
285              
286 0           arena = pdfmake_doc_arena(doc);
287              
288 0           key_id = pdfmake_arena_intern_name(arena, PDFMAKE_META_TRAPPED, 7);
289 0 0         if (key_id == 0) return PDFMAKE_TRAPPED_UNKNOWN;
290              
291 0           val = pdfmake_dict_get(info, key_id);
292 0 0         if (!val || val->kind != PDFMAKE_NAME) return PDFMAKE_TRAPPED_UNKNOWN;
    0          
293              
294 0           name = pdfmake_get_name_bytes(arena, val);
295 0 0         if (!name) return PDFMAKE_TRAPPED_UNKNOWN;
296              
297 0 0         if (strcmp(name, "True") == 0) return PDFMAKE_TRAPPED_TRUE;
298 0 0         if (strcmp(name, "False") == 0) return PDFMAKE_TRAPPED_FALSE;
299 0           return PDFMAKE_TRAPPED_UNKNOWN;
300             }
301              
302             /*----------------------------------------------------------------------------
303             * Auto-metadata (called during write)
304             *--------------------------------------------------------------------------*/
305              
306             /* Producer string constant */
307             #ifndef PDFMAKE_VERSION
308             #define PDFMAKE_VERSION "0.03"
309             #endif
310              
311 174           void pdfmake_meta_auto_fill(pdfmake_doc_t *doc) {
312             time_t now;
313              
314 174 50         if (!doc) return;
315              
316 174           now = time(NULL);
317              
318             /* Set Producer if not already set */
319 174 100         if (!pdfmake_meta_get(doc, PDFMAKE_META_PRODUCER)) {
320 172           pdfmake_meta_set(doc, PDFMAKE_META_PRODUCER, "PDF-Make/" PDFMAKE_VERSION);
321             }
322              
323             /* Set CreationDate if not already set */
324 174 50         if (!pdfmake_meta_get(doc, PDFMAKE_META_CREATION_DATE)) {
325 174           pdfmake_meta_set_creation_date(doc, now);
326             }
327              
328             /* Set ModDate if not already set. Callers who need to refresh it
329             * (e.g. after an edit) should call pdfmake_meta_set_mod_date
330             * explicitly; leaving it untouched here keeps the serialized bytes
331             * deterministic across multiple writes of the same doc — required
332             * for the two-pass TSA signing flow where pass 1's RSA imprint must
333             * match pass 2's RSA bytes. */
334 174 50         if (!pdfmake_meta_get(doc, PDFMAKE_META_MOD_DATE)) {
335 174           pdfmake_meta_set_mod_date(doc, now);
336             }
337             }