xref: /haiku/src/add-ons/translators/jpeg/exif_parser.cpp (revision 97dfeb96704e5dbc5bec32ad7b21379d0125e031)
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 			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*
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
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
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
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
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
349 convert_exif_to_message(BPositionIO& source, BMessage& target)
350 {
351 	return convert_exif_to_message(source, target, kDefaultTags,
352 		kNumDefaultTags);
353 }
354