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