xref: /haiku/src/add-ons/translators/jpeg/exif_parser.cpp (revision 002f37b0cca92e4cf72857c72ac95db5a8b09615)
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
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 		}
142 
143 		// floating point
144 		case TIFF_FLOAT_TYPE:
145 			defaultType = B_FLOAT_TYPE;
146 			doubleValue = source.Next<float>();
147 			break;
148 		case TIFF_DOUBLE_TYPE:
149 			defaultType = B_DOUBLE_TYPE;
150 			doubleValue = source.Next<double>();
151 			break;
152 
153 		default:
154 			return B_BAD_VALUE;
155 	}
156 
157 	if (defaultType == B_INT32_TYPE)
158 		doubleValue = intValue;
159 	else
160 		intValue = int32(doubleValue + 0.5);
161 
162 	if (type == B_ANY_TYPE)
163 		type = defaultType;
164 
165 	switch (type) {
166 		case B_INT32_TYPE:
167 			return target.AddInt32(name, intValue);
168 		case B_FLOAT_TYPE:
169 			return target.AddFloat(name, doubleValue);
170 		case B_DOUBLE_TYPE:
171 			return target.AddDouble(name, doubleValue);
172 
173 		default:
174 			return B_BAD_VALUE;
175 	}
176 }
177 
178 
179 static const convert_tag*
180 find_convert_tag(uint16 id, const convert_tag* tags, size_t count)
181 {
182 	for (size_t i = 0; i < count; i++) {
183 		if (tags[i].tag == id)
184 			return &tags[i];
185 	}
186 
187 	return NULL;
188 }
189 
190 
191 /*!
192 	Reads a TIFF tag and positions the file stream to its data section
193 */
194 void
195 parse_tiff_tag(TReadHelper& read, tiff_tag& tag, off_t& offset)
196 {
197 	read(tag.tag);
198 	read(tag.type);
199 	read(tag.length);
200 
201 	offset = read.Position() + 4;
202 
203 	uint32 length = tag.length;
204 
205 	switch (tag.type) {
206 		case TIFF_UINT16_TYPE:
207 		case TIFF_INT16_TYPE:
208 			length *= 2;
209 			break;
210 
211 		case TIFF_UINT32_TYPE:
212 		case TIFF_INT32_TYPE:
213 		case TIFF_FLOAT_TYPE:
214 			length *= 4;
215 			break;
216 
217 		case TIFF_UFRACTION_TYPE:
218 		case TIFF_FRACTION_TYPE:
219 		case TIFF_DOUBLE_TYPE:
220 			length *= 8;
221 			break;
222 
223 		default:
224 			break;
225 	}
226 
227 	if (length > 4) {
228 		uint32 position;
229 		read(position);
230 
231 		read.Seek(position, SEEK_SET);
232 	}
233 }
234 
235 
236 static status_t
237 parse_tiff_directory(TReadHelper& read, set<off_t>& visited, off_t offset,
238 	BMessage& target, const convert_tag* convertTags, size_t convertTagCount)
239 {
240 	if (visited.find(offset) != visited.end()) {
241 		// The EXIF data is obviously corrupt
242 		return B_BAD_DATA;
243 	}
244 
245 	read.Seek(offset, SEEK_SET);
246 	visited.insert(offset);
247 
248 	uint16 tags;
249 	read(tags);
250 	if (tags > 512)
251 		return B_BAD_DATA;
252 
253 	while (tags--) {
254 		off_t nextOffset;
255 		tiff_tag tag;
256 		parse_tiff_tag(read, tag, nextOffset);
257 
258 		//printf("TAG %u\n", tag.tag);
259 
260 		switch (tag.tag) {
261 			case TAG_EXIF_OFFSET:
262 			case TAG_SUB_DIR_OFFSET:
263 			{
264 				status_t status = parse_tiff_directory(read, visited, target,
265 					convertTags, convertTagCount);
266 				if (status < B_OK)
267 					return status;
268 				break;
269 			}
270 
271 			default:
272 				const convert_tag* convertTag = find_convert_tag(tag.tag,
273 					convertTags, convertTagCount);
274 				if (convertTag != NULL) {
275 					add_to_message(read, target, tag, convertTag->name,
276 						convertTag->type);
277 				}
278 				break;
279 		}
280 
281 		if (visited.find(nextOffset) != visited.end())
282 			return B_BAD_DATA;
283 
284 		read.Seek(nextOffset, SEEK_SET);
285 		visited.insert(nextOffset);
286 	}
287 
288 	return B_OK;
289 }
290 
291 
292 static status_t
293 parse_tiff_directory(TReadHelper& read, set<off_t>& visited, BMessage& target,
294 	const convert_tag* tags, size_t tagCount)
295 {
296 	while (true) {
297 		int32 offset;
298 		read(offset);
299 		if (offset == 0)
300 			break;
301 
302 		status_t status = parse_tiff_directory(read, visited, offset, target,
303 			tags, tagCount);
304 		if (status < B_OK)
305 			return status;
306 	}
307 
308 	return B_OK;
309 }
310 
311 
312 //	#pragma mark -
313 
314 
315 /*!	Converts the EXIF data that starts in \a source to a BMessage in \a target.
316 	If the EXIF data is corrupt, this function will return an appropriate error
317 	code. Nevertheless, there might be some data ending up in \a target that
318 	was parsed until this point.
319 */
320 status_t
321 convert_exif_to_message(BPositionIO& source, BMessage& target,
322 	const convert_tag* tags, size_t tagCount)
323 {
324 	TReadHelper read(source);
325 
326 	uint16 endian;
327 	read(endian);
328 	if (endian != 'MM' && endian != 'II')
329 		return B_BAD_TYPE;
330 
331 #if B_HOST_IS_LENDIAN
332 	read.SetSwap(endian == 'MM');
333 #else
334 	read.SetSwap(endian == 'II');
335 #endif
336 
337 	int16 magic;
338 	read(magic);
339 	if (magic != 42)
340 		return B_BAD_TYPE;
341 
342 	set<off_t> visitedOffsets;
343 	return parse_tiff_directory(read, visitedOffsets, target, tags, tagCount);
344 }
345 
346 
347 status_t
348 convert_exif_to_message(BPositionIO& source, BMessage& target)
349 {
350 	return convert_exif_to_message(source, target, kDefaultTags,
351 		kNumDefaultTags);
352 }
353