/src/ghostpdl/devices/vector/gdevpdfe.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright (C) 2001-2022 Artifex Software, Inc. |
2 | | All Rights Reserved. |
3 | | |
4 | | This software is provided AS-IS with no warranty, either express or |
5 | | implied. |
6 | | |
7 | | This software is distributed under license and may not be copied, |
8 | | modified or distributed except as expressly authorized under the terms |
9 | | of the license contained in the file LICENSE in this distribution. |
10 | | |
11 | | Refer to licensing information at http://www.artifex.com or contact |
12 | | Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato, |
13 | | CA 94945, U.S.A., +1(415)492-9861, for further information. |
14 | | */ |
15 | | |
16 | | |
17 | | /* Metadata writer. */ |
18 | | #include "gx.h" |
19 | | #include "gserrors.h" |
20 | | #include "string_.h" |
21 | | #include "time_.h" |
22 | | #include "stream.h" |
23 | | #include "gp.h" |
24 | | #include "smd5.h" |
25 | | #include "gscdefs.h" |
26 | | #include "gdevpdfx.h" |
27 | | #include "gdevpdfg.h" |
28 | | #include "gdevpdfo.h" |
29 | | |
30 | | static char PDFDocEncodingLookup [92] = { |
31 | | 0x20, 0x22, 0x20, 0x20, 0x20, 0x21, 0x20, 0x26, |
32 | | 0x20, 0x14, 0x20, 0x13, 0x01, 0x92, 0x20, 0x44, |
33 | | 0x20, 0x39, 0x20, 0x3A, 0x22, 0x12, 0x20, 0x30, |
34 | | 0x20, 0x1E, 0x20, 0x1C, 0x20, 0x1D, 0x20, 0x18, |
35 | | 0x20, 0x19, 0x20, 0x1A, 0x21, 0x22, 0xFB, 0x01, |
36 | | 0xFB, 0x02, 0x01, 0x41, 0x01, 0x52, 0x01, 0x60, |
37 | | 0x01, 0x78, 0x01, 0x7D, 0x01, 0x31, 0x01, 0x42, |
38 | | 0x01, 0x53, 0x01, 0x61, 0x01, 0x7E, 0x00, 0x00, |
39 | | 0x20, 0xAC, 0x00, 0xA1, 0x00, 0xA2, 0x00, 0xA3, |
40 | | 0x00, 0xA4, 0x00, 0xA5, 0x00, 0xA6, 0x00, 0xA7, |
41 | | 0x00, 0xA8, 0x00, 0xA9, 0x00, 0xAA, 0x00, 0xAB, |
42 | | 0x00, 0xAC, 0x00, 0x00 |
43 | | }; |
44 | | |
45 | | static void |
46 | | copy_bytes(stream *s, const byte **data, int *data_length, int n) |
47 | 11.4k | { |
48 | 36.2k | while (n-- && (*data_length)--) { |
49 | 24.7k | stream_putc(s, *((*data)++)); |
50 | 24.7k | } |
51 | 11.4k | } |
52 | | |
53 | | /* Write XML data */ |
54 | | static void |
55 | | pdf_xml_data_write(stream *s, const byte *data, int data_length) |
56 | 107k | { |
57 | 107k | int l = data_length; |
58 | 107k | const byte *p = data; |
59 | | |
60 | 3.03M | while (l > 0) { |
61 | 2.92M | switch (*p) { |
62 | 92 | case '<' : stream_puts(s, "<"); l--; p++; break; |
63 | 315 | case '>' : stream_puts(s, ">"); l--; p++; break; |
64 | 30 | case '&' : stream_puts(s, "&"); l--; p++; break; |
65 | 551 | case '\'': stream_puts(s, "'"); l--; p++; break; |
66 | 65 | case '"' : stream_puts(s, """); l--; p++; break; |
67 | 2.92M | default: |
68 | 2.92M | if (*p < 32) { |
69 | | /* Not allowed in XML. */ |
70 | 13.8k | pprintd1(s, "&#%d;", *p); |
71 | 13.8k | l--; p++; |
72 | 2.91M | } else if (*p >= 0x7F && *p <= 0x9f) { |
73 | | /* Control characters are discouraged in XML. */ |
74 | 0 | pprintd1(s, "&#%d;", *p); |
75 | 0 | l--; p++; |
76 | 2.91M | } else if ((*p & 0xE0) == 0xC0) { |
77 | | /* A 2-byte UTF-8 sequence */ |
78 | 9.63k | copy_bytes(s, &p, &l, 2); |
79 | 2.90M | } else if ((*p & 0xF0) == 0xE0) { |
80 | | /* A 3-byte UTF-8 sequence */ |
81 | 1.82k | copy_bytes(s, &p, &l, 3); |
82 | 2.90M | } else if ((*p & 0xF0) == 0xF0) { |
83 | | /* A 4-byte UTF-8 sequence */ |
84 | 0 | copy_bytes(s, &p, &l, 4); |
85 | 2.90M | } else { |
86 | 2.90M | stream_putc(s, *p); |
87 | 2.90M | l--; p++; |
88 | 2.90M | } |
89 | 2.92M | } |
90 | 2.92M | } |
91 | 107k | } |
92 | | |
93 | | /* Write XML string */ |
94 | | static inline void |
95 | | pdf_xml_string_write(stream *s, const char *data) |
96 | 89.8k | { |
97 | 89.8k | pdf_xml_data_write(s, (const byte *)data, strlen(data)); |
98 | 89.8k | } |
99 | | |
100 | | /* Begin an opening XML tag */ |
101 | | static inline void |
102 | | pdf_xml_tag_open_beg(stream *s, const char *data) |
103 | 90.6k | { |
104 | 90.6k | stream_putc(s, '<'); |
105 | 90.6k | stream_puts(s, data); |
106 | 90.6k | } |
107 | | |
108 | | /* End an XML tag */ |
109 | | static inline void |
110 | | pdf_xml_tag_end(stream *s) |
111 | 68.6k | { |
112 | 68.6k | stream_putc(s, '>'); |
113 | 68.6k | } |
114 | | |
115 | | /* End an empty XML tag */ |
116 | | static inline void |
117 | | pdf_xml_tag_end_empty(stream *s) |
118 | 22.0k | { |
119 | 22.0k | stream_puts(s, "/>"); |
120 | 22.0k | } |
121 | | |
122 | | /* Write an opening XML tag */ |
123 | | static inline void |
124 | | pdf_xml_tag_open(stream *s, const char *data) |
125 | 26.3k | { |
126 | 26.3k | stream_putc(s, '<'); |
127 | 26.3k | stream_puts(s, data); |
128 | 26.3k | stream_putc(s, '>'); |
129 | 26.3k | } |
130 | | |
131 | | /* Write a closing XML tag */ |
132 | | static inline void |
133 | | pdf_xml_tag_close(stream *s, const char *data) |
134 | 95.0k | { |
135 | 95.0k | stream_puts(s, "</"); |
136 | 95.0k | stream_puts(s, data); |
137 | 95.0k | stream_putc(s, '>'); |
138 | 95.0k | } |
139 | | |
140 | | /* Write an attribute name */ |
141 | | static inline void |
142 | | pdf_xml_attribute_name(stream *s, const char *data) |
143 | 111k | { |
144 | 111k | stream_putc(s, ' '); |
145 | 111k | stream_puts(s, data); |
146 | 111k | stream_putc(s, '='); |
147 | 111k | } |
148 | | |
149 | | /* Write a attribute value */ |
150 | | static inline void |
151 | | pdf_xml_attribute_value(stream *s, const char *data) |
152 | 89.8k | { |
153 | 89.8k | stream_putc(s, '\''); |
154 | 89.8k | pdf_xml_string_write(s, data); |
155 | 89.8k | stream_putc(s, '\''); |
156 | 89.8k | } |
157 | | /* Write a attribute value */ |
158 | | static inline void |
159 | | pdf_xml_attribute_value_data(stream *s, const byte *data, int data_length) |
160 | 10.8k | { |
161 | 10.8k | stream_putc(s, '\''); |
162 | 10.8k | pdf_xml_data_write(s, data, data_length); |
163 | 10.8k | stream_putc(s, '\''); |
164 | 10.8k | } |
165 | | |
166 | | /* Begin an XML instruction */ |
167 | | static inline void |
168 | | pdf_xml_ins_beg(stream *s, const char *data) |
169 | 11.2k | { |
170 | 11.2k | stream_puts(s, "<?"); |
171 | 11.2k | stream_puts(s, data); |
172 | 11.2k | } |
173 | | |
174 | | /* End an XML instruction */ |
175 | | static inline void |
176 | | pdf_xml_ins_end(stream *s) |
177 | 11.2k | { |
178 | 11.2k | stream_puts(s, "?>"); |
179 | 11.2k | } |
180 | | |
181 | | /* Write a newline character */ |
182 | | static inline void |
183 | | pdf_xml_newline(stream *s) |
184 | 79.4k | { |
185 | 79.4k | stream_puts(s, "\n"); |
186 | 79.4k | } |
187 | | |
188 | | /* Copy to XML output */ |
189 | | static inline void |
190 | | pdf_xml_copy(stream *s, const char *data) |
191 | 168k | { |
192 | 168k | stream_puts(s, data); |
193 | 168k | } |
194 | | |
195 | | /* -------------------------------------------- */ |
196 | | |
197 | | static int |
198 | | pdf_xmp_time(char *buf, int buf_length) |
199 | 0 | { |
200 | | /* We don't write a day time because we don't have a time zone. */ |
201 | 0 | struct tm tms; |
202 | 0 | time_t t; |
203 | 0 | char buf1[4+1+2+1+2+1]; /* yyyy-mm-dd\0 */ |
204 | |
|
205 | | #ifdef CLUSTER |
206 | | memset(&t, 0, sizeof(t)); |
207 | | memset(&tms, 0, sizeof(tms)); |
208 | | #else |
209 | 0 | time(&t); |
210 | 0 | tms = *localtime(&t); |
211 | 0 | #endif |
212 | 0 | gs_snprintf(buf1, sizeof(buf1), |
213 | 0 | "%04d-%02d-%02d", |
214 | 0 | tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday); |
215 | 0 | strncpy(buf, buf1, buf_length); |
216 | 0 | return strlen(buf); |
217 | 0 | } |
218 | | |
219 | | static int |
220 | | pdf_xmp_convert_time(char *dt, int dtl, char *buf, int bufl) |
221 | 22.4k | { /* The 'dt' buffer is of same size as 'buf'. */ |
222 | | /* Input sample : D:199812231952?08'00' */ |
223 | | /* Output sample : 1997-07-16T19:20:30+01:00 */ |
224 | 22.4k | int l = dtl; |
225 | | |
226 | 22.4k | if (l > bufl) |
227 | 0 | l = bufl; |
228 | 22.4k | if (dt[0] == 'D' && dt[1] == ':') { |
229 | 22.4k | l -= 2; |
230 | 22.4k | memcpy(buf, dt + 2, l); |
231 | 22.4k | } else |
232 | 0 | memcpy(buf, dt, l); |
233 | 22.4k | memcpy(dt, buf, 4); /* year */ |
234 | 22.4k | if (l <= 4) |
235 | 0 | return 4; |
236 | | |
237 | 22.4k | dt[4] = '-'; |
238 | 22.4k | memcpy(dt + 5, buf + 4, 2); /* month */ |
239 | 22.4k | if (l <= 6) |
240 | 0 | return 7; |
241 | | |
242 | 22.4k | dt[7] = '-'; |
243 | 22.4k | memcpy(dt + 8, buf + 6, 2); /* day */ |
244 | 22.4k | if (l <= 8) |
245 | 0 | return 10; |
246 | | |
247 | 22.4k | dt[10] = 'T'; |
248 | 22.4k | memcpy(dt + 11, buf + 8, 2); /* hour */ |
249 | 22.4k | dt[13] = ':'; |
250 | 22.4k | memcpy(dt + 14, buf + 10, 2); /* minute */ |
251 | 22.4k | if (l <= 12) { |
252 | 0 | dt[16] = 'Z'; /* Default time zone 0. */ |
253 | 0 | return 17; |
254 | 0 | } |
255 | | |
256 | 22.4k | dt[16] = ':'; |
257 | 22.4k | memcpy(dt + 17, buf + 12, 2); /* second */ |
258 | 22.4k | if (l <= 14) { |
259 | 0 | dt[19] = 'Z'; /* Default time zone 0. */ |
260 | 0 | return 20; |
261 | 0 | } |
262 | | |
263 | 22.4k | dt[19] = buf[14]; /* designator */ |
264 | 22.4k | if (dt[19] == 'Z') |
265 | 22.4k | return 20; |
266 | 0 | if (l <= 15) |
267 | 0 | return 20; |
268 | 0 | memcpy(dt + 20, buf + 15, 2); /* Time zone hour difference. */ |
269 | 0 | if (l <= 17) |
270 | 0 | return 22; |
271 | | |
272 | 0 | dt[22] = ':'; |
273 | | /* Skipping '\'' in 'buf'. */ |
274 | 0 | memcpy(dt + 23, buf + 18, 2); /* Time zone minutes difference. */ |
275 | 0 | return 25; |
276 | 0 | } |
277 | | |
278 | | int |
279 | | pdf_get_docinfo_item(gx_device_pdf *pdev, const char *key, char *buf, int buf_length) |
280 | 27.2k | { |
281 | 27.2k | const cos_value_t *v = cos_dict_find(pdev->Info, (const byte *)key, strlen(key)); |
282 | 27.2k | int l; |
283 | 27.2k | const byte *s; |
284 | | |
285 | 27.2k | if (v != NULL && (v->value_type == COS_VALUE_SCALAR || |
286 | 27.2k | v->value_type == COS_VALUE_CONST)) { |
287 | 27.2k | if (v->contents.chars.size >= 2 && v->contents.chars.data[0] == '(') { |
288 | 27.2k | s = v->contents.chars.data + 1; |
289 | 27.2k | l = v->contents.chars.size - 2; |
290 | 27.2k | } else { |
291 | 0 | s = v->contents.chars.data; |
292 | 0 | l = v->contents.chars.size; |
293 | 0 | } |
294 | 27.2k | } else |
295 | 0 | return 0; |
296 | 27.2k | if (l < 0) |
297 | 0 | l = 0; |
298 | 27.2k | if (l > buf_length) |
299 | 0 | l = buf_length; |
300 | 27.2k | memcpy(buf, s, l); |
301 | 27.2k | return l; |
302 | 27.2k | } |
303 | | |
304 | | static inline byte |
305 | | decode_escape(const byte *data, int data_length, size_t *index) |
306 | 27.9k | { |
307 | 27.9k | byte c; |
308 | | |
309 | 27.9k | (*index)++; /* skip '\' */ |
310 | 27.9k | if (*index >= data_length) |
311 | 0 | return 0; /* Must_not_happen, because the string is PS encoded. */ |
312 | 27.9k | c = data[*index]; |
313 | 27.9k | switch (c) { |
314 | 1.14k | case '(': return '('; |
315 | 681 | case ')': return ')'; |
316 | 1.18k | case '\\': return '\\'; |
317 | 8 | case 'n': return '\n'; |
318 | 4 | case 'r': return '\r'; |
319 | 164 | case 't': return '\t'; |
320 | 5 | case 'b': return '\b'; |
321 | 0 | case 'f': return '\f'; |
322 | 24.7k | default: |
323 | 24.7k | break; |
324 | 27.9k | } |
325 | 24.7k | if (c >= '0' && c <= '7') { |
326 | 24.7k | int oct_loop; |
327 | | /* octal */ |
328 | 24.7k | byte v = c - '0'; |
329 | | |
330 | | /* Octal values should always be three digits, one is consumed above! */ |
331 | 74.3k | for (oct_loop = 0;oct_loop < 2; oct_loop++) { |
332 | 49.5k | (*index)++; |
333 | 49.5k | if (*index >= data_length) |
334 | | /* Ran out of data, return what we found */ |
335 | 0 | return v; |
336 | 49.5k | c = data[*index]; |
337 | 49.5k | if (c < '0' || c > '7') { |
338 | | /* Ran out of numeric data, return what we found */ |
339 | | /* Need to 'unget' the non-numeric character */ |
340 | 0 | (*index)--; |
341 | 0 | break; |
342 | 0 | } |
343 | 49.5k | v = v * 8 + (c - '0'); |
344 | 49.5k | } |
345 | 24.7k | return v; |
346 | 24.7k | } |
347 | 0 | return c; /* A wrong escapement sequence. */ |
348 | 24.7k | } |
349 | | |
350 | | /* |
351 | | * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed |
352 | | * into the first byte, depending on how many bytes follow. There are |
353 | | * as many entries in this table as there are UTF-8 sequence types. |
354 | | * (I.e., one byte sequence, two byte... etc.). Remember that sequencs |
355 | | * for *legal* UTF-8 will be 4 or fewer bytes total. |
356 | | */ |
357 | | static const char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; |
358 | | |
359 | | static int gs_ConvertUTF16(unsigned char *UTF16, size_t UTF16Len, unsigned char **UTF8Start, int UTF8Len) |
360 | 17.6k | { |
361 | 17.6k | size_t i, bytes = 0; |
362 | 17.6k | unsigned short U16; |
363 | 17.6k | unsigned char *UTF8 = *UTF8Start; |
364 | 17.6k | unsigned char *UTF8End = UTF8 + UTF8Len; |
365 | | |
366 | 17.6k | if (UTF16Len % sizeof(short) != 0) |
367 | 0 | return gs_note_error(gs_error_rangecheck); |
368 | | |
369 | 610k | for (i=0;i<UTF16Len / sizeof(short);i++) |
370 | 592k | { |
371 | 592k | U16 = (*UTF16++) << 8; |
372 | 592k | U16 += *UTF16++; |
373 | | |
374 | 592k | if (U16 >= 0xD800 && U16 <= 0xDBFF) { |
375 | 0 | return gs_note_error(gs_error_rangecheck); |
376 | 0 | } |
377 | 592k | if (U16 >= 0xDC00 && U16 <= 0xDFFF) { |
378 | 0 | return gs_note_error(gs_error_rangecheck); |
379 | 0 | } |
380 | | |
381 | 592k | if(U16 < 0x80) { |
382 | 581k | bytes = 1; |
383 | 581k | } else { |
384 | 11.4k | if (U16 < 0x800) { |
385 | 9.63k | bytes = 2; |
386 | 9.63k | } else { |
387 | 1.82k | bytes = 3; |
388 | 1.82k | U16 = 0xFFFD; |
389 | 1.82k | } |
390 | 11.4k | } |
391 | 592k | if (UTF8 + bytes > UTF8End) |
392 | 0 | return gs_note_error(gs_error_VMerror); |
393 | | |
394 | | /* Write from end to beginning, low bytes first */ |
395 | 592k | UTF8 += bytes; |
396 | | |
397 | 592k | switch(bytes) { |
398 | 1.82k | case 3: |
399 | 1.82k | *--UTF8 = (unsigned char)((U16 | 0x80) & 0xBF); |
400 | 1.82k | U16 >>= 6; |
401 | 11.4k | case 2: |
402 | 11.4k | *--UTF8 = (unsigned char)((U16 | 0x80) & 0xBF); |
403 | 11.4k | U16 >>= 6; |
404 | 592k | case 1: |
405 | 592k | *--UTF8 = (unsigned char)(U16 | firstByteMark[bytes]); |
406 | 592k | break; |
407 | 0 | default: |
408 | 0 | return gs_note_error(gs_error_rangecheck); |
409 | 592k | } |
410 | | |
411 | | /* Move to start of next set */ |
412 | 592k | UTF8 += bytes; |
413 | 592k | } |
414 | 17.6k | *UTF8Start = UTF8; |
415 | 17.6k | return 0; |
416 | 17.6k | } |
417 | | |
418 | | static int |
419 | | pdf_xmp_write_translated(gx_device_pdf *pdev, stream *s, const byte *data, int data_length, |
420 | | void(*write)(stream *s, const byte *data, int data_length)) |
421 | 18.0k | { |
422 | 18.0k | size_t i, j=0; |
423 | 18.0k | unsigned char *buf0; |
424 | | |
425 | 18.0k | if (data_length == 0) |
426 | 429 | return 0; |
427 | | |
428 | 17.6k | buf0 = (unsigned char *)gs_alloc_bytes(pdev->memory, data_length * sizeof(unsigned char), |
429 | 17.6k | "pdf_xmp_write_translated"); |
430 | 17.6k | if (buf0 == NULL) |
431 | 0 | return_error(gs_error_VMerror); |
432 | 612k | for (i = 0; i < (size_t)data_length; i++) { |
433 | 594k | byte c = data[i]; |
434 | | |
435 | 594k | if (c == '\\') |
436 | 27.9k | c = decode_escape(data, data_length, &i); |
437 | 594k | buf0[j] = c; |
438 | 594k | j++; |
439 | 594k | } |
440 | 17.6k | if (buf0[0] != 0xfe || buf0[1] != 0xff) { |
441 | 17.5k | unsigned char *buf1; |
442 | | /* We must assume that the information is PDFDocEncoding. In this case |
443 | | * we need to convert it into UTF-8. If we just convert it to UTF-16 |
444 | | * then we can safely fall through to the code below. |
445 | | */ |
446 | | /* NB the code below skips the BOM in positions 0 and 1, so we need |
447 | | * two extra bytes, to be ignored. |
448 | | */ |
449 | 17.5k | buf1 = (unsigned char *)gs_alloc_bytes(pdev->memory, (j * sizeof(short)) + 2, |
450 | 17.5k | "pdf_xmp_write_translated"); |
451 | 17.5k | if (buf1 == NULL) { |
452 | 0 | gs_free_object(pdev->memory, buf0, "pdf_xmp_write_translated"); |
453 | 0 | return_error(gs_error_VMerror); |
454 | 0 | } |
455 | 17.5k | memset(buf1, 0x00, (j * sizeof(short)) + 2); |
456 | 608k | for (i = 0; i < j; i++) { |
457 | 591k | if (buf0[i] <= 0x7f || buf0[i] >= 0xAE) { |
458 | 587k | if (buf0[i] == 0x7f) { |
459 | 2.05k | emprintf1(pdev->memory, "PDFDocEncoding %x cannot be represented in Unicode\n", |
460 | 2.05k | buf0[i]); |
461 | 2.05k | } else |
462 | 585k | buf1[(i * 2) + 3] = buf0[i]; |
463 | 587k | } else { |
464 | 3.33k | buf1[(i * 2) + 2] = PDFDocEncodingLookup[(buf0[i] - 0x80) * 2]; |
465 | 3.33k | buf1[(i * 2) + 3] = PDFDocEncodingLookup[((buf0[i] - 0x80) * 2) + 1]; |
466 | 3.33k | if (PDFDocEncodingLookup[((buf0[i] - 0x80) * 2) + 1] == 0x00) |
467 | 42 | emprintf1(pdev->memory, "PDFDocEncoding %x cannot be represented in Unicode\n", |
468 | 3.33k | PDFDocEncodingLookup[((buf0[i] - 0x80) * 2) + 1]); |
469 | 3.33k | } |
470 | 591k | } |
471 | 17.5k | gs_free_object(pdev->memory, buf0, "pdf_xmp_write_translated"); |
472 | 17.5k | buf0 = buf1; |
473 | 17.5k | data_length = j = (j * 2) + 2; |
474 | 17.5k | } |
475 | 17.6k | { |
476 | | /* Its a Unicode (UTF-16BE) string, convert to UTF-8 */ |
477 | 17.6k | short *buf0b; |
478 | 17.6k | char *buf1, *buf1b; |
479 | 17.6k | int code; |
480 | | |
481 | | /* A single UTF-16 (2 bytes) can end up as 4 bytes in UTF-8 */ |
482 | 17.6k | buf1 = (char *)gs_alloc_bytes(pdev->memory, data_length * 2 * sizeof(unsigned char), |
483 | 17.6k | "pdf_xmp_write_translated"); |
484 | 17.6k | if (buf1 == NULL) { |
485 | 0 | gs_free_object(pdev->memory, buf0, "pdf_xmp_write_translated"); |
486 | 0 | return_error(gs_error_VMerror); |
487 | 0 | } |
488 | 17.6k | buf1b = buf1; |
489 | | /* Skip the Byte Order Mark (0xfe 0xff) */ |
490 | 17.6k | buf0b = (short *)(buf0 + 2); |
491 | 17.6k | code = gs_ConvertUTF16((unsigned char *)buf0b, j - 2, (unsigned char **)&buf1b, data_length * 2 * sizeof(unsigned char)); |
492 | 17.6k | if (code < 0) { |
493 | 0 | gs_free_object(pdev->memory, buf0, "pdf_xmp_write_translated"); |
494 | 0 | gs_free_object(pdev->memory, buf1, "pdf_xmp_write_translated"); |
495 | 0 | return code; |
496 | 0 | } |
497 | 17.6k | write(s, (const byte *)buf1, buf1b - buf1); |
498 | 17.6k | gs_free_object(pdev->memory, buf1, "pdf_xmp_write_translated"); |
499 | 17.6k | } |
500 | 17.6k | gs_free_object(pdev->memory, buf0, "pdf_xmp_write_translated"); |
501 | 17.6k | return 0; |
502 | 17.6k | } |
503 | | |
504 | | static int |
505 | | pdf_xmp_write_docinfo_item(gx_device_pdf *pdev, stream *s, const char *key, const char *default_value, |
506 | | void(*write)(stream *s, const byte *data, int data_length)) |
507 | 35.4k | { |
508 | 35.4k | const cos_value_t *v = cos_dict_find(pdev->Info, (const byte *)key, strlen(key)); |
509 | | |
510 | 35.4k | if (v != NULL && (v->value_type == COS_VALUE_SCALAR || |
511 | 18.0k | v->value_type == COS_VALUE_CONST)) { |
512 | 18.0k | if (v->contents.chars.size >= 2 && v->contents.chars.data[0] == '(') |
513 | 18.0k | return pdf_xmp_write_translated(pdev, s, v->contents.chars.data + 1, |
514 | 18.0k | v->contents.chars.size - 2, write); |
515 | 14 | else |
516 | 14 | return pdf_xmp_write_translated(pdev, s, v->contents.chars.data, |
517 | 14 | v->contents.chars.size, write); |
518 | 18.0k | } else { |
519 | 17.3k | stream_puts(s, default_value); |
520 | 17.3k | return 0; |
521 | 17.3k | } |
522 | 35.4k | } |
523 | | |
524 | | static uint64_t |
525 | | pdf_uuid_time(gx_device_pdf *pdev) |
526 | 22.4k | { |
527 | 22.4k | long *dt = pdev->uuid_time; /* In seconds since Jan. 1, 1980 and fraction in nanoseconds. */ |
528 | 22.4k | uint64_t t; |
529 | | |
530 | | /* UUIDs use time in 100ns ticks since Oct 15, 1582. */ |
531 | 22.4k | t = (uint64_t)10000000 * dt[0] + dt[0] / 100; /* since Jan. 1, 1980 */ |
532 | 22.4k | t += (uint64_t) (1000*1000*10) /* seconds */ |
533 | 22.4k | * (uint64_t) (60 * 60 * 24) /* days */ |
534 | 22.4k | * (uint64_t) (17+30+31+365*397+99); /* # of days */ |
535 | 22.4k | return t; |
536 | 22.4k | } |
537 | | |
538 | | static void writehex(char **p, ulong v, int l) |
539 | 247k | { |
540 | 247k | int i = l * 2; |
541 | 247k | static const char digit[] = "0123456789abcdef"; |
542 | | |
543 | 965k | for (; i--;) |
544 | 718k | *((*p)++) = digit[v >> (i * 4) & 15]; |
545 | 247k | } |
546 | | |
547 | | static void |
548 | | pdf_make_uuid(const byte node[6], uint64_t uuid_time, ulong time_seq, char *buf, int buf_length) |
549 | 22.4k | { |
550 | 22.4k | char b[45], *p = b; |
551 | 22.4k | ulong uuid_time_lo = (ulong)(uuid_time & 0xFFFFFFFF); /* MSVC 7.1.3088 */ |
552 | 22.4k | ushort uuid_time_md = (ushort)((uuid_time >> 32) & 0xFFFF); /* cannot compile this */ |
553 | 22.4k | ushort uuid_time_hi = (ushort)((uuid_time >> 48) & 0x0FFF); /* as function arguments. */ |
554 | | |
555 | 22.4k | writehex(&p, uuid_time_lo, 4); /* time_low */ |
556 | 22.4k | *(p++) = '-'; |
557 | 22.4k | writehex(&p, uuid_time_md, 2); /* time_mid */ |
558 | 22.4k | *(p++) = '-'; |
559 | 22.4k | writehex(&p, uuid_time_hi | (ushort)(1 << 12), 2); /* time_hi_and_version */ |
560 | 22.4k | *(p++) = '-'; |
561 | 22.4k | writehex(&p, (time_seq & 0x3F00) >> 8, 1); /* clock_seq_hi_and_reserved */ |
562 | 22.4k | writehex(&p, time_seq & 0xFF, 1); /* clock_seq & 0xFF */ |
563 | 22.4k | *(p++) = '-'; |
564 | 22.4k | writehex(&p, node[0], 1); |
565 | 22.4k | writehex(&p, node[1], 1); |
566 | 22.4k | writehex(&p, node[2], 1); |
567 | 22.4k | writehex(&p, node[3], 1); |
568 | 22.4k | writehex(&p, node[4], 1); |
569 | 22.4k | writehex(&p, node[5], 1); |
570 | 22.4k | *p = 0; |
571 | 22.4k | strncpy(buf, b, strlen(b) + 1); |
572 | 22.4k | } |
573 | | |
574 | | static int |
575 | | pdf_make_instance_uuid(gx_device_pdf *pdev, const byte digest[6], char *buf, int buf_length) |
576 | 11.2k | { |
577 | 11.2k | char URI_prefix[5] = "uuid:"; |
578 | | |
579 | 11.2k | memcpy(buf, URI_prefix, 5); |
580 | 11.2k | if (pdev->InstanceUUID.size) { |
581 | 0 | int l = min(buf_length - 6, pdev->InstanceUUID.size); |
582 | |
|
583 | 0 | memcpy(buf+5, pdev->InstanceUUID.data, l); |
584 | 0 | buf[l+5] = 0; |
585 | 0 | } else |
586 | 11.2k | pdf_make_uuid(digest, pdf_uuid_time(pdev), pdev->DocumentTimeSeq, buf + 5, buf_length - 5); |
587 | 11.2k | return 0; |
588 | 11.2k | } |
589 | | |
590 | | static int |
591 | | pdf_make_document_uuid(gx_device_pdf *pdev, const byte digest[6], char *buf, int buf_length) |
592 | 11.2k | { |
593 | 11.2k | char URI_prefix[5] = "uuid:"; |
594 | | |
595 | 11.2k | memcpy(buf, URI_prefix, 5); |
596 | 11.2k | if (pdev->DocumentUUID.size) { |
597 | 0 | int l = min(buf_length - 6, pdev->DocumentUUID.size); |
598 | |
|
599 | 0 | memcpy(buf+5, pdev->DocumentUUID.data, l); |
600 | 0 | buf[l+5] = 0; |
601 | 0 | } else |
602 | 11.2k | pdf_make_uuid(digest, pdf_uuid_time(pdev), pdev->DocumentTimeSeq, buf+5, buf_length - 5); |
603 | 11.2k | return 0; |
604 | 11.2k | } |
605 | | |
606 | | static const char dd[]={'\'', '\357', '\273', '\277', '\'', 0}; |
607 | | |
608 | | /* -------------------------------------------- */ |
609 | | |
610 | | /* Write Document metadata */ |
611 | | static int |
612 | | pdf_write_document_metadata(gx_device_pdf *pdev, const byte digest[6]) |
613 | 11.2k | { |
614 | 11.2k | char instance_uuid[45], document_uuid[45], cre_date_time[40], mod_date_time[40], date_time_buf[40]; |
615 | 11.2k | int cre_date_time_len, mod_date_time_len; |
616 | 11.2k | int code; |
617 | 11.2k | stream *s = pdev->strm; |
618 | | |
619 | 11.2k | code = pdf_make_instance_uuid(pdev, digest, instance_uuid, sizeof(instance_uuid)); |
620 | 11.2k | if (code < 0) |
621 | 0 | return code; |
622 | 11.2k | code = pdf_make_document_uuid(pdev, digest, document_uuid, sizeof(document_uuid)); |
623 | 11.2k | if (code < 0) |
624 | 0 | return code; |
625 | | |
626 | | /* PDF/A XMP reference recommends setting UUID to empty. If not empty must be a URI */ |
627 | 11.2k | if (pdev->PDFA != 0) |
628 | 0 | instance_uuid[0] = 0x00; |
629 | | |
630 | 11.2k | cre_date_time_len = pdf_get_docinfo_item(pdev, "/CreationDate", cre_date_time, sizeof(cre_date_time)); |
631 | 11.2k | if (!cre_date_time_len) |
632 | 0 | cre_date_time_len = pdf_xmp_time(cre_date_time, sizeof(cre_date_time)); |
633 | 11.2k | else |
634 | 11.2k | cre_date_time_len = pdf_xmp_convert_time(cre_date_time, cre_date_time_len, date_time_buf, sizeof(date_time_buf)); |
635 | 11.2k | mod_date_time_len = pdf_get_docinfo_item(pdev, "/ModDate", mod_date_time, sizeof(mod_date_time)); |
636 | 11.2k | if (!mod_date_time_len) |
637 | 0 | mod_date_time_len = pdf_xmp_time(mod_date_time, sizeof(mod_date_time)); |
638 | 11.2k | else |
639 | 11.2k | mod_date_time_len = pdf_xmp_convert_time(mod_date_time, mod_date_time_len, date_time_buf, sizeof(date_time_buf)); |
640 | | |
641 | 11.2k | pdf_xml_ins_beg(s, "xpacket"); |
642 | 11.2k | pdf_xml_attribute_name(s, "begin"); |
643 | 11.2k | pdf_xml_copy(s, dd); |
644 | 11.2k | pdf_xml_attribute_name(s, "id"); |
645 | 11.2k | pdf_xml_attribute_value(s, "W5M0MpCehiHzreSzNTczkc9d"); |
646 | 11.2k | pdf_xml_ins_end(s); |
647 | 11.2k | pdf_xml_newline(s); |
648 | | |
649 | 11.2k | pdf_xml_copy(s, "<?adobe-xap-filters esc=\"CRLF\"?>\n"); |
650 | 11.2k | pdf_xml_copy(s, "<x:xmpmeta xmlns:x='adobe:ns:meta/'" |
651 | 11.2k | " x:xmptk='XMP toolkit 2.9.1-13, framework 1.6'>\n"); |
652 | 11.2k | { |
653 | 11.2k | pdf_xml_copy(s, "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' " |
654 | 11.2k | "xmlns:iX='http://ns.adobe.com/iX/1.0/'>\n"); |
655 | 11.2k | { |
656 | | |
657 | 11.2k | pdf_xml_tag_open_beg(s, "rdf:Description"); |
658 | 11.2k | pdf_xml_copy(s, " rdf:about=\"\""); |
659 | 11.2k | pdf_xml_attribute_name(s, "xmlns:pdf"); |
660 | 11.2k | pdf_xml_attribute_value(s, "http://ns.adobe.com/pdf/1.3/"); |
661 | | |
662 | 11.2k | if (cos_dict_find(pdev->Info, (const byte *)"/Keywords", 9)) { |
663 | 396 | pdf_xml_tag_end(s); |
664 | 396 | pdf_xml_tag_open_beg(s, "pdf:Producer"); |
665 | 396 | pdf_xml_tag_end(s); |
666 | 396 | code = pdf_xmp_write_docinfo_item(pdev, s, "/Producer", "UnknownProducer", |
667 | 396 | pdf_xml_data_write); |
668 | 396 | if (code < 0) |
669 | 0 | return code; |
670 | 396 | pdf_xml_tag_close(s, "pdf:Producer"); |
671 | 396 | pdf_xml_newline(s); |
672 | | |
673 | 396 | pdf_xml_tag_open_beg(s, "pdf:Keywords"); |
674 | 396 | pdf_xml_tag_end(s); |
675 | 396 | code = pdf_xmp_write_docinfo_item(pdev, s, "/Keywords", "Unknown", |
676 | 396 | pdf_xml_data_write); |
677 | 396 | if (code < 0) |
678 | 0 | return code; |
679 | 396 | pdf_xml_tag_close(s, "pdf:Keywords"); |
680 | 396 | pdf_xml_newline(s); |
681 | | |
682 | 396 | pdf_xml_tag_close(s, "rdf:Description"); |
683 | 396 | pdf_xml_newline(s); |
684 | 10.8k | } else { |
685 | 10.8k | pdf_xml_attribute_name(s, "pdf:Producer"); |
686 | 10.8k | code = pdf_xmp_write_docinfo_item(pdev, s, "/Producer", "UnknownProducer", |
687 | 10.8k | pdf_xml_attribute_value_data); |
688 | 10.8k | if (code < 0) |
689 | 0 | return code; |
690 | 10.8k | pdf_xml_tag_end_empty(s); |
691 | 10.8k | pdf_xml_newline(s); |
692 | 10.8k | } |
693 | | |
694 | 11.2k | pdf_xml_tag_open_beg(s, "rdf:Description"); |
695 | 11.2k | pdf_xml_copy(s, " rdf:about=\"\""); |
696 | 11.2k | pdf_xml_attribute_name(s, "xmlns:xmp"); |
697 | 11.2k | pdf_xml_attribute_value(s, "http://ns.adobe.com/xap/1.0/"); |
698 | 11.2k | pdf_xml_tag_end(s); |
699 | 11.2k | if (!pdev->OmitInfoDate) { |
700 | 11.2k | { |
701 | 11.2k | pdf_xml_tag_open_beg(s, "xmp:ModifyDate"); |
702 | 11.2k | pdf_xml_tag_end(s); |
703 | 11.2k | mod_date_time[mod_date_time_len] = 0x00; |
704 | 11.2k | pdf_xml_copy(s, mod_date_time); |
705 | 11.2k | pdf_xml_tag_close(s, "xmp:ModifyDate"); |
706 | 11.2k | pdf_xml_newline(s); |
707 | 11.2k | } |
708 | 11.2k | { |
709 | 11.2k | pdf_xml_tag_open_beg(s, "xmp:CreateDate"); |
710 | 11.2k | pdf_xml_tag_end(s); |
711 | 11.2k | cre_date_time[cre_date_time_len] = 0x00; |
712 | 11.2k | pdf_xml_copy(s, cre_date_time); |
713 | 11.2k | pdf_xml_tag_close(s, "xmp:CreateDate"); |
714 | 11.2k | pdf_xml_newline(s); |
715 | 11.2k | } |
716 | 11.2k | } |
717 | 11.2k | { |
718 | 11.2k | pdf_xml_tag_open_beg(s, "xmp:CreatorTool"); |
719 | 11.2k | pdf_xml_tag_end(s); |
720 | 11.2k | code = pdf_xmp_write_docinfo_item(pdev, s, "/Creator", "UnknownApplication", |
721 | 11.2k | pdf_xml_data_write); |
722 | 11.2k | if (code < 0) |
723 | 0 | return code; |
724 | 11.2k | pdf_xml_tag_close(s, "xmp:CreatorTool"); |
725 | 11.2k | } |
726 | 0 | pdf_xml_tag_close(s, "rdf:Description"); |
727 | 11.2k | pdf_xml_newline(s); |
728 | | |
729 | 11.2k | pdf_xml_tag_open_beg(s, "rdf:Description"); |
730 | 11.2k | pdf_xml_copy(s, " rdf:about=\"\""); |
731 | 11.2k | pdf_xml_attribute_name(s, "xmlns:xapMM"); |
732 | 11.2k | pdf_xml_attribute_value(s, "http://ns.adobe.com/xap/1.0/mm/"); |
733 | 11.2k | pdf_xml_attribute_name(s, "xapMM:DocumentID"); |
734 | 11.2k | pdf_xml_attribute_value(s, document_uuid); |
735 | 11.2k | pdf_xml_tag_end_empty(s); |
736 | 11.2k | pdf_xml_newline(s); |
737 | | |
738 | 11.2k | pdf_xml_tag_open_beg(s, "rdf:Description"); |
739 | 11.2k | pdf_xml_copy(s, " rdf:about=\"\""); |
740 | 11.2k | pdf_xml_attribute_name(s, "xmlns:dc"); |
741 | 11.2k | pdf_xml_attribute_value(s, "http://purl.org/dc/elements/1.1/"); |
742 | 11.2k | pdf_xml_attribute_name(s, "dc:format"); |
743 | 11.2k | pdf_xml_attribute_value(s,"application/pdf"); |
744 | 11.2k | pdf_xml_tag_end(s); |
745 | 11.2k | { |
746 | 11.2k | pdf_xml_tag_open(s, "dc:title"); |
747 | 11.2k | { |
748 | 11.2k | pdf_xml_tag_open(s, "rdf:Alt"); |
749 | 11.2k | { |
750 | 11.2k | pdf_xml_tag_open_beg(s, "rdf:li"); |
751 | 11.2k | pdf_xml_attribute_name(s, "xml:lang"); |
752 | 11.2k | pdf_xml_attribute_value(s, "x-default"); |
753 | 11.2k | pdf_xml_tag_end(s); |
754 | 11.2k | { |
755 | 11.2k | code = pdf_xmp_write_docinfo_item(pdev, s, "/Title", "Untitled", |
756 | 11.2k | pdf_xml_data_write); |
757 | 11.2k | if (code < 0) |
758 | 0 | return code; |
759 | 11.2k | } |
760 | 11.2k | pdf_xml_tag_close(s, "rdf:li"); |
761 | 11.2k | } |
762 | 0 | pdf_xml_tag_close(s, "rdf:Alt"); |
763 | 11.2k | } |
764 | 0 | pdf_xml_tag_close(s, "dc:title"); |
765 | | |
766 | 11.2k | if (cos_dict_find(pdev->Info, (const byte *)"/Author", 7)) { |
767 | 1.28k | pdf_xml_tag_open(s, "dc:creator"); |
768 | 1.28k | { /* According to the PDF/A specification |
769 | | "it shall be represented by an ordered Text array of |
770 | | length one whose single entry shall consist |
771 | | of one or more names". */ |
772 | 1.28k | pdf_xml_tag_open(s, "rdf:Seq"); |
773 | 1.28k | { |
774 | 1.28k | pdf_xml_tag_open(s, "rdf:li"); |
775 | 1.28k | { |
776 | 1.28k | code = pdf_xmp_write_docinfo_item(pdev, s, "/Author", "Unknown", |
777 | 1.28k | pdf_xml_data_write); |
778 | 1.28k | if (code < 0) |
779 | 0 | return code; |
780 | 1.28k | } |
781 | 1.28k | pdf_xml_tag_close(s, "rdf:li"); |
782 | 1.28k | } |
783 | 0 | pdf_xml_tag_close(s, "rdf:Seq"); |
784 | 1.28k | } |
785 | 0 | pdf_xml_tag_close(s, "dc:creator"); |
786 | 1.28k | } |
787 | 11.2k | if (cos_dict_find(pdev->Info, (const byte *)"/Subject", 8)) { |
788 | 44 | pdf_xml_tag_open(s, "dc:description"); |
789 | 44 | { |
790 | 44 | pdf_xml_tag_open(s, "rdf:Alt"); |
791 | 44 | { |
792 | 44 | pdf_xml_tag_open_beg(s, "rdf:li"); |
793 | 44 | pdf_xml_attribute_name(s, "xml:lang"); |
794 | 44 | pdf_xml_attribute_value(s, "x-default"); |
795 | 44 | pdf_xml_tag_end(s); |
796 | 44 | { |
797 | 44 | code = pdf_xmp_write_docinfo_item(pdev, s, "/Subject", "No Subject", |
798 | 44 | pdf_xml_data_write); |
799 | 44 | if (code < 0) |
800 | 0 | return code; |
801 | 44 | } |
802 | 44 | pdf_xml_tag_close(s, "rdf:li"); |
803 | 44 | } |
804 | 0 | pdf_xml_tag_close(s, "rdf:Alt"); |
805 | 44 | } |
806 | 0 | pdf_xml_tag_close(s, "dc:description"); |
807 | 44 | } |
808 | 11.2k | } |
809 | 11.2k | pdf_xml_tag_close(s, "rdf:Description"); |
810 | 11.2k | pdf_xml_newline(s); |
811 | 11.2k | if (pdev->PDFA != 0) { |
812 | 0 | pdf_xml_tag_open_beg(s, "rdf:Description"); |
813 | 0 | pdf_xml_copy(s, " rdf:about=\"\""); |
814 | 0 | pdf_xml_attribute_name(s, "xmlns:pdfaid"); |
815 | 0 | pdf_xml_attribute_value(s, "http://www.aiim.org/pdfa/ns/id/"); |
816 | 0 | pdf_xml_attribute_name(s, "pdfaid:part"); |
817 | 0 | switch(pdev->PDFA) { |
818 | 0 | case 1: |
819 | 0 | pdf_xml_attribute_value(s,"1"); |
820 | 0 | break; |
821 | 0 | case 2: |
822 | 0 | pdf_xml_attribute_value(s,"2"); |
823 | 0 | break; |
824 | 0 | case 3: |
825 | 0 | pdf_xml_attribute_value(s,"3"); |
826 | 0 | break; |
827 | 0 | } |
828 | 0 | pdf_xml_attribute_name(s, "pdfaid:conformance"); |
829 | 0 | pdf_xml_attribute_value(s,"B"); |
830 | 0 | pdf_xml_tag_end_empty(s); |
831 | 0 | } |
832 | 11.2k | } |
833 | 11.2k | if (pdev->ExtensionMetadata) { |
834 | 0 | pdf_xml_copy(s, pdev->ExtensionMetadata); |
835 | 0 | } |
836 | 11.2k | pdf_xml_copy(s, "</rdf:RDF>\n"); |
837 | 11.2k | } |
838 | 0 | pdf_xml_copy(s, "</x:xmpmeta>\n"); |
839 | | |
840 | 11.2k | pdf_xml_copy(s, " \n"); |
841 | 11.2k | pdf_xml_copy(s, " \n"); |
842 | 11.2k | pdf_xml_copy(s, "<?xpacket end='w'?>"); |
843 | 11.2k | return 0; |
844 | 11.2k | } |
845 | | |
846 | | int |
847 | | pdf_document_metadata(gx_device_pdf *pdev) |
848 | 11.2k | { |
849 | 11.2k | if (pdev->CompatibilityLevel < 1.4) |
850 | 6 | return 0; |
851 | 11.2k | if (cos_dict_find_c_key(pdev->Catalog, "/Metadata")) |
852 | 0 | return 0; |
853 | | |
854 | 11.2k | if (pdev->ParseDSCCommentsForDocInfo || pdev->PreserveEPSInfo || pdev->PDFA) { |
855 | 11.2k | pdf_resource_t *pres; |
856 | 11.2k | char buf[20]; |
857 | 11.2k | byte digest[6] = {0,0,0,0,0,0}; |
858 | 11.2k | int code; |
859 | 11.2k | int options = DATA_STREAM_NOT_BINARY; |
860 | | |
861 | 11.2k | sflush(pdev->strm); |
862 | 11.2k | s_MD5C_get_digest(pdev->strm, digest, sizeof(digest)); |
863 | 11.2k | if (pdev->EncryptMetadata) |
864 | 11.2k | options |= DATA_STREAM_ENCRYPT; |
865 | 11.2k | code = pdf_open_aside(pdev, resourceMetadata, gs_no_id, &pres, true, options); |
866 | 11.2k | if (code < 0) |
867 | 0 | return code; |
868 | 11.2k | code = cos_dict_put_c_key_string((cos_dict_t *)pres->object, "/Type", (const byte *)"/Metadata", 9); |
869 | 11.2k | if (code < 0) { |
870 | 0 | pdf_close_aside(pdev); |
871 | 0 | return code; |
872 | 0 | } |
873 | 11.2k | code = cos_dict_put_c_key_string((cos_dict_t *)pres->object, "/Subtype", (const byte *)"/XML", 4); |
874 | 11.2k | if (code < 0) { |
875 | 0 | pdf_close_aside(pdev); |
876 | 0 | return code; |
877 | 0 | } |
878 | 11.2k | code = pdf_write_document_metadata(pdev, digest); |
879 | 11.2k | if (code < 0) { |
880 | 0 | pdf_close_aside(pdev); |
881 | 0 | return code; |
882 | 0 | } |
883 | 11.2k | code = pdf_close_aside(pdev); |
884 | 11.2k | if (code < 0) |
885 | 0 | return code; |
886 | 11.2k | code = COS_WRITE_OBJECT(pres->object, pdev, resourceNone); |
887 | 11.2k | if (code < 0) |
888 | 0 | return code; |
889 | 11.2k | gs_snprintf(buf, sizeof(buf), "%ld 0 R", pres->object->id); |
890 | 11.2k | pdf_record_usage(pdev, pres->object->id, resource_usage_part9_structure); |
891 | | |
892 | 11.2k | code = cos_dict_put_c_key_object(pdev->Catalog, "/Metadata", pres->object); |
893 | 11.2k | if (code < 0) |
894 | 0 | return code; |
895 | | |
896 | 11.2k | } |
897 | 11.2k | return 0; |
898 | 11.2k | } |
899 | | |
900 | | /* -------------------------------------------- */ |