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 <ctype.h> 10 #include <set> 11 #include <stdio.h> 12 #include <stdlib.h> 13 14 #include <Message.h> 15 16 #include <ReadHelper.h> 17 18 using std::set; 19 20 enum { 21 TAG_EXIF_OFFSET = 0x8769, 22 TAG_SUB_DIR_OFFSET = 0xa005, 23 24 TAG_MAKER = 0x10f, 25 TAG_MODEL = 0x110, 26 TAG_ORIENTATION = 0x112, 27 TAG_EXPOSURE_TIME = 0x829a, 28 TAG_ISO = 0x8827, 29 }; 30 31 static const convert_tag kDefaultTags[] = { 32 {TAG_MAKER, B_ANY_TYPE, "Maker"}, 33 {TAG_MODEL, B_ANY_TYPE, "Model"}, 34 {TAG_ORIENTATION, B_INT32_TYPE, "Orientation"}, 35 {TAG_EXPOSURE_TIME, B_DOUBLE_TYPE, "ExposureTime"}, 36 {TAG_ISO, B_INT32_TYPE, "ISO"}, 37 }; 38 static const size_t kNumDefaultTags = sizeof(kDefaultTags) 39 / sizeof(kDefaultTags[0]); 40 41 42 static status_t parse_tiff_directory(TReadHelper& read, set<off_t>& visited, 43 BMessage& target, const convert_tag* tags, size_t tagCount); 44 45 46 static status_t 47 add_to_message(TReadHelper& source, BMessage& target, tiff_tag& tag, 48 const char* name, type_code type) 49 { 50 type_code defaultType = B_INT32_TYPE; 51 double doubleValue = 0.0; 52 int32 intValue = 0; 53 54 switch (tag.type) { 55 case TIFF_STRING_TYPE: 56 { 57 if (type != B_ANY_TYPE && type != B_STRING_TYPE) 58 return B_BAD_VALUE; 59 60 char* buffer = (char*)malloc(tag.length); 61 if (buffer == NULL) 62 return B_NO_MEMORY; 63 64 source(buffer, tag.length); 65 66 // remove trailing spaces 67 int32 i = tag.length; 68 while (--i > 0 && isspace(buffer[i]) || !buffer[i]) { 69 buffer[i] = '\0'; 70 } 71 72 status_t status = target.AddString(name, buffer); 73 free(buffer); 74 75 return status; 76 } 77 78 case TIFF_UNDEFINED_TYPE: 79 { 80 if (type != B_ANY_TYPE && type != B_STRING_TYPE && type != B_RAW_TYPE) 81 return B_BAD_VALUE; 82 83 char* buffer = (char*)malloc(tag.length); 84 if (buffer == NULL) 85 return B_NO_MEMORY; 86 87 source(buffer, tag.length); 88 89 status_t status; 90 if (type == B_STRING_TYPE) 91 status = target.AddString(name, buffer); 92 else 93 status = target.AddData(name, B_RAW_TYPE, buffer, tag.length); 94 95 free(buffer); 96 97 return status; 98 } 99 100 // unsigned 101 case TIFF_UINT8_TYPE: 102 intValue = source.Next<uint8>(); 103 break; 104 case TIFF_UINT16_TYPE: 105 defaultType = B_INT32_TYPE; 106 intValue = source.Next<uint16>(); 107 break; 108 case TIFF_UINT32_TYPE: 109 defaultType = B_INT32_TYPE; 110 intValue = source.Next<uint32>(); 111 break; 112 case TIFF_UFRACTION_TYPE: 113 { 114 defaultType = B_DOUBLE_TYPE; 115 double value = source.Next<uint32>(); 116 doubleValue = value / source.Next<uint32>(); 117 break; 118 } 119 120 // signed 121 case TIFF_INT8_TYPE: 122 intValue = source.Next<int8>(); 123 break; 124 case TIFF_INT16_TYPE: 125 intValue = source.Next<int16>(); 126 break; 127 case TIFF_INT32_TYPE: 128 intValue = source.Next<int32>(); 129 break; 130 case TIFF_FRACTION_TYPE: 131 { 132 defaultType = B_DOUBLE_TYPE; 133 double value = source.Next<int32>(); 134 doubleValue = value / source.Next<int32>(); 135 } 136 137 // floating point 138 case TIFF_FLOAT_TYPE: 139 defaultType = B_FLOAT_TYPE; 140 doubleValue = source.Next<float>(); 141 break; 142 case TIFF_DOUBLE_TYPE: 143 defaultType = B_DOUBLE_TYPE; 144 doubleValue = source.Next<double>(); 145 break; 146 147 default: 148 return B_BAD_VALUE; 149 } 150 151 if (defaultType == B_INT32_TYPE) 152 doubleValue = intValue; 153 else 154 intValue = int32(doubleValue + 0.5); 155 156 if (type == B_ANY_TYPE) 157 type = defaultType; 158 159 switch (type) { 160 case B_INT32_TYPE: 161 return target.AddInt32(name, intValue); 162 case B_FLOAT_TYPE: 163 return target.AddFloat(name, doubleValue); 164 case B_DOUBLE_TYPE: 165 return target.AddDouble(name, doubleValue); 166 167 default: 168 return B_BAD_VALUE; 169 } 170 } 171 172 173 static const convert_tag* 174 find_convert_tag(uint16 id, const convert_tag* tags, size_t count) 175 { 176 for (size_t i = 0; i < count; i++) { 177 if (tags[i].tag == id) 178 return &tags[i]; 179 } 180 181 return NULL; 182 } 183 184 185 /*! 186 Reads a TIFF tag and positions the file stream to its data section 187 */ 188 void 189 parse_tiff_tag(TReadHelper& read, tiff_tag& tag, off_t& offset) 190 { 191 read(tag.tag); 192 read(tag.type); 193 read(tag.length); 194 195 offset = read.Position() + 4; 196 197 uint32 length = tag.length; 198 199 switch (tag.type) { 200 case TIFF_UINT16_TYPE: 201 case TIFF_INT16_TYPE: 202 length *= 2; 203 break; 204 205 case TIFF_UINT32_TYPE: 206 case TIFF_INT32_TYPE: 207 case TIFF_FLOAT_TYPE: 208 length *= 4; 209 break; 210 211 case TIFF_UFRACTION_TYPE: 212 case TIFF_FRACTION_TYPE: 213 case TIFF_DOUBLE_TYPE: 214 length *= 8; 215 break; 216 217 default: 218 break; 219 } 220 221 if (length > 4) { 222 uint32 position; 223 read(position); 224 225 read.Seek(position, SEEK_SET); 226 } 227 } 228 229 230 static status_t 231 parse_tiff_directory(TReadHelper& read, set<off_t>& visited, off_t offset, 232 BMessage& target, const convert_tag* convertTags, size_t convertTagCount) 233 { 234 if (visited.find(offset) != visited.end()) { 235 // The EXIF data is obviously corrupt 236 return B_BAD_DATA; 237 } 238 239 read.Seek(offset, SEEK_SET); 240 visited.insert(offset); 241 242 uint16 tags; 243 read(tags); 244 if (tags > 512) 245 return B_BAD_DATA; 246 247 while (tags--) { 248 off_t nextOffset; 249 tiff_tag tag; 250 parse_tiff_tag(read, tag, nextOffset); 251 252 //printf("TAG %u\n", tag.tag); 253 254 switch (tag.tag) { 255 case TAG_EXIF_OFFSET: 256 case TAG_SUB_DIR_OFFSET: 257 { 258 status_t status = parse_tiff_directory(read, visited, target, 259 convertTags, convertTagCount); 260 if (status < B_OK) 261 return status; 262 break; 263 } 264 265 default: 266 const convert_tag* convertTag = find_convert_tag(tag.tag, 267 convertTags, convertTagCount); 268 if (convertTag != NULL) { 269 add_to_message(read, target, tag, convertTag->name, 270 convertTag->type); 271 } 272 break; 273 } 274 275 if (visited.find(nextOffset) != visited.end()) 276 return B_BAD_DATA; 277 278 read.Seek(nextOffset, SEEK_SET); 279 visited.insert(nextOffset); 280 } 281 282 return B_OK; 283 } 284 285 286 static status_t 287 parse_tiff_directory(TReadHelper& read, set<off_t>& visited, BMessage& target, 288 const convert_tag* tags, size_t tagCount) 289 { 290 while (true) { 291 int32 offset; 292 read(offset); 293 if (offset == 0) 294 break; 295 296 status_t status = parse_tiff_directory(read, visited, offset, target, 297 tags, tagCount); 298 if (status < B_OK) 299 return status; 300 } 301 302 return B_OK; 303 } 304 305 306 // #pragma mark - 307 308 309 /*! Converts the EXIF data that starts in \a source to a BMessage in \a target. 310 If the EXIF data is corrupt, this function will return an appropriate error 311 code. Nevertheless, there might be some data ending up in \a target that 312 was parsed until this point. 313 */ 314 status_t 315 convert_exif_to_message(BPositionIO& source, BMessage& target, 316 const convert_tag* tags, size_t tagCount) 317 { 318 TReadHelper read(source); 319 320 uint16 endian; 321 read(endian); 322 if (endian != 'MM' && endian != 'II') 323 return B_BAD_TYPE; 324 325 #if B_HOST_IS_LENDIAN 326 read.SetSwap(endian == 'MM'); 327 #else 328 read.SetSwap(endian == 'II'); 329 #endif 330 331 int16 magic; 332 read(magic); 333 if (magic != 42) 334 return B_BAD_TYPE; 335 336 set<off_t> visitedOffsets; 337 return parse_tiff_directory(read, visitedOffsets, target, tags, tagCount); 338 } 339 340 341 status_t 342 convert_exif_to_message(BPositionIO& source, BMessage& target) 343 { 344 return convert_exif_to_message(source, target, kDefaultTags, 345 kNumDefaultTags); 346 } 347