1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* libwps
3 * Version: MPL 2.0 / LGPLv2.1+
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 * Major Contributor(s):
10 * Copyright (C) 2002 William Lachance (william.lachance@sympatico.ca)
11 * Copyright (C) 2002-2004 Marc Maurer (uwog@uwog.net)
12 *
13 * For minor contributions see the git repository.
14 *
15 * Alternatively, the contents of this file may be used under the terms
16 * of the GNU Lesser General Public License Version 2.1 or later
17 * (LGPLv2.1+), in which case the provisions of the LGPLv2.1+ are
18 * applicable instead of those above.
19 *
20 * For further information visit http://libwps.sourceforge.net
21 */
22
23 #include <string.h>
24
25 #include "libwps_internal.h"
26
27 #include "WPSHeader.h"
28
29 using namespace libwps;
30
WPSHeader(RVNGInputStreamPtr & input,RVNGInputStreamPtr & fileInput,int majorVersion,WPSKind kind,WPSCreator creator)31 WPSHeader::WPSHeader(RVNGInputStreamPtr &input, RVNGInputStreamPtr &fileInput, int majorVersion, WPSKind kind, WPSCreator creator)
32 : m_input(input)
33 , m_fileInput(fileInput)
34 , m_majorVersion(majorVersion)
35 , m_kind(kind)
36 , m_creator(creator)
37 , m_isEncrypted(false)
38 , m_needEncodingFlag(false)
39 {
40 }
41
~WPSHeader()42 WPSHeader::~WPSHeader()
43 {
44 }
45
46
47 /**
48 * So far, we have identified three categories of Works documents.
49 *
50 * Works documents versions 3 and later use a MS OLE container, so we detect
51 * their type by checking for OLE stream names. Works version 2 is like
52 * Works 3 without OLE, so those two types use the same parser.
53 *
54 */
constructHeader(RVNGInputStreamPtr & input)55 WPSHeader *WPSHeader::constructHeader(RVNGInputStreamPtr &input)
56 {
57 if (!input->isStructured())
58 {
59 input->seek(0, librevenge::RVNG_SEEK_SET);
60 uint8_t val[6];
61 for (unsigned char &i : val) i = libwps::readU8(input);
62
63 if (val[0] < 6 && val[1] == 0xFE)
64 {
65 WPS_DEBUG_MSG(("WPSHeader::constructHeader: Microsoft Works v2 format detected\n"));
66 return new WPSHeader(input, input, 2);
67 }
68 // works1 dos file begin by 2054
69 if ((val[0] == 0xFF || val[0] == 0x20) && val[1]==0x54)
70 {
71 WPS_DEBUG_MSG(("WPSHeader::constructHeader: Microsoft Works wks database\n"));
72 return new WPSHeader(input, input, 1, WPS_DATABASE);
73 }
74 if (val[0] == 0xFF && val[1] == 0 && val[2]==2)
75 {
76 WPS_DEBUG_MSG(("WPSHeader::constructHeader: Microsoft Works wks detected\n"));
77 return new WPSHeader(input, input, 3, WPS_SPREADSHEET);
78 }
79 if (val[0] == 00 && val[1] == 0 && val[2]==2)
80 {
81 if (val[3]==0 && (val[4]==0x20 || val[4]==0x21) && val[5]==0x51)
82 {
83 WPS_DEBUG_MSG(("WPSHeader::constructHeader: Quattro Pro wq1 or wq2 detected\n"));
84 return new WPSHeader(input, input, 2, WPS_SPREADSHEET, WPS_QUATTRO_PRO);
85 }
86 if (val[3]==0 && (val[4]==1 || val[4]==2) && val[5]==0x10)
87 {
88 WPS_DEBUG_MSG(("WPSHeader::constructHeader: Quattro Pro wb1 or wb2 detected\n"));
89 return new WPSHeader(input, input, 1000, WPS_SPREADSHEET, WPS_QUATTRO_PRO);
90 }
91 WPS_DEBUG_MSG(("WPSHeader::constructHeader: potential Lotus|Microsft Works|Quattro Pro spreadsheet detected\n"));
92 return new WPSHeader(input, input, 2, WPS_SPREADSHEET);
93 }
94 if (val[0] == 00 && val[1] == 0x0 && val[2]==0x1a)
95 {
96 WPS_DEBUG_MSG(("WPSHeader::constructHeader: Lotus spreadsheet detected\n"));
97 return new WPSHeader(input, input, 101, WPS_SPREADSHEET, WPS_LOTUS);
98 }
99 if ((val[0] == 0x31 || val[0] == 0x32) && val[1] == 0xbe && val[2] == 0 && val[3] == 0 && val[4] == 0 && val[5] == 0xab)
100 {
101 // This value is always 0 for Word for DOS
102 input->seek(96, librevenge::RVNG_SEEK_SET);
103 if (libwps::readU16(input))
104 {
105 WPS_DEBUG_MSG(("WPSHeader::constructHeader: Microsoft Write detected\n"));
106 return new WPSHeader(input, input, 3, WPS_TEXT, WPS_MSWRITE);
107 }
108 else
109 {
110 WPS_DEBUG_MSG(("WPSHeader::constructHeader: Microsoft Word for DOS detected\n"));
111 return new WPSHeader(input, input, 0, WPS_TEXT, WPS_DOSWORD);
112 }
113 }
114 if (val[0]==0x7b && val[1]==0x5c && val[2]==0x70 && val[3]==0x77 &&
115 val[4]==0x69 && val[5]==0x15)
116 {
117 WPS_DEBUG_MSG(("WPSHeader::constructHeader: PocketWord document detected\n"));
118 return new WPSHeader(input, input, 1, WPS_TEXT, WPS_POCKETWORD);
119 }
120 if (val[0] == 0x08 && val[1] == 0xe7)
121 {
122 WPS_DEBUG_MSG(("WPSHeader::constructHeader: Multiplan spreadsheet v1 detected\n"));
123 return new WPSHeader(input, input, 1, WPS_SPREADSHEET, WPS_MULTIPLAN);
124 }
125 if (val[0] == 0x0c && (val[1] == 0xec || val[1] == 0xed))
126 {
127 int vers=int(val[1]-0xeb);
128 WPS_DEBUG_MSG(("WPSHeader::constructHeader: Multiplan spreadsheet v%d detected\n", vers));
129 return new WPSHeader(input, input, vers, WPS_SPREADSHEET, WPS_MULTIPLAN);
130 }
131 // now look at the end of file
132 input->seek(-1, librevenge::RVNG_SEEK_END);
133 val[0]=libwps::readU8(input);
134 if (val[0]==0x1a) // Dos XYWrite ends with 0x1a, Win4 XYWrite ends with fe fc fe 01 00
135 {
136 WPS_DEBUG_MSG(("WPSHeader::constructHeader: potential XYWrite document detected\n"));
137 return new WPSHeader(input, input, 0, WPS_TEXT, WPS_XYWRITE);
138 }
139 if (val[0]==0)
140 {
141 input->seek(-5, librevenge::RVNG_SEEK_END);
142 if (libwps::readU32(input)==0x1fefcfe)
143 {
144 WPS_DEBUG_MSG(("WPSHeader::constructHeader: potential XYWrite document detected\n"));
145 return new WPSHeader(input, input, 1, WPS_TEXT, WPS_XYWRITE);
146 }
147 }
148 return nullptr;
149 }
150
151 RVNGInputStreamPtr document_mn0(input->getSubStreamByName("MN0"));
152 if (document_mn0)
153 {
154 // can be a mac or a pc document
155 // each must contains a MM Ole which begins by 0x444e: Mac or 0x4e44: PC
156 RVNGInputStreamPtr document_mm(input->getSubStreamByName("MM"));
157 if (document_mm && libwps::readU16(document_mm) == 0x4e44)
158 {
159 WPS_DEBUG_MSG(("WPSHeader::constructHeader: Microsoft Works Mac v4 format detected\n"));
160 return nullptr;
161 }
162 // now, look if this is a database document
163 uint16_t fileMagic=libwps::readU16(document_mn0);
164 if (fileMagic == 0x54FF)
165 {
166 WPS_DEBUG_MSG(("WPSHeader::constructHeader: Microsoft Works Database format detected\n"));
167 return new WPSHeader(document_mn0, input, 4, WPS_DATABASE);
168 }
169 WPS_DEBUG_MSG(("WPSHeader::constructHeader: Microsoft Works v4 format detected\n"));
170 return new WPSHeader(document_mn0, input, 4);
171 }
172
173 RVNGInputStreamPtr document_contents(input->getSubStreamByName("CONTENTS"));
174 if (document_contents)
175 {
176 /* check the Works 2000/7/8 format magic */
177 document_contents->seek(0, librevenge::RVNG_SEEK_SET);
178
179 char fileMagic[8];
180 int i = 0;
181 for (; i<7 && !document_contents->isEnd(); i++)
182 fileMagic[i] = char(libwps::readU8(document_contents.get()));
183 fileMagic[i] = '\0';
184
185 /* Works 7/8 */
186 if (0 == strcmp(fileMagic, "CHNKWKS"))
187 {
188 WPS_DEBUG_MSG(("WPSHeader::constructHeader: Microsoft Works v8 (maybe 7) format detected\n"));
189 return new WPSHeader(document_contents, input, 8);
190 }
191
192 /* Works 2000 */
193 if (0 == strcmp(fileMagic, "CHNKINK"))
194 {
195 return new WPSHeader(document_contents, input, 5);
196 }
197 }
198 if (input->existsSubStream("PerfectOffice_MAIN"))
199 {
200 RVNGInputStreamPtr stream(input->getSubStreamByName("PerfectOffice_MAIN"));
201 if (stream && stream->seek(0, librevenge::RVNG_SEEK_SET) == 0 && libwps::readU16(stream)==0 &&
202 libwps::readU8(stream)==2 && libwps::readU8(stream)==0 &&
203 libwps::readU8(stream)==7 && libwps::readU8(stream)==0x10)
204 {
205 WPS_DEBUG_MSG(("WPSHeader::constructHeader: find a Quatto Pro wb3 spreadsheet\n"));
206 return new WPSHeader(stream, input, 1003, WPS_SPREADSHEET, WPS_QUATTRO_PRO);
207 }
208 }
209 if (input->existsSubStream("NativeContent_MAIN"))
210 {
211 RVNGInputStreamPtr stream(input->getSubStreamByName("NativeContent_MAIN"));
212 // check that the first field has type=1, size=0xe, data=QPW9...
213 if (stream && stream->seek(0, librevenge::RVNG_SEEK_SET) == 0 && libwps::readU16(stream)==1 &&
214 libwps::readU16(stream)==0xe && libwps::readU32(stream)==0x39575051)
215 {
216 WPS_DEBUG_MSG(("WPSHeader::constructHeader: find a Quatto Pro qpw spreadsheet\n"));
217 return new WPSHeader(stream, input, 2000, WPS_SPREADSHEET, WPS_QUATTRO_PRO);
218 }
219 }
220
221 /* check for a lotus 123 zip file containing a WK3 and FM3
222 or a old lotus file containing WK1 and FMT
223 */
224 if (input->existsSubStream("WK1") && input->existsSubStream("FMT"))
225 {
226 RVNGInputStreamPtr stream(input->getSubStreamByName("WK1"));
227 if (stream && stream->seek(0, librevenge::RVNG_SEEK_SET) == 0 && libwps::readU16(stream)==0 &&
228 libwps::readU8(stream)==2 && libwps::readU8(stream)==0)
229 {
230 WPS_DEBUG_MSG(("WPSHeader::constructHeader: find a zip Lotus spreadsheet\n"));
231 return new WPSHeader(stream, input, 2, WPS_SPREADSHEET, WPS_LOTUS);
232 }
233 }
234 if (input->existsSubStream("WK3") && input->existsSubStream("FM3"))
235 {
236 RVNGInputStreamPtr stream(input->getSubStreamByName("WK3"));
237 if (stream && stream->seek(0, librevenge::RVNG_SEEK_SET) == 0 && libwps::readU16(stream)==0 &&
238 libwps::readU8(stream)==0x1a && libwps::readU8(stream)==0)
239 {
240 WPS_DEBUG_MSG(("WPSHeader::constructHeader: find a zip Lotus spreadsheet\n"));
241 return new WPSHeader(stream, input, 101, WPS_SPREADSHEET, WPS_LOTUS);
242 }
243 }
244 return nullptr;
245 }
246 /* vim:set shiftwidth=4 softtabstop=4 noexpandtab: */
247