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			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
180static const convert_tag*
181find_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*/
195void
196parse_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
237static status_t
238parse_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
293static status_t
294parse_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*/
321status_t
322convert_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
348status_t
349convert_exif_to_message(BPositionIO& source, BMessage& target)
350{
351	return convert_exif_to_message(source, target, kDefaultTags,
352		kNumDefaultTags);
353}
354