1 //--------------------------------------------------------------------------
2 // Program to pull the information out of various types of EXIF digital
3 // camera files and show it in a reasonably consistent way
4 //
5 // This module handles basic Jpeg file handling
6 //
7 // Matthias Wandel,  Dec 1999 - May 2002
8 //--------------------------------------------------------------------------
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <memory.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <time.h>
15 #include <sys/stat.h>
16 #include <errno.h>
17 #include <ctype.h>
18 
19 #ifdef _WIN32
20     #include <process.h>
21     #include <io.h>
22     #include <sys/utime.h>
23 #else
24     #include <utime.h>
25     #include <sys/types.h>
26     #include <unistd.h>
27     #include <errno.h>
28     #include <limits.h>
29 #endif
30 
31 #include "jhead.h"
32 
33 // Storage for simplified info extracted from file.
34 ImageInfo_t ImageInfo;
35 
36 
37 #define MAX_SECTIONS 20
38 static Section_t Sections[MAX_SECTIONS];
39 static int SectionsRead;
40 static int HaveAll;
41 
42 
43 
44 #define PSEUDO_IMAGE_MARKER 0x123; // Extra value.
45 //--------------------------------------------------------------------------
46 // Get 16 bits motorola order (always) for jpeg header stuff.
47 //--------------------------------------------------------------------------
Get16m(const void * Short)48 static int Get16m(const void * Short)
49 {
50     return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1];
51 }
52 
53 
54 //--------------------------------------------------------------------------
55 // Process a COM marker.
56 // We want to print out the marker contents as legible text;
57 // we must guard against random junk and varying newline representations.
58 //--------------------------------------------------------------------------
process_COM(const uchar * Data,int length)59 static void process_COM (const uchar * Data, int length)
60 {
61     int ch;
62     char Comment[MAX_COMMENT+1];
63     int nch;
64     int a;
65 
66     nch = 0;
67 
68     if (length > MAX_COMMENT) length = MAX_COMMENT; // Truncate if it won't fit in our structure.
69 
70     for (a=2;a<length;a++){
71         ch = Data[a];
72 
73         if (ch == '\r' && Data[a+1] == '\n') continue; // Remove cr followed by lf.
74 
75         if (isprint(ch) || ch == '\n' || ch == '\t'){
76             Comment[nch++] = (char)ch;
77         }else{
78             Comment[nch++] = '?';
79         }
80     }
81 
82     Comment[nch] = '\0'; // Null terminate
83 
84     if (ShowTags){
85         printf("COM marker comment: %s\n",Comment);
86     }
87 
88     strcpy(ImageInfo.Comments,Comment);
89 }
90 
91 
92 //--------------------------------------------------------------------------
93 // Process a SOFn marker.  This is useful for the image dimensions
94 //--------------------------------------------------------------------------
process_SOFn(const uchar * Data,int marker)95 static void process_SOFn (const uchar * Data, int marker)
96 {
97     int data_precision, num_components;
98 
99     data_precision = Data[2];
100     ImageInfo.Height = Get16m(Data+3);
101     ImageInfo.Width = Get16m(Data+5);
102     num_components = Data[7];
103 
104     if (num_components == 3){
105         ImageInfo.IsColor = 1;
106     }else{
107         ImageInfo.IsColor = 0;
108     }
109 
110     ImageInfo.Process = marker;
111 
112     if (ShowTags){
113         printf("JPEG image is %uw * %uh, %d color components, %d bits per sample\n",
114                    ImageInfo.Width, ImageInfo.Height, num_components, data_precision);
115     }
116 }
117 
118 
119 
120 
121 //--------------------------------------------------------------------------
122 // Parse the marker stream until SOS or EOI is seen;
123 //--------------------------------------------------------------------------
ReadJpegSections(FILE * infile,ReadMode_t ReadMode)124 int ReadJpegSections (FILE * infile, ReadMode_t ReadMode)
125 {
126     int a;
127     int HaveCom = FALSE;
128 
129     a = fgetc(infile);
130 
131 
132     if (a != 0xff || fgetc(infile) != M_SOI){
133         return FALSE;
134     }
135     for(;;){
136         int itemlen;
137         int marker = 0;
138         int ll,lh, got;
139         uchar * Data;
140 
141         if (SectionsRead >= MAX_SECTIONS){
142             ErrFatal("Too many sections in jpg file");
143         }
144 
145         for (a=0;a<7;a++){
146             marker = fgetc(infile);
147             if (marker != 0xff) break;
148 
149             if (a >= 6){
150                 printf("too many padding bytes\n");
151                 return FALSE;
152             }
153         }
154 
155         if (marker == 0xff){
156             // 0xff is legal padding, but if we get that many, something's wrong.
157             ErrFatal("too many padding bytes!");
158         }
159 
160         Sections[SectionsRead].Type = marker;
161 
162         // Read the length of the section.
163         lh = fgetc(infile);
164         ll = fgetc(infile);
165 
166         itemlen = (lh << 8) | ll;
167 
168         if (itemlen < 2){
169             ErrFatal("invalid marker");
170         }
171 
172         Sections[SectionsRead].Size = itemlen;
173 
174         Data = (uchar *)malloc(itemlen);
175         if (Data == NULL){
176             ErrFatal("Could not allocate memory");
177         }
178         Sections[SectionsRead].Data = Data;
179 
180         // Store first two pre-read bytes.
181         Data[0] = (uchar)lh;
182         Data[1] = (uchar)ll;
183 
184         got = fread(Data+2, 1, itemlen-2, infile); // Read the whole section.
185         if (got != itemlen-2){
186             ErrFatal("Premature end of file?");
187         }
188         SectionsRead += 1;
189 
190         switch(marker){
191 
192             case M_SOS:   // stop before hitting compressed data
193                 // If reading entire image is requested, read the rest of the data.
194                 if (ReadMode & READ_IMAGE){
195                     int cp, ep, size;
196                     // Determine how much file is left.
197                     cp = ftell(infile);
198                     fseek(infile, 0, SEEK_END);
199                     ep = ftell(infile);
200                     fseek(infile, cp, SEEK_SET);
201 
202                     size = ep-cp;
203                     Data = (uchar *)malloc(size);
204                     if (Data == NULL){
205                         ErrFatal("could not allocate data for entire image");
206                     }
207 
208                     got = fread(Data, 1, size, infile);
209                     if (got != size){
210                         ErrFatal("could not read the rest of the image");
211                     }
212 
213                     Sections[SectionsRead].Data = Data;
214                     Sections[SectionsRead].Size = size;
215                     Sections[SectionsRead].Type = PSEUDO_IMAGE_MARKER;
216                     SectionsRead ++;
217                     HaveAll = 1;
218                 }
219                 return TRUE;
220 
221             case M_EOI:   // in case it's a tables-only JPEG stream
222                 printf("No image in jpeg!\n");
223                 return FALSE;
224 
225             case M_COM: // Comment section
226                 if (HaveCom || ((ReadMode & READ_EXIF) == 0)){
227                     // Discard this section.
228                     free(Sections[--SectionsRead].Data);
229                 }else{
230                     process_COM(Data, itemlen);
231                     HaveCom = TRUE;
232                 }
233                 break;
234 
235             case M_JFIF:
236                 // Regular jpegs always have this tag, exif images have the exif
237                 // marker instead, althogh ACDsee will write images with both markers.
238                 // this program will re-create this marker on absence of exif marker.
239                 // hence no need to keep the copy from the file.
240                 free(Sections[--SectionsRead].Data);
241                 break;
242 
243             case M_EXIF:
244                 // Seen files from some 'U-lead' software with Vivitar scanner
245                 // that uses marker 31 for non exif stuff.  Thus make sure
246                 // it says 'Exif' in the section before treating it as exif.
247                 if ((ReadMode & READ_EXIF) && memcmp(Data+2, "Exif", 4) == 0){
248                     process_EXIF(Data, itemlen);
249                 }else{
250                     // Discard this section.
251                     free(Sections[--SectionsRead].Data);
252                 }
253                 break;
254 
255             case M_SOF0:
256             case M_SOF1:
257             case M_SOF2:
258             case M_SOF3:
259             case M_SOF5:
260             case M_SOF6:
261             case M_SOF7:
262             case M_SOF9:
263             case M_SOF10:
264             case M_SOF11:
265             case M_SOF13:
266             case M_SOF14:
267             case M_SOF15:
268                 process_SOFn(Data, marker);
269                 break;
270             default:
271                 // Skip any other sections.
272                 if (ShowTags){
273                     printf("Jpeg section marker 0x%02x size %d\n",marker, itemlen);
274                 }
275                 break;
276         }
277     }
278     return TRUE;
279 }
280 
281 //--------------------------------------------------------------------------
282 // Discard read data.
283 //--------------------------------------------------------------------------
DiscardData(void)284 void DiscardData(void)
285 {
286     int a;
287     for (a=0;a<SectionsRead;a++){
288         free(Sections[a].Data);
289     }
290     memset(&ImageInfo, 0, sizeof(ImageInfo));
291     SectionsRead = 0;
292     HaveAll = 0;
293 }
294 
295 //--------------------------------------------------------------------------
296 // Read image data.
297 //--------------------------------------------------------------------------
ReadJpegFile(const char * FileName,ReadMode_t ReadMode)298 int ReadJpegFile(const char * FileName, ReadMode_t ReadMode)
299 {
300     FILE * infile;
301     int ret;
302 
303     infile = fopen(FileName, "rb"); // Unix ignores 'b', windows needs it.
304 
305     if (infile == NULL) {
306         fprintf(stderr, "can't open '%s'\n", FileName);
307         return FALSE;
308     }
309 
310     // Scan the JPEG headers.
311     ret = ReadJpegSections(infile, ReadMode);
312 #ifdef VERBOSE
313     if (!ret){
314         printf("Not JPEG: %s\n",FileName);
315     }
316 #endif
317 
318     fclose(infile);
319 
320     if (ret == FALSE){
321         DiscardData();
322     }
323     return ret;
324 }
325 
326 //--------------------------------------------------------------------------
327 // Remove exif thumbnail
328 //--------------------------------------------------------------------------
TrimExifFunc(void)329 int TrimExifFunc(void)
330 {
331     int a;
332     for (a=0;a<SectionsRead-1;a++){
333         if (Sections[a].Type == M_EXIF && memcmp(Sections[a].Data+2, "Exif",4)==0){
334             unsigned int NewSize;
335             NewSize = RemoveThumbnail(Sections[a].Data, Sections[a].Size);
336             // Truncate the thumbnail section of the exif.
337             printf("%d bytes removed\n",Sections[a].Size-NewSize);
338             if (Sections[a].Size == NewSize) return FALSE; // Nothing removed.
339             Sections[a].Size = NewSize;
340             Sections[a].Data[0] = (uchar)(NewSize >> 8);
341             Sections[a].Data[1] = (uchar)NewSize;
342             return TRUE;
343         }
344     }
345     // Not an exif image.  Can't remove exif thumbnail.
346     return FALSE;
347 }
348 
349 //--------------------------------------------------------------------------
350 // Discard everything but the exif and comment sections.
351 //--------------------------------------------------------------------------
DiscardAllButExif(void)352 void DiscardAllButExif(void)
353 {
354     Section_t ExifKeeper;
355     Section_t CommentKeeper;
356     int a;
357 
358     memset(&ExifKeeper, 0, sizeof(ExifKeeper));
359     memset(&CommentKeeper, 0, sizeof(ExifKeeper));
360 
361     for (a=0;a<SectionsRead;a++){
362         if (Sections[a].Type == M_EXIF && ExifKeeper.Type == 0){
363             ExifKeeper = Sections[a];
364         }else if (Sections[a].Type == M_COM && CommentKeeper.Type == 0){
365             CommentKeeper = Sections[a];
366         }else{
367             free(Sections[a].Data);
368         }
369     }
370     SectionsRead = 0;
371     if (ExifKeeper.Type){
372         Sections[SectionsRead++] = ExifKeeper;
373     }
374     if (CommentKeeper.Type){
375         Sections[SectionsRead++] = CommentKeeper;
376     }
377 }
378 
379 //--------------------------------------------------------------------------
380 // Write image data back to disk.
381 //--------------------------------------------------------------------------
WriteJpegFile(const char * FileName)382 void WriteJpegFile(const char * FileName)
383 {
384     FILE * outfile;
385     int a;
386 
387     if (!HaveAll){
388         ErrFatal("Can't write back - didn't read all");
389     }
390 
391     outfile = fopen(FileName,"wb");
392     if (outfile == NULL){
393         ErrFatal("Could not open file for write");
394     }
395 
396     // Initial static jpeg marker.
397     fputc(0xff,outfile);
398     fputc(0xd8,outfile);
399 
400     if (Sections[0].Type != M_EXIF && Sections[0].Type != M_JFIF){
401         // The image must start with an exif or jfif marker.  If we threw those away, create one.
402         static uchar JfifHead[18] = {
403             0xff, M_JFIF,
404             0x00, 0x10, 'J' , 'F' , 'I' , 'F' , 0x00, 0x01,
405             0x01, 0x01, 0x01, 0x2C, 0x01, 0x2C, 0x00, 0x00
406         };
407         fwrite(JfifHead, 18, 1, outfile);
408     }
409 
410     // Write all the misc sections
411     for (a=0;a<SectionsRead-1;a++){
412         fputc(0xff,outfile);
413         fputc(Sections[a].Type, outfile);
414         fwrite(Sections[a].Data, Sections[a].Size, 1, outfile);
415     }
416 
417     // Write the remaining image data.
418     fwrite(Sections[a].Data, Sections[a].Size, 1, outfile);
419 
420     fclose(outfile);
421 }
422 
423 
424 //--------------------------------------------------------------------------
425 // Check if image has exif header.
426 //--------------------------------------------------------------------------
FindSection(int SectionType)427 Section_t * FindSection(int SectionType)
428 {
429     int a;
430     for (a=0;a<SectionsRead-1;a++){
431         if (Sections[a].Type == SectionType){
432             return &Sections[a];
433         }
434     }
435     // Could not be found.
436     return NULL;
437 }
438 
439 //--------------------------------------------------------------------------
440 // Remove a certain type of section.
441 //--------------------------------------------------------------------------
RemoveSectionType(int SectionType)442 int RemoveSectionType(int SectionType)
443 {
444     int a;
445     for (a=0;a<SectionsRead-1;a++){
446         if (Sections[a].Type == SectionType){
447             // Free up this section
448             free (Sections[a].Data);
449             // Move succeding sections back by one to close space in array.
450             memmove(Sections+a, Sections+a+1, sizeof(Section_t) * (SectionsRead-a));
451             SectionsRead -= 1;
452             return TRUE;
453         }
454     }
455     return FALSE;
456 }
457 
458 //--------------------------------------------------------------------------
459 // Add a section (assume it doesn't already exist) - used for
460 // adding comment sections.
461 //--------------------------------------------------------------------------
CreateSection(int SectionType,unsigned char * Data,int Size)462 Section_t * CreateSection(int SectionType, unsigned char * Data, int Size)
463 {
464     Section_t * NewSection;
465     int a;
466 
467     // Insert it in third position - seems like a safe place to put
468     // things like comments.
469 
470     if (SectionsRead < 2){
471         ErrFatal("Too few sections!");
472     }
473     if (SectionsRead >= MAX_SECTIONS){
474         ErrFatal("Too many sections!");
475     }
476 
477     for (a=SectionsRead;a>2;a--){
478         Sections[a] = Sections[a-1];
479     }
480     SectionsRead += 1;
481 
482     NewSection = Sections+2;
483 
484     NewSection->Type = SectionType;
485     NewSection->Size = Size;
486     NewSection->Data = Data;
487 
488     return NewSection;
489 }
490 
491 
492 //--------------------------------------------------------------------------
493 // Initialisation.
494 //--------------------------------------------------------------------------
ResetJpgfile(void)495 void ResetJpgfile(void)
496 {
497     memset(&Sections, 0, sizeof(Sections));
498     SectionsRead = 0;
499     HaveAll = 0;
500 }
501