xref: /AOO42X/main/filter/source/msfilter/msvbasic.cxx (revision b1c5455db1639c48e26c568e4fa7ee78ca5d60ee)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_filter.hxx"
26 
27 
28 #include <string.h>     // memset(), ...
29 #ifndef UNX
30 #include <io.h>         // access()
31 #endif
32 #include <osl/endian.h>
33 #include <rtl/tencinfo.h>   //rtl_getTextEncodingFromWindowsCodePage
34 #include "msvbasic.hxx"
35 
36 #include <com/sun/star/script/ModuleType.hpp>
37 
38 using namespace ::com::sun::star::script;
39 
40 /*
41 A few urls which may in the future be of some use
42 http://www.virusbtn.com/vb2000/Programme/papers/bontchev.pdf
43 */
44 
45 /* class VBA_Impl:
46  * The VBA class provides a set of methods to handle Visual Basic For
47  * Applications streams, the constructor is given the root ole2 stream
48  * of the document, Open reads the VBA project file and figures out
49  * the number of VBA streams, and the offset of the data within them.
50  * Decompress decompresses a particular numbered stream, NoStreams returns
51  * this number, and StreamName can give you the streams name. Decompress
52  * will call Output when it has a 4096 byte collection of data to output,
53  * and also with the final remainder of data if there is still some left
54  * at the end of compression. Output is virtual to allow custom handling
55  * of each chunk of decompressed data. So inherit from this to do something
56  * useful with the data.
57  *
58  * cmc
59  * */
60 const int MINVBASTRING = 6;
61 
VBA_Impl(SvStorage & rIn,bool bCmmntd)62 VBA_Impl::VBA_Impl(SvStorage &rIn, bool bCmmntd)
63     : aVBAStrings(0),
64     sComment(RTL_CONSTASCII_USTRINGPARAM("Rem ")),
65     xStor(&rIn), pOffsets(0), nOffsets(0), meCharSet(RTL_TEXTENCODING_MS_1252),
66     bCommented(bCmmntd), mbMac(false), nLines(0)
67 {
68 }
69 
~VBA_Impl()70 VBA_Impl::~VBA_Impl()
71 {
72     delete [] pOffsets;
73     for (sal_uLong i=0;i<aVBAStrings.GetSize();++i)
74         delete aVBAStrings.Get(i);
75 }
76 
ReadPString(SvStorageStreamRef & xVBAProject,bool bIsUnicode)77 sal_uInt8 VBA_Impl::ReadPString(SvStorageStreamRef &xVBAProject,
78     bool bIsUnicode)
79 {
80     sal_uInt16 nIdLen, nOut16;
81     sal_uInt8 nType = 0, nOut8;
82     String sReference;
83 
84     *xVBAProject >> nIdLen;
85 
86     if (nIdLen < MINVBASTRING) //Error recovery
87         xVBAProject->SeekRel(-2); //undo 2 byte len
88     else
89     {
90         for(sal_uInt16 i=0; i < nIdLen / (bIsUnicode ? 2 : 1); i++)
91         {
92             if (bIsUnicode)
93                 *xVBAProject >> nOut16;
94             else
95             {
96                 *xVBAProject >> nOut8;
97                 nOut16 = nOut8;
98             }
99             sReference += nOut16;
100             if (i==2)
101             {
102                 if ((nOut16 == 'G') || (nOut16 == 'H') || (nOut16 == 'C') ||
103                     nOut16 == 'D')
104                 {
105                     nType = static_cast<sal_uInt8>(nOut16);
106                 }
107                 if (nType == 0)
108                 {
109                     //Error recovery, 2byte len + 3 characters of used type
110                     xVBAProject->SeekRel(-(2 + 3 * (bIsUnicode ? 2 : 1)));
111                     break;
112                 }
113             }
114         }
115         maReferences.push_back(sReference);
116     }
117     return nType;
118 }
119 
Output(int nLen,const sal_uInt8 * pData)120 void VBA_Impl::Output( int nLen, const sal_uInt8*pData )
121 {
122     /*
123     Each StarBasic module is tragically limited to the maximum len of a
124     string and WordBasic is not, so each overlarge module must be split
125     */
126     String sTemp((const sal_Char *)pData, (xub_StrLen)nLen,
127         meCharSet);
128     int nTmp = sTemp.GetTokenCount('\x0D');
129     int nIndex = aVBAStrings.GetSize()-1;
130     if (aVBAStrings.Get(nIndex)->Len() +
131         nLen + ((nLines+nTmp) * sComment.Len()) >= STRING_MAXLEN)
132     {
133         //DBG_ASSERT(0,"New Module String\n");
134         //we are too large for our boots, break out into another
135         //string
136         nLines=0;
137         nIndex++;
138         aVBAStrings.SetSize(nIndex+1);
139         aVBAStrings.Put(nIndex,new String);
140     }
141     *(aVBAStrings.Get(nIndex)) += sTemp;
142     nLines+=nTmp;
143 }
144 
145 
ReadVBAProject(const SvStorageRef & rxVBAStorage)146 int VBA_Impl::ReadVBAProject(const SvStorageRef &rxVBAStorage)
147 {
148     SvStorageStreamRef xVBAProject;
149     xVBAProject = rxVBAStorage->OpenSotStream(
150                     String( RTL_CONSTASCII_USTRINGPARAM( "_VBA_PROJECT" ) ),
151                     STREAM_STD_READ | STREAM_NOCREATE );
152 
153     if( !xVBAProject.Is() || SVSTREAM_OK != xVBAProject->GetError() )
154     {
155         DBG_WARNING("Not able to find vba project, cannot find macros");
156         return 0;
157     }
158 
159     static const sal_uInt8 aKnownId[] = {0xCC, 0x61};
160     sal_uInt8 aId[2];
161     xVBAProject->Read( aId, sizeof(aId) );
162     if (memcmp( aId, aKnownId, sizeof(aId)))
163     {
164         DBG_WARNING("unrecognized VBA macro project type");
165         return 0;
166     }
167 
168     static const sal_uInt8 aOffice2007LE[]   = { 0x88, 0x00, 0x00, 0x01, 0x00, 0xFF };
169     static const sal_uInt8 aOffice2003LE_2[] = { 0x79, 0x00, 0x00, 0x01, 0x00, 0xFF };
170     static const sal_uInt8 aOffice2003LE[]   = { 0x76, 0x00, 0x00, 0x01, 0x00, 0xFF };
171     static const sal_uInt8 aOfficeXPLE[]     = { 0x73, 0x00, 0x00, 0x01, 0x00, 0xFF };
172     static const sal_uInt8 aOfficeXPBE[]     = { 0x63, 0x00, 0x00, 0x0E, 0x00, 0xFF };
173     static const sal_uInt8 aOffice2000LE[]   = { 0x6D, 0x00, 0x00, 0x01, 0x00, 0xFF };
174     static const sal_uInt8 aOffice98BE[]     = { 0x60, 0x00, 0x00, 0x0E, 0x00, 0xFF };
175     static const sal_uInt8 aOffice97LE[]     = { 0x5E, 0x00, 0x00, 0x01, 0x00, 0xFF };
176 
177     sal_uInt8 aProduct[6];
178     xVBAProject->Read( aProduct, sizeof(aProduct) );
179 
180     bool bIsUnicode;
181     if (!(memcmp(aProduct, aOffice2007LE,   sizeof(aProduct))) ||
182         !(memcmp(aProduct, aOffice2003LE,   sizeof(aProduct))) ||
183         !(memcmp(aProduct, aOffice2003LE_2, sizeof(aProduct))) ||
184         !(memcmp(aProduct, aOfficeXPLE,     sizeof(aProduct))) ||
185         !(memcmp(aProduct, aOffice2000LE,   sizeof(aProduct))) ||
186         !(memcmp(aProduct, aOffice97LE,     sizeof(aProduct))) )
187     {
188         xVBAProject->SetNumberFormatInt( NUMBERFORMAT_INT_LITTLEENDIAN );
189         bIsUnicode = true;
190     }
191     else if (!(memcmp(aProduct, aOfficeXPBE, sizeof(aProduct))) ||
192              !(memcmp(aProduct, aOffice98BE, sizeof(aProduct))) )
193     {
194         xVBAProject->SetNumberFormatInt( NUMBERFORMAT_INT_BIGENDIAN );
195         mbMac = true;
196         bIsUnicode = false;
197     }
198     else
199     {
200         switch (aProduct[3])
201         {
202             case 0x1:
203                 xVBAProject->SetNumberFormatInt(NUMBERFORMAT_INT_LITTLEENDIAN);
204                 bIsUnicode = true;
205                 DBG_ASSERT(sal_False, "unrecognized VBA macro version, report to cmc. Guessing at unicode little endian");
206                 break;
207             case 0xe:
208                 xVBAProject->SetNumberFormatInt(NUMBERFORMAT_INT_BIGENDIAN);
209                 mbMac = true;
210                 bIsUnicode = false;
211                 DBG_ASSERT(sal_False, "unrecognized VBA macro version, report to cmc. Guessing at 8bit big endian");
212                 break;
213             default:
214                 DBG_ASSERT(sal_False, "totally unrecognized VBA macro version, report to cmc");
215                 return 0;
216         }
217     }
218 
219     sal_uInt32 nLidA;  //Language identifiers
220     sal_uInt32 nLidB;
221     sal_uInt16 nCharSet;
222     sal_uInt16 nLenA;
223     sal_uInt32 nUnknownB;
224     sal_uInt32 nUnknownC;
225     sal_uInt16 nLenB;
226     sal_uInt16 nLenC;
227     sal_uInt16 nLenD;
228 
229     *xVBAProject >> nLidA >> nLidB >> nCharSet >> nLenA >> nUnknownB;
230     *xVBAProject >> nUnknownC >> nLenB >> nLenC >> nLenD;
231 
232     meCharSet = rtl_getTextEncodingFromWindowsCodePage(nCharSet);
233 
234     DBG_ASSERT(meCharSet != RTL_TEXTENCODING_DONTKNOW,
235                 "don't know what vba charset to use");
236     if (meCharSet == RTL_TEXTENCODING_DONTKNOW)
237         meCharSet = RTL_TEXTENCODING_MS_1252;
238 
239     if (nLenD != 0x02)
240     {
241         DBG_WARNING("Warning VBA number is different, please report");
242         return 0;
243     }
244 
245     /*
246     A sequence of string that are prepended with a len and then begin with G
247     or H, there are also those that begin with C or D. If a string begins with
248     C or D, it is really two strings, one right after the other.  Each string
249     then has a 12 bytes suffix
250 
251     Recognizing the end of the sequence is done by finding a str len of < 6
252     which does not appear to be the beginning of an object id. Admittedly this
253     isn't a great test, but nothing in the header appears to count the number
254     of strings, and nothing else seems to match. So it'll have to do, its
255     protected by a number of secondry tests to prove its a valid string, and
256     everything gives up if this isn't proven.
257     */
258     bool bPredictsTrailingTwenty = false;
259     while (1)
260     {
261         sal_uInt8 nType = ReadPString(xVBAProject,bIsUnicode);
262         //Type C and D seem to come as pairs, so skip the following one
263         if (nType == 'C' || nType == 'D')
264         {
265             nType = ReadPString(xVBAProject,bIsUnicode);
266             DBG_ASSERT( nType == 'C' || nType == 'D',
267                 "VBA: This must be a 'C' or 'D' string!" );
268             if (nType != 'C' && nType != 'D')
269                 return 0;
270         }
271         if (!nType)
272             break;
273         xVBAProject->SeekRel(10);
274         sal_uInt16 nPredictsTrailingTwenty;
275         *xVBAProject >> nPredictsTrailingTwenty;
276         if (nPredictsTrailingTwenty)
277             bPredictsTrailingTwenty = true;
278         if (bPredictsTrailingTwenty)
279         {
280             sal_uInt16 nTestIsNotString;
281             *xVBAProject >> nTestIsNotString;
282             if (nTestIsNotString < MINVBASTRING)
283             {
284                 DBG_ASSERT(nTestIsNotString <= 1,
285                     "Haven't seen a len like this in VBA, report to CMC");
286                 xVBAProject->SeekRel(18);
287                 bPredictsTrailingTwenty = false;
288             }
289             else
290                 xVBAProject->SeekRel(-2);
291         }
292     }
293 
294     sal_Int16 nInt16s;
295     *xVBAProject >> nInt16s;
296     DBG_ASSERT( nInt16s >= 0, "VBA: Bad no of records in VBA Project, panic!" );
297     if (!nInt16s)
298         return 0;
299 
300     xVBAProject->SeekRel(2*nInt16s);
301 
302     sal_Int16 nInt32s;
303     *xVBAProject >> nInt32s;
304     DBG_ASSERT( nInt32s >= 0, "VBA: Bad no of records in VBA Project, panic!" );
305     if (!nInt32s)
306         return 0;
307     xVBAProject->SeekRel(4*nInt32s);
308 
309     xVBAProject->SeekRel(2);
310     for(int k=0;k<3;k++)
311     {
312         sal_uInt16 nLen;
313         *xVBAProject >> nLen;
314         if (nLen != 0xFFFF)
315             xVBAProject->SeekRel(nLen);
316     }
317     xVBAProject->SeekRel(100); //Seems fixed len
318 
319     *xVBAProject >> nOffsets;
320     DBG_ASSERT( nOffsets != 0xFFFF, "VBA: Bad nOffsets, panic!!" );
321     if ((nOffsets == 0xFFFF) || (nOffsets == 0))
322         return 0;
323     pOffsets = new VBAOffset_Impl[ nOffsets ];
324 
325     int i, j;
326     for( i=0; i < nOffsets; i++)
327     {
328         sal_uInt16 nLen;
329         *xVBAProject >> nLen;
330 
331         if (bIsUnicode)
332         {
333             sal_Unicode* pBuf = pOffsets[i].sName.AllocBuffer( nLen / 2 );
334             xVBAProject->Read( (sal_Char*)pBuf, nLen  );
335 
336 #ifdef OSL_BIGENDIAN
337             for( j = 0; j < nLen / 2; ++j, ++pBuf )
338                 *pBuf = SWAPSHORT( *pBuf );
339 #endif // ifdef OSL_BIGENDIAN
340         }
341         else
342         {
343             ByteString aByteStr;
344             sal_Char*  pByteData = aByteStr.AllocBuffer( nLen );
345             sal_Size nWasRead = xVBAProject->Read( pByteData, nLen );
346             if( nWasRead != nLen )
347                 aByteStr.ReleaseBufferAccess();
348             pOffsets[i].sName += String( aByteStr, meCharSet);
349         }
350 
351         *xVBAProject >> nLen;
352         xVBAProject->SeekRel( nLen );
353 
354         //begin section, another problem area
355         *xVBAProject >> nLen;
356         if ( nLen == 0xFFFF)
357         {
358             xVBAProject->SeekRel(2);
359             *xVBAProject >> nLen;
360             xVBAProject->SeekRel( nLen );
361         }
362         else
363             xVBAProject->SeekRel( nLen+2 );
364 
365         *xVBAProject >> nLen;
366         DBG_ASSERT( nLen == 0xFFFF, "VBA: Bad field in VBA Project, panic!!" );
367         if ( nLen != 0xFFFF)
368             return 0;
369 
370         xVBAProject->SeekRel(6);
371         sal_uInt16 nOctects;
372         *xVBAProject >> nOctects;
373         for(j=0;j<nOctects;j++)
374             xVBAProject->SeekRel(8);
375 
376         xVBAProject->SeekRel(5);
377         //end section
378 
379         *xVBAProject >> pOffsets[i].nOffset;
380         xVBAProject->SeekRel(2);
381     }
382 
383     return nOffsets;
384 }
385 
386 
387 /* #117718# For a given Module name return its type,
388  * Form, Class, Document, Normal or Unknown
389  *
390 */
391 
GetModuleType(const UniString & rModuleName)392 ModType VBA_Impl::GetModuleType( const UniString& rModuleName )
393 {
394     ModuleTypeHash::iterator iter = mhModHash.find( rModuleName );
395     ModuleTypeHash::iterator iterEnd = mhModHash.end();
396     if ( iter != iterEnd )
397     {
398         return iter->second;
399     }
400     return ModuleType::UNKNOWN;
401 }
402 
Open(const String & rToplevel,const String & rSublevel)403 bool VBA_Impl::Open( const String &rToplevel, const String &rSublevel )
404 {
405     /* beginning test for vba stuff */
406     bool bRet = false;
407     SvStorageRef xMacros= xStor->OpenSotStorage( rToplevel,
408                                     STREAM_READWRITE | STREAM_NOCREATE |
409                                     STREAM_SHARE_DENYALL );
410     if( !xMacros.Is() || SVSTREAM_OK != xMacros->GetError() )
411     {
412         DBG_WARNING("No Macros Storage");
413     }
414     else
415     {
416         xVBA = xMacros->OpenSotStorage( rSublevel,
417                                     STREAM_READWRITE | STREAM_NOCREATE |
418                                     STREAM_SHARE_DENYALL );
419         if( !xVBA.Is() || SVSTREAM_OK != xVBA->GetError() )
420         {
421             DBG_WARNING("No Visual Basic in Storage");
422         }
423         else
424         {
425             if (ReadVBAProject(xVBA))
426                 bRet = true;
427         }
428         /* #117718#
429          * Information regarding the type of module is contained in the
430          * "PROJECT" stream, this stream consists of a number of ascii lines
431          * entries are of the form Key=Value, the ones that we are interested
432          * in have the keys; Class, BaseClass & Module indicating the module
433          * ( value ) is either a Class Module, Form Module or a plain VB Module.        */
434         SvStorageStreamRef xProject = xMacros->OpenSotStream(
435             String( RTL_CONSTASCII_USTRINGPARAM( "PROJECT" ) ) );
436         SvStorageStream* pStp = xProject;
437         UniString tmp;
438         static const String sThisDoc(   RTL_CONSTASCII_USTRINGPARAM( "ThisDocument" ) );
439         static const String sModule(    RTL_CONSTASCII_USTRINGPARAM( "Module" ) );
440         static const String sClass(     RTL_CONSTASCII_USTRINGPARAM( "Class" ) );
441         static const String sBaseClass( RTL_CONSTASCII_USTRINGPARAM( "BaseClass" ) );
442         static const String sDocument(  RTL_CONSTASCII_USTRINGPARAM( "Document" ) );
443         mhModHash[ sThisDoc ] = ModuleType::CLASS;
444         while ( pStp->ReadByteStringLine( tmp, meCharSet ) )
445         {
446             xub_StrLen index = tmp.Search( '=' );
447             if ( index != STRING_NOTFOUND )
448             {
449                 String key = tmp.Copy( 0, index  );
450                 String value = tmp.Copy( index + 1 );
451                 if ( key == sClass )
452                 {
453                     mhModHash[ value ] = ModuleType::CLASS;
454                     OSL_TRACE("Module %s is of type Class",
455                         ::rtl::OUStringToOString( value ,
456                             RTL_TEXTENCODING_ASCII_US ).pData->buffer );
457                 }
458                 else if ( key == sBaseClass )
459                 {
460                     mhModHash[ value ] = ModuleType::FORM;
461                     OSL_TRACE("Module %s is of type Form",
462                         ::rtl::OUStringToOString( value ,
463                             RTL_TEXTENCODING_ASCII_US ).pData->buffer );
464                 }
465                 else if ( key == sDocument )
466                 {
467                     /*  #i37965# DR 2004-12-03: add "Document", used i.e.
468                         in Excel for macros attached to sheet or document. */
469 
470                     // value is of form <name>/&H<identifier>, strip the identifier
471                     value.Erase( value.Search( '/' ) );
472 
473                     mhModHash[ value ] = ModuleType::DOCUMENT;
474                     OSL_TRACE("Module %s is of type Document VBA",
475                         ::rtl::OUStringToOString( value ,
476                             RTL_TEXTENCODING_ASCII_US ).pData->buffer );
477                 }
478                 else if ( key == sModule )
479                 {
480                     mhModHash[ value ] = ModuleType::NORMAL;
481                     OSL_TRACE("Module %s is of type Normal VBA",
482                         ::rtl::OUStringToOString( value ,
483                             RTL_TEXTENCODING_ASCII_US ).pData->buffer );
484                 }
485             }
486         }
487     }
488     /* end test for vba stuff */
489     return bRet;
490 }
491 
Decompress(sal_uInt16 nIndex,int * pOverflow)492 const StringArray &VBA_Impl::Decompress(sal_uInt16 nIndex, int *pOverflow)
493 {
494     DBG_ASSERT( nIndex < nOffsets, "Index out of range" );
495     SvStorageStreamRef xVBAStream;
496     aVBAStrings.SetSize(1);
497     aVBAStrings.Put(0,new String);
498 
499     xVBAStream = xVBA->OpenSotStream( pOffsets[nIndex].sName,
500                         STREAM_STD_READ | STREAM_NOCREATE );
501     if (pOverflow)
502         *pOverflow=0;
503 
504     if( !xVBAStream.Is() || SVSTREAM_OK != xVBAStream->GetError() )
505     {
506         DBG_WARNING("Not able to open vb module ");
507     }
508     else
509     {
510         xVBAStream->SetNumberFormatInt( NUMBERFORMAT_INT_LITTLEENDIAN );
511         DecompressVBA( nIndex, xVBAStream );
512         /*
513          * if len was too big for a single string set that variable ?
514          *  if ((len > XX) && (pOverflow))
515                 *pOverflow=1;
516          */
517         if (bCommented)
518         {
519             String sTempStringa;
520             if (mbMac)
521                 sTempStringa = String( RTL_CONSTASCII_USTRINGPARAM( "\x0D" ) );
522             else
523                 sTempStringa = String( RTL_CONSTASCII_USTRINGPARAM( "\x0D\x0A" ) );
524             String sTempStringb(sTempStringa);
525             sTempStringb+=sComment;
526             for(sal_uLong i=0;i<aVBAStrings.GetSize();i++)
527             {
528                 aVBAStrings.Get(i)->SearchAndReplaceAll(
529                     sTempStringa,sTempStringb);
530                 aVBAStrings.Get(i)->Insert(sComment,0);
531             }
532         }
533     }
534     return aVBAStrings;
535 }
536 
537 
DecompressVBA(int nIndex,SvStorageStreamRef & xVBAStream)538 int VBA_Impl::DecompressVBA( int nIndex, SvStorageStreamRef &xVBAStream )
539 {
540     sal_uInt8 nLeadbyte;
541     sal_uInt16 nToken;
542     unsigned int nPos = 0;
543     int nLen, nDistance, nShift, nClean=1;
544 
545     xVBAStream->Seek( pOffsets[ nIndex ].nOffset + 3 );
546 
547     while(xVBAStream->Read(&nLeadbyte,1))
548     {
549         for(int nPosition=0x01;nPosition < 0x100;nPosition=nPosition<<1)
550         {
551             //we see if the leadbyte has flagged this location as a dataunit
552             //which is actually a token which must be looked up in the history
553             if (nLeadbyte & nPosition)
554             {
555                 *xVBAStream >> nToken;
556 
557                 if (nClean == 0)
558                     nClean=1;
559 
560                 //For some reason the division of the token into the length
561                 //field of the data to be inserted, and the distance back into
562                 //the history differs depending on how full the history is
563                 int nPos2 = nPos % nWINDOWLEN;
564                 if (nPos2 <= 0x10)
565                     nShift = 12;
566                 else if (nPos2 <= 0x20)
567                     nShift = 11;
568                 else if (nPos2 <= 0x40)
569                     nShift = 10;
570                 else if (nPos2 <= 0x80)
571                     nShift = 9;
572                 else if (nPos2 <= 0x100)
573                     nShift = 8;
574                 else if (nPos2 <= 0x200)
575                     nShift = 7;
576                 else if (nPos2 <= 0x400)
577                     nShift = 6;
578                 else if (nPos2 <= 0x800)
579                     nShift = 5;
580                 else
581                     nShift = 4;
582 
583                 int i;
584                 nLen=0;
585                 for(i=0;i<nShift;i++)
586                     nLen |= nToken & (1<<i);
587 
588                 nLen += 3;
589 
590                 nDistance = nToken >> nShift;
591 
592                 //read the len of data from the history, wrapping around the
593                 //nWINDOWLEN boundary if necessary data read from the history
594                 //is also copied into the recent part of the history as well.
595                 for (i = 0; i < nLen; i++)
596                 {
597                     unsigned char c;
598                     c = aHistory[(nPos-nDistance-1) % nWINDOWLEN];
599                     aHistory[nPos % nWINDOWLEN] = c;
600                     nPos++;
601                 }
602             }
603             else
604             {
605                 // special boundary case code, not guarantueed to be correct
606                 // seems to work though, there is something wrong with the
607                 // compression scheme (or maybe a feature) where when the data
608                 // ends on a nWINDOWLEN boundary and the excess bytes in the 8
609                 // dataunit list are discarded, and not interpreted as tokens
610                 // or normal data.
611                 if ((nPos != 0) && ((nPos % nWINDOWLEN) == 0) && (nClean))
612                 {
613                     xVBAStream->SeekRel(2);
614                     nClean=0;
615                     Output(nWINDOWLEN, aHistory);
616                     break;
617                 }
618                 //This is the normal case for when the data unit is not a
619                 //token to be looked up, but instead some normal data which
620                 //can be output, and placed in the history.
621                 if (xVBAStream->Read(&aHistory[nPos % nWINDOWLEN],1))
622                     nPos++;
623 
624                 if (nClean == 0)
625                     nClean=1;
626             }
627         }
628     }
629     if (nPos % nWINDOWLEN)
630         Output(nPos % nWINDOWLEN,aHistory);
631     return(nPos);
632 }
633 
634 /* vi:set tabstop=4 shiftwidth=4 expandtab: */
635