1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
22 #include <comphelper/docpasswordhelper.hxx>
23 #include <com/sun/star/beans/NamedValue.hpp>
24 #include <filter/msfilter/mscodec.hxx>
25 #include <tools/stream.hxx>
27 #include "xlstream.hxx"
28 #include "xlconst.hxx"
32 /* ============================================================================
33 Input stream class for Excel import
34 - CONTINUE record handling
36 ============================================================================ */
40 class XclImpDecrypter
;
41 typedef std::shared_ptr
< XclImpDecrypter
> XclImpDecrypterRef
;
43 /** Base class for BIFF stream decryption. */
44 class XclImpDecrypter
: public ::comphelper::IDocPasswordVerifier
47 explicit XclImpDecrypter();
48 virtual ~XclImpDecrypter() override
;
50 /** Returns the current error code of the decrypter. */
51 const ErrCode
& GetError() const { return mnError
; }
52 /** Returns true, if the decoder has been initialized correctly. */
53 bool IsValid() const { return mnError
== ERRCODE_NONE
; }
55 /** Creates a (ref-counted) copy of this decrypter object. */
56 XclImpDecrypterRef
Clone() const;
58 /** Implementation of the ::comphelper::IDocPasswordVerifier interface */
59 virtual ::comphelper::DocPasswordVerifierResult
verifyPassword( const OUString
& rPassword
, css::uno::Sequence
< css::beans::NamedValue
>& o_rEncryptionData
) override
;
60 virtual ::comphelper::DocPasswordVerifierResult
verifyEncryptionData( const css::uno::Sequence
< css::beans::NamedValue
>& rEncryptionData
) override
;
62 /** Updates the decrypter on start of a new record or after seeking stream. */
63 void Update( const SvStream
& rStrm
, sal_uInt16 nRecSize
);
64 /** Reads and decrypts nBytes bytes and stores data into the existing(!) buffer pData.
65 @return Count of bytes really read. */
66 sal_uInt16
Read( SvStream
& rStrm
, void* pData
, sal_uInt16 nBytes
);
69 /** Protected copy c'tor for OnClone(). */
70 explicit XclImpDecrypter( const XclImpDecrypter
& rSrc
);
73 /** Implementation of cloning this object. */
74 virtual XclImpDecrypter
* OnClone() const = 0;
75 /** Derived classes implement password verification and initialization of
77 virtual css::uno::Sequence
< css::beans::NamedValue
>
78 OnVerifyPassword( const OUString
& rPassword
) = 0;
79 virtual bool OnVerifyEncryptionData( const css::uno::Sequence
< css::beans::NamedValue
>& rEncryptionData
) = 0;
81 /** Implementation of updating the decrypter. */
82 virtual void OnUpdate( std::size_t nOldStrmPos
, std::size_t nNewStrmPos
, sal_uInt16 nRecSize
) = 0;
83 /** Implementation of the decryption. */
84 virtual sal_uInt16
OnRead( SvStream
& rStrm
, sal_uInt8
* pnData
, sal_uInt16 nBytes
) = 0;
87 ErrCode mnError
; /// Decrypter error code.
88 sal_uInt64 mnOldPos
; /// Last known stream position.
89 sal_uInt16 mnRecSize
; /// Current record size.
92 /** Decrypts BIFF5 stream contents. */
93 class XclImpBiff5Decrypter
: public XclImpDecrypter
96 explicit XclImpBiff5Decrypter( sal_uInt16 nKey
, sal_uInt16 nHash
);
99 /** Private copy c'tor for OnClone(). */
100 explicit XclImpBiff5Decrypter( const XclImpBiff5Decrypter
& rSrc
);
102 /** Implementation of cloning this object. */
103 virtual XclImpBiff5Decrypter
* OnClone() const override
;
104 /** Implements password verification and initialization of the decoder. */
105 virtual css::uno::Sequence
< css::beans::NamedValue
>
106 OnVerifyPassword( const OUString
& rPassword
) override
;
107 virtual bool OnVerifyEncryptionData( const css::uno::Sequence
< css::beans::NamedValue
>& rEncryptionData
) override
;
108 /** Implementation of updating the decrypter. */
109 virtual void OnUpdate( std::size_t nOldStrmPos
, std::size_t nNewStrmPos
, sal_uInt16 nRecSize
) override
;
110 /** Implementation of the decryption. */
111 virtual sal_uInt16
OnRead( SvStream
& rStrm
, sal_uInt8
* pnData
, sal_uInt16 nBytes
) override
;
114 ::msfilter::MSCodec_XorXLS95 maCodec
; /// Crypto algorithm implementation.
115 css::uno::Sequence
< css::beans::NamedValue
> maEncryptionData
;
120 /** Decrypts BIFF8 stream contents using the given document identifier. */
121 class XclImpBiff8Decrypter
: public XclImpDecrypter
124 /** Implements password verification and initialization of the decoder. */
125 virtual css::uno::Sequence
< css::beans::NamedValue
>
126 OnVerifyPassword( const OUString
& rPassword
) override
;
127 virtual bool OnVerifyEncryptionData( const css::uno::Sequence
< css::beans::NamedValue
>& rEncryptionData
) override
;
128 /** Implementation of updating the decrypter. */
129 virtual void OnUpdate( std::size_t nOldStrmPos
, std::size_t nNewStrmPos
, sal_uInt16 nRecSize
) override
;
130 /** Implementation of the decryption. */
131 virtual sal_uInt16
OnRead( SvStream
& rStrm
, sal_uInt8
* pnData
, sal_uInt16 nBytes
) override
;
133 /** Returns the block number corresponding to the passed stream position. */
134 static sal_uInt32
GetBlock( std::size_t nStrmPos
);
135 /** Returns the block offset corresponding to the passed stream position. */
136 static sal_uInt16
GetOffset( std::size_t nStrmPos
);
139 explicit XclImpBiff8Decrypter( std::vector
<sal_uInt8
>&& rSalt
,
140 std::vector
<sal_uInt8
>&& rVerifier
,
141 std::vector
<sal_uInt8
>&& rVerifierHash
);
143 explicit XclImpBiff8Decrypter(const XclImpBiff8Decrypter
& rSrc
);
145 css::uno::Sequence
< css::beans::NamedValue
> maEncryptionData
;
146 std::vector
< sal_uInt8
> maSalt
;
147 std::vector
< sal_uInt8
> maVerifier
;
148 std::vector
< sal_uInt8
> maVerifierHash
;
149 msfilter::MSCodec97
* mpCodec
; /// Crypto algorithm implementation.
152 class XclImpBiff8StdDecrypter
: public XclImpBiff8Decrypter
155 explicit XclImpBiff8StdDecrypter( std::vector
<sal_uInt8
>&& rSalt
,
156 std::vector
<sal_uInt8
>&& rVerifier
,
157 std::vector
<sal_uInt8
>&& rVerifierHash
)
158 : XclImpBiff8Decrypter(std::move(rSalt
), std::move(rVerifier
), std::move(rVerifierHash
))
164 /** Private copy c'tor for OnClone(). */
165 explicit XclImpBiff8StdDecrypter(const XclImpBiff8StdDecrypter
& rSrc
);
167 /** Implementation of cloning this object. */
168 virtual XclImpBiff8StdDecrypter
* OnClone() const override
;
171 ::msfilter::MSCodec_Std97 maCodec
; /// Crypto algorithm implementation.
174 class XclImpBiff8CryptoAPIDecrypter
: public XclImpBiff8Decrypter
177 explicit XclImpBiff8CryptoAPIDecrypter( std::vector
<sal_uInt8
>&& rSalt
,
178 std::vector
<sal_uInt8
>&& rVerifier
,
179 std::vector
<sal_uInt8
>&& rVerifierHash
)
180 : XclImpBiff8Decrypter(std::move(rSalt
), std::move(rVerifier
), std::move(rVerifierHash
))
186 /** Private copy c'tor for OnClone(). */
187 explicit XclImpBiff8CryptoAPIDecrypter(const XclImpBiff8CryptoAPIDecrypter
& rSrc
);
189 /** Implementation of cloning this object. */
190 virtual XclImpBiff8CryptoAPIDecrypter
* OnClone() const override
;
193 ::msfilter::MSCodec_CryptoAPI maCodec
; /// Crypto algorithm implementation.
198 /** This class represents an Excel stream position.
199 @descr It contains the relevant data for a stream position inside of a record
200 (including CONTINUE records). */
201 class XclImpStreamPos
204 /** Constructs an invalid stream position data object. */
205 explicit XclImpStreamPos();
207 /** Sets the stream position data to the passed values. */
208 void Set( const SvStream
& rStrm
, std::size_t nNextPos
, std::size_t nCurrSize
,
209 sal_uInt16 nRawRecId
, sal_uInt16 nRawRecSize
, sal_uInt16 nRawRecLeft
,
212 /** Writes the contained stream position data to the given variables. */
213 void Get( SvStream
& rStrm
, std::size_t& rnNextPos
, std::size_t& rnCurrSize
,
214 sal_uInt16
& rnRawRecId
, sal_uInt16
& rnRawRecSize
, sal_uInt16
& rnRawRecLeft
,
215 bool& rbValid
) const;
217 /** Returns the stored stream position. */
218 std::size_t GetPos() const { return mnPos
; }
221 std::size_t mnPos
; /// Absolute position of the stream.
222 std::size_t mnNextPos
; /// Absolute position of next record.
223 std::size_t mnCurrSize
; /// Current calculated size of the record.
224 sal_uInt16 mnRawRecId
; /// Current raw record ID (including CONTINUEs).
225 sal_uInt16 mnRawRecSize
; /// Current raw record size (without following CONTINUEs).
226 sal_uInt16 mnRawRecLeft
; /// Bytes left in current raw record (without following CONTINUEs).
227 bool mbValid
; /// Read state: false = record overread.
230 /** This class is used to import record oriented streams.
231 @descr An instance is constructed with an SvStream. The SvStream stream is
232 reset to its start while constructing this stream.
234 To start reading a record call StartNextRecord(). Now it is possible to
235 read all contents of the record using operator>>() or any of the Read***()
236 functions. If some data exceeds the record size limit, the stream looks for
237 a following CONTINUE record and jumps automatically to it. It is NOT
238 allowed that an atomic data type is split into two records (i.e. 4 bytes of
239 a double in one record and the other 4 bytes in a following CONTINUE).
241 Trying to read over the record limits results in a stream error. The
242 IsValid() function indicates that with returning false. From now on it is
243 undefined what data the read functions will return. The error state will be
244 reset, if the record is reset (with the method ResetRecord()) or if the
245 next record is started.
247 To switch off the automatic lookup of CONTINUE records, use ResetRecord()
248 with false parameter. This is useful i.e. on import of Escher objects,
249 where sometimes solely CONTINUE records will occur. The automatic lookup
250 keeps switched off until the method ResetRecord() is called with parameter
251 true. All other settings done on the stream (i.e. alternative CONTINUE
252 record identifier, enabled decryption, NUL substitution character) will be
253 reset to default values, if a new record is started.
255 The import stream supports decrypting the stream data. The contents of a
256 record (not the record header) will be encrypted by Excel if the file has
257 been stored with password protection. The functions SetDecrypter(),
258 EnableDecryption(), and DisableDecryption() control the usage of the
259 decryption algorithms. SetDecrypter() sets a new decryption algorithm and
260 initially enables it. DisableDecryption() may be used to stop the usage of
261 the decryption temporarily (sometimes record contents are never encrypted,
262 i.e. all BOF records or the stream position in BOUNDSHEET). Decryption will
263 be re-enabled automatically, if a new record is started with the function
266 It is possible to store several stream positions inside a record (including
267 its CONTINUE records). The positions are stored on a stack, which can be
268 controlled with the functions PushPosition(), PopPosition() and
269 RejectPosition(). The stack will be cleared whenever a new record is
270 started with the function StartNextRecord().
272 Additionally a single global stream position can be stored which keeps
273 valid during the whole import process (methods StoreGlobalPosition(),
274 SeekGlobalPosition() and DeleteGlobalPosition()). This is the only way to
275 jump back to a previous record (that is a real jump without return).
280 /** Detects the BIFF version of the passed workbook stream. */
281 static XclBiff
DetectBiffVersion( SvStream
& rStrm
);
283 /** Constructs the Excel record import stream using a TOOLS stream object.
284 @param rInStrm The system input stream. Will be set to its start position.
285 Must exist as long as this object exists */
286 explicit XclImpStream(
288 const XclImpRoot
& rRoot
);
292 /** Returns the filter root data. */
293 const XclImpRoot
& GetRoot() const { return mrRoot
; }
295 /** Sets stream pointer to the start of the next record content.
296 @descr Ignores all CONTINUE records of the current record, if automatic
297 CONTINUE usage is switched on.
298 @return false = no record found (end of stream). */
299 bool StartNextRecord();
300 /** Sets stream pointer to the start of the record content for the record
301 at the passed absolute stream position.
302 @return false = no record found (end of stream). */
303 bool StartNextRecord( std::size_t nNextRecPos
);
304 /** Sets stream pointer to begin of record content.
305 @param bContLookup Automatic CONTINUE lookup on/off. In difference
306 to other stream settings, this setting is persistent until next call of
307 this function (because it is wanted to receive the next CONTINUE
309 @param nAltContId Sets an alternative record ID for content
310 continuation. This value is reset automatically when a new record is
311 started with StartNextRecord(). */
312 void ResetRecord( bool bContLookup
,
313 sal_uInt16 nAltContId
= EXC_ID_UNKNOWN
);
314 /** Sets stream pointer before current record and invalidates stream.
315 @descr The next call to StartNextRecord() will start again the current
316 record. This can be used in situations where a loop or a function
317 leaves on a specific record, but the parent context expects to start
318 this record by itself. The stream is invalid as long as the first
319 record has not been started (it is not allowed to call any other stream
323 /** Enables decryption of record contents for the rest of the stream. */
324 void SetDecrypter( XclImpDecrypterRef
const & xDecrypter
);
325 /** Sets decrypter from another stream. */
326 void CopyDecrypterFrom( const XclImpStream
& rStrm
);
327 /** Switches usage of current decryption algorithm on/off.
328 @descr Encryption is re-enabled automatically, if a new record is
329 started using the function StartNextRecord(). */
330 void EnableDecryption( bool bEnable
= true );
331 /** Switches usage of current decryption algorithm off.
332 @descr This is a record-local setting. The function StartNextRecord()
333 always enables decryption. */
334 void DisableDecryption() { EnableDecryption( false ); }
336 /** Pushes current position on user position stack.
337 @descr This stack is emptied when starting a new record with
338 StartNextRecord(). The decryption state (enabled/disabled) is not
339 pushed onto the stack. */
341 /** Seeks to last position from user position stack.
342 @descr This position will be removed from the stack. */
345 /** Stores current position. This position keeps valid in all records. */
346 void StoreGlobalPosition();
347 /** Seeks to the stored global user position. */
348 void SeekGlobalPosition();
350 /** Returns record reading state: false = record overread. */
351 bool IsValid() const { return mbValid
; }
352 /** Returns the current record ID. */
353 sal_uInt16
GetRecId() const { return mnRecId
; }
354 /** Returns the position inside of the whole record content. */
355 std::size_t GetRecPos() const;
356 /** Returns the data size of the whole record without record headers. */
357 std::size_t GetRecSize();
358 /** Returns remaining data size of the whole record without record headers. */
359 std::size_t GetRecLeft();
360 /** Returns the record ID of the following record. */
361 sal_uInt16
GetNextRecId();
363 sal_uInt16
PeekRecId( std::size_t nPos
);
366 sal_uInt8
ReaduInt8();
368 sal_Int16
ReadInt16();
370 sal_uInt16
ReaduInt16();
372 sal_Int32
ReadInt32();
374 sal_uInt32
ReaduInt32();
378 /** Reads nBytes bytes to the existing(!) buffer pData.
379 @return Count of bytes really read. */
380 std::size_t Read( void* pData
, std::size_t nBytes
);
381 /** Copies nBytes bytes to rOutStrm.
382 @return Count of bytes really written. */
383 std::size_t CopyToStream( SvStream
& rOutStrm
, std::size_t nBytes
);
385 /** Copies the entire record to rOutStrm. The current record position keeps unchanged. */
386 void CopyRecordToStream( SvStream
& rOutStrm
);
388 /** Seeks absolute in record content to the specified position.
389 @descr The value 0 means start of record, independent from physical stream position. */
390 void Seek( std::size_t nPos
);
391 /** Seeks forward inside the current record. */
392 void Ignore( std::size_t nBytes
);
394 // *** special string functions *** ---------------------------------------
396 // *** read/ignore unicode strings *** ------------------------------------
397 /* - look for CONTINUE records even if CONTINUE handling disabled
398 (only if inside of a CONTINUE record - for TXO import)
399 - no overread assertions (for Applix wrong string length export bug)
401 structure of an Excel unicode string:
402 (1) 2 byte character count
403 (2) 1 byte flags (16-bit-characters, rich string, far east string)
404 (3) [2 byte rich string format run count]
405 (4) [4 byte far east data size]
407 (6) [4 * (rich string format run count) byte]
408 (7) [(far east data size) byte]
410 ext. header = (3), (4)
414 /** Reads ext. header, detects 8/16 bit mode, sets all ext. info.
415 @return Total size of ext. data. */
416 std::size_t ReadUniStringExtHeader(
417 bool& rb16Bit
, bool& rbRich
, bool& rbFareast
,
418 sal_uInt16
& rnFormatRuns
, sal_uInt32
& rnExtInf
, sal_uInt8 nFlags
);
419 /** Seeks to begin of character array, detects 8/16 bit mode.
420 @return Total size of ext. data. */
421 std::size_t ReadUniStringExtHeader( bool& rb16Bit
, sal_uInt8 nFlags
);
423 /** Sets a replacement character for NUL characters.
424 @descr NUL characters must be replaced, because Tools strings cannot
425 handle them. The substitution character is reset to '?' automatically,
426 if a new record is started using the function StartNextRecord().
427 @param cNulSubst The character to use for NUL replacement. It is
428 possible to specify NUL here. in this case strings are terminated when
429 the first NUL occurs during string import. */
430 void SetNulSubstChar( sal_Unicode cNulSubst
= '?' ) { mcNulSubst
= cNulSubst
; }
432 /** Reads nChars characters and returns the string. */
433 OUString
ReadRawUniString( sal_uInt16 nChars
, bool b16Bit
);
434 /** Reads ext. header, nChar characters, ext. data and returns the string. */
435 OUString
ReadUniString( sal_uInt16 nChars
, sal_uInt8 nFlags
);
436 /** Reads 8 bit flags, ext. header, nChar characters, ext. data and returns the string. */
437 OUString
ReadUniString( sal_uInt16 nChars
);
438 /** Reads 16 bit character count, 8 bit flags, ext. header, character array,
439 ext. data and returns the string. */
440 OUString
ReadUniString();
442 /** Ignores nChars characters. */
443 void IgnoreRawUniString( sal_uInt16 nChars
, bool b16Bit
);
444 /** Ignores ext. header, nChar characters, ext. data. */
445 void IgnoreUniString( sal_uInt16 nChars
, sal_uInt8 nFlags
);
446 /** Ignores 8 bit flags, ext. header, nChar characters, ext. data. */
447 void IgnoreUniString( sal_uInt16 nChars
);
449 // *** read/ignore 8-bit-strings, store in String *** ---------------------
451 /** Reads nChar byte characters and returns the string. */
452 OUString
ReadRawByteString( sal_uInt16 nChars
);
453 /** Reads 8/16 bit string length, character array and returns the string. */
454 OUString
ReadByteString( bool b16BitLen
);
456 // *** SvStream functions *** ---------------------------------------------
458 /** Returns the absolute stream position. */
459 std::size_t GetSvStreamPos() const { return mrStrm
.Tell(); }
460 /** Returns the stream size. */
461 std::size_t GetSvStreamSize() const { return mnStreamSize
; }
463 /** Stores current stream position into rPos. */
464 void StorePosition( XclImpStreamPos
& rPos
);
465 /** Restores stream position contained in rPos. */
466 void RestorePosition( const XclImpStreamPos
& rPos
);
468 /** Set an SVSTREAM_..._ERROR. */
469 void SetSvStreamError( const ErrCode
& rErrCode
)
470 { mrStrm
.SetError( rErrCode
); }
473 /** Seeks to next raw record header and reads record ID and size.
474 @descr This is a "raw" function, means that stream members are
475 inconsistent after return. Does only change mnRawRecId, mnRawRecSize,
476 and the base stream position, but no other members.
477 @return false = No record header found (end of stream). */
478 bool ReadNextRawRecHeader();
480 /** Initializes the decrypter to read a new record. */
481 void SetupDecrypter();
482 /** Initializes all members after base stream has been sought to new raw record. */
483 void SetupRawRecord();
484 /** Initializes all members after base stream has been sought to new record. */
487 /** Returns true, if the passed ID is real or alternative continuation record ID. */
488 bool IsContinueId( sal_uInt16 nRecId
) const;
490 /** Goes to start of the next CONTINUE record.
491 @descr Stream must be located at the end of a raw record, and handling
492 of CONTINUE records must be enabled.
493 @return Copy of mbValid. */
494 bool JumpToNextContinue();
495 /** Goes to start of the next CONTINUE record while reading strings.
496 @descr Stream must be located at the end of a raw record. If reading
497 has been started in a CONTINUE record, jumps to an existing following
498 CONTINUE record, even if handling of CONTINUE records is disabled (This
499 is a special handling for TXO string data). Reads additional Unicode
500 flag byte at start of the new raw record and sets or resets rb16Bit.
501 @return Copy of mbValid. */
502 bool JumpToNextStringContinue( bool& rb16Bit
);
504 /** Ensures that reading nBytes bytes is possible with next stream access.
505 @descr Stream must be located at the end of a raw record, and handling
506 of CONTINUE records must be enabled.
507 @return Copy of mbValid. */
508 bool EnsureRawReadSize( sal_uInt16 nBytes
);
509 /** Returns the maximum size of raw data possible to read in one block. */
510 sal_uInt16
GetMaxRawReadSize( std::size_t nBytes
) const;
512 /** Reads and decrypts nBytes bytes to the existing(!) buffer pData.
513 @return Count of bytes really read. */
514 sal_uInt16
ReadRawData( void* pData
, sal_uInt16 nBytes
);
517 SvStream
& mrStrm
; /// Reference to the system input stream.
518 const XclImpRoot
& mrRoot
; /// Filter root data.
520 XclImpDecrypterRef mxDecrypter
; /// Provides methods to decrypt data.
522 XclImpStreamPos maFirstRec
; /// Start position of current record.
523 std::vector
< XclImpStreamPos
>
524 maPosStack
; /// Stack for record positions.
526 XclImpStreamPos maGlobPos
; /// User defined position elsewhere in stream.
527 sal_uInt16 mnGlobRecId
; /// Record ID for user defined position.
528 bool mbGlobValidRec
; /// Was user position a valid record?
529 bool mbHasGlobPos
; /// Is user position defined?
531 std::size_t mnStreamSize
; /// Size of system stream.
532 std::size_t mnNextRecPos
; /// Start of next record header.
533 std::size_t mnCurrRecSize
; /// Helper for record position.
534 std::size_t mnComplRecSize
; /// Size of complete record data (with CONTINUEs).
535 bool mbHasComplRec
; /// true = mnComplRecSize is valid.
537 sal_uInt16 mnRecId
; /// Current record ID (not the CONTINUE ID).
538 sal_uInt16 mnAltContId
; /// Alternative record ID for content continuation.
540 sal_uInt16 mnRawRecId
; /// Current raw record ID (including CONTINUEs).
541 sal_uInt16 mnRawRecSize
; /// Current raw record size (without following CONTINUEs).
542 sal_uInt16 mnRawRecLeft
; /// Bytes left in current raw record (without following CONTINUEs).
544 sal_Unicode mcNulSubst
; /// Replacement for NUL characters.
546 bool mbCont
; /// Automatic CONTINUE lookup on/off.
547 bool mbUseDecr
; /// Usage of decryption.
548 bool mbValidRec
; /// false = No more records to read.
549 bool mbValid
; /// false = Record overread.
552 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */