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