Coverage Report

Created: 2026-10-28 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libetonyek/src/lib/IWAObjectIndex.cpp
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/*
3
 * This file is part of the libetonyek project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 */
9
10
#include "IWAObjectIndex.h"
11
12
#include <cassert>
13
14
#include "IWAMessage.h"
15
#include "IWASnappyStream.h"
16
#include "IWORKTypes.h"
17
18
#include "IWAParser.h"
19
20
namespace libetonyek
21
{
22
23
using boost::optional;
24
25
using std::deque;
26
using std::make_pair;
27
using std::make_shared;
28
using std::string;
29
30
IWAObjectIndex::ObjectRecord::ObjectRecord()
31
0
  : m_stream()
32
0
  , m_type(0)
33
0
  , m_headerRange(0, 0)
34
0
  , m_dataRange(0, 0)
35
0
{
36
0
}
37
38
IWAObjectIndex::ObjectRecord::ObjectRecord(const RVNGInputStreamPtr_t &stream, const unsigned type,
39
                                           const long pos, const unsigned long headerLen, const unsigned long dataLen)
40
0
  : m_stream(stream)
41
0
  , m_type(type)
42
0
  , m_headerRange(pos, pos + long(headerLen))
43
0
  , m_dataRange(m_headerRange.second, m_headerRange.second + long(dataLen))
44
0
{
45
0
}
46
47
IWAObjectIndex::IWAObjectIndex(const RVNGInputStreamPtr_t &fragments, const RVNGInputStreamPtr_t &package)
48
0
  : m_fragments(fragments)
49
0
  , m_package(package)
50
0
  , m_unparsedFragments()
51
0
  , m_fragmentObjectMap()
52
0
  , m_fileMap()
53
0
  , m_fileColorMap()
54
0
{
55
0
}
56
57
void IWAObjectIndex::parse()
58
0
{
59
0
  m_unparsedFragments[2] = "Index/Metadata.iwa";
60
0
  m_fragmentObjectMap[2] = make_pair(2, ObjectRecord());
61
0
  scanFragment(2);
62
0
  const auto indexIt = m_fragmentObjectMap.find(2);
63
0
  if (indexIt == m_fragmentObjectMap.end() || !indexIt->second.second.m_stream)
64
0
  {
65
    // TODO: scan all fragment files
66
0
    ETONYEK_DEBUG_MSG(("IWAObjectIndex::parse: object index is broken, nothing will be parsed\n"));
67
0
  }
68
0
  else
69
0
  {
70
0
    const ObjectRecord &rec = indexIt->second.second;
71
0
    const IWAMessage objectIndex(rec.m_stream, rec.m_dataRange.first, rec.m_dataRange.second);
72
0
    const deque<IWAMessage> &fragments = objectIndex.message(3).repeated();
73
0
    for (const auto &fragment : fragments)
74
0
    {
75
0
      if (fragment.uint32(1) && (fragment.string(2) || fragment.string(3)))
76
0
      {
77
0
        const unsigned pathIdx = fragment.string(3) ? 3 : 2;
78
0
        m_unparsedFragments[fragment.uint32(1).get()] = "Index/" + fragment.string(pathIdx).get() + ".iwa";
79
0
        m_fragmentObjectMap[fragment.uint32(1).get()] = make_pair(fragment.uint32(1).get(), ObjectRecord());
80
0
      }
81
0
      const deque<IWAMessage> &refs = fragment.message(6).repeated();
82
0
      for (const auto &ref : refs)
83
0
      {
84
0
        if (ref.uint32(1) && ref.uint32(2))
85
0
          m_fragmentObjectMap[ref.uint32(2).get()] = make_pair(ref.uint32(1).get(), ObjectRecord());
86
0
      }
87
0
    }
88
0
    const deque<IWAMessage> &files = objectIndex.message(4).repeated();
89
0
    for (const auto &file : files)
90
0
    {
91
0
      if (file.uint32(1) && m_package)
92
0
      {
93
0
        const string virtualPath(file.string(3) ? ("Data/" + get(file.string(3))) : "");
94
0
        const string internalPath(file.string(4) ? ("Data/" + get(file.string(4))) : "");
95
0
        string path;
96
0
        if (!internalPath.empty() && m_package->existsSubStream(internalPath.c_str()))
97
0
          path = internalPath;
98
0
        else if (!virtualPath.empty() && m_package->existsSubStream(virtualPath.c_str()))
99
0
          path = virtualPath;
100
0
        if (!path.empty())
101
0
          m_fileMap[file.uint32(1).get()] = make_pair(path, RVNGInputStreamPtr_t());
102
0
      }
103
0
    }
104
105
    // search the color id map
106
0
    auto replaceId=objectIndex.uint32(1).optional();
107
0
    boost::optional<unsigned> replaceRef;
108
0
    if (objectIndex.message(10)) replaceRef=objectIndex.message(10).uint32(1).optional();
109
0
    if (replaceRef)
110
0
    {
111
0
      if (replaceId && get(replaceRef)!=get(replaceId))
112
0
      {
113
        // happens in pages file
114
0
        ETONYEK_DEBUG_MSG(("IWAObjectIndex::parse: replace id=%d is different from replace ref=%d\n", int(get(replaceId)), int(get(replaceRef))));
115
0
      }
116
0
      scanColorFileMap(get(replaceRef));
117
0
    }
118
0
    else if (replaceId)
119
0
      scanColorFileMap(get(replaceId));
120
0
  }
121
0
}
122
123
void IWAObjectIndex::queryObject(const unsigned id, unsigned &type, boost::optional<IWAMessage> &msg) const
124
0
{
125
0
  const auto recIt = m_fragmentObjectMap.find(id);
126
0
  if (recIt == m_fragmentObjectMap.end())
127
0
  {
128
0
    ETONYEK_DEBUG_MSG(("IWAObjectIndex::queryObject: object %u not found\n", id));
129
0
    return;
130
0
  }
131
0
  if (!recIt->second.second.m_stream)
132
0
    const_cast<IWAObjectIndex *>(this)->scanFragment(recIt->second.first);
133
0
  if (recIt->second.second.m_stream)
134
0
  {
135
0
    const ObjectRecord &objRecord = recIt->second.second;
136
0
    msg = IWAMessage(objRecord.m_stream, objRecord.m_dataRange.first, objRecord.m_dataRange.second);
137
0
    type = objRecord.m_type;
138
0
  }
139
0
}
140
141
boost::optional<unsigned> IWAObjectIndex::getObjectType(const unsigned id) const
142
0
{
143
  // improve me
144
0
  boost::optional<IWAMessage> msg;
145
0
  unsigned type=0; // for g++-7
146
0
  queryObject(id, type, msg);
147
0
  if (!msg)
148
0
    return boost::none;
149
0
  return type;
150
0
}
151
152
const RVNGInputStreamPtr_t IWAObjectIndex::queryFile(const unsigned id) const
153
0
{
154
0
  const auto it = m_fileMap.find(id);
155
156
0
  if (it == m_fileMap.end())
157
0
  {
158
0
    ETONYEK_DEBUG_MSG(("IWAObjectIndex::queryFile: file %u not found\n", id));
159
0
    return RVNGInputStreamPtr_t();
160
0
  }
161
162
0
  if (!it->second.second && m_package)
163
0
  {
164
0
    assert(m_package->existsSubStream(it->second.first.c_str())); // we already checked for its presence
165
0
    it->second.second.reset(m_package->getSubStreamByName(it->second.first.c_str()));
166
0
  }
167
168
0
  return it->second.second;
169
0
}
170
171
void IWAObjectIndex::scanFragment(const unsigned id)
172
0
{
173
  // scan the fragment file
174
0
  const auto fragmentIt = m_unparsedFragments.find(id);
175
0
  if (fragmentIt != m_unparsedFragments.end())
176
0
  {
177
0
    const RVNGInputStreamPtr_t stream(m_fragments->getSubStreamByName(fragmentIt->second.c_str()));
178
0
    if (stream)
179
0
    {
180
0
      const auto fragment = make_shared<IWASnappyStream>(stream);
181
0
      scanFragment(fragmentIt->first, fragment);
182
0
    }
183
0
    else
184
0
    {
185
0
      ETONYEK_DEBUG_MSG(("IWAObjectIndex::scanFragment: file %s does not exist\n", fragmentIt->second.c_str()));
186
0
    }
187
0
    m_unparsedFragments.erase(fragmentIt);
188
0
  }
189
0
}
190
191
void IWAObjectIndex::scanFragment(const unsigned id, const RVNGInputStreamPtr_t &stream)
192
0
try
193
0
{
194
0
  while (!stream->isEnd())
195
0
  {
196
    // scan a single object
197
0
    const uint64_t headerLen = readUVar(stream);
198
0
    const long start = stream->tell();
199
0
    const uint64_t remaining = getRemainingLength(stream);
200
0
    if (headerLen > remaining)
201
0
      break;
202
0
    const IWAMessage header(stream, headerLen);
203
0
    uint64_t dataLen = 0;
204
0
    optional<unsigned> type;
205
0
    bool ok=true;
206
0
    for (auto const &info : header.message(2))   // go through all data information
207
0
    {
208
0
      if (!info.uint64(3))
209
0
      {
210
0
        ok=false;
211
0
        break;
212
0
      }
213
0
      if (info.uint64(3).get() > remaining - headerLen - dataLen)
214
0
      {
215
0
        ok=false;
216
0
        break;
217
0
      }
218
0
      dataLen += info.uint64(3).get();
219
0
      if (!type) type=info.uint32(1).optional(); // normally, all data must define the same type
220
0
    }
221
0
    if (!ok) break;
222
0
    if (header.uint32(1))
223
0
    {
224
0
      const ObjectRecord rec(stream, get_optional_value_or(type, 0), start, (unsigned long)(headerLen), (unsigned long)(dataLen));
225
0
      m_fragmentObjectMap[header.uint32(1).get()] = make_pair(id, rec);
226
0
    }
227
0
    if (stream->seek(start + long(headerLen) + long(dataLen), librevenge::RVNG_SEEK_SET) != 0)
228
0
      break;
229
0
  }
230
0
}
231
0
catch (...)
232
0
{
233
  // just read as much as possible
234
0
}
235
236
boost::optional<IWORKColor> IWAObjectIndex::queryFileColor(unsigned id) const
237
0
{
238
0
  auto it=m_fileColorMap.find(id);
239
0
  if (it==m_fileColorMap.end())
240
0
  {
241
0
    ETONYEK_DEBUG_MSG(("IWAObjectIndex::queryFileColor: can not find color for %d\n", int(id)));
242
0
    return boost::none;
243
0
  }
244
0
  return it->second;
245
0
}
246
247
void IWAObjectIndex::scanColorFileMap(unsigned id)
248
0
try
249
0
{
250
0
  const auto indexIt = m_fragmentObjectMap.find(id);
251
0
  if (indexIt == m_fragmentObjectMap.end() || !indexIt->second.second.m_stream)
252
0
  {
253
    // TODO: scan all fragment files
254
0
    ETONYEK_DEBUG_MSG(("IWAObjectIndex::scanColorFileMap: can not find object %d\n", int(id)));
255
0
    return;
256
0
  }
257
0
  const ObjectRecord &rec = indexIt->second.second;
258
0
  const IWAMessage objectIndex(rec.m_stream, rec.m_dataRange.first, rec.m_dataRange.second);
259
0
  for (auto const &corr : objectIndex.message(1).repeated())
260
0
  {
261
0
    auto ref=IWAParser::readRef(corr, 2);
262
0
    if (!corr.uint32(1) || !ref)
263
0
    {
264
0
      ETONYEK_DEBUG_MSG(("IWAObjectIndex::scanColorFileMap: can not parse some correspondances\n"));
265
0
      continue;
266
0
    }
267
0
    auto color=scanColorFileCorrespondance(*ref);
268
0
    if (color) m_fileColorMap[get(corr.uint32(1))]=get(color);
269
0
  }
270
0
}
271
0
catch (...)
272
0
{
273
0
}
274
275
boost::optional<IWORKColor> IWAObjectIndex::scanColorFileCorrespondance(unsigned id)
276
0
try
277
0
{
278
0
  const auto indexIt = m_fragmentObjectMap.find(id);
279
0
  if (indexIt == m_fragmentObjectMap.end() || !indexIt->second.second.m_stream)
280
0
  {
281
    // TODO: scan all fragment files
282
0
    ETONYEK_DEBUG_MSG(("IWAObjectIndex::scanColorFileCorrespondance: can not find object %d\n", int(id)));
283
0
    return boost::none;
284
0
  }
285
0
  const ObjectRecord &rec = indexIt->second.second;
286
0
  const IWAMessage objectIndex(rec.m_stream, rec.m_dataRange.first, rec.m_dataRange.second);
287
0
  return IWAParser::readColor(objectIndex, 1);
288
0
}
289
0
catch (...)
290
0
{
291
0
  return boost::none;
292
0
}
293
294
}
295
296
/* vim:set shiftwidth=2 softtabstop=2 expandtab: */