1 /*
2 * Copyright 2007-2008, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7 #include "exif_parser.h"
8
9 #include <Catalog.h>
10 #include <ctype.h>
11 #include <set>
12 #include <stdio.h>
13 #include <stdlib.h>
14
15 #include <Catalog.h>
16 #include <Message.h>
17
18 #include <ReadHelper.h>
19
20 #undef B_TRANSLATION_CONTEXT
21 #define B_TRANSLATION_CONTEXT "exit_parser"
22
23
24 using std::set;
25
26 enum {
27 TAG_EXIF_OFFSET = 0x8769,
28 TAG_SUB_DIR_OFFSET = 0xa005,
29
30 TAG_MAKER = 0x10f,
31 TAG_MODEL = 0x110,
32 TAG_ORIENTATION = 0x112,
33 TAG_EXPOSURE_TIME = 0x829a,
34 TAG_ISO = 0x8827,
35 };
36
37 static const convert_tag kDefaultTags[] = {
38 {TAG_MAKER, B_ANY_TYPE, "Maker"},
39 {TAG_MODEL, B_ANY_TYPE, "Model"},
40 {TAG_ORIENTATION, B_INT32_TYPE, "Orientation"},
41 {TAG_EXPOSURE_TIME, B_DOUBLE_TYPE, "ExposureTime"},
42 {TAG_ISO, B_INT32_TYPE, "ISO"},
43 };
44 static const size_t kNumDefaultTags = sizeof(kDefaultTags)
45 / sizeof(kDefaultTags[0]);
46
47
48 static status_t parse_tiff_directory(TReadHelper& read, set<off_t>& visited,
49 BMessage& target, const convert_tag* tags, size_t tagCount);
50
51
52 static status_t
add_to_message(TReadHelper & source,BMessage & target,tiff_tag & tag,const char * name,type_code type)53 add_to_message(TReadHelper& source, BMessage& target, tiff_tag& tag,
54 const char* name, type_code type)
55 {
56 type_code defaultType = B_INT32_TYPE;
57 double doubleValue = 0.0;
58 int32 intValue = 0;
59
60 switch (tag.type) {
61 case TIFF_STRING_TYPE:
62 {
63 if (type != B_ANY_TYPE && type != B_STRING_TYPE)
64 return B_BAD_VALUE;
65
66 char* buffer = (char*)malloc(tag.length);
67 if (buffer == NULL)
68 return B_NO_MEMORY;
69
70 source(buffer, tag.length);
71
72 // remove trailing spaces
73 int32 i = tag.length;
74 while ((--i > 0 && isspace(buffer[i])) || !buffer[i]) {
75 buffer[i] = '\0';
76 }
77
78 status_t status = target.AddString(name, buffer);
79 free(buffer);
80
81 return status;
82 }
83
84 case TIFF_UNDEFINED_TYPE:
85 {
86 if (type != B_ANY_TYPE && type != B_STRING_TYPE && type != B_RAW_TYPE)
87 return B_BAD_VALUE;
88
89 char* buffer = (char*)malloc(tag.length);
90 if (buffer == NULL)
91 return B_NO_MEMORY;
92
93 source(buffer, tag.length);
94
95 status_t status;
96 if (type == B_STRING_TYPE)
97 status = target.AddString(name, buffer);
98 else
99 status = target.AddData(name, B_RAW_TYPE, buffer, tag.length);
100
101 free(buffer);
102
103 return status;
104 }
105
106 // unsigned
107 case TIFF_UINT8_TYPE:
108 intValue = source.Next<uint8>();
109 break;
110 case TIFF_UINT16_TYPE:
111 defaultType = B_INT32_TYPE;
112 intValue = source.Next<uint16>();
113 break;
114 case TIFF_UINT32_TYPE:
115 defaultType = B_INT32_TYPE;
116 intValue = source.Next<uint32>();
117 break;
118 case TIFF_UFRACTION_TYPE:
119 {
120 defaultType = B_DOUBLE_TYPE;
121 double value = source.Next<uint32>();
122 doubleValue = value / source.Next<uint32>();
123 break;
124 }
125
126 // signed
127 case TIFF_INT8_TYPE:
128 intValue = source.Next<int8>();
129 break;
130 case TIFF_INT16_TYPE:
131 intValue = source.Next<int16>();
132 break;
133 case TIFF_INT32_TYPE:
134 intValue = source.Next<int32>();
135 break;
136 case TIFF_FRACTION_TYPE:
137 {
138 defaultType = B_DOUBLE_TYPE;
139 double value = source.Next<int32>();
140 doubleValue = value / source.Next<int32>();
141 break;
142 }
143
144 // floating point
145 case TIFF_FLOAT_TYPE:
146 defaultType = B_FLOAT_TYPE;
147 doubleValue = source.Next<float>();
148 break;
149 case TIFF_DOUBLE_TYPE:
150 defaultType = B_DOUBLE_TYPE;
151 doubleValue = source.Next<double>();
152 break;
153
154 default:
155 return B_BAD_VALUE;
156 }
157
158 if (defaultType == B_INT32_TYPE)
159 doubleValue = intValue;
160 else
161 intValue = int32(doubleValue + 0.5);
162
163 if (type == B_ANY_TYPE)
164 type = defaultType;
165
166 switch (type) {
167 case B_INT32_TYPE:
168 return target.AddInt32(name, intValue);
169 case B_FLOAT_TYPE:
170 return target.AddFloat(name, doubleValue);
171 case B_DOUBLE_TYPE:
172 return target.AddDouble(name, doubleValue);
173
174 default:
175 return B_BAD_VALUE;
176 }
177 }
178
179
180 static const convert_tag*
find_convert_tag(uint16 id,const convert_tag * tags,size_t count)181 find_convert_tag(uint16 id, const convert_tag* tags, size_t count)
182 {
183 for (size_t i = 0; i < count; i++) {
184 if (tags[i].tag == id)
185 return &tags[i];
186 }
187
188 return NULL;
189 }
190
191
192 /*!
193 Reads a TIFF tag and positions the file stream to its data section
194 */
195 void
parse_tiff_tag(TReadHelper & read,tiff_tag & tag,off_t & offset)196 parse_tiff_tag(TReadHelper& read, tiff_tag& tag, off_t& offset)
197 {
198 read(tag.tag);
199 read(tag.type);
200 read(tag.length);
201
202 offset = read.Position() + 4;
203
204 uint32 length = tag.length;
205
206 switch (tag.type) {
207 case TIFF_UINT16_TYPE:
208 case TIFF_INT16_TYPE:
209 length *= 2;
210 break;
211
212 case TIFF_UINT32_TYPE:
213 case TIFF_INT32_TYPE:
214 case TIFF_FLOAT_TYPE:
215 length *= 4;
216 break;
217
218 case TIFF_UFRACTION_TYPE:
219 case TIFF_FRACTION_TYPE:
220 case TIFF_DOUBLE_TYPE:
221 length *= 8;
222 break;
223
224 default:
225 break;
226 }
227
228 if (length > 4) {
229 uint32 position;
230 read(position);
231
232 read.Seek(position, SEEK_SET);
233 }
234 }
235
236
237 static status_t
parse_tiff_directory(TReadHelper & read,set<off_t> & visited,off_t offset,BMessage & target,const convert_tag * convertTags,size_t convertTagCount)238 parse_tiff_directory(TReadHelper& read, set<off_t>& visited, off_t offset,
239 BMessage& target, const convert_tag* convertTags, size_t convertTagCount)
240 {
241 if (visited.find(offset) != visited.end()) {
242 // The EXIF data is obviously corrupt
243 return B_BAD_DATA;
244 }
245
246 read.Seek(offset, SEEK_SET);
247 visited.insert(offset);
248
249 uint16 tags;
250 read(tags);
251 if (tags > 512)
252 return B_BAD_DATA;
253
254 while (tags--) {
255 off_t nextOffset;
256 tiff_tag tag;
257 parse_tiff_tag(read, tag, nextOffset);
258
259 //printf("TAG %u\n", tag.tag);
260
261 switch (tag.tag) {
262 case TAG_EXIF_OFFSET:
263 case TAG_SUB_DIR_OFFSET:
264 {
265 status_t status = parse_tiff_directory(read, visited, target,
266 convertTags, convertTagCount);
267 if (status < B_OK)
268 return status;
269 break;
270 }
271
272 default:
273 const convert_tag* convertTag = find_convert_tag(tag.tag,
274 convertTags, convertTagCount);
275 if (convertTag != NULL) {
276 add_to_message(read, target, tag, convertTag->name,
277 convertTag->type);
278 }
279 break;
280 }
281
282 if (visited.find(nextOffset) != visited.end())
283 return B_BAD_DATA;
284
285 read.Seek(nextOffset, SEEK_SET);
286 visited.insert(nextOffset);
287 }
288
289 return B_OK;
290 }
291
292
293 static status_t
parse_tiff_directory(TReadHelper & read,set<off_t> & visited,BMessage & target,const convert_tag * tags,size_t tagCount)294 parse_tiff_directory(TReadHelper& read, set<off_t>& visited, BMessage& target,
295 const convert_tag* tags, size_t tagCount)
296 {
297 while (true) {
298 int32 offset;
299 read(offset);
300 if (offset == 0)
301 break;
302
303 status_t status = parse_tiff_directory(read, visited, offset, target,
304 tags, tagCount);
305 if (status < B_OK)
306 return status;
307 }
308
309 return B_OK;
310 }
311
312
313 // #pragma mark -
314
315
316 /*! Converts the EXIF data that starts in \a source to a BMessage in \a target.
317 If the EXIF data is corrupt, this function will return an appropriate error
318 code. Nevertheless, there might be some data ending up in \a target that
319 was parsed until this point.
320 */
321 status_t
convert_exif_to_message(BPositionIO & source,BMessage & target,const convert_tag * tags,size_t tagCount)322 convert_exif_to_message(BPositionIO& source, BMessage& target,
323 const convert_tag* tags, size_t tagCount)
324 {
325 TReadHelper read(source);
326
327 uint16 endian;
328 read(endian);
329 if (endian != 'MM' && endian != 'II')
330 return B_BAD_TYPE;
331
332 #if B_HOST_IS_LENDIAN
333 read.SetSwap(endian == 'MM');
334 #else
335 read.SetSwap(endian == 'II');
336 #endif
337
338 int16 magic;
339 read(magic);
340 if (magic != 42)
341 return B_BAD_TYPE;
342
343 set<off_t> visitedOffsets;
344 return parse_tiff_directory(read, visitedOffsets, target, tags, tagCount);
345 }
346
347
348 status_t
convert_exif_to_message(BPositionIO & source,BMessage & target)349 convert_exif_to_message(BPositionIO& source, BMessage& target)
350 {
351 return convert_exif_to_message(source, target, kDefaultTags,
352 kNumDefaultTags);
353 }
354