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