1 /*
2  * Copyright (c) 2002-2007, Eric M. Johnston <emj@postal.net>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by Eric M. Johnston.
16  * 4. Neither the name of the author nor the names of any co-contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  * $Id: exifcom.c,v 1.16 2007/12/16 02:11:44 ejohnst Exp $
33  */
34 
35 /*
36  * exifcom: display or set Exif user comment.
37  *
38  */
39 
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <errno.h>
44 
45 /* For getopt(). */
46 
47 #ifndef WIN32
48 #include <unistd.h>
49 #else
50 extern char *optarg;
51 extern int optind, opterr, optopt;
52 int getopt(int, char * const [], const char *);
53 #endif
54 
55 #include "jpeg.h"
56 #include "exif.h"
57 
58 
59 static const char *version = "1.01";
60 static int fnum, bflag, iflag, nflag, vflag;
61 static const char *com;
62 static const char *delim = ": ";
63 
64 #define ASCCOM		"ASCII\0\0\0"
65 
66 
67 /*
68  * Display the comment.  This function just uses what's returned by
69  * exifscan() -- it doesn't touch the file.
70  */
71 static int
printcom(const char * fname,struct exifprop * p,const unsigned char * btiff)72 printcom(const char *fname, struct exifprop *p, const unsigned char *btiff)
73 {
74 	int rc = 0;
75 
76 	/* No comment tag. */
77 
78 	if (!p) {
79 		fprintf(stderr, "%s: comment not available\n", fname);
80 		return (1);
81 	}
82 
83 	if (vflag)
84 		printf("Supported Length%s%d\n", delim, p->count - 8);
85 
86 	/* Comment tag unset. */
87 
88 	if (!*(btiff + p->value))
89 		rc = 2;
90 
91 	else {
92 		/* Comment tag not ASCII. */
93 
94 		if (memcmp(ASCCOM, btiff + p->value, 8)) {
95 			if (!vflag)
96 				exifwarn2("comment type not supported",
97 				    (const char *)(btiff + p->value));
98 			rc = 3;
99 		}
100 
101 		if (vflag)
102 			printf("Type%s%s\n", delim, btiff + p->value);
103 	}
104 
105 	/* Comment tag OK, but blank. */
106 
107 	if (!rc && (!p->str || !*(p->str)))
108 		rc = 2;
109 
110 	/* Print comment in the normal, non-verbose case. */
111 
112 	if (!vflag) {
113 		printf("%s", rc ? "" : p->str);
114 		return (rc);
115 	}
116 
117 	/* Print length and comment if it's supported. */
118 
119 	if (rc != 1 && rc != 3) {
120 		printf("Length%s%d\n", delim, rc ? 0 : (int)strlen(p->str));
121 		printf("Comment%s%s\n", delim, rc ? "" : p->str);
122 	}
123 
124 	return (rc);
125 }
126 
127 
128 /*
129  * Blank or write a comment.
130  */
131 static int
writecom(FILE * fp,const char * fname,long pos,struct exifprop * p,const unsigned char * buf,const unsigned char * btiff)132 writecom(FILE *fp, const char *fname, long pos, struct exifprop *p,
133     const unsigned char *buf, const unsigned char *btiff)
134 {
135 	u_int32_t l;
136 	int ch, checkch;
137 	long psave;
138 
139 	/* No comment tag or it's zero length. */
140 
141 	if (!p) {
142 		fprintf(stderr, "%s: comment not available\n", fname);
143 		return (1);
144 	}
145 
146 	if (p->count < 9) {
147 		fprintf(stderr, "%s: comment size zero\n", fname);
148 		return (1);
149 	}
150 
151 	/* Be careful with existing or unsupported comments. */
152 
153 	if (iflag && *(btiff + p->value)) {
154 
155 		if (memcmp(ASCCOM, btiff + p->value, 8)) {
156 			if (nflag)
157 				return (1);
158 			fprintf(stderr, "overwrite %.8s comment in %s? "
159 			    "(y/n [n]) ", btiff + p->value, fname);
160 
161 			checkch = ch = getchar();
162 			while (ch != '\n' && ch != EOF)
163 				ch = getchar();
164 			if (checkch != 'y' && checkch != 'Y') {
165 				fprintf(stderr, "not overwritten\n");
166 				return (1);
167 			}
168 
169 		} else if (p->str && *(p->str)) {
170 			if (nflag)
171 				return (1);
172 			fprintf(stderr, "overwrite comment in %s? (y/n [n]) ",
173 			    fname);
174 
175 			checkch = ch = getchar();
176 			while (ch != '\n' && ch != EOF)
177 				ch = getchar();
178 			if (checkch != 'y' && checkch != 'Y') {
179 				fprintf(stderr, "not overwritten\n");
180 				return (1);
181 			}
182 		}
183 	}
184 
185 	/* Remember where we are and move to the comment in our file. */
186 
187 	psave = ftell(fp);
188 	if (fseek(fp, pos + (btiff - buf) + p->value, SEEK_SET))
189 		exifdie((const char *)strerror(errno));
190 
191 	/*
192 	 * Blank it with zeros and then reset the position.
193 	 * XXX Note that this is counter to the spec's recommendation:
194 	 * "When a UserComment area is set aside, it is recommended that
195 	 * the ID code be ASCII and that the following user comment part
196 	 * be filled with blank characters [20.H]."  However, cameras (Canon)
197 	 * seem to make it all zero; the rationale is that one can restore
198 	 * the image file to its original state.
199 	 */
200 
201 	if (bflag) {
202 		for (l = 0; l < p->count; l++)
203 			if (fwrite("\0", 1, 1, fp) != 1)
204 				exifdie((const char *)strerror(errno));
205 		if (fseek(fp, pos + (btiff - buf) + p->value, SEEK_SET))
206 			exifdie((const char *)strerror(errno));
207 	}
208 
209 	/* Write the character code and comment. */
210 
211 	if (com) {
212 		l = strlen(com);
213 		if (l > p->count - 8) {
214 			fprintf(stderr, "%s: truncating comment to fit\n",
215 			    fname);
216 			l = p->count - 8;
217 		}
218 
219 		/* Character code. */
220 
221 		if (fwrite(ASCCOM, 8, 1, fp) != 1)
222 			exifdie((const char *)strerror(errno));
223 
224 		/* Comment. */
225 
226 		if (fwrite(com, l, 1, fp) != 1)
227 			exifdie((const char *)strerror(errno));
228 
229 		/*
230 		 * Pad with spaces (this seems to be standard practice).
231 		 * XXX For now we're not NUL terminating the string.
232 		 * This doesn't appear to be required by the spec, but it's
233 		 * always possible that something out there will break.
234 		 * I've seen some utilities pad with spaces and set the
235 		 * last byte to NUL.
236 		 */
237 
238 		for (l = p->count - 8 - l; l; l--)
239 			if (fwrite(" ", 1, 1, fp) != 1)
240 				exifdie((const char *)strerror(errno));
241 	}
242 
243 	/* Restore the file pointer. */
244 
245 	if (fseek(fp, psave, SEEK_SET))
246 		exifdie((const char *)strerror(errno));
247 
248 	return (0);
249 }
250 
251 
252 /*
253  * Scan the JPEG file for Exif data and parse it.
254  */
255 static int
doit(FILE * fp,const char * fname)256 doit(FILE *fp, const char *fname)
257 {
258 	int mark, gotapp1, first, rc;
259 	unsigned int len, rlen;
260 	unsigned char *exifbuf;
261 	struct exiftags *t;
262 	long app1;
263 
264 	gotapp1 = FALSE;
265 	first = 0;
266 	exifbuf = NULL;
267 	rc = 0;
268 
269 	while (jpegscan(fp, &mark, &len, !(first++))) {
270 
271 		if (mark != JPEG_M_APP1) {
272 			if (fseek(fp, len, SEEK_CUR))
273 				exifdie((const char *)strerror(errno));
274 			continue;
275 		}
276 
277 		exifbuf = (unsigned char *)malloc(len);
278 		if (!exifbuf)
279 			exifdie((const char *)strerror(errno));
280 
281 		app1 = ftell(fp);
282 		rlen = fread(exifbuf, 1, len, fp);
283 		if (rlen != len) {
284 			fprintf(stderr, "%s: error reading JPEG (length "
285 			    "mismatch)\n", fname);
286 			free(exifbuf);
287 			return (1);
288 		}
289 
290 		gotapp1 = TRUE;
291 		t = exifscan(exifbuf, len, FALSE);
292 
293 		if (t && t->props) {
294 			if (bflag || com)
295 				rc = writecom(fp, fname, app1,
296 				    findprop(t->props, tags,
297 				    EXIF_T_USERCOMMENT), exifbuf, t->md.btiff);
298 			else
299 				rc = printcom(fname, findprop(t->props, tags,
300 				    EXIF_T_USERCOMMENT), t->md.btiff);
301 		} else {
302 			fprintf(stderr, "%s: couldn't find Exif properties\n",
303 			    fname);
304 			rc = 1;
305 		}
306 		exiffree(t);
307 		free(exifbuf);
308 	}
309 
310 	if (!gotapp1) {
311 		fprintf(stderr, "%s: couldn't find Exif data\n", fname);
312 		return (1);
313 	}
314 
315 	return (rc);
316 }
317 
318 
319 static
usage()320 void usage()
321 {
322 	fprintf(stderr, "Usage: %s [options] [-w string] file ...\n", progname);
323 	fprintf(stderr, "Displays or sets Exif comment in the specified "
324 	    "files.\n");
325 	fprintf(stderr, "Version: %s\n\n", version);
326 	fprintf(stderr, "Available options:\n");
327 	fprintf(stderr, "  -b\tOverwrite the comment tag with zeros.\n");
328 	fprintf(stderr, "  -f\tAnswer 'yes' to any confirmation prompts.\n");
329 	fprintf(stderr, "  -i\tConfirm overwrites (default).\n");
330 	fprintf(stderr, "  -n\tAnswer 'no' to any confirmation prompts.\n");
331 	fprintf(stderr, "  -v\tBe verbose about comment information.\n");
332 	fprintf(stderr, "  -w\tSet comment to provided string.\n");
333 	fprintf(stderr, "  -s\tSet delimiter to provided string "
334 	    "(default: \": \").\n");
335 
336 	exit(1);
337 }
338 
339 
340 int
main(int argc,char ** argv)341 main(int argc, char **argv)
342 {
343 	register int ch;
344 	int eval;
345 	char *rmode, *wmode;
346 	FILE *fp;
347 
348 	progname = argv[0];
349 	eval = 0;
350 	debug = FALSE;
351 	bflag = nflag = vflag = FALSE;
352 	iflag = TRUE;
353 	com = NULL;
354 #ifdef WIN32
355 	rmode = "rb";
356 	wmode = "r+b";
357 #else
358 	rmode = "r";
359 	wmode = "r+";
360 #endif
361 
362 	while ((ch = getopt(argc, argv, "bfinvw:s:")) != -1)
363 		switch (ch) {
364 		case 'b':
365 			bflag = TRUE;
366 			break;
367 		case 'f':
368 			iflag = FALSE;
369 			break;
370 		case 'i':
371 			iflag = TRUE;
372 			break;
373 		case 'n':
374 			iflag = nflag = TRUE;
375 			break;
376 		case 'v':
377 			vflag = TRUE;
378 			break;
379 		case 'w':
380 			com = optarg;
381 			break;
382 		case 's':
383 			delim = optarg;
384 			break;
385 		case '?':
386 		default:
387 			usage();
388 		}
389 	argc -= optind;
390 	argv += optind;
391 
392 	if (!*argv)
393 		usage();
394 
395 	for (fnum = 0; *argv; ++argv) {
396 
397 		/* Only open for read/write if we need to. */
398 
399 		if ((fp = fopen(*argv,
400 		    bflag || com ? wmode : rmode)) == NULL) {
401 			exifwarn2(strerror(errno), *argv);
402 			eval = 1;
403 			continue;
404 		}
405 
406 		fnum++;
407 
408 		if (argc > 1) {
409 
410 			/* Print filenames if more than one. */
411 
412 			if (vflag && !(bflag || com))
413 				printf("%s%s:\n",
414 				    fnum == 1 ? "" : "\n", *argv);
415 			else if (!(bflag || com))
416 				printf("%s%s", *argv, delim);
417 
418 			/* Don't error >1 with multiple files. */
419 
420 			eval = (doit(fp, *argv) == 1 || eval);
421 
422 		} else
423 			eval = doit(fp, *argv);
424 
425 		if (!vflag && !(bflag || com))
426 			printf("\n");
427 
428 		fclose(fp);
429 	}
430 
431 	return (eval);
432 }
433