Coverage Report

Created: 2026-10-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/exiv2/src/rafimage.cpp
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
3
// included header files
4
#include "rafimage.hpp"
5
6
#include "basicio.hpp"
7
#include "config.h"
8
#include "enforce.hpp"
9
#include "error.hpp"
10
#include "futils.hpp"
11
#include "image.hpp"
12
#include "image_int.hpp"
13
#include "jpgimage.hpp"
14
#include "safe_op.hpp"
15
#include "tags.hpp"
16
#include "tiffimage.hpp"
17
18
#include <array>
19
#include <iostream>
20
21
// *****************************************************************************
22
// class member definitions
23
namespace Exiv2 {
24
RafImage::RafImage(BasicIo::UniquePtr io, const ImageCtorParams& params) :
25
181
    Image(ImageType::raf, mdExif | mdIptc | mdXmp, std::move(io), params) {
26
181
}  // RafImage::RafImage
27
28
0
std::string RafImage::mimeType() const {
29
0
  return "image/x-fuji-raf";
30
0
}
31
32
31
uint32_t RafImage::pixelWidth() const {
33
31
  if (pixelWidth_ != 0)
34
0
    return pixelWidth_;
35
36
31
  auto widthIter = exifData_.findKey(Exiv2::ExifKey("Exif.Fujifilm.RawImageFullWidth"));
37
31
  if (widthIter == exifData_.end() || widthIter->count() == 0)
38
31
    return 0;
39
0
  return widthIter->toUint32();
40
31
}
41
42
31
uint32_t RafImage::pixelHeight() const {
43
31
  if (pixelHeight_ != 0)
44
0
    return pixelHeight_;
45
46
31
  auto heightIter = exifData_.findKey(Exiv2::ExifKey("Exif.Fujifilm.RawImageFullHeight"));
47
31
  if (heightIter == exifData_.end() || heightIter->count() == 0)
48
31
    return 0;
49
0
  return heightIter->toUint32();
50
31
}
51
52
0
void RafImage::setExifData(const ExifData& /*exifData*/) {
53
  // Todo: implement me!
54
0
  throw(Error(ErrorCode::kerInvalidSettingForImage, "Exif metadata", "RAF"));
55
0
}
56
57
0
void RafImage::setIptcData(const IptcData& /*iptcData*/) {
58
  // Todo: implement me!
59
0
  throw(Error(ErrorCode::kerInvalidSettingForImage, "IPTC metadata", "RAF"));
60
0
}
61
62
0
void RafImage::setComment(const std::string&) {
63
  // not supported
64
0
  throw(Error(ErrorCode::kerInvalidSettingForImage, "Image comment", "RAF"));
65
0
}
66
67
0
void RafImage::printStructure(std::ostream& out, PrintStructureOption option, size_t depth) {
68
0
  if (io_->open() != 0) {
69
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
70
0
  }
71
  // Ensure this is the correct image type
72
0
  if (!isRafType(*io_, true)) {
73
0
    if (io_->error() || io_->eof())
74
0
      throw Error(ErrorCode::kerFailedToReadImageData);
75
0
    throw Error(ErrorCode::kerNotAnImage, "RAF");
76
0
  }
77
78
  // The following is based on https://libopenraw.freedesktop.org/formats/raf/ and
79
  // https://exiftool.org/TagNames/FujiFilm.html#RAFHeader
80
81
0
  const bool bPrint = option == kpsBasic || option == kpsRecursive;
82
0
  if (bPrint) {
83
0
    io_->seek(0, BasicIo::beg);  // rewind
84
0
    size_t address = io_->tell();
85
0
    constexpr auto format = " {:9} | {:9} | ";
86
87
0
    {
88
0
      out << Internal::indent(depth) << "STRUCTURE OF RAF FILE: " << io().path() << '\n';
89
0
      out << Internal::indent(depth) << "   Address |    Length | Payload" << '\n';
90
0
    }
91
92
0
    byte magicdata[17];
93
0
    io_->readOrThrow(magicdata, 16);
94
0
    magicdata[16] = 0;
95
0
    {
96
0
      out << Internal::indent(depth) << stringFormat(format, address, 16)  // 0
97
0
          << "       magic : " << reinterpret_cast<const char*>(magicdata) << '\n';
98
0
    }
99
100
0
    address = io_->tell();
101
0
    byte data1[5];
102
0
    io_->read(data1, 4);
103
0
    data1[4] = 0;
104
0
    {
105
0
      out << Internal::indent(depth) << stringFormat(format, address, 4)  // 16
106
0
          << "       data1 : " << reinterpret_cast<const char*>(&data1) << '\n';
107
0
    }
108
109
0
    address = io_->tell();
110
0
    byte data2[9];
111
0
    io_->read(data2, 8);
112
0
    data2[8] = 0;
113
0
    {
114
0
      out << Internal::indent(depth) << stringFormat(format, address, 8)  // 20
115
0
          << "       data2 : " << reinterpret_cast<const char*>(&data2) << '\n';
116
0
    }
117
118
0
    address = io_->tell();
119
0
    byte camdata[33];
120
0
    io_->read(camdata, 32);
121
0
    camdata[32] = 0;
122
0
    {
123
0
      out << Internal::indent(depth) << stringFormat(format, address, 32)  // 28
124
0
          << "      camera : " << reinterpret_cast<const char*>(&camdata) << '\n';
125
0
    }
126
127
0
    address = io_->tell();
128
0
    byte dir_version[5];
129
0
    io_->read(dir_version, 4);
130
0
    dir_version[4] = 0;
131
0
    {
132
0
      out << Internal::indent(depth) << stringFormat(format, address, 4)  // 60
133
0
          << "     version : " << reinterpret_cast<const char*>(&dir_version) << '\n';
134
0
    }
135
136
0
    address = io_->tell();
137
0
    DataBuf unknown(20);
138
0
    io_->readOrThrow(unknown.data(), unknown.size());
139
0
    {
140
0
      out << Internal::indent(depth) << stringFormat(format, address, 20)
141
0
          << "     unknown : " << Internal::binaryToString(makeSlice(unknown, 0, unknown.size())) << '\n';
142
0
    }
143
144
0
    address = io_->tell();
145
0
    byte jpg_img_offset[4];
146
0
    io_->read(jpg_img_offset, 4);
147
0
    byte jpg_img_length[4];
148
0
    size_t address2 = io_->tell();
149
0
    io_->read(jpg_img_length, 4);
150
151
0
    uint32_t jpg_img_off = Exiv2::getULong(jpg_img_offset, bigEndian);
152
0
    uint32_t jpg_img_len = Exiv2::getULong(jpg_img_length, bigEndian);
153
0
    {
154
0
      out << Internal::indent(depth) << stringFormat(format, address, 4) << " JPEG offset : " << jpg_img_off << '\n';
155
0
      out << Internal::indent(depth) << stringFormat(format, address2, 4) << " JPEG length : " << jpg_img_len << '\n';
156
0
    }
157
158
    // RAFs can carry the payload in one or two parts
159
0
    uint32_t meta_off[2], meta_len[2];
160
0
    uint32_t cfa_off[2], cfa_len[2], comp[2], cfa_size[2], cfa_data[2];
161
0
    for (size_t i = 0; i < 2; i++) {
162
0
      address = io_->tell();
163
0
      byte data[4];
164
0
      io_->readOrThrow(data, 4);
165
0
      meta_off[i] = Exiv2::getULong(data, bigEndian);
166
0
      address2 = io_->tell();
167
0
      io_->readOrThrow(data, 4);
168
0
      meta_len[i] = Exiv2::getULong(data, bigEndian);
169
0
      {
170
0
        out << Internal::indent(depth) << stringFormat(format, address, 4)
171
0
            << stringFormat("meta offset{} : {}\n", i + 1, meta_off[i]);
172
0
        out << Internal::indent(depth) << stringFormat(format, address2, 4)
173
0
            << stringFormat("meta length{} : {}\n", i + 1, meta_len[i]);
174
0
      }
175
176
0
      address = io_->tell();
177
0
      io_->readOrThrow(data, 4);
178
0
      cfa_off[i] = Exiv2::getULong(data, bigEndian);
179
0
      address2 = io_->tell();
180
0
      io_->readOrThrow(data, 4);
181
0
      cfa_len[i] = Exiv2::getULong(data, bigEndian);
182
0
      size_t address3 = io_->tell();
183
0
      io_->readOrThrow(data, 4);
184
0
      comp[i] = Exiv2::getULong(data, bigEndian);
185
0
      size_t address4 = io_->tell();
186
0
      io_->readOrThrow(data, 4);
187
0
      cfa_size[i] = Exiv2::getULong(data, bigEndian);
188
0
      size_t address5 = io_->tell();
189
0
      io_->readOrThrow(data, 4);
190
0
      cfa_data[i] = Exiv2::getULong(data, bigEndian);
191
0
      {
192
0
        out << Internal::indent(depth) << stringFormat(format, address, 4U)
193
0
            << stringFormat(" CFA offset{} : {}\n", i + 1, cfa_off[i]);
194
0
        out << Internal::indent(depth) << stringFormat(format, address2, 4U)
195
0
            << stringFormat(" CFA length{} : {}\n", i + 1, cfa_len[i]);
196
0
        out << Internal::indent(depth) << stringFormat(format, address3, 4U)
197
0
            << stringFormat("compression{} : {}\n", i + 1, comp[i]);
198
0
        out << Internal::indent(depth) << stringFormat(format, address4, 4U)
199
0
            << stringFormat("  CFA chunk{} : {}\n", i + 1, cfa_size[i]);
200
0
        out << Internal::indent(depth) << stringFormat(format, address5, 4U)
201
0
            << stringFormat("    unknown{} : {}\n", i + 1, cfa_data[i]);
202
0
      }
203
0
    }
204
205
0
    io_->seek(jpg_img_off, BasicIo::beg);  // rewind
206
0
    address = io_->tell();
207
0
    DataBuf payload(16);  // header is different from chunks
208
0
    io_->readOrThrow(payload.data(), payload.size());
209
0
    {
210
0
      out << Internal::indent(depth) << stringFormat(format, address, jpg_img_len)
211
0
          << "   JPEG data : " << Internal::binaryToString(makeSlice(payload, 0, payload.size())) << '\n';
212
0
    }
213
214
0
    io_->seek(meta_off[0], BasicIo::beg);  // rewind
215
0
    address = io_->tell();
216
0
    io_->readOrThrow(payload.data(), payload.size());
217
0
    {
218
0
      out << Internal::indent(depth) << stringFormat(format, address, meta_len[0])
219
0
          << "  meta data1 : " << Internal::binaryToString(makeSlice(payload, 0, payload.size())) << '\n';
220
0
    }
221
222
0
    if (meta_off[1] && meta_len[1]) {
223
0
      io_->seek(meta_off[1], BasicIo::beg);  // rewind
224
0
      address = io_->tell();
225
0
      io_->readOrThrow(payload.data(), payload.size());
226
0
      {
227
0
        out << Internal::indent(depth) << stringFormat(format, address, meta_len[1])
228
0
            << "  meta data2 : " << Internal::binaryToString(makeSlice(payload, 0, payload.size())) << '\n';
229
0
      }
230
0
    }
231
232
0
    io_->seek(cfa_off[0], BasicIo::beg);  // rewind
233
0
    address = io_->tell();
234
0
    io_->readOrThrow(payload.data(), payload.size());
235
0
    {
236
0
      out << Internal::indent(depth) << stringFormat(format, address, cfa_len[0])
237
0
          << "   CFA data1 : " << Internal::binaryToString(makeSlice(payload, 0, payload.size())) << '\n';
238
0
    }
239
240
0
    if (cfa_off[1] && cfa_len[1]) {
241
0
      io_->seek(cfa_off[1], BasicIo::beg);  // rewind
242
0
      address = io_->tell();
243
0
      io_->readOrThrow(payload.data(), payload.size());
244
0
      {
245
0
        out << Internal::indent(depth) << stringFormat(format, address, cfa_len[1])  // cfa_off
246
0
            << "   CFA data2 : " << Internal::binaryToString(makeSlice(payload, 0, payload.size())) << '\n';
247
0
      }
248
0
    }
249
0
  }
250
0
}  // RafImage::printStructure
251
252
181
void RafImage::readMetadata() {
253
#ifdef EXIV2_DEBUG_MESSAGES
254
  std::cerr << "Reading RAF file " << io_->path() << "\n";
255
#endif
256
181
  if (io_->open() != 0)
257
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
258
181
  IoCloser closer(*io_);
259
  // Ensure that this is the correct image type
260
181
  if (!isRafType(*io_, false)) {
261
0
    if (io_->error() || io_->eof())
262
0
      throw Error(ErrorCode::kerFailedToReadImageData);
263
0
    throw Error(ErrorCode::kerNotAnImage, "RAF");
264
0
  }
265
266
181
  clearMetadata();
267
268
181
  if (io_->seek(84, BasicIo::beg) != 0)
269
6
    throw Error(ErrorCode::kerFailedToReadImageData);
270
175
  byte jpg_img_offset[4];
271
175
  if (io_->read(jpg_img_offset, 4) != 4)
272
11
    throw Error(ErrorCode::kerFailedToReadImageData);
273
164
  byte jpg_img_length[4];
274
164
  if (io_->read(jpg_img_length, 4) != 4)
275
10
    throw Error(ErrorCode::kerFailedToReadImageData);
276
154
  uint32_t jpg_img_off_u32 = Exiv2::getULong(jpg_img_offset, bigEndian);
277
154
  uint32_t jpg_img_len_u32 = Exiv2::getULong(jpg_img_length, bigEndian);
278
279
154
  Internal::enforce(Safe::add(jpg_img_off_u32, jpg_img_len_u32) <= io_->size(), ErrorCode::kerCorruptedMetadata);
280
281
154
  auto jpg_img_off = static_cast<long>(jpg_img_off_u32);
282
154
  auto jpg_img_len = static_cast<long>(jpg_img_len_u32);
283
284
154
  Internal::enforce(jpg_img_len >= 12, ErrorCode::kerCorruptedMetadata);
285
286
154
  DataBuf jpg_buf(jpg_img_len);
287
154
  if (io_->seek(jpg_img_off, BasicIo::beg) != 0)
288
0
    throw Error(ErrorCode::kerFailedToReadImageData);
289
290
154
  if (!jpg_buf.empty()) {
291
117
    io_->read(jpg_buf.data(), jpg_buf.size());
292
117
    if (io_->error() || io_->eof())
293
0
      throw Error(ErrorCode::kerFailedToReadImageData);
294
117
  }
295
296
  // Retrieve metadata from embedded JPEG preview image.
297
154
  try {
298
154
    auto jpg_io = std::make_unique<Exiv2::MemIo>(jpg_buf.data(), jpg_buf.size());
299
154
    auto jpg_img = JpegImage(std::move(jpg_io), ImageCtorParams(false, 0));
300
154
    jpg_img.readMetadata();
301
154
    setByteOrder(jpg_img.byteOrder());
302
154
    xmpData_ = jpg_img.xmpData();
303
154
    exifData_ = jpg_img.exifData();
304
154
    iptcData_ = jpg_img.iptcData();
305
154
    comment_ = jpg_img.comment();
306
154
  } catch (const Exiv2::Error&) {
307
104
  }
308
309
154
  exifData_["Exif.Image2.JPEGInterchangeFormat"] = getULong(jpg_img_offset, bigEndian);
310
117
  exifData_["Exif.Image2.JPEGInterchangeFormatLength"] = getULong(jpg_img_length, bigEndian);
311
312
  // Todo: parse the proprietary metadata structure
313
  //       at offset 92 for pixelWidth_ & pixelHeight_
314
  // See https://libopenraw.freedesktop.org/formats/raf/
315
  // and https://exiftool.org/TagNames/FujiFilm.html#RAF
316
317
  // parse the tiff
318
117
  std::array<byte, 4> readBuff;
319
117
  if (io_->seek(100, BasicIo::beg) != 0)
320
6
    throw Error(ErrorCode::kerFailedToReadImageData);
321
111
  if (io_->read(readBuff.data(), 4) != 4)
322
18
    throw Error(ErrorCode::kerFailedToReadImageData);
323
93
  uint32_t tiffOffset = Exiv2::getULong(readBuff.data(), bigEndian);
324
325
93
  if (io_->read(readBuff.data(), 4) != 4)
326
10
    throw Error(ErrorCode::kerFailedToReadImageData);
327
83
  uint32_t tiffLength = Exiv2::getULong(readBuff.data(), bigEndian);
328
329
  // sanity check.  Does tiff lie inside the file?
330
83
  Internal::enforce(Safe::add(tiffOffset, tiffLength) <= io_->size(), ErrorCode::kerCorruptedMetadata);
331
332
83
  if (io_->seek(tiffOffset, BasicIo::beg) != 0)
333
0
    throw Error(ErrorCode::kerFailedToReadImageData);
334
335
  // Check if this really is a tiff and then call the tiff parser.
336
  // Check is needed because some older models just embed a raw bitstream.
337
  // For those files we skip the parsing step.
338
83
  if (io_->read(readBuff.data(), 4) != 4) {
339
6
    throw Error(ErrorCode::kerFailedToReadImageData);
340
6
  }
341
77
  io_->seek(-4, BasicIo::cur);
342
77
  const std::array<byte, 4> Id1{0x49, 0x49, 0x2A, 0x00};
343
77
  const std::array<byte, 4> Id2{0x4D, 0x4D, 0x00, 0x2A};
344
77
  if (readBuff == Id1 || readBuff == Id2) {
345
1
    DataBuf tiff(tiffLength);
346
1
    io_->read(tiff.data(), tiff.size());
347
348
1
    if (!io_->error() && !io_->eof()) {
349
1
      const DecodeParams dp(max_recursion_depth_);
350
1
      TiffParser::decode(exifData_, iptcData_, xmpData_, tiff.c_data(), tiff.size(), dp);
351
1
    }
352
1
  }
353
77
}
354
355
0
void RafImage::writeMetadata() {
356
  //! Todo: implement me!
357
0
  throw(Error(ErrorCode::kerWritingImageFormatUnsupported, "RAF"));
358
0
}  // RafImage::writeMetadata
359
360
// *************************************************************************
361
// free functions
362
181
Image::UniquePtr newRafInstance(BasicIo::UniquePtr io, const ImageCtorParams& params) {
363
181
  auto image = std::make_unique<RafImage>(std::move(io), params);
364
181
  if (!image->good()) {
365
0
    return nullptr;
366
0
  }
367
181
  return image;
368
181
}
369
370
21.2k
bool isRafType(BasicIo& iIo, bool advance) {
371
21.2k
  const int32_t len = 8;
372
21.2k
  constexpr std::array<byte, len> RafId{'F', 'U', 'J', 'I', 'F', 'I', 'L', 'M'};
373
21.2k
  std::array<byte, len> buf;
374
21.2k
  iIo.read(buf.data(), len);
375
21.2k
  if (iIo.error() || iIo.eof()) {
376
0
    return false;
377
0
  }
378
21.2k
  bool rc = buf == RafId;
379
21.2k
  if (!advance || !rc) {
380
21.2k
    iIo.seek(-len, BasicIo::cur);
381
21.2k
  }
382
21.2k
  return rc;
383
21.2k
}
384
385
}  // namespace Exiv2