Coverage Report

Created: 2026-09-06 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libe-book/src/lib/PDBParser.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 libe-book 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 <cassert>
11
#include <string>
12
#include <vector>
13
14
#include <librevenge-stream/librevenge-stream.h>
15
16
#include "libebook_utils.h"
17
#include "EBOOKStreamView.h"
18
#include "PDBParser.h"
19
20
using std::unique_ptr;
21
22
namespace libebook
23
{
24
25
namespace
26
{
27
28
struct HeaderData
29
{
30
  HeaderData();
31
32
  std::string m_name;
33
  unsigned m_version;
34
  unsigned m_appInfoID;
35
  unsigned m_sortInfoID;
36
  unsigned m_type;
37
  unsigned m_creator;
38
  unsigned m_nextRecordListID;
39
  unsigned m_numberOfRecords;
40
  std::vector<unsigned> m_recordOffsets;
41
};
42
43
}
44
45
namespace
46
{
47
48
HeaderData::HeaderData()
49
0
  : m_name()
50
0
  , m_version(0)
51
0
  , m_appInfoID(0)
52
0
  , m_sortInfoID(0)
53
0
  , m_type(0)
54
0
  , m_creator(0)
55
0
  , m_nextRecordListID(0)
56
0
  , m_numberOfRecords(0)
57
0
  , m_recordOffsets()
58
0
{
59
0
}
60
61
}
62
63
struct PDBParserImpl
64
{
65
  PDBParserImpl(librevenge::RVNGInputStream *input, librevenge::RVNGTextInterface *document);
66
67
  HeaderData m_header;
68
  librevenge::RVNGInputStream *m_input;
69
  librevenge::RVNGTextInterface *m_document;
70
71
private:
72
// disable copying
73
  PDBParserImpl(const PDBParserImpl &other);
74
  PDBParserImpl &operator=(const PDBParserImpl &other);
75
};
76
77
PDBParserImpl::PDBParserImpl(librevenge::RVNGInputStream *const input, librevenge::RVNGTextInterface *const document)
78
0
  : m_header()
79
0
  , m_input(input)
80
0
  , m_document(document)
81
0
{
82
0
}
83
84
PDBParser::PDBParser(librevenge::RVNGInputStream *const input, librevenge::RVNGTextInterface *const document,
85
                     const unsigned type, const unsigned creator)
86
0
  : m_impl(new PDBParserImpl(input, document))
87
0
{
88
0
  readHeader();
89
90
0
  if ((type != m_impl->m_header.m_type) || (creator != m_impl->m_header.m_creator))
91
0
    throw UnsupportedFormat();
92
93
0
  if ((0 == m_impl->m_header.m_numberOfRecords)
94
0
      || (m_impl->m_header.m_recordOffsets.size() != m_impl->m_header.m_numberOfRecords))
95
0
    throw UnsupportedFormat();
96
0
}
97
98
PDBParser::~PDBParser()
99
0
{
100
0
}
101
102
bool PDBParser::parse()
103
0
{
104
0
  if (m_impl->m_header.m_appInfoID)
105
0
  {
106
    // TODO: implement me
107
0
  }
108
0
  if (m_impl->m_header.m_sortInfoID)
109
0
  {
110
    // TODO: implement me
111
0
  }
112
113
0
  {
114
0
    std::unique_ptr<librevenge::RVNGInputStream> input(getRecordStream(0));
115
0
    readIndexRecord(input.get());
116
0
  }
117
118
0
  readDataRecords();
119
120
0
  return true;
121
0
}
122
123
librevenge::RVNGTextInterface *PDBParser::getDocument() const
124
0
{
125
0
  return m_impl->m_document;
126
0
}
127
128
const char *PDBParser::getName() const
129
0
{
130
0
  return m_impl->m_header.m_name.c_str();
131
0
}
132
133
librevenge::RVNGInputStream *PDBParser::getAppInfoRecord() const
134
0
{
135
  // TODO: implement me
136
0
  return nullptr;
137
0
}
138
139
librevenge::RVNGInputStream *PDBParser::getIndexRecord() const
140
0
{
141
0
  return getRecordStream(0);
142
0
}
143
144
unsigned PDBParser::getDataRecordCount() const
145
0
{
146
0
  return m_impl->m_header.m_numberOfRecords - 1;
147
0
}
148
149
librevenge::RVNGInputStream *PDBParser::getDataRecord(unsigned n) const
150
0
{
151
0
  return getRecordStream(n + 1);
152
0
}
153
154
librevenge::RVNGInputStream *PDBParser::getDataRecords() const
155
0
{
156
0
  const auto begin = (long) m_impl->m_header.m_recordOffsets[1];
157
0
  m_impl->m_input->seek(0, librevenge::RVNG_SEEK_END);
158
0
  const long end = m_impl->m_input->tell();
159
160
0
  return new EBOOKStreamView(m_impl->m_input, begin, end);
161
0
}
162
163
librevenge::RVNGInputStream *PDBParser::getDataRecords(unsigned first, unsigned last) const
164
0
{
165
0
  if (first >= last)
166
0
    return nullptr;
167
0
  if ((m_impl->m_header.m_numberOfRecords - 1) < last)
168
0
    return nullptr;
169
170
0
  const auto begin = (long) m_impl->m_header.m_recordOffsets[first + 1];
171
0
  long end = 0;
172
0
  if ((m_impl->m_header.m_numberOfRecords - 1) == last) // ends with the last record
173
0
  {
174
0
    m_impl->m_input->seek(0, librevenge::RVNG_SEEK_END);
175
0
    end = m_impl->m_input->tell();
176
0
  }
177
0
  else
178
0
    end = (long) m_impl->m_header.m_recordOffsets[last + 1];
179
180
0
  return new EBOOKStreamView(m_impl->m_input, begin, end);
181
0
}
182
183
void PDBParser::readDataRecords()
184
0
{
185
0
  for (unsigned i = 1; i != m_impl->m_header.m_numberOfRecords; ++i)
186
0
  {
187
0
    unique_ptr<librevenge::RVNGInputStream> record(getRecordStream(i));
188
0
    readDataRecord(record.get(), m_impl->m_header.m_numberOfRecords - 1 == i);
189
0
  }
190
0
}
191
192
void PDBParser::readHeader()
193
0
{
194
0
  m_impl->m_input->seek(0, librevenge::RVNG_SEEK_SET);
195
196
0
  char name[32];
197
0
  unsigned nameLen = 0;
198
0
  while (nameLen != 32)
199
0
  {
200
0
    unsigned char c = readU8(m_impl->m_input);
201
0
    name[nameLen++] = (char) c;
202
0
    if (0 == c)
203
0
      break;
204
0
  }
205
  // We read the whole name, but there was no terminating 0. Either a
206
  // broken file or not a PDB file at all...
207
0
  if (nameLen == 32)
208
0
    --nameLen;
209
0
  name[nameLen] = 0;
210
0
  m_impl->m_header.m_name.assign(name, nameLen);
211
212
  // skip the unused bytes of name and ignore 2 bytes of attributes
213
0
  m_impl->m_input->seek(34, librevenge::RVNG_SEEK_SET);
214
0
  m_impl->m_header.m_version = readU16(m_impl->m_input, true);
215
  // ignore dates, modification number and IDs; 6*4 = 24 bytes
216
0
  skip(m_impl->m_input, 24);
217
0
  m_impl->m_header.m_type = readU32(m_impl->m_input, true);
218
0
  m_impl->m_header.m_creator = readU32(m_impl->m_input, true);
219
0
  skip(m_impl->m_input, 4); // skip uniqueIDseed
220
0
  m_impl->m_header.m_nextRecordListID = readU32(m_impl->m_input, true);
221
0
  assert(m_impl->m_input->tell() == 76);
222
0
  m_impl->m_header.m_numberOfRecords = readU16(m_impl->m_input, true);
223
0
  if (m_impl->m_header.m_numberOfRecords > getRemainingLength(m_impl->m_input) / 8)
224
0
    m_impl->m_header.m_numberOfRecords = getRemainingLength(m_impl->m_input) / 8;
225
226
  // read records
227
0
  for (unsigned i = 0; i != m_impl->m_header.m_numberOfRecords; ++i)
228
0
  {
229
0
    m_impl->m_header.m_recordOffsets.push_back(readU32(m_impl->m_input, true));
230
0
    skip(m_impl->m_input, 4); // skip the uninteresting remainder
231
0
  }
232
0
}
233
234
librevenge::RVNGInputStream *PDBParser::getRecordStream(const unsigned n) const
235
0
{
236
0
  if (n >= m_impl->m_header.m_numberOfRecords)
237
0
    return nullptr;
238
239
0
  const auto begin = (long) m_impl->m_header.m_recordOffsets[n];
240
0
  long end = 0;
241
0
  if ((m_impl->m_header.m_numberOfRecords - 1) == n) // the last record
242
0
  {
243
0
    m_impl->m_input->seek(0, librevenge::RVNG_SEEK_END);
244
0
    end = m_impl->m_input->tell();
245
0
  }
246
0
  else
247
0
    end = (long) m_impl->m_header.m_recordOffsets[n + 1];
248
249
0
  return new EBOOKStreamView(m_impl->m_input, begin, end);
250
0
}
251
252
}
253
254
/* vim:set shiftwidth=2 softtabstop=2 expandtab: */