1 /* actions.c
2  *
3  * Copyright (c) 2002-2008 Lutz Mueller <lutz@users.sourceforge.net>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA  02110-1301  USA.
19  */
20 
21 #include "config.h"
22 #include "actions.h"
23 #include "exif-i18n.h"
24 #include "libjpeg/jpeg-data.h"
25 #include "utils.h"
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <ctype.h>
31 #include <errno.h>
32 
33 #include <libexif/exif-ifd.h>
34 
35 #define CN(s) ((s) ? (s) : "(NULL)")
36 
37 #define TAG_VALUE_BUF 1024
38 
39 #define SEP " "
40 
41 static void
convert_arg_to_entry(const char * set_value,ExifEntry * e,ExifByteOrder o,ExifLog * log)42 convert_arg_to_entry (const char *set_value, ExifEntry *e, ExifByteOrder o, ExifLog *log)
43 {
44 	unsigned int i, numcomponents;
45 	char *value_p, *buf;
46 
47 	 /*
48 	 * ASCII strings are handled separately,
49 	 * since they don't require any conversion.
50 	 */
51         if (e->format == EXIF_FORMAT_ASCII ||
52 	    e->tag == EXIF_TAG_USER_COMMENT) {
53 		if (e->data) free (e->data);
54 		e->components = strlen (set_value) + 1;
55 		if (e->tag == EXIF_TAG_USER_COMMENT)
56 			e->components += 8 - 1;
57 		e->size = sizeof (char) * e->components;
58 		e->data = malloc (e->size);
59                 if (!e->data) {
60                         fprintf (stderr, _("Not enough memory."));
61                         fputc ('\n', stderr);
62                         exit (1);
63                 }
64 		if (e->tag == EXIF_TAG_USER_COMMENT) {
65 			/* assume ASCII charset */
66 			/* TODO: get this from the current locale */
67 			memcpy ((char *) e->data, "ASCII\0\0\0", 8);
68 			memcpy ((char *) e->data + 8, set_value,
69 				strlen (set_value));
70 		} else
71 			strcpy ((char *) e->data, set_value);
72                 return;
73 	}
74 
75 	/*
76 	 * Make sure we can handle this entry
77 	 */
78 	if ((e->components == 0) && *set_value) {
79 		fprintf (stderr, _("Setting a value for this tag "
80 				   "is unsupported!"));
81 		fputc ('\n', stderr);
82 		exit (1);
83 	}
84 
85 	/* Copy the string so we can modify it */
86 	buf = strdup(set_value);
87 	if (!buf) exit(1);
88 	value_p = strtok(buf, SEP);
89 	numcomponents = e->components;
90 	for (i = 0; i < numcomponents; ++i) {
91 		unsigned char s;
92 
93 		if (!value_p) {
94 				fprintf (stderr, _("Too few components specified "
95 					  "(need %d, found %d)\n"), numcomponents, i);
96 				exit (1);
97 		}
98 		if (!isdigit(*value_p) && (*value_p != '+') && (*value_p != '-')) {
99 				fprintf (stderr, _("Numeric value expected\n"));
100 				exit (1);
101 		}
102 
103 		s = exif_format_get_size (e->format);
104 		switch (e->format) {
105 		case EXIF_FORMAT_ASCII:
106 			exif_log (log, -1, "exif", _("Internal error. "
107 				"Please contact <%s>."), PACKAGE_BUGREPORT);
108 			break;
109 		case EXIF_FORMAT_SHORT:
110 			exif_set_short (e->data + (s * i), o, atoi (value_p));
111 			break;
112 		case EXIF_FORMAT_SSHORT:
113 			exif_set_sshort (e->data + (s * i), o, atoi (value_p));
114 			break;
115 		case EXIF_FORMAT_RATIONAL:
116 			/*
117 			 * Hack to simplify the loop for rational numbers.
118 			 * Should really be using exif_set_rational instead
119 			 */
120 			if (i == 0) numcomponents *= 2;
121 			s /= 2;
122 			/* Fall through to LONG handler */
123 		case EXIF_FORMAT_LONG:
124 			exif_set_long (e->data + (s * i), o, atol (value_p));
125 			break;
126 		case EXIF_FORMAT_SRATIONAL:
127 			/*
128 			 * Hack to simplify the loop for rational numbers.
129 			 * Should really be using exif_set_srational instead
130 			 */
131 			if (i == 0) numcomponents *= 2;
132 			s /= 2;
133 			/* Fall through to SLONG handler */
134 		case EXIF_FORMAT_SLONG:
135 			exif_set_slong (e->data + (s * i), o, atol (value_p));
136 			break;
137 		case EXIF_FORMAT_BYTE:
138 		case EXIF_FORMAT_SBYTE:
139 		case EXIF_FORMAT_UNDEFINED: /* treat as byte array */
140 			e->data[s * i] = atoi (value_p);
141 			break;
142 		case EXIF_FORMAT_FLOAT:
143 		case EXIF_FORMAT_DOUBLE:
144 		default:
145 			fprintf (stderr, _("Not yet implemented!"));
146 			fputc ('\n', stderr);
147 			exit (1);
148 		}
149 		value_p = strtok(NULL, SEP);
150 	}
151 	free(buf);
152 	if (value_p) {
153 		fprintf (stderr, _("Warning; Too many components specified!"));
154 		fputc ('\n', stderr);
155 	}
156 }
157 
158 void
action_save(ExifData * ed,ExifLog * log,ExifParams p,const char * fout)159 action_save (ExifData *ed, ExifLog *log, ExifParams p, const char *fout)
160 {
161 	JPEGData *jdata;
162 	unsigned char *d = NULL;
163 	unsigned int ds;
164 
165 	/* Parse the JPEG file. */
166 	jdata = jpeg_data_new ();
167 	jpeg_data_log (jdata, log);
168 	jpeg_data_load_file (jdata, p.fin);
169 
170 	/* Make sure the EXIF data is not too big. */
171 	exif_data_save_data (ed, &d, &ds);
172 	if (ds) {
173 		free (d);
174 		if (ds > 0xffff)
175 			exif_log (log, -1, "exif", _("Too much EXIF data "
176 				"(%i bytes). Only %i bytes are allowed."),
177 				ds, 0xffff);
178 	};
179 
180 	jpeg_data_set_exif_data (jdata, ed);
181 
182 	/* Save the modified image. */
183 	if (jpeg_data_save_file (jdata, fout) == 0)
184 			exif_log (log, -1, "exif", _("Could not write "
185 				"'%s' (%s)."), fout, strerror (errno));
186 	jpeg_data_unref (jdata);
187 
188 	fprintf (stdout, _("Wrote file '%s'."), fout);
189 	fprintf (stdout, "\n");
190 }
191 
192 static void
show_entry(ExifEntry * entry,unsigned int machine_readable)193 show_entry (ExifEntry *entry, unsigned int machine_readable)
194 {
195 	ExifIfd ifd = exif_entry_get_ifd (entry);
196 
197 	if (machine_readable) {
198 		char b[TAG_VALUE_BUF];
199 
200 		fprintf (stdout, "%s\n", C(exif_entry_get_value (entry, b, sizeof (b))));
201 		return;
202 	}
203 
204 	/*
205 	 * The C() macro can point to a static buffer so these printfs
206 	 * cannot be combined.
207 	 */
208 	printf (_("EXIF entry '%s' "),
209 		C(exif_tag_get_title_in_ifd (entry->tag, ifd)));
210 	printf (_("(0x%x, '%s') "),
211 		entry->tag,
212 		C(exif_tag_get_name_in_ifd (entry->tag, ifd)));
213 	printf (_("exists in IFD '%s':\n"),
214 		C(exif_ifd_get_name (ifd)));
215 
216 	exif_entry_dump (entry, 0);
217 }
218 
219 /*! If the entry doesn't exist, create it. */
220 ExifEntry *
action_create_value(ExifData * ed,ExifLog * log,ExifTag tag,ExifIfd ifd)221 action_create_value (ExifData *ed, ExifLog *log, ExifTag tag, ExifIfd ifd)
222 {
223 	ExifEntry *e;
224 
225 	if (!((e = exif_content_get_entry (ed->ifd[ifd], tag)))) {
226 	    exif_log (log, EXIF_LOG_CODE_DEBUG, "exif", _("Adding entry..."));
227 	    e = exif_entry_new ();
228 	    exif_content_add_entry (ed->ifd[ifd], e);
229 	    exif_entry_initialize (e, tag);
230 	    /* The entry has been added to the IFD, so we can unref it */
231 	    exif_entry_unref(e);
232 	}
233 	return e;
234 }
235 
236 void
action_set_value(ExifData * ed,ExifLog * log,ExifParams p)237 action_set_value (ExifData *ed, ExifLog *log, ExifParams p)
238 {
239 	/* If the entry doesn't exist, create it. */
240 	ExifEntry *e = action_create_value(ed, log, p.tag, p.ifd);
241 
242 	/* Now set the value and save the data. */
243 	convert_arg_to_entry (p.set_value, e, exif_data_get_byte_order (ed), log);
244 }
245 
246 void
action_remove_tag(ExifData * ed,ExifLog * log,ExifParams p)247 action_remove_tag (ExifData *ed, ExifLog *log, ExifParams p)
248 {
249 	ExifIfd ifd;
250 	ExifEntry *e;
251 
252 	/* We do have 2 optional parameters: ifd and tag */
253 	if (p.tag == EXIF_INVALID_TAG && (p.ifd < EXIF_IFD_0 || p.ifd >= EXIF_IFD_COUNT))
254 		for (ifd = EXIF_IFD_0; ifd < EXIF_IFD_COUNT; ifd++)
255 			while (ed->ifd[ifd] && ed->ifd[ifd]->count)
256 				exif_content_remove_entry (ed->ifd[ifd],
257 					ed->ifd[ifd]->entries[0]);
258 	else if (p.tag == EXIF_INVALID_TAG)
259 		while (ed->ifd[p.ifd] && ed->ifd[p.ifd]->count)
260 			exif_content_remove_entry (ed->ifd[p.ifd],
261 				ed->ifd[p.ifd]->entries[0]);
262 	else if (p.ifd < EXIF_IFD_0 || p.ifd >= EXIF_IFD_COUNT)
263 		while ((e = exif_data_get_entry (ed, p.tag)))
264 			exif_content_remove_entry (e->parent, e);
265 	else if (!((e = exif_content_get_entry (ed->ifd[p.ifd], p.tag))))
266 		exif_log (log, -1, "exif", _("IFD '%s' does not contain a "
267 			"tag '%s'!"), exif_ifd_get_name (p.ifd),
268 			exif_tag_get_name_in_ifd (p.tag, p.ifd));
269 	else
270 		exif_content_remove_entry (ed->ifd[p.ifd], e);
271 }
272 
273 void
action_remove_thumb(ExifData * ed,ExifLog * log,ExifParams p)274 action_remove_thumb (ExifData *ed, ExifLog *log, ExifParams p)
275 {
276 	(void) log;  /* unused */
277 	(void) p;  /* unused */
278 	if (ed->data) {
279 		free (ed->data);
280 		ed->data = NULL;
281 	}
282 	ed->size = 0;
283 }
284 
285 void
action_insert_thumb(ExifData * ed,ExifLog * log,ExifParams p)286 action_insert_thumb (ExifData *ed, ExifLog *log, ExifParams p)
287 {
288 	FILE *f;
289 
290 	if (!ed) return;
291 
292 	/* Get rid of the thumbnail */
293 	action_remove_thumb (ed, log, p);
294 
295 	/* Insert new thumbnail */
296 	f = fopen (p.set_thumb, "rb");
297 	if (!f) {
298 		exif_log (log, -1, "exif", _("Could not open "
299 			"'%s' (%s)!"), p.set_thumb, strerror (errno));
300 	} else {
301 		long fsize;
302 		if (fseek (f, 0, SEEK_END) < 0) {
303 			fclose(f);
304 			exif_log (log, -1, "exif", _("Could not determine size of "
305 				"'%s' (%s)."), p.set_thumb, strerror (errno));
306 			return;
307 		}
308 		fsize = ftell (f);
309 		if (fsize < 0) {
310 			fclose(f);
311 			exif_log (log, -1, "exif", _("Could not determine size of "
312 				"'%s' (%s)."), p.set_thumb, strerror (errno));
313 			return;
314 		}
315 		ed->size = fsize;
316 		ed->data = malloc (sizeof (char) * ed->size);
317 		if (ed->size && !ed->data) {
318 			EXIF_LOG_NO_MEMORY (log, "exif", sizeof (char) * ed->size);
319 			exit (1);
320 		}
321 		if (fseek (f, 0, SEEK_SET) < 0) {
322 			fclose(f);
323 			exif_log (log, -1, "exif", _("Could not determine size of "
324 				"'%s' (%s)."), p.set_thumb, strerror (errno));
325 			return;
326 		}
327 		if (fread (ed->data, sizeof (char), ed->size, f) != ed->size)
328 			exif_log (log, -1, "exif", _("Could not read "
329 				"'%s' (%s)."), p.set_thumb, strerror (errno));
330 		if (fclose (f) < 0)
331 			exif_log (log, -1, "exif", _("Could not read "
332 				"'%s' (%s)."), p.set_thumb, strerror (errno));
333 	}
334 }
335 
336 void
action_show_tag(ExifData * ed,ExifLog * log,ExifParams p)337 action_show_tag (ExifData *ed, ExifLog *log, ExifParams p)
338 {
339 	ExifEntry *e;
340 	unsigned int i;
341 
342 	if (!ed) return;
343 
344 	/* We have one optional parameter: ifd */
345 	if ((p.ifd >= EXIF_IFD_0) && (p.ifd < EXIF_IFD_COUNT)) {
346 		if ((e = exif_content_get_entry (ed->ifd[p.ifd], p.tag)))
347 			show_entry (e, p.machine_readable);
348 		else
349 			exif_log (log, -1, "exif", _("IFD '%s' "
350 				"does not contain tag '%s'."),
351 					exif_ifd_get_name (p.ifd),
352 					exif_tag_get_name (p.tag));
353 	} else {
354 		if (!exif_data_get_entry (ed, p.tag))
355 			exif_log (log, -1, "exif", _("'%s' does not contain "
356 				"tag '%s'."), p.fin,
357 				exif_tag_get_name (p.tag));
358 		else for (i = 0; i < EXIF_IFD_COUNT; i++)
359 			if ((e = exif_content_get_entry (ed->ifd[i], p.tag)))
360 				show_entry (e, p.machine_readable);
361 	}
362 }
363 
364 void
action_save_thumb(ExifData * ed,ExifLog * log,ExifParams p,const char * fout)365 action_save_thumb (ExifData *ed, ExifLog *log, ExifParams p, const char *fout)
366 {
367 	FILE *f;
368 
369 	if (!ed) return;
370 
371 	/* No thumbnail? Exit. */
372 	if (!ed->data) {
373 		exif_log (log, -1, "exif", _("'%s' does not "
374 			"contain a thumbnail!"), p.fin);
375 		return;
376 	}
377 
378 	/* Save the thumbnail */
379 	f = fopen (fout, "wb");
380 	if (!f)
381 		exif_log (log, -1, "exif", _("Could not open '%s' for "
382 			"writing (%s)!"), fout, strerror (errno));
383 	else {
384 		if (fwrite (ed->data, 1, ed->size, f) != ed->size) {
385 			exif_log (log, -1, "exif", _("Could not write '%s' (%s)."),
386 				fout, strerror (errno));
387 		};
388 		if (fclose (f) == EOF)
389 			exif_log (log, -1, "exif", _("Could not write '%s' (%s)."),
390 				fout, strerror (errno));
391 		fprintf (stdout, _("Wrote file '%s'."), fout);
392 		fprintf (stdout, "\n");
393 	}
394 }
395 
396 void
action_tag_table(ExifData * ed,ExifParams p)397 action_tag_table (ExifData *ed, ExifParams p)
398 {
399 	unsigned int tag;
400 	const char *name;
401 	char txt[TAG_VALUE_BUF];
402 	ExifIfd i;
403 	int fieldwidth, bytes;
404 	size_t width;
405 
406 #define ENTRY_FOUND     "   *   "
407 #define ENTRY_NOT_FOUND "   -   "
408 
409 	snprintf (txt, sizeof (txt) - 1, _("EXIF tags in '%s':"), p.fin);
410 	fieldwidth = width = p.width - 36;
411 	bytes = exif_mbstrlen(txt, &width);
412 	printf ("%.*s%*s", bytes, txt, fieldwidth-(int)width, "");
413 
414 	for (i = (ExifIfd)0; i < EXIF_IFD_COUNT; i++) {
415 		int space;
416 		fieldwidth = width = 7;
417 		bytes = exif_mbstrlen(exif_ifd_get_name (i), &width);
418 		space = fieldwidth-width;
419 		printf ("%*s%.*s%*s", space/2, "", bytes, exif_ifd_get_name (i),
420 			space - space/2, "");
421 	}
422 	fputc ('\n', stdout);
423 
424 	for (tag = 0; tag < 0xffff; tag++) {
425 		/*
426 		 * Display the name of the first tag of this number found.
427 		 * Since there is some overlap (e.g. with GPS tags), this
428 		 * name could sometimes be incorrect for the specific tags
429 		 * found in this file.
430 		 */
431 		name = exif_tag_get_title(tag);
432 		if (!name)
433 			continue;
434 
435 		fieldwidth = width = p.width - 43;
436 		bytes = exif_mbstrlen(C(name), &width);
437 		printf ("0x%04x %.*s%*s",
438 			tag, bytes, C(name), fieldwidth-(int)width, "");
439 		for (i = (ExifIfd)0; i < EXIF_IFD_COUNT; i++)
440 			if (exif_content_get_entry (ed->ifd[i], tag))
441 				printf (ENTRY_FOUND);
442 			else
443 				printf (ENTRY_NOT_FOUND);
444 		fputc ('\n', stdout);
445 	}
446 }
447 
448 static void
show_entry_list(ExifEntry * e,void * data)449 show_entry_list (ExifEntry *e, void *data)
450 {
451 	const ExifParams *p = data;
452 	char v[TAG_VALUE_BUF];
453 	ExifIfd ifd = exif_entry_get_ifd (e);
454 	const char *str;
455 	int fieldwidth, bytes;
456 	size_t width;
457 
458 	if (p->use_ids)
459 		printf("0x%04x", e->tag);
460 	else {
461 		str = C(exif_tag_get_title_in_ifd (e->tag, ifd));
462 		fieldwidth = width = 20;
463 		bytes = exif_mbstrlen(str, &width);
464 		printf ("%.*s%*s", bytes, str, fieldwidth-(int)width, "");
465 	}
466 	printf ("|");
467 
468 	fieldwidth = width = p->use_ids ? p->width-8 : p->width-22;
469 	str = C(exif_entry_get_value (e, v, sizeof(v)));
470 	bytes = exif_mbstrlen(str, &width);
471 	printf("%.*s", bytes, str);
472 	fputc ('\n', stdout);
473 }
474 
475 static void
show_ifd(ExifContent * content,void * data)476 show_ifd (ExifContent *content, void *data)
477 {
478 	exif_content_foreach_entry (content, show_entry_list, data);
479 }
480 
481 static void
print_hline(unsigned char ids,unsigned int screenwidth)482 print_hline (unsigned char ids, unsigned int screenwidth)
483 {
484         unsigned int i, width;
485 
486         width = ids ? 6 : 20;
487         for (i = 0; i < width; i++)
488 		fputc ('-', stdout);
489         fputc ('+', stdout);
490         for (i = 0; i < screenwidth - 2 - width; i++)
491 		fputc ('-', stdout);
492 	fputc ('\n', stdout);
493 }
494 
495 void
action_mnote_list(ExifData * ed,ExifParams p)496 action_mnote_list (ExifData *ed, ExifParams p)
497 {
498 	char b[TAG_VALUE_BUF], b1[TAG_VALUE_BUF], b2[TAG_VALUE_BUF];
499 	unsigned int i, c, id;
500 	ExifMnoteData *n;
501 	const char *s;
502 	int fieldwidth, bytes;
503 	size_t width;
504 
505 	n = exif_data_get_mnote_data (ed);
506 	if (!n) {
507 		printf (_("Unknown format or nonexistent MakerNote.\n"));
508 		return;
509 	}
510 
511 	c = exif_mnote_data_count (n);
512 	if (!p.machine_readable) {
513 		switch (c) {
514 		case 0:
515 			printf (_("MakerNote does not contain any value.\n"));
516 			break;
517 		default:
518 			printf (ngettext("MakerNote contains %i value:\n",
519 					 "MakerNote contains %i values:\n",
520 					 c), c);
521 		}
522 	}
523 	for (i = 0; i < c; i++) {
524 	        if (p.use_ids) {
525 			id = exif_mnote_data_get_id  (n,i);
526 			sprintf(b1,"0x%04x",id);
527 		} else {
528 			s = C (exif_mnote_data_get_title (n, i));
529 			strncpy (b1, s && *s ? s : _("Unknown Tag"), TAG_VALUE_BUF);
530 			b1[sizeof(b1)-1] = 0;
531 		}
532 		if (p.machine_readable) {
533 			printf ("%s\t", b1);
534 		} else {
535 			fieldwidth = width = p.use_ids ? 6 : 20;
536 			bytes = exif_mbstrlen(b1, &width);
537 			printf ("%.*s%*s|", bytes, b1, fieldwidth-(int)width, "");
538 		}
539 
540 		s = C (exif_mnote_data_get_value (n, i, b, TAG_VALUE_BUF));
541 		strncpy (b2, s ? s : _("Unknown value"), TAG_VALUE_BUF);
542 		b2[sizeof(b2)-1] = 0;
543         	if (p.use_ids) {
544 			fputs (b2, stdout);
545         	} else {
546 			fieldwidth = width = p.width-22;
547 			bytes = exif_mbstrlen(b2, &width);
548 			printf ("%.*s", bytes, b2);
549 		}
550         	fputc ('\n', stdout);
551 	}
552 }
553 
554 void
action_tag_list(ExifData * ed,ExifParams p)555 action_tag_list (ExifData *ed, ExifParams p)
556 {
557 	ExifByteOrder order;
558 	const char *s;
559 	int fieldwidth, bytes;
560 	size_t width;
561 
562 	if (!ed)
563 		return;
564 
565 	order = exif_data_get_byte_order (ed);
566 	printf (_("EXIF tags in '%s' ('%s' byte order):"), p.fin,
567 		exif_byte_order_get_name (order));
568 	fputc ('\n', stdout);
569 	print_hline (p.use_ids, p.width);
570 
571 	fieldwidth = width = p.use_ids ? 6 : 20;
572 	s = _("Tag");
573 	bytes = exif_mbstrlen(s, &width);
574 	printf ("%.*s%*s", bytes, s, fieldwidth-(int)width, "");
575 	fputc ('|', stdout);
576 
577 	fieldwidth = width = p.use_ids ? p.width-8 : p.width-22;
578 	s = _("Value");
579 	bytes = exif_mbstrlen(s, &width);
580 	printf ("%.*s", bytes, s);
581         fputc ('\n', stdout);
582         print_hline (p.use_ids, p.width);
583 
584 	if (p.ifd < EXIF_IFD_COUNT)
585 		/* Show only a single IFD */
586 		show_ifd(ed->ifd[p.ifd], &p);
587 	else
588 		/* Show contents of all IFDs */
589 		exif_data_foreach_content (ed, show_ifd, &p);
590 
591         print_hline (p.use_ids, p.width);
592         if (ed->size) {
593                 printf (_("EXIF data contains a thumbnail "
594 			  "(%i bytes)."), ed->size);
595                 fputc ('\n', stdout);
596         }
597 }
598 
599 static void
show_entry_machine(ExifEntry * e,void * data)600 show_entry_machine (ExifEntry *e, void *data)
601 {
602 	unsigned char *ids = data;
603 	char v[TAG_VALUE_BUF];
604 	ExifIfd ifd = exif_entry_get_ifd (e);
605 
606 	if (*ids) {
607 		fprintf (stdout, "0x%04x", e->tag);
608 	} else {
609 		fputs (CN (exif_tag_get_title_in_ifd (e->tag, ifd)), stdout);
610 	}
611 	fputc ('\t', stdout);
612 	fputs (CN (exif_entry_get_value (e, v, sizeof (v))), stdout);
613 	fputc ('\n', stdout);
614 }
615 
616 static void
show_ifd_machine(ExifContent * content,void * data)617 show_ifd_machine (ExifContent *content, void *data)
618 {
619 	exif_content_foreach_entry (content, show_entry_machine, data);
620 }
621 
622 void
action_tag_list_machine(ExifData * ed,ExifParams p)623 action_tag_list_machine (ExifData *ed, ExifParams p)
624 {
625 	if (!ed) return;
626 
627 	if (p.ifd < EXIF_IFD_COUNT)
628 		/* Show only a single IFD */
629 		show_ifd_machine(ed->ifd[p.ifd], &p.use_ids);
630 	else
631 		/* Show contents of all IFDs */
632 		exif_data_foreach_content (ed, show_ifd_machine, &p.use_ids);
633 
634 	if (ed->size)
635 		fprintf (stdout, _("ThumbnailSize\t%i\n"), ed->size);
636 }
637 
638 /*!
639  * Replace characters which are invalid in an XML tag with safe characters
640  * in place.
641  */
642 static inline void
remove_bad_chars(char * s)643 remove_bad_chars(char *s)
644 {
645 	while (*s) {
646 		if ((*s == '(') || (*s == ')') || (*s == ' '))
647 			*s = '_';
648 		++s;
649 	}
650 }
651 
652 /*!
653  * Escape any special XML characters in the text and return a new static string
654  * buffer.
655  */
656 static const char *
escape_xml(const char * text)657 escape_xml(const char *text)
658 {
659 	static char *escaped;
660 	static size_t escaped_size;
661 	char *out;
662 	size_t len;
663 
664 	for (out=escaped, len=0; *text; ++len, ++out, ++text) {
665 		/* Make sure there's plenty of room for a quoted character */
666 		if ((len + 8) > escaped_size) {
667 			char *bigger_escaped;
668 			escaped_size += 128;
669 			bigger_escaped = realloc(escaped, escaped_size);
670 			if (!bigger_escaped) {
671 				free(escaped);	/* avoid leaking memory */
672 				escaped = NULL;
673 				escaped_size = 0;
674 				/* Error string is cleverly chosen to fail XML validation */
675 				return ">>> out of memory <<<";
676 			}
677 			out = bigger_escaped + len;
678 			escaped = bigger_escaped;
679 		}
680 		switch (*text) {
681 			case '&':
682 				strcpy(out, "&amp;");
683 				len += strlen(out) - 1;
684 				out = escaped + len;
685 				break;
686 			case '<':
687 				strcpy(out, "&lt;");
688 				len += strlen(out) - 1;
689 				out = escaped + len;
690 				break;
691 			case '>':
692 				strcpy(out, "&gt;");
693 				len += strlen(out) - 1;
694 				out = escaped + len;
695 				break;
696 			default:
697 				*out = *text;
698 				break;
699 		}
700 	}
701 	*out = '\x0';  /* NUL terminate the string */
702 	return escaped;
703 }
704 
705 static void
show_entry_xml(ExifEntry * e,void * data)706 show_entry_xml (ExifEntry *e, void *data)
707 {
708 	unsigned char *ids = data;
709 	char v[TAG_VALUE_BUF], t[TAG_VALUE_BUF];
710 
711 	if (*ids) {
712 		fprintf (stdout, "<x%04x>", e->tag);
713 		fprintf (stdout, "%s", escape_xml(exif_entry_get_value (e, v, sizeof (v))));
714 		fprintf (stdout, "</x%04x>", e->tag);
715 	} else {
716 		strncpy (t, exif_tag_get_title_in_ifd(e->tag, exif_entry_get_ifd(e)), sizeof (t));
717 		t[sizeof(t)-1] = 0;
718 
719 		/* Remove invalid characters from tag eg. (, ), space */
720 		remove_bad_chars(t);
721 
722 		fprintf (stdout, "\t<%s>", t);
723 		fprintf (stdout, "%s", escape_xml(exif_entry_get_value (e, v, sizeof (v))));
724 		fprintf (stdout, "</%s>\n", t);
725 	}
726 }
727 
728 static void
show_xml(ExifContent * content,void * data)729 show_xml (ExifContent *content, void *data)
730 {
731 	exif_content_foreach_entry (content, show_entry_xml, data);
732 }
733 
734 void
action_tag_list_xml(ExifData * ed,ExifParams p)735 action_tag_list_xml (ExifData *ed, ExifParams p)
736 {
737 	if (!ed) return;
738 
739 	fprintf(stdout, "<exif>\n");
740 	if (p.ifd < EXIF_IFD_COUNT)
741 		/* Show only a single IFD */
742 		show_xml(ed->ifd[p.ifd], &p.use_ids);
743 	else
744 		/* Show contents of all IFDs */
745 		exif_data_foreach_content (ed, show_xml, &p.use_ids);
746 	fprintf(stdout, "</exif>\n");
747 }
748