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
24using std::set;
25
26enum {
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
37static 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};
44static const size_t kNumDefaultTags = sizeof(kDefaultTags)
45	/ sizeof(kDefaultTags[0]);
46
47
48static status_t parse_tiff_directory(TReadHelper& read, set<off_t>& visited,
49	BMessage& target, const convert_tag* tags, size_t tagCount);
50
51
52static status_t
53add_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
179static const convert_tag*
180find_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*/
194void
195parse_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
236static status_t
237parse_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
292static status_t
293parse_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*/
320status_t
321convert_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
347status_t
348convert_exif_to_message(BPositionIO& source, BMessage& target)
349{
350	return convert_exif_to_message(source, target, kDefaultTags,
351		kNumDefaultTags);
352}
353