0030550: Coding - Integer overflow in Standard_CString HashCodes
[occt.git] / src / RWStl / RWStl_Reader.cxx
CommitLineData
4178b353 1// Created: 2016-05-01
2// Author: Andrey Betenev
3// Copyright: Open CASCADE 2016
4//
5// This file is part of Open CASCADE Technology software library.
6//
7// This library is free software; you can redistribute it and/or modify it under
8// the terms of the GNU Lesser General Public License version 2.1 as published
9// by the Free Software Foundation, with special exception defined in the file
10// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
11// distribution for complete text of the license and disclaimer of any warranty.
12//
13// Alternatively, this file may be used under the terms of Open CASCADE
14// commercial license or contractual agreement.
15
16#include <RWStl_Reader.hxx>
17
18#include <gp_XY.hxx>
19#include <Message.hxx>
20#include <Message_Messenger.hxx>
21#include <Message_ProgressSentry.hxx>
22#include <NCollection_DataMap.hxx>
23#include <NCollection_IncAllocator.hxx>
24#include <FSD_BinaryFile.hxx>
25#include <OSD_OpenFile.hxx>
26#include <OSD_Timer.hxx>
27#include <Precision.hxx>
28#include <Standard_CLocaleSentry.hxx>
29
30#include <algorithm>
31#include <limits>
32
33IMPLEMENT_STANDARD_RTTIEXT(RWStl_Reader, Standard_Transient)
34
35namespace
36{
37 // Binary STL sizes
38 static const size_t THE_STL_HEADER_SIZE = 84;
39 static const size_t THE_STL_SIZEOF_FACET = 50;
40 static const size_t THE_STL_MIN_FILE_SIZE = THE_STL_HEADER_SIZE + THE_STL_SIZEOF_FACET;
41
42 //! Auxiliary tool for merging nodes during STL reading.
43 class MergeNodeTool
44 {
45 public:
46
47 //! Constructor
48 MergeNodeTool (RWStl_Reader* theReader)
49 : myReader (theReader),
50 myMap (1024, new NCollection_IncAllocator (1024 * 1024))
51 {
52 }
53
54 //! Add new triangle
55 int AddNode (double theX, double theY, double theZ)
56 {
57 // use existing node if found at the same point
58 gp_XYZ aPnt (theX, theY, theZ);
59
60 Standard_Integer anIndex = -1;
61 if (myMap.Find (aPnt, anIndex))
62 {
63 return anIndex;
64 }
65
66 anIndex = myReader->AddNode (aPnt);
67 myMap.Bind (aPnt, anIndex);
68 return anIndex;
69 }
70
71 public:
72
73 static Standard_Boolean IsEqual (const gp_XYZ& thePnt1, const gp_XYZ& thePnt2)
74 {
75 return (thePnt1 - thePnt2).SquareModulus() < Precision::SquareConfusion();
76 }
77
2b2be3fb 78 //! Computes a hash code for the point, in the range [1, theUpperBound]
79 //! @param thePoint the point which hash code is to be computed
80 //! @param theUpperBound the upper bound of the range a computing hash code must be within
81 //! @return a computed hash code, in the range [1, theUpperBound]
82 static Standard_Integer HashCode (const gp_XYZ& thePoint, const Standard_Integer theUpperBound)
4178b353 83 {
2b2be3fb 84 return ::HashCode (thePoint.X() * M_LN10 + thePoint.Y() * M_PI + thePoint.Z() * M_E, theUpperBound);
4178b353 85 }
86
87 private:
88 RWStl_Reader* myReader;
89 NCollection_DataMap<gp_XYZ, Standard_Integer, MergeNodeTool> myMap;
90 };
91
92 //! Read a Little Endian 32 bits float
93 inline static float readStlFloat (const char* theData)
94 {
95 #if OCCT_BINARY_FILE_DO_INVERSE
96 // on big-endian platform, map values byte-per-byte
97 union
98 {
99 uint32_t i;
100 float f;
101 } bidargum;
102 bidargum.i = theData[0] & 0xFF;
103 bidargum.i |= (theData[1] & 0xFF) << 0x08;
104 bidargum.i |= (theData[2] & 0xFF) << 0x10;
105 bidargum.i |= (theData[3] & 0xFF) << 0x18;
106 return bidargum.f;
107 #else
108 // on little-endian platform, use plain cast
109 return *reinterpret_cast<const float*>(theData);
110 #endif
111 }
112
113 //! Read a Little Endian 32 bits float
114 inline static gp_XYZ readStlFloatVec3 (const char* theData)
115 {
116 return gp_XYZ (readStlFloat (theData),
117 readStlFloat (theData + sizeof(float)),
118 readStlFloat (theData + sizeof(float) * 2));
119 }
120
121}
122
123//==============================================================================
124//function : Read
125//purpose :
126//==============================================================================
127
128Standard_Boolean RWStl_Reader::Read (const char* theFile,
129 const Handle(Message_ProgressIndicator)& theProgress)
130{
131 std::filebuf aBuf;
132 OSD_OpenStream (aBuf, theFile, std::ios::in | std::ios::binary);
133 if (!aBuf.is_open())
134 {
135 return Standard_False;
136 }
137
138 Standard_IStream aStream (&aBuf);
1d949423 139
140 // get length of file to feed progress indicator in Ascii mode
141 aStream.seekg (0, aStream.end);
142 std::streampos theEnd = aStream.tellg();
143 aStream.seekg (0, aStream.beg);
144
22e70738 145 // binary STL files cannot be shorter than 134 bytes
146 // (80 bytes header + 4 bytes facet count + 50 bytes for one facet);
147 // thus assume files shorter than 134 as Ascii without probing
148 // (probing may bring stream to fail state if EOF is reached)
149 bool isAscii = ((size_t)theEnd < THE_STL_MIN_FILE_SIZE || IsAscii (aStream));
150
151 while (aStream.good())
4178b353 152 {
22e70738 153 if (isAscii)
1d949423 154 {
155 if (!ReadAscii (aStream, theEnd, theProgress))
156 {
157 break;
158 }
159 }
160 else
161 {
162 if (!ReadBinary (aStream, theProgress))
163 {
164 break;
165 }
166 }
167 aStream >> std::ws; // skip any white spaces
4178b353 168 }
22e70738 169 return ! aStream.fail();
4178b353 170}
171
172//==============================================================================
173//function : IsAscii
174//purpose :
175//==============================================================================
176
177Standard_Boolean RWStl_Reader::IsAscii (Standard_IStream& theStream)
178{
179 // read first 134 bytes to detect file format
180 char aBuffer[THE_STL_MIN_FILE_SIZE];
181 std::streamsize aNbRead = theStream.read (aBuffer, THE_STL_MIN_FILE_SIZE).gcount();
22e70738 182 if (! theStream)
4178b353 183 {
184 Message::DefaultMessenger()->Send ("Error: Cannot read file", Message_Fail);
22e70738 185 return true;
4178b353 186 }
187
188 // put back the read symbols
189 for (std::streamsize aByteIter = aNbRead; aByteIter > 0; --aByteIter)
190 {
191 theStream.unget();
192 }
193
194 // if file is shorter than size of binary file with 1 facet, it must be ascii
195 if (aNbRead < std::streamsize(THE_STL_MIN_FILE_SIZE))
196 {
197 return true;
198 }
199
200 // otherwise, detect binary format by presence of non-ascii symbols in first 128 bytes
201 // (note that binary STL file may start with the same bytes "solid " as Ascii one)
202 for (Standard_Integer aByteIter = 0; aByteIter < aNbRead; ++aByteIter)
203 {
204 if ((unsigned char )aBuffer[aByteIter] > (unsigned char )'~')
205 {
206 return false;
207 }
208 }
209 return true;
210}
211
212// adapted from Standard_CString.cxx
213#ifdef __APPLE__
214 // There are a lot of *_l functions availalbe on Mac OS X - we use them
215 #define SAVE_TL()
216#elif defined(_MSC_VER)
217 // MSVCRT has equivalents with slightly different syntax
218 #define SAVE_TL()
219 #define sscanf_l(theBuffer, theLocale, theFormat, ...) _sscanf_s_l(theBuffer, theFormat, theLocale, __VA_ARGS__)
220#else
221 // glibc provides only limited xlocale implementation:
222 // strtod_l/strtol_l/strtoll_l functions with explicitly specified locale
223 // and newlocale/uselocale/freelocale to switch locale within current thread only.
224 // So we switch to C locale temporarily
225 #define SAVE_TL() Standard_CLocaleSentry aLocaleSentry;
226 #define sscanf_l(theBuffer, theLocale, theFormat, ...) sscanf(theBuffer, theFormat, __VA_ARGS__)
227#endif
228
229// Macro to get 64-bit position of the file from streampos
6b1800cb 230#if defined(_MSC_VER) && _MSC_VER < 1700
231 // In MSVC 2010, cast of streampos to 64-bit int is implemented incorrectly;
232 // work-around (relevant for files larger than 4 GB) is to use internal function seekpos().
233 // Since MSVC 15.8, seekpos() is deprecated and is said to always return 0.
4178b353 234 #define GETPOS(aPos) aPos.seekpos()
235#else
236 #define GETPOS(aPos) ((int64_t)aPos)
237#endif
238
239static inline bool str_starts_with (const char* theStr, const char* theWord, int theN)
240{
241 while (isspace (*theStr) && *theStr != '\0') theStr++;
242 return !strncmp (theStr, theWord, theN);
243}
244
07bbde45 245static bool ReadVertex (const char* theStr, double& theX, double& theY, double& theZ)
246{
247 const char *aStr = theStr;
248
249 // skip 'vertex'
250 while (isspace ((unsigned char)*aStr) || isalpha ((unsigned char)*aStr))
251 ++aStr;
252
253 // read values
254 char *aEnd;
255 theX = Strtod (aStr, &aEnd);
256 theY = Strtod (aStr = aEnd, &aEnd);
257 theZ = Strtod (aStr = aEnd, &aEnd);
258
259 return aEnd != aStr;
260}
261
4178b353 262//==============================================================================
263//function : ReadAscii
264//purpose :
265//==============================================================================
266Standard_Boolean RWStl_Reader::ReadAscii (Standard_IStream& theStream,
267 const std::streampos theUntilPos,
268 const Handle(Message_ProgressIndicator)& theProgress)
269{
270 // use method seekpos() to get true 64-bit offset to enable
271 // handling of large files (VS 2010 64-bit)
272 const int64_t aStartPos = GETPOS(theStream.tellg());
22e70738 273 // Note: 1 is added to theUntilPos to be sure to read the last symbol (relevant for files without EOL at the end)
274 const int64_t aEndPos = (theUntilPos > 0 ? 1 + GETPOS(theUntilPos) : std::numeric_limits<int64_t>::max());
4178b353 275
276 // skip header "solid ..."
71c810df 277 theStream.ignore ((std::streamsize)(aEndPos - aStartPos), '\n');
4178b353 278 if (!theStream)
279 {
280 Message::DefaultMessenger()->Send ("Error: premature end of file", Message_Fail);
281 return false;
282 }
283
284 MergeNodeTool aMergeTool (this);
285 Standard_CLocaleSentry::clocale_t aLocale = Standard_CLocaleSentry::GetCLocale();
286 (void)aLocale; // to avoid warning on GCC where it is actually not used
287 SAVE_TL() // for GCC only, set C locale globally
288
289 // report progress every 1 MiB of read data
290 const int aStepB = 1024 * 1024;
71c810df 291 const Standard_Integer aNbSteps = 1 + Standard_Integer((GETPOS(theUntilPos) - aStartPos) / aStepB);
4178b353 292 Message_ProgressSentry aPSentry (theProgress, "Reading text STL file", 0, aNbSteps, 1);
293
294 int64_t aProgressPos = aStartPos + aStepB;
295 const int64_t LINELEN = 1024;
296 int aNbLine = 1;
297 char aLine1[LINELEN], aLine2[LINELEN], aLine3[LINELEN];
298 while (aPSentry.More())
299 {
300 if (GETPOS(theStream.tellg()) > aProgressPos)
301 {
302 aPSentry.Next();
303 aProgressPos += aStepB;
304 }
305
306 char facet[LINELEN], outer[LINELEN];
71c810df 307 theStream.getline (facet, (std::streamsize)std::min (LINELEN, aEndPos - GETPOS(theStream.tellg()))); // "facet normal nx ny nz"
4178b353 308 if (str_starts_with (facet, "endsolid", 8))
309 {
310 // end of STL code
311 break;
312 }
71c810df 313 theStream.getline (outer, (std::streamsize)std::min (LINELEN, aEndPos - GETPOS(theStream.tellg()))); // "outer loop"
4178b353 314 if (!str_starts_with (facet, "facet", 5) || !str_starts_with (outer, "outer", 5))
315 {
316 TCollection_AsciiString aStr ("Error: unexpected format of facet at line ");
317 aStr += aNbLine + 1;
318 Message::DefaultMessenger()->Send (aStr, Message_Fail);
319 return false;
320 }
321
71c810df 322 theStream.getline (aLine1, (std::streamsize)std::min (LINELEN, aEndPos - GETPOS(theStream.tellg())));
323 theStream.getline (aLine2, (std::streamsize)std::min (LINELEN, aEndPos - GETPOS(theStream.tellg())));
324 theStream.getline (aLine3, (std::streamsize)std::min (LINELEN, aEndPos - GETPOS(theStream.tellg())));
4178b353 325
326 // stop reading if end of file is reached;
327 // note that well-formatted file never ends by the vertex line
328 if (theStream.eof() || GETPOS(theStream.tellg()) >= aEndPos)
329 {
330 break;
331 }
332
333 if (!theStream)
334 {
335 Message::DefaultMessenger()->Send ("Error: premature end of file", Message_Fail);
336 return false;
337 }
338 aNbLine += 5;
339
340 Standard_Real x1, y1, z1, x2, y2, z2, x3, y3, z3;
07bbde45 341 if (! ReadVertex (aLine1, x1, y1, z1) ||
342 ! ReadVertex (aLine2, x2, y2, z2) ||
343 ! ReadVertex (aLine3, x3, y3, z3))
4178b353 344 {
345 TCollection_AsciiString aStr ("Error: cannot read vertex co-ordinates at line ");
346 aStr += aNbLine;
347 Message::DefaultMessenger()->Send(aStr, Message_Fail);
348 return false;
349 }
350
351 // add triangle
352 int n1 = aMergeTool.AddNode (x1, y1, z1);
353 int n2 = aMergeTool.AddNode (x2, y2, z2);
354 int n3 = aMergeTool.AddNode (x3, y3, z3);
355 if (n1 != n2 && n2 != n3 && n3 != n1)
356 {
357 AddTriangle (n1, n2, n3);
358 }
359
71c810df 360 theStream.ignore ((std::streamsize)(aEndPos - GETPOS(theStream.tellg())), '\n'); // skip "endloop"
361 theStream.ignore ((std::streamsize)(aEndPos - GETPOS(theStream.tellg())), '\n'); // skip "endfacet"
4178b353 362
363 aNbLine += 2;
364 }
365
366 return aPSentry.More();
367}
368
369//==============================================================================
370//function : readStlBinary
371//purpose :
372//==============================================================================
373
374Standard_Boolean RWStl_Reader::ReadBinary (Standard_IStream& theStream,
375 const Handle(Message_ProgressIndicator)& theProgress)
376{
377/*
378 // the size of the file (minus the header size)
379 // must be a multiple of SIZEOF_STL_FACET
380 if ((theFileLen - THE_STL_HEADER_SIZE) % THE_STL_SIZEOF_FACET != 0
381 || (theFileLen < THE_STL_MIN_FILE_SIZE))
382 {
383 Message::DefaultMessenger()->Send ("Error: Corrupted binary STL file (inconsistent file size)!", Message_Fail);
384 return Standard_False;
385 }
386 const Standard_Integer aNbFacets = Standard_Integer((theFileLen - THE_STL_HEADER_SIZE) / THE_STL_SIZEOF_FACET);
387*/
388
389 // read file header at first
390 char aHeader[THE_STL_HEADER_SIZE + 1];
391 if (theStream.read (aHeader, THE_STL_HEADER_SIZE).gcount() != std::streamsize(THE_STL_HEADER_SIZE))
392 {
393 Message::DefaultMessenger()->Send ("Error: Corrupted binary STL file!", Message_Fail);
394 return false;
395 }
396
397 // number of facets is stored as 32-bit integer at position 80
398 const Standard_Integer aNbFacets = *(int32_t*)(aHeader + 80);
399
400 MergeNodeTool aMergeTool (this);
401
402 // don't trust the number of triangles which is coded in the file
403 // sometimes it is wrong, and with this technique we don't need to swap endians for integer
404 Message_ProgressSentry aPSentry (theProgress, "Reading binary STL file", 0, aNbFacets, 1);
405 Standard_Integer aNbRead = 0;
406
407 // allocate buffer for 80 triangles
408 const int THE_CHUNK_NBFACETS = 80;
409 char aBuffer[THE_STL_SIZEOF_FACET * THE_CHUNK_NBFACETS];
410
411 // normal + 3 nodes + 2 extra bytes
412 const size_t aVec3Size = sizeof(float) * 3;
413 const size_t aFaceDataLen = aVec3Size * 4 + 2;
414 const char* aBufferPtr = aBuffer;
415 Standard_Integer aNbFacesInBuffer = 0;
416 for (Standard_Integer aNbFacetRead = 0; aNbFacetRead < aNbFacets && aPSentry.More();
417 ++aNbFacetRead, ++aNbRead, --aNbFacesInBuffer, aBufferPtr += aFaceDataLen, aPSentry.Next())
418 {
419 // read more data
420 if (aNbFacesInBuffer <= 0)
421 {
422 aNbFacesInBuffer = Min (THE_CHUNK_NBFACETS, aNbFacets - aNbFacetRead);
423 const std::streamsize aDataToRead = aNbFacesInBuffer * aFaceDataLen;
424 if (theStream.read (aBuffer, aDataToRead).gcount() != aDataToRead)
425 {
4c4420df 426 Message::DefaultMessenger()->Send ("Error: binary STL read failed", Message_Fail);
4178b353 427 return false;
428 }
429 aBufferPtr = aBuffer;
430 }
431
432 // get points from buffer
433// readStlFloatVec3 (aBufferPtr); // skip normal
434 gp_XYZ aP1 = readStlFloatVec3 (aBufferPtr + aVec3Size);
435 gp_XYZ aP2 = readStlFloatVec3 (aBufferPtr + aVec3Size * 2);
436 gp_XYZ aP3 = readStlFloatVec3 (aBufferPtr + aVec3Size * 3);
437
438 // add triangle
439 int n1 = aMergeTool.AddNode (aP1.X(), aP1.Y(), aP1.Z());
440 int n2 = aMergeTool.AddNode (aP2.X(), aP2.Y(), aP2.Z());
441 int n3 = aMergeTool.AddNode (aP3.X(), aP3.Y(), aP3.Z());
442 if (n1 != n2 && n2 != n3 && n3 != n1)
443 {
444 AddTriangle (n1, n2, n3);
445 }
446 }
447
448 return true;
449}