Coverage Report

Created: 2026-10-28 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/exiv2/src/photoshop.cpp
Line
Count
Source
1
#include "config.h"
2
3
#include "photoshop.hpp"
4
5
#include "enforce.hpp"
6
#include "image.hpp"
7
#include "safe_op.hpp"
8
9
#include <array>
10
#include <cstring>
11
12
#ifdef EXIV2_DEBUG_MESSAGES
13
#include <iostream>
14
#endif
15
16
namespace Exiv2 {
17
18
const char Photoshop::irbId_[4][4] = {
19
    {'8', 'B', 'I', 'M'},
20
    {'A', 'g', 'H', 'g'},
21
    {'D', 'C', 'S', 'R'},
22
    {'P', 'H', 'U', 'T'},
23
};
24
25
const char Photoshop::ps3Id_[14] = {'P', 'h', 'o', 't', 'o', 's', 'h', 'o', 'p', ' ', '3', '.', '0', '\0'};
26
27
6.58k
bool Photoshop::isIrb(const byte* pPsData) {
28
6.58k
  if (pPsData == nullptr)
29
0
    return false;
30
31
6.58k
  for (auto id : irbId_)
32
13.0k
    if (std::equal(id, id + 4, pPsData))
33
4.47k
      return true;
34
35
2.10k
  return false;
36
6.58k
}
37
38
1.65k
bool Photoshop::valid(const byte* pPsData, size_t sizePsData) {
39
1.65k
  const byte* record = nullptr;
40
1.65k
  uint32_t sizeIptc = 0;
41
1.65k
  uint32_t sizeHdr = 0;
42
1.65k
  const byte* pCur = pPsData;
43
1.65k
  const byte* pEnd = pPsData + sizePsData;
44
1.65k
  int ret = 0;
45
2.47k
  while (pCur < pEnd && 0 == (ret = Photoshop::locateIptcIrb(pCur, (pEnd - pCur), &record, sizeHdr, sizeIptc))) {
46
818
    pCur = record + sizeHdr + sizeIptc + (sizeIptc & 1);
47
818
  }
48
1.65k
  return ret >= 0;
49
1.65k
}
50
51
// Todo: Generalised from JpegBase::locateIptcData without really understanding
52
//       the format (in particular the header). So it remains to be confirmed
53
//       if this also makes sense for psTag != Photoshop::iptc
54
int Photoshop::locateIrb(const byte* pPsData, size_t sizePsData, uint16_t psTag, const byte** record, uint32_t& sizeHdr,
55
3.53k
                         uint32_t& sizeData) {
56
3.53k
  if (sizePsData < 12) {
57
137
    return 3;
58
137
  }
59
60
  // Used for error checking
61
3.40k
  size_t position = 0;
62
#ifdef EXIV2_DEBUG_MESSAGES
63
  std::cerr << "Photoshop::locateIrb: ";
64
#endif
65
  // Data should follow Photoshop format, if not exit
66
6.37k
  while (position <= (sizePsData - 12) && isIrb(pPsData + position)) {
67
4.29k
    const byte* hrd = pPsData + position;
68
4.29k
    position += 4;
69
4.29k
    uint16_t type = getUShort(pPsData + position, bigEndian);
70
4.29k
    position += 2;
71
#ifdef EXIV2_DEBUG_MESSAGES
72
    std::cerr << "0x" << std::hex << type << std::dec << " ";
73
#endif
74
    // Pascal string is padded to have an even size (including size byte)
75
4.29k
    byte psSize = pPsData[position] + 1;
76
4.29k
    psSize += (psSize & 1);
77
4.29k
    position += psSize;
78
4.29k
    if (position + 4 > sizePsData) {
79
#ifdef EXIV2_DEBUG_MESSAGES
80
      std::cerr << "Warning: "
81
                << "Invalid or extended Photoshop IRB\n";
82
#endif
83
29
      return -2;
84
29
    }
85
4.26k
    uint32_t dataSize = getULong(pPsData + position, bigEndian);
86
4.26k
    position += 4;
87
4.26k
    if (dataSize > (sizePsData - position)) {
88
#ifdef EXIV2_DEBUG_MESSAGES
89
      std::cerr << "Warning: "
90
                << "Invalid Photoshop IRB data size " << dataSize << " or extended Photoshop IRB\n";
91
#endif
92
55
      return -2;
93
55
    }
94
#ifdef EXIV2_DEBUG_MESSAGES
95
    if ((dataSize & 1) && position + dataSize == sizePsData) {
96
      std::cerr << "Warning: "
97
                << "Photoshop IRB data is not padded to even size\n";
98
    }
99
#endif
100
4.21k
    if (type == psTag) {
101
#ifdef EXIV2_DEBUG_MESSAGES
102
      std::cerr << "ok\n";
103
#endif
104
1.23k
      sizeData = dataSize;
105
1.23k
      sizeHdr = psSize + 10;
106
1.23k
      *record = hrd;
107
1.23k
      return 0;
108
1.23k
    }
109
    // Data size is also padded to be even
110
2.97k
    position += dataSize + (dataSize & 1);
111
2.97k
  }
112
#ifdef EXIV2_DEBUG_MESSAGES
113
  std::cerr << "pPsData doesn't start with '8BIM'\n";
114
#endif
115
2.08k
  if (position < sizePsData) {
116
#ifdef EXIV2_DEBUG_MESSAGES
117
    std::cerr << "Warning: "
118
              << "Invalid or extended Photoshop IRB\n";
119
#endif
120
1.93k
    return -2;
121
1.93k
  }
122
145
  return 3;
123
2.08k
}
124
125
int Photoshop::locateIptcIrb(const byte* pPsData, size_t sizePsData, const byte** record, uint32_t& sizeHdr,
126
3.53k
                             uint32_t& sizeData) {
127
3.53k
  return locateIrb(pPsData, sizePsData, iptc_, record, sizeHdr, sizeData);
128
3.53k
}
129
130
int Photoshop::locatePreviewIrb(const byte* pPsData, size_t sizePsData, const byte** record, uint32_t& sizeHdr,
131
0
                                uint32_t& sizeData) {
132
0
  return locateIrb(pPsData, sizePsData, preview_, record, sizeHdr, sizeData);
133
0
}
134
135
26
DataBuf Photoshop::setIptcIrb(const byte* pPsData, size_t sizePsData, const IptcData& iptcData) {
136
#ifdef EXIV2_DEBUG_MESSAGES
137
  std::cerr << "IRB block at the beginning of Photoshop::setIptcIrb\n";
138
  if (sizePsData == 0)
139
    std::cerr << "  None.\n";
140
  else
141
    hexdump(std::cerr, pPsData, sizePsData);
142
#endif
143
26
  const byte* record = pPsData;
144
26
  uint32_t sizeIptc = 0;
145
26
  uint32_t sizeHdr = 0;
146
26
  DataBuf rc;
147
26
  if (0 > Photoshop::locateIptcIrb(pPsData, sizePsData, &record, sizeHdr, sizeIptc)) {
148
6
    return rc;
149
6
  }
150
151
20
  Blob psBlob;
152
20
  const auto sizeFront = static_cast<size_t>(record - pPsData);
153
  // Write data before old record.
154
20
  if (sizePsData > 0 && sizeFront > 0) {
155
0
    append(psBlob, pPsData, sizeFront);
156
0
  }
157
158
  // Write new iptc record if we have it
159
20
  if (DataBuf rawIptc = IptcParser::encode(iptcData); !rawIptc.empty()) {
160
0
    std::array<byte, 12> tmpBuf;
161
0
    std::copy_n(Photoshop::irbId_[0], 4, tmpBuf.begin());
162
0
    us2Data(tmpBuf.data() + 4, iptc_, bigEndian);
163
0
    tmpBuf[6] = 0;
164
0
    tmpBuf[7] = 0;
165
0
    ul2Data(tmpBuf.data() + 8, static_cast<uint32_t>(rawIptc.size()), bigEndian);
166
0
    append(psBlob, tmpBuf.data(), 12);
167
0
    append(psBlob, rawIptc.c_data(), rawIptc.size());
168
    // Data is padded to be even (but not included in size)
169
0
    if (rawIptc.size() & 1)
170
0
      psBlob.push_back(0x00);
171
0
  }
172
173
  // Write existing stuff after record, skip the current and all remaining IPTC blocks
174
20
  size_t pos = sizeFront;
175
20
  auto nextSizeData = Safe::add<long>(static_cast<long>(sizePsData), -static_cast<long>(pos));
176
20
  Internal::enforce(nextSizeData >= 0, ErrorCode::kerCorruptedMetadata);
177
20
  while (0 == Photoshop::locateIptcIrb(pPsData + pos, nextSizeData, &record, sizeHdr, sizeIptc)) {
178
0
    const auto newPos = static_cast<size_t>(record - pPsData);
179
0
    if (newPos > pos) {  // Copy data up to the IPTC IRB
180
0
      append(psBlob, pPsData + pos, newPos - pos);
181
0
    }
182
0
    pos = newPos + sizeHdr + sizeIptc + (sizeIptc & 1);  // Skip the IPTC IRB
183
0
    nextSizeData = Safe::add<long>(static_cast<long>(sizePsData), -static_cast<long>(pos));
184
0
    Internal::enforce(nextSizeData >= 0, ErrorCode::kerCorruptedMetadata);
185
0
  }
186
20
  if (pos < sizePsData) {
187
3
    append(psBlob, pPsData + pos, sizePsData - pos);
188
3
  }
189
190
  // Data is rounded to be even
191
20
  if (!psBlob.empty())
192
3
    rc = DataBuf(psBlob.data(), psBlob.size());
193
#ifdef EXIV2_DEBUG_MESSAGES
194
  std::cerr << "IRB block at the end of Photoshop::setIptcIrb\n";
195
  if (rc.empty())
196
    std::cerr << "  None.\n";
197
  else
198
    hexdump(std::cerr, rc.c_data(), rc.size());
199
#endif
200
20
  return rc;
201
26
}
202
203
}  // namespace Exiv2