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