diff --git a/irc/TechBot/CHMLibrary/AssemblyInfo.cs b/irc/TechBot/CHMLibrary/AssemblyInfo.cs new file mode 100644 index 00000000000..dbacda112e8 --- /dev/null +++ b/irc/TechBot/CHMLibrary/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following +// attributes. +// +// change them to the information which is associated with the assembly +// you compile. + +[assembly: AssemblyTitle("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has following format : +// +// Major.Minor.Build.Revision +// +// You can specify all values by your own or you can build default build and revision +// numbers with the '*' character (the default): + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes specify the key for the sign of your assembly. See the +// .NET Framework documentation for more information about signing. +// This is not required, if you don't want signing let these attributes like they're. +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] diff --git a/irc/TechBot/CHMLibrary/CHMDecoding/BinaryReaderHelp.cs b/irc/TechBot/CHMLibrary/CHMDecoding/BinaryReaderHelp.cs new file mode 100644 index 00000000000..64c9668eb30 --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMDecoding/BinaryReaderHelp.cs @@ -0,0 +1,274 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; +using System.Globalization; + +namespace HtmlHelp.ChmDecoding +{ + /// + /// The class BinaryReaderHelp implements static helper methods for extracting binary data + /// from a binary reader object. + /// + internal class BinaryReaderHelp + { + /// + /// Internal helper method to extract null-terminated strings from a binary reader + /// + /// reference to the binary reader + /// offset in the stream + /// true if the offset value should be used + /// encoder used for text encoding + /// An extracted string value + internal static string ExtractString(ref BinaryReader binReader, int offset, bool noOffset, Encoding encoder) + { + string strReturn = ""; + + if(encoder == null) + encoder = Encoding.ASCII; + + ArrayList nameBytes = new ArrayList(); + byte curByte; + + if(!noOffset) + binReader.BaseStream.Seek(offset, SeekOrigin.Begin); + + if(binReader.BaseStream.Position >= binReader.BaseStream.Length) + return ""; + + curByte = binReader.ReadByte(); + while( (curByte != (byte)0) && (binReader.BaseStream.Position < binReader.BaseStream.Length) ) + { + nameBytes.Add( curByte ); + curByte = binReader.ReadByte(); + } + + byte[] name = (byte[]) (nameBytes.ToArray(System.Type.GetType("System.Byte"))); + strReturn = encoder.GetString(name,0,name.Length); + + return strReturn; + } + + /// + /// Internal helper method to extract a string with a specific length from the binary reader + /// + /// reference to the binary reader + /// length of the string (number of bytes) + /// offset in the stream + /// true if the offset value should be used + /// encoder used for text encoding + /// An extracted string value + internal static string ExtractString(ref BinaryReader binReader, int length, int offset, bool noOffset, Encoding encoder) + { + string strReturn = ""; + + if(length == 0) + return ""; + + if(encoder == null) + encoder = Encoding.ASCII; + + ArrayList nameBytes = new ArrayList(); + byte curByte; + + if(!noOffset) + binReader.BaseStream.Seek(offset, SeekOrigin.Begin); + + if(binReader.BaseStream.Position >= binReader.BaseStream.Length) + return ""; + + curByte = binReader.ReadByte(); + while( (curByte != (byte)0) && (nameBytes.Count < length) && (binReader.BaseStream.Position < binReader.BaseStream.Length) ) + { + nameBytes.Add( curByte ); + + if(nameBytes.Count < length) + curByte = binReader.ReadByte(); + } + + byte[] name = (byte[]) (nameBytes.ToArray(System.Type.GetType("System.Byte"))); + strReturn = encoder.GetString(name,0,name.Length); + + return strReturn; + } + + /// + /// Internal helper method to extract a string with a specific length from the binary reader + /// + /// reference to the binary reader + /// reference to a bool vairable which will receive true if the + /// string terminator \0 was found. false indicates that the end of the stream was reached. + /// offset in the stream + /// true if the offset value should be used + /// encoder used for text encoding + /// An extracted string value + internal static string ExtractString(ref BinaryReader binReader, ref bool bFoundTerminator, int offset, bool noOffset, Encoding encoder) + { + string strReturn = ""; + + ArrayList nameBytes = new ArrayList(); + byte curByte; + + if(encoder == null) + encoder = Encoding.ASCII; + + if(!noOffset) + binReader.BaseStream.Seek(offset, SeekOrigin.Begin); + + if(binReader.BaseStream.Position >= binReader.BaseStream.Length) + return ""; + + curByte = binReader.ReadByte(); + while( (curByte != (byte)0) && (binReader.BaseStream.Position < binReader.BaseStream.Length) ) + { + nameBytes.Add( curByte ); + curByte = binReader.ReadByte(); + + if( curByte == (byte)0 ) + { + bFoundTerminator = true; + } + } + + byte[] name = (byte[]) (nameBytes.ToArray(System.Type.GetType("System.Byte"))); + strReturn = encoder.GetString(name,0,name.Length); + + return strReturn; + } + + /// + /// Internal helper method to extract a null-terminated UTF-16/UCS-2 strings from a binary reader + /// + /// reference to the binary reader + /// offset in the stream + /// true if the offset value should be used + /// encoder used for text encoding + /// An extracted string value + internal static string ExtractUTF16String(ref BinaryReader binReader, int offset, bool noOffset, Encoding encoder) + { + string strReturn = ""; + + ArrayList nameBytes = new ArrayList(); + byte curByte; + int lastByte=-1; + + if(!noOffset) + binReader.BaseStream.Seek(offset, SeekOrigin.Begin); + + if(binReader.BaseStream.Position >= binReader.BaseStream.Length) + return ""; + + if(encoder == null) + encoder = Encoding.Unicode; + + curByte = binReader.ReadByte(); + int nCnt = 0; + while( ((curByte != (byte)0) || (lastByte != 0) ) && (binReader.BaseStream.Position < binReader.BaseStream.Length) ) + { + nameBytes.Add( curByte ); + + if(nCnt%2 == 0) + lastByte = (int)curByte; + + curByte = binReader.ReadByte(); + + nCnt++; + } + + byte[] name = (byte[]) (nameBytes.ToArray(System.Type.GetType("System.Byte"))); + strReturn = Encoding.Unicode.GetString(name,0,name.Length); + + // apply text encoding + name = Encoding.Default.GetBytes(strReturn); + strReturn = encoder.GetString(name,0,name.Length); + + return strReturn; + } + + /// + /// Internal helper for reading ENCINT encoded integer values + /// + /// reference to the reader + /// a long value + internal static long ReadENCINT(ref BinaryReader binReader) + { + long nRet = 0; + byte buffer = 0; + int shift = 0; + + if(binReader.BaseStream.Position >= binReader.BaseStream.Length) + return nRet; + + do + { + buffer = binReader.ReadByte(); + nRet |= ((long)((buffer & (byte)0x7F))) << shift; + shift += 7; + + }while ( (buffer & (byte)0x80) != 0); + + return nRet; + } + + /// + /// Reads an s/r encoded value from the byte array and decodes it into an integer + /// + /// a byte array containing all bits (contains only 0 or 1 elements) + /// scale param for encoding + /// root param for encoding + /// current index in the wclBits array + /// Returns an decoded integer value. + internal static int ReadSRItem(byte[] wclBits, int s, int r, ref int nBitIndex) + { + int nRet = 0; + int q = r; + + int nPref1Cnt = 0; + + while( wclBits[nBitIndex++] == 1) + { + nPref1Cnt++; + } + + if(nPref1Cnt == 0) + { + int nMask = 0; + + for(int nbits=0; nbits + /// The class CHMBtree implements methods/properties to decode the binary help index. + /// This class automatically creates an index arraylist for the current CHMFile instance. + /// It does not store the index internally ! + /// + /// The binary index can be found in the storage file $WWKeywordLinks/BTree + internal sealed class CHMBtree : IDisposable + { + /// + /// Constant specifying the size of the string blocks + /// + private const int BLOCK_SIZE = 2048; + /// + /// Internal flag specifying if the object is going to be disposed + /// + private bool disposed = false; + /// + /// Internal member storing the binary file data + /// + private byte[] _binaryFileData = null; + /// + /// Internal member storing flags + /// + private int _flags = 0; + /// + /// Internal member storing the data format + /// + private byte[] _dataFormat = new byte[16]; + /// + /// Internal member storing the index of the last listing block + /// + private int _indexOfLastListingBlock = 0; + /// + /// Internal member storing the index of the root block + /// + private int _indexOfRootBlock = 0; + /// + /// Internal member storing the number of blocks + /// + private int _numberOfBlocks = 0; + /// + /// Internal member storing the tree depth. + /// (1 if no index blocks, 2 one level of index blocks, ...) + /// + private int _treeDepth = 0; + /// + /// Internal member storing the number of keywords in the file + /// + private int _numberOfKeywords = 0; + /// + /// Internal member storing the codepage + /// + private int _codePage = 0; + /// + /// true if the index is from a CHI or CHM file, else CHW + /// + private bool _isCHI_CHM = true; + /// + /// Internal member storing the associated chmfile object + /// + private CHMFile _associatedFile = null; + /// + /// Internal flag specifying if we have to read listing or index blocks + /// + private bool _readListingBlocks = true; + /// + /// Internal member storing an indexlist of the current file. + /// + private ArrayList _indexList = new ArrayList(); + + /// + /// Constructor of the class + /// + /// binary file data of the $WWKeywordLinks/BTree file + /// associated chm file + public CHMBtree(byte[] binaryFileData, CHMFile associatedFile) + { + if( associatedFile == null) + { + throw new ArgumentException("CHMBtree.ctor() - Associated CHMFile must not be null !", "associatedFile"); + } + + _binaryFileData = binaryFileData; + _associatedFile = associatedFile; + DecodeData(); + + // clear internal binary data after extraction + _binaryFileData = null; + } + + /// + /// Decodes the binary file data and fills the internal properties + /// + /// true if succeeded + private bool DecodeData() + { + bool bRet = true; + + MemoryStream memStream = new MemoryStream(_binaryFileData); + BinaryReader binReader = new BinaryReader(memStream); + + int nCurOffset = 0; + int nTemp = 0; + + // decode header + binReader.ReadChars(2); // 2chars signature (not important) + + _flags = (int)binReader.ReadInt16(); // WORD flags + + binReader.ReadInt16(); // size of blocks (always 2048) + + _dataFormat = binReader.ReadBytes(16); + + binReader.ReadInt32(); // unknown DWORD + + _indexOfLastListingBlock = binReader.ReadInt32(); + _indexOfRootBlock = binReader.ReadInt32(); + + binReader.ReadInt32(); // unknown DWORD + + _numberOfBlocks = binReader.ReadInt32(); + _treeDepth = binReader.ReadInt16(); + _numberOfKeywords = binReader.ReadInt32(); + _codePage = binReader.ReadInt32(); + + binReader.ReadInt32(); // lcid DWORD + + nTemp = binReader.ReadInt32(); + _isCHI_CHM = (nTemp==1); + + binReader.ReadInt32(); // unknown DWORD + binReader.ReadInt32(); // unknown DWORD + binReader.ReadInt32(); // unknown DWORD + binReader.ReadInt32(); // unknown DWORD + + // end of header decode + + while( (memStream.Position < memStream.Length) && (bRet) ) + { + nCurOffset = (int)memStream.Position; + byte [] dataBlock = binReader.ReadBytes(BLOCK_SIZE); + bRet &= DecodeBlock(dataBlock, ref nCurOffset, _treeDepth-1); + } + + return bRet; + } + + /// + /// Decodes a block of url-string data + /// + /// block of data + /// current file offset + /// number of index blocks + /// true if succeeded + private bool DecodeBlock( byte[] dataBlock, ref int nOffset, int indexBlocks ) + { + bool bRet = true; + int nblockOffset = nOffset; + + MemoryStream memStream = new MemoryStream(dataBlock); + BinaryReader binReader = new BinaryReader(memStream); + + int freeSpace = binReader.ReadInt16(); // length of freespace + int nrOfEntries = binReader.ReadInt16(); // number of entries + + bool bListingEndReached = false; + + //while( (memStream.Position < (memStream.Length-freeSpace)) && (bRet) ) + //{ + int nIndexOfPrevBlock = -1; + int nIndexOfNextBlock = -1; + int nIndexOfChildBlock = 0; + + if(_readListingBlocks) + { + nIndexOfPrevBlock = binReader.ReadInt32(); // -1 if this is the header + nIndexOfNextBlock = binReader.ReadInt32(); // -1 if this is the last block + } + else + { + nIndexOfChildBlock = binReader.ReadInt32(); + } + + for(int nE = 0; nE < nrOfEntries; nE++) + { + if(_readListingBlocks) + { + bListingEndReached = (nIndexOfNextBlock==-1); + + string keyWord = BinaryReaderHelp.ExtractUTF16String(ref binReader, 0, true, _associatedFile.TextEncoding); + + bool isSeeAlsoKeyword = (binReader.ReadInt16()!=0); + + int indent = binReader.ReadInt16(); // indent of entry + int nCharIndex = binReader.ReadInt32(); + + binReader.ReadInt32(); + + int numberOfPairs = binReader.ReadInt32(); + + int[] nTopics = new int[numberOfPairs]; + string[] seeAlso = new string[numberOfPairs]; + + for(int i=0; i < numberOfPairs; i++) + { + if(isSeeAlsoKeyword) + { + seeAlso[i] = BinaryReaderHelp.ExtractUTF16String(ref binReader, 0, true, _associatedFile.TextEncoding); + } + else + { + nTopics[i] = binReader.ReadInt32(); + } + } + + binReader.ReadInt32(); // unknown + + int nIndexOfThisEntry = binReader.ReadInt32(); + + IndexItem newItem = new IndexItem(_associatedFile, keyWord, isSeeAlsoKeyword, indent, nCharIndex, nIndexOfThisEntry, seeAlso, nTopics); + _indexList.Add(newItem); + } + else + { + string keyWord = BinaryReaderHelp.ExtractUTF16String(ref binReader, 0, true, _associatedFile.TextEncoding); + + bool isSeeAlsoKeyword = (binReader.ReadInt16()!=0); + + int indent = binReader.ReadInt16(); // indent of entry + int nCharIndex = binReader.ReadInt32(); + + binReader.ReadInt32(); + + int numberOfPairs = binReader.ReadInt32(); + + int[] nTopics = new int[numberOfPairs]; + string[] seeAlso = new string[numberOfPairs]; + + for(int i=0; i < numberOfPairs; i++) + { + if(isSeeAlsoKeyword) + { + seeAlso[i] = BinaryReaderHelp.ExtractUTF16String(ref binReader, 0, true, _associatedFile.TextEncoding); + } + else + { + nTopics[i] = binReader.ReadInt32(); + } + } + + int nIndexChild = binReader.ReadInt32(); + int nIndexOfThisEntry=-1; + + IndexItem newItem = new IndexItem(_associatedFile, keyWord, isSeeAlsoKeyword, indent, nCharIndex, nIndexOfThisEntry, seeAlso, nTopics); + _indexList.Add(newItem); + + } + } + //} + + binReader.ReadBytes(freeSpace); + + + if( bListingEndReached ) + _readListingBlocks = false; + + return bRet; + } + + /// + /// Gets the internal generated index list + /// + internal ArrayList IndexList + { + get { return _indexList; } + } + + /// + /// Implement IDisposable. + /// + public void Dispose() + { + Dispose(true); + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SupressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// disposing flag + private void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if(!this.disposed) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if(disposing) + { + // Dispose managed resources. + _binaryFileData = null; + } + } + disposed = true; + } + } +} diff --git a/irc/TechBot/CHMLibrary/CHMDecoding/CHMFile.cs b/irc/TechBot/CHMLibrary/CHMDecoding/CHMFile.cs new file mode 100644 index 00000000000..a39ffc4d684 --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMDecoding/CHMFile.cs @@ -0,0 +1,2061 @@ +using System; +using System.Diagnostics; +using System.Collections; +using System.Collections.Specialized; +using System.IO; +using System.Text; +using System.Runtime.InteropServices; +using System.Globalization; +// using HtmlHelp.Storage; + +namespace HtmlHelp.ChmDecoding +{ + /// + /// Internal enumeration for specifying the type of the html-help file + /// + internal enum HtmlHelpFileType + { + /// + /// CHM - compiled contents file + /// + /// A file with this extension must always exist. If the file would be too long, some parts + /// can be splitted into the filestypes below. + CHM = 0, + /// + /// CHI - compiled system file + /// + CHI = 1, + /// + /// CHQ - compiled fulltext search file + /// + CHQ = 2, + /// + /// CHW - compiled index file + /// + CHW = 3 + + } + + /// + /// The class CHMFile implemts methods and properties for handling a single chmfile. + /// + public sealed class CHMFile : IDisposable + { + /// + /// Internal member storing a reference to the hosting HtmlHelpSystem instance + /// + private HtmlHelpSystem _systemInstance = null; + /// + /// Internal flag specifying if only system data has been loaded + /// + private bool _onlySystem = false; + /// + /// Internal flag specifying if the object is going to be disposed + /// + private bool disposed = false; + /// + /// Internal arraylist containing the table of contents + /// + private ArrayList _toc = new ArrayList(); + /// + /// Internal arraylist containing items of the toc which are merge-Links + /// + private ArrayList _mergeLinks = new ArrayList(); + /// + /// Internal member storing the read information types + /// + private ArrayList _informationTypes = new ArrayList(); + /// + /// Internal member storing the read categories + /// + private ArrayList _categories = new ArrayList(); + /// + /// Internal arraylist containing the index (klinks) + /// + private ArrayList _indexKLinks = new ArrayList(); + /// + /// Internal arraylist containing the index (alinks) + /// + private ArrayList _indexALinks = new ArrayList(); + /// + /// Internal member storing the full filename + /// + private string _chmFileName = ""; + /// + /// Internal member storing the full filename of the chi-file (includes all system files) + /// The file name is zero-length if there is no chi-file + /// + private string _chiFileName = ""; + /// + /// Internal member storing the full filename of the chw-file (includes the help index) + /// The file name is zero-length if there is no chw-file + /// + private string _chwFileName = ""; + /// + /// Internal member storing the full filename of the chq-file (includes the fulltext contents) + /// The file name is zero-length if there is no chq-file + /// + private string _chqFileName = ""; + /// + /// Internal member storing the decoded information from the internal #SYSTEM file + /// + private CHMSystem _systemFile = null; + /// + /// Internal member storing the decoded information from the internal #IDXHDR file + /// + private CHMIdxhdr _idxhdrFile = null; + /// + /// Internal member storing the decoded information from the internal #STRINGS file + /// + private CHMStrings _stringsFile = null; + /// + /// Internal member storing the decoded information from the internal #URLSTR file + /// + private CHMUrlstr _urlstrFile = null; + /// + /// Internal member storing the decoded information from the internal #URLTBL file + /// + private CHMUrltable _urltblFile = null; + /// + /// Internal member storing the decoded information from the internal #TOPICS file + /// + private CHMTopics _topicsFile = null; + /// + /// Internal member storing the decoded information from the internal #TOCIDX file + /// + private CHMTocidx _tocidxFile = null; + /// + /// Internal member storing the decoded information from the internal binary index file (KLinks). + /// + private CHMBtree _kLinks = null; + /// + /// Internal member storing the decoded information from the internal binary index file (ALinks). + /// + private CHMBtree _aLinks = null; + /// + /// Internal member storing the fulltext searcher for this file + /// + private FullTextEngine _ftSearcher = null; + /// + /// Internal member storing the default encoder + /// + private Encoding _textEncoding = Encoding.GetEncoding(1252); // standard windows-1252 encoder + /// + /// Internal memebr storing the chm file info + /// + private ChmFileInfo _chmFileInfo = null; + /// + /// Internal flag specifying if the dump must be written (if enabled) + /// + private bool _mustWriteDump = false; + /// + /// Internal flag specifying if data was read using the dump + /// + private bool _dumpRead = false; + /// + /// Internal member for specifying the number of dump-reading trys. + /// If dump-reading fails, this is used that it will not be opened a second time + /// (in CHM-Systems with CHM, CHI, etc. files) + /// + private int _dumpReadTrys = 0; + /// + /// Internal member storing the dumping info instance + /// + private DumpingInfo _dmpInfo = null; + + private CHMStream.CHMStream _currentWrapper; + private CHMStream.CHMStream _baseStream=null; + public CHMStream.CHMStream BaseStream + { + get + { + if (_baseStream==null) + _baseStream=new CHMStream.CHMStream(this.ChmFilePath); + return _baseStream; + } + } + + /// + /// Creates a new instance of the class + /// + /// a reference to the hosting HtmlHelpSystem instance + /// chm file to read + public CHMFile(HtmlHelpSystem systemInstance, string chmFile) : this(systemInstance, chmFile, false, null) + { + } + + /// + /// Creates a new instance of the class + /// + /// a reference to the hosting HtmlHelpSystem instance + /// chm file to read + /// A dumping info class + public CHMFile(HtmlHelpSystem systemInstance, string chmFile, DumpingInfo dmpInfo) : this(systemInstance, chmFile, false, dmpInfo) + { + } + + /// + /// Creates a new instance of the class + /// + /// a reference to the hosting HtmlHelpSystem instance + /// chm file to read + /// true if only system data should be extracted (no index or toc) + internal CHMFile(HtmlHelpSystem systemInstance, string chmFile, bool onlySystemData) : this(systemInstance, chmFile, onlySystemData, null) + { + } + + /// + /// Creates a new instance of the class + /// + /// a reference to the hosting HtmlHelpSystem instance + /// chm file to read + /// true if only system data should be extracted (no index or toc) + /// A dumping info class + internal CHMFile(HtmlHelpSystem systemInstance, string chmFile, bool onlySystemData, DumpingInfo dmpInfo) + { + _systemInstance = systemInstance; + _dumpReadTrys=0; + + _dmpInfo = dmpInfo; + if(dmpInfo != null) + { + dmpInfo.ChmFile = this; + } + + if( ! chmFile.ToLower().EndsWith(".chm") ) + { + throw new ArgumentException("HtmlHelp file must have the extension .chm !", "chmFile"); + } + + _chmFileName = chmFile; + _chiFileName = ""; + + // Read the IStorage file system + if( File.Exists(chmFile) ) + { + _onlySystem = onlySystemData; + + DateTime dtStartHH = DateTime.Now; + + string sCHIName = _chmFileName.Substring(0, _chmFileName.Length-3) + "chi"; + string sCHQName = _chmFileName.Substring(0, _chmFileName.Length-3) + "chq"; + string sCHWName = _chmFileName.Substring(0, _chmFileName.Length-3) + "chw"; + + // If there is a CHI file present (this file includes the internal system files for the current chm file) + if( File.Exists(sCHIName) ) + { + _chiFileName = sCHIName; + + ReadFile(_chiFileName, HtmlHelpFileType.CHI); + } + + // If there is a CHW file present (this file includes the internal binary index of the help) + if(( File.Exists(sCHWName) ) && (!_onlySystem) ) + { + _chwFileName = sCHWName; + + ReadFile(_chwFileName, HtmlHelpFileType.CHW); + } + + // If there is a CHQ file present (this file includes the fulltext-search data) + if(( File.Exists(sCHQName) ) && (!_onlySystem) ) + { + _chqFileName = sCHQName; + + ReadFile(_chqFileName, HtmlHelpFileType.CHQ); + } + + ReadFile(chmFile, HtmlHelpFileType.CHM); + + if(_mustWriteDump) + { + _mustWriteDump = !SaveDump(dmpInfo); + + } + + // check the default-topic setting + if(_systemFile.DefaultTopic.Length > 0) + { + CHMStream.CHMStream iw=null; + iw = new CHMStream.CHMStream(chmFile); + _currentWrapper=iw; + + // tryo to open the topic file + MemoryStream fileObject = iw.OpenStream( _systemFile.DefaultTopic); + if( fileObject != null) + { + // if succeed, the topic default topic is OK + fileObject.Close(); + } + else + { + // set the first topic of the toc-tree as default topic + if(_toc.Count > 0) + { + _systemFile.SetDefaultTopic( ((TOCItem) _toc[0]).Local ); + } + } + _currentWrapper=null; + } + else + { + // set the first topic of the toc-tree as default topic + if(_toc.Count > 0) + { + _systemFile.SetDefaultTopic( ((TOCItem) _toc[0]).Local ); + } + } + + _chmFileInfo = new ChmFileInfo(this); + } + else + { + throw new ArgumentException("File '" + chmFile + "' not found !", "chmFile"); + } + } + + /// + /// Read a IStorage file + /// + /// filename + /// type of file + private void ReadFile(string fname, HtmlHelpFileType type) + { + CHMStream.CHMStream iw=null; + iw=new CHMStream.CHMStream(); + iw.OpenCHM(fname); + _currentWrapper=iw; + MemoryStream fileObject=null; + + // ITStorageWrapper iw = null; + + // Open the internal chm system files and parse their content + // FileObject fileObject = null; + // iw = new ITStorageWrapper(fname, false); + + if( (type != HtmlHelpFileType.CHQ) && (type != HtmlHelpFileType.CHW) ) + { + fileObject = iw.OpenStream("#SYSTEM"); + if ((fileObject != null) && (fileObject.Length>0)) + _systemFile = new CHMSystem(fileObject.ToArray(), this); + + fileObject = iw.OpenStream("#IDXHDR"); + if ((fileObject != null) && (fileObject.Length>0)) + _idxhdrFile = new CHMIdxhdr(fileObject.ToArray(), this); + + // try to read Dump + if((!_dumpRead)&&(CheckDump(_dmpInfo))&&(_dumpReadTrys==0)) + { + _dumpReadTrys++; + _dumpRead = LoadDump(_dmpInfo); + } + + if( (!_dumpRead)||(!_dmpInfo.DumpStrings) ) + { + fileObject = iw.OpenStream( "#STRINGS"); + if ((fileObject != null) && (fileObject.Length>0)) + _stringsFile = new CHMStrings(fileObject.ToArray(), this); + } + + if( (!_dumpRead)||(!_dmpInfo.DumpUrlStr) ) + { + fileObject = iw.OpenStream( "#URLSTR"); + if ((fileObject != null) && (fileObject.Length>0)) + _urlstrFile = new CHMUrlstr(fileObject.ToArray(), this); + } + + if( (!_dumpRead)||(!_dmpInfo.DumpUrlTbl) ) + { + fileObject = iw.OpenStream( "#URLTBL"); + if ((fileObject != null) && (fileObject.Length>0)) + _urltblFile = new CHMUrltable(fileObject.ToArray(), this); + } + + if( (!_dumpRead)||(!_dmpInfo.DumpTopics) ) + { + fileObject = iw.OpenStream( "#TOPICS"); + if ((fileObject != null) && (fileObject.Length>0)) + _topicsFile = new CHMTopics(fileObject.ToArray(), this); + } + + if(!_onlySystem) + { + if( (!_dumpRead)||(!_dmpInfo.DumpBinaryTOC) ) + { + fileObject = iw.OpenStream( "#TOCIDX"); + if( (fileObject != null) && (fileObject.Length>0) && (_systemFile.BinaryTOC) ) + { + _tocidxFile = new CHMTocidx(fileObject.ToArray(), this); + _toc = _tocidxFile.TOC; + } + } + } + + if( (_systemFile != null) && (!_onlySystem) ) + { + if(!_systemFile.BinaryTOC) + { + if( (!_dumpRead)||(!_dmpInfo.DumpTextTOC) ) + { + CHMStream.chmUnitInfo HHCInfo=iw.GetFileInfoByExtension(".hhc"); + if (HHCInfo!=null) + { + fileObject = iw.OpenStream(HHCInfo); + if ((fileObject != null) && (fileObject.Length>0)) + { + ASCIIEncoding ascii=new ASCIIEncoding(); + string fileString =ascii.GetString(fileObject.ToArray(),0,(int)fileObject.Length); + + _toc = HHCParser.ParseHHC(fileString, this); + } + + if(HHCParser.HasMergeLinks) + _mergeLinks = HHCParser.MergeItems; + } + } + } + } + } + + if( type != HtmlHelpFileType.CHQ ) // no index information in CHQ files (only fulltext search) + { + if( (_systemFile != null) && (!_onlySystem) ) + { + if( ! _systemFile.BinaryIndex ) + { + if( (!_dumpRead)||(!_dmpInfo.DumpTextIndex) ) + { + fileObject = iw.OpenStream( _systemFile.IndexFile); + if ((fileObject != null) && (fileObject.Length>0)) + { + + string fileString = this.TextEncoding.GetString(fileObject.ToArray(),0,(int)fileObject.Length); + // string fileString = fileObject.ReadFromFile(this.TextEncoding); + fileObject.Close(); + + _indexKLinks = HHKParser.ParseHHK(fileString, this); + _indexKLinks.Sort(); + } + } + } + else + { + if( (!_dumpRead)||(!_dmpInfo.DumpBinaryIndex) ) + { + fileObject=iw.OpenStream(@"$WWKeywordLinks\BTree"); + if ((fileObject != null) && (fileObject.Length>0)) + { + _kLinks = new CHMBtree(fileObject.ToArray(), this); + _indexKLinks = _kLinks.IndexList; + } + + fileObject =iw.OpenStream(@"$WWAssociativeLinks\BTree"); + if ((fileObject != null) && (fileObject.Length>0)) + { + _aLinks = new CHMBtree(fileObject.ToArray(), this); + _indexALinks = _aLinks.IndexList; + _indexALinks.Sort(); + } + } + } + } + } + + if( (type != HtmlHelpFileType.CHI) && (type != HtmlHelpFileType.CHW) ) + { + if( (_systemFile != null) && (!_onlySystem) ) + { + if( _systemFile.FullTextSearch) + { + if( (!_dumpRead)||(!_dmpInfo.DumpFullText) ) + { + fileObject = iw.OpenStream("$FIftiMain"); + if(( fileObject != null) && (fileObject .Length>0)) + _ftSearcher = new FullTextEngine(fileObject .ToArray(), this); + } + } + } + } + _currentWrapper=null; + } + + /// + /// Enumerates the files in the chm storage and gets all files matching a given extension. + /// + /// extension to return + /// Returns an arraylist of filenames or null if nothing found + /// On large CHMs, enumerations are very slow. Only use it if necessary ! + internal ArrayList EnumFilesByExtension(string extension) + { + ArrayList arrRet = new ArrayList(); + + CHMStream.CHMStream iw = null; + if(_currentWrapper == null) + iw = new CHMStream.CHMStream(_chmFileName); + else + iw = _currentWrapper; + + arrRet=iw.GetFileListByExtenstion(extension); + + if(arrRet.Count > 0) + return arrRet; + + return null; + } + + /// + /// Searches an TOC entry using the local + /// + /// local to search + /// Returns the TOC item + internal TOCItem GetTOCItemByLocal(string local) + { + return GetTOCItemByLocal(this.TOC, local); + } + + /// + /// Recursively searches an TOC entry using its local + /// + /// toc level list + /// local to search + /// Returns the TOC item + private TOCItem GetTOCItemByLocal(ArrayList arrTOC, string local) + { + TOCItem ret = null; + foreach(TOCItem curItem in arrTOC) + { + string scL = curItem.Local.ToLower(); + string sL = local.ToLower(); + + while(scL[0]=='/') // delete prefixing '/' + scL = scL.Substring(1); + + while(sL[0]=='/') // delete prefixing '/' + sL = sL.Substring(1); + + if(scL == sL) + return curItem; + + if(curItem.Children.Count > 0) + { + ret = GetTOCItemByLocal(curItem.Children, local); + if(ret != null) + return ret; + } + } + + return ret; + } + + /// + /// Removes a TOCItem from the toc + /// + /// item to remove + /// Returns true if removed + internal bool RemoveTOCItem(TOCItem rem) + { + if(rem == null) + return false; + + return RemoveTOCItem(this.TOC, rem); + } + + /// + /// Recursively searches a TOCItem and removes it if found + /// + /// toc level list + /// item to remove + /// Returns true if removed + private bool RemoveTOCItem(ArrayList arrTOC, TOCItem rem) + { + for(int i=0; i 0) + { + bool bRem = RemoveTOCItem(curItem.Children, rem); + if(bRem) + return true; + } + } + + return false; + } + + /// + /// Returns true if the HtmlHelpSystem instance contains 1 or more information types + /// + public bool HasInformationTypes + { + get { return (_informationTypes.Count>0); } + } + + /// + /// Returns true if the HtmlHelpSystem instance contains 1 or more categories + /// + public bool HasCategories + { + get { return (_categories.Count>0); } + } + + /// + /// Gets an ArrayList of InformationType items + /// + public ArrayList InformationTypes + { + get { return _informationTypes; } + } + + /// + /// Gets an ArrayList of Category items + /// + public ArrayList Categories + { + get { return _categories; } + } + + /// + /// Gets the information type specified by its name + /// + /// name of the information type to receive + /// Returns the Instance for the name or null if not found + public InformationType GetInformationType(string name) + { + if(HasInformationTypes) + { + for(int i=0; i<_informationTypes.Count;i++) + { + InformationType iT = _informationTypes[i] as InformationType; + + if(iT.Name == name) + return iT; + } + } + + return null; + } + + /// + /// Gets the category specifiyd by its name + /// + /// name of the category + /// Returns the Instance for the name or null if not found + public Category GetCategory(string name) + { + if(HasCategories) + { + for(int i=0; i<_categories.Count;i++) + { + Category cat = _categories[i] as Category; + + if(cat.Name == name) + return cat; + } + } + + return null; + } + + #region Dumping methods + + /// + /// Checks if a dump for this file exists and if it can be read + /// + /// dumping info class + /// true if it can be read + private bool CheckDump(DumpingInfo dmpInfo) + { + if(_dumpReadTrys<=0) + _mustWriteDump = false; + + if(_onlySystem) + return false; + + if( dmpInfo != null ) + { + if(_dumpReadTrys > 0) + return _mustWriteDump; + + _mustWriteDump = !dmpInfo.DumpExists; + return !_mustWriteDump; + } + + return false; + } + + /// + /// Saves the the toc and index into a data dump + /// + /// dumping info + /// true if succeed + private bool SaveDump(DumpingInfo dmpInfo) + { + if(dmpInfo == null) + return false; + + bool bRet = false; + + + BinaryWriter writer = dmpInfo.Writer; + + int nCnt = 0; + try + { + Debug.WriteLine("writing dump-file header"); + FileInfo fi = new FileInfo(_chmFileName); + string ftime = fi.LastWriteTime.ToString("dd.MM.yyyy HH:mm:ss.ffffff"); + + writer.Write("HtmlHelpSystem dump file 1.0"); + writer.Write(ftime); + writer.Write(_textEncoding.CodePage); + + // strings dumping + if(dmpInfo.DumpStrings) + { + writer.Write(true); // data should be in dump + + if(_stringsFile==null) + { + writer.Write(false); // data not supported by the chm + } + else + { + Debug.WriteLine("writing #STRINGS"); + writer.Write(true); // data supported and following + _stringsFile.Dump(ref writer); + } + } + else + { + writer.Write(false); // data is not in dump + } + + // urlstr dumping + if(dmpInfo.DumpUrlStr) + { + writer.Write(true); + + if(_urlstrFile==null) + { + writer.Write(false); + } + else + { + Debug.WriteLine("writing #URLSTR"); + writer.Write(true); + _urlstrFile.Dump(ref writer); + } + } + else + { + writer.Write(false); + } + + // urltbl dumping + if(dmpInfo.DumpUrlTbl) + { + writer.Write(true); + + if(_urltblFile==null) + { + writer.Write(false); + } + else + { + Debug.WriteLine("writing #URLTBL"); + writer.Write(true); + _urltblFile.Dump(ref writer); + } + } + else + { + writer.Write(false); + } + + // topics dumping + if(dmpInfo.DumpTopics) + { + writer.Write(true); + + if(_topicsFile==null) + { + writer.Write(false); + } + else + { + Debug.WriteLine("writing #TOPICS"); + writer.Write(true); + _topicsFile.Dump(ref writer); + } + } + else + { + writer.Write(false); + } + + // ftsearch dumping + if(dmpInfo.DumpFullText) + { + writer.Write(true); + + if(_ftSearcher==null) + { + writer.Write(false); + } + else + { + Debug.WriteLine("writing $FIftiMain"); + writer.Write(true); + _ftSearcher.Dump(ref writer); + } + } + else + { + writer.Write(false); + } + + // TOC dumping + bool bWriteTOC = false; + + if( (_systemFile.BinaryTOC) && (dmpInfo.DumpBinaryTOC) ) + { + Debug.WriteLine("writing binary TOC"); + bWriteTOC = true; + } + + if( (!_systemFile.BinaryTOC) && (dmpInfo.DumpTextTOC) ) + { + Debug.WriteLine("writing text-based TOC"); + bWriteTOC = true; + } + + writer.Write(bWriteTOC); + + if(bWriteTOC) + { + // write table of contents + writer.Write( _toc.Count ); + + for(nCnt=0; nCnt < _toc.Count; nCnt++) + { + TOCItem curItem = ((TOCItem)(_toc[nCnt])); + curItem.Dump( ref writer ); + } + } + + // Index dumping + bool bWriteIdx = false; + + if( (_systemFile.BinaryIndex) && (dmpInfo.DumpBinaryIndex) ) + { + Debug.WriteLine("writing binary index"); + bWriteIdx = true; + } + + if( (!_systemFile.BinaryIndex) && (dmpInfo.DumpTextIndex) ) + { + Debug.WriteLine("writing text-based index"); + bWriteIdx = true; + } + + writer.Write(bWriteIdx); + + if(bWriteIdx) + { + // write index + writer.Write( _indexALinks.Count ); + for(nCnt=0; nCnt < _indexALinks.Count; nCnt++) + { + IndexItem curItem = ((IndexItem)(_indexALinks[nCnt])); + curItem.Dump( ref writer ); + } + + writer.Write( _indexKLinks.Count ); + for(nCnt=0; nCnt < _indexKLinks.Count; nCnt++) + { + IndexItem curItem = ((IndexItem)(_indexKLinks[nCnt])); + curItem.Dump( ref writer ); + } + } + + // Information types dumping + writer.Write( _informationTypes.Count ); + + Debug.WriteLine("writing " + _informationTypes.Count.ToString() + " information types"); + + for(nCnt=0; nCnt<_informationTypes.Count;nCnt++) + { + InformationType curType = _informationTypes[nCnt] as InformationType; + + curType.Dump(ref writer); + } + + // Categories dumping + writer.Write( _categories.Count ); + + Debug.WriteLine("writing " + _categories.Count.ToString() + " categories"); + + for(nCnt=0; nCnt<_categories.Count; nCnt++) + { + Category curCat = _categories[nCnt] as Category; + + curCat.Dump( ref writer); + } + + bRet=true; + } + finally + { + dmpInfo.SaveData(); + } + + return bRet; + } + + /// + /// Parses a HHC file which is located in the current CHM. + /// + /// hhc file to parse + /// an arraylist with toc items + public ArrayList ParseHHC(string hhcFile) + { + ArrayList arrRet = new ArrayList(); + + CHMStream.CHMStream iw=null; + iw=new CHMStream.CHMStream(); + iw.OpenCHM(_chmFileName); + MemoryStream fileObject=null; + + fileObject = iw.OpenStream(hhcFile); + if( fileObject != null) + { + ASCIIEncoding ascii=new ASCIIEncoding(); + string fileString =ascii.GetString(fileObject.ToArray(),0,(int)fileObject.Length); + fileObject.Close(); + + arrRet = HHCParser.ParseHHC(fileString, this); + + if(HHCParser.HasMergeLinks) + { + foreach(TOCItem curItem in HHCParser.MergeItems) + { + _mergeLinks.Add(curItem); + } + } + } + + return arrRet; + } + + /// + /// Loads the toc and index from a data dump + /// + /// dumping info + /// true if succeed + private bool LoadDump(DumpingInfo dmpInfo) + { + if(dmpInfo == null) + return false; + + bool bRet = false; + + try + { + BinaryReader reader = dmpInfo.Reader; + + if(reader == null) + { + Debug.WriteLine("No reader returned !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; + } + + int nCnt = 0; + + Debug.WriteLine("reading dump-file header"); + FileInfo fi = new FileInfo(_chmFileName); + string ftime = fi.LastWriteTime.ToString("dd.MM.yyyy HH:mm:ss.ffffff"); + + string header = reader.ReadString(); + + if( header != "HtmlHelpSystem dump file 1.0") + { + Debug.WriteLine("Unsupported dump-file format !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; + } + + string ftimecheck = reader.ReadString(); + + reader.ReadInt32(); // codepage, we'll use the same as for the chm file which is already set. + +// if(ftimecheck != ftime) +// { +// Debug.WriteLine("Dump is out of date (CHM file changed during last dump creation) !"); +// dmpInfo.SaveData(); // closes the dump +// _mustWriteDump = true; +// return false; // force reload +// } + + + bool bFlag=false; // true if data should be in dump + bool bFlagSupp=false; // false if data is not supported by the chm + + bFlag = reader.ReadBoolean(); + + if(bFlag) + { + bFlagSupp = reader.ReadBoolean(); + + if(!dmpInfo.DumpStrings) + { + Debug.WriteLine("Dumped #STRINGS found but not expected !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + + if(bFlagSupp) + { + Debug.WriteLine("reading #STRINGS"); + _stringsFile = new CHMStrings(); + _stringsFile.SetCHMFile(this); + _stringsFile.ReadDump(ref reader); + } + } + else + { + if(dmpInfo.DumpStrings) + { + Debug.WriteLine("Dumped #STRINGS expected but not found !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + } + + bFlag = reader.ReadBoolean(); + + if(bFlag) + { + bFlagSupp = reader.ReadBoolean(); + + if(!dmpInfo.DumpUrlStr) + { + Debug.WriteLine("Dumped #URLSTR found but not expected !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + + if(bFlagSupp) + { + Debug.WriteLine("reading #URLSTR"); + _urlstrFile = new CHMUrlstr(); + _urlstrFile.SetCHMFile(this); + _urlstrFile.ReadDump(ref reader); + } + } + else + { + if(dmpInfo.DumpUrlStr) + { + Debug.WriteLine("Dumped #URLSTR expected but not found !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + } + + bFlag = reader.ReadBoolean(); + + if(bFlag) + { + bFlagSupp = reader.ReadBoolean(); + + if(!dmpInfo.DumpUrlTbl) + { + Debug.WriteLine("Dumped #URLTBL found but not expected !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + + if(bFlagSupp) + { + Debug.WriteLine("reading #URLTBL"); + _urltblFile = new CHMUrltable(); + _urltblFile.SetCHMFile(this); + _urltblFile.ReadDump(ref reader); + } + } + else + { + if(dmpInfo.DumpUrlTbl) + { + Debug.WriteLine("Dumped #URLTBL expected but not found !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + } + + bFlag = reader.ReadBoolean(); + + if(bFlag) + { + bFlagSupp = reader.ReadBoolean(); + + if(!dmpInfo.DumpTopics) + { + Debug.WriteLine("Dumped #TOPICS found but not expected !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + + if(bFlagSupp) + { + Debug.WriteLine("reading #TOPICS"); + _topicsFile = new CHMTopics(); + _topicsFile.SetCHMFile(this); + _topicsFile.ReadDump(ref reader); + } + } + else + { + if(dmpInfo.DumpTopics) + { + Debug.WriteLine("Dumped #TOPICS expected but not found !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + } + + bFlag = reader.ReadBoolean(); + + if(bFlag) + { + bFlagSupp = reader.ReadBoolean(); + + if(!dmpInfo.DumpFullText) + { + Debug.WriteLine("Dumped $FIftiMain found but not expected !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + + if(bFlagSupp) + { + Debug.WriteLine("reading $FIftiMain"); + _ftSearcher = new FullTextEngine(); + _ftSearcher.SetCHMFile(this); + _ftSearcher.ReadDump(ref reader); + } + } + else + { + if(dmpInfo.DumpFullText) + { + Debug.WriteLine("Dumped $FIftiMain expected but not found !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + } + + // read table of contents + _toc.Clear(); + bFlag = reader.ReadBoolean(); + + if(bFlag) + { + if((_systemFile.BinaryTOC)&&(!dmpInfo.DumpBinaryTOC)) + { + Debug.WriteLine("Binary TOC expected but not found !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + + if((!_systemFile.BinaryTOC)&&(!dmpInfo.DumpTextTOC)) + { + Debug.WriteLine("Text-based TOC expected but not found !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + + if(_systemFile.BinaryTOC) + Debug.WriteLine("reading binary TOC"); + else + Debug.WriteLine("reading text-based TOC"); + + int nTocCnt = reader.ReadInt32(); + + for(nCnt=0; nCnt < nTocCnt; nCnt++) + { + TOCItem item = new TOCItem(); + item.AssociatedFile = this; + item.ChmFile = _chmFileName; + item.ReadDump(ref reader); + if(item.MergeLink.Length > 0) + _mergeLinks.Add(item); + + _toc.Add(item); + } + } + else + { + if((_systemFile.BinaryTOC)&&(dmpInfo.DumpBinaryTOC)) + { + Debug.WriteLine("Binary TOC expected but no TOC dump !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + + if((!_systemFile.BinaryTOC)&&(dmpInfo.DumpTextTOC)) + { + Debug.WriteLine("Text-based TOC expected but no TOC dump !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + } + + // read index + _indexALinks.Clear(); + _indexKLinks.Clear(); + + bFlag = reader.ReadBoolean(); + + if(bFlag) + { + if((_systemFile.BinaryIndex)&&(!dmpInfo.DumpBinaryIndex)) + { + Debug.WriteLine("Binary index expected but not found !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + + if((!_systemFile.BinaryIndex)&&(!dmpInfo.DumpTextIndex)) + { + Debug.WriteLine("Binary index expected but not found !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + + if(_systemFile.BinaryIndex) + Debug.WriteLine("reading binary index"); + else + Debug.WriteLine("reading text-based index"); + + int nIndxaCnt = reader.ReadInt32(); + + for(nCnt=0; nCnt < nIndxaCnt; nCnt++) + { + IndexItem item = new IndexItem(); + item.ChmFile = this; + item.ReadDump(ref reader); + _indexALinks.Add(item); + } + + + int nIndxkCnt = reader.ReadInt32(); + + for(nCnt=0; nCnt < nIndxkCnt; nCnt++) + { + IndexItem item = new IndexItem(); + item.ChmFile = this; + item.ReadDump(ref reader); + _indexKLinks.Add(item); + } + } + else + { + if((_systemFile.BinaryIndex)&&(dmpInfo.DumpBinaryIndex)) + { + Debug.WriteLine("Binary index expected but no index in dump !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + + if((!_systemFile.BinaryIndex)&&(dmpInfo.DumpTextIndex)) + { + Debug.WriteLine("Text-based index expected but no index dump !"); + dmpInfo.SaveData(); // closes the dump + _mustWriteDump = true; + return false; // force reload + } + } + + // read information types from dump + int nITCnt = reader.ReadInt32(); + + Debug.WriteLine("Reading " + nITCnt.ToString() + " information types from dump !"); + + for(nCnt=0; nCnt + /// Gets the current storage wrapper. + /// + /// This property will return not null, if there are currently file read actions running ! + internal CHMStream.CHMStream CurrentStorageWrapper + { + get { return _currentWrapper;} + } + /// + /// Gets/sets the hosting HtmlHelpSystem instance + /// + internal HtmlHelpSystem SystemInstance + { + get { return _systemInstance; } + set { _systemInstance = value; } + } + /// + /// Gets an arraylist of TOC items which contains merg-links to other CHMs + /// + internal ArrayList MergLinks + { + get { return _mergeLinks; } + } + + /// + /// Gets the internal system file instance + /// + internal CHMSystem SystemFile + { + get { return _systemFile; } + } + + /// + /// Gets the internal idxhdr file instance + /// + internal CHMIdxhdr IdxHdrFile + { + get { return _idxhdrFile; } + } + + /// + /// Gets the internal strings file instance + /// + internal CHMStrings StringsFile + { + get { return _stringsFile; } + } + + /// + /// Gets the internal urlstr file instance + /// + internal CHMUrlstr UrlstrFile + { + get { return _urlstrFile; } + } + + /// + /// Gets the internal urltbl file instance + /// + internal CHMUrltable UrltblFile + { + get { return _urltblFile; } + } + + /// + /// Gets the internal topics file instance + /// + internal CHMTopics TopicsFile + { + get { return _topicsFile; } + } + + /// + /// Gets the internal tocidx file instance + /// + internal CHMTocidx TocidxFile + { + get { return _tocidxFile; } + } + + /// + /// Gets the internal btree file instance for alinks + /// + internal CHMBtree ALinksFile + { + get { return _aLinks; } + } + + /// + /// Gets the internal btree file instance for klinks + /// + internal CHMBtree KLinksFile + { + get { return _kLinks; } + } + + /// + /// Gets/Sets the text encoding + /// + internal Encoding TextEncoding + { + get { return _textEncoding; } + set { _textEncoding = value; } + } + + #endregion + + #region Properties from the #SYSTEM file + /// + /// Gets the file version of the chm file. + /// 2 for Compatibility=1.0, 3 for Compatibility=1.1 + /// + internal int FileVersion + { + get + { + if(_systemFile==null) + return 0; + + return _systemFile.FileVersion; + } + } + + /// + /// Gets the contents file name + /// + internal string ContentsFile + { + get + { + if(_systemFile==null) + return String.Empty; + + return _systemFile.ContentsFile; + } + } + + /// + /// Gets the index file name + /// + internal string IndexFile + { + get + { + if(_systemFile==null) + return String.Empty; + + return _systemFile.IndexFile; + } + } + + /// + /// Gets the default help topic + /// + internal string DefaultTopic + { + get + { + if(_systemFile==null) + return String.Empty; + + return _systemFile.DefaultTopic; + } + } + + /// + /// Gets the title of the help window + /// + internal string HelpWindowTitle + { + get + { + if(_systemFile==null) + return String.Empty; + + return _systemFile.Title; + } + } + + /// + /// Gets the flag if DBCS is in use + /// + internal bool DBCS + { + get + { + if(_systemFile==null) + return false; + + return _systemFile.DBCS; + } + } + + /// + /// Gets the flag if full-text-search is available + /// + internal bool FullTextSearch + { + get + { + if(_systemFile==null) + return false; + + return _systemFile.FullTextSearch; + } + } + + /// + /// Gets the flag if the file has ALinks + /// + internal bool HasALinks + { + get + { + if(_systemFile==null) + return false; + + return _systemFile.HasALinks; + } + } + + /// + /// Gets the flag if the file has KLinks + /// + internal bool HasKLinks + { + get + { + if(_systemFile==null) + return false; + + return _systemFile.HasKLinks; + } + } + + /// + /// Gets the default window name + /// + internal string DefaultWindow + { + get + { + if(_systemFile==null) + return String.Empty; + + return _systemFile.DefaultWindow; + } + } + + /// + /// Gets the file name of the compile file + /// + internal string CompileFile + { + get + { + if(_systemFile==null) + return String.Empty; + + return _systemFile.CompileFile; + } + } + + /// + /// Gets the flag if the chm has a binary index file + /// + internal bool BinaryIndex + { + get + { + if(_systemFile==null) + return false; + + return _systemFile.BinaryIndex; + } + } + + /// + /// Gets the flag if the chm has a binary index file + /// + internal string CompilerVersion + { + get + { + if(_systemFile==null) + return String.Empty; + + return _systemFile.CompilerVersion; + } + } + + /// + /// Gets the flag if the chm has a binary toc file + /// + internal bool BinaryTOC + { + get + { + if(_systemFile==null) + return false; + + return _systemFile.BinaryTOC; + } + } + + /// + /// Gets the font face of the read font property. + /// Empty string for default font. + /// + internal string FontFace + { + get + { + if(_systemFile==null) + return ""; + + return _systemFile.FontFace; + } + } + + /// + /// Gets the font size of the read font property. + /// 0 for default font size + /// + internal double FontSize + { + get + { + if(_systemFile==null) + return 0; + + return _systemFile.FontSize; + } + } + + /// + /// Gets the character set of the read font property + /// 1 for default + /// + internal int CharacterSet + { + get + { + if(_systemFile==null) + return 1; + + return _systemFile.CharacterSet; + } + } + + /// + /// Gets the codepage depending on the read font property + /// + internal int CodePage + { + get + { + if(_systemFile==null) + return CultureInfo.CurrentCulture.TextInfo.ANSICodePage; + + return _systemFile.CodePage; + } + } + + /// + /// Gets the assiciated culture info + /// + internal CultureInfo Culture + { + get + { + if(_systemFile==null) + return CultureInfo.CurrentCulture; + + return _systemFile.Culture; + } + } + + #endregion + + #region Properties from the #IDXHDR file + /// + /// Gets the number of topic nodes including the contents and index files + /// + internal int NumberOfTopicNodes + { + get + { + if(_idxhdrFile==null) + return 0; + + return _idxhdrFile.NumberOfTopicNodes; + } + } + + /// + /// Gets the ImageList string specyfied in the #IDXHDR file. + /// + /// This property uses the #STRINGS file to extract the string at a given offset. + internal string ImageList + { + get + { + if( (_stringsFile == null) || (_idxhdrFile == null) ) + return ""; + + return _stringsFile[ _idxhdrFile.ImageListOffset ]; + } + } + + /// + /// True if the value of the ImageType param of the + /// "text/site properties" object of the sitemap contents is "Folder". + /// + /// If this is set to true, the help will display folders instead of books + internal bool ImageTypeFolder + { + get + { + if(_idxhdrFile==null) + return false; + + return _idxhdrFile.ImageTypeFolder; + } + } + + /// + /// Gets the background setting + /// + internal int Background + { + get + { + if(_idxhdrFile==null) + return 0; + + return _idxhdrFile.Background; + } + } + + /// + /// Gets the foreground setting + /// + internal int Foreground + { + get + { + if(_idxhdrFile==null) + return 0; + + return _idxhdrFile.Foreground; + } + } + + /// + /// Gets the Font string specyfied in the #IDXHDR file. + /// + /// This property uses the #STRINGS file to extract the string at a given offset. + internal string FontName + { + get + { + if( (_stringsFile == null) || (_idxhdrFile == null) ) + return ""; + + return _stringsFile[ _idxhdrFile.FontOffset ]; + } + } + + /// + /// Gets the FrameName string specyfied in the #IDXHDR file. + /// + /// This property uses the #STRINGS file to extract the string at a given offset. + internal string FrameName + { + get + { + if( (_stringsFile == null) || (_idxhdrFile == null) ) + return ""; + + return _stringsFile[ _idxhdrFile.FrameNameOffset ]; + } + } + + /// + /// Gets the WindowName string specyfied in the #IDXHDR file. + /// + /// This property uses the #STRINGS file to extract the string at a given offset. + internal string WindowName + { + get + { + if( (_stringsFile == null) || (_idxhdrFile == null) ) + return ""; + + return _stringsFile[ _idxhdrFile.WindowNameOffset ]; + } + } + + /// + /// Gets a string array containing the merged file names + /// + internal string[] MergedFiles + { + get + { + if( (_stringsFile == null) || (_idxhdrFile == null) ) + return new string[0]; + + string[] saRet = new string[ _idxhdrFile.MergedFileOffsets.Count ]; + + for(int i=0; i < _idxhdrFile.MergedFileOffsets.Count; i++) + { + saRet[i] = _stringsFile[ (int)_idxhdrFile.MergedFileOffsets[i] ]; + } + + return saRet; + } + } + #endregion + + /// + /// Gets the file info associated with this instance + /// + public ChmFileInfo FileInfo + { + get { return _chmFileInfo; } + } + + /// + /// Gets the internal toc read from the text-based hhc file + /// + public ArrayList TOC + { + get { return _toc; } + } + + /// + /// Gets the internal index read from the chm file. + /// + public ArrayList IndexKLinks + { + get { return _indexKLinks; } + } + + /// + /// Gets the internal index read from the chm file. + /// + public ArrayList IndexALinks + { + get { return _indexALinks; } + } + + /// + /// Gets the full-text search engine for this file + /// + internal FullTextEngine FullTextSearchEngine + { + get { return _ftSearcher; } + } + + /// + /// Gets the full pathname of the file + /// + public string ChmFilePath + { + get { return _chmFileName; } + } + + /// + /// Gets the full pathname of the chi-file + /// The file name is zero-length if there is no chi-file + /// + public string ChiFilePath + { + get { return _chiFileName; } + } + + /// + /// Gets the full pathname of the chw-file + /// The file name is zero-length if there is no chw-file + /// + public string ChwFilePath + { + get { return _chwFileName; } + } + + /// + /// Gets the full pathname of the chq-file + /// The file name is zero-length if there is no chq-file + /// + public string ChqFilePath + { + get { return _chqFileName; } + } + + /// + /// Forms an URL for the web browser + /// + /// local resource + /// a url for the web-browser + internal string FormURL(string local) + { + if( (local.ToLower().IndexOf("http://") >= 0) || + (local.ToLower().IndexOf("https://") >= 0) || + (local.ToLower().IndexOf("mailto:") >= 0) || + (local.ToLower().IndexOf("ftp://") >= 0) || + (local.ToLower().IndexOf("ms-its:") >= 0)) + return local; + + return HtmlHelpSystem.UrlPrefix + _chmFileName + "::/" + local; + } + + /// + /// Implement IDisposable. + /// + public void Dispose() + { + Dispose(true); + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SupressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// disposing flag + private void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if(!this.disposed) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if(disposing) + { + // Dispose managed resources. + _toc.Clear(); + _indexKLinks.Clear(); + _indexALinks.Clear(); + + if(_systemFile!=null) + _systemFile.Dispose(); + + if(_idxhdrFile != null) + _idxhdrFile.Dispose(); + + if(_stringsFile != null) + _stringsFile.Dispose(); + + if(_urlstrFile != null) + _urlstrFile.Dispose(); + + if(_urltblFile != null) + _urltblFile.Dispose(); + + if(_topicsFile != null) + _topicsFile.Dispose(); + + if(_tocidxFile != null) + _tocidxFile.Dispose(); + + if(_kLinks != null) + _kLinks.Dispose(); + + if(_aLinks != null) + _aLinks.Dispose(); + + if(_ftSearcher != null) + _ftSearcher.Dispose(); + + if(_chmFileInfo != null) + _chmFileInfo = null; + } + } + disposed = true; + } + + } +} diff --git a/irc/TechBot/CHMLibrary/CHMDecoding/CHMIdxhdr.cs b/irc/TechBot/CHMLibrary/CHMDecoding/CHMIdxhdr.cs new file mode 100644 index 00000000000..7ac64ac3ea7 --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMDecoding/CHMIdxhdr.cs @@ -0,0 +1,286 @@ +using System; +using System.Collections; +using System.IO; + +namespace HtmlHelp.ChmDecoding +{ + /// + /// The class CHMIdxhdr implements t properties which have been read from the #IDXHDR file. + /// + internal sealed class CHMIdxhdr : IDisposable + { + /// + /// Internal flag specifying if the object is going to be disposed + /// + private bool disposed = false; + /// + /// Internal member storing the binary file data + /// + private byte[] _binaryFileData = null; + /// + /// Internal member storing the number of topic nodes including the contents and index files + /// + private int _numberOfTopicNodes = 0; + /// + /// Internal member storing the offset in the #STRINGS file of the ImageList param of the "text/site properties" object of the sitemap contents + /// + private int _imageListOffset = 0; + /// + /// True if the value of the ImageType param of the "text/site properties" object of the sitemap contents is "Folder". + /// + private bool _imageTypeFolder = false; + /// + /// Internal member storing the background value + /// + private int _background = 0; + /// + /// Internal member storing the foreground value + /// + private int _foreground = 0; + /// + /// Internal member storing the offset in the #STRINGS file of the Font param of the "text/site properties" object of the sitemap contents + /// + private int _fontOffset = 0; + /// + /// Internal member storing the offset in the #STRINGS file of the FrameName param of the "text/site properties" object of the sitemap contents + /// + private int _frameNameOffset = 0; + /// + /// Internal member storing the offset in the #STRINGS file of the WindowName param of the "text/site properties" object of the sitemap contents + /// + private int _windowNameOffset = 0; + /// + /// Internal member storing the number of merged files + /// + private int _numberOfMergedFiles = 0; + /// + /// Internal member storing the offset in the #STRINGS file of the merged file names + /// + private ArrayList _mergedFileOffsets = new ArrayList(); + /// + /// Internal member storing the associated chmfile object + /// + private CHMFile _associatedFile = null; + + /// + /// Constructor of the class + /// + /// binary file data of the #IDXHDR file + /// associated CHMFile instance + public CHMIdxhdr(byte[] binaryFileData, CHMFile associatedFile) + { + _binaryFileData = binaryFileData; + _associatedFile = associatedFile; + DecodeData(); + } + + /// + /// Decodes the binary file data and fills the internal properties + /// + /// true if succeeded + private bool DecodeData() + { + bool bRet = true; + + MemoryStream memStream = new MemoryStream(_binaryFileData); + BinaryReader binReader = new BinaryReader(memStream); + + int nTemp = 0; + + // 4 character T#SM + binReader.ReadBytes(4); + // unknown timestamp DWORD + nTemp = binReader.ReadInt32(); + + // unknown 1 + nTemp = binReader.ReadInt32(); + + // number of topic nodes including the contents & index files + _numberOfTopicNodes = binReader.ReadInt32(); + + // unknown DWORD + nTemp = binReader.ReadInt32(); + + // offset in the strings file + _imageListOffset = binReader.ReadInt32(); + if( _imageListOffset == 0) + _imageListOffset = -1; // 0/-1 = none + + // unknown DWORD + nTemp = binReader.ReadInt32(); + + // 1 if the value of the ImageType param of the "text/site properties" object of the sitemap contents is "Folder". + nTemp = binReader.ReadInt32(); + _imageTypeFolder = (nTemp == 1); + + // offset in the strings file + _background = binReader.ReadInt32(); + // offset in the strings file + _foreground = binReader.ReadInt32(); + + // offset in the strings file + _fontOffset = binReader.ReadInt32(); + + // window styles DWORD + nTemp = binReader.ReadInt32(); + // window styles DWORD + nTemp = binReader.ReadInt32(); + + // unknown DWORD + nTemp = binReader.ReadInt32(); + + // offset in the strings file + _frameNameOffset = binReader.ReadInt32(); + if( _frameNameOffset == 0) + _frameNameOffset = -1; // 0/-1 = none + // offset in the strings file + _windowNameOffset = binReader.ReadInt32(); + if( _windowNameOffset == 0) + _windowNameOffset = -1; // 0/-1 = none + + // informations types DWORD + nTemp = binReader.ReadInt32(); + + // unknown DWORD + nTemp = binReader.ReadInt32(); + + // number of merged files in the merged file list DWORD + _numberOfMergedFiles = binReader.ReadInt32(); + + nTemp = binReader.ReadInt32(); + + for(int i = 0; i < _numberOfMergedFiles; i++) + { + // DWORD offset value of merged file + nTemp = binReader.ReadInt32(); + + if(nTemp > 0) + _mergedFileOffsets.Add(nTemp); + } + + return bRet; + } + + /// + /// Gets the number of topic nodes including the contents and index files + /// + public int NumberOfTopicNodes + { + get { return _numberOfTopicNodes; } + } + + /// + /// Gets the offset in the #STRINGS file of the ImageList + /// param of the "text/site properties" object of the sitemap contents + /// + public int ImageListOffset + { + get { return _imageListOffset; } + } + + /// + /// True if the value of the ImageType param of the + /// "text/site properties" object of the sitemap contents is "Folder". + /// + /// If this is set to true, the help will display folders instead of books + public bool ImageTypeFolder + { + get { return _imageTypeFolder; } + } + + /// + /// Gets the background setting + /// + public int Background + { + get { return _background; } + } + + /// + /// Gets the foreground setting + /// + public int Foreground + { + get { return _foreground; } + } + + /// + /// Gets the offset in the #STRINGS file of the Font + /// param of the "text/site properties" object of the sitemap contents + /// + public int WindowNameOffset + { + get { return _fontOffset; } + } + + /// + /// Gets the offset in the #STRINGS file of the FrameName + /// param of the "text/site properties" object of the sitemap contents + /// + public int FrameNameOffset + { + get { return _frameNameOffset; } + } + + /// + /// Gets the offset in the #STRINGS file of the WindowName + /// param of the "text/site properties" object of the sitemap contents + /// + public int FontOffset + { + get { return _windowNameOffset; } + } + + /// + /// Gets an array list of offset numbers in the #STRINGS file of the + /// merged file names. + /// + public ArrayList MergedFileOffsets + { + get { return _mergedFileOffsets; } + } + + /// + /// Implement IDisposable. + /// + public void Dispose() + { + Dispose(true); + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SupressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// disposing flag + private void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if(!this.disposed) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if(disposing) + { + // Dispose managed resources. + _binaryFileData = null; + _mergedFileOffsets = null; + } + + + } + disposed = true; + } + } +} diff --git a/irc/TechBot/CHMLibrary/CHMDecoding/CHMStrings.cs b/irc/TechBot/CHMLibrary/CHMDecoding/CHMStrings.cs new file mode 100644 index 00000000000..5942d4e8cfe --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMDecoding/CHMStrings.cs @@ -0,0 +1,256 @@ +using System; +using System.IO; +using System.Collections; +using System.Collections.Specialized; + +namespace HtmlHelp.ChmDecoding +{ + /// + /// The class CHMStrings implements a string collection read from the #STRINGS file + /// + internal sealed class CHMStrings : IDisposable + { + /// + /// Constant specifying the size of the string blocks + /// + private const int STRING_BLOCK_SIZE = 4096; + /// + /// Internal flag specifying if the object is going to be disposed + /// + private bool disposed = false; + /// + /// Internal member storing the binary file data + /// + private byte[] _binaryFileData = null; + /// + /// Internal member storing the string dictionary + /// + private Hashtable _stringDictionary = new Hashtable(); + /// + /// Internal member storing the associated chmfile object + /// + private CHMFile _associatedFile = null; + + /// + /// Constructor of the class + /// + /// binary file data of the #STRINGS file + /// associated chm file + public CHMStrings(byte[] binaryFileData, CHMFile associatedFile) + { + _binaryFileData = binaryFileData; + _associatedFile = associatedFile; + DecodeData(); + } + + + /// + /// Standard constructor + /// + internal CHMStrings() + { + } + + #region Data dumping + /// + /// Dump the class data to a binary writer + /// + /// writer to write the data + internal void Dump(ref BinaryWriter writer) + { + writer.Write( _stringDictionary.Count ); + + if (_stringDictionary.Count != 0) + { + IDictionaryEnumerator iDictionaryEnumerator = _stringDictionary.GetEnumerator(); + while (iDictionaryEnumerator.MoveNext()) + { + DictionaryEntry dictionaryEntry = (DictionaryEntry)iDictionaryEnumerator.Current; + writer.Write( Int32.Parse(dictionaryEntry.Key.ToString()) ); + writer.Write( dictionaryEntry.Value.ToString() ); + } + } + } + + /// + /// Reads the object data from a dump store + /// + /// reader to read the data + internal void ReadDump(ref BinaryReader reader) + { + int nCnt = reader.ReadInt32(); + + for(int i=0; i + /// Sets the associated CHMFile instance + /// + /// instance to set + internal void SetCHMFile(CHMFile associatedFile) + { + _associatedFile = associatedFile; + } + #endregion + + /// + /// Decodes the binary file data and fills the internal properties + /// + /// true if succeeded + private bool DecodeData() + { + bool bRet = true; + + MemoryStream memStream = new MemoryStream(_binaryFileData); + BinaryReader binReader = new BinaryReader(memStream); + + //binReader.ReadByte(); // file starts with a NULL character for 0-based offset indexing + + int nStringOffset = 0; + int nSubsetOffset = 0; + + while( (memStream.Position < memStream.Length) && (bRet) ) + { + nStringOffset = (int)memStream.Position; + byte [] stringBlock = binReader.ReadBytes(STRING_BLOCK_SIZE); + bRet &= DecodeBlock(stringBlock, ref nStringOffset, ref nSubsetOffset); + } + + return bRet; + } + + /// + /// Decodes a string block + /// + /// byte array which represents the string block + /// current string offset number + /// reference to a subset variable + /// true if succeeded + /// If a string crosses the end of a block then it will be cut off + /// without a NT and repeated in full, with a NT, at the start of the next block. + /// For eg "To customize the appearance of a contents file" might become + /// "To customize the (block ending)To customize the appearance of a contents file" + /// when there are 17 bytes left at the end of the block. + private bool DecodeBlock( byte[] stringBlock, ref int nStringOffset, ref int nSubsetOffset) + { + bool bRet = true; + + MemoryStream memStream = new MemoryStream(stringBlock); + BinaryReader binReader = new BinaryReader(memStream); + + while( (memStream.Position < memStream.Length) && (bRet) ) + { + bool bFoundTerminator = false; + + int nCurOffset = nStringOffset + (int)memStream.Position; + + string sTemp = BinaryReaderHelp.ExtractString(ref binReader, ref bFoundTerminator, 0, true, _associatedFile.TextEncoding); + + if(nSubsetOffset != 0) + { + _stringDictionary[nSubsetOffset.ToString()] = sTemp.ToString(); + } + else + { + _stringDictionary[nCurOffset.ToString()] = sTemp.ToString(); + } + + if( bFoundTerminator ) + { + nSubsetOffset = 0; + } + else + { + nSubsetOffset = nCurOffset; + } + } + + return bRet; + } + + /// + /// Indexer which returns the string at a given offset + /// + public string this[int offset] + { + get + { + if(offset == -1) + return String.Empty; + + string sTemp = (string)_stringDictionary[ offset.ToString() ]; + + if(sTemp == null) + return String.Empty; + + return sTemp; + } + } + + /// + /// Indexer which returns the string at a given offset + /// + public string this[string offset] + { + get + { + if(offset == "-1") + return String.Empty; + + string sTemp = (string)_stringDictionary[ offset ]; + + if(sTemp == null) + return String.Empty; + + return sTemp; + } + } + + /// + /// Implement IDisposable. + /// + public void Dispose() + { + Dispose(true); + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SupressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// disposing flag + private void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if(!this.disposed) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if(disposing) + { + // Dispose managed resources. + _binaryFileData = null; + _stringDictionary = null; + } + } + disposed = true; + } + } +} diff --git a/irc/TechBot/CHMLibrary/CHMDecoding/CHMSystem.cs b/irc/TechBot/CHMLibrary/CHMDecoding/CHMSystem.cs new file mode 100644 index 00000000000..042e243f5e1 --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMDecoding/CHMSystem.cs @@ -0,0 +1,821 @@ +using System; +using System.Collections; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Globalization; + +namespace HtmlHelp.ChmDecoding +{ + /// + /// The class CHMSystem reads the #SYSTEM file of the chm and stores its settings + /// + internal sealed class CHMSystem : IDisposable + { + /// + /// Internal flag specifying if the object is going to be disposed + /// + private bool disposed = false; + /// + /// Internal member storing the binary file data + /// + private byte[] _binaryFileData = null; + /// + /// Internal member storing the file version + /// + private int _fileVersion = 0; + /// + /// Internal member storing the contents file path + /// + private string _contentsFile = ""; + /// + /// Internal member storing the index file path + /// + private string _indexFile = ""; + /// + /// Internal member storing the default help topic + /// + private string _defaultTopic = ""; + /// + /// Internal member storing the help-window title + /// + private string _title = ""; + /// + /// Internal flag if dbcs is on + /// + private bool _dbcs = false; + /// + /// Internal flag if fulltext search is enabled + /// + private bool _fullTextSearch = false; + /// + /// Internal flag if KLinks are in the file + /// + private bool _hasKLinks = false; + /// + /// Internal flag if ALinks are in the file + /// + private bool _hasALinks = false; + /// + /// Internal member storing the name of the default window + /// + private string _defaultWindow = ""; + /// + /// Internal member storing the filename of the compiled file + /// + private string _compileFile = ""; + /// + /// Internal flag storing the offset value of the binary index + /// + private uint _binaryIndexURLTableID = 0; + /// + /// Inernal member storing the compiler version this file was compiled + /// + private string _compilerVersion = ""; + /// + /// Internal flag storing the offset value of the binary TOC + /// + private uint _binaryTOCURLTableID = 0; + /// + /// Internal member storing the associated chmfile object + /// + private CHMFile _associatedFile = null; + /// + /// Internal member storing the default fontface, size, charset + /// + private string _defaultFont = ""; + /// + /// Internal member storing the culture info of the file + /// + private CultureInfo _culture; + + /// + /// Constructor of the class + /// + /// binary file data of the #SYSTEM file + /// associated chm file + public CHMSystem(byte[] binaryFileData, CHMFile associatedFile) + { + _binaryFileData = binaryFileData; + _associatedFile = associatedFile; + DecodeData(); + + if(_culture == null) + { + // Set the text encoder of the chm file to the read charset/codepage + _associatedFile.TextEncoding = Encoding.GetEncoding( this.CodePage ); + } + } + + /// + /// Decodes the binary file data and fills the internal properties + /// + /// true if succeeded + private bool DecodeData() + { + bool bRet = true; + + MemoryStream memStream = new MemoryStream(_binaryFileData); + BinaryReader binReader = new BinaryReader(memStream); + + // First entry = DWORD for version number + _fileVersion = (int) binReader.ReadInt32(); + + while( (memStream.Position < memStream.Length) && (bRet) ) + { + bRet &= DecodeEntry(ref binReader); + } + + return bRet; + } + + /// + /// Decodes an #system file entry + /// + /// binary reader reference + /// true if succeeded + private bool DecodeEntry(ref BinaryReader binReader) + { + bool bRet = true; + + int code = (int) binReader.ReadInt16(); // entry code, WORD + int length = (int) binReader.ReadInt16(); // length of entry + + switch(code) + { + case 0: + { + _contentsFile = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding); + };break; + case 1: + { + _indexFile = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding); + };break; + case 2: + { + _defaultTopic = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding); + };break; + case 3: + { + _title = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding); + };break; + case 4: + { + int nTemp = 0; + nTemp = binReader.ReadInt32(); // read DWORD LCID + _culture = new CultureInfo(nTemp); + + if(_culture != null) + _associatedFile.TextEncoding = Encoding.GetEncoding(_culture.TextInfo.ANSICodePage); + + nTemp = binReader.ReadInt32(); // read DWORD DBCS + _dbcs = (nTemp == 1); + + nTemp = binReader.ReadInt32(); // read DWORD Fulltext search + _fullTextSearch = (nTemp == 1); + + nTemp = binReader.ReadInt32(); // read DWORD has klinks + _hasKLinks = (nTemp != 0); + + nTemp = binReader.ReadInt32(); // read DWORD has alinks + _hasALinks = (nTemp != 0); + + // read the rest of code 4 (not important for us) + byte[] temp = new byte[length-(5*4)]; + temp = binReader.ReadBytes(length-(5*4)); + };break; + case 5: + { + _defaultWindow = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding); + };break; + case 6: + { + _compileFile = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding); + };break; + case 7: + { + if(_fileVersion > 2) + { + _binaryIndexURLTableID = (uint) binReader.ReadInt32(); + } + else + { + byte[] read = binReader.ReadBytes(length); + int i=read.Length; + } + };break; + case 8: + { + // abbreviation (not interresting for us) + byte[] read = binReader.ReadBytes(length); + int i=read.Length; + };break; + case 9: + { + _compilerVersion = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding); + };break; + case 10: + { + // timestamp of the file (not interresting for us) + byte[] read = binReader.ReadBytes(length); + int i=read.Length; + };break; + case 11: + { + if(_fileVersion > 2) + { + _binaryTOCURLTableID = (uint) binReader.ReadInt32(); + } + else + { + byte[] read = binReader.ReadBytes(length); + int i=read.Length; + } + };break; + case 12: + { + // number of information bytes + byte[] read = binReader.ReadBytes(length); + int i=read.Length; + };break; + case 13: + { + // copy of file #idxhdr + byte[] read = binReader.ReadBytes(length); + int i=read.Length; + };break; + case 14: + { + // custom tabs for HH viewer + byte[] read = binReader.ReadBytes(length); + int i=read.Length; + };break; + case 15: + { + // a checksum + byte[] read = binReader.ReadBytes(length); + int i=read.Length; + };break; + case 16: + { + // Default Font=string,number,number + // The string is the name of the font, the first number is the + // point size & the last number is the character set used by the font. + // For acceptable values see *_CHARSET defines in wingdi.h from the + // Windows SDK or the same file in MinGW or Wine. + // Most of the time you will only want to use 0, which is the value for ANSI, + // which is the subset of ASCII used by Windows. + _defaultFont = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding); + + };break; + default: + { + byte[] temp = new byte[length]; + temp = binReader.ReadBytes(length); + //bRet = false; + int i=temp.Length; + };break; + } + + return bRet; + } + + /// + /// Reads all HHC files and checks which one has the global object tag. + /// This hhc file will be returned as master hhc + /// + /// list of topics containing the extension hhc + /// true if the arraylist contains topic items + /// the filename of the found master toc + private string GetMasterHHC(ArrayList hhcTopics, bool TopicItemArrayList) + { + string sRet = ""; + + if( (hhcTopics!=null) && (hhcTopics.Count > 0) ) + { + if( TopicItemArrayList ) + { + if(hhcTopics.Count == 1) + { + sRet = ((TopicEntry)hhcTopics[0]).Locale; + } + else + { + foreach(TopicEntry curEntry in hhcTopics) + { + CHMStream.CHMStream iw=null; + MemoryStream fileObject=null; + + if( _associatedFile.CurrentStorageWrapper == null) + { + iw=new CHMStream.CHMStream(); + iw.OpenCHM(_associatedFile.ChmFilePath); + } + else + { + iw = _associatedFile.CurrentStorageWrapper; + } + + fileObject = iw.OpenStream(curEntry.Locale); + if( fileObject != null) + { + string fileString =_associatedFile.TextEncoding.GetString(fileObject.ToArray(),0,(int)fileObject.Length); + fileObject.Close(); + + if( HHCParser.HasGlobalObjectTag(fileString, _associatedFile) ) + { + sRet = curEntry.Locale; + break; + } + } + } + } + } + else + { + if(hhcTopics.Count == 1) + { + sRet = ((string)hhcTopics[0]); + } + else + { + foreach(string curEntry in hhcTopics) + { + CHMStream.CHMStream iw=null; + MemoryStream fileObject=null; + + if( _associatedFile.CurrentStorageWrapper == null) + { + iw=new CHMStream.CHMStream(); + iw.OpenCHM(_associatedFile.ChmFilePath); + } + else + { + iw = _associatedFile.CurrentStorageWrapper; + } + + fileObject = iw.OpenStream(curEntry); + if( fileObject != null) + { + string fileString =_associatedFile.TextEncoding.GetString(fileObject.ToArray(),0,(int)fileObject.Length); + fileObject.Close(); + + if( HHCParser.HasGlobalObjectTag(fileString, _associatedFile) ) + { + sRet = curEntry; + break; + } + } + } + } + } + } + + return sRet; + } + + /// + /// Gets the file version of the chm file. + /// 2 for Compatibility=1.0, 3 for Compatibility=1.1 + /// + public int FileVersion + { + get { return _fileVersion; } + } + + /// + /// Gets the contents file name + /// + public string ContentsFile + { + get + { + if( BinaryTOC ) // if the file contains a binary TOC + { + // make sure the CHMFile instance exists and has loaded the file #URLTBL + if( (_associatedFile != null) && (_associatedFile.UrltblFile != null ) ) + { + // Get an url-table entry by its unique id + UrlTableEntry entry = _associatedFile.UrltblFile.GetByUniqueID( this.BinaryTOCURLTableID ); + if(entry != null) + { + // entry found, return the url ( = filename ) + return entry.URL; + } + } + } + else + { + if(_contentsFile.Length <= 0) + { + string sCheck = "Table of Contents.hhc"; // default HHP contents filename + + if( (_associatedFile != null) && (_associatedFile.TopicsFile != null ) ) + { + TopicEntry te = _associatedFile.TopicsFile.GetByLocale( sCheck ); + + if( te == null) + { + sCheck = "toc.hhc"; // default HHP contents filename + + te = _associatedFile.TopicsFile.GetByLocale( sCheck ); + + if( te == null) + { + sCheck = CompileFile + ".hhc"; + + te = _associatedFile.TopicsFile.GetByLocale( sCheck ); + + if( te == null) + { + ArrayList arrExt = _associatedFile.TopicsFile.GetByExtension("hhc"); + + if( arrExt == null ) + { + arrExt = _associatedFile.EnumFilesByExtension("hhc"); + + if( arrExt == null ) + { + Debug.WriteLine("CHMSystem.ContentsFile - Failed, contents file not found !"); + } + else + { + if(arrExt.Count > 1) + { + sCheck = GetMasterHHC(arrExt, false); + _contentsFile = sCheck; + } + else + { + _contentsFile = ((string)arrExt[0]); + sCheck = _contentsFile; + } + } + } + else + { + if(arrExt.Count > 1) + { + sCheck = GetMasterHHC(arrExt, true); + _contentsFile = sCheck; + } + else + { + _contentsFile = ((TopicEntry)arrExt[0]).Locale; + sCheck = _contentsFile; + } + } + } + else + { + _contentsFile = sCheck; + } + } + else + { + _contentsFile = sCheck; + } + } + else + { + _contentsFile = sCheck; + } + } + + return sCheck; + } + } + + return _contentsFile; + } + } + + /// + /// Gets the index file name + /// + public string IndexFile + { + get + { + if( BinaryIndex ) // if the file contains a binary index + { + // make sure the CHMFile instance exists and has loaded the file #URLTBL + if( (_associatedFile != null) && (_associatedFile.UrltblFile != null ) ) + { + // Get an url-table entry by its unique id + UrlTableEntry entry = _associatedFile.UrltblFile.GetByUniqueID( this.BinaryIndexURLTableID ); + if(entry != null) + { + // entry found, return the url ( = filename ) + return entry.URL; + } + } + } + else + { + if(_indexFile.Length <= 0) + { + string sCheck = "Index.hhk"; // default HHP index filename + + if( (_associatedFile != null) && (_associatedFile.TopicsFile != null ) ) + { + TopicEntry te = _associatedFile.TopicsFile.GetByLocale( sCheck ); + + if( te == null) + { + sCheck = CompileFile + ".hhk"; + + te = _associatedFile.TopicsFile.GetByLocale( sCheck ); + + if( te == null) + { + ArrayList arrExt = _associatedFile.TopicsFile.GetByExtension("hhk"); + + if( arrExt == null ) + { + Debug.WriteLine("CHMSystem.IndexFile - Failed, index file not found !"); + } + else + { + _indexFile = ((TopicEntry)arrExt[0]).Locale; + sCheck = _indexFile; + } + } + else + { + _indexFile = sCheck; + } + } + else + { + _indexFile = sCheck; + } + } + + return sCheck; + } + } + return _indexFile; + } + } + + /// + /// Sets the default topic of this file + /// + /// new local value of the topic + internal void SetDefaultTopic(string local) + { + _defaultTopic = local; + } + + /// + /// Gets the default help topic + /// + public string DefaultTopic + { + get { return _defaultTopic; } + } + + /// + /// Gets the title of the help window + /// + public string Title + { + get { return _title; } + } + + /// + /// Gets the flag if DBCS is in use + /// + public bool DBCS + { + get { return _dbcs; } + } + + /// + /// Gets the flag if full-text-search is available + /// + public bool FullTextSearch + { + get { return _fullTextSearch; } + } + + /// + /// Gets the flag if the file has ALinks + /// + public bool HasALinks + { + get { return _hasALinks; } + } + + /// + /// Gets the flag if the file has KLinks + /// + public bool HasKLinks + { + get { return _hasKLinks; } + } + + /// + /// Gets the default window name + /// + public string DefaultWindow + { + get { return _defaultWindow; } + } + + /// + /// Gets the file name of the compile file + /// + public string CompileFile + { + get { return _compileFile; } + } + + /// + /// Gets the id of the binary index in the url table + /// + public uint BinaryIndexURLTableID + { + get { return _binaryIndexURLTableID; } + } + + /// + /// Gets the flag if the chm has a binary index file + /// + public bool BinaryIndex + { + get { return (_binaryIndexURLTableID>0); } + } + + /// + /// Gets the flag if the chm has a binary index file + /// + public string CompilerVersion + { + get { return _compilerVersion; } + } + + /// + /// Gets the id of the binary toc in the url table + /// + public uint BinaryTOCURLTableID + { + get { return _binaryTOCURLTableID; } + } + + /// + /// Gets the flag if the chm has a binary toc file + /// + public bool BinaryTOC + { + get { return (_binaryTOCURLTableID>0); } + } + + /// + /// Gets the font face of the read font property. + /// Empty string for default font. + /// + public string FontFace + { + get + { + if( _defaultFont.Length > 0) + { + string [] fontSplit = _defaultFont.Split( new char[]{','}); + if(fontSplit.Length > 0) + return fontSplit[0].Trim(); + } + + return ""; + } + } + + /// + /// Gets the font size of the read font property. + /// 0 for default font size + /// + public double FontSize + { + get + { + if( _defaultFont.Length > 0) + { + string [] fontSplit = _defaultFont.Split( new char[]{','}); + if(fontSplit.Length > 1) + return double.Parse(fontSplit[1].Trim()); + } + + return 0.0; + } + } + + /// + /// Gets the character set of the read font property + /// 1 for default + /// + public int CharacterSet + { + get + { + if( _defaultFont.Length > 0) + { + string [] fontSplit = _defaultFont.Split( new char[]{','}); + if(fontSplit.Length > 2) + return Int32.Parse(fontSplit[2].Trim()); + } + + return 0; + } + } + + /// + /// Gets the codepage depending on the read font property + /// + public int CodePage + { + get + { + // if we've read a LCID from the system file + // ignore the font-property settings and return + // the codepage generated from the culture info + if(_culture != null) + { + return _culture.TextInfo.ANSICodePage; + } + + int nRet = 1252; // default codepage windows-1252 + + int nCSet = CharacterSet; + + switch(nCSet) + { + case 0x00: nRet = 1252;break; // ANSI_CHARSET + case 0xCC: nRet = 1251;break; // RUSSIAN_CHARSET + case 0xEE: nRet = 1250;break; // EE_CHARSET + case 0xA1: nRet = 1253;break; // GREEK_CHARSET + case 0xA2: nRet = 1254;break; // TURKISH_CHARSET + case 0xBA: nRet = 1257;break; // BALTIC_CHARSET + case 0xB1: nRet = 1255;break; // HEBREW_CHARSET + case 0xB2: nRet = 1256;break; // ARABIC_CHARSET + case 0x80: nRet = 932;break; // SHIFTJIS_CHARSET + case 0x81: nRet = 949;break; // HANGEUL_CHARSET + case 0x86: nRet = 936;break; // GB2313_CHARSET + case 0x88: nRet = 950;break; // CHINESEBIG5_CHARSET + } + + return nRet; + } + } + + /// + /// Gets the assiciated culture info + /// + public CultureInfo Culture + { + get { return _culture; } + } + + /// + /// Implement IDisposable. + /// + public void Dispose() + { + Dispose(true); + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SupressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// disposing flag + private void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if(!this.disposed) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if(disposing) + { + // Dispose managed resources. + _binaryFileData = null; + } + + + } + disposed = true; + } + } +} diff --git a/irc/TechBot/CHMLibrary/CHMDecoding/CHMTocidx.cs b/irc/TechBot/CHMLibrary/CHMDecoding/CHMTocidx.cs new file mode 100644 index 00000000000..d6ca1aa02c1 --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMDecoding/CHMTocidx.cs @@ -0,0 +1,288 @@ +using System; +using System.IO; +using System.Collections; + +namespace HtmlHelp.ChmDecoding +{ + /// + /// The class CHMTocidx implements functions to decode the #TOCIDX internal file. + /// + internal sealed class CHMTocidx : IDisposable + { + /// + /// Constant specifying the size of the data blocks + /// + private const int BLOCK_SIZE = 0x1000; + /// + /// Internal flag specifying if the object is going to be disposed + /// + private bool disposed = false; + /// + /// Internal member storing the binary file data + /// + private byte[] _binaryFileData = null; + /// + /// Internal memebr storing the offset to the 20/28 byte structs + /// + private int _offset2028 = 0; + /// + /// Internal member storing the offset to the 16 byte structs + /// + private int _offset16structs = 0; + /// + /// Internal member storing the number of 16 byte structs + /// + private int _numberOf16structs = 0; + /// + /// Internal member storing the offset to the topic list + /// + private int _offsetOftopics = 0; + /// + /// Internal member storing the toc + /// + private ArrayList _toc = new ArrayList(); + /// + /// Internal member for offset seeking + /// + private Hashtable _offsetTable = new Hashtable(); + /// + /// Internal member storing the associated chmfile object + /// + private CHMFile _associatedFile = null; + + /// + /// Constructor of the class + /// + /// binary file data of the #TOCIDX file + /// associated chm file + public CHMTocidx(byte[] binaryFileData, CHMFile associatedFile) + { + _binaryFileData = binaryFileData; + _associatedFile = associatedFile; + DecodeData(); + + // clear internal binary data after extraction + _binaryFileData = null; + } + + /// + /// Decodes the binary file data and fills the internal properties + /// + /// true if succeeded + private bool DecodeData() + { + _toc = new ArrayList(); + _offsetTable = new Hashtable(); + + bool bRet = true; + + MemoryStream memStream = new MemoryStream(_binaryFileData); + BinaryReader binReader = new BinaryReader(memStream); + + int nCurOffset = 0; + + _offset2028 = binReader.ReadInt32(); + _offset16structs = binReader.ReadInt32(); + _numberOf16structs = binReader.ReadInt32(); + _offsetOftopics = binReader.ReadInt32(); + + binReader.BaseStream.Seek( _offset2028, SeekOrigin.Begin ); + + if( RecursivelyBuildTree(ref binReader, _offset2028, _toc, null) ) + { + binReader.BaseStream.Seek( _offset16structs, SeekOrigin.Begin ); + nCurOffset = (int)binReader.BaseStream.Position; + + for(int i=0; i < _numberOf16structs; i++) + { + int tocOffset = binReader.ReadInt32(); + int sqNr = binReader.ReadInt32(); + int topOffset = binReader.ReadInt32(); + int hhctopicIdx = binReader.ReadInt32(); + + nCurOffset = (int)binReader.BaseStream.Position; + + int topicIdx = -1; + // if the topic offset is within the range of the stream + // and is >= the offset of the first topic dword + if((topOffset < (binReader.BaseStream.Length - 4)) && (topOffset >= _offsetOftopics)) + { + // read the index of the topic for this item + binReader.BaseStream.Seek( topOffset, SeekOrigin.Begin); + topicIdx = binReader.ReadInt32(); + binReader.BaseStream.Seek( nCurOffset, SeekOrigin.Begin); + + + TOCItem item = (TOCItem)_offsetTable[tocOffset.ToString()]; + if( item != null) + { + if(( topicIdx < _associatedFile.TopicsFile.TopicTable.Count)&&(topicIdx>=0)) + { + TopicEntry te = (TopicEntry) (_associatedFile.TopicsFile.TopicTable[topicIdx]); + if( (te != null) && (item.TopicOffset < 0) ) + { + item.TopicOffset = te.EntryOffset; + } + + } + } + } + } + } + + return bRet; + } + + /// + /// Recursively reads the binary toc tree from the file + /// + /// reference to binary reader + /// offset of the first node in the current level + /// arraylist of TOCItems for the current level + /// parent item for the item + /// Returns true if succeeded + private bool RecursivelyBuildTree(ref BinaryReader binReader, int NodeOffset, ArrayList level, TOCItem parentItem) + { + bool bRet = true; + int nextOffset=0; + + int nReadOffset = (int)binReader.BaseStream.Position; + + binReader.BaseStream.Seek(NodeOffset, SeekOrigin.Begin); + + do + { + int nCurOffset = (int)binReader.BaseStream.Position; + + int unkn1 = binReader.ReadInt16(); // unknown + int unkn2 = binReader.ReadInt16(); // unknown + + int flag = binReader.ReadInt32(); + + int nFolderAdd = 0; + + if((_associatedFile != null) && (_associatedFile.ImageTypeFolder)) + { + // get the value which should be added, to display folders instead of books + if(HtmlHelpSystem.UseHH2TreePics) + nFolderAdd = 8; + else + nFolderAdd = 4; + } + + int nFolderImgIdx = (HtmlHelpSystem.UseHH2TreePics ? (TOCItem.STD_FOLDER_HH2+nFolderAdd) : (TOCItem.STD_FOLDER_HH1+nFolderAdd)); + int nFileImgIdx = (HtmlHelpSystem.UseHH2TreePics ? TOCItem.STD_FILE_HH2 : TOCItem.STD_FILE_HH1); + + int stdImage = ((flag & 0x4)!=0) ? nFolderImgIdx : nFileImgIdx; + + int stringOffset = binReader.ReadInt32(); + + int ParentOffset = binReader.ReadInt32(); + nextOffset = binReader.ReadInt32(); + + int firstChildOffset = 0; + int unkn3=0; + + if( (flag&0x4)!=0 ) + { + firstChildOffset = binReader.ReadInt32(); + unkn3 = binReader.ReadInt32(); // unknown + } + + TOCItem newItem = new TOCItem(); + newItem.ImageIndex = stdImage; + newItem.Offset = nCurOffset; + newItem.OffsetNext = nextOffset; + newItem.AssociatedFile = _associatedFile; + newItem.TocMode = DataMode.Binary; + newItem.Parent = parentItem; + + if( (flag&0x08) == 0) + { + // toc item doesn't have a local value (=> stringOffset = offset of strings file) + newItem.Name = _associatedFile.StringsFile[stringOffset]; + } + else + { + // this item has a topic entry (=> stringOffset = index of topic entry) + if((stringOffset < _associatedFile.TopicsFile.TopicTable.Count) && (stringOffset >= 0)) + { + TopicEntry te = (TopicEntry) (_associatedFile.TopicsFile.TopicTable[stringOffset]); + if(te != null) + { + newItem.TopicOffset = te.EntryOffset; + } + } + } + + _offsetTable[nCurOffset.ToString()] = newItem; + + // if this item has children (firstChildOffset > 0) + if( firstChildOffset > 0) + { + bRet &= RecursivelyBuildTree(ref binReader, firstChildOffset, newItem.Children, newItem); + } + + level.Add( newItem ); + + if(nCurOffset != nextOffset) + binReader.BaseStream.Seek(nextOffset, SeekOrigin.Begin); + + }while(nextOffset != 0); + + binReader.BaseStream.Seek(nReadOffset, SeekOrigin.Begin); + + return bRet; + } + + /// + /// Gets the internal read toc + /// + internal ArrayList TOC + { + get { return _toc; } + } + + /// + /// Implement IDisposable. + /// + public void Dispose() + { + Dispose(true); + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SupressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// disposing flag + private void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if(!this.disposed) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if(disposing) + { + // Dispose managed resources. + _binaryFileData = null; + _toc = null; + _offsetTable = null; + } + } + disposed = true; + } + } +} diff --git a/irc/TechBot/CHMLibrary/CHMDecoding/CHMTopics.cs b/irc/TechBot/CHMLibrary/CHMDecoding/CHMTopics.cs new file mode 100644 index 00000000000..ce1451b53ef --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMDecoding/CHMTopics.cs @@ -0,0 +1,235 @@ +using System; +using System.IO; +using System.Collections; +using System.Collections.Specialized; + +namespace HtmlHelp.ChmDecoding +{ + /// + /// The class CHMTopics implements functionality to decode the #TOPICS internal file + /// + internal sealed class CHMTopics : IDisposable + { + /// + /// Internal flag specifying if the object is going to be disposed + /// + private bool disposed = false; + /// + /// Internal member storing the binary file data + /// + private byte[] _binaryFileData = null; + /// + /// Internal member storing the associated chmfile object + /// + private CHMFile _associatedFile = null; + /// + /// Internal member storing the topic list + /// + private ArrayList _topicTable = new ArrayList(); + + /// + /// Constructor of the class + /// + /// binary file data of the #TOPICS file + /// associated chm file + public CHMTopics(byte[] binaryFileData, CHMFile associatedFile) + { + _binaryFileData = binaryFileData; + _associatedFile = associatedFile; + DecodeData(); + + // clear internal binary data after extraction + _binaryFileData = null; + } + + + /// + /// Standard constructor + /// + internal CHMTopics() + { + } + + #region Data dumping + /// + /// Dump the class data to a binary writer + /// + /// writer to write the data + internal void Dump(ref BinaryWriter writer) + { + writer.Write( _topicTable.Count ); + foreach(TopicEntry curItem in _topicTable) + { + curItem.Dump(ref writer); + } + } + + /// + /// Reads the object data from a dump store + /// + /// reader to read the data + internal void ReadDump(ref BinaryReader reader) + { + int i=0; + int nCnt = reader.ReadInt32(); + + for(i=0; i + /// Sets the associated CHMFile instance + /// + /// instance to set + internal void SetCHMFile(CHMFile associatedFile) + { + _associatedFile = associatedFile; + + foreach(TopicEntry curEntry in _topicTable) + { + curEntry.SetCHMFile(associatedFile); + } + } + #endregion + + /// + /// Decodes the binary file data and fills the internal properties + /// + /// true if succeeded + private bool DecodeData() + { + bool bRet = true; + + MemoryStream memStream = new MemoryStream(_binaryFileData); + BinaryReader binReader = new BinaryReader(memStream); + + int nCurOffset = 0; + + while( (memStream.Position < memStream.Length) && (bRet) ) + { + int entryOffset = nCurOffset; + int tocIdx = binReader.ReadInt32(); + int titleOffset = binReader.ReadInt32(); + int urltablOffset = binReader.ReadInt32(); + int visibilityMode = binReader.ReadInt16(); + int unknownMode = binReader.ReadInt16(); + + TopicEntry newEntry = new TopicEntry(entryOffset, tocIdx, titleOffset, urltablOffset, visibilityMode, unknownMode, _associatedFile); + _topicTable.Add( newEntry ); + + nCurOffset = (int)memStream.Position; + } + + return bRet; + } + + /// + /// Gets the arraylist containing all topic entries. + /// + public ArrayList TopicTable + { + get + { + return _topicTable; + } + } + + /// + /// Gets the topic entry of a given offset + /// + public TopicEntry this[int offset] + { + get + { + foreach(TopicEntry curEntry in _topicTable) + if(curEntry.EntryOffset == offset) + return curEntry; + + return null; + } + } + + /// + /// Searches a topic by the locale name + /// + /// locale name to search + /// The topicentry instance if found, otherwise null + public TopicEntry GetByLocale(string locale) + { + foreach(TopicEntry curEntry in TopicTable) + { + if(curEntry.Locale.ToLower() == locale.ToLower()) + return curEntry; + } + + return null; + } + + /// + /// Searches the topics for all files with a given file extension + /// + /// extension to search + /// An arraylist of TopicEntry instances or null if no topic was found + public ArrayList GetByExtension(string fileExtension) + { + ArrayList arrRet = new ArrayList(); + + foreach(TopicEntry curEntry in TopicTable) + { + if(curEntry.Locale.ToLower().EndsWith(fileExtension.ToLower())) + arrRet.Add(curEntry); + } + + if(arrRet.Count > 0) + return arrRet; + + return null; + } + + /// + /// Implement IDisposable. + /// + public void Dispose() + { + Dispose(true); + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SupressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// disposing flag + private void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if(!this.disposed) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if(disposing) + { + // Dispose managed resources. + _binaryFileData = null; + _topicTable=null; + } + } + disposed = true; + } + } +} diff --git a/irc/TechBot/CHMLibrary/CHMDecoding/CHMUrlstr.cs b/irc/TechBot/CHMLibrary/CHMDecoding/CHMUrlstr.cs new file mode 100644 index 00000000000..b87eebab58e --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMDecoding/CHMUrlstr.cs @@ -0,0 +1,308 @@ +using System; +using System.IO; +using System.Collections; +using System.Collections.Specialized; + +namespace HtmlHelp.ChmDecoding +{ + /// + /// The class CHMUrlstr implements a string collection storing the URL strings of the help file + /// + internal sealed class CHMUrlstr : IDisposable + { + /// + /// Constant specifying the size of the string blocks + /// + private const int BLOCK_SIZE = 0x1000; + /// + /// Internal flag specifying if the object is going to be disposed + /// + private bool disposed = false; + /// + /// Internal member storing the binary file data + /// + private byte[] _binaryFileData = null; + /// + /// Internal member storing the url dictionary + /// + private Hashtable _urlDictionary = new Hashtable(); + /// + /// Internal member storing the framename dictionary + /// + private Hashtable _framenameDictionary = new Hashtable(); + /// + /// Internal member storing the associated chmfile object + /// + private CHMFile _associatedFile = null; + + /// + /// Constructor of the class + /// + /// binary file data of the #URLSTR file + /// associated chm file + public CHMUrlstr(byte[] binaryFileData, CHMFile associatedFile) + { + _binaryFileData = binaryFileData; + _associatedFile = associatedFile; + DecodeData(); + + // clear internal binary data after extraction + _binaryFileData = null; + } + + /// + /// Standard constructor + /// + internal CHMUrlstr() + { + } + + #region Data dumping + /// + /// Dump the class data to a binary writer + /// + /// writer to write the data + internal void Dump(ref BinaryWriter writer) + { + writer.Write( _urlDictionary.Count ); + + if (_urlDictionary.Count != 0) + { + IDictionaryEnumerator iDictionaryEnumerator = _urlDictionary.GetEnumerator(); + while (iDictionaryEnumerator.MoveNext()) + { + DictionaryEntry dictionaryEntry = (DictionaryEntry)iDictionaryEnumerator.Current; + writer.Write( Int32.Parse(dictionaryEntry.Key.ToString()) ); + writer.Write( dictionaryEntry.Value.ToString() ); + } + } + + writer.Write( _framenameDictionary.Count ); + + if (_framenameDictionary.Count != 0) + { + IDictionaryEnumerator iDictionaryEnumerator = _framenameDictionary.GetEnumerator(); + while (iDictionaryEnumerator.MoveNext()) + { + DictionaryEntry dictionaryEntry = (DictionaryEntry)iDictionaryEnumerator.Current; + writer.Write( Int32.Parse(dictionaryEntry.Key.ToString()) ); + writer.Write( dictionaryEntry.Value.ToString() ); + } + } + } + + /// + /// Reads the object data from a dump store + /// + /// reader to read the data + internal void ReadDump(ref BinaryReader reader) + { + int i=0; + int nCnt = reader.ReadInt32(); + + for(i=0; i + /// Sets the associated CHMFile instance + /// + /// instance to set + internal void SetCHMFile(CHMFile associatedFile) + { + _associatedFile = associatedFile; + } + #endregion + + /// + /// Decodes the binary file data and fills the internal properties + /// + /// true if succeeded + private bool DecodeData() + { + bool bRet = true; + + MemoryStream memStream = new MemoryStream(_binaryFileData); + BinaryReader binReader = new BinaryReader(memStream); + + int nCurOffset = 0; + + while( (memStream.Position < memStream.Length) && (bRet) ) + { + nCurOffset = (int)memStream.Position; + byte [] dataBlock = binReader.ReadBytes(BLOCK_SIZE); + bRet &= DecodeBlock(dataBlock, ref nCurOffset); + } + + return bRet; + } + + /// + /// Decodes a block of url-string data + /// + /// block of data + /// current file offset + /// true if succeeded + private bool DecodeBlock( byte[] dataBlock, ref int nOffset ) + { + bool bRet = true; + int blockOffset = nOffset; + + MemoryStream memStream = new MemoryStream(dataBlock); + BinaryReader binReader = new BinaryReader(memStream); + + if(nOffset==0) + binReader.ReadByte(); // first block starts with an unknown byte + + while( (memStream.Position < (memStream.Length-8)) && (bRet) ) + { + int entryOffset = blockOffset + (int)memStream.Position; + + int urlOffset = binReader.ReadInt32(); + int frameOffset = binReader.ReadInt32(); + + + // There is one way to tell where the end of the URL/FrameName + // pairs occurs: Repeat the following: read 2 DWORDs and if both + // are less than the current offset then this is the start of the Local + // strings else skip two NT strings. + // if(( (urlOffset < (entryOffset+8)) && (frameOffset < (entryOffset+8)) )) + // { + // //TODO: add correct string reading if an offset has been found + // /* + // int curOffset = (int)memStream.Position; + // + // memStream.Seek( (long)(blockOffset-urlOffset), SeekOrigin.Begin); + // string sTemp = CHMReader.ExtractString(ref binReader, 0, true); + // + // memStream.Seek( (long)(blockOffset-frameOffset), SeekOrigin.Begin); + // sTemp = CHMReader.ExtractString(ref binReader, 0, true); + // + // memStream.Seek((long)curOffset, SeekOrigin.Begin); + // */ + // + // + // int curOffs = (int)memStream.Position; + // BinaryReaderHelp.ExtractString(ref binReader, 0, true, _associatedFile.TextEncoding); + // nOffset += (int)memStream.Position - curOffs; + // + // curOffs = (int)memStream.Position; + // BinaryReaderHelp.ExtractString(ref binReader, 0, true, _associatedFile.TextEncoding); + // nOffset += (int)memStream.Position - curOffs; + // } + // else + { + bool bFoundTerminator = false; + + string sTemp = BinaryReaderHelp.ExtractString(ref binReader, ref bFoundTerminator, 0, true, _associatedFile.TextEncoding); + + if(sTemp == "") + { + //nOffset = nOffset + 1 + (int)memStream.Length - (int)memStream.Position; + memStream.Seek(memStream.Length-1, SeekOrigin.Begin); + } + else + { + _urlDictionary[entryOffset.ToString()] = sTemp.ToString(); + _framenameDictionary[ entryOffset.ToString() ] = sTemp.ToString() ; + } + } + } + + return bRet; + } + + /// + /// Gets the url at a given offset + /// + /// offset of url + /// the url at the given offset + public string GetURLatOffset(int offset) + { + if(offset == -1) + return String.Empty; + + string sTemp = (string)_urlDictionary[ offset.ToString() ]; + + if(sTemp == null) + return String.Empty; + + return sTemp; + } + + /// + /// Gets the framename at a given offset + /// + /// offset of the framename + /// the frame name at the given offset + public string GetFrameNameatOffset(int offset) + { + if(offset == -1) + return String.Empty; + + string sTemp = (string)_framenameDictionary[ offset.ToString() ]; + + if(sTemp == null) + return String.Empty; + + return sTemp; + } + + /// + /// Implement IDisposable. + /// + public void Dispose() + { + Dispose(true); + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SupressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// disposing flag + private void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if(!this.disposed) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if(disposing) + { + // Dispose managed resources. + _binaryFileData = null; + _urlDictionary = null; + _framenameDictionary = null; + } + } + disposed = true; + } + } +} diff --git a/irc/TechBot/CHMLibrary/CHMDecoding/CHMUrltable.cs b/irc/TechBot/CHMLibrary/CHMDecoding/CHMUrltable.cs new file mode 100644 index 00000000000..79f718b84ce --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMDecoding/CHMUrltable.cs @@ -0,0 +1,245 @@ +using System; +using System.IO; +using System.Collections; + +namespace HtmlHelp.ChmDecoding +{ + /// + /// The class CHMUrltable implements methods to decode the #URLTBL internal file. + /// + internal sealed class CHMUrltable : IDisposable + { + /// + /// Constant specifying the size of the data blocks + /// + private const int BLOCK_SIZE = 0x1000; + /// + /// Constant specifying the number of records per block + /// + private const int RECORDS_PER_BLOCK = 341; + /// + /// Internal flag specifying if the object is going to be disposed + /// + private bool disposed = false; + /// + /// Internal member storing the binary file data + /// + private byte[] _binaryFileData = null; + /// + /// Internal member storing the url table + /// + private ArrayList _urlTable = new ArrayList(); + /// + /// Internal member storing the associated chmfile object + /// + private CHMFile _associatedFile = null; + + /// + /// Constructor of the class + /// + /// binary file data of the #URLTBL file + /// associated chm file + public CHMUrltable(byte[] binaryFileData, CHMFile associatedFile) + { + _binaryFileData = binaryFileData; + _associatedFile = associatedFile; + DecodeData(); + + // clear internal binary data after extraction + _binaryFileData = null; + } + + /// + /// Standard constructor + /// + internal CHMUrltable() + { + } + + #region Data dumping + /// + /// Dump the class data to a binary writer + /// + /// writer to write the data + internal void Dump(ref BinaryWriter writer) + { + writer.Write( _urlTable.Count ); + foreach(UrlTableEntry curItem in _urlTable) + { + curItem.Dump(ref writer); + } + } + + /// + /// Reads the object data from a dump store + /// + /// reader to read the data + internal void ReadDump(ref BinaryReader reader) + { + int i=0; + int nCnt = reader.ReadInt32(); + + for(i=0; i + /// Sets the associated CHMFile instance + /// + /// instance to set + internal void SetCHMFile(CHMFile associatedFile) + { + _associatedFile = associatedFile; + + foreach(UrlTableEntry curEntry in _urlTable) + { + curEntry.SetCHMFile(associatedFile); + } + } + #endregion + + /// + /// Decodes the binary file data and fills the internal properties + /// + /// true if succeeded + private bool DecodeData() + { + bool bRet = true; + + MemoryStream memStream = new MemoryStream(_binaryFileData); + BinaryReader binReader = new BinaryReader(memStream); + + int nCurOffset = 0; + + while( (memStream.Position < memStream.Length) && (bRet) ) + { + nCurOffset = (int)memStream.Position; + byte [] dataBlock = binReader.ReadBytes(BLOCK_SIZE); + bRet &= DecodeBlock(dataBlock, ref nCurOffset); + } + + return bRet; + } + + /// + /// Decodes a block of url-string data + /// + /// block of data + /// current file offset + /// true if succeeded + private bool DecodeBlock( byte[] dataBlock, ref int nOffset ) + { + bool bRet = true; + int blockOffset = nOffset; + + MemoryStream memStream = new MemoryStream(dataBlock); + BinaryReader binReader = new BinaryReader(memStream); + + for(int i=0; i < RECORDS_PER_BLOCK; i++) + { + int recordOffset = blockOffset + (int)memStream.Position; + + uint nuniqueID = (uint) binReader.ReadInt32(); // unknown dword + int ntopicsIdx = binReader.ReadInt32(); + int urlstrOffset = binReader.ReadInt32(); + + UrlTableEntry newEntry = new UrlTableEntry(nuniqueID, recordOffset, ntopicsIdx, urlstrOffset, _associatedFile); + _urlTable.Add(newEntry); + + if( memStream.Position >= memStream.Length) + break; + } + + if(dataBlock.Length == BLOCK_SIZE) + binReader.ReadInt32(); + + return bRet; + } + + /// + /// Gets the arraylist containing all urltable entries. + /// + public ArrayList UrlTable + { + get + { + return _urlTable; + } + } + + /// + /// Gets the urltable entry of a given offset + /// + public UrlTableEntry this[int offset] + { + get + { + foreach(UrlTableEntry curEntry in _urlTable) + if(curEntry.EntryOffset == offset) + return curEntry; + + return null; + } + } + + /// + /// Gets the urltable entry of a given uniqueID + /// + public UrlTableEntry GetByUniqueID(uint uniqueID) + { + foreach(UrlTableEntry curEntry in UrlTable) + { + if(curEntry.UniqueID == uniqueID) + return curEntry; + } + + return null; + } + + /// + /// Implement IDisposable. + /// + public void Dispose() + { + Dispose(true); + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SupressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// disposing flag + private void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if(!this.disposed) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if(disposing) + { + // Dispose managed resources. + _binaryFileData = null; + _urlTable = null; + } + } + disposed = true; + } + } +} diff --git a/irc/TechBot/CHMLibrary/CHMDecoding/DumpingInfo.cs b/irc/TechBot/CHMLibrary/CHMDecoding/DumpingInfo.cs new file mode 100644 index 00000000000..f42fb98ee11 --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMDecoding/DumpingInfo.cs @@ -0,0 +1,395 @@ +using System; +using System.IO; +using System.Text; +using System.Diagnostics; +using System.Collections.Specialized; + +using ICSharpCode.SharpZipLib; +using ICSharpCode.SharpZipLib.Zip; +using ICSharpCode.SharpZipLib.Zip.Compression; +using ICSharpCode.SharpZipLib.Zip.Compression.Streams; + +using HtmlHelp; +// using HtmlHelp.Storage; + +namespace HtmlHelp.ChmDecoding +{ + /// + /// Enumeration for specifying the dumping compression + /// + public enum DumpCompression + { + /// + /// None - no data compression will be used. + /// Fastest but most memory intensive + /// + None = 0, + /// + /// Minimum - a minimum data compression will be used. + /// Fast but not much data reduction + /// + Minimum = 1, + /// + /// Medium - a medium data compression will be used. + /// Slower but medium data reduction + /// + Medium = 2, + /// + /// Maximum - a maximum data compression will be used. + /// Slowest but maximum data reduction + /// + Maximum = 3 + } + + /// + /// Flags which specify which data should be dumped + /// + [FlagsAttribute()] + public enum DumpingFlags + { + /// + /// DumpTextTOC - if this flag is set, text-based TOCs (sitemap format) will be dumped + /// + DumpTextTOC = 1, + /// + /// DumpBinaryTOC - if this flag is set, binary TOCs will be dumped + /// + DumpBinaryTOC = 2, + /// + /// DumpTextIndex - if this flag is set, the text-based index (sitemap format) will be dumped + /// + DumpTextIndex = 4, + /// + /// DumpBinaryIndex - if this flag is set, the binary index will be dumped + /// + DumpBinaryIndex = 8, + /// + /// DumpStrings - if this flag is set, the internal #STRINGS file will be dumped + /// + DumpStrings = 16, + /// + /// DumpUrlStr - if this flag is set, the internal #URLSTR file will be dumped + /// + DumpUrlStr = 32, + /// + /// DumpUrlTbl - if this flag is set, the internal #URLTBL file will be dumped + /// + DumpUrlTbl = 64, + /// + /// DumpTopics - if this flag is set, the internal #TOPICS file will be dumped + /// + DumpTopics = 128, + /// + /// DumpFullText - if this flag is set, the internal $FIftiMain file will be dumped + /// + DumpFullText = 256 + } + + /// + /// The class DumpingInfo implements information properties for the CHMFile class + /// if and how data dumping should be used. + /// + public sealed class DumpingInfo + { + public bool m_bAllowSaveDump=true; + + private readonly static BitVector32.Section DumpFlags = BitVector32.CreateSection(512); + + private const string _dumpHeader = "HtmlHelpSystem dump file 1.0"; + + private string _outputDir = ""; // emtpy string means, same directory as chm file + private DumpCompression _compressionLevel = DumpCompression.Maximum; + private CHMFile _chmFile = null; + + private DeflaterOutputStream _outputStream = null; + private InflaterInputStream _inputStream = null; + + private BinaryWriter _writer = null; + private BinaryReader _reader = null; + + private BitVector32 _flags; + + /// + /// Constructor of the class + /// + /// Combine flag values to specify which data should be dumped. + /// output directory. emtpy string means, + /// same directory as chm file (only if destination = ExternalFile) + /// compression which should be used + public DumpingInfo(DumpingFlags flags, string outputDir, DumpCompression compressionLevel) + { + _flags = new BitVector32(0); + int i = _flags[DumpFlags]; + _flags[DumpFlags] = i | (int)flags; + + _outputDir = outputDir; + _compressionLevel = compressionLevel; + } + + /// + /// Gets the flag if text-based TOCs will be written to the dumping file + /// + public bool DumpTextTOC + { + get { return ((_flags[DumpFlags] & (int)DumpingFlags.DumpTextTOC) != 0); } + } + + /// + /// Gets the flag if binary TOCs will be written to the dumping file + /// + public bool DumpBinaryTOC + { + get { return ((_flags[DumpFlags] & (int)DumpingFlags.DumpBinaryTOC) != 0); } + } + + /// + /// Gets the flag if the text-based index will be written to the dumping file + /// + public bool DumpTextIndex + { + get { return ((_flags[DumpFlags] & (int)DumpingFlags.DumpTextIndex) != 0); } + } + + /// + /// Gets the flag if the binary index will be written to the dumping file + /// + public bool DumpBinaryIndex + { + get { return ((_flags[DumpFlags] & (int)DumpingFlags.DumpBinaryIndex) != 0); } + } + + /// + /// Gets the flag if the #STRINGS file will be written to the dumping file + /// + public bool DumpStrings + { + get { return ((_flags[DumpFlags] & (int)DumpingFlags.DumpStrings) != 0); } + } + + /// + /// Gets the flag if the #URLSTR file will be written to the dumping file + /// + public bool DumpUrlStr + { + get { return ((_flags[DumpFlags] & (int)DumpingFlags.DumpUrlStr) != 0); } + } + + /// + /// Gets the flag if the #URLTBL file will be written to the dumping file + /// + public bool DumpUrlTbl + { + get { return ((_flags[DumpFlags] & (int)DumpingFlags.DumpUrlTbl) != 0); } + } + + /// + /// Gets the flag if the #TOPICS file will be written to the dumping file + /// + public bool DumpTopics + { + get { return ((_flags[DumpFlags] & (int)DumpingFlags.DumpTopics) != 0); } + } + + /// + /// Gets the flag if the $FIftiMain file will be written to the dumping file + /// + public bool DumpFullText + { + get { return ((_flags[DumpFlags] & (int)DumpingFlags.DumpFullText) != 0); } + } + + /// + /// Gets the dump output directory. + /// + /// emtpy string means, same directory as chm file + /// If Destination is set to DumpingOutput.InternalFile this property will be ignored + public string OutputDir + { + get { return _outputDir; } + } + + /// + /// The compression level used. + /// + public DumpCompression CompressionLevel + { + get { return _compressionLevel; } + } + + /// + /// Gets/Sets the CHMFile instance associated with this object + /// + internal CHMFile ChmFile + { + get { return _chmFile; } + set { _chmFile = value; } + } + + /// + /// Translates the compression level to the deflater constants + /// + private int CompLvl + { + get + { + switch(CompressionLevel) + { + case DumpCompression.None: return Deflater.NO_COMPRESSION; + case DumpCompression.Minimum: return Deflater.BEST_SPEED; + case DumpCompression.Medium: return Deflater.DEFAULT_COMPRESSION; + case DumpCompression.Maximum: return Deflater.BEST_COMPRESSION; + } + + return Deflater.BEST_COMPRESSION; + } + } + + /// + /// Checks if a dump exists + /// + internal bool DumpExists + { + get + { + if(_flags[DumpFlags] == 0) + return false; + + // we have a reader or writer to the dump so it must exist + if( (_reader != null) || (_writer != null) ) + return true; + + string sDmpFile = _chmFile.ChmFilePath; + sDmpFile=sDmpFile.ToLower().Replace(".chm",".CHB"); + + return File.Exists(sDmpFile); + } + } + + /// + /// Gets a binary writer instance which allows you to write to the dump + /// + internal BinaryWriter Writer + { + get + { + if (m_bAllowSaveDump==false) + return null; + + if(_flags[DumpFlags] == 0) + throw new InvalidOperationException("Nothing to dump. No flags have been set !"); + + if(_reader != null) + throw new InvalidOperationException("Can't write and read at the same time !"); + + if(_chmFile == null) + throw new InvalidOperationException("Only usable with an associated CHMFile instance !"); + + if(_writer==null) + { + string sDmpFile = _chmFile.ChmFilePath; + sDmpFile=sDmpFile.ToLower().Replace(".chm",".CHB"); + StreamWriter stream = new StreamWriter(sDmpFile, false, _chmFile.TextEncoding); + + // write header info uncompressed + BinaryWriter _hwriter = new BinaryWriter(stream.BaseStream); + _hwriter.Write(_dumpHeader); + _hwriter.Write((int)CompressionLevel); + + if(_compressionLevel == DumpCompression.None) + { + _writer = new BinaryWriter(stream.BaseStream); + } + else + { + _outputStream = new DeflaterOutputStream(stream.BaseStream, new Deflater(CompLvl)); + + _writer = new BinaryWriter(_outputStream); + } + } + + return _writer; + + } + } + + /// + /// Gets a binary reader which allows you to read from the dump + /// + internal BinaryReader Reader + { + get + { + if(_writer != null) + throw new InvalidOperationException("Can't write and read at the same time !"); + + if(_chmFile == null) + throw new InvalidOperationException("Only usable with an associated CHMFile instance !"); + + if(_reader==null) + { + string sDmpFile = _chmFile.ChmFilePath; + sDmpFile=sDmpFile.ToLower().Replace(".chm",".CHB"); + StreamReader stream = new StreamReader(sDmpFile, _chmFile.TextEncoding); + + BinaryReader _hReader = new BinaryReader(stream.BaseStream); + string sH = _hReader.ReadString(); + + if(sH != _dumpHeader) + { + _hReader.Close(); + Debug.WriteLine("Unexpected dump-file header !"); + throw new FormatException("DumpingInfo.Reader - Unexpected dump-file header !"); + } + + _compressionLevel = (DumpCompression)_hReader.ReadInt32(); +// if(_compressionLevel != (DumpCompression)_hReader.ReadInt32()) +// { +// _hReader.Close(); +// return null; +// } + + if(_compressionLevel == DumpCompression.None) + { + _reader = new BinaryReader(stream.BaseStream); + } + else + { + _inputStream = new InflaterInputStream(stream.BaseStream, new Inflater()); + + _reader = new BinaryReader(_inputStream); + } + } + + return _reader; + } + } + + /// + /// Saves data and closes the dump + /// + /// true if succeed + internal bool SaveData() + { + if (m_bAllowSaveDump==false) + return true; + + if(_writer != null) + { + if(_writer!=null) + _writer.Close(); + _outputStream = null; + _writer = null; + } + + if(_reader != null) + { + if(_reader!=null) + _reader.Close(); + _inputStream = null; + _reader = null; + } + + return true; + } + } +} diff --git a/irc/TechBot/CHMLibrary/CHMDecoding/FullTextEngine.cs b/irc/TechBot/CHMLibrary/CHMDecoding/FullTextEngine.cs new file mode 100644 index 00000000000..fc1d634d924 --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMDecoding/FullTextEngine.cs @@ -0,0 +1,1131 @@ +using System; +using System.Data; +using System.Diagnostics; +using System.Text; +using System.Text.RegularExpressions; +using System.IO; +using System.Collections; +using System.Globalization; + +namespace HtmlHelp.ChmDecoding +{ + /// + /// The class FullTextSearcher implements a fulltext searcher for a single chm file ! + /// + internal sealed class FullTextEngine : IDisposable + { + #region Internal helper classes + /// + /// Internal class for decoding the header + /// + private sealed class FTHeader + { + /// + /// Internal member storing the number of indexed files + /// + private int _numberOfIndexFiles = 0; + /// + /// Internal member storing the offset of the root node + /// + private int _rootOffset = 0; + /// + /// Internal member storing the index-page count + /// + private int _pageCount = 0; + /// + /// Internal member storing the depth of the tree + /// + private int _depth = 0; + /// + /// Internal member storing the scale param for document index en-/decoding + /// + private byte _scaleDocIdx = 0; + /// + /// Internal member storing the scale param for code-count en-/decoding + /// + private byte _scaleCodeCnt = 0; + /// + /// Internal member storing the scale param for location codes en-/decoding + /// + private byte _scaleLocCodes = 0; + /// + /// Internal member storing the root param for document index en-/decoding + /// + private byte _rootDocIdx = 0; + /// + /// Internal member storing the root param for code-count en-/decoding + /// + private byte _rootCodeCnt = 0; + /// + /// Internal member storing the root param for location codes en-/decoding + /// + private byte _rootLocCodes = 0; + /// + /// Internal member storing the size of the nodes in bytes + /// + private int _nodeSize = 0; + /// + /// Internal member storing the length of the longest word + /// + private int _lengthOfLongestWord = 0; + /// + /// Internal member storing the total number of words + /// + private int _totalNumberOfWords = 0; + /// + /// Internal member storing the total number of unique words + /// + private int _numberOfUniqueWords = 0; + /// + /// Internal member storing the codepage identifier + /// + private int _codePage = 1252; + /// + /// Internal member storing the language code id + /// + private int _lcid = 1033; + /// + /// Internal member storing the text encoder + /// + private Encoding _textEncoder = Encoding.Default; + + /// + /// Constructor of the header + /// + /// binary data from which the header will be extracted + public FTHeader(byte[] binaryData) + { + DecodeHeader(binaryData); + } + + /// + /// Internal constructor for reading from dump + /// + internal FTHeader() + { + } + + /// + /// Decodes the binary header information and fills the members + /// + /// binary data from which the header will be extracted + private void DecodeHeader(byte[] binaryData) + { + MemoryStream memStream = new MemoryStream(binaryData); + BinaryReader binReader = new BinaryReader(memStream); + + binReader.ReadBytes(4); // 4 unknown bytes + + _numberOfIndexFiles = binReader.ReadInt32(); // number of indexed files + + binReader.ReadInt32(); // unknown + binReader.ReadInt32(); // unknown + + _pageCount = binReader.ReadInt32(); // page-count + _rootOffset = binReader.ReadInt32(); // file offset of the root node + _depth = binReader.ReadInt16(); // depth of the tree + + binReader.ReadInt32(); // unknown + + _scaleDocIdx = binReader.ReadByte(); + _rootDocIdx = binReader.ReadByte(); + _scaleCodeCnt = binReader.ReadByte(); + _rootCodeCnt = binReader.ReadByte(); + _scaleLocCodes = binReader.ReadByte(); + _rootLocCodes = binReader.ReadByte(); + + if( (_scaleDocIdx != 2) || ( _scaleCodeCnt != 2 ) || ( _scaleLocCodes != 2 ) ) + { + Debug.WriteLine("Unsupported scale for s/r encoding !"); + throw new InvalidOperationException("Unsupported scale for s/r encoding !"); + } + + binReader.ReadBytes(10); // unknown + + _nodeSize = binReader.ReadInt32(); + + binReader.ReadInt32(); // unknown + binReader.ReadInt32(); // not important + binReader.ReadInt32(); // not important + + _lengthOfLongestWord = binReader.ReadInt32(); + _totalNumberOfWords = binReader.ReadInt32(); + _numberOfUniqueWords = binReader.ReadInt32(); + + binReader.ReadInt32(); // not important + binReader.ReadInt32(); // not important + binReader.ReadInt32(); // not important + binReader.ReadInt32(); // not important + binReader.ReadInt32(); // not important + binReader.ReadInt32(); // not important + + binReader.ReadBytes(24); // not important + + _codePage = binReader.ReadInt32(); + _lcid = binReader.ReadInt32(); + + CultureInfo ci = new CultureInfo(_lcid); + _textEncoder = Encoding.GetEncoding( ci.TextInfo.ANSICodePage ); + + // rest of header is not important for us + } + + /// + /// Dump the class data to a binary writer + /// + /// writer to write the data + internal void Dump(ref BinaryWriter writer) + { + writer.Write( _numberOfIndexFiles ); + writer.Write( _rootOffset ); + writer.Write( _pageCount ); + writer.Write( _depth ); + writer.Write( _scaleDocIdx ); + writer.Write( _rootDocIdx ); + writer.Write( _scaleCodeCnt ); + writer.Write( _rootCodeCnt ); + writer.Write( _scaleLocCodes ); + writer.Write( _rootLocCodes ); + writer.Write( _nodeSize ); + writer.Write( _lengthOfLongestWord ); + writer.Write( _totalNumberOfWords ); + writer.Write( _numberOfUniqueWords ); + } + + /// + /// Reads the object data from a dump store + /// + /// reader to read the data + internal void ReadDump(ref BinaryReader reader) + { + _numberOfIndexFiles = reader.ReadInt32(); + _rootOffset = reader.ReadInt32(); + _pageCount = reader.ReadInt32(); + _depth = reader.ReadInt32(); + + _scaleDocIdx = reader.ReadByte(); + _rootDocIdx = reader.ReadByte(); + _scaleCodeCnt = reader.ReadByte(); + _rootCodeCnt = reader.ReadByte(); + _scaleLocCodes = reader.ReadByte(); + _rootLocCodes = reader.ReadByte(); + + _nodeSize = reader.ReadInt32(); + _lengthOfLongestWord = reader.ReadInt32(); + _totalNumberOfWords = reader.ReadInt32(); + _numberOfUniqueWords = reader.ReadInt32(); + } + + /// + /// Gets the number of indexed files + /// + public int IndexedFileCount + { + get { return _numberOfIndexFiles; } + } + + /// + /// Gets the file offset of the root node + /// + public int RootOffset + { + get { return _rootOffset; } + } + + /// + /// Gets the page count + /// + public int PageCount + { + get { return _pageCount; } + } + + /// + /// Gets the index depth + /// + public int Depth + { + get { return _depth; } + } + + /// + /// Gets the scale param for document index en-/decoding + /// + /// The scale and root method of integer encoding needs two parameters, + /// which I'll call s (scale) and r (root size). + /// The integer is encoded as two parts, p (prefix) and q (actual bits). + /// p determines how many bits are stored, as well as implicitly determining + /// the high-order bit of the integer. + public byte ScaleDocumentIndex + { + get { return _scaleDocIdx; } + } + + /// + /// Gets the root param for the document index en-/decoding + /// + /// The scale and root method of integer encoding needs two parameters, + /// which I'll call s (scale) and r (root size). + /// The integer is encoded as two parts, p (prefix) and q (actual bits). + /// p determines how many bits are stored, as well as implicitly determining + /// the high-order bit of the integer. + public byte RootDocumentIndex + { + get { return _rootDocIdx; } + } + + /// + /// Gets the scale param for the code-count en-/decoding + /// + /// The scale and root method of integer encoding needs two parameters, + /// which I'll call s (scale) and r (root size). + /// The integer is encoded as two parts, p (prefix) and q (actual bits). + /// p determines how many bits are stored, as well as implicitly determining + /// the high-order bit of the integer. + public byte ScaleCodeCount + { + get { return _scaleCodeCnt; } + } + + /// + /// Gets the root param for the code-count en-/decoding + /// + /// The scale and root method of integer encoding needs two parameters, + /// which I'll call s (scale) and r (root size). + /// The integer is encoded as two parts, p (prefix) and q (actual bits). + /// p determines how many bits are stored, as well as implicitly determining + /// the high-order bit of the integer. + public byte RootCodeCount + { + get { return _rootCodeCnt; } + } + + /// + /// Gets the scale param for the location codes en-/decoding + /// + /// The scale and root method of integer encoding needs two parameters, + /// which I'll call s (scale) and r (root size). + /// The integer is encoded as two parts, p (prefix) and q (actual bits). + /// p determines how many bits are stored, as well as implicitly determining + /// the high-order bit of the integer. + public byte ScaleLocationCodes + { + get { return _scaleLocCodes; } + } + + /// + /// Gets the root param for the location codes en-/decoding + /// + /// The scale and root method of integer encoding needs two parameters, + /// which I'll call s (scale) and r (root size). + /// The integer is encoded as two parts, p (prefix) and q (actual bits). + /// p determines how many bits are stored, as well as implicitly determining + /// the high-order bit of the integer. + public byte RootLocationCodes + { + get { return _rootLocCodes; } + } + + /// + /// Gets the size in bytes of each index/leaf node + /// + public int NodeSize + { + get { return _nodeSize; } + } + + /// + /// Gets the length of the longest word in the index + /// + private int LengthOfLongestWord + { + get { return _lengthOfLongestWord; } + } + + /// + /// Gets the total number of words indexed (including duplicates) + /// + public int TotalWordCount + { + get { return _totalNumberOfWords; } + } + + /// + /// Gets the total number of unique words indexed (excluding duplicates) + /// + public int UniqueWordCount + { + get { return _numberOfUniqueWords; } + } + + /// + /// Gets the codepage identifier + /// + public int CodePage + { + get { return _codePage; } + } + + /// + /// Gets the language code id + /// + public int LCID + { + get { return _lcid; } + } + + public Encoding TextEncoder + { + get + { + return _textEncoder; + } + } + } + + + /// + /// Internal class for easier hit recording and rate-calculation + /// + private sealed class HitHelper : IComparable + { + /// + /// Internal member storing the associated document index + /// + private int _documentIndex = 0; + /// + /// Internal member storing the title + /// + private string _title = ""; + /// + /// Internal member storing the locale + /// + private string _locale = ""; + /// + /// Internal member storing the location + /// + private string _location = ""; + /// + /// Internal member storing the url + /// + private string _url = ""; + /// + /// Internal member storing the rating + /// + private double _rating = 0; + /// + /// Internal member used for rating calculation + /// + private Hashtable _partialRating = new Hashtable(); + + /// + /// Constructor of the class + /// + /// document index + /// title + /// locale parameter + /// location + /// url of document + /// rating + public HitHelper(int documentIndex, string title, string locale, string location, string url, double rating) + { + _documentIndex = documentIndex; + _title = title; + _locale = locale; + _location = location; + _url = url; + _rating = rating; + } + + /// + /// Updates the rating for a found word + /// + /// word found + public void UpdateRating(string word) + { + if( _partialRating[word] == null) + { + _partialRating[word] = 100.0; + } + else + { + _partialRating[word] = ((double)_partialRating[word])*1.01; + } + + _rating = 0.0; + + foreach(double val in _partialRating.Values) + { + _rating += val; + } + } + + /// + /// Implements the CompareTo method of the IComparable interface. + /// Allows an easy sort by the document rating + /// + /// object to compare + /// 0 ... equal, -1 ... this instance is less than obj, 1 ... this instance is greater than obj + public int CompareTo(object obj) + { + if( obj is HitHelper ) + { + HitHelper hObj = (HitHelper)obj; + + return this.Rating.CompareTo( hObj.Rating ); + } + + return -1; + } + + /// + /// Gets the internal hashtable used for counting word hits of the document + /// + internal Hashtable PartialRating + { + get { return _partialRating; } + } + + /// + /// Gets the document index of the hit helper instance + /// + public int DocumentIndex + { + get { return _documentIndex; } + } + + /// + /// Gets the title + /// + public string Title + { + get { return _title; } + } + + /// + /// Gets the locale + /// + public string Locale + { + get { return _locale; } + } + + /// + /// Gets the location + /// + public string Location + { + get { return _location; } + } + + /// + /// Gets the url + /// + public string URL + { + get { return _url; } + } + + /// + /// Gets the rating + /// + public double Rating + { + get { return _rating; } + } + + } + + #endregion + + /// + /// Regular expression getting the text between to quotes + /// + private string RE_Quotes = @"\""(?.*?)\"""; + /// + /// Internal flag specifying if the object is going to be disposed + /// + private bool disposed = false; + /// + /// Internal member storing the binary file data + /// + private byte[] _binaryFileData = null; + /// + /// Internal datatable storing the search hits + /// + private DataTable _hits =null; + /// + /// Internal arraylist for hit management + /// + private ArrayList _hitsHelper = new ArrayList(); + /// + /// Internal member storing the header of the file + /// + private FTHeader _header = null; + /// + /// Internal member storing the associated chmfile object + /// + private CHMFile _associatedFile = null; + + /// + /// Constructor of the class + /// + /// binary file data of the $FIftiMain file + /// associated chm file + public FullTextEngine(byte[] binaryFileData, CHMFile associatedFile) + { + _binaryFileData = binaryFileData; + _associatedFile = associatedFile; + + if(_associatedFile.SystemFile.FullTextSearch) + { + _header = new FTHeader(_binaryFileData); // reading header + } + } + + /// + /// Standard constructor + /// + internal FullTextEngine() + { + } + + #region Data dumping + /// + /// Dump the class data to a binary writer + /// + /// writer to write the data + internal void Dump(ref BinaryWriter writer) + { + _header.Dump(ref writer); + writer.Write( _binaryFileData.Length ); + writer.Write(_binaryFileData); + } + + /// + /// Reads the object data from a dump store + /// + /// reader to read the data + internal void ReadDump(ref BinaryReader reader) + { + _header = new FTHeader(); + _header.ReadDump(ref reader); + + int nCnt = reader.ReadInt32(); + _binaryFileData = reader.ReadBytes(nCnt); + } + + /// + /// Sets the associated CHMFile instance + /// + /// instance to set + internal void SetCHMFile(CHMFile associatedFile) + { + _associatedFile = associatedFile; + } + #endregion + + /// + /// Gets a flag if full-text searching is available for this chm file. + /// + public bool CanSearch + { + get { return (_associatedFile.SystemFile.FullTextSearch && (_header != null) ); } + } + + /// + /// Performs a fulltext search of a single file. + /// + /// word(s) or phrase to search + /// true if partial word should be matched also + /// ( if this is true a search of 'support' will match 'supports', otherwise not ) + /// true if only search in titles + /// Hits are available through the Hists property. + public bool Search(string search, bool partialMatches, bool titleOnly) + { + return Search(search, -1, partialMatches, titleOnly); + } + + /// + /// Performs a fulltext search of a single file. + /// + /// word(s) or phrase to search + /// max hits. If this number is reached, the search will be interrupted + /// true if partial word should be matched also + /// ( if this is true a search of 'support' will match 'supports', otherwise not ) + /// true if only search in titles + /// Hits are available through the Hists property. + public bool Search(string search, int MaxHits, bool partialMatches, bool titleOnly) + { + if(CanSearch) + { + string searchString = search; + + // Check if this is a quoted string + bool IsQuoted = (search.IndexOf("\"")>-1); + + if(IsQuoted) + searchString = search.Replace("\"",""); // remove the quotes during search + + bool bRet = true; + + _hitsHelper = null; + _hitsHelper = new ArrayList(); + + _hits = null; + CreateHitsTable(); + + string[] words = searchString.Split(new char[] {' '}); + + for(int i=0; i= MaxHits) + break; + } + + if(bRet && IsQuoted) + { + FinalizeQuoted(search); + } + + if(bRet) + { + _hitsHelper.Sort(); + + int nhCount = MaxHits; + + if( MaxHits < 0) + { + nhCount = _hitsHelper.Count; + } + + if( nhCount > _hitsHelper.Count ) + nhCount = _hitsHelper.Count; + + // create hits datatable + for(int i=nhCount; i > 0; i--) + { + HitHelper curHlp = (HitHelper)(_hitsHelper[i-1]); + + DataRow newRow = _hits.NewRow(); + + newRow["Rating"] = curHlp.Rating; + newRow["Title"] = curHlp.Title; + newRow["Locale"] = curHlp.Locale; + newRow["Location"] = curHlp.Location; + newRow["URL"] = curHlp.URL; + + _hits.Rows.Add( newRow ); + } + } + return bRet; + } + + return false; + } + + /// + /// Gets rid of all search hits which doesn't match the quoted phrase + /// + /// full search string entered by the user + /// Phrase search is not possible using the internal full-text index. We're just filtering all + /// documents which don't contain all words of the phrase. + private void FinalizeQuoted(string search) + { + Regex quoteRE = new Regex(RE_Quotes, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + int innerTextIdx = quoteRE.GroupNumberFromName("innerText"); + int nIndex = 0; + + // get all phrases + while( quoteRE.IsMatch(search, nIndex) ) + { + Match m = quoteRE.Match(search, nIndex); + + string phrase = m.Groups["innerText"].Value; + + string[] wordsInPhrase = phrase.Split( new char[] {' '} ); + int nCnt = _hitsHelper.Count; + + for(int i=0; i < _hitsHelper.Count; i++) + { + if( ! CheckHit( ((HitHelper)(_hitsHelper[i])), wordsInPhrase) ) + _hitsHelper.RemoveAt(i--); + } + + nIndex = m.Index+m.Length; + } + } + + /// + /// Eliminates all search hits where not all of the words have been found + /// + /// hithelper instance to check + /// word list + private bool CheckHit(HitHelper hit, string[] wordsInPhrase) + { + + for(int i=0; i + /// Performs a search for a single word in the index + /// + /// word to search + /// maximal hits to return + /// true if partial word should be matched also + /// ( if this is true a search of 'support' will match 'supports', otherwise not ) + /// true if only search in titles + /// Returns true if succeeded + private bool SearchSingleWord(string word,int MaxHits, bool partialMatches, bool titleOnly) + { + string wordLower = word.ToLower(); + + MemoryStream memStream = new MemoryStream(_binaryFileData); + BinaryReader binReader = new BinaryReader(memStream); + + // seek to root node + binReader.BaseStream.Seek( _header.RootOffset, SeekOrigin.Begin ); + + if( _header.Depth > 2 ) + { + // unsupported index depth + Debug.WriteLine("FullTextSearcher.SearchSingleWord() - Failed with message: Unsupported index depth !"); + Debug.WriteLine("File: " + _associatedFile.ChmFilePath); + Debug.WriteLine(" "); + return false; + } + + if( _header.Depth > 1 ) + { + // seek to the right leaf node ( if depth == 1, we are at the leaf node) + int freeSpace = binReader.ReadInt16(); + + for(int i=0; i < _header.PageCount; ++i) + { + // exstract index entries + int nWLength = (int)binReader.ReadByte(); + int nCPosition = (int)binReader.ReadByte(); + + string sName = BinaryReaderHelp.ExtractString(ref binReader, nWLength-1, 0, true, _header.TextEncoder); + + int nLeafOffset = binReader.ReadInt32(); + binReader.ReadInt16(); // unknown + + if( sName.CompareTo(wordLower) >= 0) + { + // store current position + long curPos = binReader.BaseStream.Position; + + // seek to leaf offset + binReader.BaseStream.Seek( nLeafOffset, SeekOrigin.Begin ); + + // read leafnode + ReadLeafNode(ref binReader, word, MaxHits, partialMatches, titleOnly); + + // return to current position and continue reading index nodes + binReader.BaseStream.Seek( curPos, SeekOrigin.Begin ); + } + } + } + + return true; + } + + /// + /// Reads a leaf node and extracts documents which holds the searched word + /// + /// reference to the reader + /// word to search + /// maximal hits to return + /// true if partial word should be matched also + /// ( if this is true a search of 'support' will match 'supports', otherwise not ) + /// true if only search in titles + private void ReadLeafNode(ref BinaryReader binReader, string word, int MaxHits, bool partialMatches, bool titleOnly) + { + int nNextPageOffset = binReader.ReadInt32(); + binReader.ReadInt16(); // unknown + int lfreeSpace = binReader.ReadInt16(); + string curFullWord = ""; + bool bFound = false; + string wordLower = word.ToLower(); + + for(;;) + { + if(binReader.BaseStream.Position >= binReader.BaseStream.Length) + break; + + int nWLength = (int)binReader.ReadByte(); + + if(nWLength == 0) + break; + + int nCPosition = (int)binReader.ReadByte(); + + string sName = BinaryReaderHelp.ExtractString(ref binReader, nWLength-1, 0, true, _header.TextEncoder); + + int Context = (int)binReader.ReadByte(); // 0...body tag, 1...title tag, others unknown + + long nrOfWCL = BinaryReaderHelp.ReadENCINT(ref binReader); + int wclOffset = binReader.ReadInt32(); + + binReader.ReadInt16(); // unknown + + long bytesOfWCL = BinaryReaderHelp.ReadENCINT(ref binReader); + + if( nCPosition > 0) + { + curFullWord = CombineStrings(curFullWord, sName, nCPosition); + } + else + { + curFullWord = sName; + } + + bFound = false; + if(partialMatches) + bFound = ( curFullWord.IndexOf(wordLower) >= 0 ); + else + bFound = (curFullWord == wordLower); + + if( bFound ) + { + if( (titleOnly && (Context==1)) || (!titleOnly) ) + { + // store actual offset + long curPos = binReader.BaseStream.Position; + + // found the word, begin with WCL encoding + binReader.BaseStream.Seek(wclOffset, SeekOrigin.Begin ); + + byte[] wclBytes = binReader.ReadBytes((int)bytesOfWCL); + + DecodeWCL(wclBytes, MaxHits, word); + + // back and continue reading leafnodes + binReader.BaseStream.Seek(curPos, SeekOrigin.Begin ); + } + } + } + } + + /// + /// Decodes the s/r encoded WordCodeList (=wcl) and creates hit entries + /// + /// wcl encoded byte array + /// maximal hits + /// the word to find + private void DecodeWCL(byte[] wclBytes,int MaxHits, string word) + { + byte[] wclBits = new byte[ wclBytes.Length*8 ]; + + int nBitIdx=0; + + for(int i=0; i (byte)0 ? (byte)1 : (byte)0; + nBitIdx++; + } + } + + nBitIdx = 0; + + int nDocIdx = 0; // delta encoded + + while(nBitIdx < wclBits.Length) + { + nDocIdx += BinaryReaderHelp.ReadSRItem(wclBits, _header.ScaleDocumentIndex, _header.RootDocumentIndex, ref nBitIdx); + int nCodeCnt = BinaryReaderHelp.ReadSRItem(wclBits, _header.ScaleCodeCount, _header.RootCodeCount, ref nBitIdx); + + int nWordLocation = 0; // delta encoded + + for(int locidx=0; locidx MaxHits) + return; + + hitObj = new HitHelper(nDocIdx, ((TopicEntry)(_associatedFile.TopicsFile.TopicTable[nDocIdx])).Title, + ((TopicEntry)(_associatedFile.TopicsFile.TopicTable[nDocIdx])).Locale, _associatedFile.CompileFile, + ((TopicEntry)(_associatedFile.TopicsFile.TopicTable[nDocIdx])).URL, 0.0); + + for(int k=0;k + /// Combines a "master" word with a partial word. + /// + /// the master word + /// the partial word + /// position to place the parial word + /// returns a combined string + private string CombineStrings(string word, string partial, int partialPosition) + { + string sCombined = word; + int i=0; + + for(i=0; i (sCombined.Length-1) ) + { + sCombined += partial[i]; + } + else + { + StringBuilder sb = new StringBuilder(sCombined); + + sb.Replace( sCombined[partialPosition+i], partial[i], partialPosition+i, 1); + sCombined = sb.ToString(); + } + } + + if(! ((i+partialPosition) > (sCombined.Length-1)) ) + { + sCombined = sCombined.Substring(0, partialPosition+partial.Length); + } + + return sCombined; + } + + /// + /// Gets the HitHelper instance for a specific document index + /// + /// document index + /// The reference of the hithelper instance for this document index, otherwise null + private HitHelper DocumentHit(int index) + { + foreach(HitHelper curObj in _hitsHelper) + { + if( curObj.DocumentIndex == index) + return curObj; + } + + return null; + } + + /// + /// Creates a DataTable for storing the hits + /// + private void CreateHitsTable() + { + _hits = new DataTable("FT_Search_Hits"); + + DataColumn ftColumn; + + ftColumn = new DataColumn(); + ftColumn.DataType = System.Type.GetType("System.Double"); + ftColumn.ColumnName = "Rating"; + ftColumn.ReadOnly = false; + ftColumn.Unique = false; + + _hits.Columns.Add(ftColumn); + + ftColumn = new DataColumn(); + ftColumn.DataType = System.Type.GetType("System.String"); + ftColumn.ColumnName = "Title"; + ftColumn.ReadOnly = false; + ftColumn.Unique = false; + + _hits.Columns.Add(ftColumn); + + ftColumn = new DataColumn(); + ftColumn.DataType = System.Type.GetType("System.String"); + ftColumn.ColumnName = "Locale"; + ftColumn.ReadOnly = false; + ftColumn.Unique = false; + + _hits.Columns.Add(ftColumn); + + ftColumn = new DataColumn(); + ftColumn.DataType = System.Type.GetType("System.String"); + ftColumn.ColumnName = "Location"; + ftColumn.ReadOnly = false; + ftColumn.Unique = false; + + _hits.Columns.Add(ftColumn); + + ftColumn = new DataColumn(); + ftColumn.DataType = System.Type.GetType("System.String"); + ftColumn.ColumnName = "URL"; + ftColumn.ReadOnly = false; + ftColumn.Unique = false; + + _hits.Columns.Add(ftColumn); + } + + /// + /// Gets an datatable containing the hits of the last search + /// + public DataTable Hits + { + get { return _hits; } + } + + /// + /// Implement IDisposable. + /// + public void Dispose() + { + Dispose(true); + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SupressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// disposing flag + private void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if(!this.disposed) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if(disposing) + { + // Dispose managed resources. + _binaryFileData = null; + } + } + disposed = true; + } + } +} diff --git a/irc/TechBot/CHMLibrary/CHMDecoding/HHCParser.cs b/irc/TechBot/CHMLibrary/CHMDecoding/HHCParser.cs new file mode 100644 index 00000000000..73482712291 --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMDecoding/HHCParser.cs @@ -0,0 +1,593 @@ +using System; +using System.Collections; +using System.Text; +using System.Text.RegularExpressions; + +namespace HtmlHelp.ChmDecoding +{ + /// + /// The class HHCParser implements a parser for HHC contents files. + /// + internal sealed class HHCParser + { + /// + /// regular expressions for replacing the sitemap boundary tags + /// + private static string RE_ULOpening = @"\"; // will be replaced by a '(' for nested parsing + private static string RE_ULClosing = @"\"; // will be replaced by a ')' for nested parsing + + /// + /// Matching ul-tags + /// + private static string RE_ULBoundaries = @"\(?.*)\"; + /// + /// Matching the nested tree structure. + /// + private static string RE_NestedBoundaries = @"\( (?> [^()]+ | \( (?) | \) (?<-DEPTH>) )* (?(DEPTH)(?!)) \)"; + /// + /// Matching object-tags + /// + private static string RE_ObjectBoundaries = @"\.*?)\"; + /// + /// Matching param tags + /// + private static string RE_ParamBoundaries = @"\.*?)\>"; + /// + /// Extracting tag attributes + /// + private const string RE_QuoteAttributes = @"( |\t)*(?[\-a-zA-Z0-9]*)( |\t)*=( |\t)*(?[\""\'])?(?.*?(?(attributeTD)\k|([\s>]|.$)))"; + + /// + /// private regular expressionobjects + /// + private static Regex ulRE; + private static Regex NestedRE; + private static Regex ObjectRE; + private static Regex ParamRE; + private static Regex AttributesRE; + + /// + /// Internal member storing the list of TOCItems which are holding merge links + /// + private static ArrayList _mergeItems = null; + + /// + /// Internal member storing the last read regular topic item. + /// This is used to handle "Merge" entries and add them as child to this instance. + /// + private static TOCItem _lastTopicItem = null; + + /// + /// Parses a HHC file and returns an ArrayList with the table of contents (TOC) tree + /// + /// string content of the hhc file + /// CHMFile instance + /// Returns an ArrayList with the table of contents (TOC) tree + public static ArrayList ParseHHC(string hhcFile, CHMFile chmFile) + { + _lastTopicItem = null; + _mergeItems = null; // clear merged item list + ArrayList tocList = new ArrayList(); + + ulRE = new Regex(RE_ULBoundaries, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + NestedRE = new Regex(RE_NestedBoundaries, RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + ObjectRE = new Regex(RE_ObjectBoundaries, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + ParamRE = new Regex(RE_ParamBoundaries, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + AttributesRE = new Regex(RE_QuoteAttributes, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + + int innerTextIdx = ulRE.GroupNumberFromName("innerText"); + + if( ulRE.IsMatch(hhcFile, 0) ) + { + Match m = ulRE.Match(hhcFile, 0); + + int nFirstUL = 0; + + nFirstUL = hhcFile.ToLower().IndexOf("
    "); + + if(nFirstUL == -1) + nFirstUL = hhcFile.ToLower().IndexOf(""); + + if( ObjectRE.IsMatch(hhcFile, 0) ) // first object block contains information types and categories + { + Match mO = ObjectRE.Match(hhcFile, 0); + int iOTxt = ObjectRE.GroupNumberFromName("innerText"); + + string globalText = mO.Groups[iOTxt].Value; + + if( mO.Groups[iOTxt].Index <= nFirstUL) + ParseGlobalSettings( globalText, chmFile ); + } + + // parse toc tree + string innerText = m.Groups["innerText"].Value; + + innerText = innerText.Replace("(", "("); + innerText = innerText.Replace(")", ")"); + innerText = Regex.Replace(innerText, RE_ULOpening, "(", RegexOptions.IgnoreCase); + innerText = Regex.Replace(innerText, RE_ULClosing, ")", RegexOptions.IgnoreCase); + + ParseTree( innerText, null, tocList, chmFile ); + + } + + return tocList; + } + + /// + /// Checks if the hhc file contains a global object tag. + /// + /// string content of the hhc file + /// chm file + /// true if the hhc content contains a global object tag + public static bool HasGlobalObjectTag(string hhcFile, CHMFile chmFile) + { + bool bRet = false; + + ulRE = new Regex(RE_ULBoundaries, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + ObjectRE = new Regex(RE_ObjectBoundaries, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + + int innerTextIdx = ulRE.GroupNumberFromName("innerText"); + + if( ulRE.IsMatch(hhcFile, 0) ) + { + Match m = ulRE.Match(hhcFile, 0); + + int nFirstUL = 0; + + nFirstUL = hhcFile.ToLower().IndexOf("
      "); + + if(nFirstUL == -1) + nFirstUL = hhcFile.ToLower().IndexOf(""); + + if( ObjectRE.IsMatch(hhcFile, 0) ) // first object block contains information types and categories + { + Match mO = ObjectRE.Match(hhcFile, 0); + int iOTxt = ObjectRE.GroupNumberFromName("innerText"); + + string globalText = mO.Groups[iOTxt].Value; + + if( mO.Groups[iOTxt].Index <= nFirstUL) + bRet = true; + } + } + + return bRet; + } + + /// + /// Gets true if the previously done parsing found merge-links + /// + public static bool HasMergeLinks + { + get + { + if(_mergeItems==null) + return false; + + return _mergeItems.Count > 0; + } + } + + /// + /// Gets all TOCItem references which are holding merge-links + /// + public static ArrayList MergeItems + { + get { return _mergeItems; } + } + + /// + /// Recursively parses a sitemap tree + /// + /// content text + /// Parent for all read items + /// arraylist which receives the extracted nodes + /// CHMFile instance + private static void ParseTree( string text, TOCItem parent, ArrayList arrNodes, CHMFile chmFile ) + { + string strPreItems="", strPostItems=""; + string innerText = ""; + + int nIndex = 0; + + while( NestedRE.IsMatch(text, nIndex) ) + { + Match m = NestedRE.Match(text, nIndex); + + innerText = m.Value.Substring( 1, m.Length-2); + + strPreItems = text.Substring(nIndex,m.Index-nIndex); + + ParseItems(strPreItems, parent, arrNodes, chmFile); + + if((arrNodes.Count>0) && (innerText.Length > 0) ) + { + TOCItem p = ((TOCItem)(arrNodes[arrNodes.Count-1])); + ParseTree( innerText, p, p.Children, chmFile ); + } + + nIndex = m.Index+m.Length; + } + + if( nIndex == 0) + { + strPostItems = text.Substring(nIndex, text.Length-nIndex); + ParseItems(strPostItems, parent, arrNodes, chmFile); + } + else if( nIndex < text.Length-1) + { + strPostItems = text.Substring(nIndex, text.Length-nIndex); + ParseTree(strPostItems, parent, arrNodes, chmFile); + } + } + + /// + /// Parses tree nodes from the text + /// + /// text containing the items + /// Parent for all read items + /// arraylist where the nodes should be added + /// CHMFile instance + private static void ParseItems( string itemstext, TOCItem parent, ArrayList arrNodes, CHMFile chmFile) + { + int innerTextIdx = ObjectRE.GroupNumberFromName("innerText"); + int innerPTextIdx = ParamRE.GroupNumberFromName("innerText"); + + // get group-name indexes + int nameIndex = AttributesRE.GroupNumberFromName("attributeName"); + int valueIndex = AttributesRE.GroupNumberFromName("attributeValue"); + int tdIndex = AttributesRE.GroupNumberFromName("attributeTD"); + + int nObjStartIndex = 0; + + while( ObjectRE.IsMatch(itemstext, nObjStartIndex) ) + { + Match m = ObjectRE.Match(itemstext, nObjStartIndex); + + string innerText = m.Groups[innerTextIdx].Value; + + TOCItem tocItem = new TOCItem(); + tocItem.TocMode = DataMode.TextBased; + tocItem.AssociatedFile = chmFile; + tocItem.Parent = parent; + + // read parameters + int nParamIndex = 0; + + while( ParamRE.IsMatch(innerText, nParamIndex) ) + { + Match mP = ParamRE.Match(innerText, nParamIndex); + + string innerP = mP.Groups[innerPTextIdx].Value; + + string paramName = ""; + string paramValue = ""; + + int nAttrIdx = 0; + + while( AttributesRE.IsMatch( innerP, nAttrIdx ) ) + { + Match mA = AttributesRE.Match(innerP, nAttrIdx); + + string attributeName = mA.Groups[nameIndex].Value; + string attributeValue = mA.Groups[valueIndex].Value; + string attributeTD = mA.Groups[tdIndex].Value; + + if(attributeTD.Length > 0) + { + // delete the trailing textqualifier + if( attributeValue.Length > 0) + { + int ltqi = attributeValue.LastIndexOf( attributeTD ); + + if(ltqi >= 0) + { + attributeValue = attributeValue.Substring(0,ltqi); + } + } + } + + if( attributeName.ToLower() == "name") + { + paramName = HttpUtility.HtmlDecode(attributeValue); // for unicode encoded values + } + + if( attributeName.ToLower() == "value") + { + paramValue = HttpUtility.HtmlDecode(attributeValue); // for unicode encoded values + // delete trailing / + while((paramValue.Length>0)&&(paramValue[paramValue.Length-1] == '/')) + paramValue = paramValue.Substring(0,paramValue.Length-1); + + } + + nAttrIdx = mA.Index+mA.Length; + } + + tocItem.Params[paramName] = paramValue; + switch(paramName.ToLower()) + { + case "name": + { + tocItem.Name = paramValue; + };break; + case "local": + { + tocItem.Local = paramValue.Replace("../", "").Replace("./", ""); + };break; + case "imagenumber": + { + tocItem.ImageIndex = Int32.Parse(paramValue); + tocItem.ImageIndex-=1; + + int nFolderAdd = 0; + + if((chmFile != null) && (chmFile.ImageTypeFolder)) + { + // get the value which should be added, to display folders instead of books + if(HtmlHelpSystem.UseHH2TreePics) + nFolderAdd = 8; + else + nFolderAdd = 4; + } + + if(tocItem.ImageIndex%2 != 0) + { + if(tocItem.ImageIndex==1) + tocItem.ImageIndex=0; + } + if(HtmlHelpSystem.UseHH2TreePics) + if( tocItem.ImageIndex == 0) + tocItem.ImageIndex = TOCItem.STD_FOLDER_HH2+nFolderAdd; + };break; + case "merge": // this item contains topics or a full TOC from a merged CHM + { + tocItem.MergeLink = paramValue; + + // "register" this item as merge-link + if(_mergeItems==null) + _mergeItems=new ArrayList(); + + _mergeItems.Add(tocItem); + + };break; + case "type": // information type assignment for item + { + tocItem.InfoTypeStrings.Add( paramValue ); + };break; + } + + nParamIndex = mP.Index+mP.Length; + } + + tocItem.ChmFile = chmFile.ChmFilePath; + + if(tocItem.MergeLink.Length > 0) + { + if(_lastTopicItem != null) + { + tocItem.Parent = _lastTopicItem; + _lastTopicItem.Children.Add(tocItem); + } + else + arrNodes.Add( tocItem ); + } + else + { + _lastTopicItem = tocItem; + arrNodes.Add( tocItem ); + } + + nObjStartIndex = m.Index+m.Length; + } + } + + /// + /// Parses the very first <OBJECT> tag in the sitemap file and extracts + /// information types and categories. + /// + /// text of the object tag + /// CHMFile instance + private static void ParseGlobalSettings(string sText, CHMFile chmFile) + { + int innerPTextIdx = ParamRE.GroupNumberFromName("innerText"); + + // get group-name indexes + int nameIndex = AttributesRE.GroupNumberFromName("attributeName"); + int valueIndex = AttributesRE.GroupNumberFromName("attributeValue"); + int tdIndex = AttributesRE.GroupNumberFromName("attributeTD"); + + // read parameters + int nParamIndex = 0; + + // 0... unknown + // 1... inclusinve info type name + // 2... exclusive info type name + // 3... hidden info type name + // 4... category name + // 5... incl infotype name for category + // 6... excl infotype name for category + // 7... hidden infotype name for category + int prevItem = 0; + + string sName = ""; + string sDescription = ""; + string curCategory = ""; + + while( ParamRE.IsMatch(sText, nParamIndex) ) + { + Match mP = ParamRE.Match(sText, nParamIndex); + + string innerP = mP.Groups[innerPTextIdx].Value; + + string paramName = ""; + string paramValue = ""; + + int nAttrIdx = 0; + + while( AttributesRE.IsMatch( innerP, nAttrIdx ) ) + { + Match mA = AttributesRE.Match(innerP, nAttrIdx); + + string attributeName = mA.Groups[nameIndex].Value; + string attributeValue = mA.Groups[valueIndex].Value; + string attributeTD = mA.Groups[tdIndex].Value; + + if(attributeTD.Length > 0) + { + // delete the trailing textqualifier + if( attributeValue.Length > 0) + { + int ltqi = attributeValue.LastIndexOf( attributeTD ); + + if(ltqi >= 0) + { + attributeValue = attributeValue.Substring(0,ltqi); + } + } + } + + if( attributeName.ToLower() == "name") + { + paramName = HttpUtility.HtmlDecode(attributeValue); // for unicode encoded values + } + + if( attributeName.ToLower() == "value") + { + paramValue = HttpUtility.HtmlDecode(attributeValue); // for unicode encoded values + // delete trailing / + while((paramValue.Length>0)&&(paramValue[paramValue.Length-1] == '/')) + paramValue = paramValue.Substring(0,paramValue.Length-1); + + } + + nAttrIdx = mA.Index+mA.Length; + } + + switch(paramName.ToLower()) + { + case "savetype": // inclusive information type name + { + prevItem = 1; + sName = paramValue; + };break; + case "savetypedesc": // description of information type + { + InformationTypeMode mode = InformationTypeMode.Inclusive; + sDescription = paramValue; + + if( prevItem == 1) + mode = InformationTypeMode.Inclusive; + if( prevItem == 2) + mode = InformationTypeMode.Exclusive; + if( prevItem == 3) + mode = InformationTypeMode.Hidden; + + if( chmFile.GetInformationType( sName ) == null) + { + // check if the HtmlHelpSystem already holds such an information type + if( chmFile.SystemInstance.GetInformationType( sName ) == null) + { + // info type not found yet + + InformationType newType = new InformationType(sName, sDescription, mode); + chmFile.InformationTypes.Add(newType); + } + else + { + InformationType sysType = chmFile.SystemInstance.GetInformationType( sName ); + chmFile.InformationTypes.Add( sysType ); + } + } + + prevItem = 0; + };break; + case "saveexclusive": // exclusive information type name + { + prevItem = 2; + sName = paramValue; + };break; + case "savehidden": // hidden information type name + { + prevItem = 3; + sName = paramValue; + };break; + case "category": // category name + { + prevItem = 4; + sName = paramValue; + curCategory = sName; + };break; + case "categorydesc": // category description + { + sDescription = paramValue; + + if( chmFile.GetCategory( sName ) == null) + { + // check if the HtmlHelpSystem already holds such a category + if( chmFile.SystemInstance.GetCategory( sName ) == null) + { + // add category + Category newCat = new Category(sName, sDescription); + chmFile.Categories.Add(newCat); + } + else + { + Category sysCat = chmFile.SystemInstance.GetCategory( sName ); + chmFile.Categories.Add( sysCat ); + } + } + + prevItem = 0; + };break; + case "type": // inclusive information type which is member of the previously read category + { + prevItem = 5; + sName = paramValue; + };break; + case "typedesc": // description of type for category + { + sDescription = paramValue; + Category cat = chmFile.GetCategory( curCategory ); + + if( cat != null) + { + // category found + InformationType infoType = chmFile.GetInformationType( sName ); + + if( infoType != null) + { + if( !cat.ContainsInformationType(infoType)) + { + infoType.SetCategoryFlag(true); + cat.AddInformationType(infoType); + } + } + } + + prevItem = 0; + };break; + case "typeexclusive": // exclusive information type which is member of the previously read category + { + prevItem = 6; + sName = paramValue; + };break; + case "typehidden": // hidden information type which is member of the previously read category + { + prevItem = 7; + sName = paramValue; + };break; + default: + { + prevItem = 0; + sName = ""; + sDescription = ""; + };break; + } + + nParamIndex = mP.Index+mP.Length; + } + } + } +} diff --git a/irc/TechBot/CHMLibrary/CHMDecoding/HHCParser2.cs b/irc/TechBot/CHMLibrary/CHMDecoding/HHCParser2.cs new file mode 100644 index 00000000000..bdb988a8665 --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMDecoding/HHCParser2.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections; +using System.Text; +using System.Text.RegularExpressions; + +namespace HtmlHelp.ChmDecoding +{ + /// + /// The class HHCParser implements a parser for HHC contents files. + /// + // internal sealed class HHCParser : IHHCParser + public class HHCParser2 + { + static private string m_text1=""; + static private string m_text2=""; + static private int m_CurrentPos=0; + + /// + /// Parses a HHC file and returns an ArrayList with the table of contents (TOC) tree + /// + /// string content of the hhc file + /// CHMFile instance + /// Returns an ArrayList with the table of contents (TOC) tree + public static ArrayList ParseHHC(string hhcFile, CHMFile chmFile) + { + DateTime StartTime=DateTime.Now; + + ArrayList tocList = new ArrayList(); + + m_text2=hhcFile; + m_text1=hhcFile.ToLower(); + + int idx=m_text1.IndexOf("
        "); + if (idx==-1) + return null; + m_CurrentPos=idx+4; + + ParamRE = new Regex(RE_ParamBoundaries, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + AttributesRE = new Regex(RE_QuoteAttributes, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + + ParseTree(tocList,chmFile); + + DateTime EndTime=DateTime.Now; + TimeSpan Diff=EndTime-StartTime; + string x=Diff.ToString(); + + return tocList; + } + + /// + /// Recursively parses a sitemap tree + /// + /// content text + /// arraylist which receives the extracted nodes + /// CHMFile instance + static private void ParseTree( ArrayList arrNodes, CHMFile chmFile ) + { + bool bProcessing=true; + do + { + bProcessing=false; + + // Indent + int idxa=m_text1.IndexOf("
          ",m_CurrentPos); + int idxb=m_text1.IndexOf("
        • ",m_CurrentPos); + int idxc=m_text1.IndexOf("
        ",m_CurrentPos); + + if ((idxa-1)) + { + bProcessing=true; + m_CurrentPos=idxa+4; + if (arrNodes.Count<1) + { + ParseTree(arrNodes,chmFile); + } + else + { + ParseTree(((TOCItem)(arrNodes[arrNodes.Count-1])).Children,chmFile); + } + continue; + } + + // new item + if ((idxb-1)) + { + + bProcessing=true; + m_CurrentPos=idxb+4; + + int idx2=m_text1.IndexOf("",idx2+7); + if (idx3!=-1) + { + string text=m_text2.Substring(idx2,idx3-idx2); + + m_CurrentPos=idx3+9; + + // Parse items in text. + TOCItem tocItem=ParseItems(text, chmFile); + if (tocItem!=null) + { + arrNodes.Add(tocItem); + } + } + } + } + + // Undent + if ((idxc-1)) + { + m_CurrentPos=idxc+5; + bProcessing=true; + return; + } + } + while (bProcessing); + } + + + private static string RE_ParamBoundaries = @"\.*?)\>"; + private const string RE_QuoteAttributes = @"( |\t)*(?[\-a-zA-Z0-9]*)( |\t)*=( |\t)*(?[\""\'])?(?.*?(?(attributeTD)\k|([\s>]|.$)))"; + private static Regex ParamRE; + private static Regex AttributesRE; + + /// + /// Parses tree nodes from the text + /// + /// text containing the items + /// arraylist where the nodes should be added + /// CHMFile instance + private static TOCItem ParseItems( string itemstext, CHMFile chmFile) + { + int innerPTextIdx = ParamRE.GroupNumberFromName("innerText"); + + // get group-name indexes + int nameIndex = AttributesRE.GroupNumberFromName("attributeName"); + int valueIndex = AttributesRE.GroupNumberFromName("attributeValue"); + int tdIndex = AttributesRE.GroupNumberFromName("attributeTD"); + + TOCItem tocItem = new TOCItem(); + + // read parameters + int nParamIndex = 0; + + while( ParamRE.IsMatch(itemstext, nParamIndex) ) + { + Match mP = ParamRE.Match(itemstext, nParamIndex); + + string innerP = mP.Groups[innerPTextIdx].Value; + + string paramName = ""; + string paramValue = ""; + + int nAttrIdx = 0; + + while( AttributesRE.IsMatch( innerP, nAttrIdx ) ) + { + Match mA = AttributesRE.Match(innerP, nAttrIdx); + + string attributeName = mA.Groups[nameIndex].Value; + string attributeValue = mA.Groups[valueIndex].Value; + string attributeTD = mA.Groups[tdIndex].Value; + + if(attributeTD.Length > 0) + { + // delete the trailing textqualifier + if( attributeValue.Length > 0) + { + int ltqi = attributeValue.LastIndexOf( attributeTD ); + + if(ltqi >= 0) + { + attributeValue = attributeValue.Substring(0,ltqi); + } + } + } + + if( attributeName.ToLower() == "name") + { + paramName = attributeValue; + } + + if( attributeName.ToLower() == "value") + { + paramValue = attributeValue; + } + + nAttrIdx = mA.Index+mA.Length; + } + + tocItem.Params[paramName] = paramValue; + switch(paramName.ToLower()) + { + case "name": + { + tocItem.Name = paramValue; + };break; + case "local": + { + tocItem.Local = paramValue; + };break; + case "imagenumber": + { + tocItem.ImageIndex = Int32.Parse(paramValue); + + if( tocItem.ImageIndex == 2) + tocItem.ImageIndex = TOCItem.STD_FOLDER_HH1; + };break; + } + + nParamIndex = mP.Index+mP.Length; + } + + tocItem.ChmFile = chmFile.ChmFilePath; + return tocItem; + } + } +} diff --git a/irc/TechBot/CHMLibrary/CHMDecoding/HHKParser.cs b/irc/TechBot/CHMLibrary/CHMDecoding/HHKParser.cs new file mode 100644 index 00000000000..63f1c136799 --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMDecoding/HHKParser.cs @@ -0,0 +1,550 @@ +using System; +using System.IO; +using System.Collections; +using System.Text; +using System.Text.RegularExpressions; + +namespace HtmlHelp.ChmDecoding +{ + /// + /// The class HHKParser implements a parser for HHK contents files. + /// + internal sealed class HHKParser + { + /// + /// regular expressions for replacing the sitemap boundary tags + /// + private static string RE_ULOpening = @"\"; // will be replaced by a '(' for nested parsing + private static string RE_ULClosing = @"\"; // will be replaced by a ')' for nested parsing + + /// + /// Matching ul-tags + /// + private static string RE_ULBoundaries = @"\(?.*)\"; + /// + /// Matching the nested tree structure. + /// + private static string RE_NestedBoundaries = @"\( (?> [^()]+ | \( (?) | \) (?<-DEPTH>) )* (?(DEPTH)(?!)) \)"; + /// + /// Matching object-tags + /// + private static string RE_ObjectBoundaries = @"\.*?)\"; + /// + /// Matching param tags + /// + private static string RE_ParamBoundaries = @"\.*?)\>"; + /// + /// Extracting tag attributes + /// + private const string RE_QuoteAttributes = @"( |\t)*(?[\-a-zA-Z0-9]*)( |\t)*=( |\t)*(?[\""\'])?(?.*?(?(attributeTD)\k|([\s>]|.$)))"; + + /// + /// private regular expressionobjects + /// + private static Regex ulRE; + private static Regex NestedRE; + private static Regex ObjectRE; + private static Regex ParamRE; + private static Regex AttributesRE; + + /// + /// Parses a HHK file and returns an ArrayList with the index tree + /// + /// string content of the hhk file + /// CHMFile instance + /// Returns an ArrayList with the index tree + public static ArrayList ParseHHK(string hhkFile, CHMFile chmFile) + { + ArrayList indexList = new ArrayList(); + + ulRE = new Regex(RE_ULBoundaries, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + NestedRE = new Regex(RE_NestedBoundaries, RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + ObjectRE = new Regex(RE_ObjectBoundaries, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + ParamRE = new Regex(RE_ParamBoundaries, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + AttributesRE = new Regex(RE_QuoteAttributes, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + + int innerTextIdx = ulRE.GroupNumberFromName("innerText"); + + if( ulRE.IsMatch(hhkFile, 0) ) + { + Match m = ulRE.Match(hhkFile, 0); + + if( ObjectRE.IsMatch(hhkFile, 0) ) // first object block contains information types and categories + { + Match mO = ObjectRE.Match(hhkFile, 0); + int iOTxt = ObjectRE.GroupNumberFromName("innerText"); + + string globalText = mO.Groups[iOTxt].Value; + + ParseGlobalSettings( globalText, chmFile ); + } + + string innerText = m.Groups["innerText"].Value; + + innerText = innerText.Replace("(", "("); + innerText = innerText.Replace(")", ")"); + innerText = Regex.Replace(innerText, RE_ULOpening, "(", RegexOptions.IgnoreCase); + innerText = Regex.Replace(innerText, RE_ULClosing, ")", RegexOptions.IgnoreCase); + + ParseTree( innerText, null, indexList, chmFile ); + } + + return indexList; + } + + /// + /// Recursively parses a sitemap tree + /// + /// content text + /// Parent for all read items + /// arraylist which receives the extracted nodes + /// CHMFile instance + private static void ParseTree( string text, IndexItem parent, ArrayList arrNodes, CHMFile chmFile ) + { + string strPreItems="", strPostItems=""; + string innerText = ""; + + int nIndex = 0; + + while( NestedRE.IsMatch(text, nIndex) ) + { + Match m = NestedRE.Match(text, nIndex); + + innerText = m.Value.Substring( 1, m.Length-2); + + strPreItems = text.Substring(nIndex,m.Index-nIndex); + + ParseItems(strPreItems, parent, arrNodes, chmFile); + + if((arrNodes.Count>0) && (innerText.Length > 0) ) + { + IndexItem p = ((IndexItem)(arrNodes[arrNodes.Count-1])); + ParseTree( innerText, p, arrNodes, chmFile ); + } + + nIndex = m.Index+m.Length; + } + + if( nIndex == 0) + { + strPostItems = text.Substring(nIndex, text.Length-nIndex); + ParseItems(strPostItems, parent, arrNodes, chmFile); + } + else if( nIndex < text.Length-1) + { + strPostItems = text.Substring(nIndex, text.Length-nIndex); + ParseTree(strPostItems, parent, arrNodes, chmFile); + } + } + + + /// + /// Parses nodes from the text + /// + /// text containing the items + /// parent index item + /// arraylist where the nodes should be added + /// CHMFile instance + private static void ParseItems( string itemstext, IndexItem parentItem, ArrayList arrNodes, CHMFile chmFile) + { + int innerTextIdx = ObjectRE.GroupNumberFromName("innerText"); + int innerPTextIdx = ParamRE.GroupNumberFromName("innerText"); + + // get group-name indexes + int nameIndex = AttributesRE.GroupNumberFromName("attributeName"); + int valueIndex = AttributesRE.GroupNumberFromName("attributeValue"); + int tdIndex = AttributesRE.GroupNumberFromName("attributeTD"); + + int nObjStartIndex = 0; + int nLastObjStartIndex = 0; + string sKeyword = ""; + + while( ObjectRE.IsMatch(itemstext, nObjStartIndex) ) + { + Match m = ObjectRE.Match(itemstext, nObjStartIndex); + + string innerText = m.Groups[innerTextIdx].Value; + + IndexItem idxItem = new IndexItem(); + + // read parameters + int nParamIndex = 0; + int nNameCnt = 0; + + string paramTitle = ""; + string paramLocal = ""; + bool bAdded = false; + + while( ParamRE.IsMatch(innerText, nParamIndex) ) + { + Match mP = ParamRE.Match(innerText, nParamIndex); + + string innerP = mP.Groups[innerPTextIdx].Value; + + string paramName = ""; + string paramValue = ""; + + int nAttrIdx = 0; + //sKeyword = ""; + + while( AttributesRE.IsMatch( innerP, nAttrIdx ) ) + { + Match mA = AttributesRE.Match(innerP, nAttrIdx); + + string attributeName = mA.Groups[nameIndex].Value; + string attributeValue = mA.Groups[valueIndex].Value; + string attributeTD = mA.Groups[tdIndex].Value; + + if(attributeTD.Length > 0) + { + // delete the trailing textqualifier + if( attributeValue.Length > 0) + { + int ltqi = attributeValue.LastIndexOf( attributeTD ); + + if(ltqi >= 0) + { + attributeValue = attributeValue.Substring(0,ltqi); + } + } + } + + if( attributeName.ToLower() == "name") + { + paramName = HttpUtility.HtmlDecode(attributeValue); // for unicode encoded values + nNameCnt++; + } + + if( attributeName.ToLower() == "value") + { + paramValue = HttpUtility.HtmlDecode(attributeValue); // for unicode encoded values + // delete trailing / + while((paramValue.Length>0)&&(paramValue[paramValue.Length-1] == '/')) + paramValue = paramValue.Substring(0,paramValue.Length-1); + } + + nAttrIdx = mA.Index+mA.Length; + } + + if( nNameCnt == 1) // first "Name" param = keyword + { + sKeyword = ""; + + if(parentItem != null) + sKeyword = parentItem.KeyWordPath + ","; + + string sOldKW = sKeyword; + + sKeyword += paramValue; + + IndexItem idxFind = FindByKeyword(arrNodes, sKeyword); + + if(idxFind != null) + { + idxItem = idxFind; + } + else + { + if( sKeyword.Split(new char[] {','}).Length > 1 ) + { + idxItem.CharIndex = sKeyword.Length - paramValue.Length; + } + else + { + sKeyword = paramValue; + sOldKW = sKeyword; + idxItem.CharIndex = 0; + } + + idxItem.KeyWordPath = sKeyword; + idxItem.Indent = sKeyword.Split(new char[] {','}).Length - 1; + idxItem.IsSeeAlso = false; + + sKeyword = sOldKW; + } + } + else + { + + if( (nNameCnt > 2) && (paramName.ToLower()=="name") ) + { + bAdded = true; + IndexTopic idxTopic = new IndexTopic(paramTitle, paramLocal, chmFile.CompileFile, chmFile.ChmFilePath); + + idxItem.Topics.Add( idxTopic ); + + paramTitle = ""; + paramLocal = ""; + } + + switch(paramName.ToLower()) + { + case "name": + //case "keyword": + { + paramTitle = paramValue; + };break; + case "local": + { + paramLocal = paramValue.Replace("../", "").Replace("./", ""); + };break; + case "type": // information type assignment for item + { + idxItem.InfoTypeStrings.Add( paramValue ); + };break; + case "see also": + { + idxItem.AddSeeAlso(paramValue); + idxItem.IsSeeAlso = true; + bAdded = true; + };break; + } + } + + nParamIndex = mP.Index+mP.Length; + } + + if(!bAdded) + { + bAdded=false; + IndexTopic idxTopic = new IndexTopic(paramTitle, paramLocal, chmFile.CompileFile, chmFile.ChmFilePath); + + idxItem.Topics.Add( idxTopic ); + + paramTitle = ""; + paramLocal = ""; + } + + idxItem.ChmFile = chmFile; + arrNodes.Add( idxItem ); + + nLastObjStartIndex = nObjStartIndex; + nObjStartIndex = m.Index+m.Length; + } + } + + /// + /// Searches an index-keyword in the index list + /// + /// index list to search + /// keyword to find + /// Returns an IndexItem instance if found, otherwise null. + private static IndexItem FindByKeyword(ArrayList indexList, string Keyword) + { + foreach(IndexItem curItem in indexList) + { + if( curItem.KeyWordPath == Keyword) + return curItem; + } + + return null; + } + + /// + /// Parses the very first <OBJECT> tag in the sitemap file and extracts + /// information types and categories. + /// + /// text of the object tag + /// CHMFile instance + private static void ParseGlobalSettings(string sText, CHMFile chmFile) + { + int innerPTextIdx = ParamRE.GroupNumberFromName("innerText"); + + // get group-name indexes + int nameIndex = AttributesRE.GroupNumberFromName("attributeName"); + int valueIndex = AttributesRE.GroupNumberFromName("attributeValue"); + int tdIndex = AttributesRE.GroupNumberFromName("attributeTD"); + + // read parameters + int nParamIndex = 0; + + // 0... unknown + // 1... inclusinve info type name + // 2... exclusive info type name + // 3... hidden info type name + // 4... category name + // 5... incl infotype name for category + // 6... excl infotype name for category + // 7... hidden infotype name for category + int prevItem = 0; + + string sName = ""; + string sDescription = ""; + string curCategory = ""; + + while( ParamRE.IsMatch(sText, nParamIndex) ) + { + Match mP = ParamRE.Match(sText, nParamIndex); + + string innerP = mP.Groups[innerPTextIdx].Value; + + string paramName = ""; + string paramValue = ""; + + int nAttrIdx = 0; + + while( AttributesRE.IsMatch( innerP, nAttrIdx ) ) + { + Match mA = AttributesRE.Match(innerP, nAttrIdx); + + string attributeName = mA.Groups[nameIndex].Value; + string attributeValue = mA.Groups[valueIndex].Value; + string attributeTD = mA.Groups[tdIndex].Value; + + if(attributeTD.Length > 0) + { + // delete the trailing textqualifier + if( attributeValue.Length > 0) + { + int ltqi = attributeValue.LastIndexOf( attributeTD ); + + if(ltqi >= 0) + { + attributeValue = attributeValue.Substring(0,ltqi); + } + } + } + + if( attributeName.ToLower() == "name") + { + paramName = HttpUtility.HtmlDecode(attributeValue); // for unicode encoded values + } + + if( attributeName.ToLower() == "value") + { + paramValue = HttpUtility.HtmlDecode(attributeValue); // for unicode encoded values + // delete trailing / + while((paramValue.Length>0)&&(paramValue[paramValue.Length-1] == '/')) + paramValue = paramValue.Substring(0,paramValue.Length-1); + + } + + nAttrIdx = mA.Index+mA.Length; + } + + switch(paramName.ToLower()) + { + case "savetype": // inclusive information type name + { + prevItem = 1; + sName = paramValue; + };break; + case "savetypedesc": // description of information type + { + InformationTypeMode mode = InformationTypeMode.Inclusive; + sDescription = paramValue; + + if( prevItem == 1) + mode = InformationTypeMode.Inclusive; + if( prevItem == 2) + mode = InformationTypeMode.Exclusive; + if( prevItem == 3) + mode = InformationTypeMode.Hidden; + + if( chmFile.GetInformationType( sName ) == null) + { + // check if the HtmlHelpSystem already holds such an information type + if( chmFile.SystemInstance.GetInformationType( sName ) == null) + { + // info type not found yet + + InformationType newType = new InformationType(sName, sDescription, mode); + chmFile.InformationTypes.Add(newType); + } + else + { + InformationType sysType = chmFile.SystemInstance.GetInformationType( sName ); + chmFile.InformationTypes.Add( sysType ); + } + } + + prevItem = 0; + };break; + case "saveexclusive": // exclusive information type name + { + prevItem = 2; + sName = paramValue; + };break; + case "savehidden": // hidden information type name + { + prevItem = 3; + sName = paramValue; + };break; + case "category": // category name + { + prevItem = 4; + sName = paramValue; + curCategory = sName; + };break; + case "categorydesc": // category description + { + sDescription = paramValue; + + if( chmFile.GetCategory( sName ) == null) + { + // check if the HtmlHelpSystem already holds such a category + if( chmFile.SystemInstance.GetCategory( sName ) == null) + { + // add category + Category newCat = new Category(sName, sDescription); + chmFile.Categories.Add(newCat); + } + else + { + Category sysCat = chmFile.SystemInstance.GetCategory( sName ); + chmFile.Categories.Add( sysCat ); + } + } + + prevItem = 0; + };break; + case "type": // inclusive information type which is member of the previously read category + { + prevItem = 5; + sName = paramValue; + };break; + case "typedesc": // description of type for category + { + sDescription = paramValue; + Category cat = chmFile.GetCategory( curCategory ); + + if( cat != null) + { + // category found + InformationType infoType = chmFile.GetInformationType( sName ); + + if( infoType != null) + { + if( !cat.ContainsInformationType(infoType)) + { + infoType.SetCategoryFlag(true); + cat.AddInformationType(infoType); + } + } + } + + prevItem = 0; + };break; + case "typeexclusive": // exclusive information type which is member of the previously read category + { + prevItem = 6; + sName = paramValue; + };break; + case "typehidden": // hidden information type which is member of the previously read category + { + prevItem = 7; + sName = paramValue; + };break; + default: + { + prevItem = 0; + sName = ""; + sDescription = ""; + };break; + } + + nParamIndex = mP.Index+mP.Length; + } + } + } +} diff --git a/irc/TechBot/CHMLibrary/CHMDecoding/TopicEntry.cs b/irc/TechBot/CHMLibrary/CHMDecoding/TopicEntry.cs new file mode 100644 index 00000000000..16bde7d9d0d --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMDecoding/TopicEntry.cs @@ -0,0 +1,245 @@ +using System; +using System.IO; + +namespace HtmlHelp.ChmDecoding +{ + /// + /// The class TopicEntry stores the data for one topic entry + /// + internal sealed class TopicEntry + { + /// + /// Internal member storing the offset of this topic entry + /// + private int _entryOffset = 0; + /// + /// Internal member storing the index of the binary toc + /// + private int _tocidxOffset = 0; + /// + /// Internal member storing the string offset of the title + /// + private int _titleOffset = 0; + /// + /// Internal member storuing the urltable offset + /// + private int _urltableOffset = 0; + /// + /// Internal member storing the visibility mode + /// + private int _visibilityMode = 0; + /// + /// Internal member storing an unknown mode + /// + private int _unknownMode = 0; + /// + /// Internal member storing the associated chmfile object + /// + private CHMFile _associatedFile = null; + + /// + /// Constructor of the class + /// + /// offset of this entry + /// offset in the binary toc index + /// offset of the title (in the #STRINGS file) + /// offset in the urltable containing the urlstr offset for the url + /// visibility mode 2 indicates not in contents, 6 indicates that it is in the contents, 0/4 something else (unknown) + /// 0, 2, 4, 8, 10, 12, 16, 32 (unknown) + public TopicEntry(int entryOffset, int tocidxOffset, int titleOffset, int urltableOffset, int visibilityMode, int unknownMode) :this(entryOffset, tocidxOffset, titleOffset, urltableOffset, visibilityMode, unknownMode, null) + { + + } + + /// + /// Constructor of the class + /// + /// offset of this entry + /// offset in the binary toc index + /// offset of the title (in the #STRINGS file) + /// offset in the urltable containing the urlstr offset for the url + /// visibility mode 2 indicates not in contents, 6 indicates that it is in the contents, 0/4 something else (unknown) + /// 0, 2, 4, 8, 10, 12, 16, 32 (unknown) + /// associated chmfile object + internal TopicEntry(int entryOffset, int tocidxOffset, int titleOffset, int urltableOffset, int visibilityMode, int unknownMode, CHMFile associatedFile) + { + _entryOffset = entryOffset; + _tocidxOffset = tocidxOffset; + _titleOffset = titleOffset; + _urltableOffset = urltableOffset; + _visibilityMode = visibilityMode; + _unknownMode = unknownMode; + _associatedFile = associatedFile; + } + + /// + /// Standard constructor + /// + internal TopicEntry() + { + } + + #region Data dumping + /// + /// Dump the class data to a binary writer + /// + /// writer to write the data + internal void Dump(ref BinaryWriter writer) + { + writer.Write( _entryOffset ); + writer.Write( _tocidxOffset ); + writer.Write( _titleOffset ); + writer.Write( _urltableOffset ); + writer.Write( _visibilityMode ); + writer.Write( _unknownMode ); + } + + /// + /// Reads the object data from a dump store + /// + /// reader to read the data + internal void ReadDump(ref BinaryReader reader) + { + _entryOffset = reader.ReadInt32(); + _tocidxOffset = reader.ReadInt32(); + _titleOffset = reader.ReadInt32(); + _urltableOffset = reader.ReadInt32(); + _visibilityMode = reader.ReadInt32(); + _unknownMode = reader.ReadInt32(); + } + + /// + /// Sets the associated CHMFile instance + /// + /// instance to set + internal void SetCHMFile(CHMFile associatedFile) + { + _associatedFile = associatedFile; + } + #endregion + + /// + /// Gets the associated chm file + /// + internal CHMFile ChmFile + { + get { return _associatedFile; } + } + + /// + /// Gets the offset of this entry + /// + internal int EntryOffset + { + get { return _entryOffset; } + } + + /// + /// Gets the tocidx offset + /// + internal int TOCIdxOffset + { + get { return _tocidxOffset; } + } + + /// + /// Gets the title offset of the #STRINGS file + /// + internal int TitleOffset + { + get { return _titleOffset; } + } + + /// + /// Gets the urltable offset + /// + internal int UrlTableOffset + { + get { return _urltableOffset; } + } + + /// + /// Gets the title of the topic entry + /// + public string Title + { + get + { + if( _associatedFile == null) + return String.Empty; + + if( _associatedFile.StringsFile == null) + return String.Empty; + + string sTemp = (string)_associatedFile.StringsFile[ _titleOffset ]; + + if(sTemp == null) + return String.Empty; + + return sTemp; + } + } + + /// + /// Gets the url of the topic + /// + public string Locale + { + get + { + if( _associatedFile == null) + return String.Empty; + + if( _associatedFile.UrltblFile == null) + return String.Empty; + + UrlTableEntry utEntry = (UrlTableEntry)_associatedFile.UrltblFile[ _urltableOffset ]; + + if(utEntry == null) + return String.Empty; + + if(utEntry.URL == "") + return String.Empty; + + return utEntry.URL; + } + } + + /// + /// Gets the URL of this topic + /// + public string URL + { + get + { + if(Locale.Length <= 0) + return "about:blank"; + + if( (Locale.ToLower().IndexOf("http://") >= 0) || + (Locale.ToLower().IndexOf("https://") >= 0) || + (Locale.ToLower().IndexOf("mailto:") >= 0) || + (Locale.ToLower().IndexOf("ftp://") >= 0) || + (Locale.ToLower().IndexOf("ms-its:") >= 0)) + return Locale; + + return HtmlHelpSystem.UrlPrefix + _associatedFile.ChmFilePath + "::/" + Locale; + } + } + + /// + /// Gets the visibility mode + /// + public int VisibilityMode + { + get { return _visibilityMode; } + } + + /// + /// Gets the unknown mode + /// + public int UknownMode + { + get { return _unknownMode; } + } + } +} diff --git a/irc/TechBot/CHMLibrary/CHMDecoding/UrlTableEntry.cs b/irc/TechBot/CHMLibrary/CHMDecoding/UrlTableEntry.cs new file mode 100644 index 00000000000..53391ce2f05 --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMDecoding/UrlTableEntry.cs @@ -0,0 +1,175 @@ +using System; +using System.IO; + +namespace HtmlHelp.ChmDecoding +{ + /// + /// The class UrlTableEntry stores data for an URL-Table entry + /// + internal sealed class UrlTableEntry + { + /// + /// Internal member storing the offset of this entry + /// + private int _entryOffset = 0; + /// + /// Internal member storing a unique id + /// + private uint _uniqueID = 0; + /// + /// Internal member storing the topics index + /// + private int _topicsIndex = 0; + /// + /// Internal member storing the offset in the urlstr table + /// + private int _urlStrOffset = 0; + /// + /// Internal member storing the associated chmfile object + /// + private CHMFile _associatedFile = null; + + /// + /// Constructor of the class + /// + /// unique id + /// offset of the entry + /// topic index + /// urlstr offset for filename + public UrlTableEntry(uint uniqueID, int entryOffset, int topicIndex, int urlstrOffset) : this(uniqueID, entryOffset, topicIndex, urlstrOffset, null) + { + } + + /// + /// Constructor of the class + /// + /// unique id + /// offset of the entry + /// topic index + /// urlstr offset for filename + /// associated chm file + internal UrlTableEntry(uint uniqueID, int entryOffset, int topicIndex, int urlstrOffset, CHMFile associatedFile) + { + _uniqueID = uniqueID; + _entryOffset = entryOffset; + _topicsIndex = topicIndex; + _urlStrOffset = urlstrOffset; + _associatedFile = associatedFile; + } + + /// + /// Standard constructor + /// + internal UrlTableEntry() + { + } + + #region Data dumping + /// + /// Dump the class data to a binary writer + /// + /// writer to write the data + internal void Dump(ref BinaryWriter writer) + { + writer.Write( _urlStrOffset ); + writer.Write( _entryOffset ); + writer.Write( _topicsIndex ); + writer.Write( _urlStrOffset ); + } + + /// + /// Reads the object data from a dump store + /// + /// reader to read the data + internal void ReadDump(ref BinaryReader reader) + { + _urlStrOffset = reader.ReadInt32(); + _entryOffset = reader.ReadInt32(); + _topicsIndex = reader.ReadInt32(); + _urlStrOffset = reader.ReadInt32(); + } + + /// + /// Sets the associated CHMFile instance + /// + /// instance to set + internal void SetCHMFile(CHMFile associatedFile) + { + _associatedFile = associatedFile; + } + #endregion + + /// + /// Gets the unique id of the entry + /// + internal uint UniqueID + { + get {return _uniqueID; } + } + + /// + /// Gets the offset of the entry + /// + internal int EntryOffset + { + get {return _entryOffset; } + } + + /// + /// Gets the topics index + /// + internal int TopicIndex + { + get {return _topicsIndex; } + } + + /// + /// Gets the urlstr offset + /// + internal int UrlstrOffset + { + get { return _urlStrOffset; } + } + + /// + /// Gets the url of the entry + /// + public string URL + { + get + { + if(_associatedFile == null) + return String.Empty; + + if(_associatedFile.UrlstrFile == null) + return String.Empty; + + string sTemp = (string)_associatedFile.UrlstrFile.GetURLatOffset( _urlStrOffset ); + + if( sTemp == null) + return String.Empty; + + return sTemp; + } + } + + /// + /// Gets the associated topic for this url entry + /// + internal TopicEntry Topic + { + get + { + if(_associatedFile == null) + return null; + + if(_associatedFile.TopicsFile == null) + return null; + + TopicEntry tentry = _associatedFile.TopicsFile[ _topicsIndex*16 ]; + + return tentry; + } + } + } +} diff --git a/irc/TechBot/CHMLibrary/CHMDecoding/enumerations.cs b/irc/TechBot/CHMLibrary/CHMDecoding/enumerations.cs new file mode 100644 index 00000000000..0e2d314ecd6 --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMDecoding/enumerations.cs @@ -0,0 +1,19 @@ +using System; + +namespace HtmlHelp.ChmDecoding +{ + /// + /// Enumeration for specifying the extraction mode of an toc or index item. + /// + public enum DataMode + { + /// + /// TextBased - this item comes from a text-based sitemap file + /// + TextBased = 0, + /// + /// Binary - this item was extracted out of a binary stream + /// + Binary = 1 + } +} diff --git a/irc/TechBot/CHMLibrary/CHMLibrary.cmbx b/irc/TechBot/CHMLibrary/CHMLibrary.cmbx new file mode 100644 index 00000000000..7209cbb462c --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMLibrary.cmbx @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/irc/TechBot/CHMLibrary/CHMLibrary.prjx b/irc/TechBot/CHMLibrary/CHMLibrary.prjx new file mode 100644 index 00000000000..3a888b43106 --- /dev/null +++ b/irc/TechBot/CHMLibrary/CHMLibrary.prjx @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/irc/TechBot/CHMLibrary/Category.cs b/irc/TechBot/CHMLibrary/Category.cs new file mode 100644 index 00000000000..d1ae4e74d44 --- /dev/null +++ b/irc/TechBot/CHMLibrary/Category.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections; +using System.IO; + +using HtmlHelp.ChmDecoding; + +namespace HtmlHelp +{ + /// + /// The class Category implements methods/properties for handling an information category + /// + /// Note: Information types and categories allow users to filter help contents. + /// They are only supported if using sitemap TOC and/or sitemap Index. + public class Category + { + private string _name = ""; + private string _description = ""; + private ArrayList _infoTypes = null; + private int _referenceCount = 1; + + /// + /// Standard constructor + /// + public Category() : this("","") + { + } + + /// + /// Standard constructor + /// + /// name of the category + /// description + public Category(string name, string description) : this(name, description, new ArrayList()) + { + } + /// + /// Standard constructor + /// + /// name of the category + /// description + /// Arraylist of InformationType instances which applies to this category + public Category(string name, string description, ArrayList linkedInformationTypes) + { + _name = name; + _description = description; + _infoTypes = linkedInformationTypes; + } + + #region Data dumping + /// + /// Dump the class data to a binary writer + /// + /// writer to write the data + internal void Dump(ref BinaryWriter writer) + { + writer.Write( _name ); + writer.Write( _description ); + + writer.Write( _infoTypes.Count ); + + for(int i=0; i<_infoTypes.Count;i++) + { + InformationType curType = _infoTypes[i] as InformationType; + writer.Write( curType.Name ); + } + + } + + /// + /// Reads the object data from a dump store + /// + /// reader to read the data + /// current CHMFile instance which reads from dump + internal void ReadDump(ref BinaryReader reader, CHMFile chmFile) + { + _name = reader.ReadString(); + _description = reader.ReadString(); + + int nCnt = reader.ReadInt32(); + + for(int i=0; i + /// Merges the lineked information types from cat into this instance + /// + /// category instance + internal void MergeInfoTypes(Category cat) + { + if(cat!=null) + { + if(cat.InformationTypes.Count > 0) + { + for(int i=0;i + /// Gets/Sets the reference count of this information type instance + /// + internal int ReferenceCount + { + get { return _referenceCount; } + set { _referenceCount = value; } + } + + /// + /// Gets/Sets the name of the information type + /// + public string Name + { + get { return _name; } + set { _name = value; } + } + + /// + /// Gets/Sets the description of the information type + /// + public string Description + { + get { return _description; } + set { _name = value; } + } + + /// + /// Gets an ArrayList with the linked Information types + /// + public ArrayList InformationTypes + { + get { return _infoTypes; } + } + + /// + /// Adds a new information type to the category + /// + /// + public void AddInformationType(InformationType type) + { + _infoTypes.Add(type); + } + + /// + /// Removes an information type from the category + /// + /// + public void RemoveInformationType(InformationType type) + { + _infoTypes.Remove(type); + } + + /// + /// Checks if the category contains an information type + /// + /// information type instance to check + /// Return true if the information type is part of this category + public bool ContainsInformationType(InformationType type) + { + return _infoTypes.Contains(type); + } + + /// + /// Checks if the category contains an information type + /// + /// name of the information type + /// Return true if the information type is part of this category + public bool ContainsInformationType(string name) + { + for(int i=0;i<_infoTypes.Count;i++) + { + InformationType curType = _infoTypes[i] as InformationType; + + if(curType.Name == name) + return true; + } + + return false; + } + } +} diff --git a/irc/TechBot/CHMLibrary/ChmFileInfo.cs b/irc/TechBot/CHMLibrary/ChmFileInfo.cs new file mode 100644 index 00000000000..15f6a1e266f --- /dev/null +++ b/irc/TechBot/CHMLibrary/ChmFileInfo.cs @@ -0,0 +1,478 @@ +using System; +using System.Collections; +using System.Text; +using System.IO; +using System.Globalization; +using System.Diagnostics; +using System.ComponentModel; + +using HtmlHelp.ChmDecoding; +// using HtmlHelp.Storage; + +namespace HtmlHelp +{ + /// + /// The class ChmFileInfo only extracts system information from a CHM file. + /// It doesn't build the index and table of contents. + /// + public class ChmFileInfo + { + /// + /// Internal member storing the full filename + /// + private string _chmFileName = ""; + /// + /// Internal member storing the associated chmfile object + /// + private CHMFile _associatedFile = null; + + /// + /// Constructor for extrating the file information of the provided file. + /// The constructor opens the chm-file and reads its system data. + /// + /// full file name which information should be extracted + public ChmFileInfo(string chmFile) + { + if(!File.Exists(chmFile)) + throw new ArgumentException("Chm file must exist on disk !", "chmFileName"); + + if( ! chmFile.ToLower().EndsWith(".chm") ) + throw new ArgumentException("HtmlHelp file must have the extension .chm !", "chmFile"); + + _chmFileName = chmFile; + _associatedFile = new CHMFile(null, chmFile, true); // only load system data of chm + } + + /// + /// Internal constructor used in the class CHMFile. + /// + /// associated chm file + internal ChmFileInfo(CHMFile associatedFile) + { + _associatedFile = associatedFile; + + if( _associatedFile == null) + throw new ArgumentException("Associated CHMFile instance must not be null !", "associatedFile"); + } + + #region default info properties + /// + /// Gets the full filename of the chm file + /// + public string ChmFileName + { + get + { + return _associatedFile.ChmFilePath; + } + } + + /// + /// Gets a FileInfo instance for the chm file. + /// + public FileInfo FileInfo + { + get { return new FileInfo(_associatedFile.ChmFilePath); } + } + #endregion + + #region #SYSTEM properties + /// + /// Gets the file version of the chm file. + /// 2 for Compatibility=1.0, 3 for Compatibility=1.1 + /// + public int FileVersion + { + get + { + if(_associatedFile != null) + return _associatedFile.FileVersion; + + return 0; + } + } + + /// + /// Gets the contents file name + /// + + public string ContentsFile + { + get + { + if(_associatedFile != null) + return _associatedFile.ContentsFile; + + return ""; + } + } + + /// + /// Gets the index file name + /// + + public string IndexFile + { + get + { + if(_associatedFile != null) + return _associatedFile.IndexFile; + + return ""; + } + } + + /// + /// Gets the default help topic + /// + + public string DefaultTopic + { + get + { + if(_associatedFile != null) + return _associatedFile.DefaultTopic; + + return ""; + } + } + + /// + /// Gets the title of the help window + /// + + public string HelpWindowTitle + { + get + { + if(_associatedFile != null) + return _associatedFile.HelpWindowTitle; + + return ""; + } + } + + /// + /// Gets the flag if DBCS is in use + /// + + public bool DBCS + { + get + { + if(_associatedFile != null) + return _associatedFile.DBCS; + + return false; + } + } + + /// + /// Gets the flag if full-text-search is available + /// + + public bool FullTextSearch + { + get + { + if(_associatedFile != null) + return _associatedFile.FullTextSearch; + + return false; + } + } + + /// + /// Gets the flag if the file has ALinks + /// + + public bool HasALinks + { + get + { + if(_associatedFile != null) + return _associatedFile.HasALinks; + + return false; + } + } + + /// + /// Gets the flag if the file has KLinks + /// + + public bool HasKLinks + { + get + { + if(_associatedFile != null) + return _associatedFile.HasKLinks; + + return false; + } + } + + /// + /// Gets the default window name + /// + + public string DefaultWindow + { + get + { + if(_associatedFile != null) + return _associatedFile.DefaultWindow; + + return ""; + } + } + + /// + /// Gets the file name of the compile file + /// + + public string CompileFile + { + get + { + if(_associatedFile != null) + return _associatedFile.CompileFile; + + return ""; + } + } + + /// + /// Gets the flag if the chm has a binary index file + /// + + public bool BinaryIndex + { + get + { + if(_associatedFile != null) + return _associatedFile.BinaryIndex; + + return false; + } + } + + /// + /// Gets the flag if the chm has a binary index file + /// + + public string CompilerVersion + { + get + { + if(_associatedFile != null) + return _associatedFile.CompilerVersion; + + return ""; + } + } + + /// + /// Gets the flag if the chm has a binary toc file + /// + + public bool BinaryTOC + { + get + { + if(_associatedFile != null) + return _associatedFile.BinaryTOC; + + return false; + } + } + + /// + /// Gets the font face of the read font property. + /// Empty string for default font. + /// + + public string FontFace + { + get + { + if(_associatedFile != null) + return _associatedFile.FontFace; + + return ""; + } + } + + /// + /// Gets the font size of the read font property. + /// 0 for default font size + /// + + public double FontSize + { + get + { + if(_associatedFile != null) + return _associatedFile.FontSize; + + return 0.0; + } + } + + /// + /// Gets the character set of the read font property + /// 1 for default + /// + + public int CharacterSet + { + get + { + if(_associatedFile != null) + return _associatedFile.CharacterSet; + + return 1; + } + } + + /// + /// Gets the codepage depending on the read font property + /// + + public int CodePage + { + get + { + if(_associatedFile != null) + return _associatedFile.CodePage; + + return 0; + } + } + + /// + /// Gets the assiciated culture info + /// + public CultureInfo Culture + { + get + { + if(_associatedFile != null) + return _associatedFile.Culture; + + return CultureInfo.CurrentCulture; + } + } + #endregion + + #region #IDXHDR properties + /// + /// Gets the number of topic nodes including the contents and index files + /// + + public int NumberOfTopicNodes + { + get + { + if(_associatedFile != null) + return _associatedFile.NumberOfTopicNodes; + + return 0; + } + } + + /// + /// Gets the ImageList string specyfied in the #IDXHDR file. + /// + /// This property uses the #STRINGS file to extract the string at a given offset. + + public string ImageList + { + get + { + if(_associatedFile != null) + return _associatedFile.ImageList; + + return ""; + } + } + + /// + /// Gets the background setting + /// + + public int Background + { + get + { + if(_associatedFile != null) + return _associatedFile.Background; + + return 0; + } + } + + /// + /// Gets the foreground setting + /// + + public int Foreground + { + get + { + if(_associatedFile != null) + return _associatedFile.Foreground; + + return 0; + } + } + + /// + /// Gets the FrameName string specyfied in the #IDXHDR file. + /// + /// This property uses the #STRINGS file to extract the string at a given offset. + + public string FrameName + { + get + { + if(_associatedFile != null) + return _associatedFile.FrameName; + + return ""; + } + } + + /// + /// Gets the WindowName string specyfied in the #IDXHDR file. + /// + /// This property uses the #STRINGS file to extract the string at a given offset. + + public string WindowName + { + get + { + if(_associatedFile != null) + return _associatedFile.WindowName; + + return ""; + } + } + + /// + /// Gets a string array containing the merged file names + /// + public string[] MergedFiles + { + get + { + if(_associatedFile != null) + return _associatedFile.MergedFiles; + + return new string[0]; + } + } + + #endregion + } +} diff --git a/irc/TechBot/CHMLibrary/Default.build b/irc/TechBot/CHMLibrary/Default.build new file mode 100644 index 00000000000..6363074a8db --- /dev/null +++ b/irc/TechBot/CHMLibrary/Default.build @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + diff --git a/irc/TechBot/CHMLibrary/HtmlHelpSystem.cs b/irc/TechBot/CHMLibrary/HtmlHelpSystem.cs new file mode 100644 index 00000000000..966cd3d7205 --- /dev/null +++ b/irc/TechBot/CHMLibrary/HtmlHelpSystem.cs @@ -0,0 +1,894 @@ +using System; +using System.IO; +using System.Collections; +using System.Data; + +using HtmlHelp.ChmDecoding; + +namespace HtmlHelp +{ + /// + /// The class HtmlHelpSystem implements the main object for reading chm files + /// + public sealed class HtmlHelpSystem + { + /// + /// Private shared instance of current HtmlHelpSystem class + /// + private static HtmlHelpSystem _current=null; + /// + /// Internal member storing the attached files + /// + private ArrayList _chmFiles = new ArrayList(); + /// + /// Internal member storing a merged table of contents + /// + private TableOfContents _toc = new TableOfContents(); + /// + /// Internal member storing a merged index + /// + private Index _index = new Index(); + /// + /// URL prefix for specifying a chm destination + /// + private static string _urlPrefix = "ms-its:"; + /// + /// Internal flag specifying if the system should use the tree-images list + /// from HtmlHelp2. If false the standard CHM-Viewer pics will be used. + /// + private static bool _useHH2TreePics = false; + /// + /// Internal member storing the read information types + /// + private ArrayList _informationTypes = new ArrayList(); + /// + /// Internal member storing the read categories + /// + private ArrayList _categories = new ArrayList(); + + /// + /// Gets/Sets the url prefix for specifying a chm destination + /// + public static string UrlPrefix + { + get { return _urlPrefix; } + set { _urlPrefix = value; } + } + + public CHMStream.CHMStream BaseStream + { + get + { + CHMFile chm=(CHMFile)_chmFiles[0]; + return chm.BaseStream; + } + } + + /// + /// Gets/Sets the flag specifying if the system should use the tree-images list + /// from HtmlHelp2. If false the standard CHM-Viewer pics will be used. + /// + public static bool UseHH2TreePics + { + get { return _useHH2TreePics; } + set { _useHH2TreePics = value; } + } + + /// + /// Gets the current HtmlHelpSystem instance + /// + public static HtmlHelpSystem Current + { + get + { + return _current; + } + } + + /// + /// Standard constructor + /// + public HtmlHelpSystem() : this("") + { + } + + /// + /// Constructor of the reader class + /// + /// chm file to attach with the reader + public HtmlHelpSystem(string chmFile) + { + _current = this; + OpenFile(chmFile); + } + + + /// + /// Opens a chm file and creates + /// + /// full file path of the chm file to open + /// If you call this method, all existing merged files will be cleared. + public void OpenFile(string chmFile) + { + OpenFile(chmFile, null); + } + + /// + /// Opens a chm file and creates + /// + /// full file path of the chm file to open + /// dumping info + /// If you call this method, all existing merged files will be cleared. + public void OpenFile(string chmFile, DumpingInfo dmpInfo) + { + if( File.Exists(chmFile ) ) + { + _chmFiles.Clear(); + _toc.Clear(); + _index.Clear(); + _informationTypes.Clear(); + _categories.Clear(); + + CHMFile newFile = new CHMFile(this, chmFile, dmpInfo); + + _toc = new TableOfContents( newFile.TOC ); + _index = new Index( newFile.IndexKLinks, newFile.IndexALinks ); + + _chmFiles.Add(newFile); + // add all infotypes and categories of the read file to this system instance + MergeFileInfoTypesCategories(newFile); + + // check if the file has a merged files list + if( newFile.MergedFiles.Length > 0 ) + { + // extract the path of the chm file (usually merged files are in the same path) + FileInfo fi = new FileInfo(chmFile); + string sPath = fi.DirectoryName; + + for(int i=0; i 0) + { + if(sFile[1] != ':') // no full path setting + { + sFile = Path.Combine(sPath, sFile); + } + + MergeFile(sFile, dmpInfo, true); + } + } + + // if (newFile.MergLinks.Count>0) + // RecalculateMergeLinks(newFile); + + RemoveMergeLinks(); // clear all merge-links which have no target ! + } + } + } + + /// + /// Merges a chm file to the current help contents + /// + /// full file path of the chm file to merge + public void MergeFile(string chmFile) + { + MergeFile(chmFile, null); + } + + /// + /// Merges a chm file to the current help contents + /// + /// full file path of the chm file to merge + /// dumping info + public void MergeFile(string chmFile, DumpingInfo dmpInfo) + { + MergeFile(chmFile, dmpInfo, false); + } + + /// + /// Merges a chm file to the current help contents + /// + /// full file path of the chm file to merge + /// dumping info + /// true if the merge is done because a merged file list + /// was found in the previously loaded CHM. + internal void MergeFile(string chmFile, DumpingInfo dmpInfo, bool mergedFileList) + { + if( File.Exists(chmFile ) ) + { + if( _chmFiles.Count == 1) + { + // if we open the first file, we directly point into the toc and index of this file. + // So that we don't merge the new toc's indexe's into the first file, we have to + // clone the internal arraylists first to a new instance of the toc/index holder classes. + ArrayList atoc = _toc.TOC; + ArrayList alinks = _index.ALinks; + ArrayList klinks = _index.KLinks; + + _toc = new TableOfContents(); + _index = new Index(); + + _toc.MergeToC( atoc ); + _index.MergeIndex( alinks, IndexType.AssiciativeLinks ); + _index.MergeIndex( klinks, IndexType.KeywordLinks ); + } + + CHMFile newFile = new CHMFile(this, chmFile, dmpInfo); + + if(mergedFileList) // if we've called this method due to a merged file list merge + { + RecalculateMergeLinks(newFile); + + _toc.MergeToC( newFile.TOC, _chmFiles ); + _index.MergeIndex( newFile.IndexALinks, IndexType.AssiciativeLinks ); + _index.MergeIndex( newFile.IndexKLinks, IndexType.KeywordLinks ); + + _chmFiles.Add(newFile); + + // add all infotypes and categories of the read file to this system instance + MergeFileInfoTypesCategories(newFile); + } + else + { + _toc.MergeToC( newFile.TOC, _chmFiles ); + _index.MergeIndex( newFile.IndexALinks, IndexType.AssiciativeLinks ); + _index.MergeIndex( newFile.IndexKLinks, IndexType.KeywordLinks ); + + _chmFiles.Add(newFile); + + // add all infotypes and categories of the read file to this system instance + MergeFileInfoTypesCategories(newFile); + + // check if the file has a merged files list + if( newFile.MergedFiles.Length > 0 ) + { + // extract the path of the chm file (usually merged files are in the same path) + FileInfo fi = new FileInfo(chmFile); + string sPath = fi.DirectoryName; + + for(int i=0; i 0) + { + if(sFile[1] != ':') // no full path setting + { + sFile = Path.Combine(sPath, sFile); + } + + MergeFile(sFile, dmpInfo, true); + } + } + + RemoveMergeLinks(); // clear all merge-links which have no target ! + } + } + } + } + + /// + /// Checks all Merg-links read till now. Checks if the merg-link points to the + /// file currentFile. If yes the link will be replaced by the contents of the + /// merged file. + /// + /// Current CHMFile instance + internal void RecalculateMergeLinks(CHMFile currentFile) + { + foreach(CHMFile curFile in _chmFiles) + { + if( curFile.MergLinks.Count > 0) + { + for(int i=0; i 3) // merge info contains path name + { + sFName = sSplit[0] + ":" + sSplit[1]; + sTarget = sSplit[3]; + } + else if( sSplit.Length == 3)// merge info contains only file name + { + FileInfo fi = new FileInfo(currentFile.ChmFilePath); + string sPath = fi.DirectoryName; + + string sFile = sSplit[0]; + + if(sFile.Length > 0) + { + if(sFile[1] != ':') // no full path setting + { + sFile = Path.Combine(sPath, sFile); + } + } + + sFName = sFile; + sTarget = sSplit[2]; + } + + ArrayList arrToc = null; + if( (sFName.Length>0) && (sTarget.Length>0) ) + { + // if this link points into the current file + if( sFName.ToLower() == currentFile.ChmFilePath.ToLower() ) + { + if(sTarget.ToLower().IndexOf(".hhc") >= 0) + { + string sfCheck = sTarget; + + // remove prefixing ./ + while( (sfCheck[0]=='.') || (sfCheck[0]=='/') ) + { + sfCheck = sfCheck.Substring(1); + } + + if( currentFile.ContentsFile.ToLower() != sfCheck ) + { + arrToc = currentFile.ParseHHC( sTarget ); + + if( arrToc.Count > 0) + { + } + } + else + { + arrToc = currentFile.TOC; + } + + // target points to a complete TOC + int nCnt = 0; + + foreach(TOCItem chkItem in arrToc) + { + if(nCnt == 0) + { + curItem.AssociatedFile = currentFile; + curItem.Children = chkItem.Children; + curItem.ChmFile = currentFile.ChmFilePath; + curItem.ImageIndex = chkItem.ImageIndex; + curItem.Local = chkItem.Local; + curItem.MergeLink = chkItem.MergeLink; + curItem.Name = chkItem.Name; + curItem.TocMode = chkItem.TocMode; + curItem.TopicOffset = chkItem.TopicOffset; + + MarkChildrenAdded(chkItem.Children, curFile.MergLinks); + } + else + { + ArrayList checkList = null; + + if(curItem.Parent != null) + checkList = curItem.Parent.Children; + else + checkList = curFile.TOC; + + int nIdx = checkList.IndexOf(curItem); + if((nIdx+nCnt)>checkList.Count) + checkList.Add(chkItem); + else + checkList.Insert(nIdx+nCnt, chkItem); + + curFile.MergLinks.Add(chkItem); + MarkChildrenAdded(chkItem.Children, curFile.MergLinks); + } + + nCnt++; + } + } + else + { + + // target points to a single topic + TOCItem chkItem = currentFile.GetTOCItemByLocal(sTarget); + if(chkItem != null) + { + curItem.AssociatedFile = currentFile; + curItem.Children = chkItem.Children; + curItem.ChmFile = currentFile.ChmFilePath; + curItem.ImageIndex = chkItem.ImageIndex; + curItem.Local = chkItem.Local; + curItem.MergeLink = chkItem.MergeLink; + curItem.Name = chkItem.Name; + curItem.TocMode = chkItem.TocMode; + curItem.TopicOffset = chkItem.TopicOffset; + + curFile.MergLinks.Add(chkItem); + MarkChildrenAdded(chkItem.Children, curFile.MergLinks); + } + } + } + } + } + } + } + } + + /// + /// Adds sub-items of an TOC-entry to the merg-linked list. + /// This will mark this item as "added" during the extra merge run + /// of the HtmlHelpSystem class. + /// + /// TOCItem list + /// Arraylist which holds the merged-items + internal void MarkChildrenAdded(ArrayList tocs, ArrayList merged) + { + foreach(TOCItem curItem in tocs) + { + if(!merged.Contains(curItem)) + { + merged.Add(curItem); + + MarkChildrenAdded(curItem.Children, merged); + } + } + } + + /// + /// Removes merge-links from the toc of files which were not loaded + /// + internal void RemoveMergeLinks() + { + foreach(CHMFile curFile in _chmFiles) + { + if( curFile.MergLinks.Count > 0) + { + while(curFile.MergLinks.Count > 0) + { + TOCItem curItem = curFile.MergLinks[0] as TOCItem; + if(curItem.MergeLink.Length > 0) + curFile.RemoveTOCItem(curItem); + + curFile.MergLinks.RemoveAt(0); + } + } + } + } + + /// + /// Merges the information types and categories read by the CHMFile instance + /// into the system instance + /// + /// file instance + private void MergeFileInfoTypesCategories(CHMFile chmFile) + { + if(chmFile.HasInformationTypes) + { + for(int i=0; i + /// Removes the information types and categories read by the CHMFile instance + /// + /// file instance + private void RemoveFileInfoTypesCategories(CHMFile chmFile) + { + if(chmFile.HasInformationTypes) + { + for(int i=0; i + /// Removes a chm file from the internal file collection + /// + /// full file path of the chm file to remove + public void RemoveFile(string chmFile) + { + int nIdx = -1; + CHMFile removeInstance=null; + + foreach(CHMFile curFile in _chmFiles) + { + nIdx++; + + if( curFile.ChmFilePath.ToLower() == chmFile.ToLower() ) + { + removeInstance = curFile; + break; + } + } + + if(nIdx >= 0) + { + _toc.Clear(); // forces a rebuild of the merged toc + _index.Clear(); // force a rebuild of the merged index + + RemoveFileInfoTypesCategories(removeInstance); + _chmFiles.RemoveAt(nIdx); + } + } + + /// + /// Closes all files and destroys TOC/index + /// + public void CloseAllFiles() + { + for(int i=0; i < _chmFiles.Count; i++) + { + CHMFile curFile = _chmFiles[i] as CHMFile; + + _chmFiles.RemoveAt(i); + curFile.Dispose(); + i--; + } + + _chmFiles.Clear(); + _toc.Clear(); + _index.Clear(); + _informationTypes.Clear(); + _categories.Clear(); + } + + /// + /// Gets an array of loaded chm files. + /// + public CHMFile[] FileList + { + get + { + CHMFile[] ret = new CHMFile[ _chmFiles.Count ]; + for(int i=0;i<_chmFiles.Count;i++) + ret[i] = (CHMFile)_chmFiles[i]; + + return ret; + } + } + + /// + /// Returns true if the HtmlHelpSystem instance contains 1 or more information types + /// + public bool HasInformationTypes + { + get { return (_informationTypes.Count>0); } + } + + /// + /// Returns true if the HtmlHelpSystem instance contains 1 or more categories + /// + public bool HasCategories + { + get { return (_categories.Count>0); } + } + + /// + /// Gets an ArrayList of InformationType items + /// + public ArrayList InformationTypes + { + get { return _informationTypes; } + } + + /// + /// Gets an ArrayList of Category items + /// + public ArrayList Categories + { + get { return _categories; } + } + + /// + /// Gets the information type specified by its name + /// + /// name of the information type to receive + /// Returns the Instance for the name or null if not found + public InformationType GetInformationType(string name) + { + if(HasInformationTypes) + { + for(int i=0; i<_informationTypes.Count;i++) + { + InformationType iT = _informationTypes[i] as InformationType; + + if(iT.Name == name) + return iT; + } + } + + return null; + } + + /// + /// Gets the category specifiyd by its name + /// + /// name of the category + /// Returns the Instance for the name or null if not found + public Category GetCategory(string name) + { + if(HasCategories) + { + for(int i=0; i<_categories.Count;i++) + { + Category cat = _categories[i] as Category; + + if(cat.Name == name) + return cat; + } + } + + return null; + } + + /// + /// Gets the default topic + /// + public string DefaultTopic + { + get + { + if( _chmFiles.Count > 0 ) + { + foreach(CHMFile curFile in _chmFiles) + { + if( curFile.DefaultTopic.Length > 0) + { + return curFile.FormURL( curFile.DefaultTopic ); + } + } + } + + return "about:blank"; + } + } + + /// + /// Gets a merged table of contents of all opened chm files + /// + public TableOfContents TableOfContents + { + get + { + if( _chmFiles.Count > 0 ) + { + if( _toc.Count() <= 0) + { + // merge toc of files + foreach(CHMFile curFile in _chmFiles) + { + _toc.MergeToC( curFile.TOC ); + } + } + } + + return _toc; + } + } + + /// + /// Gets a merged index of all opened chm files + /// + public Index Index + { + get + { + if( _chmFiles.Count > 0 ) + { + if( (_index.Count(IndexType.KeywordLinks)+_index.Count(IndexType.AssiciativeLinks)) <= 0) + { + // merge index files + foreach(CHMFile curFile in _chmFiles) + { + _index.MergeIndex( curFile.IndexKLinks, IndexType.KeywordLinks); + _index.MergeIndex( curFile.IndexALinks, IndexType.AssiciativeLinks); + } + } + } + + return _index; + } + } + + /// + /// Gets a flag if the current instance offers a table of contents + /// + public bool HasTableOfContents + { + get + { + return (TableOfContents.Count() > 0); + } + } + + /// + /// Gets a flag if the current instance offers an index + /// + public bool HasIndex + { + get + { + return (HasALinks || HasKLinks); + } + } + + /// + /// Gets a flag if the index holds klinks + /// + public bool HasKLinks + { + get + { + return (_index.Count(IndexType.KeywordLinks) > 0); + } + } + + /// + /// Gets a flag if the index holds alinks + /// + public bool HasALinks + { + get + { + return (_index.Count(IndexType.AssiciativeLinks) > 0); + } + } + + /// + /// Gets a flag if the current instance supports fulltext searching + /// + public bool FullTextSearch + { + get + { + bool bRet = false; + + foreach(CHMFile curFile in _chmFiles) + { + bRet |= curFile.FullTextSearch; + } + + return bRet; + } + } + + /// + /// Performs a full-text search over the chm files + /// + /// words to search + /// true if partial word should be matched also + /// ( if this is true a search of 'support' will match 'supports', otherwise not ) + /// true if titles only + /// A DataTable containing the search hits + public DataTable PerformSearch(string words, bool partialMatches, bool titleOnly) + { + return PerformSearch(words, -1, partialMatches, titleOnly); + } + + /// + /// Performs a full-text search over the chm files + /// + /// words to search + /// maximal number of hits to return + /// true if partial word should be matched also + /// ( if this is true a search of 'support' will match 'supports', otherwise not ) + /// true if titles only + /// A DataTable containing the search hits + public DataTable PerformSearch(string words, int MaxResults, bool partialMatches, bool titleOnly) + { + if( ! FullTextSearch ) + return null; + + DataTable dtResult = null; + + int nCnt = 0; + + foreach(CHMFile curFile in _chmFiles) + { + if(nCnt == 0) + { + if(curFile.FullTextSearchEngine.CanSearch) + { + if(curFile.FullTextSearchEngine.Search(words, MaxResults, partialMatches, titleOnly)) + { + dtResult = curFile.FullTextSearchEngine.Hits; + dtResult.DefaultView.Sort = "Rating DESC"; + } + } + } + else + { + if(curFile.FullTextSearchEngine.CanSearch) + { + if(curFile.FullTextSearchEngine.Search(words, MaxResults, partialMatches, titleOnly)) + { + DataTable table = curFile.FullTextSearchEngine.Hits; + + // append rows from 2nd file + foreach(DataRow curRow in table.Rows) + { + dtResult.ImportRow( curRow ); + } + + dtResult.DefaultView.Sort = "Rating DESC"; + + // adjust max hits + if(MaxResults >= 0) + { + if(dtResult.DefaultView.Count > MaxResults) + { + for(int i=MaxResults-1; i 0) { + output.Append (GetChars (bytes, e)); + bytes.SetLength (0); + } + output.Append ((char) Int32.Parse (s.Substring (i + 2, 4), hexa)); + i += 5; + } else { + bytes.WriteByte ((byte) Int32.Parse (s.Substring (i + 1, 2), hexa)); + i += 2; + } + continue; + } + + if (bytes.Length > 0) { + output.Append (GetChars (bytes, e)); + bytes.SetLength (0); + } + + if (s [i] == '+') { + output.Append (' '); + } else { + output.Append (s [i]); + } + } + + if (bytes.Length > 0) { + output.Append (GetChars (bytes, e)); + } + + bytes = null; + return output.ToString (); + } + + public static string UrlDecode (byte [] bytes, Encoding e) + { + if (bytes == null) + return null; + + return UrlDecode (bytes, 0, bytes.Length, e); + } + + private static int GetInt (byte b) + { + char c = Char.ToUpper ((char) b); + if (c >= '0' && c <= '9') + return c - '0'; + + if (c < 'A' || c > 'F') + return 0; + + return (c - 'A' + 10); + } + + private static char GetChar (byte [] bytes, int offset, int length) + { + int value = 0; + int end = length + offset; + for (int i = offset; i < end; i++) + value = (value << 4) + GetInt (bytes [offset]); + + return (char) value; + } + + public static string UrlDecode (byte [] bytes, int offset, int count, Encoding e) + { + if (bytes == null || count == 0) + return null; + + if (bytes == null) + throw new ArgumentNullException ("bytes"); + + if (offset < 0 || offset > bytes.Length) + throw new ArgumentOutOfRangeException ("offset"); + + if (count < 0 || offset + count > bytes.Length) + throw new ArgumentOutOfRangeException ("count"); + + StringBuilder output = new StringBuilder (); + MemoryStream acc = new MemoryStream (); + + int end = count + offset; + for (int i = offset; i < end; i++) { + if (bytes [i] == '%' && i + 2 < count) { + if (bytes [i + 1] == (byte) 'u' && i + 5 < end) { + if (acc.Length > 0) { + output.Append (GetChars (acc, e)); + acc.SetLength (0); + } + output.Append (GetChar (bytes, offset + 2, 4)); + i += 5; + } else { + acc.WriteByte ((byte) GetChar (bytes, offset + 1, 2)); + i += 2; + } + continue; + } + + if (acc.Length > 0) { + output.Append (GetChars (acc, e)); + acc.SetLength (0); + } + + if (bytes [i] == '+') { + output.Append (' '); + } else { + output.Append ((char) bytes [i]); + } + } + + if (acc.Length > 0) { + output.Append (GetChars (acc, e)); + } + + acc = null; + return output.ToString (); + } + + public static byte [] UrlDecodeToBytes (byte [] bytes) + { + if (bytes == null) + return null; + + return UrlDecodeToBytes (bytes, 0, bytes.Length); + } + + public static byte [] UrlDecodeToBytes (string str) + { + return UrlDecodeToBytes (str, Encoding.UTF8); + } + + public static byte [] UrlDecodeToBytes (string str, Encoding e) + { + if (str == null) + return null; + + if (e == null) + throw new ArgumentNullException ("e"); + + return UrlDecodeToBytes (e.GetBytes (str)); + } + + public static byte [] UrlDecodeToBytes (byte [] bytes, int offset, int count) + { + if (bytes == null) + return null; + + int len = bytes.Length; + if (offset < 0 || offset >= len) + throw new ArgumentOutOfRangeException("offset"); + + if (count < 0 || offset > len - count) + throw new ArgumentOutOfRangeException("count"); + + MemoryStream result = new MemoryStream (); + int end = offset + count; + for (int i = offset; i < end; i++){ + char c = (char) bytes [i]; + if (c == '+') + c = ' '; + else if (c == '%' && i < end - 2) { + c = GetChar (bytes, i, 2); + i += 2; + } + result.WriteByte ((byte) c); + } + + return result.ToArray (); + } + + public static string UrlEncode(string str) + { + return UrlEncode(str, Encoding.UTF8); + } + + public static string UrlEncode (string s, Encoding Enc) + { + if (s == null) + return null; + + if (s == "") + return ""; + + byte [] bytes = Enc.GetBytes (s); + byte [] b =UrlEncodeToBytes (bytes, 0, bytes.Length); + return Encoding.ASCII.GetString (b,0,b.Length); + } + + public static string UrlEncode (byte [] bytes) + { + if (bytes == null) + return null; + + if (bytes.Length == 0) + return ""; + + byte []b=UrlEncodeToBytes(bytes, 0, bytes.Length); + return Encoding.ASCII.GetString (b,0,b.Length); + } + + public static string UrlEncode (byte [] bytes, int offset, int count) + { + if (bytes == null) + return null; + + if (bytes.Length == 0) + return ""; + + byte []b=UrlEncodeToBytes(bytes, offset, count); + return Encoding.ASCII.GetString (b,0,b.Length); + } + + public static byte [] UrlEncodeToBytes (string str) + { + return UrlEncodeToBytes (str, Encoding.UTF8); + } + + public static byte [] UrlEncodeToBytes (string str, Encoding e) + { + if (str == null) + return null; + + if (str == "") + return new byte [0]; + + byte [] bytes = e.GetBytes (str); + return UrlEncodeToBytes (bytes, 0, bytes.Length); + } + + public static byte [] UrlEncodeToBytes (byte [] bytes) + { + if (bytes == null) + return null; + + if (bytes.Length == 0) + return new byte [0]; + + return UrlEncodeToBytes (bytes, 0, bytes.Length); + } + + static char [] hexChars = "0123456789abcdef".ToCharArray (); + + public static byte [] UrlEncodeToBytes (byte [] bytes, int offset, int count) + { + if (bytes == null) + return null; + + int len = bytes.Length; + if (len == 0) + return new byte [0]; + + if (offset < 0 || offset >= len) + throw new ArgumentOutOfRangeException("offset"); + + if (count < 0 || count > len - offset) + throw new ArgumentOutOfRangeException("count"); + + MemoryStream result = new MemoryStream (); + int end = offset + count; + for (int i = offset; i < end; i++) { + char c = (char) bytes [i]; + if ((c == ' ') || (c < '0' && c != '-' && c != '.') || + (c < 'A' && c > '9') || + (c > 'Z' && c < 'a' && c != '_') || + (c > 'z')) { + result.WriteByte ((byte) '%'); + int idx = ((int) c) >> 4; + result.WriteByte ((byte) hexChars [idx]); + idx = ((int) c) & 0x0F; + result.WriteByte ((byte) hexChars [idx]); + } else { + result.WriteByte ((byte) c); + } + } + + return result.ToArray (); + } + + public static string UrlEncodeUnicode (string str) + { + if (str == null) + return null; + + StringBuilder result = new StringBuilder (); + int end = str.Length; + for (int i = 0; i < end; i++) { + int idx; + char c = str [i]; + if (c > 255) { + result.Append ("%u"); + idx = ((int) c) >> 24; + result.Append (hexChars [idx]); + idx = (((int) c) >> 16) & 0x0F; + result.Append (hexChars [idx]); + idx = (((int) c) >> 8) & 0x0F; + result.Append (hexChars [idx]); + idx = ((int) c) & 0x0F; + result.Append (hexChars [idx]); + continue; + } + + if ((c == ' ') || (c < '0' && c != '-' && c != '.') || + (c < 'A' && c > '9') || + (c > 'Z' && c < 'a' && c != '_') || + (c > 'z')) { + result.Append ('%'); + idx = ((int) c) >> 4; + result.Append (hexChars [idx]); + idx = ((int) c) & 0x0F; + result.Append (hexChars [idx]); + continue; + } + + result.Append (c); + } + + return result.ToString (); + } + + public static byte [] UrlEncodeUnicodeToBytes (string str) + { + if (str == null) + return null; + + if (str == "") + return new byte [0]; + + return Encoding.ASCII.GetBytes (UrlEncodeUnicode (str)); + } + + /// + /// Decodes an HTML-encoded string and returns the decoded string. + /// + /// The HTML string to decode. + /// The decoded text. + public static string HtmlDecode (string s) + { + if (s == null) + throw new ArgumentNullException ("s"); + + if (s.IndexOf ('&') == -1) + return s; + + bool insideEntity = false; // used to indicate that we are in a potential entity + string entity = String.Empty; + StringBuilder output = new StringBuilder (); + int len = s.Length; + + for (int i = 0; i < len; i++) { + char c = s [i]; + switch (c) { + case '&' : + output.Append (entity); + entity = "&"; + insideEntity = true; + break; + case ';' : + if (!insideEntity) { + output.Append (c); + break; + } + + entity += c; + int length = entity.Length; + if (length >= 2 && entity[1] == '#' && entity[2] != ';') + entity = ((char) Int32.Parse (entity.Substring (2, entity.Length - 3))).ToString(); + else if (length > 1 && Entities.ContainsKey (entity.Substring (1, entity.Length - 2))) + entity = Entities [entity.Substring (1, entity.Length - 2)].ToString (); + + output.Append (entity); + entity = String.Empty; + insideEntity = false; + break; + default : + if (insideEntity) + entity += c; + else + output.Append (c); + break; + } + } + output.Append (entity); + return output.ToString (); + } + + /// + /// Decodes an HTML-encoded string and sends the resulting output to a TextWriter output stream. + /// + /// The HTML string to decode + /// The TextWriter output stream containing the decoded string. + public static void HtmlDecode(string s, TextWriter output) + { + if (s != null) + output.Write (HtmlDecode (s)); + } + + /// + /// HTML-encodes a string and returns the encoded string. + /// + /// The text string to encode. + /// The HTML-encoded text. + public static string HtmlEncode (string s) + { + if (s == null) + return null; + + StringBuilder output = new StringBuilder (); + + foreach (char c in s) + switch (c) { + case '&' : + output.Append ("&"); + break; + case '>' : + output.Append (">"); + break; + case '<' : + output.Append ("<"); + break; + case '"' : + output.Append ("""); + break; + default: + if ((int) c > 128) { + output.Append ("&#"); + output.Append (((int) c).ToString ()); + output.Append (";"); + } + else + output.Append (c); + break; + } + return output.ToString (); + } + + /// + /// HTML-encodes a string and sends the resulting output to a TextWriter output stream. + /// + /// The string to encode. + /// The TextWriter output stream containing the encoded string. + public static void HtmlEncode(string s, TextWriter output) + { + if (s != null) + output.Write (HtmlEncode (s)); + } + +#if NET_1_1 + public string UrlPathEncode (string s) + { + if (s == null) + return null; + + int idx = s.IndexOf ("?"); + string s2 = null; + if (idx != -1) { + s2 = s.Substring (0, idx-1); + s2 = UrlEncode (s2) + s.Substring (idx); + } else { + s2 = UrlEncode (s); + } + + return s2; + } +#endif + #endregion // Methods + } +} diff --git a/irc/TechBot/CHMLibrary/Index.cs b/irc/TechBot/CHMLibrary/Index.cs new file mode 100644 index 00000000000..76f73013f0d --- /dev/null +++ b/irc/TechBot/CHMLibrary/Index.cs @@ -0,0 +1,322 @@ +using System; +using System.Diagnostics; +using System.Collections; +using HtmlHelp.ChmDecoding; + +namespace HtmlHelp +{ + /// + /// Enumeration for specifying the index type + /// + public enum IndexType + { + /// + /// Keyword links should be used + /// + KeywordLinks = 0, + /// + /// Associative links should be used + /// + AssiciativeLinks = 1 + } + + /// + /// The class Index holds the (keyword links) KLinks and (associative links) ALinks of the htmlhelp + /// system. It implements methods for easy index-based searching. + /// + public class Index + { + private ArrayList _kLinks = new ArrayList(); + private ArrayList _aLinks = new ArrayList(); + + /// + /// Standard constructor + /// + public Index() + { + } + + /// + /// Constructor of the class + /// + /// arraylist with keyword links + /// arraylist with associative links + public Index(ArrayList kLinks, ArrayList aLinks) + { + _kLinks= kLinks; + _aLinks = aLinks; + } + + /// + /// Clears the current toc + /// + public void Clear() + { + if(_aLinks != null) + _aLinks.Clear(); + if(_kLinks != null) + _kLinks.Clear(); + } + + /// + /// Gets the number of index items for a specific type + /// + /// type of index + /// Returns the number of index items for a specific type + public int Count(IndexType typeOfIndex) + { + ArrayList _index = null; + + switch( typeOfIndex ) + { + case IndexType.AssiciativeLinks: _index = _aLinks; break; + case IndexType.KeywordLinks: _index = _kLinks; break; + } + + if(_index != null) + return _index.Count; + + return 0; + } + + /// + /// Gets the internal index list of keyword links + /// + public ArrayList KLinks + { + get + { + if(_kLinks==null) + _kLinks = new ArrayList(); + + return _kLinks; + } + } + + /// + /// Gets the internal index list of associative links + /// + public ArrayList ALinks + { + get + { + if(_aLinks==null) + _aLinks = new ArrayList(); + + return _aLinks; + } + } + + /// + /// Merges the the index list arrIndex into the current one + /// + /// indexlist which should be merged with the current one + /// type of index to merge + public void MergeIndex( ArrayList arrIndex, IndexType typeOfIndex ) + { + ArrayList _index = null; + + switch(typeOfIndex) + { + case IndexType.AssiciativeLinks: _index = _aLinks;break; + case IndexType.KeywordLinks: _index = _kLinks;break; + } + + foreach(IndexItem curItem in arrIndex) + { + //IndexItem searchItem = ContainsIndex(_index, curItem.KeyWordPath); + int insertIndex=0; + IndexItem searchItem = BinSearch(0, _index.Count-1, _index, curItem.KeyWordPath, false, false, ref insertIndex); + + if(searchItem != null) + { + // extend the keywords topics + foreach(IndexTopic curEntry in curItem.Topics) + { + searchItem.Topics.Add( curEntry ); + } + } + else + { + // add the item to the global collection + //_index.Add( curItem ); + + if(insertIndex > _index.Count) + _index.Add(curItem); + else + _index.Insert(insertIndex, curItem); + } + } + } + + /// + /// Searches an index entry using recursive binary search algo (divide and conquer). + /// + /// start index for searching + /// end index for searching + /// arraylist containing sorted IndexItem entries + /// keyword path to search + /// true if the keywordPath will only contain the keyword not the complete path + /// True if case should be ignored + /// out reference. will receive the index where the item with the + /// keywordPath should be inserted if not found (receives -1 if the item was found) + /// Returns an IndexItem instance if found, otherwise null + /// (use insertIndex for inserting the new item in a sorted order) + private IndexItem BinSearch(int nStart, int nEnd, ArrayList arrIndex, string keywordPath, + bool searchKeyword, bool caseInsensitive, ref int insertIndex) + { + if( arrIndex.Count <= 0 ) + { + insertIndex=0; + return null; + } + + if(caseInsensitive) + keywordPath = keywordPath.ToLower(); + + if( (nEnd - nStart) > 1) + { + int nCheck = nStart + (nEnd-nStart)/2; + + IndexItem iC = arrIndex[nCheck] as IndexItem; + + string sCompare = iC.KeyWordPath; + + if(searchKeyword) + sCompare = iC.KeyWord; + + if(caseInsensitive) + sCompare = sCompare.ToLower(); + + if( sCompare == keywordPath ) + { + insertIndex=-1; + return iC; + } + + if( keywordPath.CompareTo(sCompare) < 0 ) + { + return BinSearch(nStart, nCheck-1, arrIndex, keywordPath, searchKeyword, caseInsensitive, ref insertIndex); + } + + if( keywordPath.CompareTo(sCompare) > 0 ) + { + return BinSearch(nCheck+1, nEnd, arrIndex, keywordPath, searchKeyword, caseInsensitive, ref insertIndex); + } + } + else if(nEnd-nStart == 1) + { + IndexItem i1 = arrIndex[nStart] as IndexItem; + IndexItem i2 = arrIndex[nEnd] as IndexItem; + + string sCompare1 = i1.KeyWordPath; + + if(searchKeyword) + sCompare1 = i1.KeyWord; + + if(caseInsensitive) + sCompare1 = sCompare1.ToLower(); + + string sCompare2 = i2.KeyWordPath; + + if(searchKeyword) + sCompare2 = i2.KeyWord; + + if(caseInsensitive) + sCompare2 = sCompare2.ToLower(); + + if( sCompare1 == keywordPath) + { + insertIndex = -1; + return i1; + } + + if( sCompare2 == keywordPath) + { + insertIndex = -1; + return i2; + } + + if( sCompare1.CompareTo(keywordPath) > 0) + { + insertIndex = nStart; + return null; + } + else if( sCompare2.CompareTo(keywordPath) > 0) + { + insertIndex = nEnd; + return null; + } + else + { + insertIndex = nEnd+1; + } + } + + IndexItem itm = arrIndex[nEnd] as IndexItem; + + string sCompareI = itm.KeyWordPath; + + if(searchKeyword) + sCompareI = itm.KeyWord; + + if(caseInsensitive) + sCompareI = sCompareI.ToLower(); + + if( sCompareI.CompareTo(keywordPath) > 0) + { + insertIndex = nStart; + return null; + } + else if( sCompareI.CompareTo(keywordPath) < 0) + { + insertIndex = nEnd+1; + return null; + } + else + { + insertIndex = -1; + return arrIndex[nEnd] as IndexItem; + } + } + + /// + /// Checks if a keyword exists in a index collection + /// + /// index to search (arraylist of IndexItems) + /// keywordpath to search + /// Returns the found IndexItem, otherwise null + private IndexItem ContainsIndex(ArrayList arrIndex, string keywordPath) + { + foreach(IndexItem curItem in arrIndex) + { + if(curItem.KeyWordPath == keywordPath) + return curItem; + } + + return null; + } + + /// + /// Searches the alinks- or klinks-index for a specific keyword/associative + /// + /// keyword/associative to search + /// type of index to search + /// Returns an ArrayList which contains IndexTopic items or null if nothing was found + public IndexItem SearchIndex(string search, IndexType typeOfIndex) + { + ArrayList _index = null; + + switch( typeOfIndex ) + { + case IndexType.AssiciativeLinks: _index = _aLinks;break; + case IndexType.KeywordLinks: _index = _kLinks;break; + } + + int insertIdx=0; + IndexItem foundItem = BinSearch(0, _index.Count, _index, search, true, true, ref insertIdx); + + return foundItem; + } + } +} diff --git a/irc/TechBot/CHMLibrary/IndexItem.cs b/irc/TechBot/CHMLibrary/IndexItem.cs new file mode 100644 index 00000000000..a21415880cd --- /dev/null +++ b/irc/TechBot/CHMLibrary/IndexItem.cs @@ -0,0 +1,396 @@ +using System; +using System.IO; +using System.Text; +using System.Collections; + +using HtmlHelp.ChmDecoding; + +namespace HtmlHelp +{ + /// + /// The class IndexItem implements an help-index item + /// + public sealed class IndexItem : IComparable + { + /// + /// Internal member storing the keyword + /// + private string _keyWord = ""; + /// + /// Internal member storing all associated information type strings + /// + private ArrayList _infoTypeStrings = new ArrayList(); + /// + /// Internal member storing the flag if this is a see-also keyword + /// + private bool _isSeeAlso = false; + /// + /// Internal member storing the indent of the keyword + /// + private int _indent = 0; + /// + /// Internal member storing the last index of the keyword in the seperated list + /// + private int _charIndex = 0; + /// + /// Internal member storing the entry index + /// + private int _entryIndex = 0; + /// + /// Internal member storing an array of see-also values + /// + private string[] _seeAlso = new string[0]; + /// + /// Internal member storing an array of topic offsets + /// + private int[] _nTopics = new int[0]; + /// + /// Internal member storing the topics + /// + private ArrayList _Topics = null; + /// + /// Associated CHMFile instance + /// + private CHMFile _chmFile = null; + /// + /// Internal flag specifying the chm file path + /// + private string _chmFileName = ""; + + /// + /// Constructor of the class + /// + /// associated CHMFile instance + /// keyword + /// true if it is a see-also keyword + /// indent of the entry + /// char index of the last keyword in the separated list + /// index of the entry + /// string array with see-also values + /// integer array with topic offsets + internal IndexItem(CHMFile chmFile, string keyWord, bool isSeeAlso, int indent, int charIndex, int entryIndex, string[] seeAlsoValues, int[] topicOffsets) + { + _chmFile = chmFile; + _chmFileName = _chmFile.ChmFilePath; + _keyWord = keyWord; + _isSeeAlso = isSeeAlso; + _indent = indent; + _charIndex = charIndex; + _entryIndex = entryIndex; + _seeAlso = seeAlsoValues; + _nTopics = topicOffsets; + } + + /// + /// Standard constructor + /// + public IndexItem() + { + } + + #region Data dumping + /// + /// Dump the class data to a binary writer + /// + /// writer to write the data + /// true if the chm filename should be written + internal void Dump(ref BinaryWriter writer, bool writeFileName) + { + int i=0; + + writer.Write(_keyWord); + writer.Write(_isSeeAlso); + writer.Write(_indent); + + if(writeFileName) + writer.Write(_chmFileName); + + writer.Write(_infoTypeStrings.Count); + + for(i=0; i<_infoTypeStrings.Count; i++) + writer.Write( (_infoTypeStrings[i]).ToString() ); + + writer.Write(_seeAlso.Length); + + for(i=0; i<_seeAlso.Length; i++) + { + if(_seeAlso[i] == null) + writer.Write(""); + else + writer.Write( _seeAlso[i] ); + } + + writer.Write(Topics.Count); + + for(i=0; i + /// Dump the class data to a binary writer + /// + /// writer to write the data + internal void Dump(ref BinaryWriter writer) + { + Dump(ref writer, false); + } + + /// + /// Reads the object data from a dump store + /// + /// reader to read the data + /// filelist from helpsystem + internal bool ReadDump(ref BinaryReader reader, ArrayList filesList) + { + int i=0; + _keyWord = reader.ReadString(); + _isSeeAlso = reader.ReadBoolean(); + _indent = reader.ReadInt32(); + _chmFileName = reader.ReadString(); + + foreach(CHMFile curFile in filesList) + { + if(curFile.ChmFilePath == _chmFileName) + { + _chmFile = curFile; + break; + } + } + + if(_chmFile==null) + return false; + + int nCnt = reader.ReadInt32(); + + for(i=0; i + /// Reads the object data from a dump store + /// + /// reader to read the data + internal void ReadDump(ref BinaryReader reader) + { + int i=0; + _keyWord = reader.ReadString(); + _isSeeAlso = reader.ReadBoolean(); + _indent = reader.ReadInt32(); + + int nCnt = reader.ReadInt32(); + + for(i=0; i + /// Implements the compareto method which allows sorting. + /// + /// object to compare to + /// See IComparable.CompareTo() + public int CompareTo(object obj) + { + if( obj.GetType() == this.GetType() ) + { + IndexItem cmp = (IndexItem)obj; + + return this.KeyWordPath.CompareTo( cmp.KeyWordPath ); + } + + return 0; + } + + /// + /// Gets/Sets the associated CHMFile instance + /// + internal CHMFile ChmFile + { + get { return _chmFile; } + set { _chmFile = value; } + } + + /// + /// Gets the ArrayList which holds all information types/categories this item is associated + /// + internal ArrayList InfoTypeStrings + { + get { return _infoTypeStrings; } + } + + /// + /// Adds a see-also string to the index item and marks it as see also item + /// + /// see also string to add + internal void AddSeeAlso(string seeAlsoString) + { + string[] seeAlso = new string[ _seeAlso.Length +1 ]; + for(int i=0; i<_seeAlso.Length; i++) + seeAlso[i] = _seeAlso[i]; + + seeAlso[_seeAlso.Length] = seeAlsoString; + _seeAlso = seeAlso; + _isSeeAlso = true; + } + + /// + /// Gets/Sets the full keyword-path of this item ( ", " separated list) + /// + public string KeyWordPath + { + get { return _keyWord; } + set { _keyWord = value; } + } + + /// + /// Gets the keyword of this item + /// + public string KeyWord + { + get + { + return _keyWord.Substring(_charIndex, _keyWord.Length-_charIndex); + } + } + + /// + /// Gets the keyword of this item with prefixing indent spaces + /// + public string IndentKeyWord + { + get + { + string sKW = this.KeyWord; + StringBuilder sb = new StringBuilder("",this.Indent*3 + sKW.Length); + for(int i=0; i + /// Gets/Sets the see-also flag of this item + /// + public bool IsSeeAlso + { + get { return _isSeeAlso; } + set { _isSeeAlso = value; } + } + + /// + /// Gets/Sets the listbox indent for this item + /// + public int Indent + { + get { return _indent; } + set { _indent = value; } + } + + /// + /// Gets/Sets the character index of an indent keyword + /// + public int CharIndex + { + get { return _charIndex; } + set { _charIndex = value; } + } + + /// + /// Gets the see-also values of this item + /// + public string[] SeeAlso + { + get { return _seeAlso; } + } + + /// + /// Gets an array with the associated topics + /// + public ArrayList Topics + { + get + { + if( _Topics == null ) + { + if(IsSeeAlso) + { + _Topics = new ArrayList(); + } + else + { + if( (_chmFile != null) && (_chmFile.TopicsFile != null) ) + { + _Topics = new ArrayList(); + + for(int i=0; i<_nTopics.Length; i++) + { + IndexTopic newTopic = IndexTopic.FromTopicEntry((TopicEntry)_chmFile.TopicsFile.TopicTable[ _nTopics[i] ]); + newTopic.AssociatedFile = _chmFile; + _Topics.Add( newTopic ); + } + } + else + { + _Topics = new ArrayList(); + } + } + } + + return _Topics; + } + } + } +} diff --git a/irc/TechBot/CHMLibrary/IndexTopic.cs b/irc/TechBot/CHMLibrary/IndexTopic.cs new file mode 100644 index 00000000000..07c8f66d022 --- /dev/null +++ b/irc/TechBot/CHMLibrary/IndexTopic.cs @@ -0,0 +1,216 @@ +using System; +using System.IO; + +using HtmlHelp.ChmDecoding; + +namespace HtmlHelp +{ + /// + /// The class IndexTopic implements an entry for the IndexItem topics list. + /// + public sealed class IndexTopic + { + private DataMode _topicMode = DataMode.TextBased; + private string _title=""; + private string _local=""; + private string _compileFile = ""; + private string _chmPath = ""; + private int _topicOffset = -1; + private CHMFile _associatedFile = null; + + /// + /// Creates a new instance of the class based on an existing TopicEntry + /// + /// + internal static IndexTopic FromTopicEntry(TopicEntry entry) + { + return new IndexTopic(entry.EntryOffset, entry.ChmFile); + //return new IndexTopic( entry.Title, entry.Locale, entry.ChmFile.CompileFile, entry.ChmFile.ChmFilePath); + } + + /// + /// Creates a new instance of the class (binary extraction mode) + /// + /// offset of the topic entry + /// associated CHMFile instance + internal IndexTopic(int topicOffset, CHMFile associatedFile) + { + _topicMode = DataMode.Binary; + _topicOffset = topicOffset; + _associatedFile = associatedFile; + } + + /// + /// Constructor of the class + /// + /// topic title + /// topic local (content filename) + /// name of the chm file (location of topic) + /// path of the chm file + public IndexTopic(string Title, string local, string compilefile, string chmpath) + { + _topicMode = DataMode.TextBased; + _title = Title; + _local = local; + _compileFile = compilefile; + _chmPath = chmpath; + } + + #region Data dumping + /// + /// Dump the class data to a binary writer + /// + /// writer to write the data + internal void Dump(ref BinaryWriter writer) + { + writer.Write((int)_topicMode); + + if(_topicMode==DataMode.TextBased) + { + writer.Write(_title); + writer.Write(_local); + } + else + { + writer.Write(_topicOffset); + } + } + + /// + /// Reads the object data from a dump store + /// + /// reader to read the data + internal void ReadDump(ref BinaryReader reader) + { + _topicMode = (DataMode)reader.ReadInt32(); + + if(_topicMode==DataMode.TextBased) + { + _title = reader.ReadString(); + _local = reader.ReadString(); + } + else + { + _topicOffset = reader.ReadInt32(); + } + } + #endregion + + /// + /// Internally used to set the chm-finos when reading from dump store + /// + /// + /// + internal void SetChmInfo(string compilefile, string chmpath) + { + _compileFile = compilefile; + _chmPath = chmpath; + } + + /// + /// Gets/Sets the associated CHMFile instance + /// + internal CHMFile AssociatedFile + { + get { return _associatedFile; } + set { _associatedFile = value; } + } + + /// + /// Gets the topic title + /// + public string Title + { + get + { + if((_topicMode == DataMode.Binary )&&(_associatedFile!=null)) + { + if( _topicOffset >= 0) + { + TopicEntry te = (TopicEntry) (_associatedFile.TopicsFile[_topicOffset]); + if(te != null) + { + return te.Title; + } + } + } + + return _title; + } + } + + /// + /// Gets the local (content filename) + /// + public string Local + { + get + { + if((_topicMode == DataMode.Binary )&&(_associatedFile!=null)) + { + if( _topicOffset >= 0) + { + TopicEntry te = (TopicEntry) (_associatedFile.TopicsFile[_topicOffset]); + if(te != null) + { + return te.Locale; + } + } + } + + return _local; + } + } + + /// + /// Gets the compile file (location) + /// + public string CompileFile + { + get + { + if(_associatedFile != null) + return _associatedFile.CompileFile; + + return _compileFile; + } + } + + /// + /// Gets the chm file path + /// + public string ChmFilePath + { + get + { + if(_associatedFile != null) + return _associatedFile.ChmFilePath; + + return _chmPath; + } + } + + /// + /// Gets the url + /// + public string URL + { + get + { + string sL = Local; + + if(sL.Length<=0) + return "";//"about:blank"; + + if( (sL.ToLower().IndexOf("http://") >= 0) || + (sL.ToLower().IndexOf("https://") >= 0) || + (sL.ToLower().IndexOf("mailto:") >= 0) || + (sL.ToLower().IndexOf("ftp://") >= 0) || + (sL.ToLower().IndexOf("ms-its:") >= 0)) + return sL; + + return HtmlHelpSystem.UrlPrefix + ChmFilePath + "::/" + sL; + } + } + } +} diff --git a/irc/TechBot/CHMLibrary/InformationType.cs b/irc/TechBot/CHMLibrary/InformationType.cs new file mode 100644 index 00000000000..7f23e5c4abc --- /dev/null +++ b/irc/TechBot/CHMLibrary/InformationType.cs @@ -0,0 +1,146 @@ +using System; +using System.IO; + +namespace HtmlHelp +{ + /// + /// Enumeration for specifying the mode of the information type + /// + public enum InformationTypeMode + { + /// + /// Inclusive information type. The user will be allowed to select from one or more information types. + /// + Inclusive = 0, + /// + /// Exclusive information type. The user will be allowed to choose only one information type within each category + /// + Exclusive = 1, + /// + /// Hidden information type. The user cannot see this information types (only for API calls). + /// + Hidden = 2 + } + + /// + /// The class InformationType implements a methods/properties for an information type. + /// + /// Note: Information types and categories allow users to filter help contents. + /// They are only supported if using sitemap TOC and/or sitemap Index. + public class InformationType + { + private string _name = ""; + private string _description = ""; + private InformationTypeMode _typeMode = InformationTypeMode.Inclusive; + private bool _isInCategory = false; + private int _referenceCount = 1; + + /// + /// Standard constructor + /// + /// the mode is set to InformationTypeMode.Inclusive by default + public InformationType() : this("","") + { + } + + /// + /// Standard constructor + /// + /// name of the information type + /// description + /// the mode is set to InformationTypeMode.Inclusive by default + public InformationType(string name, string description) : this(name, description, InformationTypeMode.Inclusive) + { + } + + /// + /// Standard constructor + /// + /// name of the information type + /// description + /// mode of the information type + public InformationType(string name, string description, InformationTypeMode mode) + { + _name = name; + _description = description; + _typeMode = mode; + } + + #region Data dumping + /// + /// Dump the class data to a binary writer + /// + /// writer to write the data + internal void Dump(ref BinaryWriter writer) + { + writer.Write( (int)_typeMode ); + writer.Write( _name ); + writer.Write( _description ); + } + + /// + /// Reads the object data from a dump store + /// + /// reader to read the data + internal void ReadDump(ref BinaryReader reader) + { + _typeMode = (InformationTypeMode)reader.ReadInt32(); + _name = reader.ReadString(); + _description = reader.ReadString(); + } + #endregion + + /// + /// Sets the flag if this information type is nested in at least one category + /// + /// true or false + internal void SetCategoryFlag(bool newValue) + { + _isInCategory = newValue; + } + + /// + /// Gets/Sets the reference count of this information type instance + /// + internal int ReferenceCount + { + get { return _referenceCount; } + set { _referenceCount = value; } + } + + /// + /// Gets true if this information type is nested in at least one category + /// + public bool IsInCategory + { + get { return _isInCategory; } + } + + /// + /// Gets/Sets the name of the information type + /// + public string Name + { + get { return _name; } + set { _name = value; } + } + + /// + /// Gets/Sets the description of the information type + /// + public string Description + { + get { return _description; } + set { _name = value; } + } + + /// + /// Gets/Sets the mode of the information type + /// + public InformationTypeMode Mode + { + get { return _typeMode; } + set { _typeMode = value; } + } + } +} diff --git a/irc/TechBot/CHMLibrary/Storage/CHMStream.cs b/irc/TechBot/CHMLibrary/Storage/CHMStream.cs new file mode 100644 index 00000000000..8eab65942a4 --- /dev/null +++ b/irc/TechBot/CHMLibrary/Storage/CHMStream.cs @@ -0,0 +1,2842 @@ +using System; +using System.Diagnostics; +using System.Text; +using System.Data; +using System.Text.RegularExpressions; +using System.Collections; +using System.Collections.Specialized; +using System.IO; +using System.Runtime.InteropServices; + +namespace CHMStream +{ + /// + /// Summary description for CHMFile. + /// + /// + public class CHMStream : IDisposable + { + public MemoryStream OpenStream(chmUnitInfo Info) + { + if (Info==null) + return null; + + MemoryStream st=new MemoryStream(); + this.ExtractFile(Info,st); + return st; + } + + public MemoryStream OpenStream(string FileName) + { + chmUnitInfo info=this.GetFileInfo(FileName); + if (info==null) + return null; + return OpenStream(info); + } + + private string m_CHMFileName; + public string CHMFileName + { + get + { + return m_CHMFileName; + } + } + + public CHMStream() + { + } + + public CHMStream(string CHMFileName) + { + OpenCHM(CHMFileName); + } + + public void OpenCHM(string CHMFileName) + { + m_CHMFileName=CHMFileName; + FileInfo fi=new FileInfo(m_CHMFileName); + Dir=fi.DirectoryName; + m_CHMName=fi.Name; + fi=null; + chm_open(m_CHMFileName); + } + + private bool m_bCHMLoaded=false; + public bool CHMLoaded + { + get + { + return m_bCHMLoaded; + } + } + + private string m_CHMName=""; + public string CHMName + { + get + { + return m_CHMName; + } + } + + private string Dir=""; + private string m_FileFind=""; + private string m_FileFindLastPart=""; + private chmUnitInfo m_FileInfo=null; + private int m_FileCount=0; + public void FindFile(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status) + { + string LocalFile=Info.path; + LocalFile=LocalFile.Replace("/",@"\"); + if (!LocalFile.StartsWith(@"\")) + LocalFile=@"\"+LocalFile; + LocalFile=LocalFile.ToLower(); + + if (m_FileFind.Length<=LocalFile.Length) + { + if (LocalFile.IndexOf(m_FileFind)==LocalFile.Length-m_FileFind.Length) + { + Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_SUCCESS; + m_FileInfo=Info; + return; + } + } + Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE; + } + + public void FileCount(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status) + { + m_FileCount++; + Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE; + } + + private ArrayList m_FileList=null; + private string m_strByExt=""; + public void FileList(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status) + { + m_FileList.Add(Info.path); + Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE; + } + + public void FileListByExtension(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status) + { + FileInfo fi=new FileInfo(Info.path); + if (fi.Extension.ToLower()==m_strByExt.ToLower()) + m_FileList.Add(Info.path); + Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE; + } + + public void FindFileIndex(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status) + { + if (m_FileCount==m_FileFindIndex) + { + m_FileInfo=Info; + Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_SUCCESS; + } + else + { + m_FileCount++; + Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE; + } + } + + public int GetFileCount() + { + if (!m_bCHMLoaded) + return 0; + + m_FileCount=0; + this.CHMFileFoundEvent+=new CHMStream.CHMFileFound(FileCount); + this.chm_enumerate(CHM_ENUMERATE.CHM_ENUMERATE_ALL); + this.CHMFileFoundEvent-=new CHMStream.CHMFileFound(FileCount); + return m_FileCount; + } + + public ArrayList GetFileList() + { + if (!m_bCHMLoaded) + return null; + + m_FileList=null; + m_FileList=new ArrayList(1000); + this.CHMFileFoundEvent+=new CHMStream.CHMFileFound(FileList); + this.chm_enumerate(CHM_ENUMERATE.CHM_ENUMERATE_ALL); + this.CHMFileFoundEvent-=new CHMStream.CHMFileFound(FileList); + return m_FileList; + } + + public ArrayList GetFileListByExtenstion(string Ext) + { + if (!m_bCHMLoaded) + return null; + + m_FileList=null; + m_FileList=new ArrayList(1000); + m_strByExt=Ext; + this.CHMFileFoundEvent+=new CHMStream.CHMFileFound(FileListByExtension); + this.chm_enumerate(CHM_ENUMERATE.CHM_ENUMERATE_ALL); + this.CHMFileFoundEvent-=new CHMStream.CHMFileFound(FileListByExtension); + return m_FileList; + } + + public chmUnitInfo GetFileInfo(string FileName) + { + if (!m_bCHMLoaded) + return null; + + m_FileFind=FileName.ToLower().Replace("/",@"\"); + + // Remove all leading '..\' + do + { + if (m_FileFind.StartsWith(@"..\")) + m_FileFind=m_FileFind.Substring(3); + else + break; + } + while(true); + + if (!m_FileFind.StartsWith(@"\")) + m_FileFind=@"\"+m_FileFind; + + string []parts=m_FileFind.Split('\\'); + m_FileFindLastPart=@"\"+parts[parts.GetUpperBound(0)]; + + this.CHMFileFoundEvent+=new CHMStream.CHMFileFound(FindFile); + m_FileInfo=null; + this.chm_enumerate(CHM_ENUMERATE.CHM_ENUMERATE_ALL); + this.CHMFileFoundEvent-=new CHMStream.CHMFileFound(FindFile); + return m_FileInfo; + } + + private int m_FileFindIndex=0; + public chmUnitInfo GetFileInfo(int FileIndex) + { + if (!m_bCHMLoaded) + return null; + + m_FileFindIndex=FileIndex; + + this.CHMFileFoundEvent+=new CHMStream.CHMFileFound(FindFileIndex); + m_FileCount=0; + m_FileInfo=null; + this.chm_enumerate(CHM_ENUMERATE.CHM_ENUMERATE_ALL); + this.CHMFileFoundEvent-=new CHMStream.CHMFileFound(FindFileIndex); + return m_FileInfo; + } + + public chmUnitInfo GetFileInfoByExtension(string Ext) + { + this.CHMFileFoundEvent+=new CHMStream.CHMFileFound(FindFileByExtension); + m_FileInfo=null; + m_FileFind=Ext.ToLower(); + this.chm_enumerate(CHMStream.CHM_ENUMERATE.CHM_ENUMERATE_ALL); + this.CHMFileFoundEvent-=new CHMStream.CHMFileFound(FindFileByExtension); + return m_FileInfo; + } + + public bool ExtractFile(string FileName, System.IO.Stream st) + { + if (!m_bCHMLoaded) + return false; + + chmUnitInfo Info=GetFileInfo(FileName); + return ExtractFile(Info,st); + } + + public bool ExtractFile(chmUnitInfo Info, System.IO.Stream st) + { + if (!m_bCHMLoaded) + return false; + + if (Info==null) + return false; + else + { + chm_retrieve_object(Info,st,0,Info.length); + } + return true; + } + + public string ExtractTextFile(string FileName) + { + if (!m_bCHMLoaded) + return "CHM File not loaded"; + + chmUnitInfo Info=GetFileInfo(FileName); + return ExtractTextFile(Info); + } + + public string ExtractTextFile(chmUnitInfo Info) + { + if (!m_bCHMLoaded) + return "CHM File not loaded"; + + if (Info==null) + return ""; + + if (Info.path.Length>=2) + { + if (Info.path.Substring(0,2).CompareTo("/#")==0) + return ""; + if (Info.path.Substring(0,2).CompareTo("/$")==0) + return ""; + } + + MemoryStream st=new MemoryStream((int)Info.length); + this.chm_retrieve_object(Info,st,0,Info.length); + + if (st.Length==0) + return ""; + + string Text=""; + + ASCIIEncoding ascii=new ASCIIEncoding(); + Text=ascii.GetString(st.ToArray(),0,50); + + // UTF Decoding + if (Text.IndexOf("UTF-8")!=-1) + { + UTF8Encoding utf8 = new UTF8Encoding(); + Text=utf8.GetString(st.ToArray(),0,(int)st.Length); + } + else + Text=ascii.GetString(st.ToArray(),0,(int)st.Length); + + return Text; + } + + public void FindFileByExtension(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status) + { + if ((Info.path.StartsWith("::")) || (Info.path.StartsWith("#")) ||(Info.path.StartsWith("$"))) + { + Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE; + return; + } + + FileInfo Fi=new FileInfo(Info.path); + if (Fi.Extension.ToLower()==m_FileFind) + { + Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_SUCCESS; + m_FileInfo=Info; + } + else + Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE; + } + + public bool GetCHMParts(string Url, ref string CHMFileName, ref string FileName, ref string Anchor) + { + Regex ParseURLRegEx= new Regex( @"ms-its:(?'CHMFile'.*)::(?'Topic'.*)", RegexOptions.IgnoreCase| RegexOptions.Singleline | RegexOptions.ExplicitCapture| RegexOptions.IgnorePatternWhitespace| RegexOptions.Compiled); + + // Parse URL - Get CHM Filename & Page Name + // Format 'ms-its:file name.chm::/topic.htm' + if (ParseURLRegEx.IsMatch(Url)) + { + Match m=ParseURLRegEx.Match(Url); + CHMFileName=m.Groups["CHMFile"].Value; + string Topic=m.Groups["Topic"].Value; + int idx=Topic.IndexOf("#"); + if (idx>-1) + { + FileName=Topic.Substring(0,idx); + Anchor=Topic.Substring(idx+1); + } + else + FileName=Topic; + return true; + } + return false; + } + + private string m_TempDir=""; + string ReplaceFileName(Match m) + { + string strReplace = m.ToString(); + + // Process string. + if (m.Groups["FileName"]==null) + return strReplace; + + string FileName=m.Groups["FileName"].Value; + string FileName2=FileName.Replace("/",@"\"); + int idx=FileName2.IndexOf("::"); + if (idx!=-1) + FileName2=FileName2.Substring(idx+2); + string []parts=FileName2.Split('\\'); + string NewName=@"file://"+m_TempDir+parts[parts.GetUpperBound(0)]; + + strReplace=strReplace.Replace(FileName,NewName); + return strReplace; + } + + public ArrayList GetFileList(ref string Text, string TempDir) + { + if (!m_bCHMLoaded) + return null; + + m_TempDir=TempDir; + + ArrayList FilesList=new ArrayList(); + + // Parse HTML for CCS, ima, etc + string regexContent=@"[\x2f a-zA-Z0-9\x5C\x2E\x28\x29\x23\x24\x25\x26\x27\x22\x21\x3F\x3E\x3D\x3C\x3B\x3A\x5B\x5D\x5E\x5F\x7D\x7C\x7B\x7E\x40\x2D\x2C\x2B\x2A]*\s*"; + string regexFileName=@"\s*=\s*[""|'](?'FileName'[^""^']*)[""|']\s*"; + + Regex ScriptRegex = new Regex(@"]*>.*", + RegexOptions.IgnoreCase + | RegexOptions.Multiline + | RegexOptions.Singleline + | RegexOptions.IgnorePatternWhitespace + | RegexOptions.Compiled); + + Regex XMLRegex = new Regex(@"<\?xml.*\?>", + RegexOptions.IgnoreCase + | RegexOptions.Multiline + | RegexOptions.Singleline + | RegexOptions.IgnorePatternWhitespace + | RegexOptions.Compiled); + + Regex XMLRegex2 = new Regex(@"]*>.*", + RegexOptions.IgnoreCase + | RegexOptions.Multiline + | RegexOptions.Singleline + | RegexOptions.IgnorePatternWhitespace + | RegexOptions.Compiled); + + Regex SRCRegex = new Regex( + @"src"+regexFileName, + RegexOptions.IgnoreCase + | RegexOptions.Multiline + | RegexOptions.Singleline + | RegexOptions.IgnorePatternWhitespace + | RegexOptions.Compiled); + + Regex StyleSheetRegex = new Regex( + @" 0; i--) + { + UInt32 curBlockIdx = (UInt32)(block-i); + + /* check if we most recently decompressed the previous block */ + if ((ulong)lzx_last_block != curBlockIdx) + { + if ((curBlockIdx % reset_blkcount)==0) + { + lzx_state.LZXreset(); + } + + _chm_get_cmpblock_bounds(st,curBlockIdx, ref cmpStart, ref cmpLen); + st.BaseStream.Seek((long)cmpStart,SeekOrigin.Begin); + if (lzx_state.LZXdecompress(st,OutBuffer, ref cmpLen, ref reset_table.block_len) != lzw.DECR_OK) + return (Int64)0; + } + lzx_last_block = (int)(curBlockIdx); + } + } + else + { + if ((block % reset_blkcount)==0) + { + lzx_state.LZXreset(); + } + } + + // decompress the block we actually want + if (_chm_get_cmpblock_bounds(st, block, ref cmpStart, ref cmpLen)==0) + return 0; + + st.BaseStream.Seek((long)cmpStart,SeekOrigin.Begin); + + if (lzx_state.LZXdecompress(st, OutBuffer, ref cmpLen,ref reset_table.block_len) != lzw.DECR_OK) + return (Int64)0; + + lzx_last_block = (int)block; + + // XXX: modify LZX routines to return the length of the data they + // * decompressed and return that instead, for an extra sanity check. + return reset_table.block_len; + } + + // grab a region from a compressed block + private ulong _chm_decompress_region(Stream buf, ulong start, ulong len) + { + ulong nBlock, nOffset; + ulong nLen; + ulong gotLen; + // byte [] ubuffer=null; + + if (len <= 0) + return (Int64)0; + + // figure out what we need to read + nBlock = start / reset_table.block_len; + nOffset = start % reset_table.block_len; + nLen = len; + if (nLen > (reset_table.block_len - nOffset)) + nLen = reset_table.block_len - nOffset; + + // data request not satisfied, so... start up the decompressor machine + if (lzx_state==null) + { + int window_size = ffs(this.window_size) - 1; + lzx_last_block = -1; + + lzx_state=new lzw(); + lzx_state.LZXinit(window_size); + } + + // decompress some data + MemoryStream ms=new MemoryStream((int)reset_table.block_len+6144); + gotLen = _chm_decompress_block(nBlock, ms); + if (gotLen < nLen) + nLen = gotLen; + + // memcpy(buf, ubuffer+nOffset, (unsigned int)nLen); + ms.Position=(long)nOffset; + for(ulong i=0;i= (ulong)ui.length) + return (Int64)0; + + // clip length + if (addr + (ulong)len > (ulong)ui.length) + len = (ulong)ui.length - (ulong)addr; + + // if the file is uncompressed, it's simple + if (ui.space == CHMStream.CHM_COMPRESSION.CHM_UNCOMPRESSED) + { + // read data + long FilePos=st.BaseStream.Position; + st.BaseStream.Seek((long)((long)data_offset + (long)ui.start + (long)addr),SeekOrigin.Begin); + // byte [] buffer=st.ReadBytes((int)len); + buf.Write(st.ReadBytes((int)len),0,(int) len); + st.BaseStream.Seek(FilePos,SeekOrigin.Begin); + return (ulong)len; + } + + // else if the file is compressed, it's a little trickier + else // ui->space == CHM_COMPRESSED + { + if (lzx_state!=null) + { + lzx_state.LZXteardown(); + lzx_state=null; + } + ulong swath=0, total=0; + do + { + if (!compression_enabled) + return total; + + // swill another mouthful + swath = _chm_decompress_region(buf, ui.start + addr, len); + + // if we didn't get any... + if (swath == 0) + { + Trace.Assert((total!=ui.length),"De-compress failed","Length Required = "+ui.length.ToString()+" Length returned = "+total.ToString()); + return total; + } + + // update stats + total += swath; + len -= swath; + addr += swath; + } while (len != 0); + lzx_state=null; + + Trace.Assert((len!=ui.length),"De-compress failed","Length Required = "+ui.length.ToString()+" Length returned = "+len.ToString()); + return len; + } + } + #endregion + + #region Enumerate functions + // Enumerate the objects in the .chm archive + // Use delegate to handle callback + + public delegate void CHMFileFound(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status); + public event CHMFileFound CHMFileFoundEvent; + + public void OnFileFound(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status) + { + if (CHMFileFoundEvent!=null) + CHMFileFoundEvent(Info,ref Status); + } + private int chm_enumerate(CHM_ENUMERATE what) + { + Int32 curPage; + + // buffer to hold whatever page we're looking at + chmPmglHeader header; + uint end=0; + uint cur=0; + + // the current ui + chmUnitInfo ui= new chmUnitInfo(); + CHMStream.CHM_ENUMERATE flag=CHMStream.CHM_ENUMERATE.None; + + // starting page + curPage = index_head; + + // until we have either returned or given up + while (curPage != -1) + { + st.BaseStream.Seek((long)((long)dir_offset + (long)(curPage*block_len)),SeekOrigin.Begin); + + // figure out start and end for this page + cur = (uint)st.BaseStream.Position; + + header=new chmPmglHeader(); + if (header.Read_pmgl_header(st)==0) + return 0; + + end = (uint)(st.BaseStream.Position + block_len - (header.free_space)- chmPmglHeader._CHM_PMGL_LEN); + + // loop over this page + while (st.BaseStream.Position < end) + { + if (header._chm_parse_PMGL_entry(st,ref ui)==0) + return 0; + + // check for DIRS + if (ui.length == 0 && ((what & CHM_ENUMERATE.CHM_ENUMERATE_DIRS)==0)) + continue; + + // check for FILES + if (ui.length != 0 && ((what & CHM_ENUMERATE.CHM_ENUMERATE_FILES)==0)) + continue; + + // check for NORMAL vs. META + if (ui.path[0] == '/') + { + // check for NORMAL vs. SPECIAL + if (ui.path.Length>2) + { + if (ui.path[1] == '#' || ui.path[1] == '$') + flag = CHMStream.CHM_ENUMERATE.CHM_ENUMERATE_SPECIAL; + else + flag = CHMStream.CHM_ENUMERATE.CHM_ENUMERATE_NORMAL; + } + else + flag = CHMStream.CHM_ENUMERATE.CHM_ENUMERATE_META; + if ((what & flag)==0) + continue; + } + + // call the enumerator + { + CHMStream.CHM_ENUMERATOR status = CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE; + OnFileFound(ui,ref status); + + switch (status) + { + case CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_FAILURE: + return 0; + + case CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE: + break; + + case CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_SUCCESS: + return 1; + + default: + break; + } + } + } + + // advance to next page + curPage = header.block_next; + } + + return 1; + } + #endregion + + #region IDisposable Members + + private bool disposed=false; + public void Dispose() + { + Dispose(true); + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SupressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// disposing flag + private void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if(!this.disposed) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if(disposing) + { + // Dispose managed resources. + } + } + disposed = true; + } + + #endregion + } + + #region Structures used by CHM Storage + public class BaseStructure + { + public bool CheckSig(string Sig1, char[] Sig2) + { + int i=0; + foreach(char ch in Sig1.ToCharArray()) + { + if (ch!=Sig2[i]) + return false; + i++; + } + return true; + } + + // skip a compressed dword + public void skip_cword(BinaryReader st) + { + byte b=0; + while ((b=st.ReadByte())>= 0x80); + } + + // skip the data from a PMGL entry + public void _chm_skip_PMGL_entry_data(BinaryReader st) + { + skip_cword(st); + skip_cword(st); + skip_cword(st); + } + + // parse a compressed dword + public UInt64 _chm_parse_cword(BinaryReader st) + { + UInt64 accum = 0; + byte temp=0; + while ((temp=st.ReadByte()) >= 0x80) + { + accum <<= 7; + accum += (ulong)(temp & 0x7f); + } + + return (accum << 7) + temp; + } + + // parse a utf-8 string into an ASCII char buffer + public int _chm_parse_UTF8(BinaryReader st, UInt64 count, ref string path) + { + UTF8Encoding utf8=new UTF8Encoding(); + path=utf8.GetString(st.ReadBytes((int)count),0,(int)count); + return 1; + } + } + + public class chmUnitInfo + { + public UInt64 start=0; + public UInt64 length=0; + public CHMStream.CHM_COMPRESSION space=CHMStream.CHM_COMPRESSION.CHM_UNCOMPRESSED; + public string path=""; + } + + // structure of ITSF headers + public class chmItsfHeader : BaseStructure + { + public const int _CHM_ITSF_V2_LEN=0x58; + public const int _CHM_ITSF_V3_LEN=0x60; + + public char[] signature=null; // 0 (ITSF) + public Int32 version=0; // 4 + public Int32 header_len=0; // 8 + public Int32 unknown_000c=0; // c + public UInt32 last_modified=0; // 10 + public UInt32 lang_id=0; // 14 + public Guid dir_uuid; // 18 + public Guid stream_uuid; // 28 + public UInt64 unknown_offset=0; // 38 + public UInt64 unknown_len=0; // 40 + public UInt64 dir_offset=0; // 48 + public UInt64 dir_len=0; // 50 + public UInt64 data_offset=0; // 58 (Not present before V3) + + public int Read_itsf_header(BinaryReader st) + { + signature=st.ReadChars(4); + if (CheckSig("ITSF",signature)==false) + return 0; + + version=st.ReadInt32(); + header_len=st.ReadInt32(); + unknown_000c=st.ReadInt32(); + last_modified=st.ReadUInt32(); + lang_id=st.ReadUInt32(); + dir_uuid=new Guid(st.ReadBytes(16)); + stream_uuid=new Guid(st.ReadBytes(16)); + unknown_offset=st.ReadUInt64(); + unknown_len=st.ReadUInt64(); + dir_offset=st.ReadUInt64(); + dir_len=st.ReadUInt64(); + + if (version==2) + { + if (header_len != chmItsfHeader._CHM_ITSF_V2_LEN) + return 0; + } + else if (version==3) + { + if (header_len != chmItsfHeader._CHM_ITSF_V3_LEN) + return 0; + } + else return 0; + + if (version==3) + data_offset=st.ReadUInt64(); + else + data_offset = dir_offset + dir_len; + + return 1; + } + } + + // structure of ITSP headers + public class chmItspHeader : BaseStructure + { + const int CHM_ITSP_V1_LEN=0x54; + + public char[] signature=null; // 0 (ITSP) + public Int32 version=0; + public Int32 header_len=0; + public Int32 unknown_000c=0; + public UInt32 block_len=0; + public Int32 blockidx_intvl=0; + public Int32 index_depth=0; + public Int32 index_root=0; + public Int32 index_head=0; + public Int32 unknown_0024=0; + public Int32 num_blocks=0; + public Int32 unknown_002c=0; + public UInt32 lang_id=0; + public Guid system_uuid; + public Guid unknown_0044; + + public int Read_itsp_header(BinaryReader st) + { + signature=st.ReadChars(4); // 0 (ITSP) + if (CheckSig("ITSP",signature)==false) + return 0; + + version=st.ReadInt32(); + header_len=st.ReadInt32(); + + if (header_len!=CHM_ITSP_V1_LEN) + return 0; + + unknown_000c=st.ReadInt32(); + block_len=st.ReadUInt32(); + blockidx_intvl=st.ReadInt32(); + index_depth=st.ReadInt32(); + index_root=st.ReadInt32(); + index_head=st.ReadInt32(); + unknown_0024=st.ReadInt32(); + num_blocks=st.ReadInt32(); + unknown_002c=st.ReadInt32(); + lang_id=st.ReadUInt32(); + system_uuid=new Guid(st.ReadBytes(16)); + unknown_0044=new Guid(st.ReadBytes(16)); + + return 1; + } + } + + public class chmPmglHeader : BaseStructure + { + public const int _CHM_PMGL_LEN=0x14; + public char[] signature=null; // 0 (PMGL) + public UInt32 free_space=0; // 4 + public UInt32 unknown_0008=0; // 8 + public Int32 block_prev=0; // c + public Int32 block_next=0; // 10 + + public int Read_pmgl_header(BinaryReader st) + { + signature=st.ReadChars(4); + if (CheckSig("PMGL",signature)==false) + return 0; + + free_space=st.ReadUInt32(); + unknown_0008=st.ReadUInt32(); + block_prev=st.ReadInt32(); + block_next=st.ReadInt32(); + return 1; + } + + // parse a PMGL entry into a chmUnitInfo struct; return 1 on success. + public int _chm_parse_PMGL_entry(BinaryReader st, ref chmUnitInfo ui) + { + UInt64 strLen; + + // parse str len + strLen = _chm_parse_cword(st); + + // parse path + if (_chm_parse_UTF8(st, strLen, ref ui.path)==0) + return 0; + + // parse info + ui.space = (CHMStream.CHM_COMPRESSION)_chm_parse_cword(st); + ui.start = _chm_parse_cword(st); + ui.length = _chm_parse_cword(st); + return 1; + } + + public chmUnitInfo FindObject(BinaryReader st, UInt32 block_len, string objPath) + { + UInt32 end = (UInt32)st.BaseStream.Position+ block_len - free_space - _CHM_PMGL_LEN; + + // now, scan progressively + chmUnitInfo FoundObject=new chmUnitInfo(); + + while (st.BaseStream.Position < end) + { + _chm_parse_PMGL_entry(st,ref FoundObject); + if (FoundObject.path.ToLower().CompareTo(objPath.ToLower())==0) + return FoundObject; + } + FoundObject=null; + + return null; + } + } + + public class chmPmgiHeader : BaseStructure + { + public const int _CHM_PMGI_LEN=0x8; + + public char[] signature=null; // 0 (PMGL) + public UInt32 free_space=0; // 4 + + public int Read_pmgi_header(BinaryReader st) + { + signature=st.ReadChars(4); + + if ((signature[0]!='P') || (signature[1]!='M') || (signature[2]!='G') || (signature[3]!='I')) + return 0; + + free_space=st.ReadUInt32(); + return 1; + } + + public Int32 _chm_find_in_PMGI(BinaryReader st, UInt32 block_len, string objPath) + { + int page=-1; + UInt64 strLen; + string buffer=""; + uint end = (uint)st.BaseStream.Position + block_len - free_space - _CHM_PMGI_LEN; + + // now, scan progressively + while (st.BaseStream.Position < end) + { + // grab the name + strLen = _chm_parse_cword(st); + buffer=""; + if (_chm_parse_UTF8(st, strLen, ref buffer)==0) + return -1; + + // check if it is the right name + if (buffer.ToLower().CompareTo(objPath.ToLower())>0) + return page; + + // load next value for path + page = (int)_chm_parse_cword(st); + } + return page; + } + } + + public class chmLzxcResetTable:BaseStructure + { + public UInt32 version=0; + public UInt32 block_count=0; + public UInt32 unknown=0; + public UInt32 table_offset=0; + public UInt64 uncompressed_len=0; + public UInt64 compressed_len=0; + public UInt64 block_len=0; + + public int Read_lzxc_reset_table(BinaryReader st) + { + version=st.ReadUInt32(); + block_count=st.ReadUInt32(); + unknown=st.ReadUInt32(); + table_offset=st.ReadUInt32(); + uncompressed_len=st.ReadUInt64(); + compressed_len=st.ReadUInt64(); + block_len=st.ReadUInt64(); + + // check structure + if (version != 2) + return 0; + else + return 1; + } + } + + // structure of LZXC control data block + public class chmLzxcControlData:BaseStructure + { + public const int _CHM_LZXC_MIN_LEN=0x18; + public const int _CHM_LZXC_V2_LEN=0x1c; + + public UInt32 size=0; // 0 + public char[] signature=null; // 4 (LZXC) + public UInt32 version=0; // 8 + public UInt32 resetInterval=0; // c + public UInt32 windowSize=0; // 10 + public UInt32 windowsPerReset=0; // 14 + public UInt32 unknown_18=0; // 18 + + public int Read_lzxc_control_data(BinaryReader st) + { + size=st.ReadUInt32(); + signature=st.ReadChars(4); + + if (CheckSig("LZXC",signature)==false) + return 0; + + version=st.ReadUInt32(); + resetInterval=st.ReadUInt32(); + windowSize=st.ReadUInt32(); + windowsPerReset=st.ReadUInt32(); + + if (size>=_CHM_LZXC_V2_LEN) + unknown_18=st.ReadUInt32(); + else + unknown_18 = 0; + + if (version == 2) + { + resetInterval *= 0x8000; + windowSize *= 0x8000; + } + if (windowSize == 0 || resetInterval == 0) + return 0; + + // for now, only support resetInterval a multiple of windowSize/2 + if (windowSize == 1) + return 0; + if ((resetInterval % (windowSize/2)) != 0) + return 0; + + return 1; + } + } + #endregion + + #region LZW Decoder + + internal class lzx_bits + { + public UInt32 bb=0; + public int bl=0; + } + + internal class lzw + { + public lzw() + { + } + + /* $Id: lzx.c,v 1.5 2002/10/09 01:16:33 jedwin Exp $ */ + /*************************************************************************** + * lzx.c - LZX decompression routines * + * ------------------- * + * * + * maintainer: Jed Wing * + * source: modified lzx.c from cabextract v0.5 * + * notes: This file was taken from cabextract v0.5, which was, * + * itself, a modified version of the lzx decompression code * + * from unlzx. * + * * + * platforms: In its current incarnation, this file has been tested on * + * two different Linux platforms (one, redhat-based, with a * + * 2.1.2 glibc and gcc 2.95.x, and the other, Debian, with * + * 2.2.4 glibc and both gcc 2.95.4 and gcc 3.0.2). Both were * + * Intel x86 compatible machines. * + ***************************************************************************/ + + /*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. Note that an exemption to this * + * license has been granted by Stuart Caie for the purposes of * + * distribution with CHMFile. This does not, to the best of my * + * knowledge, constitute a change in the license of this (the LZX) code * + * in general. * + * * + ***************************************************************************/ + + /* some constants defined by the LZX specification */ + private const int LZX_MIN_MATCH = 2; + private const int LZX_MAX_MATCH = 257; + private const int LZX_NUM_CHARS = 256; + private const int LZX_BLOCKTYPE_INVALID = 0; /* also blocktypes 4-7 invalid */ + private const int LZX_BLOCKTYPE_VERBATIM = 1; + private const int LZX_BLOCKTYPE_ALIGNED = 2; + private const int LZX_BLOCKTYPE_UNCOMPRESSED = 3; + private const int LZX_PRETREE_NUM_ELEMENTS = 20; + private const int LZX_ALIGNED_NUM_ELEMENTS = 8; /* aligned offset tree #elements */ + private const int LZX_NUM_PRIMARY_LENGTHS = 7; /* this one missing from spec! */ + private const int LZX_NUM_SECONDARY_LENGTHS = 249; /* length tree #elements */ + + /* LZX huffman defines: tweak tablebits as desired */ + private const int LZX_PRETREE_MAXSYMBOLS = LZX_PRETREE_NUM_ELEMENTS; + private const int LZX_PRETREE_TABLEBITS = 6; + private const int LZX_MAINTREE_MAXSYMBOLS = LZX_NUM_CHARS + 50*8; + private const int LZX_MAINTREE_TABLEBITS = 12; + private const int LZX_LENGTH_MAXSYMBOLS = LZX_NUM_SECONDARY_LENGTHS+1; + private const int LZX_LENGTH_TABLEBITS = 12; + private const int LZX_ALIGNED_MAXSYMBOLS = LZX_ALIGNED_NUM_ELEMENTS; + private const int LZX_ALIGNED_TABLEBITS = 7; + private const int LZX_LENTABLE_SAFETY = 64; /* we allow length table decoding overruns */ + + public const int DECR_OK = 0; + public const int DECR_DATAFORMAT = 1; + public const int DECR_ILLEGALDATA = 2; + public const int DECR_NOMEMORY = 3; + + private byte[] window; /* the actual decoding window */ + private ulong window_size; /* window size (32Kb through 2Mb) */ + private ulong actual_size; /* window size when it was first allocated */ + private ulong window_posn; /* current offset within the window */ + private ulong R0, R1, R2; /* for the LRU offset system */ + private UInt32 main_elements; /* number of main tree elements */ + private int header_read; /* have we started decoding at all yet? */ + private UInt32 block_type; /* type of this block */ + private ulong block_length; /* uncompressed length of this block */ + private ulong block_remaining; /* uncompressed bytes still left to decode */ + private ulong frames_read; /* the number of CFDATA blocks processed */ + private long intel_filesize; /* magic header value used for transform */ + private long intel_curpos; /* current offset in transform space */ + private int intel_started; /* have we seen any translatable data yet? */ + + + private uint [] PRETREE_table = new uint[(1<<(6)) + (((20))<<1)]; + private byte [] PRETREE_len = new byte [((20)) + (64)]; + + private uint [] MAINTREE_table= new uint[(1<<(12)) + (((256) + 50*8)<<1)]; + private byte [] MAINTREE_len = new byte [((256) + 50*8) + (64)]; + + private uint [] LENGTH_table= new uint[(1<<(12)) + (((249)+1)<<1)]; + private byte [] LENGTH_len = new byte [((249)+1) + (64)]; + + private uint [] ALIGNED_table= new uint[(1<<(7)) + (((8))<<1)]; + private byte [] ALIGNED_len = new byte [((8)) + (64)]; + private System.IO.BinaryReader BitSource=null; + private System.IO.Stream OutputStream=null; + + /* LZX decruncher */ + + /* Microsoft's LZX document and their implementation of the + * com.ms.util.cab Java package do not concur. + * + * In the LZX document, there is a table showing the correlation between + * window size and the number of position slots. It states that the 1MB + * window = 40 slots and the 2MB window = 42 slots. In the implementation, + * 1MB = 42 slots, 2MB = 50 slots. The actual calculation is 'find the + * first slot whose position base is equal to or more than the required + * window size'. This would explain why other tables in the document refer + * to 50 slots rather than 42. + * + * The constant NUM_PRIMARY_LENGTHS used in the decompression pseudocode + * is not defined in the specification. + * + * The LZX document does not state the uncompressed block has an + * uncompressed length field. Where does this length field come from, so + * we can know how large the block is? The implementation has it as the 24 + * bits following after the 3 blocktype bits, before the alignment + * padding. + * + * The LZX document states that aligned offset blocks have their aligned + * offset huffman tree AFTER the main and length trees. The implementation + * suggests that the aligned offset tree is BEFORE the main and length + * trees. + * + * The LZX document decoding algorithm states that, in an aligned offset + * block, if an extra_bits value is 1, 2 or 3, then that number of bits + * should be read and the result added to the match offset. This is + * correct for 1 and 2, but not 3, where just a huffman symbol (using the + * aligned tree) should be read. + * + * Regarding the E8 preprocessing, the LZX document states 'No translation + * may be performed on the last 6 bytes of the input block'. This is + * correct. However, the pseudocode provided checks for the *E8 leader* + * up to the last 6 bytes. If the leader appears between -10 and -7 bytes + * from the end, this would cause the next four bytes to be modified, at + * least one of which would be in the last 6 bytes, which is not allowed + * according to the spec. + * + * The specification states that the huffman trees must always contain at + * least one element. However, many CAB files contain blocks where the + * length tree is completely empty (because there are no matches), and + * this is expected to succeed. + */ + + /* LZX uses what it calls 'position slots' to represent match offsets. + * What this means is that a small 'position slot' number and a small + * offset from that slot are encoded instead of one large offset for + * every match. + * - position_base is an index to the position slot bases + * - extra_bits states how many bits of offset-from-base data is needed. + */ + private byte [] extra_bits = { + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, + 15, 15, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17 + }; + + private ulong [] position_base = { + 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, + 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152, + 65536, 98304, 131072, 196608, 262144, 393216, 524288, 655360, 786432, 917504, 1048576, 1179648, 1310720, 1441792, 1572864, 1703936, + 1835008, 1966080, 2097152 + }; + + private UInt32 ReadUInt16() + { + UInt32 rc=0; + UInt32 Byte1=0; + UInt32 Byte2=0; + try + { + Byte1=BitSource.ReadByte(); + Byte2=BitSource.ReadByte(); + } + catch(Exception) + { + } + rc=(Byte2<<8)+Byte1; + return rc; + } + + public bool LZXinit(int WindowSize) + { + ulong wndsize = (ulong)(1 << WindowSize); + int i, posn_slots; + + /* LZX supports window sizes of 2^15 (32Kb) through 2^21 (2Mb) */ + /* if a previously allocated window is big enough, keep it */ + if (WindowSize< 15 || WindowSize> 21) return false; + + /* allocate state and associated window */ + window = new byte[wndsize]; + if (window==null) + { + return false; + } + + actual_size = wndsize; + window_size = wndsize; + + /* calculate required position slots */ + if (WindowSize == 20) posn_slots = 42; + else if (WindowSize== 21) posn_slots = 50; + else posn_slots = WindowSize << 1; + + /** alternatively **/ + /* posn_slots=i=0; while (i < wndsize) i += 1 << extra_bits[posn_slots++]; */ + + /* initialize other state */ + R0 = R1 = R2 = 1; + main_elements = (uint)(LZX_NUM_CHARS + (posn_slots << 3)); + header_read = 0; + frames_read = 0; + block_remaining = 0; + block_type = LZX_BLOCKTYPE_INVALID; + intel_curpos = 0; + intel_started = 0; + window_posn = 0; + + /* initialise tables to 0 (because deltas will be applied to them) */ + for (i = 0; i < LZX_MAINTREE_MAXSYMBOLS; i++) MAINTREE_len[i] = 0; + for (i = 0; i < LZX_LENGTH_MAXSYMBOLS; i++) LENGTH_len[i] = 0; + + return true; + } + + public void LZXteardown() + { + window=null; + } + + public int LZXreset() + { + R0 = R1 = R2 = 1; + header_read = 0; + frames_read = 0; + block_remaining = 0; + block_type = LZX_BLOCKTYPE_INVALID; + intel_curpos = 0; + intel_started = 0; + window_posn = 0; + + for (int i = 0; i < LZX_MAINTREE_MAXSYMBOLS + LZX_LENTABLE_SAFETY; i++) MAINTREE_len[i] = 0; + for (int i = 0; i < LZX_LENGTH_MAXSYMBOLS + LZX_LENTABLE_SAFETY; i++) LENGTH_len[i] = 0; + + return DECR_OK; + } + + + /* Bitstream reading macros: + * + * INIT_BITSTREAM should be used first to set up the system + * READ_BITS(var,n) takes N bits from the buffer and puts them in var + * + * ENSURE_BITS(n) ensures there are at least N bits in the bit buffer + * PEEK_BITS(n) extracts (without removing) N bits from the bit buffer + * REMOVE_BITS(n) removes N bits from the bit buffer + * + * These bit access routines work by using the area beyond the MSB and the + * LSB as a free source of zeroes. This avoids having to mask any bits. + * So we have to know the bit width of the bitbuffer variable. This is + * sizeof(ulong) * 8, also defined as ULONG_BITS + */ + + /* number of bits in ulong. Note: This must be at multiple of 16, and at + * least 32 for the bitbuffer code to work (ie, it must be able to ensure + * up to 17 bits - that's adding 16 bits when there's one bit left, or + * adding 32 bits when there are no bits left. The code should work fine + * for machines where ulong >= 32 bits. + */ + private int ULONG_BITS() + { + int rc=(System.Runtime.InteropServices.Marshal.SizeOf(typeof(System.UInt32))<<3); + return rc; + } + + /* make_decode_table(nsyms, nbits, length[], table[]) + * + * This function was coded by David Tritscher. It builds a fast huffman + * decoding table out of just a canonical huffman code lengths table. + * + * nsyms = total number of symbols in this huffman tree. + * nbits = any symbols with a code length of nbits or less can be decoded + * in one lookup of the table. + * length = A table to get code lengths from [0 to syms-1] + * table = The table to fill up with decoded symbols and pointers. + * + * Returns 0 for OK or 1 for error + */ + + private int make_decode_table(ulong nsyms, byte nbits, ref byte [] length, ref UInt32[] table) + { + ulong sym; + ulong leaf; + byte bit_num = 1; + ulong fill; + ulong pos = 0; /* the current position in the decode table */ + ulong table_mask = (ulong)(1 << nbits); + ulong bit_mask = table_mask >> 1; /* don't do 0 length codes */ + ulong next_symbol = bit_mask; /* base of allocation for long codes */ + + /* fill entries for codes short enough for a direct mapping */ + while (bit_num <= nbits) + { + for (sym = 0; sym < nsyms; sym++) + { + if (length[sym] == bit_num) + { + leaf = pos; + + if((pos += bit_mask) > table_mask) return 1; /* table overrun */ + + /* fill all possible lookups of this symbol with the symbol itself */ + fill = bit_mask; + while (fill-- > 0) table[leaf++] = (uint)sym; + } + } + bit_mask >>= 1; + bit_num++; + } + + /* if there are any codes longer than nbits */ + if (pos != table_mask) + { + /* clear the remainder of the table */ + for (sym = pos; sym < table_mask; sym++) table[sym] = 0; + + /* give ourselves room for codes to grow by up to 16 more bits */ + pos <<= 16; + table_mask <<= 16; + bit_mask = 1 << 15; + + while (bit_num <= 16) + { + for (sym = 0; sym < nsyms; sym++) + { + if (length[sym] == bit_num) + { + leaf = pos >> 16; + for (fill = 0; fill < (ulong)(bit_num - nbits); fill++) + { + /* if this path hasn't been taken yet, 'allocate' two entries */ + if (table[leaf] == 0) + { + table[(next_symbol << 1)] = 0; + table[(next_symbol << 1) + 1] = 0; + table[leaf] = (uint)next_symbol++; + } + /* follow the path and select either left or right for next bit */ + leaf = table[leaf] << 1; + if (((pos >> (byte)(15-fill)) & 1)==1) + leaf++; + } + table[leaf] = (uint)sym; + + if ((pos += bit_mask) > table_mask) + return 1; /* table overflow */ + } + } + bit_mask >>= 1; + bit_num++; + } + } + + /* full table? */ + if (pos == table_mask) + return 0; + + /* either erroneous table, or all elements are 0 - let's find out. */ + for (sym = 0; sym < nsyms; sym++) if (length[(uint)sym]!=0) + return 1; + + return 0; + } + + private int lzx_read_lens(byte []lens, ulong first, ulong last, ref lzx_bits lb) + { + ulong i,j, x,y; + int z; + + UInt32 bitbuf = lb.bb; + int bitsleft = lb.bl; + + UInt32 [] hufftbl=null; + + for (x = 0; x < 20; x++) + { + do + { + while (bitsleft < (4)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + y = (bitbuf >> (ULONG_BITS()- (4))); + bitbuf <<= 4; + bitsleft -= 4; + } + while (false); + PRETREE_len[x] = (byte)y; + } + if (make_decode_table( 20, 6, ref PRETREE_len, ref PRETREE_table)!=0) + return 2; + + for (x = first; x < last; ) + { + do + { + while (bitsleft < 16) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + hufftbl = PRETREE_table; + if ((i = hufftbl[((ulong)bitbuf >> (ULONG_BITS()- 6))]) >= 20) + { + j = (ulong)(1 << (byte)(ULONG_BITS()- ((6)))); + do + { + j >>= 1; + i <<= 1; + if ((bitbuf & j)!=0) + i|=1; + else + i|=0; + + if (j==0) + { + return (2); + } + } + while ((i = hufftbl[i]) >= 20); + } + z = (int)i; + j = PRETREE_len[z]; + bitbuf <<= (byte)j; + bitsleft -= (int)j; + } + while (false); + + if (z == 17) + { + do + { + while (bitsleft < (4)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + y = (bitbuf >> (ULONG_BITS()- (4))); + bitbuf <<= 4; + bitsleft -= 4; + } + while(false); + y += 4; + + while ((y--)!=0) + lens[x++] = 0; + } + else if (z == 18) + { + do + { + while (bitsleft < (5)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + (y) = (bitbuf >> (ULONG_BITS()- (5))); + bitbuf <<= 5; + bitsleft -= 5; + } + while (false); + + y += 20; + + while ((y--)!=0) + lens[x++] = 0; + } + else if (z == 19) + { + do + { + while (bitsleft < (1)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + y = (bitbuf >> (ULONG_BITS()- (1))); + bitbuf <<= 1; + bitsleft -= 1; + } + while(false); + y += 4; + do + { + while (bitsleft < (16)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + hufftbl = (PRETREE_table); + if ((i = hufftbl[(bitbuf >> (ULONG_BITS()- 6))]) >= 20) + { + j = (ulong)1 << (byte)(ULONG_BITS()- 6); + do + { + j >>= 1; + i <<= 1; + if ((bitbuf & j)==0) + i|=0; + else + i|=1; + if (j==0) + { + return (2); + } + } + while ((i = hufftbl[i]) >= 20); + } + z = (int)i; + j = PRETREE_len[z]; + + bitbuf <<= (byte)j; + bitsleft -= (int)j; + } + while(false); + z = lens[x] - z; + if (z < 0) + z += 17; + + while ((y--)!=0) + lens[x++] = (byte)z; + } + else + { + z = lens[x] - z; + if (z < 0) + z += 17; + lens[x++] = (byte)z; + } + } + lb.bb = bitbuf; + lb.bl = bitsleft; + return 0; + } + + public int LZXdecompress(System.IO.BinaryReader inpos, System.IO.Stream outpos, ref ulong inlen, ref ulong outlen) + { + BitSource=inpos; + OutputStream=outpos; + + long endinp = BitSource.BaseStream.Position+(long)inlen; + ulong runsrc, rundest; + UInt32 [] hufftbl; /* used in READ_HUFFSYM macro as chosen decoding table */ + + UInt32 bitbuf; + int bitsleft; + ulong match_offset, i,j,k; /* ijk used in READ_HUFFSYM macro */ + lzx_bits lb; /* used in READ_LENGTHS macro */ + lb=new lzx_bits(); + + int togo = (int)outlen, this_run, main_element, aligned_bits; + int match_length, length_footer, extra, verbatim_bits; + + bitsleft = 0; + bitbuf = 0; + + /* read header if necessary */ + if (header_read==0) + { + i = j = 0; + do + { + while (bitsleft < (1)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + k = (bitbuf >> (ULONG_BITS()- (1))); + bitbuf <<= 1; + bitsleft -= 1; + } + while(false); + + if (k!=0) + { + do + { + while (bitsleft < (16)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() -16 - bitsleft); + bitsleft += 16; + } + i = (bitbuf >> (ULONG_BITS()- (16))); + bitbuf <<= 16; + bitsleft -= 1; + } + while(false); + + do + { + while (bitsleft < (16)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + j = (bitbuf >> (ULONG_BITS()- (16))); + bitbuf <<= 16; + bitsleft -= 16; + } + while(false); + } + intel_filesize = (long)((i << 16) | j); + header_read = 1; + } + + /* main decoding loop */ + while (togo > 0) + { + if (block_remaining == 0) + { + if (block_type == (3)) + { + if ((block_length & 1)!=0) + BitSource.ReadByte(); + bitsleft = 0; + bitbuf = 0; + } + + do + { + while (bitsleft < (3)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + (block_type) = (uint)(bitbuf >> (ULONG_BITS()- (3))); + bitbuf <<= 3; + bitsleft -= 3; + } + while (false); + + do + { + while (bitsleft < (16)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + (i) = (bitbuf >> (ULONG_BITS()- (16))); + bitbuf <<= 16; + bitsleft -= 16; + } + while (false); + + do + { + while (bitsleft < (8)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + (j) = (bitbuf >> (ULONG_BITS()- (8))); + bitbuf <<= 8; + bitsleft -= 8; + } + while (false); + block_remaining = block_length = (i << 8) | j; + + switch (block_type) + { + case (LZX_BLOCKTYPE_ALIGNED): + for (i = 0; i < 8; i++) + { + do + { + while (bitsleft < (3)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + (j) = (bitbuf >> (ULONG_BITS()- (3))); + bitbuf <<= 3; + bitsleft -= 3; + } + while (false); + (ALIGNED_len)[i] = (byte)j; + } + if (make_decode_table( 8, 7, ref ALIGNED_len, ref ALIGNED_table)!=0) + { + return (2); + } + + do + { + lb.bb = bitbuf; + lb.bl = bitsleft; + if (lzx_read_lens(MAINTREE_len,0,256,ref lb)!=0) + { + return (2); + } + bitbuf = lb.bb; + bitsleft = lb.bl; + } + while (false); + do + { + lb.bb = bitbuf; + lb.bl = bitsleft; + if (lzx_read_lens(MAINTREE_len,256,main_elements,ref lb)!=0) + { + return (2); + } + bitbuf = lb.bb; + bitsleft = lb.bl; + } + while (false); + + if (make_decode_table( (256 + 50*8), 12, ref MAINTREE_len, ref MAINTREE_table)!=0) + { + return (2); + } + if (MAINTREE_len[0xE8] != 0) intel_started = 1; + + do + { + lb.bb = bitbuf; + lb.bl = bitsleft; + if (lzx_read_lens(LENGTH_len,0,249,ref lb)!=0) + { + return (2); + } + bitbuf = lb.bb; + bitsleft = lb.bl; + } + while (false); + if (make_decode_table( (249+1), 12, ref LENGTH_len, ref LENGTH_table)!=0) + { + return (2); + } + break; + + case (LZX_BLOCKTYPE_VERBATIM): + do + { + lb.bb = bitbuf; + lb.bl = bitsleft; + if (lzx_read_lens(MAINTREE_len,0,256,ref lb)!=0) + { + return (2); + } + bitbuf = lb.bb; + bitsleft = lb.bl; + } + while (false); + do + { + lb.bb = bitbuf; + lb.bl = bitsleft; + if (lzx_read_lens(MAINTREE_len,256,main_elements,ref lb)!=0) + { + return (2); + } + bitbuf = lb.bb; + bitsleft = lb.bl; + } + while (false); + + if (make_decode_table( (256 + 50*8), 12, ref MAINTREE_len, ref MAINTREE_table)!=0) + { + return (2); + } + if (MAINTREE_len[0xE8] != 0) intel_started = 1; + + do + { + lb.bb = bitbuf; + lb.bl = bitsleft; + if (lzx_read_lens(LENGTH_len,0,249,ref lb)!=0) + { + return (2); + } + bitbuf = lb.bb; + bitsleft = lb.bl; + } + while (false); + if (make_decode_table( (249+1), 12, ref LENGTH_len, ref LENGTH_table)!=0) + { + return (2); + } + break; + + case (LZX_BLOCKTYPE_UNCOMPRESSED): + intel_started = 1; + while (bitsleft < (16)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - bitsleft); + bitsleft += 16; + } + if (bitsleft > 16) + { + BitSource.BaseStream.Seek(-2,System.IO.SeekOrigin.Current); + } + R0 = (ulong)(BitSource.ReadByte()+(BitSource.ReadByte()<<8)+(BitSource.ReadByte()<<16)+(BitSource.ReadByte()<<24)); + R1 = (ulong)(BitSource.ReadByte()+(BitSource.ReadByte()<<8)+(BitSource.ReadByte()<<16)+(BitSource.ReadByte()<<24)); + R2 = (ulong)(BitSource.ReadByte()+(BitSource.ReadByte()<<8)+(BitSource.ReadByte()<<16)+(BitSource.ReadByte()<<24)); + break; + + default: + return (DECR_ILLEGALDATA); + } + } + + /* buffer exhaustion check */ + if (BitSource.BaseStream.Position > (long) endinp) + { + /* it's possible to have a file where the next run is less than + * 16 bits in size. In this case, the READ_HUFFSYM() macro used + * in building the tables will exhaust the buffer, so we should + * allow for this, but not allow those accidentally read bits to + * be used (so we check that there are at least 16 bits + * remaining - in this boundary case they aren't really part of + * the compressed data) + */ + if (BitSource.BaseStream.Position> (long)(endinp+2) || bitsleft < 16) + return DECR_ILLEGALDATA; + } + + while ((this_run = (int)block_remaining) > 0 && togo > 0) + { + if (this_run > togo) + this_run = togo; + + togo -= this_run; + block_remaining -= (ulong)this_run; + + /* apply 2^x-1 mask */ + window_posn &= window_size - 1; + + /* runs can't straddle the window wraparound */ + if ((window_posn + (ulong)this_run) > window_size) + return DECR_DATAFORMAT; + + switch (block_type) + { + case LZX_BLOCKTYPE_VERBATIM: + while (this_run > 0) + { + do + { + while (bitsleft < (16)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + hufftbl = MAINTREE_table; + if ((i = hufftbl[(bitbuf >> (ULONG_BITS()- 12))]) >= 256 + 50*8) + { + j = (ulong)(1 << (ULONG_BITS()- 12)); + do + { + j >>= 1; + i <<= 1; + if ((bitbuf & j)!=0) + i|=1; + else + i|=0; + if (j==0) + { + return (2); + } + } + while ((i = hufftbl[i]) >= (((256) + 50*8))); + } + j = MAINTREE_len[main_element = (int)i]; + bitbuf <<= (byte)j; + bitsleft -= (byte)j; + } + while (false); + + if (main_element < (256)) + { + window[window_posn++] = (byte)main_element; + this_run--; + } + else + { + main_element -= (256); + + match_length = main_element & (7); + if (match_length == (7)) + { + do + { + while (bitsleft < (16)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + hufftbl = (LENGTH_table); + if ((i = hufftbl[(bitbuf >> (ULONG_BITS()- 12))]) >= (((249)+1))) + { + j = (ulong)(1 << (ULONG_BITS()- ((12)))); + do + { + j >>= 1; + i <<= 1; + if ((bitbuf & j)!=0) + i|=1; + else + i|=0; + if (j==0) + { + return (2); + } + } + while ((i = hufftbl[i]) >= (((249)+1))); + } + j = LENGTH_len[(length_footer) = (int)i]; + bitbuf <<= (byte)j; + bitsleft -= (byte)j; + } + while (false); + + match_length += length_footer; + } + match_length += (2); + + match_offset = (ulong)(main_element >> 3); + + if (match_offset > 2) + { + if (match_offset != 3) + { + extra = extra_bits[match_offset]; + do + { + while (bitsleft < (extra)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + verbatim_bits = (int)(bitbuf >> (ULONG_BITS()- (extra))); + bitbuf <<= extra; + bitsleft -= extra; + } + while (false); + match_offset = position_base[match_offset] - 2 + (ulong)verbatim_bits; + } + else + { + match_offset = 1; + } + + R2 = R1; R1 = R0; R0 = match_offset; + } + else if (match_offset == 0) + { + match_offset = R0; + } + else if (match_offset == 1) + { + match_offset = R1; + R1 = R0; R0 = match_offset; + } + else + { + match_offset = R2; + R2 = R0; R0 = match_offset; + } + + rundest = window_posn; + // rundest= window+window_posn + runsrc = rundest - match_offset; + window_posn += (ulong)match_length; + this_run -= match_length; + + // runsrc < window + while ((runsrc<0) && (match_length-- > 0)) + { + window[rundest++]=window[runsrc+window_size]; + // *rundest++ = *(runsrc + window_size); + runsrc++; + } + + while (match_length-- > 0) + { + window[rundest++]=window[runsrc++]; + // *rundest++ = *runsrc++; + } + } + } + break; + + case LZX_BLOCKTYPE_ALIGNED: + while (this_run > 0) + { + do + { + while (bitsleft < (16)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + hufftbl = MAINTREE_table; + if ((i = hufftbl[(bitbuf >> (ULONG_BITS()- 12))]) >= (((256) + 50*8))) + { + j = (ulong)1 << (ULONG_BITS()- ((12))); + do + { + j >>= 1; + i <<= 1; + if ((bitbuf & j)!=0) + i|=1; + else + i|=0; + if (j==0) + { + return (2); + } + } + while ((i = hufftbl[i]) >= (((256) + 50*8))); + } + j = MAINTREE_len[(main_element) = (int)i]; + bitbuf <<= (int)j; + bitsleft -= (int)j; + } + while (false); + + if (main_element < (256)) + { + window[window_posn++] = (byte)main_element; + this_run--; + } + else + { + main_element -= (256); + match_length = main_element & (7); + if (match_length == (7)) + { + do + { + while (bitsleft < (16)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + hufftbl = LENGTH_table; + if ((i = hufftbl[(bitbuf >> (ULONG_BITS()- 12))]) >= (((249)+1))) + { + j = (ulong) 1 << (ULONG_BITS()- ((12))); + do + { + j >>= 1; + i <<= 1; + if ((bitbuf & j)!=0) + i|=1; + else + i|=0; + + if (j==0) + { + return (2); + } + } + while ((i = hufftbl[i]) >= (((249)+1))); + } + j = LENGTH_len[length_footer = (int)i]; + bitbuf <<= (int)j; + bitsleft -= (int)j; + } + while (false); + match_length += length_footer; + } + match_length += (2); + + match_offset = (ulong)(main_element >> 3); + + if (match_offset > 2) + { + extra = extra_bits[match_offset]; + match_offset = position_base[match_offset] - 2; + if (extra > 3) + { + extra -= 3; + do + { + while (bitsleft < (extra)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + verbatim_bits = (int)(bitbuf >> (ULONG_BITS()- (extra))); + bitbuf <<= extra; + bitsleft -= extra; + } + while (false); + match_offset += (ulong)(verbatim_bits << 3); + do + { + while (bitsleft < (16)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + hufftbl = (ALIGNED_table); + if ((i = hufftbl[(bitbuf >> (ULONG_BITS()- 7))]) >= 8) + { + j = (ulong)1 << (ULONG_BITS()- ((7))); + do + { + j >>= 1; + i <<= 1; + if ((bitbuf & j)!=0) + i|=1; + else + i|=0; + if (j==0) + { + return (2); + } + } + while ((i = hufftbl[i]) >= (((8)))); + } + + j = (ALIGNED_len)[(aligned_bits) = (int)i]; + bitbuf <<= (int)j; + bitsleft -= (int)j; + } + while (false); + match_offset += (ulong)aligned_bits; + } + else if (extra == 3) + { + do + { + while (bitsleft < (16)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + hufftbl = (ALIGNED_table); + if ((i = hufftbl[(bitbuf >> (ULONG_BITS()- 7))]) >= 8) + { + j = (ulong)1 << (ULONG_BITS()- ((7))); + do + { + j >>= 1; + i <<= 1; + if ((bitbuf & j)!=0) + i|=1; + else + i|=0; + if (j!=0) + { + return (2); + } + } + while ((i = hufftbl[i]) >= 8); + } + j = (ALIGNED_len)[(aligned_bits) = (int)i]; + bitbuf <<= (int)j; + bitsleft -= (int)j; + } + while (false); + match_offset += (ulong)aligned_bits; + } + else if (extra > 0) + { + do + { + while (bitsleft < (extra)) + { + bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); + bitsleft += 16; + } + (verbatim_bits) = (int)(bitbuf >> (int)(ULONG_BITS()- (extra))); + bitbuf <<= extra; + bitsleft -= extra; + } + while (false); + match_offset += (ulong)verbatim_bits; + } + else + { + match_offset = 1; + } + + R2 = R1; R1 = R0; R0 = match_offset; + } + else if (match_offset == 0) + { + match_offset = R0; + } + else if (match_offset == 1) + { + match_offset = R1; + R1 = R0; R0 = match_offset; + } + else + { + match_offset = R2; + R2 = R0; R0 = match_offset; + } + + rundest = window_posn; + runsrc = rundest - match_offset; + window_posn += (ulong)match_length; + this_run -= match_length; + + while ((runsrc<0) && (match_length-- > 0)) + { + // *rundest++ = *(runsrc + window_size); runsrc++; + window[rundest++]=window[runsrc + window_size]; + runsrc++; + } + + while (match_length-- > 0) + { + // *rundest++ = *runsrc++; + window[rundest++]=window[runsrc++]; + } + } + } + break; + + case LZX_BLOCKTYPE_UNCOMPRESSED: + if ((BitSource.BaseStream.Position + (long)this_run) > (long)endinp) + return (2); + + // memcpy(window + window_posn, inposCount, this_run); + for(i=0; i<(ulong)this_run;i++) + { + window[window_posn+i]=BitSource.ReadByte(); + } + window_posn += (ulong)this_run; + break; + + default: + return DECR_ILLEGALDATA; /* might as well */ + } + + } + } + + if (togo != 0) return DECR_ILLEGALDATA; + + // memcpy(outpos, window + ((!window_posn) ? window_size : window_posn) - outlen, (size_t) outlen); + ulong start=0; + if (window_posn==0) + start=(ulong)window_size; + else + start=(ulong)window_posn; + + start-=(ulong)outlen; + + long Pos=OutputStream.Position; + for(i=0;i<(ulong)outlen;i++) + { + OutputStream.WriteByte(window[start+i]); + } + OutputStream.Seek(Pos,System.IO.SeekOrigin.Begin); + + /* intel E8 decoding */ + if ((frames_read++ < 32768) && intel_filesize != 0) + { + if (outlen <= 6 || (intel_started==0)) + { + intel_curpos += (long)outlen; + } + else + { + // UBYTE *data = outpos; + long dataend = OutputStream.Position + (int)outlen - 10; + long curpos = intel_curpos; + long filesize = intel_filesize; + long abs_off, rel_off; + + intel_curpos = (long)curpos + (long)outlen; + + while (OutputStream.Position < dataend) + { + if (OutputStream.ReadByte() != 0xE8) + { + curpos++; + continue; + } + + abs_off = (long)(OutputStream.ReadByte() | (OutputStream.ReadByte() <<8) | (OutputStream.ReadByte() <<16) | (OutputStream.ReadByte() <<24)); + if (abs_off < filesize) + { + if (abs_off >= 0) + rel_off = (long)(abs_off - curpos); + else + rel_off = (long)abs_off + filesize; + OutputStream.WriteByte((byte)(rel_off & 0x000000ff)); + OutputStream.WriteByte((byte)((rel_off & 0x0000ff00)>>8)); + OutputStream.WriteByte((byte)((rel_off & 0x00ff0000)>>16)); + OutputStream.WriteByte((byte)((rel_off & 0xff000000)>>24)); + } + curpos += 5; + } + } + } + + return DECR_OK; + } + } + #endregion +} diff --git a/irc/TechBot/CHMLibrary/TOCItem.cs b/irc/TechBot/CHMLibrary/TOCItem.cs new file mode 100644 index 00000000000..8f6b7f97909 --- /dev/null +++ b/irc/TechBot/CHMLibrary/TOCItem.cs @@ -0,0 +1,494 @@ +using System; +using System.IO; +using System.Collections; +using System.Windows.Forms; + +using HtmlHelp.ChmDecoding; + +namespace HtmlHelp +{ + /// + /// The class TOCItem implements a toc-entry item + /// + public sealed class TOCItem + { + /// + /// Constant for standard folder (closed) image index (HH2 image list) + /// + public const int STD_FOLDER_HH2 = 4; + /// + /// Constant for standard folder (opened) image index (HH2 image list) + /// + public const int STD_FOLDER_OPEN_HH2 = 6; + /// + /// Constant for standard file image index (HH2 image list) + /// + public const int STD_FILE_HH2 = 16; + + /// + /// Constant for standard folder (closed) image index (HH1 image list) + /// + public const int STD_FOLDER_HH1 = 0; + /// + /// Constant for standard folder (opened) image index (HH1 image list) + /// + public const int STD_FOLDER_OPEN_HH1 = 1; + /// + /// Constant for standard file image index (HH1 image list) + /// + public const int STD_FILE_HH1 = 10; + + /// + /// Internal flag specifying the data extraction mode used for this item + /// + private DataMode _tocMode = DataMode.TextBased; + /// + /// Internal member storing the offset (only used in binary tocs) + /// + private int _offset = 0; + /// + /// Internal member storing the offset of the next item(only used in binary tocs) + /// + private int _offsetNext = 0; + /// + /// Internal member storing a merge link. + /// If the target file is in the merged files list of the CHM, + /// this item will be replaced with the target TOC or Topic, if not it will + /// be removed from TOC. + /// + private string _mergeLink = ""; + /// + /// Internal member storing the toc name + /// + private string _name = ""; + /// + /// Internal member storing the toc loca (content file) + /// + private string _local = ""; + /// + /// Internal member storing all associated information type strings + /// + private ArrayList _infoTypeStrings = new ArrayList(); + /// + /// Internal member storing the associated chm file + /// + private string _chmFile = ""; + /// + /// Internal member storing the image index + /// + private int _imageIndex = -1; + /// + /// Internal member storing the offset of the associated topic entry (for binary tocs) + /// + private int _topicOffset = -1; + /// + /// Internal member storing the toc children + /// + private ArrayList _children = new ArrayList(); + /// + /// Internal member storing the parameter collection + /// + private Hashtable _otherParams = new Hashtable(); + /// + /// Internal member storing the associated chmfile object + /// + private CHMFile _associatedFile = null; + /// + /// Parent item + /// + private TOCItem _parent=null; + + /// + /// Holds a pointer to the next item in the TOC + /// + public TOCItem Next=null; + + /// + /// Holds a pointer to the previous item in the TOC + /// + public TOCItem Prev=null; + + /// + /// Holds a pointer to the TreeNode where this TOC Item is used + /// + public System.Windows.Forms.TreeNode treeNode=null; + + /// + /// Constructor of the class used during text-based data extraction + /// + /// name of the item + /// local content file + /// image index + /// associated chm file + public TOCItem(string name, string local, int ImageIndex, string chmFile) + { + _tocMode = DataMode.TextBased; + _name = name; + _local = local; + _imageIndex = ImageIndex; + _chmFile = chmFile; + } + + /// + /// Constructor of the class used during binary data extraction + /// + /// offset of the associated topic entry + /// image index to use + /// associated chm file + public TOCItem(int topicOffset, int ImageIndex, CHMFile associatedFile) + { + _tocMode = DataMode.Binary; + _associatedFile = associatedFile; + _chmFile = associatedFile.ChmFilePath; + _topicOffset = topicOffset; + _imageIndex = ImageIndex; + } + + /// + /// Standard constructor + /// + public TOCItem() + { + } + + #region Data dumping + /// + /// Dump the class data to a binary writer + /// + /// writer to write the data + /// true if the chmfile name should be written + internal void Dump(ref BinaryWriter writer, bool writeFilename) + { + writer.Write((int)_tocMode); + writer.Write(_topicOffset); + writer.Write(_name); + + if((_tocMode == DataMode.TextBased)||(_topicOffset<0)) + { + writer.Write(_local); + } + + writer.Write(_imageIndex); + + writer.Write(_mergeLink); + + if(writeFilename) + writer.Write(_chmFile); + + writer.Write(_infoTypeStrings.Count); + + for(int i=0; i<_infoTypeStrings.Count; i++) + writer.Write( (_infoTypeStrings[i]).ToString() ); + + writer.Write(_children.Count); + + for(int i=0; i<_children.Count; i++) + { + TOCItem child = ((TOCItem)(_children[i])); + child.Dump(ref writer, writeFilename); + } + } + + /// + /// Dump the class data to a binary writer + /// + /// writer to write the data + internal void Dump(ref BinaryWriter writer) + { + Dump(ref writer, false); + } + + /// + /// Reads the object data from a dump store + /// + /// reader to read the data + /// true if the chmfile name should be read + internal void ReadDump(ref BinaryReader reader, bool readFilename) + { + int i=0; + _tocMode = (DataMode)reader.ReadInt32(); + _topicOffset = reader.ReadInt32(); + _name = reader.ReadString(); + + if((_tocMode == DataMode.TextBased)||(_topicOffset<0)) + { + _local = reader.ReadString(); + } + + _imageIndex = reader.ReadInt32(); + + _mergeLink = reader.ReadString(); + + if(readFilename) + _chmFile = reader.ReadString(); + + int nCnt = reader.ReadInt32(); + + for(i=0; i 0) + _associatedFile.MergLinks.Add(child); + } + } + + /// + /// Reads the object data from a dump store + /// + /// reader to read the data + internal void ReadDump(ref BinaryReader reader) + { + ReadDump(ref reader, false); + } + #endregion + + /// + /// Gets/Sets the data extraction mode with which this item was created. + /// + internal DataMode TocMode + { + get { return _tocMode; } + set { _tocMode = value; } + } + + /// + /// Gets/Sets the offset of the associated topic entry + /// + internal int TopicOffset + { + get { return _topicOffset; } + set { _topicOffset = value; } + } + + /// + /// Gets/Sets the associated CHMFile instance + /// + internal CHMFile AssociatedFile + { + get { return _associatedFile; } + set + { + _associatedFile = value; + } + } + + /// + /// Gets/Sets the offset of the item. + /// + /// Only used in binary tocs + internal int Offset + { + get { return _offset; } + set { _offset = value; } + } + + /// + /// Gets/Sets the offset of the next item. + /// + /// Only used in binary tocs + internal int OffsetNext + { + get { return _offsetNext; } + set { _offsetNext = value; } + } + + /// + /// Gets the ArrayList which holds all information types/categories this item is associated + /// + internal ArrayList InfoTypeStrings + { + get { return _infoTypeStrings; } + } + + /// + /// Gets/Sets the parent of this item + /// + public TOCItem Parent + { + get { return _parent; } + set { _parent = value; } + } + + /// + /// Gets/Sets the mergelink for this item. + /// You should not set the mergedlink by your own ! + /// This is only for loading merged CHMs. + /// + public string MergeLink + { + get { return _mergeLink; } + set { _mergeLink = value; } + } + + /// + /// Gets/Sets the name of the item + /// + public string Name + { + get + { + if(_mergeLink.Length > 0) + return ""; + + if(_name.Length <= 0) + { + if((_tocMode == DataMode.Binary )&&(_associatedFile!=null)) + { + if( _topicOffset >= 0) + { + TopicEntry te = (TopicEntry) (_associatedFile.TopicsFile[_topicOffset]); + if(te != null) + { + return te.Title; + } + } + } + } + + return _name; + } + set + { + _name = value; + } + } + + /// + /// Gets/Sets the local of the item + /// + public string Local + { + get + { + if(_mergeLink.Length > 0) + return ""; + + if(_local.Length <= 0) + { + if((_tocMode == DataMode.Binary )&&(_associatedFile!=null)) + { + if( _topicOffset >= 0) + { + TopicEntry te = (TopicEntry) (_associatedFile.TopicsFile[_topicOffset]); + if(te != null) + { + return te.Locale; + } + } + } + } + + return _local; + } + set { _local = value; } + } + + /// + /// Gets/Sets the chm file + /// + public string ChmFile + { + get + { + if(_associatedFile!=null) + return _associatedFile.ChmFilePath; + + return _chmFile; + } + set { _chmFile = value; } + } + + /// + /// Gets the url for the webbrowser for this file + /// + public string Url + { + get + { + string sL = Local; + + if( (sL.ToLower().IndexOf("http://") >= 0) || + (sL.ToLower().IndexOf("https://") >= 0) || + (sL.ToLower().IndexOf("mailto:") >= 0) || + (sL.ToLower().IndexOf("ftp://") >= 0) || + (sL.ToLower().IndexOf("ms-its:") >= 0)) + return sL; + + return HtmlHelpSystem.UrlPrefix + ChmFile + "::/" + sL; + } + } + + /// + /// Gets/Sets the image index of the item + /// + /// Set this to -1 for a default icon + public int ImageIndex + { + get + { + if( _imageIndex == -1) + { + int nFolderAdd = 0; + + if((_associatedFile != null) && (_associatedFile.ImageTypeFolder)) + { + // get the value which should be added, to display folders instead of books + if(HtmlHelpSystem.UseHH2TreePics) + nFolderAdd = 8; + else + nFolderAdd = 4; + } + + + if( _children.Count > 0) + return (HtmlHelpSystem.UseHH2TreePics ? (STD_FOLDER_HH2+nFolderAdd) : (STD_FOLDER_HH1+nFolderAdd)); + + return (HtmlHelpSystem.UseHH2TreePics ? STD_FILE_HH2 : STD_FILE_HH1); + } + return _imageIndex; + } + set { _imageIndex = value; } + } + + /// + /// Gets/Sets the children of this item. + /// + /// Each entry in the ArrayList is of type TOCItem + public ArrayList Children + { + get { return _children; } + set { _children = value; } + } + + /// + /// Gets the internal hashtable storing all params + /// + public Hashtable Params + { + get { return _otherParams; } + } + } +} diff --git a/irc/TechBot/CHMLibrary/TableOfContents.cs b/irc/TechBot/CHMLibrary/TableOfContents.cs new file mode 100644 index 00000000000..68dfb050a6a --- /dev/null +++ b/irc/TechBot/CHMLibrary/TableOfContents.cs @@ -0,0 +1,198 @@ +using System; +using System.Diagnostics; +using System.Collections; +using HtmlHelp.ChmDecoding; + +namespace HtmlHelp +{ + /// + /// The class TableOfContents holds the TOC of the htmlhelp system class. + /// + public class TableOfContents + { + private ArrayList _toc = new ArrayList(); + + /// + /// Standard constructor + /// + public TableOfContents() + { + } + + /// + /// Constructor of the class + /// + /// + public TableOfContents(ArrayList toc) + { + _toc = toc; + } + + /// + /// Gets the internal stored table of contents + /// + public ArrayList TOC + { + get { return _toc; } + } + + /// + /// Clears the current toc + /// + public void Clear() + { + if(_toc!=null) + _toc.Clear(); + } + + /// + /// Gets the number of topics in the toc + /// + /// Returns the number of topics in the toc + public int Count() + { + if(_toc!=null) + return _toc.Count; + else + return 0; + } + + /// + /// Merges the arrToC list to the one in this instance + /// + /// the toc list which should be merged with the current one + internal void MergeToC( ArrayList arrToC ) + { + if(_toc==null) + _toc = new ArrayList(); + + MergeToC(_toc, arrToC, null); + } + + /// + /// Merges the arrToC list to the one in this instance (called if merged files + /// were found in a CHM) + /// + /// the toc list which should be merged with the current one + /// An arraylist of CHMFile instances. + internal void MergeToC( ArrayList arrToC, ArrayList openFiles ) + { + if(_toc==null) + _toc = new ArrayList(); + MergeToC(_toc, arrToC, openFiles); + } + + /// + /// Internal method for recursive toc merging + /// + /// level of global toc + /// level of local toc + /// An arraylist of CHMFile instances. + private void MergeToC( ArrayList globalLevel, ArrayList localLevel, ArrayList openFiles ) + { + foreach( TOCItem curItem in localLevel) + { + // if it is a part of the merged-links, we have to do nothing, + // because the method HtmlHelpSystem.RecalculateMergeLinks() has already + // placed this item at its correct position. + if(!IsMergedItem(curItem.Name, curItem.Local, openFiles)) + { + TOCItem globalItem = ContainsToC(globalLevel, curItem.Name); + if(globalItem == null) + { + // the global toc doesn't have a topic with this name + // so we need to add the complete toc node to the global toc + + globalLevel.Add( curItem ); + } + else + { + // the global toc contains the current topic + // advance to the next level + + if( (globalItem.Local.Length <= 0) && (curItem.Local.Length > 0) ) + { + // set the associated url + globalItem.Local = curItem.Local; + globalItem.ChmFile = curItem.ChmFile; + } + + MergeToC(globalItem.Children, curItem.Children); + } + } + } + } + + /// + /// Checks if the item is part of the merged-links + /// + /// name of the topic + /// local of the topic + /// An arraylist of CHMFile instances. + /// Returns true if this item is part of the merged-links + private bool IsMergedItem(string name, string local, ArrayList openFiles) + { + if(openFiles==null) + return false; + + foreach(CHMFile curFile in openFiles) + { + foreach(TOCItem curItem in curFile.MergLinks) + if( (curItem.Name == name) && (curItem.Local == local) ) + return true; + } + return false; + } + + /// + /// Checks if a topicname exists in a SINGLE toc level + /// + /// toc list + /// topic to search + /// Returns the topic item if found, otherwise null + private TOCItem ContainsToC(ArrayList arrToC, string Topic) + { + foreach(TOCItem curItem in arrToC) + { + if(curItem.Name == Topic) + return curItem; + } + + return null; + } + + /// + /// Searches the table of contents for a special topic + /// + /// topic to search + /// Returns an instance of TOCItem if found, otherwise null + public TOCItem SearchTopic(string topic) + { + return SearchTopic(topic, _toc); + } + + /// + /// Internal recursive tree search + /// + /// topic to search + /// tree level list to look in + /// Returns an instance of TOCItem if found, otherwise null + private TOCItem SearchTopic(string topic, ArrayList searchIn) + { + foreach(TOCItem curItem in searchIn) + { + if(curItem.Name.ToLower() == topic.ToLower() ) + return curItem; + + if(curItem.Children.Count>0) + { + TOCItem nf = SearchTopic(topic, curItem.Children); + if(nf != null) + return nf; + } + } + + return null; + } + } +} diff --git a/irc/TechBot/Compression/AssemblyInfo.cs b/irc/TechBot/Compression/AssemblyInfo.cs new file mode 100644 index 00000000000..dbacda112e8 --- /dev/null +++ b/irc/TechBot/Compression/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following +// attributes. +// +// change them to the information which is associated with the assembly +// you compile. + +[assembly: AssemblyTitle("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has following format : +// +// Major.Minor.Build.Revision +// +// You can specify all values by your own or you can build default build and revision +// numbers with the '*' character (the default): + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes specify the key for the sign of your assembly. See the +// .NET Framework documentation for more information about signing. +// This is not required, if you don't want signing let these attributes like they're. +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] diff --git a/irc/TechBot/Compression/Checksums/Adler32.cs b/irc/TechBot/Compression/Checksums/Adler32.cs new file mode 100644 index 00000000000..220dcbafa0c --- /dev/null +++ b/irc/TechBot/Compression/Checksums/Adler32.cs @@ -0,0 +1,200 @@ +// Adler32.cs - Computes Adler32 data checksum of a data stream +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +namespace ICSharpCode.SharpZipLib.Checksums +{ + + /// + /// Computes Adler32 checksum for a stream of data. An Adler32 + /// checksum is not as reliable as a CRC32 checksum, but a lot faster to + /// compute. + /// + /// The specification for Adler32 may be found in RFC 1950. + /// ZLIB Compressed Data Format Specification version 3.3) + /// + /// + /// From that document: + /// + /// "ADLER32 (Adler-32 checksum) + /// This contains a checksum value of the uncompressed data + /// (excluding any dictionary data) computed according to Adler-32 + /// algorithm. This algorithm is a 32-bit extension and improvement + /// of the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073 + /// standard. + /// + /// Adler-32 is composed of two sums accumulated per byte: s1 is + /// the sum of all bytes, s2 is the sum of all s1 values. Both sums + /// are done modulo 65521. s1 is initialized to 1, s2 to zero. The + /// Adler-32 checksum is stored as s2*65536 + s1 in most- + /// significant-byte first (network) order." + /// + /// "8.2. The Adler-32 algorithm + /// + /// The Adler-32 algorithm is much faster than the CRC32 algorithm yet + /// still provides an extremely low probability of undetected errors. + /// + /// The modulo on unsigned long accumulators can be delayed for 5552 + /// bytes, so the modulo operation time is negligible. If the bytes + /// are a, b, c, the second sum is 3a + 2b + c + 3, and so is position + /// and order sensitive, unlike the first sum, which is just a + /// checksum. That 65521 is prime is important to avoid a possible + /// large class of two-byte errors that leave the check unchanged. + /// (The Fletcher checksum uses 255, which is not prime and which also + /// makes the Fletcher check insensitive to single byte changes 0 - + /// 255.) + /// + /// The sum s1 is initialized to 1 instead of zero to make the length + /// of the sequence part of s2, so that the length does not have to be + /// checked separately. (Any sequence of zeroes has a Fletcher + /// checksum of zero.)" + /// + /// + /// + public sealed class Adler32 : IChecksum + { + /// + /// largest prime smaller than 65536 + /// + readonly static uint BASE = 65521; + + uint checksum; + + /// + /// Returns the Adler32 data checksum computed so far. + /// + public long Value { + get { + return checksum; + } + } + + /// + /// Creates a new instance of the Adler32 class. + /// The checksum starts off with a value of 1. + /// + public Adler32() + { + Reset(); + } + + /// + /// Resets the Adler32 checksum to the initial value. + /// + public void Reset() + { + checksum = 1; //Initialize to 1 + } + + /// + /// Updates the checksum with the byte b. + /// + /// + /// the data value to add. The high byte of the int is ignored. + /// + public void Update(int bval) + { + //We could make a length 1 byte array and call update again, but I + //would rather not have that overhead + uint s1 = checksum & 0xFFFF; + uint s2 = checksum >> 16; + + s1 = (s1 + ((uint)bval & 0xFF)) % BASE; + s2 = (s1 + s2) % BASE; + + checksum = (s2 << 16) + s1; + } + + /// + /// Updates the checksum with the bytes taken from the array. + /// + /// + /// buffer an array of bytes + /// + public void Update(byte[] buffer) + { + Update(buffer, 0, buffer.Length); + } + + /// + /// Updates the checksum with the bytes taken from the array. + /// + /// + /// an array of bytes + /// + /// + /// the start of the data used for this update + /// + /// + /// the number of bytes to use for this update + /// + public void Update(byte[] buf, int off, int len) + { + if (buf == null) { + throw new ArgumentNullException("buf"); + } + + if (off < 0 || len < 0 || off + len > buf.Length) { + throw new ArgumentOutOfRangeException(); + } + + //(By Per Bothner) + uint s1 = checksum & 0xFFFF; + uint s2 = checksum >> 16; + + while (len > 0) { + // We can defer the modulo operation: + // s1 maximally grows from 65521 to 65521 + 255 * 3800 + // s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31 + int n = 3800; + if (n > len) { + n = len; + } + len -= n; + while (--n >= 0) { + s1 = s1 + (uint)(buf[off++] & 0xFF); + s2 = s2 + s1; + } + s1 %= BASE; + s2 %= BASE; + } + + checksum = (s2 << 16) | s1; + } + } +} diff --git a/irc/TechBot/Compression/Checksums/CRC32.cs b/irc/TechBot/Compression/Checksums/CRC32.cs new file mode 100644 index 00000000000..a78ad4c29f6 --- /dev/null +++ b/irc/TechBot/Compression/Checksums/CRC32.cs @@ -0,0 +1,211 @@ +// CRC32.cs - Computes CRC32 data checksum of a data stream +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +namespace ICSharpCode.SharpZipLib.Checksums +{ + + /// + /// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: + /// x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + /// + /// Polynomials over GF(2) are represented in binary, one bit per coefficient, + /// with the lowest powers in the most significant bit. Then adding polynomials + /// is just exclusive-or, and multiplying a polynomial by x is a right shift by + /// one. If we call the above polynomial p, and represent a byte as the + /// polynomial q, also with the lowest power in the most significant bit (so the + /// byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + /// where a mod b means the remainder after dividing a by b. + /// + /// This calculation is done using the shift-register method of multiplying and + /// taking the remainder. The register is initialized to zero, and for each + /// incoming bit, x^32 is added mod p to the register if the bit is a one (where + /// x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by + /// x (which is shifting right by one and adding x^32 mod p if the bit shifted + /// out is a one). We start with the highest power (least significant bit) of + /// q and repeat for all eight bits of q. + /// + /// The table is simply the CRC of all possible eight bit values. This is all + /// the information needed to generate CRC's on data a byte at a time for all + /// combinations of CRC register values and incoming bytes. + /// + public sealed class Crc32 : IChecksum + { + readonly static uint CrcSeed = 0xFFFFFFFF; + + readonly static uint[] CrcTable = new uint[] { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, + 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, + 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, + 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, + 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, + 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, + 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, + 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, + 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, + 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, + 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, + 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, + 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, + 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, + 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, + 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, + 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, + 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, + 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, + 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, + 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, + 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, + 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, + 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, + 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, + 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, + 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, + 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, + 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, + 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, + 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, + 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, + 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, + 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, + 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, + 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, + 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, + 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, + 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, + 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, + 0x2D02EF8D + }; + + internal static uint ComputeCrc32(uint oldCrc, byte bval) + { + return (uint)(Crc32.CrcTable[(oldCrc ^ bval) & 0xFF] ^ (oldCrc >> 8)); + } + + /// + /// The crc data checksum so far. + /// + uint crc = 0; + + /// + /// Returns the CRC32 data checksum computed so far. + /// + public long Value { + get { + return (long)crc; + } + set { + crc = (uint)value; + } + } + + /// + /// Resets the CRC32 data checksum as if no update was ever called. + /// + public void Reset() + { + crc = 0; + } + + /// + /// Updates the checksum with the int bval. + /// + /// + /// the byte is taken as the lower 8 bits of bval + /// + public void Update(int bval) + { + crc ^= CrcSeed; + crc = CrcTable[(crc ^ bval) & 0xFF] ^ (crc >> 8); + crc ^= CrcSeed; + } + + /// + /// Updates the checksum with the bytes taken from the array. + /// + /// + /// buffer an array of bytes + /// + public void Update(byte[] buffer) + { + Update(buffer, 0, buffer.Length); + } + + /// + /// Adds the byte array to the data checksum. + /// + /// + /// the buffer which contains the data + /// + /// + /// the offset in the buffer where the data starts + /// + /// + /// the length of the data + /// + public void Update(byte[] buf, int off, int len) + { + if (buf == null) { + throw new ArgumentNullException("buf"); + } + + if (off < 0 || len < 0 || off + len > buf.Length) { + throw new ArgumentOutOfRangeException(); + } + + crc ^= CrcSeed; + + while (--len >= 0) { + crc = CrcTable[(crc ^ buf[off++]) & 0xFF] ^ (crc >> 8); + } + + crc ^= CrcSeed; + } + } +} diff --git a/irc/TechBot/Compression/Checksums/IChecksum.cs b/irc/TechBot/Compression/Checksums/IChecksum.cs new file mode 100644 index 00000000000..98385b316ac --- /dev/null +++ b/irc/TechBot/Compression/Checksums/IChecksum.cs @@ -0,0 +1,93 @@ +// IChecksum.cs - Interface to compute a data checksum +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +namespace ICSharpCode.SharpZipLib.Checksums +{ + + /// + /// Interface to compute a data checksum used by checked input/output streams. + /// A data checksum can be updated by one byte or with a byte array. After each + /// update the value of the current checksum can be returned by calling + /// getValue. The complete checksum object can also be reset + /// so it can be used again with new data. + /// + public interface IChecksum + { + /// + /// Returns the data checksum computed so far. + /// + long Value + { + get; + } + + /// + /// Resets the data checksum as if no update was ever called. + /// + void Reset(); + + /// + /// Adds one byte to the data checksum. + /// + /// + /// the data value to add. The high byte of the int is ignored. + /// + void Update(int bval); + + /// + /// Updates the data checksum with the bytes taken from the array. + /// + /// + /// buffer an array of bytes + /// + void Update(byte[] buffer); + + /// + /// Adds the byte array to the data checksum. + /// + /// + /// the buffer which contains the data + /// + /// + /// the offset in the buffer where the data starts + /// + /// + /// the length of the data + /// + void Update(byte[] buf, int off, int len); + } +} diff --git a/irc/TechBot/Compression/Checksums/StrangeCRC.cs b/irc/TechBot/Compression/Checksums/StrangeCRC.cs new file mode 100644 index 00000000000..a2dc10fcf12 --- /dev/null +++ b/irc/TechBot/Compression/Checksums/StrangeCRC.cs @@ -0,0 +1,159 @@ +// StrangeCRC.cs - computes a crc used in the bziplib ... I don't think that +// this is the 'standard' crc, please correct me, if I'm wrong +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +namespace ICSharpCode.SharpZipLib.Checksums +{ + + public class StrangeCRC : IChecksum + { + readonly static uint[] crc32Table = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, + 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, + 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, + 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, + 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, + 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, + 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, + 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, + 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, + 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, + 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, + 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, + 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, + 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, + 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, + 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, + 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, + 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 + }; + int globalCrc; + + public StrangeCRC() + { + Reset(); + } + + public void Reset() + { + globalCrc = -1; + } + + public long Value { + get { + return ~globalCrc; + } + } + + public void Update(int inCh) + { + int temp = (globalCrc >> 24) ^ inCh; + if (temp < 0) { + temp = 256 + temp; + } + globalCrc = (int)((globalCrc << 8) ^ crc32Table[temp]); + } + + public void Update(byte[] buf) + { + Update(buf, 0, buf.Length); + } + + public void Update(byte[] buf, int off, int len) + { + if (buf == null) { + throw new ArgumentNullException("buf"); + } + + if (off < 0 || len < 0 || off + len > buf.Length) { + throw new ArgumentOutOfRangeException(); + } + + for (int i = 0; i < len; ++i) { + Update(buf[off++]); + } + } + } +} diff --git a/irc/TechBot/Compression/Compression.cmbx b/irc/TechBot/Compression/Compression.cmbx new file mode 100644 index 00000000000..28d8e0cd398 --- /dev/null +++ b/irc/TechBot/Compression/Compression.cmbx @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/irc/TechBot/Compression/Compression.prjx b/irc/TechBot/Compression/Compression.prjx new file mode 100644 index 00000000000..95316d15b26 --- /dev/null +++ b/irc/TechBot/Compression/Compression.prjx @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/irc/TechBot/Compression/Default.build b/irc/TechBot/Compression/Default.build new file mode 100644 index 00000000000..5a229e272d6 --- /dev/null +++ b/irc/TechBot/Compression/Default.build @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + diff --git a/irc/TechBot/Compression/Deflater.cs b/irc/TechBot/Compression/Deflater.cs new file mode 100644 index 00000000000..bb8d4cc3ca8 --- /dev/null +++ b/irc/TechBot/Compression/Deflater.cs @@ -0,0 +1,542 @@ +// Deflater.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +namespace ICSharpCode.SharpZipLib.Zip.Compression +{ + + /// + /// This is the Deflater class. The deflater class compresses input + /// with the deflate algorithm described in RFC 1951. It has several + /// compression levels and three different strategies described below. + /// + /// This class is not thread safe. This is inherent in the API, due + /// to the split of deflate and setInput. + /// + /// author of the original java version : Jochen Hoenicke + /// + public class Deflater + { + /// + /// The best and slowest compression level. This tries to find very + /// long and distant string repetitions. + /// + public static int BEST_COMPRESSION = 9; + + /// + /// The worst but fastest compression level. + /// + public static int BEST_SPEED = 1; + + /// + /// The default compression level. + /// + public static int DEFAULT_COMPRESSION = -1; + + /// + /// This level won't compress at all but output uncompressed blocks. + /// + public static int NO_COMPRESSION = 0; + + /// + /// The compression method. This is the only method supported so far. + /// There is no need to use this constant at all. + /// + public static int DEFLATED = 8; + + /* + * The Deflater can do the following state transitions: + * + * (1) -> INIT_STATE ----> INIT_FINISHING_STATE ---. + * / | (2) (5) | + * / v (5) | + * (3)| SETDICT_STATE ---> SETDICT_FINISHING_STATE |(3) + * \ | (3) | ,-------' + * | | | (3) / + * v v (5) v v + * (1) -> BUSY_STATE ----> FINISHING_STATE + * | (6) + * v + * FINISHED_STATE + * \_____________________________________/ + * | (7) + * v + * CLOSED_STATE + * + * (1) If we should produce a header we start in INIT_STATE, otherwise + * we start in BUSY_STATE. + * (2) A dictionary may be set only when we are in INIT_STATE, then + * we change the state as indicated. + * (3) Whether a dictionary is set or not, on the first call of deflate + * we change to BUSY_STATE. + * (4) -- intentionally left blank -- :) + * (5) FINISHING_STATE is entered, when flush() is called to indicate that + * there is no more INPUT. There are also states indicating, that + * the header wasn't written yet. + * (6) FINISHED_STATE is entered, when everything has been flushed to the + * internal pending output buffer. + * (7) At any time (7) + * + */ + + private static int IS_SETDICT = 0x01; + private static int IS_FLUSHING = 0x04; + private static int IS_FINISHING = 0x08; + + private static int INIT_STATE = 0x00; + private static int SETDICT_STATE = 0x01; + // private static int INIT_FINISHING_STATE = 0x08; + // private static int SETDICT_FINISHING_STATE = 0x09; + private static int BUSY_STATE = 0x10; + private static int FLUSHING_STATE = 0x14; + private static int FINISHING_STATE = 0x1c; + private static int FINISHED_STATE = 0x1e; + private static int CLOSED_STATE = 0x7f; + + /// + /// Compression level. + /// + private int level; + + /// + /// should we include a header. + /// + private bool noHeader; + + // /// + // /// Compression strategy. + // /// + // private int strategy; + + /// + /// The current state. + /// + private int state; + + /// + /// The total bytes of output written. + /// + private int totalOut; + + /// + /// The pending output. + /// + private DeflaterPending pending; + + /// + /// The deflater engine. + /// + private DeflaterEngine engine; + + /// + /// Creates a new deflater with default compression level. + /// + public Deflater() : this(DEFAULT_COMPRESSION, false) + { + + } + + /// + /// Creates a new deflater with given compression level. + /// + /// + /// the compression level, a value between NO_COMPRESSION + /// and BEST_COMPRESSION, or DEFAULT_COMPRESSION. + /// + /// if lvl is out of range. + public Deflater(int lvl) : this(lvl, false) + { + + } + + /// + /// Creates a new deflater with given compression level. + /// + /// + /// the compression level, a value between NO_COMPRESSION + /// and BEST_COMPRESSION. + /// + /// + /// true, if we should suppress the deflate header at the + /// beginning and the adler checksum at the end of the output. This is + /// useful for the GZIP format. + /// + /// if lvl is out of range. + public Deflater(int lvl, bool nowrap) + { + if (lvl == DEFAULT_COMPRESSION) { + lvl = 6; + } else if (lvl < NO_COMPRESSION || lvl > BEST_COMPRESSION) { + throw new ArgumentOutOfRangeException("lvl"); + } + + pending = new DeflaterPending(); + engine = new DeflaterEngine(pending); + this.noHeader = nowrap; + SetStrategy(DeflateStrategy.Default); + SetLevel(lvl); + Reset(); + } + + + /// + /// Resets the deflater. The deflater acts afterwards as if it was + /// just created with the same compression level and strategy as it + /// had before. + /// + public void Reset() + { + state = (noHeader ? BUSY_STATE : INIT_STATE); + totalOut = 0; + pending.Reset(); + engine.Reset(); + } + + /// + /// Gets the current adler checksum of the data that was processed so far. + /// + public int Adler { + get { + return engine.Adler; + } + } + + /// + /// Gets the number of input bytes processed so far. + /// + public int TotalIn { + get { + return engine.TotalIn; + } + } + + /// + /// Gets the number of output bytes so far. + /// + public int TotalOut { + get { + return totalOut; + } + } + + /// + /// Flushes the current input block. Further calls to deflate() will + /// produce enough output to inflate everything in the current input + /// block. This is not part of Sun's JDK so I have made it package + /// private. It is used by DeflaterOutputStream to implement + /// flush(). + /// + public void Flush() + { + state |= IS_FLUSHING; + } + + /// + /// Finishes the deflater with the current input block. It is an error + /// to give more input after this method was called. This method must + /// be called to force all bytes to be flushed. + /// + public void Finish() + { + state |= IS_FLUSHING | IS_FINISHING; + } + + /// + /// Returns true if the stream was finished and no more output bytes + /// are available. + /// + public bool IsFinished { + get { + return state == FINISHED_STATE && pending.IsFlushed; + } + } + + /// + /// Returns true, if the input buffer is empty. + /// You should then call setInput(). + /// NOTE: This method can also return true when the stream + /// was finished. + /// + public bool IsNeedingInput { + get { + return engine.NeedsInput(); + } + } + + /// + /// Sets the data which should be compressed next. This should be only + /// called when needsInput indicates that more input is needed. + /// If you call setInput when needsInput() returns false, the + /// previous input that is still pending will be thrown away. + /// The given byte array should not be changed, before needsInput() returns + /// true again. + /// This call is equivalent to setInput(input, 0, input.length). + /// + /// + /// the buffer containing the input data. + /// + /// + /// if the buffer was finished() or ended(). + /// + public void SetInput(byte[] input) + { + SetInput(input, 0, input.Length); + } + + /// + /// Sets the data which should be compressed next. This should be + /// only called when needsInput indicates that more input is needed. + /// The given byte array should not be changed, before needsInput() returns + /// true again. + /// + /// + /// the buffer containing the input data. + /// + /// + /// the start of the data. + /// + /// + /// the length of the data. + /// + /// + /// if the buffer was finished() or ended() or if previous input is still pending. + /// + public void SetInput(byte[] input, int off, int len) + { + if ((state & IS_FINISHING) != 0) { + throw new InvalidOperationException("finish()/end() already called"); + } + engine.SetInput(input, off, len); + } + + /// + /// Sets the compression level. There is no guarantee of the exact + /// position of the change, but if you call this when needsInput is + /// true the change of compression level will occur somewhere near + /// before the end of the so far given input. + /// + /// + /// the new compression level. + /// + public void SetLevel(int lvl) + { + if (lvl == DEFAULT_COMPRESSION) { + lvl = 6; + } else if (lvl < NO_COMPRESSION || lvl > BEST_COMPRESSION) { + throw new ArgumentOutOfRangeException("lvl"); + } + + if (level != lvl) { + level = lvl; + engine.SetLevel(lvl); + } + } + + /// + /// Sets the compression strategy. Strategy is one of + /// DEFAULT_STRATEGY, HUFFMAN_ONLY and FILTERED. For the exact + /// position where the strategy is changed, the same as for + /// setLevel() applies. + /// + /// + /// the new compression strategy. + /// + public void SetStrategy(DeflateStrategy stgy) + { + engine.Strategy = stgy; + } + + /// + /// Deflates the current input block to the given array. It returns + /// the number of bytes compressed, or 0 if either + /// needsInput() or finished() returns true or length is zero. + /// + /// + /// the buffer where to write the compressed data. + /// + public int Deflate(byte[] output) + { + return Deflate(output, 0, output.Length); + } + + /// + /// Deflates the current input block to the given array. It returns + /// the number of bytes compressed, or 0 if either + /// needsInput() or finished() returns true or length is zero. + /// + /// + /// the buffer where to write the compressed data. + /// + /// + /// the offset into the output array. + /// + /// + /// the maximum number of bytes that may be written. + /// + /// + /// if end() was called. + /// + /// + /// if offset and/or length don't match the array length. + /// + public int Deflate(byte[] output, int offset, int length) + { + int origLength = length; + + if (state == CLOSED_STATE) { + throw new InvalidOperationException("Deflater closed"); + } + + if (state < BUSY_STATE) { + /* output header */ + int header = (DEFLATED + + ((DeflaterConstants.MAX_WBITS - 8) << 4)) << 8; + int level_flags = (level - 1) >> 1; + if (level_flags < 0 || level_flags > 3) { + level_flags = 3; + } + header |= level_flags << 6; + if ((state & IS_SETDICT) != 0) { + /* Dictionary was set */ + header |= DeflaterConstants.PRESET_DICT; + } + header += 31 - (header % 31); + + + pending.WriteShortMSB(header); + if ((state & IS_SETDICT) != 0) { + int chksum = engine.Adler; + engine.ResetAdler(); + pending.WriteShortMSB(chksum >> 16); + pending.WriteShortMSB(chksum & 0xffff); + } + + state = BUSY_STATE | (state & (IS_FLUSHING | IS_FINISHING)); + } + + for (;;) { + int count = pending.Flush(output, offset, length); + offset += count; + totalOut += count; + length -= count; + + if (length == 0 || state == FINISHED_STATE) { + break; + } + + if (!engine.Deflate((state & IS_FLUSHING) != 0, (state & IS_FINISHING) != 0)) { + if (state == BUSY_STATE) { + /* We need more input now */ + return origLength - length; + } else if (state == FLUSHING_STATE) { + if (level != NO_COMPRESSION) { + /* We have to supply some lookahead. 8 bit lookahead + * are needed by the zlib inflater, and we must fill + * the next byte, so that all bits are flushed. + */ + int neededbits = 8 + ((-pending.BitCount) & 7); + while (neededbits > 0) { + /* write a static tree block consisting solely of + * an EOF: + */ + pending.WriteBits(2, 10); + neededbits -= 10; + } + } + state = BUSY_STATE; + } else if (state == FINISHING_STATE) { + pending.AlignToByte(); + /* We have completed the stream */ + if (!noHeader) { + int adler = engine.Adler; + pending.WriteShortMSB(adler >> 16); + pending.WriteShortMSB(adler & 0xffff); + } + state = FINISHED_STATE; + } + } + } + return origLength - length; + } + + /// + /// Sets the dictionary which should be used in the deflate process. + /// This call is equivalent to setDictionary(dict, 0, dict.Length). + /// + /// + /// the dictionary. + /// + /// + /// if setInput () or deflate () were already called or another dictionary was already set. + /// + public void SetDictionary(byte[] dict) + { + SetDictionary(dict, 0, dict.Length); + } + + /// + /// Sets the dictionary which should be used in the deflate process. + /// The dictionary should be a byte array containing strings that are + /// likely to occur in the data which should be compressed. The + /// dictionary is not stored in the compressed output, only a + /// checksum. To decompress the output you need to supply the same + /// dictionary again. + /// + /// + /// the dictionary. + /// + /// + /// an offset into the dictionary. + /// + /// + /// the length of the dictionary. + /// + /// + /// if setInput () or deflate () were already called or another dictionary was already set. + /// + public void SetDictionary(byte[] dict, int offset, int length) + { + if (state != INIT_STATE) { + throw new InvalidOperationException(); + } + + state = SETDICT_STATE; + engine.SetDictionary(dict, offset, length); + } + } +} diff --git a/irc/TechBot/Compression/DeflaterConstants.cs b/irc/TechBot/Compression/DeflaterConstants.cs new file mode 100644 index 00000000000..8e2be6e21ca --- /dev/null +++ b/irc/TechBot/Compression/DeflaterConstants.cs @@ -0,0 +1,85 @@ +// DeflaterConstants.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +namespace ICSharpCode.SharpZipLib.Zip.Compression +{ + + /// + /// This class contains constants used for the deflater. + /// + public class DeflaterConstants + { + public const bool DEBUGGING = false; + + public const int STORED_BLOCK = 0; + public const int STATIC_TREES = 1; + public const int DYN_TREES = 2; + public const int PRESET_DICT = 0x20; + + public const int DEFAULT_MEM_LEVEL = 8; + + public const int MAX_MATCH = 258; + public const int MIN_MATCH = 3; + + public const int MAX_WBITS = 15; + public const int WSIZE = 1 << MAX_WBITS; + public const int WMASK = WSIZE - 1; + + public const int HASH_BITS = DEFAULT_MEM_LEVEL + 7; + public const int HASH_SIZE = 1 << HASH_BITS; + public const int HASH_MASK = HASH_SIZE - 1; + public const int HASH_SHIFT = (HASH_BITS + MIN_MATCH - 1) / MIN_MATCH; + + public const int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1; + public const int MAX_DIST = WSIZE - MIN_LOOKAHEAD; + + public const int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8); + public static int MAX_BLOCK_SIZE = Math.Min(65535, PENDING_BUF_SIZE-5); + + public const int DEFLATE_STORED = 0; + public const int DEFLATE_FAST = 1; + public const int DEFLATE_SLOW = 2; + + public static int[] GOOD_LENGTH = { 0, 4, 4, 4, 4, 8, 8, 8, 32, 32 }; + public static int[] MAX_LAZY = { 0, 4, 5, 6, 4,16, 16, 32, 128, 258 }; + public static int[] NICE_LENGTH = { 0, 8,16,32,16,32,128,128, 258, 258 }; + public static int[] MAX_CHAIN = { 0, 4, 8,32,16,32,128,256,1024,4096 }; + public static int[] COMPR_FUNC = { 0, 1, 1, 1, 1, 2, 2, 2, 2, 2 }; + } +} diff --git a/irc/TechBot/Compression/DeflaterEngine.cs b/irc/TechBot/Compression/DeflaterEngine.cs new file mode 100644 index 00000000000..c543f850980 --- /dev/null +++ b/irc/TechBot/Compression/DeflaterEngine.cs @@ -0,0 +1,653 @@ +// DeflaterEngine.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +using ICSharpCode.SharpZipLib.Checksums; + +namespace ICSharpCode.SharpZipLib.Zip.Compression +{ + + public enum DeflateStrategy + { + // The default strategy. + Default = 0, + + // This strategy will only allow longer string repetitions. It is + // useful for random data with a small character set. + Filtered = 1, + + // This strategy will not look for string repetitions at all. It + // only encodes with Huffman trees (which means, that more common + // characters get a smaller encoding. + HuffmanOnly = 2 + } + + public class DeflaterEngine : DeflaterConstants + { + static int TOO_FAR = 4096; + + int ins_h; + // private byte[] buffer; + short[] head; + short[] prev; + + int matchStart, matchLen; + bool prevAvailable; + int blockStart; + int strstart, lookahead; + byte[] window; + + DeflateStrategy strategy; + int max_chain, max_lazy, niceLength, goodLength; + + /// + /// The current compression function. + /// + int comprFunc; + + /// + /// The input data for compression. + /// + byte[] inputBuf; + + /// + /// The total bytes of input read. + /// + int totalIn; + + /// + /// The offset into inputBuf, where input data starts. + /// + int inputOff; + + /// + /// The end offset of the input data. + /// + int inputEnd; + + DeflaterPending pending; + DeflaterHuffman huffman; + + /// + /// The adler checksum + /// + Adler32 adler; + + public DeflaterEngine(DeflaterPending pending) + { + this.pending = pending; + huffman = new DeflaterHuffman(pending); + adler = new Adler32(); + + window = new byte[2 * WSIZE]; + head = new short[HASH_SIZE]; + prev = new short[WSIZE]; + + /* We start at index 1, to avoid a implementation deficiency, that + * we cannot build a repeat pattern at index 0. + */ + blockStart = strstart = 1; + } + + public void Reset() + { + huffman.Reset(); + adler.Reset(); + blockStart = strstart = 1; + lookahead = 0; + totalIn = 0; + prevAvailable = false; + matchLen = MIN_MATCH - 1; + + for (int i = 0; i < HASH_SIZE; i++) { + head[i] = 0; + } + + for (int i = 0; i < WSIZE; i++) { + prev[i] = 0; + } + } + + public void ResetAdler() + { + adler.Reset(); + } + + public int Adler { + get { + return (int)adler.Value; + } + } + + public int TotalIn { + get { + return totalIn; + } + } + + public DeflateStrategy Strategy { + get { + return strategy; + } + set { + strategy = value; + } + } + + public void SetLevel(int lvl) + { + goodLength = DeflaterConstants.GOOD_LENGTH[lvl]; + max_lazy = DeflaterConstants.MAX_LAZY[lvl]; + niceLength = DeflaterConstants.NICE_LENGTH[lvl]; + max_chain = DeflaterConstants.MAX_CHAIN[lvl]; + + if (DeflaterConstants.COMPR_FUNC[lvl] != comprFunc) { + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("Change from "+comprFunc +" to " + // + DeflaterConstants.COMPR_FUNC[lvl]); + // } + switch (comprFunc) { + case DEFLATE_STORED: + if (strstart > blockStart) { + huffman.FlushStoredBlock(window, blockStart, + strstart - blockStart, false); + blockStart = strstart; + } + UpdateHash(); + break; + case DEFLATE_FAST: + if (strstart > blockStart) { + huffman.FlushBlock(window, blockStart, strstart - blockStart, + false); + blockStart = strstart; + } + break; + case DEFLATE_SLOW: + if (prevAvailable) { + huffman.TallyLit(window[strstart-1] & 0xff); + } + if (strstart > blockStart) { + huffman.FlushBlock(window, blockStart, strstart - blockStart, false); + blockStart = strstart; + } + prevAvailable = false; + matchLen = MIN_MATCH - 1; + break; + } + comprFunc = COMPR_FUNC[lvl]; + } + } + + void UpdateHash() + { + // if (DEBUGGING) { + // //Console.WriteLine("updateHash: "+strstart); + // } + ins_h = (window[strstart] << HASH_SHIFT) ^ window[strstart + 1]; + } + + int InsertString() + { + short match; + int hash = ((ins_h << HASH_SHIFT) ^ window[strstart + (MIN_MATCH -1)]) & HASH_MASK; + + // if (DEBUGGING) { + // if (hash != (((window[strstart] << (2*HASH_SHIFT)) ^ + // (window[strstart + 1] << HASH_SHIFT) ^ + // (window[strstart + 2])) & HASH_MASK)) { + // throw new Exception("hash inconsistent: "+hash+"/" + // +window[strstart]+"," + // +window[strstart+1]+"," + // +window[strstart+2]+","+HASH_SHIFT); + // } + // } + + prev[strstart & WMASK] = match = head[hash]; + head[hash] = (short)strstart; + ins_h = hash; + return match & 0xffff; + } + + void SlideWindow() + { + Array.Copy(window, WSIZE, window, 0, WSIZE); + matchStart -= WSIZE; + strstart -= WSIZE; + blockStart -= WSIZE; + + /* Slide the hash table (could be avoided with 32 bit values + * at the expense of memory usage). + */ + for (int i = 0; i < HASH_SIZE; ++i) { + int m = head[i] & 0xffff; + head[i] = (short)(m >= WSIZE ? (m - WSIZE) : 0); + } + + /* Slide the prev table. */ + for (int i = 0; i < WSIZE; i++) { + int m = prev[i] & 0xffff; + prev[i] = (short)(m >= WSIZE ? (m - WSIZE) : 0); + } + } + + public void FillWindow() + { + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (strstart >= WSIZE + MAX_DIST) { + SlideWindow(); + } + + /* If there is not enough lookahead, but still some input left, + * read in the input + */ + while (lookahead < DeflaterConstants.MIN_LOOKAHEAD && inputOff < inputEnd) { + int more = 2 * WSIZE - lookahead - strstart; + + if (more > inputEnd - inputOff) { + more = inputEnd - inputOff; + } + + System.Array.Copy(inputBuf, inputOff, window, strstart + lookahead, more); + adler.Update(inputBuf, inputOff, more); + + inputOff += more; + totalIn += more; + lookahead += more; + } + + if (lookahead >= MIN_MATCH) { + UpdateHash(); + } + } + + bool FindLongestMatch(int curMatch) + { + int chainLength = this.max_chain; + int niceLength = this.niceLength; + short[] prev = this.prev; + int scan = this.strstart; + int match; + int best_end = this.strstart + matchLen; + int best_len = Math.Max(matchLen, MIN_MATCH - 1); + + int limit = Math.Max(strstart - MAX_DIST, 0); + + int strend = strstart + MAX_MATCH - 1; + byte scan_end1 = window[best_end - 1]; + byte scan_end = window[best_end]; + + /* Do not waste too much time if we already have a good match: */ + if (best_len >= this.goodLength) { + chainLength >>= 2; + } + + /* Do not look for matches beyond the end of the input. This is necessary + * to make deflate deterministic. + */ + if (niceLength > lookahead) { + niceLength = lookahead; + } + + if (DeflaterConstants.DEBUGGING && strstart > 2 * WSIZE - MIN_LOOKAHEAD) { + throw new InvalidOperationException("need lookahead"); + } + + do { + if (DeflaterConstants.DEBUGGING && curMatch >= strstart) { + throw new InvalidOperationException("future match"); + } + if (window[curMatch + best_len] != scan_end || + window[curMatch + best_len - 1] != scan_end1 || + window[curMatch] != window[scan] || + window[curMatch + 1] != window[scan + 1]) { + continue; + } + + match = curMatch + 2; + scan += 2; + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + while (window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && scan < strend) ; + + if (scan > best_end) { + // if (DeflaterConstants.DEBUGGING && ins_h == 0) + // System.err.println("Found match: "+curMatch+"-"+(scan-strstart)); + matchStart = curMatch; + best_end = scan; + best_len = scan - strstart; + + if (best_len >= niceLength) { + break; + } + + scan_end1 = window[best_end - 1]; + scan_end = window[best_end]; + } + scan = strstart; + } while ((curMatch = (prev[curMatch & WMASK] & 0xffff)) > limit && --chainLength != 0); + + matchLen = Math.Min(best_len, lookahead); + return matchLen >= MIN_MATCH; + } + + public void SetDictionary(byte[] buffer, int offset, int length) + { + if (DeflaterConstants.DEBUGGING && strstart != 1) { + throw new InvalidOperationException("strstart not 1"); + } + adler.Update(buffer, offset, length); + if (length < MIN_MATCH) { + return; + } + if (length > MAX_DIST) { + offset += length - MAX_DIST; + length = MAX_DIST; + } + + System.Array.Copy(buffer, offset, window, strstart, length); + + UpdateHash(); + --length; + while (--length > 0) { + InsertString(); + strstart++; + } + strstart += 2; + blockStart = strstart; + } + + bool DeflateStored(bool flush, bool finish) + { + if (!flush && lookahead == 0) { + return false; + } + + strstart += lookahead; + lookahead = 0; + + int storedLen = strstart - blockStart; + + if ((storedLen >= DeflaterConstants.MAX_BLOCK_SIZE) || /* Block is full */ + (blockStart < WSIZE && storedLen >= MAX_DIST) || /* Block may move out of window */ + flush) { + bool lastBlock = finish; + if (storedLen > DeflaterConstants.MAX_BLOCK_SIZE) { + storedLen = DeflaterConstants.MAX_BLOCK_SIZE; + lastBlock = false; + } + + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("storedBlock["+storedLen+","+lastBlock+"]"); + // } + + huffman.FlushStoredBlock(window, blockStart, storedLen, lastBlock); + blockStart += storedLen; + return !lastBlock; + } + return true; + } + + private bool DeflateFast(bool flush, bool finish) + { + if (lookahead < MIN_LOOKAHEAD && !flush) { + return false; + } + + while (lookahead >= MIN_LOOKAHEAD || flush) { + if (lookahead == 0) { + /* We are flushing everything */ + huffman.FlushBlock(window, blockStart, strstart - blockStart, finish); + blockStart = strstart; + return false; + } + + if (strstart > 2 * WSIZE - MIN_LOOKAHEAD) { + /* slide window, as findLongestMatch need this. + * This should only happen when flushing and the window + * is almost full. + */ + SlideWindow(); + } + + int hashHead; + if (lookahead >= MIN_MATCH && + (hashHead = InsertString()) != 0 && + strategy != DeflateStrategy.HuffmanOnly && + strstart - hashHead <= MAX_DIST && + FindLongestMatch(hashHead)) { + /* longestMatch sets matchStart and matchLen */ + // if (DeflaterConstants.DEBUGGING) { + // for (int i = 0 ; i < matchLen; i++) { + // if (window[strstart+i] != window[matchStart + i]) { + // throw new Exception(); + // } + // } + // } + + // -jr- Hak hak hak this stops problems with fast/low compression and index out of range + if (huffman.TallyDist(strstart - matchStart, matchLen)) { + bool lastBlock = finish && lookahead == 0; + huffman.FlushBlock(window, blockStart, strstart - blockStart, lastBlock); + blockStart = strstart; + } + + lookahead -= matchLen; + if (matchLen <= max_lazy && lookahead >= MIN_MATCH) { + while (--matchLen > 0) { + ++strstart; + InsertString(); + } + ++strstart; + } else { + strstart += matchLen; + if (lookahead >= MIN_MATCH - 1) { + UpdateHash(); + } + } + matchLen = MIN_MATCH - 1; + continue; + } else { + /* No match found */ + huffman.TallyLit(window[strstart] & 0xff); + ++strstart; + --lookahead; + } + + if (huffman.IsFull()) { + bool lastBlock = finish && lookahead == 0; + huffman.FlushBlock(window, blockStart, strstart - blockStart, lastBlock); + blockStart = strstart; + return !lastBlock; + } + } + return true; + } + + bool DeflateSlow(bool flush, bool finish) + { + if (lookahead < MIN_LOOKAHEAD && !flush) { + return false; + } + + while (lookahead >= MIN_LOOKAHEAD || flush) { + if (lookahead == 0) { + if (prevAvailable) { + huffman.TallyLit(window[strstart-1] & 0xff); + } + prevAvailable = false; + + /* We are flushing everything */ + if (DeflaterConstants.DEBUGGING && !flush) { + throw new Exception("Not flushing, but no lookahead"); + } + huffman.FlushBlock(window, blockStart, strstart - blockStart, + finish); + blockStart = strstart; + return false; + } + + if (strstart >= 2 * WSIZE - MIN_LOOKAHEAD) { + /* slide window, as findLongestMatch need this. + * This should only happen when flushing and the window + * is almost full. + */ + SlideWindow(); + } + + int prevMatch = matchStart; + int prevLen = matchLen; + if (lookahead >= MIN_MATCH) { + int hashHead = InsertString(); + if (strategy != DeflateStrategy.HuffmanOnly && hashHead != 0 && strstart - hashHead <= MAX_DIST && FindLongestMatch(hashHead)) { + /* longestMatch sets matchStart and matchLen */ + + /* Discard match if too small and too far away */ + if (matchLen <= 5 && (strategy == DeflateStrategy.Filtered || (matchLen == MIN_MATCH && strstart - matchStart > TOO_FAR))) { + matchLen = MIN_MATCH - 1; + } + } + } + + /* previous match was better */ + if (prevLen >= MIN_MATCH && matchLen <= prevLen) { + // if (DeflaterConstants.DEBUGGING) { + // for (int i = 0 ; i < matchLen; i++) { + // if (window[strstart-1+i] != window[prevMatch + i]) + // throw new Exception(); + // } + // } + huffman.TallyDist(strstart - 1 - prevMatch, prevLen); + prevLen -= 2; + do { + strstart++; + lookahead--; + if (lookahead >= MIN_MATCH) { + InsertString(); + } + } while (--prevLen > 0); + strstart ++; + lookahead--; + prevAvailable = false; + matchLen = MIN_MATCH - 1; + } else { + if (prevAvailable) { + huffman.TallyLit(window[strstart-1] & 0xff); + } + prevAvailable = true; + strstart++; + lookahead--; + } + + if (huffman.IsFull()) { + int len = strstart - blockStart; + if (prevAvailable) { + len--; + } + bool lastBlock = (finish && lookahead == 0 && !prevAvailable); + huffman.FlushBlock(window, blockStart, len, lastBlock); + blockStart += len; + return !lastBlock; + } + } + return true; + } + + public bool Deflate(bool flush, bool finish) + { + bool progress; + do { + FillWindow(); + bool canFlush = flush && inputOff == inputEnd; + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("window: ["+blockStart+","+strstart+"," + // +lookahead+"], "+comprFunc+","+canFlush); + // } + switch (comprFunc) { + case DEFLATE_STORED: + progress = DeflateStored(canFlush, finish); + break; + case DEFLATE_FAST: + progress = DeflateFast(canFlush, finish); + break; + case DEFLATE_SLOW: + progress = DeflateSlow(canFlush, finish); + break; + default: + throw new InvalidOperationException("unknown comprFunc"); + } + } while (pending.IsFlushed && progress); /* repeat while we have no pending output and progress was made */ + return progress; + } + + public void SetInput(byte[] buf, int off, int len) + { + if (inputOff < inputEnd) { + throw new InvalidOperationException("Old input was not completely processed"); + } + + int end = off + len; + + /* We want to throw an ArrayIndexOutOfBoundsException early. The + * check is very tricky: it also handles integer wrap around. + */ + if (0 > off || off > end || end > buf.Length) { + throw new ArgumentOutOfRangeException(); + } + + inputBuf = buf; + inputOff = off; + inputEnd = end; + } + + public bool NeedsInput() + { + return inputEnd == inputOff; + } + } +} diff --git a/irc/TechBot/Compression/DeflaterHuffman.cs b/irc/TechBot/Compression/DeflaterHuffman.cs new file mode 100644 index 00000000000..338d09ed242 --- /dev/null +++ b/irc/TechBot/Compression/DeflaterHuffman.cs @@ -0,0 +1,780 @@ +// DeflaterHuffman.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +namespace ICSharpCode.SharpZipLib.Zip.Compression +{ + + /// + /// This is the DeflaterHuffman class. + /// + /// This class is not thread safe. This is inherent in the API, due + /// to the split of deflate and setInput. + /// + /// author of the original java version : Jochen Hoenicke + /// + public class DeflaterHuffman + { + private static int BUFSIZE = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6); + private static int LITERAL_NUM = 286; + private static int DIST_NUM = 30; + private static int BITLEN_NUM = 19; + private static int REP_3_6 = 16; + private static int REP_3_10 = 17; + private static int REP_11_138 = 18; + private static int EOF_SYMBOL = 256; + private static int[] BL_ORDER = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + + private static byte[] bit4Reverse = { + 0, + 8, + 4, + 12, + 2, + 10, + 6, + 14, + 1, + 9, + 5, + 13, + 3, + 11, + 7, + 15 + }; + + public class Tree + { + public short[] freqs; + public byte[] length; + public int minNumCodes, numCodes; + + short[] codes; + int[] bl_counts; + int maxLength; + DeflaterHuffman dh; + + public Tree(DeflaterHuffman dh, int elems, int minCodes, int maxLength) + { + this.dh = dh; + this.minNumCodes = minCodes; + this.maxLength = maxLength; + freqs = new short[elems]; + bl_counts = new int[maxLength]; + } + + public void Reset() + { + for (int i = 0; i < freqs.Length; i++) { + freqs[i] = 0; + } + codes = null; + length = null; + } + + public void WriteSymbol(int code) + { + // if (DeflaterConstants.DEBUGGING) { + // freqs[code]--; + // // Console.Write("writeSymbol("+freqs.length+","+code+"): "); + // } + dh.pending.WriteBits(codes[code] & 0xffff, length[code]); + } + + public void CheckEmpty() + { + bool empty = true; + for (int i = 0; i < freqs.Length; i++) { + if (freqs[i] != 0) { + //Console.WriteLine("freqs["+i+"] == "+freqs[i]); + empty = false; + } + } + if (!empty) { + throw new Exception(); + } + //Console.WriteLine("checkEmpty suceeded!"); + } + + public void SetStaticCodes(short[] stCodes, byte[] stLength) + { + codes = stCodes; + length = stLength; + } + + public void BuildCodes() + { + int numSymbols = freqs.Length; + int[] nextCode = new int[maxLength]; + int code = 0; + codes = new short[freqs.Length]; + + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("buildCodes: "+freqs.Length); + // } + + for (int bits = 0; bits < maxLength; bits++) { + nextCode[bits] = code; + code += bl_counts[bits] << (15 - bits); + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("bits: "+(bits+1)+" count: "+bl_counts[bits] + // +" nextCode: "+code); // HACK : Integer.toHexString( + // } + } + if (DeflaterConstants.DEBUGGING && code != 65536) { + throw new Exception("Inconsistent bl_counts!"); + } + + for (int i=0; i < numCodes; i++) { + int bits = length[i]; + if (bits > 0) { + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("codes["+i+"] = rev(" + nextCode[bits-1]+")," // HACK : Integer.toHexString( + // +bits); + // } + codes[i] = BitReverse(nextCode[bits-1]); + nextCode[bits-1] += 1 << (16 - bits); + } + } + } + + void BuildLength(int[] childs) + { + this.length = new byte [freqs.Length]; + int numNodes = childs.Length / 2; + int numLeafs = (numNodes + 1) / 2; + int overflow = 0; + + for (int i = 0; i < maxLength; i++) { + bl_counts[i] = 0; + } + + /* First calculate optimal bit lengths */ + int[] lengths = new int[numNodes]; + lengths[numNodes-1] = 0; + + for (int i = numNodes - 1; i >= 0; i--) { + if (childs[2*i+1] != -1) { + int bitLength = lengths[i] + 1; + if (bitLength > maxLength) { + bitLength = maxLength; + overflow++; + } + lengths[childs[2*i]] = lengths[childs[2*i+1]] = bitLength; + } else { + /* A leaf node */ + int bitLength = lengths[i]; + bl_counts[bitLength - 1]++; + this.length[childs[2*i]] = (byte) lengths[i]; + } + } + + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("Tree "+freqs.Length+" lengths:"); + // for (int i=0; i < numLeafs; i++) { + // //Console.WriteLine("Node "+childs[2*i]+" freq: "+freqs[childs[2*i]] + // + " len: "+length[childs[2*i]]); + // } + // } + + if (overflow == 0) { + return; + } + + int incrBitLen = maxLength - 1; + do { + /* Find the first bit length which could increase: */ + while (bl_counts[--incrBitLen] == 0) + ; + + /* Move this node one down and remove a corresponding + * amount of overflow nodes. + */ + do { + bl_counts[incrBitLen]--; + bl_counts[++incrBitLen]++; + overflow -= 1 << (maxLength - 1 - incrBitLen); + } while (overflow > 0 && incrBitLen < maxLength - 1); + } while (overflow > 0); + + /* We may have overshot above. Move some nodes from maxLength to + * maxLength-1 in that case. + */ + bl_counts[maxLength-1] += overflow; + bl_counts[maxLength-2] -= overflow; + + /* Now recompute all bit lengths, scanning in increasing + * frequency. It is simpler to reconstruct all lengths instead of + * fixing only the wrong ones. This idea is taken from 'ar' + * written by Haruhiko Okumura. + * + * The nodes were inserted with decreasing frequency into the childs + * array. + */ + int nodePtr = 2 * numLeafs; + for (int bits = maxLength; bits != 0; bits--) { + int n = bl_counts[bits-1]; + while (n > 0) { + int childPtr = 2*childs[nodePtr++]; + if (childs[childPtr + 1] == -1) { + /* We found another leaf */ + length[childs[childPtr]] = (byte) bits; + n--; + } + } + } + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("*** After overflow elimination. ***"); + // for (int i=0; i < numLeafs; i++) { + // //Console.WriteLine("Node "+childs[2*i]+" freq: "+freqs[childs[2*i]] + // + " len: "+length[childs[2*i]]); + // } + // } + } + + public void BuildTree() + { + int numSymbols = freqs.Length; + + /* heap is a priority queue, sorted by frequency, least frequent + * nodes first. The heap is a binary tree, with the property, that + * the parent node is smaller than both child nodes. This assures + * that the smallest node is the first parent. + * + * The binary tree is encoded in an array: 0 is root node and + * the nodes 2*n+1, 2*n+2 are the child nodes of node n. + */ + int[] heap = new int[numSymbols]; + int heapLen = 0; + int maxCode = 0; + for (int n = 0; n < numSymbols; n++) { + int freq = freqs[n]; + if (freq != 0) { + /* Insert n into heap */ + int pos = heapLen++; + int ppos; + while (pos > 0 && freqs[heap[ppos = (pos - 1) / 2]] > freq) { + heap[pos] = heap[ppos]; + pos = ppos; + } + heap[pos] = n; + + maxCode = n; + } + } + + /* We could encode a single literal with 0 bits but then we + * don't see the literals. Therefore we force at least two + * literals to avoid this case. We don't care about order in + * this case, both literals get a 1 bit code. + */ + while (heapLen < 2) { + int node = maxCode < 2 ? ++maxCode : 0; + heap[heapLen++] = node; + } + + numCodes = Math.Max(maxCode + 1, minNumCodes); + + int numLeafs = heapLen; + int[] childs = new int[4*heapLen - 2]; + int[] values = new int[2*heapLen - 1]; + int numNodes = numLeafs; + for (int i = 0; i < heapLen; i++) { + int node = heap[i]; + childs[2*i] = node; + childs[2*i+1] = -1; + values[i] = freqs[node] << 8; + heap[i] = i; + } + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + do { + int first = heap[0]; + int last = heap[--heapLen]; + + /* Propagate the hole to the leafs of the heap */ + int ppos = 0; + int path = 1; + + while (path < heapLen) { + if (path + 1 < heapLen && values[heap[path]] > values[heap[path+1]]) { + path++; + } + + heap[ppos] = heap[path]; + ppos = path; + path = path * 2 + 1; + } + + /* Now propagate the last element down along path. Normally + * it shouldn't go too deep. + */ + int lastVal = values[last]; + while ((path = ppos) > 0 && values[heap[ppos = (path - 1)/2]] > lastVal) { + heap[path] = heap[ppos]; + } + heap[path] = last; + + + int second = heap[0]; + + /* Create a new node father of first and second */ + last = numNodes++; + childs[2*last] = first; + childs[2*last+1] = second; + int mindepth = Math.Min(values[first] & 0xff, values[second] & 0xff); + values[last] = lastVal = values[first] + values[second] - mindepth + 1; + + /* Again, propagate the hole to the leafs */ + ppos = 0; + path = 1; + + while (path < heapLen) { + if (path + 1 < heapLen && values[heap[path]] > values[heap[path+1]]) { + path++; + } + + heap[ppos] = heap[path]; + ppos = path; + path = ppos * 2 + 1; + } + + /* Now propagate the new element down along path */ + while ((path = ppos) > 0 && values[heap[ppos = (path - 1)/2]] > lastVal) { + heap[path] = heap[ppos]; + } + heap[path] = last; + } while (heapLen > 1); + + if (heap[0] != childs.Length / 2 - 1) { + throw new Exception("Weird!"); + } + BuildLength(childs); + } + + public int GetEncodedLength() + { + int len = 0; + for (int i = 0; i < freqs.Length; i++) { + len += freqs[i] * length[i]; + } + return len; + } + + public void CalcBLFreq(Tree blTree) + { + int max_count; /* max repeat count */ + int min_count; /* min repeat count */ + int count; /* repeat count of the current code */ + int curlen = -1; /* length of current code */ + + int i = 0; + while (i < numCodes) { + count = 1; + int nextlen = length[i]; + if (nextlen == 0) { + max_count = 138; + min_count = 3; + } else { + max_count = 6; + min_count = 3; + if (curlen != nextlen) { + blTree.freqs[nextlen]++; + count = 0; + } + } + curlen = nextlen; + i++; + + while (i < numCodes && curlen == length[i]) { + i++; + if (++count >= max_count) { + break; + } + } + + if (count < min_count) { + blTree.freqs[curlen] += (short)count; + } else if (curlen != 0) { + blTree.freqs[REP_3_6]++; + } else if (count <= 10) { + blTree.freqs[REP_3_10]++; + } else { + blTree.freqs[REP_11_138]++; + } + } + } + + public void WriteTree(Tree blTree) + { + int max_count; /* max repeat count */ + int min_count; /* min repeat count */ + int count; /* repeat count of the current code */ + int curlen = -1; /* length of current code */ + + int i = 0; + while (i < numCodes) { + count = 1; + int nextlen = length[i]; + if (nextlen == 0) { + max_count = 138; + min_count = 3; + } else { + max_count = 6; + min_count = 3; + if (curlen != nextlen) { + blTree.WriteSymbol(nextlen); + count = 0; + } + } + curlen = nextlen; + i++; + + while (i < numCodes && curlen == length[i]) { + i++; + if (++count >= max_count) { + break; + } + } + + if (count < min_count) { + while (count-- > 0) { + blTree.WriteSymbol(curlen); + } + } else if (curlen != 0) { + blTree.WriteSymbol(REP_3_6); + dh.pending.WriteBits(count - 3, 2); + } else if (count <= 10) { + blTree.WriteSymbol(REP_3_10); + dh.pending.WriteBits(count - 3, 3); + } else { + blTree.WriteSymbol(REP_11_138); + dh.pending.WriteBits(count - 11, 7); + } + } + } + } + + public DeflaterPending pending; + private Tree literalTree, distTree, blTree; + + private short[] d_buf; + private byte[] l_buf; + private int last_lit; + private int extra_bits; + + private static short[] staticLCodes; + private static byte[] staticLLength; + private static short[] staticDCodes; + private static byte[] staticDLength; + + /// + /// Reverse the bits of a 16 bit value. + /// + public static short BitReverse(int value) + { + return (short) (bit4Reverse[value & 0xF] << 12 | + bit4Reverse[(value >> 4) & 0xF] << 8 | + bit4Reverse[(value >> 8) & 0xF] << 4 | + bit4Reverse[value >> 12]); + } + + + static DeflaterHuffman() + { + /* See RFC 1951 3.2.6 */ + /* Literal codes */ + staticLCodes = new short[LITERAL_NUM]; + staticLLength = new byte[LITERAL_NUM]; + int i = 0; + while (i < 144) { + staticLCodes[i] = BitReverse((0x030 + i) << 8); + staticLLength[i++] = 8; + } + while (i < 256) { + staticLCodes[i] = BitReverse((0x190 - 144 + i) << 7); + staticLLength[i++] = 9; + } + while (i < 280) { + staticLCodes[i] = BitReverse((0x000 - 256 + i) << 9); + staticLLength[i++] = 7; + } + while (i < LITERAL_NUM) { + staticLCodes[i] = BitReverse((0x0c0 - 280 + i) << 8); + staticLLength[i++] = 8; + } + + /* Distant codes */ + staticDCodes = new short[DIST_NUM]; + staticDLength = new byte[DIST_NUM]; + for (i = 0; i < DIST_NUM; i++) { + staticDCodes[i] = BitReverse(i << 11); + staticDLength[i] = 5; + } + } + + public DeflaterHuffman(DeflaterPending pending) + { + this.pending = pending; + + literalTree = new Tree(this, LITERAL_NUM, 257, 15); + distTree = new Tree(this, DIST_NUM, 1, 15); + blTree = new Tree(this, BITLEN_NUM, 4, 7); + + d_buf = new short[BUFSIZE]; + l_buf = new byte [BUFSIZE]; + } + + public void Reset() + { + last_lit = 0; + extra_bits = 0; + literalTree.Reset(); + distTree.Reset(); + blTree.Reset(); + } + + int Lcode(int len) + { + if (len == 255) { + return 285; + } + + int code = 257; + while (len >= 8) { + code += 4; + len >>= 1; + } + return code + len; + } + + int Dcode(int distance) + { + int code = 0; + while (distance >= 4) { + code += 2; + distance >>= 1; + } + return code + distance; + } + + public void SendAllTrees(int blTreeCodes) + { + blTree.BuildCodes(); + literalTree.BuildCodes(); + distTree.BuildCodes(); + pending.WriteBits(literalTree.numCodes - 257, 5); + pending.WriteBits(distTree.numCodes - 1, 5); + pending.WriteBits(blTreeCodes - 4, 4); + for (int rank = 0; rank < blTreeCodes; rank++) { + pending.WriteBits(blTree.length[BL_ORDER[rank]], 3); + } + literalTree.WriteTree(blTree); + distTree.WriteTree(blTree); + // if (DeflaterConstants.DEBUGGING) { + // blTree.CheckEmpty(); + // } + } + + public void CompressBlock() + { + for (int i = 0; i < last_lit; i++) { + int litlen = l_buf[i] & 0xff; + int dist = d_buf[i]; + if (dist-- != 0) { + // if (DeflaterConstants.DEBUGGING) { + // Console.Write("["+(dist+1)+","+(litlen+3)+"]: "); + // } + + int lc = Lcode(litlen); + literalTree.WriteSymbol(lc); + + int bits = (lc - 261) / 4; + if (bits > 0 && bits <= 5) { + pending.WriteBits(litlen & ((1 << bits) - 1), bits); + } + + int dc = Dcode(dist); + distTree.WriteSymbol(dc); + + bits = dc / 2 - 1; + if (bits > 0) { + pending.WriteBits(dist & ((1 << bits) - 1), bits); + } + } else { + // if (DeflaterConstants.DEBUGGING) { + // if (litlen > 32 && litlen < 127) { + // Console.Write("("+(char)litlen+"): "); + // } else { + // Console.Write("{"+litlen+"}: "); + // } + // } + literalTree.WriteSymbol(litlen); + } + } + // if (DeflaterConstants.DEBUGGING) { + // Console.Write("EOF: "); + // } + literalTree.WriteSymbol(EOF_SYMBOL); + // if (DeflaterConstants.DEBUGGING) { + // literalTree.CheckEmpty(); + // distTree.CheckEmpty(); + // } + } + + public void FlushStoredBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) + { + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("Flushing stored block "+ storedLength); + // } + pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3); + pending.AlignToByte(); + pending.WriteShort(storedLength); + pending.WriteShort(~storedLength); + pending.WriteBlock(stored, storedOffset, storedLength); + Reset(); + } + + public void FlushBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) + { + literalTree.freqs[EOF_SYMBOL]++; + + /* Build trees */ + literalTree.BuildTree(); + distTree.BuildTree(); + + /* Calculate bitlen frequency */ + literalTree.CalcBLFreq(blTree); + distTree.CalcBLFreq(blTree); + + /* Build bitlen tree */ + blTree.BuildTree(); + + int blTreeCodes = 4; + for (int i = 18; i > blTreeCodes; i--) { + if (blTree.length[BL_ORDER[i]] > 0) { + blTreeCodes = i+1; + } + } + int opt_len = 14 + blTreeCodes * 3 + blTree.GetEncodedLength() + + literalTree.GetEncodedLength() + distTree.GetEncodedLength() + + extra_bits; + + int static_len = extra_bits; + for (int i = 0; i < LITERAL_NUM; i++) { + static_len += literalTree.freqs[i] * staticLLength[i]; + } + for (int i = 0; i < DIST_NUM; i++) { + static_len += distTree.freqs[i] * staticDLength[i]; + } + if (opt_len >= static_len) { + /* Force static trees */ + opt_len = static_len; + } + + if (storedOffset >= 0 && storedLength+4 < opt_len >> 3) { + /* Store Block */ + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("Storing, since " + storedLength + " < " + opt_len + // + " <= " + static_len); + // } + FlushStoredBlock(stored, storedOffset, storedLength, lastBlock); + } else if (opt_len == static_len) { + /* Encode with static tree */ + pending.WriteBits((DeflaterConstants.STATIC_TREES << 1) + (lastBlock ? 1 : 0), 3); + literalTree.SetStaticCodes(staticLCodes, staticLLength); + distTree.SetStaticCodes(staticDCodes, staticDLength); + CompressBlock(); + Reset(); + } else { + /* Encode with dynamic tree */ + pending.WriteBits((DeflaterConstants.DYN_TREES << 1) + (lastBlock ? 1 : 0), 3); + SendAllTrees(blTreeCodes); + CompressBlock(); + Reset(); + } + } + + public bool IsFull() + { +// return last_lit + 16 >= BUFSIZE; // HACK: This was == 'last_lit == BUFSIZE', but errors occured with DeflateFast + return last_lit >= BUFSIZE; // -jr- This is the correct form! + } + + public bool TallyLit(int lit) + { + // if (DeflaterConstants.DEBUGGING) { + // if (lit > 32 && lit < 127) { + // //Console.WriteLine("("+(char)lit+")"); + // } else { + // //Console.WriteLine("{"+lit+"}"); + // } + // } + d_buf[last_lit] = 0; + l_buf[last_lit++] = (byte)lit; + literalTree.freqs[lit]++; + return IsFull(); + } + + public bool TallyDist(int dist, int len) + { + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("["+dist+","+len+"]"); + // } + + d_buf[last_lit] = (short)dist; + l_buf[last_lit++] = (byte)(len - 3); + + int lc = Lcode(len - 3); + literalTree.freqs[lc]++; + if (lc >= 265 && lc < 285) { + extra_bits += (lc - 261) / 4; + } + + int dc = Dcode(dist - 1); + distTree.freqs[dc]++; + if (dc >= 4) { + extra_bits += dc / 2 - 1; + } + return IsFull(); + } + } +} diff --git a/irc/TechBot/Compression/DeflaterPending.cs b/irc/TechBot/Compression/DeflaterPending.cs new file mode 100644 index 00000000000..8144ff35e61 --- /dev/null +++ b/irc/TechBot/Compression/DeflaterPending.cs @@ -0,0 +1,52 @@ +// DeflaterPending.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +namespace ICSharpCode.SharpZipLib.Zip.Compression +{ + + /// + /// This class stores the pending output of the Deflater. + /// + /// author of the original java version : Jochen Hoenicke + /// + public class DeflaterPending : PendingBuffer + { + public DeflaterPending() : base(DeflaterConstants.PENDING_BUF_SIZE) + { + } + } +} diff --git a/irc/TechBot/Compression/Inflater.cs b/irc/TechBot/Compression/Inflater.cs new file mode 100644 index 00000000000..b915c8ffe7d --- /dev/null +++ b/irc/TechBot/Compression/Inflater.cs @@ -0,0 +1,782 @@ +// Inflater.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +using ICSharpCode.SharpZipLib.Checksums; +using ICSharpCode.SharpZipLib.Zip.Compression.Streams; + +namespace ICSharpCode.SharpZipLib.Zip.Compression +{ + + /// + /// Inflater is used to decompress data that has been compressed according + /// to the "deflate" standard described in rfc1950. + /// + /// The usage is as following. First you have to set some input with + /// setInput(), then inflate() it. If inflate doesn't + /// inflate any bytes there may be three reasons: + ///
          + ///
        • needsInput() returns true because the input buffer is empty. + /// You have to provide more input with setInput(). + /// NOTE: needsInput() also returns true when, the stream is finished. + ///
        • + ///
        • needsDictionary() returns true, you have to provide a preset + /// dictionary with setDictionary().
        • + ///
        • finished() returns true, the inflater has finished.
        • + ///
        + /// Once the first output byte is produced, a dictionary will not be + /// needed at a later stage. + /// + /// author of the original java version : John Leuner, Jochen Hoenicke + ///
        + public class Inflater + { + /// + /// Copy lengths for literal codes 257..285 + /// + private static int[] CPLENS = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 + }; + + /// + /// Extra bits for literal codes 257..285 + /// + private static int[] CPLEXT = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 + }; + + /// + /// Copy offsets for distance codes 0..29 + /// + private static int[] CPDIST = { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577 + }; + + /// + /// Extra bits for distance codes + /// + private static int[] CPDEXT = { + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13 + }; + + /// + /// This are the state in which the inflater can be. + /// + private const int DECODE_HEADER = 0; + private const int DECODE_DICT = 1; + private const int DECODE_BLOCKS = 2; + private const int DECODE_STORED_LEN1 = 3; + private const int DECODE_STORED_LEN2 = 4; + private const int DECODE_STORED = 5; + private const int DECODE_DYN_HEADER = 6; + private const int DECODE_HUFFMAN = 7; + private const int DECODE_HUFFMAN_LENBITS = 8; + private const int DECODE_HUFFMAN_DIST = 9; + private const int DECODE_HUFFMAN_DISTBITS = 10; + private const int DECODE_CHKSUM = 11; + private const int FINISHED = 12; + + /// + /// This variable contains the current state. + /// + private int mode; + + /// + /// The adler checksum of the dictionary or of the decompressed + /// stream, as it is written in the header resp. footer of the + /// compressed stream. + /// Only valid if mode is DECODE_DICT or DECODE_CHKSUM. + /// + private int readAdler; + + /// + /// The number of bits needed to complete the current state. This + /// is valid, if mode is DECODE_DICT, DECODE_CHKSUM, + /// DECODE_HUFFMAN_LENBITS or DECODE_HUFFMAN_DISTBITS. + /// + private int neededBits; + private int repLength, repDist; + private int uncomprLen; + + /// + /// True, if the last block flag was set in the last block of the + /// inflated stream. This means that the stream ends after the + /// current block. + /// + private bool isLastBlock; + + /// + /// The total number of inflated bytes. + /// + private int totalOut; + + /// + /// The total number of bytes set with setInput(). This is not the + /// value returned by getTotalIn(), since this also includes the + /// unprocessed input. + /// + private int totalIn; + + /// + /// This variable stores the nowrap flag that was given to the constructor. + /// True means, that the inflated stream doesn't contain a header nor the + /// checksum in the footer. + /// + private bool nowrap; + + private StreamManipulator input; + private OutputWindow outputWindow; + private InflaterDynHeader dynHeader; + private InflaterHuffmanTree litlenTree, distTree; + private Adler32 adler; + + /// + /// Creates a new inflater. + /// + public Inflater() : this(false) + { + } + + /// + /// Creates a new inflater. + /// + /// + /// true if no header and checksum field appears in the + /// stream. This is used for GZIPed input. For compatibility with + /// Sun JDK you should provide one byte of input more than needed in + /// this case. + /// + public Inflater(bool nowrap) + { + this.nowrap = nowrap; + this.adler = new Adler32(); + input = new StreamManipulator(); + outputWindow = new OutputWindow(); + mode = nowrap ? DECODE_BLOCKS : DECODE_HEADER; + } + + /// + /// Resets the inflater so that a new stream can be decompressed. All + /// pending input and output will be discarded. + /// + public void Reset() + { + mode = nowrap ? DECODE_BLOCKS : DECODE_HEADER; + totalIn = totalOut = 0; + input.Reset(); + outputWindow.Reset(); + dynHeader = null; + litlenTree = null; + distTree = null; + isLastBlock = false; + adler.Reset(); + } + + /// + /// Decodes the deflate header. + /// + /// + /// false if more input is needed. + /// + /// + /// if header is invalid. + /// + private bool DecodeHeader() + { + int header = input.PeekBits(16); + if (header < 0) { + return false; + } + input.DropBits(16); + /* The header is written in "wrong" byte order */ + header = ((header << 8) | (header >> 8)) & 0xffff; + if (header % 31 != 0) { + throw new FormatException("Header checksum illegal"); + } + + if ((header & 0x0f00) != (Deflater.DEFLATED << 8)) { + throw new FormatException("Compression Method unknown"); + } + + /* Maximum size of the backwards window in bits. + * We currently ignore this, but we could use it to make the + * inflater window more space efficient. On the other hand the + * full window (15 bits) is needed most times, anyway. + int max_wbits = ((header & 0x7000) >> 12) + 8; + */ + + if ((header & 0x0020) == 0) { // Dictionary flag? + mode = DECODE_BLOCKS; + } else { + mode = DECODE_DICT; + neededBits = 32; + } + return true; + } + + /// + /// Decodes the dictionary checksum after the deflate header. + /// + /// + /// false if more input is needed. + /// + private bool DecodeDict() + { + while (neededBits > 0) { + int dictByte = input.PeekBits(8); + if (dictByte < 0) { + return false; + } + input.DropBits(8); + readAdler = (readAdler << 8) | dictByte; + neededBits -= 8; + } + return false; + } + + /// + /// Decodes the huffman encoded symbols in the input stream. + /// + /// + /// false if more input is needed, true if output window is + /// full or the current block ends. + /// + /// + /// if deflated stream is invalid. + /// + private bool DecodeHuffman() + { + int free = outputWindow.GetFreeSpace(); + while (free >= 258) { + int symbol; + switch (mode) { + case DECODE_HUFFMAN: + /* This is the inner loop so it is optimized a bit */ + while (((symbol = litlenTree.GetSymbol(input)) & ~0xff) == 0) { + outputWindow.Write(symbol); + if (--free < 258) { + return true; + } + } + if (symbol < 257) { + if (symbol < 0) { + return false; + } else { + /* symbol == 256: end of block */ + distTree = null; + litlenTree = null; + mode = DECODE_BLOCKS; + return true; + } + } + + try { + repLength = CPLENS[symbol - 257]; + neededBits = CPLEXT[symbol - 257]; + } catch (Exception) { + throw new FormatException("Illegal rep length code"); + } + goto case DECODE_HUFFMAN_LENBITS;/* fall through */ + case DECODE_HUFFMAN_LENBITS: + if (neededBits > 0) { + mode = DECODE_HUFFMAN_LENBITS; + int i = input.PeekBits(neededBits); + if (i < 0) { + return false; + } + input.DropBits(neededBits); + repLength += i; + } + mode = DECODE_HUFFMAN_DIST; + goto case DECODE_HUFFMAN_DIST;/* fall through */ + case DECODE_HUFFMAN_DIST: + symbol = distTree.GetSymbol(input); + if (symbol < 0) { + return false; + } + try { + repDist = CPDIST[symbol]; + neededBits = CPDEXT[symbol]; + } catch (Exception) { + throw new FormatException("Illegal rep dist code"); + } + + goto case DECODE_HUFFMAN_DISTBITS;/* fall through */ + case DECODE_HUFFMAN_DISTBITS: + if (neededBits > 0) { + mode = DECODE_HUFFMAN_DISTBITS; + int i = input.PeekBits(neededBits); + if (i < 0) { + return false; + } + input.DropBits(neededBits); + repDist += i; + } + outputWindow.Repeat(repLength, repDist); + free -= repLength; + mode = DECODE_HUFFMAN; + break; + default: + throw new FormatException(); + } + } + return true; + } + + /// + /// Decodes the adler checksum after the deflate stream. + /// + /// + /// false if more input is needed. + /// + /// + /// DataFormatException, if checksum doesn't match. + /// + private bool DecodeChksum() + { + while (neededBits > 0) { + int chkByte = input.PeekBits(8); + if (chkByte < 0) { + return false; + } + input.DropBits(8); + readAdler = (readAdler << 8) | chkByte; + neededBits -= 8; + } + if ((int) adler.Value != readAdler) { + throw new FormatException("Adler chksum doesn't match: " + (int)adler.Value + " vs. " + readAdler); + } + mode = FINISHED; + return false; + } + + /// + /// Decodes the deflated stream. + /// + /// + /// false if more input is needed, or if finished. + /// + /// + /// DataFormatException, if deflated stream is invalid. + /// + private bool Decode() + { + switch (mode) { + case DECODE_HEADER: + return DecodeHeader(); + case DECODE_DICT: + return DecodeDict(); + case DECODE_CHKSUM: + return DecodeChksum(); + + case DECODE_BLOCKS: + if (isLastBlock) { + if (nowrap) { + mode = FINISHED; + return false; + } else { + input.SkipToByteBoundary(); + neededBits = 32; + mode = DECODE_CHKSUM; + return true; + } + } + + int type = input.PeekBits(3); + if (type < 0) { + return false; + } + input.DropBits(3); + + if ((type & 1) != 0) { + isLastBlock = true; + } + switch (type >> 1){ + case DeflaterConstants.STORED_BLOCK: + input.SkipToByteBoundary(); + mode = DECODE_STORED_LEN1; + break; + case DeflaterConstants.STATIC_TREES: + litlenTree = InflaterHuffmanTree.defLitLenTree; + distTree = InflaterHuffmanTree.defDistTree; + mode = DECODE_HUFFMAN; + break; + case DeflaterConstants.DYN_TREES: + dynHeader = new InflaterDynHeader(); + mode = DECODE_DYN_HEADER; + break; + default: + throw new FormatException("Unknown block type "+type); + } + return true; + + case DECODE_STORED_LEN1: + { + if ((uncomprLen = input.PeekBits(16)) < 0) { + return false; + } + input.DropBits(16); + mode = DECODE_STORED_LEN2; + } + goto case DECODE_STORED_LEN2; /* fall through */ + case DECODE_STORED_LEN2: + { + int nlen = input.PeekBits(16); + if (nlen < 0) { + return false; + } + input.DropBits(16); + if (nlen != (uncomprLen ^ 0xffff)) { + throw new FormatException("broken uncompressed block"); + } + mode = DECODE_STORED; + } + goto case DECODE_STORED;/* fall through */ + case DECODE_STORED: + { + int more = outputWindow.CopyStored(input, uncomprLen); + uncomprLen -= more; + if (uncomprLen == 0) { + mode = DECODE_BLOCKS; + return true; + } + return !input.IsNeedingInput; + } + + case DECODE_DYN_HEADER: + if (!dynHeader.Decode(input)) { + return false; + } + + litlenTree = dynHeader.BuildLitLenTree(); + distTree = dynHeader.BuildDistTree(); + mode = DECODE_HUFFMAN; + goto case DECODE_HUFFMAN; /* fall through */ + case DECODE_HUFFMAN: + case DECODE_HUFFMAN_LENBITS: + case DECODE_HUFFMAN_DIST: + case DECODE_HUFFMAN_DISTBITS: + return DecodeHuffman(); + case FINISHED: + return false; + default: + throw new FormatException(); + } + } + + /// + /// Sets the preset dictionary. This should only be called, if + /// needsDictionary() returns true and it should set the same + /// dictionary, that was used for deflating. The getAdler() + /// function returns the checksum of the dictionary needed. + /// + /// + /// the dictionary. + /// + /// + /// if no dictionary is needed. + /// + /// + /// if the dictionary checksum is wrong. + /// + public void SetDictionary(byte[] buffer) + { + SetDictionary(buffer, 0, buffer.Length); + } + + /// + /// Sets the preset dictionary. This should only be called, if + /// needsDictionary() returns true and it should set the same + /// dictionary, that was used for deflating. The getAdler() + /// function returns the checksum of the dictionary needed. + /// + /// + /// the dictionary. + /// + /// + /// the offset into buffer where the dictionary starts. + /// + /// + /// the length of the dictionary. + /// + /// + /// if no dictionary is needed. + /// + /// + /// if the dictionary checksum is wrong. + /// + /// + /// if the off and/or len are wrong. + /// + public void SetDictionary(byte[] buffer, int off, int len) + { + if (!IsNeedingDictionary) { + throw new InvalidOperationException(); + } + + adler.Update(buffer, off, len); + if ((int)adler.Value != readAdler) { + throw new ArgumentException("Wrong adler checksum"); + } + adler.Reset(); + outputWindow.CopyDict(buffer, off, len); + mode = DECODE_BLOCKS; + } + + /// + /// Sets the input. This should only be called, if needsInput() + /// returns true. + /// + /// + /// the input. + /// + /// + /// if no input is needed. + /// + public void SetInput(byte[] buf) + { + SetInput(buf, 0, buf.Length); + } + + /// + /// Sets the input. This should only be called, if needsInput() + /// returns true. + /// + /// + /// the input. + /// + /// + /// the offset into buffer where the input starts. + /// + /// + /// the length of the input. + /// + /// + /// if no input is needed. + /// + /// + /// if the off and/or len are wrong. + /// + public void SetInput(byte[] buf, int off, int len) + { + input.SetInput(buf, off, len); + totalIn += len; + } + + /// + /// Inflates the compressed stream to the output buffer. If this + /// returns 0, you should check, whether needsDictionary(), + /// needsInput() or finished() returns true, to determine why no + /// further output is produced. + /// + /// + /// the output buffer. + /// + /// + /// the number of bytes written to the buffer, 0 if no further + /// output can be produced. + /// + /// + /// if buf has length 0. + /// + /// + /// if deflated stream is invalid. + /// + public int Inflate(byte[] buf) + { + return Inflate(buf, 0, buf.Length); + } + + /// + /// Inflates the compressed stream to the output buffer. If this + /// returns 0, you should check, whether needsDictionary(), + /// needsInput() or finished() returns true, to determine why no + /// further output is produced. + /// + /// + /// the output buffer. + /// + /// + /// the offset into buffer where the output should start. + /// + /// + /// the maximum length of the output. + /// + /// + /// the number of bytes written to the buffer, 0 if no further output can be produced. + /// + /// + /// if len is <= 0. + /// + /// + /// if the off and/or len are wrong. + /// + /// + /// if deflated stream is invalid. + /// + public int Inflate(byte[] buf, int off, int len) + { + if (len < 0) { + throw new ArgumentOutOfRangeException("len < 0"); + } + // Special case: len may be zero + if (len == 0) { + if (IsFinished == false) {// -jr- 08-Nov-2003 INFLATE_BUG fix.. + Decode(); + } + return 0; + } + /* // Check for correct buff, off, len triple + if (off < 0 || off + len >= buf.Length) { + throw new ArgumentException("off/len outside buf bounds"); + }*/ + int count = 0; + int more; + do { + if (mode != DECODE_CHKSUM) { + /* Don't give away any output, if we are waiting for the + * checksum in the input stream. + * + * With this trick we have always: + * needsInput() and not finished() + * implies more output can be produced. + */ + more = outputWindow.CopyOutput(buf, off, len); + adler.Update(buf, off, more); + off += more; + count += more; + totalOut += more; + len -= more; + if (len == 0) { + return count; + } + } + } while (Decode() || (outputWindow.GetAvailable() > 0 && mode != DECODE_CHKSUM)); + return count; + } + + /// + /// Returns true, if the input buffer is empty. + /// You should then call setInput(). + /// NOTE: This method also returns true when the stream is finished. + /// + public bool IsNeedingInput { + get { + return input.IsNeedingInput; + } + } + + /// + /// Returns true, if a preset dictionary is needed to inflate the input. + /// + public bool IsNeedingDictionary { + get { + return mode == DECODE_DICT && neededBits == 0; + } + } + + /// + /// Returns true, if the inflater has finished. This means, that no + /// input is needed and no output can be produced. + /// + public bool IsFinished { + get { + return mode == FINISHED && outputWindow.GetAvailable() == 0; + } + } + + /// + /// Gets the adler checksum. This is either the checksum of all + /// uncompressed bytes returned by inflate(), or if needsDictionary() + /// returns true (and thus no output was yet produced) this is the + /// adler checksum of the expected dictionary. + /// + /// + /// the adler checksum. + /// + public int Adler { + get { + return IsNeedingDictionary ? readAdler : (int) adler.Value; + } + } + + /// + /// Gets the total number of output bytes returned by inflate(). + /// + /// + /// the total number of output bytes. + /// + public int TotalOut { + get { + return totalOut; + } + } + + /// + /// Gets the total number of processed compressed input bytes. + /// + /// + /// the total number of bytes of processed input bytes. + /// + public int TotalIn { + get { + return totalIn - RemainingInput; + } + } + + /// + /// Gets the number of unprocessed input. Useful, if the end of the + /// stream is reached and you want to further process the bytes after + /// the deflate stream. + /// + /// + /// the number of bytes of the input which were not processed. + /// + public int RemainingInput { + get { + return input.AvailableBytes; + } + } + } +} diff --git a/irc/TechBot/Compression/InflaterDynHeader.cs b/irc/TechBot/Compression/InflaterDynHeader.cs new file mode 100644 index 00000000000..653c7847cea --- /dev/null +++ b/irc/TechBot/Compression/InflaterDynHeader.cs @@ -0,0 +1,207 @@ +// InflaterDynHeader.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +using ICSharpCode.SharpZipLib.Zip.Compression.Streams; + +namespace ICSharpCode.SharpZipLib.Zip.Compression +{ + + class InflaterDynHeader + { + const int LNUM = 0; + const int DNUM = 1; + const int BLNUM = 2; + const int BLLENS = 3; + const int LENS = 4; + const int REPS = 5; + + static readonly int[] repMin = { 3, 3, 11 }; + static readonly int[] repBits = { 2, 3, 7 }; + + byte[] blLens; + byte[] litdistLens; + + InflaterHuffmanTree blTree; + + int mode; + int lnum, dnum, blnum, num; + int repSymbol; + byte lastLen; + int ptr; + + static readonly int[] BL_ORDER = + { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + + public InflaterDynHeader() + { + } + + public bool Decode(StreamManipulator input) + { + decode_loop: + for (;;) { + switch (mode) { + case LNUM: + lnum = input.PeekBits(5); + if (lnum < 0) { + return false; + } + lnum += 257; + input.DropBits(5); + // System.err.println("LNUM: "+lnum); + mode = DNUM; + goto case DNUM; // fall through + case DNUM: + dnum = input.PeekBits(5); + if (dnum < 0) { + return false; + } + dnum++; + input.DropBits(5); + // System.err.println("DNUM: "+dnum); + num = lnum+dnum; + litdistLens = new byte[num]; + mode = BLNUM; + goto case BLNUM; // fall through + case BLNUM: + blnum = input.PeekBits(4); + if (blnum < 0) { + return false; + } + blnum += 4; + input.DropBits(4); + blLens = new byte[19]; + ptr = 0; + // System.err.println("BLNUM: "+blnum); + mode = BLLENS; + goto case BLLENS; // fall through + case BLLENS: + while (ptr < blnum) { + int len = input.PeekBits(3); + if (len < 0) { + return false; + } + input.DropBits(3); + // System.err.println("blLens["+BL_ORDER[ptr]+"]: "+len); + blLens[BL_ORDER[ptr]] = (byte) len; + ptr++; + } + blTree = new InflaterHuffmanTree(blLens); + blLens = null; + ptr = 0; + mode = LENS; + goto case LENS; // fall through + case LENS: + { + int symbol; + while (((symbol = blTree.GetSymbol(input)) & ~15) == 0) { + /* Normal case: symbol in [0..15] */ + + // System.err.println("litdistLens["+ptr+"]: "+symbol); + litdistLens[ptr++] = lastLen = (byte)symbol; + + if (ptr == num) { + /* Finished */ + return true; + } + } + + /* need more input ? */ + if (symbol < 0) { + return false; + } + + /* otherwise repeat code */ + if (symbol >= 17) { + /* repeat zero */ + // System.err.println("repeating zero"); + lastLen = 0; + } else { + if (ptr == 0) { + throw new Exception(); + } + } + repSymbol = symbol-16; + } + mode = REPS; + goto case REPS; // fall through + case REPS: + { + int bits = repBits[repSymbol]; + int count = input.PeekBits(bits); + if (count < 0) { + return false; + } + input.DropBits(bits); + count += repMin[repSymbol]; + // System.err.println("litdistLens repeated: "+count); + + if (ptr + count > num) { + throw new Exception(); + } + while (count-- > 0) { + litdistLens[ptr++] = lastLen; + } + + if (ptr == num) { + /* Finished */ + return true; + } + } + mode = LENS; + goto decode_loop; + } + } + } + + public InflaterHuffmanTree BuildLitLenTree() + { + byte[] litlenLens = new byte[lnum]; + Array.Copy(litdistLens, 0, litlenLens, 0, lnum); + return new InflaterHuffmanTree(litlenLens); + } + + public InflaterHuffmanTree BuildDistTree() + { + byte[] distLens = new byte[dnum]; + Array.Copy(litdistLens, lnum, distLens, 0, dnum); + return new InflaterHuffmanTree(distLens); + } + } +} diff --git a/irc/TechBot/Compression/InflaterHuffmanTree.cs b/irc/TechBot/Compression/InflaterHuffmanTree.cs new file mode 100644 index 00000000000..02beb60a34c --- /dev/null +++ b/irc/TechBot/Compression/InflaterHuffmanTree.cs @@ -0,0 +1,213 @@ +// InflaterHuffmanTree.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +using ICSharpCode.SharpZipLib.Zip.Compression.Streams; + +namespace ICSharpCode.SharpZipLib.Zip.Compression +{ + + public class InflaterHuffmanTree + { + private static int MAX_BITLEN = 15; + private short[] tree; + + public static InflaterHuffmanTree defLitLenTree, defDistTree; + + static InflaterHuffmanTree() + { + try { + byte[] codeLengths = new byte[288]; + int i = 0; + while (i < 144) { + codeLengths[i++] = 8; + } + while (i < 256) { + codeLengths[i++] = 9; + } + while (i < 280) { + codeLengths[i++] = 7; + } + while (i < 288) { + codeLengths[i++] = 8; + } + defLitLenTree = new InflaterHuffmanTree(codeLengths); + + codeLengths = new byte[32]; + i = 0; + while (i < 32) { + codeLengths[i++] = 5; + } + defDistTree = new InflaterHuffmanTree(codeLengths); + } catch (Exception) { + throw new ApplicationException("InflaterHuffmanTree: static tree length illegal"); + } + } + + /// + /// Constructs a Huffman tree from the array of code lengths. + /// + /// + /// the array of code lengths + /// + public InflaterHuffmanTree(byte[] codeLengths) + { + BuildTree(codeLengths); + } + + private void BuildTree(byte[] codeLengths) + { + int[] blCount = new int[MAX_BITLEN + 1]; + int[] nextCode = new int[MAX_BITLEN + 1]; + + for (int i = 0; i < codeLengths.Length; i++) { + int bits = codeLengths[i]; + if (bits > 0) { + blCount[bits]++; + } + } + + int code = 0; + int treeSize = 512; + for (int bits = 1; bits <= MAX_BITLEN; bits++) { + nextCode[bits] = code; + code += blCount[bits] << (16 - bits); + if (bits >= 10) { + /* We need an extra table for bit lengths >= 10. */ + int start = nextCode[bits] & 0x1ff80; + int end = code & 0x1ff80; + treeSize += (end - start) >> (16 - bits); + } + } +/* -jr comment this out! doesnt work for dynamic trees and pkzip 2.04g + if (code != 65536) + { + throw new Exception("Code lengths don't add up properly."); + } +*/ + /* Now create and fill the extra tables from longest to shortest + * bit len. This way the sub trees will be aligned. + */ + tree = new short[treeSize]; + int treePtr = 512; + for (int bits = MAX_BITLEN; bits >= 10; bits--) { + int end = code & 0x1ff80; + code -= blCount[bits] << (16 - bits); + int start = code & 0x1ff80; + for (int i = start; i < end; i += 1 << 7) { + tree[DeflaterHuffman.BitReverse(i)] = (short) ((-treePtr << 4) | bits); + treePtr += 1 << (bits-9); + } + } + + for (int i = 0; i < codeLengths.Length; i++) { + int bits = codeLengths[i]; + if (bits == 0) { + continue; + } + code = nextCode[bits]; + int revcode = DeflaterHuffman.BitReverse(code); + if (bits <= 9) { + do { + tree[revcode] = (short) ((i << 4) | bits); + revcode += 1 << bits; + } while (revcode < 512); + } else { + int subTree = tree[revcode & 511]; + int treeLen = 1 << (subTree & 15); + subTree = -(subTree >> 4); + do { + tree[subTree | (revcode >> 9)] = (short) ((i << 4) | bits); + revcode += 1 << bits; + } while (revcode < treeLen); + } + nextCode[bits] = code + (1 << (16 - bits)); + } + + } + + /// + /// Reads the next symbol from input. The symbol is encoded using the + /// huffman tree. + /// + /// + /// input the input source. + /// + /// + /// the next symbol, or -1 if not enough input is available. + /// + public int GetSymbol(StreamManipulator input) + { + int lookahead, symbol; + if ((lookahead = input.PeekBits(9)) >= 0) { + if ((symbol = tree[lookahead]) >= 0) { + input.DropBits(symbol & 15); + return symbol >> 4; + } + int subtree = -(symbol >> 4); + int bitlen = symbol & 15; + if ((lookahead = input.PeekBits(bitlen)) >= 0) { + symbol = tree[subtree | (lookahead >> 9)]; + input.DropBits(symbol & 15); + return symbol >> 4; + } else { + int bits = input.AvailableBits; + lookahead = input.PeekBits(bits); + symbol = tree[subtree | (lookahead >> 9)]; + if ((symbol & 15) <= bits) { + input.DropBits(symbol & 15); + return symbol >> 4; + } else { + return -1; + } + } + } else { + int bits = input.AvailableBits; + lookahead = input.PeekBits(bits); + symbol = tree[lookahead]; + if (symbol >= 0 && (symbol & 15) <= bits) { + input.DropBits(symbol & 15); + return symbol >> 4; + } else { + return -1; + } + } + } + } +} + diff --git a/irc/TechBot/Compression/PendingBuffer.cs b/irc/TechBot/Compression/PendingBuffer.cs new file mode 100644 index 00000000000..e0bc96b9192 --- /dev/null +++ b/irc/TechBot/Compression/PendingBuffer.cs @@ -0,0 +1,210 @@ +// PendingBuffer.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +namespace ICSharpCode.SharpZipLib.Zip.Compression +{ + + /// + /// This class is general purpose class for writing data to a buffer. + /// + /// It allows you to write bits as well as bytes + /// Based on DeflaterPending.java + /// + /// author of the original java version : Jochen Hoenicke + /// + public class PendingBuffer + { + protected byte[] buf; + int start; + int end; + + uint bits; + int bitCount; + + public PendingBuffer() : this( 4096 ) + { + + } + + public PendingBuffer(int bufsize) + { + buf = new byte[bufsize]; + } + + public void Reset() + { + start = end = bitCount = 0; + } + + public void WriteByte(int b) + { + if (DeflaterConstants.DEBUGGING && start != 0) { + throw new Exception(); + } + buf[end++] = (byte) b; + } + + public void WriteShort(int s) + { + if (DeflaterConstants.DEBUGGING && start != 0) { + throw new Exception(); + } + buf[end++] = (byte) s; + buf[end++] = (byte) (s >> 8); + } + + public void WriteInt(int s) + { + if (DeflaterConstants.DEBUGGING && start != 0) { + throw new Exception(); + } + buf[end++] = (byte) s; + buf[end++] = (byte) (s >> 8); + buf[end++] = (byte) (s >> 16); + buf[end++] = (byte) (s >> 24); + } + + public void WriteBlock(byte[] block, int offset, int len) + { + if (DeflaterConstants.DEBUGGING && start != 0) { + throw new Exception(); + } + System.Array.Copy(block, offset, buf, end, len); + end += len; + } + + public int BitCount { + get { + return bitCount; + } + } + + public void AlignToByte() + { + if (DeflaterConstants.DEBUGGING && start != 0) { + throw new Exception(); + } + if (bitCount > 0) { + buf[end++] = (byte) bits; + if (bitCount > 8) { + buf[end++] = (byte) (bits >> 8); + } + } + bits = 0; + bitCount = 0; + } + + public void WriteBits(int b, int count) + { + if (DeflaterConstants.DEBUGGING && start != 0) { + throw new Exception(); + } + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("writeBits("+b+","+count+")"); + // } + bits |= (uint)(b << bitCount); + bitCount += count; + if (bitCount >= 16) { + buf[end++] = (byte) bits; + buf[end++] = (byte) (bits >> 8); + bits >>= 16; + bitCount -= 16; + } + } + + public void WriteShortMSB(int s) + { + if (DeflaterConstants.DEBUGGING && start != 0) { + throw new Exception(); + } + buf[end++] = (byte) (s >> 8); + buf[end++] = (byte) s; + } + + public bool IsFlushed { + get { + return end == 0; + } + } + + /// + /// Flushes the pending buffer into the given output array. If the + /// output array is to small, only a partial flush is done. + /// + /// + /// the output array; + /// + /// + /// the offset into output array; + /// + /// + /// length the maximum number of bytes to store; + /// + /// + /// IndexOutOfBoundsException if offset or length are invalid. + /// + public int Flush(byte[] output, int offset, int length) + { + if (bitCount >= 8) { + buf[end++] = (byte) bits; + bits >>= 8; + bitCount -= 8; + } + if (length > end - start) { + length = end - start; + System.Array.Copy(buf, start, output, offset, length); + start = 0; + end = 0; + } else { + System.Array.Copy(buf, start, output, offset, length); + start += length; + } + return length; + } + + public byte[] ToByteArray() + { + byte[] ret = new byte[end - start]; + System.Array.Copy(buf, start, ret, 0, ret.Length); + start = 0; + end = 0; + return ret; + } + } +} diff --git a/irc/TechBot/Compression/Streams/DeflaterOutputStream.cs b/irc/TechBot/Compression/Streams/DeflaterOutputStream.cs new file mode 100644 index 00000000000..dd8c658e843 --- /dev/null +++ b/irc/TechBot/Compression/Streams/DeflaterOutputStream.cs @@ -0,0 +1,379 @@ +// DeflaterOutputStream.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; +using System.IO; +using ICSharpCode.SharpZipLib.Checksums; +using ICSharpCode.SharpZipLib.Zip.Compression; + +namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams +{ + + /// + /// This is a special FilterOutputStream deflating the bytes that are + /// written through it. It uses the Deflater for deflating. + /// + /// authors of the original java version : Tom Tromey, Jochen Hoenicke + /// + public class DeflaterOutputStream : Stream + { + /// + /// This buffer is used temporarily to retrieve the bytes from the + /// deflater and write them to the underlying output stream. + /// + protected byte[] buf; + + /// + /// The deflater which is used to deflate the stream. + /// + protected Deflater def; + + /// + /// base stream the deflater depends on. + /// + protected Stream baseOutputStream; + + /// + /// I needed to implement the abstract member. + /// + public override bool CanRead { + get { + return baseOutputStream.CanRead; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override bool CanSeek { + get { + return false; +// return baseOutputStream.CanSeek; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override bool CanWrite { + get { + return baseOutputStream.CanWrite; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override long Length { + get { + return baseOutputStream.Length; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override long Position { + get { + return baseOutputStream.Position; + } + set { + baseOutputStream.Position = value; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("Seek not supported"); // -jr- 01-Dec-2003 +// return baseOutputStream.Seek(offset, origin); + } + + /// + /// I needed to implement the abstract member. + /// + public override void SetLength(long val) + { + baseOutputStream.SetLength(val); + } + + /// + /// I needed to implement the abstract member. + /// + public override int ReadByte() + { + return baseOutputStream.ReadByte(); + } + + /// + /// I needed to implement the abstract member. + /// + public override int Read(byte[] b, int off, int len) + { + return baseOutputStream.Read(b, off, len); + } + // -jr- 01-Dec-2003 + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + throw new NotSupportedException("Asynch read not currently supported"); + } + + // -jr- 01-Dec-2003 + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + throw new NotSupportedException("Asynch write not currently supported"); + } + + /// + /// Deflates everything in the def's input buffers. This will call + /// def.deflate() until all bytes from the input buffers + /// are processed. + /// + protected void Deflate() + { + while (!def.IsNeedingInput) { + int len = def.Deflate(buf, 0, buf.Length); + + // System.err.println("DOS deflated " + len + " baseOutputStream of " + buf.length); + if (len <= 0) { + break; + } + baseOutputStream.Write(buf, 0, len); + } + + if (!def.IsNeedingInput) { + throw new ApplicationException("Can't deflate all input?"); + } + } + + /// + /// Creates a new DeflaterOutputStream with a default Deflater and default buffer size. + /// + /// + /// the output stream where deflated output should be written. + /// + public DeflaterOutputStream(Stream baseOutputStream) : this(baseOutputStream, new Deflater(), 512) + { + } + + /// + /// Creates a new DeflaterOutputStream with the given Deflater and + /// default buffer size. + /// + /// + /// the output stream where deflated output should be written. + /// + /// + /// the underlying deflater. + /// + public DeflaterOutputStream(Stream baseOutputStream, Deflater defl) :this(baseOutputStream, defl, 512) + { + } + + /// + /// Creates a new DeflaterOutputStream with the given Deflater and + /// buffer size. + /// + /// + /// the output stream where deflated output should be written. + /// + /// + /// the underlying deflater. + /// + /// + /// the buffer size. + /// + /// + /// if bufsize isn't positive. + /// + public DeflaterOutputStream(Stream baseOutputStream, Deflater defl, int bufsize) + { + this.baseOutputStream = baseOutputStream; + if (bufsize <= 0) { + throw new InvalidOperationException("bufsize <= 0"); + } + buf = new byte[bufsize]; + def = defl; + } + + /// + /// Flushes the stream by calling flush() on the deflater and then + /// on the underlying stream. This ensures that all bytes are + /// flushed. + /// + public override void Flush() + { + def.Flush(); + Deflate(); + baseOutputStream.Flush(); + } + + /// + /// Finishes the stream by calling finish() on the deflater. + /// + public virtual void Finish() + { + def.Finish(); + while (!def.IsFinished) { + int len = def.Deflate(buf, 0, buf.Length); + if (len <= 0) { + break; + } + + // kidnthrain encryption alteration + if (this.Password != null) { + // plain data has been deflated. Now encrypt result + this.EncryptBlock(buf, 0, len); + } + + baseOutputStream.Write(buf, 0, len); + } + if (!def.IsFinished) { + throw new ApplicationException("Can't deflate all input?"); + } + baseOutputStream.Flush(); + } + + /// + /// Calls finish () and closes the stream. + /// + public override void Close() + { + Finish(); + baseOutputStream.Close(); + } + + /// + /// Writes a single byte to the compressed output stream. + /// + /// + /// the byte value. + /// + public override void WriteByte(byte bval) + { + byte[] b = new byte[1]; + b[0] = (byte) bval; + Write(b, 0, 1); + } + + /// + /// Writes a len bytes from an array to the compressed stream. + /// + /// + /// the byte array. + /// + /// + /// the offset into the byte array where to start. + /// + /// + /// the number of bytes to write. + /// + public override void Write(byte[] buf, int off, int len) + { + // System.err.println("DOS with off " + off + " and len " + len); + def.SetInput(buf, off, len); + Deflate(); + } + + #region Encryption + string password = null; + uint[] keys = null; + + public string Password { + get { + return password; + } + set { + password = value; + } + } + + + //The beauty of xor-ing bits is that + //plain ^ key = enc + //and enc ^ key = plain + //accordingly, this is the exact same as the decrypt byte + //function in InflaterInputStream + protected byte EncryptByte() + { + uint temp = ((keys[2] & 0xFFFF) | 2); + return (byte)((temp * (temp ^ 1)) >> 8); + } + + + /// + /// Takes a buffer of data and uses the keys + /// that have been previously initialized from a + /// password and then updated via a random encryption header + /// to encrypt that data + /// + protected void EncryptBlock(byte[] buf, int off, int len) + { + for (int i = off; i < off + len; ++i) { + byte oldbyte = buf[i]; + buf[i] ^= EncryptByte(); + UpdateKeys(oldbyte); + } + } + + /// + /// Initializes our encryption keys using a given password + /// + protected void InitializePassword(string password) { + keys = new uint[] { + 0x12345678, + 0x23456789, + 0x34567890 + }; + + for (int i = 0; i < password.Length; ++i) { + UpdateKeys((byte)password[i]); + } + } + + protected void UpdateKeys(byte ch) + { + keys[0] = Crc32.ComputeCrc32(keys[0], ch); + keys[1] = keys[1] + (byte)keys[0]; + keys[1] = keys[1] * 134775813 + 1; + keys[2] = Crc32.ComputeCrc32(keys[2], (byte)(keys[1] >> 24)); + } + #endregion + } +} diff --git a/irc/TechBot/Compression/Streams/InflaterInputStream.cs b/irc/TechBot/Compression/Streams/InflaterInputStream.cs new file mode 100644 index 00000000000..9317e5e185d --- /dev/null +++ b/irc/TechBot/Compression/Streams/InflaterInputStream.cs @@ -0,0 +1,386 @@ +// InflaterInputStream.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; +using System.IO; + +using ICSharpCode.SharpZipLib.Zip.Compression; +using ICSharpCode.SharpZipLib.Checksums; + +namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams +{ + + /// + /// This filter stream is used to decompress data compressed baseInputStream the "deflate" + /// format. The "deflate" format is described baseInputStream RFC 1951. + /// + /// This stream may form the basis for other decompression filters, such + /// as the GzipInputStream. + /// + /// author of the original java version : John Leuner + /// + public class InflaterInputStream : Stream + { + //Variables + + /// + /// Decompressor for this filter + /// + protected Inflater inf; + + /// + /// Byte array used as a buffer + /// + protected byte[] buf; + + /// + /// Size of buffer + /// + protected int len; + + //We just use this if we are decoding one byte at a time with the read() call + private byte[] onebytebuffer = new byte[1]; + + /// + /// base stream the inflater depends on. + /// + protected Stream baseInputStream; + + protected long csize; + + /// + /// I needed to implement the abstract member. + /// + public override bool CanRead { + get { + return baseInputStream.CanRead; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override bool CanSeek { + get { + return false; + // return baseInputStream.CanSeek; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override bool CanWrite { + get { + return baseInputStream.CanWrite; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override long Length { + get { + return len; + } + } + + /// + /// I needed to implement the abstract member. + /// + public override long Position { + get { + return baseInputStream.Position; + } + set { + baseInputStream.Position = value; + } + } + + /// + /// Flushes the baseInputStream + /// + public override void Flush() + { + baseInputStream.Flush(); + } + + /// + /// I needed to implement the abstract member. + /// + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("Seek not supported"); // -jr- 01-Dec-2003 + } + + /// + /// I needed to implement the abstract member. + /// + public override void SetLength(long val) + { + baseInputStream.SetLength(val); + } + + /// + /// I needed to implement the abstract member. + /// + public override void Write(byte[] array, int offset, int count) + { + baseInputStream.Write(array, offset, count); + } + + /// + /// I needed to implement the abstract member. + /// + public override void WriteByte(byte val) + { + baseInputStream.WriteByte(val); + } + + // -jr- 01-Dec-2003 This may be flawed for some base streams? Depends on implementation of BeginWrite + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + throw new NotSupportedException("Asynch write not currently supported"); + } + + //Constructors + + /// + /// Create an InflaterInputStream with the default decompresseor + /// and a default buffer size. + /// + /// + /// the InputStream to read bytes from + /// + public InflaterInputStream(Stream baseInputStream) : this(baseInputStream, new Inflater(), 4096) + { + + } + + /// + /// Create an InflaterInputStream with the specified decompresseor + /// and a default buffer size. + /// + /// + /// the InputStream to read bytes from + /// + /// + /// the decompressor used to decompress data read from baseInputStream + /// + public InflaterInputStream(Stream baseInputStream, Inflater inf) : this(baseInputStream, inf, 4096) + { + } + + /// + /// Create an InflaterInputStream with the specified decompresseor + /// and a specified buffer size. + /// + /// + /// the InputStream to read bytes from + /// + /// + /// the decompressor used to decompress data read from baseInputStream + /// + /// + /// size of the buffer to use + /// + public InflaterInputStream(Stream baseInputStream, Inflater inf, int size) + { + this.baseInputStream = baseInputStream; + this.inf = inf; + try { + this.len = (int)baseInputStream.Length; + } catch (Exception) { + // the stream may not support .Length + this.len = 0; + } + + if (size <= 0) { + throw new ArgumentOutOfRangeException("size <= 0"); + } + + buf = new byte[size]; //Create the buffer + } + + //Methods + + /// + /// Returns 0 once the end of the stream (EOF) has been reached. + /// Otherwise returns 1. + /// + public virtual int Available { + get { + return inf.IsFinished ? 0 : 1; + } + } + + /// + /// Closes the input stream + /// + public override void Close() + { + baseInputStream.Close(); + } + + /// + /// Fills the buffer with more data to decompress. + /// + protected void Fill() + { + len = baseInputStream.Read(buf, 0, buf.Length); + // decrypting crypted data + if (cryptbuffer != null) { + DecryptBlock(buf, 0, System.Math.Min((int)(csize - inf.TotalIn), buf.Length)); + } + + if (len <= 0) { + throw new ApplicationException("Deflated stream ends early."); + } + inf.SetInput(buf, 0, len); + } + + /// + /// Reads one byte of decompressed data. + /// + /// The byte is baseInputStream the lower 8 bits of the int. + /// + public override int ReadByte() + { + int nread = Read(onebytebuffer, 0, 1); //read one byte + if (nread > 0) { + return onebytebuffer[0] & 0xff; + } + return -1; // ok + } + + /// + /// Decompresses data into the byte array + /// + /// + /// the array to read and decompress data into + /// + /// + /// the offset indicating where the data should be placed + /// + /// + /// the number of bytes to decompress + /// + public override int Read(byte[] b, int off, int len) + { + for (;;) { + int count; + try { + count = inf.Inflate(b, off, len); + } catch (Exception e) { + throw new ZipException(e.ToString()); + } + + if (count > 0) { + return count; + } + + if (inf.IsNeedingDictionary) { + throw new ZipException("Need a dictionary"); + } else if (inf.IsFinished) { + return 0; + } else if (inf.IsNeedingInput) { + Fill(); + } else { + throw new InvalidOperationException("Don't know what to do"); + } + } + } + + /// + /// Skip specified number of bytes of uncompressed data + /// + /// + /// number of bytes to skip + /// + public long Skip(long n) + { + if (n < 0) { + throw new ArgumentOutOfRangeException("n"); + } + int len = 2048; + if (n < len) { + len = (int) n; + } + byte[] tmp = new byte[len]; + return (long)baseInputStream.Read(tmp, 0, tmp.Length); + } + + #region Encryption stuff + protected byte[] cryptbuffer = null; + + uint[] keys = null; + protected byte DecryptByte() + { + uint temp = ((keys[2] & 0xFFFF) | 2); + return (byte)((temp * (temp ^ 1)) >> 8); + } + + protected void DecryptBlock(byte[] buf, int off, int len) + { + for (int i = off; i < off + len; ++i) { + buf[i] ^= DecryptByte(); + UpdateKeys(buf[i]); + } + } + + protected void InitializePassword(string password) + { + keys = new uint[] { + 0x12345678, + 0x23456789, + 0x34567890 + }; + for (int i = 0; i < password.Length; ++i) { + UpdateKeys((byte)password[i]); + } + } + + protected void UpdateKeys(byte ch) + { + keys[0] = Crc32.ComputeCrc32(keys[0], ch); + keys[1] = keys[1] + (byte)keys[0]; + keys[1] = keys[1] * 134775813 + 1; + keys[2] = Crc32.ComputeCrc32(keys[2], (byte)(keys[1] >> 24)); + } + #endregion + } +} diff --git a/irc/TechBot/Compression/Streams/OutputWindow.cs b/irc/TechBot/Compression/Streams/OutputWindow.cs new file mode 100644 index 00000000000..426c1f752d3 --- /dev/null +++ b/irc/TechBot/Compression/Streams/OutputWindow.cs @@ -0,0 +1,176 @@ +// OutputWindow.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams +{ + + /// + /// Contains the output from the Inflation process. + /// We need to have a window so that we can refer backwards into the output stream + /// to repeat stuff. + /// + /// author of the original java version : John Leuner + /// + public class OutputWindow + { + private static int WINDOW_SIZE = 1 << 15; + private static int WINDOW_MASK = WINDOW_SIZE - 1; + + private byte[] window = new byte[WINDOW_SIZE]; //The window is 2^15 bytes + private int windowEnd = 0; + private int windowFilled = 0; + + public void Write(int abyte) + { + if (windowFilled++ == WINDOW_SIZE) { + throw new InvalidOperationException("Window full"); + } + window[windowEnd++] = (byte) abyte; + windowEnd &= WINDOW_MASK; + } + + + private void SlowRepeat(int repStart, int len, int dist) + { + while (len-- > 0) { + window[windowEnd++] = window[repStart++]; + windowEnd &= WINDOW_MASK; + repStart &= WINDOW_MASK; + } + } + + public void Repeat(int len, int dist) + { + if ((windowFilled += len) > WINDOW_SIZE) { + throw new InvalidOperationException("Window full"); + } + + int rep_start = (windowEnd - dist) & WINDOW_MASK; + int border = WINDOW_SIZE - len; + if (rep_start <= border && windowEnd < border) { + if (len <= dist) { + System.Array.Copy(window, rep_start, window, windowEnd, len); + windowEnd += len; + } else { + /* We have to copy manually, since the repeat pattern overlaps. + */ + while (len-- > 0) { + window[windowEnd++] = window[rep_start++]; + } + } + } else { + SlowRepeat(rep_start, len, dist); + } + } + + public int CopyStored(StreamManipulator input, int len) + { + len = Math.Min(Math.Min(len, WINDOW_SIZE - windowFilled), input.AvailableBytes); + int copied; + + int tailLen = WINDOW_SIZE - windowEnd; + if (len > tailLen) { + copied = input.CopyBytes(window, windowEnd, tailLen); + if (copied == tailLen) { + copied += input.CopyBytes(window, 0, len - tailLen); + } + } else { + copied = input.CopyBytes(window, windowEnd, len); + } + + windowEnd = (windowEnd + copied) & WINDOW_MASK; + windowFilled += copied; + return copied; + } + + public void CopyDict(byte[] dict, int offset, int len) + { + if (windowFilled > 0) { + throw new InvalidOperationException(); + } + + if (len > WINDOW_SIZE) { + offset += len - WINDOW_SIZE; + len = WINDOW_SIZE; + } + System.Array.Copy(dict, offset, window, 0, len); + windowEnd = len & WINDOW_MASK; + } + + public int GetFreeSpace() + { + return WINDOW_SIZE - windowFilled; + } + + public int GetAvailable() + { + return windowFilled; + } + + public int CopyOutput(byte[] output, int offset, int len) + { + int copy_end = windowEnd; + if (len > windowFilled) { + len = windowFilled; + } else { + copy_end = (windowEnd - windowFilled + len) & WINDOW_MASK; + } + + int copied = len; + int tailLen = len - copy_end; + + if (tailLen > 0) { + System.Array.Copy(window, WINDOW_SIZE - tailLen, output, offset, tailLen); + offset += tailLen; + len = copy_end; + } + System.Array.Copy(window, copy_end - len, output, offset, len); + windowFilled -= copied; + if (windowFilled < 0) { + throw new InvalidOperationException(); + } + return copied; + } + + public void Reset() + { + windowFilled = windowEnd = 0; + } + } +} diff --git a/irc/TechBot/Compression/Streams/StreamManipulator.cs b/irc/TechBot/Compression/Streams/StreamManipulator.cs new file mode 100644 index 00000000000..35f98cc4f76 --- /dev/null +++ b/irc/TechBot/Compression/Streams/StreamManipulator.cs @@ -0,0 +1,245 @@ +// StreamManipulator.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams +{ + + /// + /// This class allows us to retrieve a specified amount of bits from + /// the input buffer, as well as copy big byte blocks. + /// + /// It uses an int buffer to store up to 31 bits for direct + /// manipulation. This guarantees that we can get at least 16 bits, + /// but we only need at most 15, so this is all safe. + /// + /// There are some optimizations in this class, for example, you must + /// never peek more then 8 bits more than needed, and you must first + /// peek bits before you may drop them. This is not a general purpose + /// class but optimized for the behaviour of the Inflater. + /// + /// authors of the original java version : John Leuner, Jochen Hoenicke + /// + public class StreamManipulator + { + private byte[] window; + private int window_start = 0; + private int window_end = 0; + + private uint buffer = 0; + private int bits_in_buffer = 0; + + /// + /// Get the next n bits but don't increase input pointer. n must be + /// less or equal 16 and if you if this call succeeds, you must drop + /// at least n-8 bits in the next call. + /// + /// + /// the value of the bits, or -1 if not enough bits available. */ + /// + public int PeekBits(int n) + { + if (bits_in_buffer < n) { + if (window_start == window_end) { + return -1; // ok + } + buffer |= (uint)((window[window_start++] & 0xff | + (window[window_start++] & 0xff) << 8) << bits_in_buffer); + bits_in_buffer += 16; + } + return (int)(buffer & ((1 << n) - 1)); + } + + /// + /// Drops the next n bits from the input. You should have called peekBits + /// with a bigger or equal n before, to make sure that enough bits are in + /// the bit buffer. + /// + public void DropBits(int n) + { + buffer >>= n; + bits_in_buffer -= n; + } + + /// + /// Gets the next n bits and increases input pointer. This is equivalent + /// to peekBits followed by dropBits, except for correct error handling. + /// + /// + /// the value of the bits, or -1 if not enough bits available. + /// + public int GetBits(int n) + { + int bits = PeekBits(n); + if (bits >= 0) { + DropBits(n); + } + return bits; + } + + /// + /// Gets the number of bits available in the bit buffer. This must be + /// only called when a previous peekBits() returned -1. + /// + /// + /// the number of bits available. + /// + public int AvailableBits { + get { + return bits_in_buffer; + } + } + + /// + /// Gets the number of bytes available. + /// + /// + /// the number of bytes available. + /// + public int AvailableBytes { + get { + return window_end - window_start + (bits_in_buffer >> 3); + } + } + + /// + /// Skips to the next byte boundary. + /// + public void SkipToByteBoundary() + { + buffer >>= (bits_in_buffer & 7); + bits_in_buffer &= ~7; + } + + public bool IsNeedingInput { + get { + return window_start == window_end; + } + } + + /// + /// Copies length bytes from input buffer to output buffer starting + /// at output[offset]. You have to make sure, that the buffer is + /// byte aligned. If not enough bytes are available, copies fewer + /// bytes. + /// + /// + /// the buffer. + /// + /// + /// the offset in the buffer. + /// + /// + /// the length to copy, 0 is allowed. + /// + /// + /// the number of bytes copied, 0 if no byte is available. + /// + public int CopyBytes(byte[] output, int offset, int length) + { + if (length < 0) { + throw new ArgumentOutOfRangeException("length negative"); + } + if ((bits_in_buffer & 7) != 0) { + /* bits_in_buffer may only be 0 or 8 */ + throw new InvalidOperationException("Bit buffer is not aligned!"); + } + + int count = 0; + while (bits_in_buffer > 0 && length > 0) { + output[offset++] = (byte) buffer; + buffer >>= 8; + bits_in_buffer -= 8; + length--; + count++; + } + if (length == 0) { + return count; + } + + int avail = window_end - window_start; + if (length > avail) { + length = avail; + } + System.Array.Copy(window, window_start, output, offset, length); + window_start += length; + + if (((window_start - window_end) & 1) != 0) { + /* We always want an even number of bytes in input, see peekBits */ + buffer = (uint)(window[window_start++] & 0xff); + bits_in_buffer = 8; + } + return count + length; + } + + public StreamManipulator() + { + } + + public void Reset() + { + buffer = (uint)(window_start = window_end = bits_in_buffer = 0); + } + + public void SetInput(byte[] buf, int off, int len) + { + if (window_start < window_end) { + throw new InvalidOperationException("Old input was not completely processed"); + } + + int end = off + len; + + /* We want to throw an ArrayIndexOutOfBoundsException early. The + * check is very tricky: it also handles integer wrap around. + */ + if (0 > off || off > end || end > buf.Length) { + throw new ArgumentOutOfRangeException(); + } + + if ((len & 1) != 0) { + /* We always want an even number of bytes in input, see peekBits */ + buffer |= (uint)((buf[off++] & 0xff) << bits_in_buffer); + bits_in_buffer += 8; + } + + window = buf; + window_start = off; + window_end = end; + } + } +} diff --git a/irc/TechBot/Compression/ZipException.cs b/irc/TechBot/Compression/ZipException.cs new file mode 100644 index 00000000000..b342592569a --- /dev/null +++ b/irc/TechBot/Compression/ZipException.cs @@ -0,0 +1,62 @@ +// ZipException.cs +// Copyright (C) 2001 Mike Krueger +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; + +namespace ICSharpCode.SharpZipLib +{ + + /// + /// Is thrown during the creation or input of a zip file. + /// + public class ZipException : Exception + { + /// + /// Initializes a new instance of the ZipException class with default properties. + /// + public ZipException() + { + } + + /// + /// Initializes a new instance of the ZipException class with a specified error message. + /// + public ZipException(string msg) : base(msg) + { + } + } +} diff --git a/irc/TechBot/Default.build b/irc/TechBot/Default.build new file mode 100644 index 00000000000..62738cfa1f0 --- /dev/null +++ b/irc/TechBot/Default.build @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/irc/TechBot/TechBot.Console/App.config b/irc/TechBot/TechBot.Console/App.config new file mode 100644 index 00000000000..bdb4e734fac --- /dev/null +++ b/irc/TechBot/TechBot.Console/App.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/irc/TechBot/TechBot.Console/AssemblyInfo.cs b/irc/TechBot/TechBot.Console/AssemblyInfo.cs new file mode 100644 index 00000000000..dbacda112e8 --- /dev/null +++ b/irc/TechBot/TechBot.Console/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following +// attributes. +// +// change them to the information which is associated with the assembly +// you compile. + +[assembly: AssemblyTitle("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has following format : +// +// Major.Minor.Build.Revision +// +// You can specify all values by your own or you can build default build and revision +// numbers with the '*' character (the default): + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes specify the key for the sign of your assembly. See the +// .NET Framework documentation for more information about signing. +// This is not required, if you don't want signing let these attributes like they're. +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] diff --git a/irc/TechBot/TechBot.Console/Default.build b/irc/TechBot/TechBot.Console/Default.build new file mode 100644 index 00000000000..706547f170b --- /dev/null +++ b/irc/TechBot/TechBot.Console/Default.build @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + diff --git a/irc/TechBot/TechBot.Console/Main.cs b/irc/TechBot/TechBot.Console/Main.cs new file mode 100644 index 00000000000..9705e678925 --- /dev/null +++ b/irc/TechBot/TechBot.Console/Main.cs @@ -0,0 +1,187 @@ +using System; +using System.Configuration; +using TechBot.Library; + +namespace TechBot.Console +{ + public class ConsoleServiceOutput : IServiceOutput + { + public void WriteLine(string message) + { + System.Console.WriteLine(message); + } + } + + + class MainClass + { + private static void VerifyRequiredOption(string optionName, + string optionValue) + { + if (optionValue == null) + { + throw new Exception(String.Format("Option '{0}' not set.", + optionName)); + } + } + + private static string IRCServerHostName + { + get + { + string optionName = "IRCServerHostName"; + string s = ConfigurationSettings.AppSettings[optionName]; + VerifyRequiredOption(optionName, + s); + return s; + } + } + + private static int IRCServerHostPort + { + get + { + string optionName = "IRCServerHostPort"; + string s = ConfigurationSettings.AppSettings[optionName]; + VerifyRequiredOption(optionName, + s); + return Int32.Parse(s); + } + } + + private static string IRCChannelName + { + get + { + string optionName = "IRCChannelName"; + string s = ConfigurationSettings.AppSettings[optionName]; + VerifyRequiredOption(optionName, + s); + return s; + } + } + + private static string IRCBotName + { + get + { + string optionName = "IRCBotName"; + string s = ConfigurationSettings.AppSettings[optionName]; + VerifyRequiredOption(optionName, + s); + return s; + } + } + + private static string ChmPath + { + get + { + string optionName = "ChmPath"; + string s = ConfigurationSettings.AppSettings[optionName]; + VerifyRequiredOption(optionName, + s); + return s; + } + } + + private static string MainChm + { + get + { + string optionName = "MainChm"; + string s = ConfigurationSettings.AppSettings[optionName]; + VerifyRequiredOption(optionName, + s); + return s; + } + } + + private static string NtstatusXml + { + get + { + string optionName = "NtstatusXml"; + string s = ConfigurationSettings.AppSettings[optionName]; + VerifyRequiredOption(optionName, + s); + return s; + } + } + + private static string WinerrorXml + { + get + { + string optionName = "WinerrorXml"; + string s = ConfigurationSettings.AppSettings[optionName]; + VerifyRequiredOption(optionName, + s); + return s; + } + } + + private static string HresultXml + { + get + { + string optionName = "HresultXml"; + string s = ConfigurationSettings.AppSettings[optionName]; + VerifyRequiredOption(optionName, + s); + return s; + } + } + + private static string SvnCommand + { + get + { + string optionName = "SvnCommand"; + string s = ConfigurationSettings.AppSettings[optionName]; + VerifyRequiredOption(optionName, + s); + return s; + } + } + + private static void RunIrcService() + { + IrcService ircService = new IrcService(IRCServerHostName, + IRCServerHostPort, + IRCChannelName, + IRCBotName, + ChmPath, + MainChm, + NtstatusXml, + WinerrorXml, + HresultXml, + SvnCommand); + ircService.Run(); + } + + public static void Main(string[] args) + { + if (args.Length > 0 && args[0].ToLower().Equals("irc")) + { + RunIrcService(); + return; + } + + System.Console.WriteLine("TechBot running console service..."); + TechBotService service = new TechBotService(new ConsoleServiceOutput(), + ChmPath, + MainChm, + NtstatusXml, + WinerrorXml, + HresultXml, + SvnCommand); + service.Run(); + while (true) + { + string s = System.Console.ReadLine(); + service.InjectMessage(s); + } + } + } +} diff --git a/irc/TechBot/TechBot.Console/TechBot.Console.cmbx b/irc/TechBot/TechBot.Console/TechBot.Console.cmbx new file mode 100644 index 00000000000..0e37e7626b7 --- /dev/null +++ b/irc/TechBot/TechBot.Console/TechBot.Console.cmbx @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/irc/TechBot/TechBot.Console/TechBot.Console.prjx b/irc/TechBot/TechBot.Console/TechBot.Console.prjx new file mode 100644 index 00000000000..6a08d66c7be --- /dev/null +++ b/irc/TechBot/TechBot.Console/TechBot.Console.prjx @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/irc/TechBot/TechBot.IRCLibrary/AssemblyInfo.cs b/irc/TechBot/TechBot.IRCLibrary/AssemblyInfo.cs new file mode 100644 index 00000000000..dbacda112e8 --- /dev/null +++ b/irc/TechBot/TechBot.IRCLibrary/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following +// attributes. +// +// change them to the information which is associated with the assembly +// you compile. + +[assembly: AssemblyTitle("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has following format : +// +// Major.Minor.Build.Revision +// +// You can specify all values by your own or you can build default build and revision +// numbers with the '*' character (the default): + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes specify the key for the sign of your assembly. See the +// .NET Framework documentation for more information about signing. +// This is not required, if you don't want signing let these attributes like they're. +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] diff --git a/irc/TechBot/TechBot.IRCLibrary/Default.build b/irc/TechBot/TechBot.IRCLibrary/Default.build new file mode 100644 index 00000000000..1c468dd5fd0 --- /dev/null +++ b/irc/TechBot/TechBot.IRCLibrary/Default.build @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + diff --git a/irc/TechBot/TechBot.IRCLibrary/IRC.cs b/irc/TechBot/TechBot.IRCLibrary/IRC.cs new file mode 100644 index 00000000000..38cb448b6a5 --- /dev/null +++ b/irc/TechBot/TechBot.IRCLibrary/IRC.cs @@ -0,0 +1,26 @@ +using System; + +namespace TechBot.IRCLibrary +{ + /// + /// IRC constants and helper methods. + /// + public class IRC + { + #region IRC commands + + public const string JOIN = "JOIN"; + public const string NICK = "NICK"; + public const string PART = "PART"; + public const string PING = "PING"; + public const string PONG = "PONG"; + public const string PRIVMSG = "PRIVMSG"; + public const string USER = "USER"; + + public const string RPL_NAMREPLY = "353"; + public const string RPL_ENDOFNAMES = "366"; + + #endregion + + } +} diff --git a/irc/TechBot/TechBot.IRCLibrary/IrcChannel.cs b/irc/TechBot/TechBot.IRCLibrary/IrcChannel.cs new file mode 100644 index 00000000000..2f26abbc607 --- /dev/null +++ b/irc/TechBot/TechBot.IRCLibrary/IrcChannel.cs @@ -0,0 +1,134 @@ +/* + Channels names are strings (beginning with a '&' or '#' character) of + length up to 200 characters. Apart from the the requirement that the + first character being either '&' or '#'; the only restriction on a + channel name is that it may not contain any spaces (' '), a control G + (^G or ASCII 7), or a comma (',' which is used as a list item + separator by the protocol). + */ +using System; +using System.Collections; + +namespace TechBot.IRCLibrary +{ + /// + /// IRC channel type. + /// + public enum IrcChannelType + { + Public, + Private, + Secret + } + + + + /// + /// IRC channel. + /// + public class IrcChannel + { + #region Private fields + + private IrcClient owner; + private string name; + private IrcChannelType type = IrcChannelType.Public; + private ArrayList users = new ArrayList(); + + #endregion + + #region Public properties + + /// + /// Owner of this channel. + /// + public IrcClient Owner + { + get + { + return owner; + } + } + + /// + /// Name of channel (no leading #). + /// + public string Name + { + get + { + return name; + } + } + + /// + /// Type of channel. + /// + public IrcChannelType Type + { + get + { + return type; + } + } + + /// + /// Users in this channel. + /// + public ArrayList Users + { + get + { + return users; + } + } + + #endregion + + /// + /// Constructor. + /// + /// Owner of this channel. + /// Name of channel. + public IrcChannel(IrcClient owner, string name) + { + if (owner == null) + { + throw new ArgumentNullException("owner", "Owner cannot be null."); + } + if (name == null) + { + throw new ArgumentNullException("name", "Name cannot be null."); + } + this.owner = owner; + this.name = name; + } + + /// + /// Locate a user. + /// + /// Nickname of user (no decorations). + /// User or null if not found. + public IrcUser LocateUser(string nickname) + { + foreach (IrcUser user in Users) + { + /* FIXME: There are special cases for nickname comparison */ + if (nickname.ToLower().Equals(user.Nickname.ToLower())) + { + return user; + } + } + return null; + } + + /// + /// Talk to the channel. + /// + /// Text to send to the channel. + public void Talk(string text) + { + owner.SendMessage(new IrcMessage(IRC.PRIVMSG, String.Format("#{0} :{1}", name, text))); + } + } +} diff --git a/irc/TechBot/TechBot.IRCLibrary/IrcClient.cs b/irc/TechBot/TechBot.IRCLibrary/IrcClient.cs new file mode 100644 index 00000000000..b5c1d4b207e --- /dev/null +++ b/irc/TechBot/TechBot.IRCLibrary/IrcClient.cs @@ -0,0 +1,654 @@ +using System; +using System.IO; +using System.Text; +using System.Collections; +using System.Net.Sockets; + +namespace TechBot.IRCLibrary +{ + /// + /// Delegate that delivers an IRC message. + /// + public delegate void MessageReceivedHandler(IrcMessage message); + + /// + /// Delegate that notifies if the user database for a channel has changed. + /// + public delegate void ChannelUserDatabaseChangedHandler(IrcChannel channel); + + /// + /// An IRC client. + /// + public class IrcClient + { + /// + /// Monitor when an IRC command is received. + /// + private class IrcCommandEventRegistration + { + /// + /// IRC command to monitor. + /// + private string command; + public string Command + { + get + { + return command; + } + } + + /// + /// Handler to call when command is received. + /// + private MessageReceivedHandler handler; + public MessageReceivedHandler Handler + { + get + { + return handler; + } + } + + /// + /// Constructor. + /// + /// IRC command to monitor. + /// Handler to call when command is received. + public IrcCommandEventRegistration(string command, + MessageReceivedHandler handler) + { + this.command = command; + this.handler = handler; + } + } + + + + /// + /// A buffer to store lines of text. + /// + private class LineBuffer + { + /// + /// Full lines of text in buffer. + /// + private ArrayList strings; + + /// + /// Part of the last line of text in buffer. + /// + private string left = ""; + + /// + /// Standard constructor. + /// + public LineBuffer() + { + strings = new ArrayList(); + } + + /// + /// Return true if there is a complete line in the buffer or false if there is not. + /// + public bool DataAvailable + { + get + { + return (strings.Count > 0); + } + } + + /// + /// Return next complete line in buffer or null if none exists. + /// + /// Next complete line in buffer or null if none exists. + public string Read() + { + if (DataAvailable) + { + string line = strings[0] as string; + strings.RemoveAt(0); + return line; + } + else + { + return null; + } + } + + /// + /// Write a string to buffer splitting it into lines. + /// + /// + public void Write(string data) + { + data = left + data; + left = ""; + string[] sa = data.Split(new char[] { '\n' }); + if (sa.Length <= 0) + { + left = data; + return; + } + else + { + left = ""; + } + for (int i = 0; i < sa.Length; i++) + { + if (i < sa.Length - 1) + { + /* This is a complete line. Remove any \r characters at the end of the line. */ + string line = sa[i].TrimEnd(new char[] { '\r', '\n'}); + /* Silently ignore empty lines */ + if (!line.Equals(String.Empty)) + { + strings.Add(line); + } + } + else + { + /* This may be a partial line. */ + left = sa[i]; + } + } + } + } + + + /// + /// State for asynchronous reads. + /// + private class StateObject + { + /// + /// Network stream where data is read from. + /// + public NetworkStream Stream; + + /// + /// Buffer where data is put. + /// + public byte[] Buffer; + + /// + /// Constructor. + /// + /// Network stream where data is read from. + /// Buffer where data is put. + public StateObject(NetworkStream stream, byte[] buffer) + { + this.Stream = stream; + this.Buffer = buffer; + } + } + + + #region Private fields + private bool firstPingReceived = false; + private System.Text.Encoding encoding = System.Text.Encoding.UTF8; + private TcpClient tcpClient; + private NetworkStream networkStream; + private bool connected = false; + private LineBuffer messageStream; + private ArrayList ircCommandEventRegistrations = new ArrayList(); + private ArrayList channels = new ArrayList(); + #endregion + + #region Public events + + public event MessageReceivedHandler MessageReceived; + + public event ChannelUserDatabaseChangedHandler ChannelUserDatabaseChanged; + + #endregion + + #region Public properties + + /// + /// Encoding used. + /// + public System.Text.Encoding Encoding + { + get + { + return encoding; + } + set + { + encoding = value; + } + } + + /// + /// List of joined channels. + /// + public ArrayList Channels + { + get + { + return channels; + } + } + + #endregion + + #region Private methods + + /// + /// Signal MessageReceived event. + /// + /// Message that was received. + private void OnMessageReceived(IrcMessage message) + { + foreach (IrcCommandEventRegistration icre in ircCommandEventRegistrations) + { + if (message.Command.ToLower().Equals(icre.Command.ToLower())) + { + icre.Handler(message); + } + } + if (MessageReceived != null) + { + MessageReceived(message); + } + } + + /// + /// Signal ChannelUserDatabaseChanged event. + /// + /// Message that was received. + private void OnChannelUserDatabaseChanged(IrcChannel channel) + { + if (ChannelUserDatabaseChanged != null) + { + ChannelUserDatabaseChanged(channel); + } + } + + /// + /// Start an asynchronous read. + /// + private void Receive() + { + if ((networkStream != null) && (networkStream.CanRead)) + { + byte[] buffer = new byte[1024]; + networkStream.BeginRead(buffer, 0, buffer.Length, + new AsyncCallback(ReadComplete), + new StateObject(networkStream, buffer)); + } + else + { + throw new Exception("Socket is closed."); + } + } + + /// + /// Asynchronous read has completed. + /// + /// IAsyncResult object. + private void ReadComplete(IAsyncResult ar) + { + StateObject stateObject = (StateObject) ar.AsyncState; + if (stateObject.Stream.CanRead) + { + int bytesReceived = stateObject.Stream.EndRead(ar); + if (bytesReceived > 0) + { + messageStream.Write(Encoding.GetString(stateObject.Buffer, 0, bytesReceived)); + while (messageStream.DataAvailable) + { + OnMessageReceived(new IrcMessage(messageStream.Read())); + } + } + } + Receive(); + } + + /// + /// Locate channel. + /// + /// Channel name. + /// Channel or null if none was found. + private IrcChannel LocateChannel(string name) + { + foreach (IrcChannel channel in Channels) + { + if (name.ToLower().Equals(channel.Name.ToLower())) + { + return channel; + } + } + return null; + } + + /// + /// Send a PONG message when a PING message is received. + /// + /// Received IRC message. + private void PingMessageReceived(IrcMessage message) + { + SendMessage(new IrcMessage(IRC.PONG, message.Parameters)); + firstPingReceived = true; + } + + /// + /// Process RPL_NAMREPLY message. + /// + /// Received IRC message. + private void RPL_NAMREPLYMessageReceived(IrcMessage message) + { + try + { + // :Oslo2.NO.EU.undernet.org 353 E101 = #E101 :E101 KongFu_uK @Exception + /* "( "=" / "*" / "@" ) + :[ "@" / "+" ] *( " " [ "@" / "+" ] ) + - "@" is used for secret channels, "*" for private + channels, and "=" for others (public channels). */ + if (message.Parameters == null) + { + System.Diagnostics.Debug.WriteLine(String.Format("Message has no parameters.")); + return; + } + string[] parameters = message.Parameters.Split(new char[] { ' '}); + if (parameters.Length < 5) + { + System.Diagnostics.Debug.WriteLine(String.Format("{0} is two few parameters.", parameters.Length)); + return; + } + IrcChannelType type; + switch (parameters[1]) + { + case "=": + type = IrcChannelType.Public; + break; + case "*": + type = IrcChannelType.Private; + break; + case "@": + type = IrcChannelType.Secret; + break; + default: + type = IrcChannelType.Public; + break; + } + IrcChannel channel = LocateChannel(parameters[2].Substring(1)); + if (channel == null) + { + System.Diagnostics.Debug.WriteLine(String.Format("Channel not found '{0}'.", + parameters[2].Substring(1))); + return; + } + string nickname = parameters[3]; + if (nickname[0] != ':') + { + System.Diagnostics.Debug.WriteLine(String.Format("String should start with : and not {0}.", nickname[0])); + return; + } + /* Skip : */ + IrcUser user = channel.LocateUser(nickname.Substring(1)); + if (user == null) + { + user = new IrcUser(nickname.Substring(1)); + channel.Users.Add(user); + } + for (int i = 4; i < parameters.Length; i++) + { + nickname = parameters[i]; + user = channel.LocateUser(nickname); + if (user == null) + { + user = new IrcUser(nickname); + channel.Users.Add(user); + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(String.Format("Ex. {0}", ex)); + } + } + + /// + /// Process RPL_ENDOFNAMES message. + /// + /// Received IRC message. + private void RPL_ENDOFNAMESMessageReceived(IrcMessage message) + { + try + { + /* :End of NAMES list */ + if (message.Parameters == null) + { + System.Diagnostics.Debug.WriteLine(String.Format("Message has no parameters.")); + return; + } + + string[] parameters = message.Parameters.Split(new char[] { ' ' }); + IrcChannel channel = LocateChannel(parameters[1].Substring(1)); + if (channel == null) + { + System.Diagnostics.Debug.WriteLine(String.Format("Channel not found '{0}'.", + parameters[0].Substring(1))); + return; + } + + OnChannelUserDatabaseChanged(channel); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(String.Format("Ex. {0}", ex)); + } + } + + #endregion + + /// + /// Connect to the specified IRC server on the specified port. + /// + /// Address of IRC server. + /// Port of IRC server. + public void Connect(string server, int port) + { + if (connected) + { + throw new AlreadyConnectedException(); + } + else + { + messageStream = new LineBuffer(); + tcpClient = new TcpClient(); + tcpClient.Connect(server, port); + tcpClient.NoDelay = true; + tcpClient.LingerState = new LingerOption(false, 0); + networkStream = tcpClient.GetStream(); + connected = networkStream.CanRead && networkStream.CanWrite; + if (!connected) + { + throw new Exception("Cannot read and write from socket."); + } + /* Install PING message handler */ + MonitorCommand(IRC.PING, new MessageReceivedHandler(PingMessageReceived)); + /* Install RPL_NAMREPLY message handler */ + MonitorCommand(IRC.RPL_NAMREPLY, new MessageReceivedHandler(RPL_NAMREPLYMessageReceived)); + /* Install RPL_ENDOFNAMES message handler */ + MonitorCommand(IRC.RPL_ENDOFNAMES, new MessageReceivedHandler(RPL_ENDOFNAMESMessageReceived)); + /* Start receiving data */ + Receive(); + } + } + + /// + /// Disconnect from IRC server. + /// + public void Diconnect() + { + if (!connected) + { + throw new NotConnectedException(); + } + else + { + + + connected = false; + tcpClient.Close(); + tcpClient = null; + } + } + + /// + /// Send an IRC message. + /// + /// The message to be sent. + public void SendMessage(IrcMessage message) + { + if (!connected) + { + throw new NotConnectedException(); + } + + /* Serialize sending messages */ + lock (typeof(IrcClient)) + { + NetworkStream networkStream = tcpClient.GetStream(); + byte[] bytes = Encoding.GetBytes(message.Line); + networkStream.Write(bytes, 0, bytes.Length); + networkStream.Flush(); + } + } + + /// + /// Monitor when a message with an IRC command is received. + /// + /// IRC command to monitor. + /// Handler to call when command is received. + public void MonitorCommand(string command, MessageReceivedHandler handler) + { + if (command == null) + { + throw new ArgumentNullException("command", "Command cannot be null."); + } + if (handler == null) + { + throw new ArgumentNullException("handler", "Handler cannot be null."); + } + ircCommandEventRegistrations.Add(new IrcCommandEventRegistration(command, handler)); + } + + /// + /// Talk to the channel. + /// + /// Nickname of user to talk to. + /// Text to send to the channel. + public void TalkTo(string nickname, string text) + { + if (nickname == null) + { + throw new ArgumentNullException("nickname", "Nickname cannot be null."); + } + if (text == null) + { + throw new ArgumentNullException("text", "Text cannot be null."); + } + + SendMessage(new IrcMessage(IRC.PRIVMSG, String.Format("{0} :{1}", nickname, text))); + } + + /// + /// Change nickname. + /// + /// New nickname. + public void ChangeNick(string nickname) + { + if (nickname == null) + { + throw new ArgumentNullException("nickname", "Nickname cannot be null."); + } + + /* NICK [ ] */ + SendMessage(new IrcMessage(IRC.NICK, nickname)); + } + + /// + /// Register. + /// + /// New nickname. + /// Real name. Can be null. + public void Register(string nickname, string realname) + { + if (nickname == null) + { + throw new ArgumentNullException("nickname", "Nickname cannot be null."); + } + firstPingReceived = false; + ChangeNick(nickname); + /* OLD: USER */ + /* NEW: USER */ + SendMessage(new IrcMessage(IRC.USER, String.Format("{0} 0 * :{1}", + nickname, realname != null ? realname : "Anonymous"))); + + /* Wait for PING for up til 10 seconds */ + int timer = 0; + while (!firstPingReceived && timer < 200) + { + System.Threading.Thread.Sleep(50); + timer++; + } + } + + /// + /// Join an IRC channel. + /// + /// Name of channel (without leading #). + /// New channel. + public IrcChannel JoinChannel(string name) + { + IrcChannel channel = new IrcChannel(this, name); + channels.Add(channel); + /* JOIN ( *( "," ) [ *( "," ) ] ) / "0" */ + SendMessage(new IrcMessage(IRC.JOIN, String.Format("#{0}", name))); + return channel; + } + + /// + /// Part an IRC channel. + /// + /// IRC channel. If null, the user parts from all channels. + /// Part message. Can be null. + public void PartChannel(IrcChannel channel, string message) + { + /* PART *( "," ) [ ] */ + if (channel != null) + { + SendMessage(new IrcMessage(IRC.PART, String.Format("#{0}{1}", + channel.Name, message != null ? String.Format(" :{0}", message) : ""))); + channels.Remove(channel); + } + else + { + string channelList = null; + foreach (IrcChannel myChannel in Channels) + { + if (channelList == null) + { + channelList = ""; + } + else + { + channelList += ","; + } + channelList += myChannel.Name; + } + if (channelList != null) + { + SendMessage(new IrcMessage(IRC.PART, String.Format("#{0}{1}", + channelList, message != null ? String.Format(" :{0}", message) : ""))); + Channels.Clear(); + } + } + } + } +} diff --git a/irc/TechBot/TechBot.IRCLibrary/IrcException.cs b/irc/TechBot/TechBot.IRCLibrary/IrcException.cs new file mode 100644 index 00000000000..a96b688ad82 --- /dev/null +++ b/irc/TechBot/TechBot.IRCLibrary/IrcException.cs @@ -0,0 +1,50 @@ +using System; + +namespace TechBot.IRCLibrary +{ + /// + /// Base class for all IRC exceptions. + /// + public class IrcException : Exception + { + public IrcException() : base() + { + } + + public IrcException(string message) : base(message) + { + } + + public IrcException(string message, Exception innerException) : base(message, innerException) + { + } + } + + /// + /// Thrown when there is no connection to an IRC server. + /// + public class NotConnectedException : IrcException + { + } + + /// + /// Thrown when there is an attempt to connect to an IRC server and there is already a connection. + /// + public class AlreadyConnectedException : IrcException + { + } + + /// + /// Thrown when there is attempted to parse a malformed or invalid IRC message. + /// + public class MalformedMessageException : IrcException + { + public MalformedMessageException(string message) : base(message) + { + } + + public MalformedMessageException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/irc/TechBot/TechBot.IRCLibrary/IrcMessage.cs b/irc/TechBot/TechBot.IRCLibrary/IrcMessage.cs new file mode 100644 index 00000000000..bab64de3216 --- /dev/null +++ b/irc/TechBot/TechBot.IRCLibrary/IrcMessage.cs @@ -0,0 +1,503 @@ +using System; + +namespace TechBot.IRCLibrary +{ + /* + ::= [':' ] + ::= | [ '!' ] [ '@' ] + ::= { } | + ::= ' ' { ' ' } + ::= [ ':' | ] + + ::= + ::= + + ::= CR LF + + NOTES: + + 1) is consists only of SPACE character(s) (0x20). + Specially notice that TABULATION, and all other control + characters are considered NON-WHITE-SPACE. + + 2) After extracting the parameter list, all parameters are equal, + whether matched by or . is just + a syntactic trick to allow SPACE within parameter. + + 3) The fact that CR and LF cannot appear in parameter strings is + just artifact of the message framing. This might change later. + + 4) The NUL character is not special in message framing, and + basically could end up inside a parameter, but as it would + cause extra complexities in normal C string handling. Therefore + NUL is not allowed within messages. + + 5) The last parameter may be an empty string. + + 6) Use of the extended prefix (['!' ] ['@' ]) must + not be used in server to server communications and is only + intended for server to client messages in order to provide + clients with more useful information about who a message is + from without the need for additional queries. + */ + /* + NOTICE AUTH :*** Looking up your hostname + NOTICE AUTH :*** Checking Ident + NOTICE AUTH :*** Found your hostname + NOTICE AUTH :*** No ident response + */ + + /// + /// IRC message. + /// + public class IrcMessage + { + #region Private fields + private string line; + private string prefix; + private string prefixServername; + private string prefixNickname; + private string prefixUser; + private string prefixHost; + private string command; + private string parameters; + #endregion + + /// + /// Line of text that is to be parsed as an IRC message. + /// + public string Line + { + get + { + return line; + } + } + + /// + /// Does the message have a prefix? + /// + public bool HasPrefix + { + get + { + return prefix != null; + } + } + + /// + /// Prefix or null if none exists. + /// + public string Prefix + { + get + { + return prefix; + } + } + + /// + /// Servername part of prefix or null if no prefix or servername exists. + /// + public string PrefixServername + { + get + { + return prefixServername; + } + } + + /// + /// Nickname part of prefix or null if no prefix or nick exists. + /// + public string PrefixNickname + { + get + { + return prefixNickname; + } + } + + /// + /// User part of (extended) prefix or null if no (extended) prefix exists. + /// + public string PrefixUser + { + get + { + return prefixUser; + } + } + + /// + /// Host part of (extended) prefix or null if no (extended) prefix exists. + /// + public string PrefixHost + { + get + { + return prefixHost; + } + } + + /// + /// Command part of message. + /// + public string Command + { + get + { + return command; + } + } + + /// + /// Is command numeric? + /// + public bool IsCommandNumeric + { + get + { + if (command == null || command.Length != 3) + { + return false; + } + try + { + Int32.Parse(command); + return true; + } + catch (Exception) + { + return false; + } + } + } + + /// + /// Command part of message as text. + /// + public string CommandText + { + get + { + return command; + } + } + + /// + /// Command part of message as a number. + /// + /// Thrown if IsCommandNumeric returns false. + public int CommandNumber + { + get + { + if (IsCommandNumeric) + { + return Int32.Parse(command); + } + else + { + throw new InvalidOperationException(); + } + } + } + + /// + /// Parameters part of message. + /// + public string Parameters + { + get + { + return parameters; + } + } + + /// + /// Constructor. + /// + /// Line of text that is to be parsed as an IRC message. + public IrcMessage(string line) + { + /* + * ::= [':' ] + * ::= | [ '!' ] [ '@' ] + * :Oslo1.NO.EU.undernet.org 461 MYNICK USER :Not enough parameters + */ + try + { + this.line = line; + int i = 0; + + #region Prefix + if (line[i].Equals(':')) + { + i++; + prefix = ""; + /* This message has a prefix */ + string s = ""; + while (i < line.Length && line[i] != ' ' && line[i] != '!' && line[i] != '@') + { + s += line[i++]; + } + if (IsValidIrcNickname(s)) + { + prefixNickname = s; + prefix += prefixNickname; + if (line[i] == '!') + { + /* This message has an extended prefix */ + i++; + s = ""; + while (i < line.Length && line[i] != ' ' && line[i] != '@') + { + s += line[i]; + i++; + } + prefixUser = s; + prefix += "!" + prefixUser; + } + if (line[i] == '@') + { + /* This message has a host prefix */ + s = ""; + do + { + s += line[++i]; + } + while (i < line.Length && line[i] != ' '); + prefixHost = s; + prefix += "@" + prefixHost; + } + } + else /* Assume it is a servername */ + { + prefixServername = s; + prefix += prefixServername; + } + + /* Skip spaces */ + while (i < line.Length && line[i] == ' ') + { + i++; + } + } + else + { + prefix = null; + } + #endregion + + #region Command + if (Char.IsDigit(line[i])) + { + if (!Char.IsDigit(line, i + 1) || !Char.IsDigit(line, i + 2)) + { + throw new Exception(); + } + command = String.Format("{0}{1}{2}", line[i++], line[i++], line[i++]); + } + else + { + command = ""; + while (i < line.Length && Char.IsLetter(line[i])) + { + command += line[i]; + i++; + } + } + #endregion + + #region Parameters + while (true) + { + /* Skip spaces */ + while (i < line.Length && line[i] == ' ') + { + i++; + } + if (i < line.Length && line[i].Equals(':')) + { + i++; + + /* Trailing */ + while (i < line.Length && line[i] != ' ' && line[i] != '\r' && line[i] != '\n' && line[i] != 0) + { + if (parameters == null) + { + parameters = ""; + } + parameters += line[i]; + i++; + } + } + else + { + /* Middle */ + while (i < line.Length && line[i] != '\r' && line[i] != '\n' && line[i] != 0) + { + if (parameters == null) + { + parameters = ""; + } + parameters += line[i]; + i++; + } + } + if (i >= line.Length) + { + break; + } + } + #endregion + } + catch (Exception ex) + { + throw new MalformedMessageException("The message is malformed.", ex); + } + } + + /// + /// Constructor. + /// + /// + /// + /// + /// + /// + /// + public IrcMessage(string prefixServername, + string prefixNickname, + string prefixUser, + string prefixHost, + string command, + string parameters) + { + throw new NotImplementedException(); + } + + /// + /// Constructor. + /// + /// IRC command. + /// IRC command parameters. May be null if there are no parameters. + public IrcMessage(string command, + string parameters) + { + if (command == null || !IsValidIrcCommand(command)) + { + throw new ArgumentException("Command is not a valid IRC command.", "command"); + } + /* An IRC message must not be longer than 512 characters (including terminating CRLF) */ + int parametersLength = (parameters != null) ? 1 + parameters.Length : 0; + if (command.Length + parametersLength > 510) + { + throw new MalformedMessageException("IRC message cannot be longer than 512 characters."); + } + this.command = command; + this.parameters = parameters; + if (parameters != null) + { + this.line = String.Format("{0} {1}\r\n", command, parameters); + } + else + { + this.line = String.Format("{0}\r\n", command); + } + } + + /// + /// Returns wether a string of text is a valid IRC command. + /// + /// The IRC command. + /// True, if command is a valid IRC command, false if not. + private static bool IsValidIrcCommand(string command) + { + foreach (char c in command) + { + if (!Char.IsLetter(c)) + { + return false; + } + } + return true; + } + + private const string IrcSpecial = @"-[]\`^{}"; + private const string IrcSpecialNonSpecs = @"_"; + + /// + /// Returns wether a character is an IRC special character. + /// + /// Character to test. + /// True if the character is an IRC special character, false if not. + private static bool IsSpecial(char c) + { + foreach (char validCharacter in IrcSpecial) + { + if (c.Equals(validCharacter)) + { + return true; + } + } + foreach (char validCharacter in IrcSpecialNonSpecs) + { + if (c.Equals(validCharacter)) + { + return true; + } + } + return false; + } + + /// + /// Returns wether a string of text is a valid IRC nickname. + /// + /// The IRC nickname. + /// True, if nickname is a valid IRC nickname, false if not. + private static bool IsValidIrcNickname(string nickname) + { + /* + * ::= { | | } + * ::= 'a' ... 'z' | 'A' ... 'Z' + * ::= '0' ... '9' + * ::= '-' | '[' | ']' | '\' | '`' | '^' | '{' | '}' + */ + /* An IRC nicknmame must be 1 - 9 characters in length. We don't care so much if it is larger */ + if ((nickname.Length < 1) || (nickname.Length > 30)) + { + return false; + } + /* First character must be a letter. */ + if (!Char.IsLetter(nickname[0])) + { + return false; + } + /* Check the other valid characters for validity. */ + foreach (char c in nickname) + { + if (!Char.IsLetter(c) && !Char.IsDigit(c) && !IsSpecial(c)) + { + return false; + } + } + return true; + } + + /// + /// Write contents to a string. + /// + /// Contents as a string. + public override string ToString() + { + return String.Format("Line({0})Prefix({1})Command({2})Parameters({3})", + line, prefix != null ? prefix : "(null)", + command != null ? command : "(null)", + parameters != null ? parameters : "(null)"); + } + } +} diff --git a/irc/TechBot/TechBot.IRCLibrary/IrcUser.cs b/irc/TechBot/TechBot.IRCLibrary/IrcUser.cs new file mode 100644 index 00000000000..901d74c92b7 --- /dev/null +++ b/irc/TechBot/TechBot.IRCLibrary/IrcUser.cs @@ -0,0 +1,96 @@ +using System; + +namespace TechBot.IRCLibrary +{ + /// + /// IRC user. + /// + public class IrcUser + { + #region Private fields + + private string nickname; + private string decoratedNickname; + + #endregion + + #region Public properties + + /// + /// Nickname of user. + /// + public string Nickname + { + get + { + return nickname; + } + } + + /// + /// Decorated nickname of user. + /// + public string DecoratedNickname + { + get + { + return decoratedNickname; + } + } + + /// + /// Wether user is channel operator. + /// + public bool Operator + { + get + { + return decoratedNickname.StartsWith("@"); + } + } + + /// + /// Wether user has voice. + /// + public bool Voice + { + get + { + return decoratedNickname.StartsWith("+"); + } + } + + #endregion + + /// + /// Constructor. + /// + /// Nickname (possibly decorated) of user. + public IrcUser(string nickname) + { + this.decoratedNickname = nickname.Trim(); + this.nickname = StripDecoration(decoratedNickname); + } + + /// + /// Strip docoration of nickname. + /// + /// Possible decorated nickname. + /// Undecorated nickname. + public static string StripDecoration(string decoratedNickname) + { + if (decoratedNickname.StartsWith("@")) + { + return decoratedNickname.Substring(1); + } + else if (decoratedNickname.StartsWith("+")) + { + return decoratedNickname.Substring(1); + } + else + { + return decoratedNickname; + } + } + } +} diff --git a/irc/TechBot/TechBot.IRCLibrary/TechBot.IRCLibrary.cmbx b/irc/TechBot/TechBot.IRCLibrary/TechBot.IRCLibrary.cmbx new file mode 100644 index 00000000000..67b1e6077d5 --- /dev/null +++ b/irc/TechBot/TechBot.IRCLibrary/TechBot.IRCLibrary.cmbx @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/irc/TechBot/TechBot.IRCLibrary/TechBot.IRCLibrary.prjx b/irc/TechBot/TechBot.IRCLibrary/TechBot.IRCLibrary.prjx new file mode 100644 index 00000000000..457a420241d --- /dev/null +++ b/irc/TechBot/TechBot.IRCLibrary/TechBot.IRCLibrary.prjx @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/irc/TechBot/TechBot.Library/ApiCommand.cs b/irc/TechBot/TechBot.Library/ApiCommand.cs new file mode 100644 index 00000000000..767a3b2f1eb --- /dev/null +++ b/irc/TechBot/TechBot.Library/ApiCommand.cs @@ -0,0 +1,319 @@ +using System; +using System.IO; +using System.Data; +using System.Text.RegularExpressions; +using HtmlHelp; +using HtmlHelp.ChmDecoding; + +namespace TechBot.Library +{ + public class ApiCommand : BaseCommand, ICommand + { + private const bool IsVerbose = false; + + private HtmlHelpSystem chm; + private IServiceOutput serviceOutput; + private string chmPath; + private string mainChm; + + public ApiCommand(IServiceOutput serviceOutput, + string chmPath, + string mainChm) + { + this.serviceOutput = serviceOutput; + this.chmPath = chmPath; + this.mainChm = mainChm; + Run(); + } + + private void WriteIfVerbose(string message) + { + if (IsVerbose) + serviceOutput.WriteLine(message); + } + + private void Run() + { + string CHMFilename = Path.Combine(chmPath, mainChm); + chm = new HtmlHelpSystem(); + chm.OpenFile(CHMFilename, null); + + Console.WriteLine(String.Format("Loaded main CHM: {0}", + Path.GetFileName(CHMFilename))); + foreach (string filename in Directory.GetFiles(chmPath)) + { + if (!Path.GetExtension(filename).ToLower().Equals(".chm")) + continue; + if (Path.GetFileName(filename).ToLower().Equals(mainChm)) + continue; + + Console.WriteLine(String.Format("Loading CHM: {0}", + Path.GetFileName(filename))); + try + { + chm.MergeFile(filename); + } + catch (Exception ex) + { + Console.WriteLine(String.Format("Could not load CHM: {0}. Exception {1}", + Path.GetFileName(filename), + ex)); + } + } + Console.WriteLine(String.Format("Loaded {0} CHMs", + chm.FileList.Length)); + } + + public bool CanHandle(string commandName) + { + return CanHandle(commandName, + new string[] { "api" }); + } + + public void Handle(string commandName, + string parameters) + { + if (parameters.Trim().Equals(String.Empty)) + DisplayNoKeyword(); + else + Search(parameters); + } + + public string Help() + { + return "!api "; + } + + private bool SearchIndex(string keyword) + { + if (chm.HasIndex) + { + IndexItem item = chm.Index.SearchIndex(keyword, + IndexType.KeywordLinks); + if (item != null && item.Topics.Count > 0) + { + WriteIfVerbose(String.Format("Keyword {0} found in index", + item.KeyWord)); + IndexTopic indexTopic = item.Topics[0] as IndexTopic; + return DisplayResult(keyword, indexTopic); + } + else + { + WriteIfVerbose(String.Format("Keyword {0} not found in index", + keyword)); + return false; + } + } + else + return false; + } + + private void SearchFullText(string keyword) + { + string sort = "Rating ASC"; +/* + sort = "Location ASC"); + sort = "Title ASC"); +*/ + WriteIfVerbose(String.Format("Searching fulltext database for {0}", + keyword)); + + bool partialMatches = false; + bool titlesOnly = true; + int maxResults = 100; + DataTable results = chm.PerformSearch(keyword, + maxResults, + partialMatches, + titlesOnly); + WriteIfVerbose(String.Format("results.Rows.Count = {0}", + results != null ? + results.Rows.Count.ToString() : "(none)")); + if (results != null && results.Rows.Count > 0) + { + results.DefaultView.Sort = sort; + if (!DisplayResult(keyword, results)) + { + DisplayNoResult(keyword); + } + } + else + { + DisplayNoResult(keyword); + } + } + + private void Search(string keyword) + { + if (!SearchIndex(keyword)) + { + SearchFullText(keyword); + } + } + + private bool DisplayResult(string keyword, + IndexTopic indexTopic) + { + keyword = keyword.Trim().ToLower(); + string url = indexTopic.URL; + WriteIfVerbose(String.Format("URL from index search {0}", + url)); + string prototype = ExtractPrototype(url); + if (prototype == null || prototype.Trim().Equals(String.Empty)) + return false; + string formattedPrototype = FormatPrototype(prototype); + serviceOutput.WriteLine(formattedPrototype); + return true; + } + + private bool DisplayResult(string keyword, + DataTable results) + { + keyword = keyword.Trim().ToLower(); + for (int i = 0; i < results.DefaultView.Count; i++) + { + DataRowView row = results.DefaultView[i]; + string title = row["Title"].ToString(); + WriteIfVerbose(String.Format("Examining {0}", title)); + if (title.Trim().ToLower().Equals(keyword)) + { + string location = row["Location"].ToString(); + string rating = row["Rating"].ToString(); + string url = row["Url"].ToString(); + string prototype = ExtractPrototype(url); + if (prototype == null || prototype.Trim().Equals(String.Empty)) + continue; + string formattedPrototype = FormatPrototype(prototype); + serviceOutput.WriteLine(formattedPrototype); + return true; + } + } + return false; + } + + private void DisplayNoResult(string keyword) + { + serviceOutput.WriteLine(String.Format("I don't know about keyword {0}", keyword)); + } + + private void DisplayNoKeyword() + { + serviceOutput.WriteLine("Please give me a keyword."); + } + + private string ReplaceComments(string s) + { + return Regex.Replace(s, "//(.+)\r\n", ""); + } + + private string ReplaceLineEndings(string s) + { + return Regex.Replace(s, "(\r\n)+", " "); + } + + private string ReplaceSpaces(string s) + { + return Regex.Replace(s, @" +", " "); + } + + private string ReplaceSpacesBeforeLeftParenthesis(string s) + { + return Regex.Replace(s, @"\( ", @"("); + } + + private string ReplaceSpacesBeforeRightParenthesis(string s) + { + return Regex.Replace(s, @" \)", @")"); + } + + private string ReplaceSemicolon(string s) + { + return Regex.Replace(s, @";", @""); + } + + private string FormatPrototype(string prototype) + { + string s = ReplaceComments(prototype); + s = ReplaceLineEndings(s); + s = ReplaceSpaces(s); + s = ReplaceSpacesBeforeLeftParenthesis(s); + s = ReplaceSpacesBeforeRightParenthesis(s); + s = ReplaceSemicolon(s); + return s; + } + + private string ExtractPrototype(string url) + { + string page = GetPage(url); + Match match = Regex.Match(page, + "
        (.+)
        ", + RegexOptions.Multiline | + RegexOptions.Singleline); + if (match.Groups.Count > 1) + { + string prototype = match.Groups[1].ToString(); + return StripHtml(StripAfterSlashPre(prototype)); + } + + return ""; + } + + private string StripAfterSlashPre(string html) + { + int index = html.IndexOf(""); + if (index != -1) + { + return html.Substring(0, index); + } + else + return html; + } + + private string StripHtml(string html) + { + return Regex.Replace(html, @"<(.|\n)*?>", String.Empty); + } + + private string GetPage(string url) + { + string CHMFileName = ""; + string topicName = ""; + string anchor = ""; + CHMStream.CHMStream baseStream; + if (!chm.BaseStream.GetCHMParts(url, ref CHMFileName, ref topicName, ref anchor)) + { + baseStream = chm.BaseStream; + CHMFileName = baseStream.CHMFileName; + topicName = url; + anchor = ""; + } + else + { + baseStream = GetBaseStreamFromCHMFileName(CHMFileName); + } + + if ((topicName == "") || (CHMFileName == "") || (baseStream == null)) + { + return ""; + } + + return baseStream.ExtractTextFile(topicName); + } + + private CHMStream.CHMStream GetBaseStreamFromCHMFileName(string CHMFileName) + { + foreach (CHMFile file in chm.FileList) + { + WriteIfVerbose(String.Format("Compare: {0} <> {1}", + file.ChmFilePath, + CHMFileName)); + if (file.ChmFilePath.ToLower().Equals(CHMFileName.ToLower())) + { + return file.BaseStream; + } + } + WriteIfVerbose(String.Format("Could not find loaded CHM file in list: {0}", + CHMFileName)); + return null; + } + } +} diff --git a/irc/TechBot/TechBot.Library/AssemblyInfo.cs b/irc/TechBot/TechBot.Library/AssemblyInfo.cs new file mode 100644 index 00000000000..dbacda112e8 --- /dev/null +++ b/irc/TechBot/TechBot.Library/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following +// attributes. +// +// change them to the information which is associated with the assembly +// you compile. + +[assembly: AssemblyTitle("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has following format : +// +// Major.Minor.Build.Revision +// +// You can specify all values by your own or you can build default build and revision +// numbers with the '*' character (the default): + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes specify the key for the sign of your assembly. See the +// .NET Framework documentation for more information about signing. +// This is not required, if you don't want signing let these attributes like they're. +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] diff --git a/irc/TechBot/TechBot.Library/Default.build b/irc/TechBot/TechBot.Library/Default.build new file mode 100644 index 00000000000..0b20ec3ff50 --- /dev/null +++ b/irc/TechBot/TechBot.Library/Default.build @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + diff --git a/irc/TechBot/TechBot.Library/HelpCommand.cs b/irc/TechBot/TechBot.Library/HelpCommand.cs new file mode 100644 index 00000000000..b38cffb9268 --- /dev/null +++ b/irc/TechBot/TechBot.Library/HelpCommand.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections; + +namespace TechBot.Library +{ + public class HelpCommand : BaseCommand, ICommand + { + private IServiceOutput serviceOutput; + private ArrayList commands; + + public HelpCommand(IServiceOutput serviceOutput, + ArrayList commands) + { + this.serviceOutput = serviceOutput; + this.commands = commands; + } + + public bool CanHandle(string commandName) + { + return CanHandle(commandName, + new string[] { "help" }); + } + + public void Handle(string commandName, + string parameters) + { + serviceOutput.WriteLine("I support the following commands:"); + foreach (ICommand command in commands) + serviceOutput.WriteLine(command.Help()); + } + + public string Help() + { + return "!help"; + } + } +} diff --git a/irc/TechBot/TechBot.Library/HresultCommand.cs b/irc/TechBot/TechBot.Library/HresultCommand.cs new file mode 100644 index 00000000000..a81567e5e50 --- /dev/null +++ b/irc/TechBot/TechBot.Library/HresultCommand.cs @@ -0,0 +1,81 @@ +using System; +using System.Xml; + +namespace TechBot.Library +{ + public class HresultCommand : BaseCommand, ICommand + { + private IServiceOutput serviceOutput; + private string hresultXml; + private XmlDocument hresultXmlDocument; + + public HresultCommand(IServiceOutput serviceOutput, + string hresultXml) + { + this.serviceOutput = serviceOutput; + this.hresultXml = hresultXml; + hresultXmlDocument = new XmlDocument(); + hresultXmlDocument.Load(hresultXml); + } + + public bool CanHandle(string commandName) + { + return CanHandle(commandName, + new string[] { "hresult" }); + } + + public void Handle(string commandName, + string parameters) + { + string hresultText = parameters; + if (hresultText.Equals(String.Empty)) + { + serviceOutput.WriteLine("Please provide a valid HRESULT value."); + return; + } + + NumberParser np = new NumberParser(); + long hresult = np.Parse(hresultText); + if (np.Error) + { + serviceOutput.WriteLine(String.Format("{0} is not a valid HRESULT value.", + hresultText)); + return; + } + + string description = GetHresultDescription(hresult); + if (description != null) + { + serviceOutput.WriteLine(String.Format("{0} is {1}.", + hresultText, + description)); + } + else + { + serviceOutput.WriteLine(String.Format("I don't know about HRESULT {0}.", + hresultText)); + } + } + + public string Help() + { + return "!hresult "; + } + + private string GetHresultDescription(long hresult) + { + XmlElement root = hresultXmlDocument.DocumentElement; + XmlNode node = root.SelectSingleNode(String.Format("Hresult[@value='{0}']", + hresult.ToString("X8"))); + if (node != null) + { + XmlAttribute text = node.Attributes["text"]; + if (text == null) + throw new Exception("Node has no text attribute."); + return text.Value; + } + else + return null; + } + } +} diff --git a/irc/TechBot/TechBot.Library/ICommand.cs b/irc/TechBot/TechBot.Library/ICommand.cs new file mode 100644 index 00000000000..3c9edea0694 --- /dev/null +++ b/irc/TechBot/TechBot.Library/ICommand.cs @@ -0,0 +1,28 @@ +using System; + +namespace TechBot.Library +{ + public interface ICommand + { + bool CanHandle(string commandName); + void Handle(string commandName, + string parameters); + string Help(); + } + + + + public class BaseCommand + { + protected bool CanHandle(string commandName, + string[] availableCommands) + { + foreach (string availableCommand in availableCommands) + { + if (String.Compare(availableCommand, commandName, true) == 0) + return true; + } + return false; + } + } +} diff --git a/irc/TechBot/TechBot.Library/IrcService.cs b/irc/TechBot/TechBot.Library/IrcService.cs new file mode 100644 index 00000000000..42abdec94b8 --- /dev/null +++ b/irc/TechBot/TechBot.Library/IrcService.cs @@ -0,0 +1,142 @@ +using System; +using System.Threading; +using TechBot.IRCLibrary; + +namespace TechBot.Library +{ + public class IrcService : IServiceOutput + { + private string hostname; + private int port; + private string channelname; + private string botname; + private string chmPath; + private string mainChm; + private string ntstatusXml; + private string winerrorXml; + private string hresultXml; + private string svnCommand; + private IrcClient client; + private IrcChannel channel1; + private TechBotService service; + private bool isStopped = false; + + public IrcService(string hostname, + int port, + string channelname, + string botname, + string chmPath, + string mainChm, + string ntstatusXml, + string winerrorXml, + string hresultXml, + string svnCommand) + { + this.hostname = hostname; + this.port = port; + this.channelname = channelname; + this.botname = botname; + this.chmPath = chmPath; + this.mainChm = mainChm; + this.ntstatusXml = ntstatusXml; + this.winerrorXml = winerrorXml; + this.hresultXml = hresultXml; + this.svnCommand = svnCommand; + } + + public void Run() + { + service = new TechBotService(this, + chmPath, + mainChm, + ntstatusXml, + winerrorXml, + hresultXml, + svnCommand); + service.Run(); + + client = new IrcClient(); + client.Encoding = System.Text.Encoding.GetEncoding("iso-8859-1"); + client.MessageReceived += new MessageReceivedHandler(client_MessageReceived); + client.ChannelUserDatabaseChanged += new ChannelUserDatabaseChangedHandler(client_ChannelUserDatabaseChanged); + System.Console.WriteLine(String.Format("Connecting to {0} port {1}", + hostname, port)); + client.Connect(hostname, port); + System.Console.WriteLine("Connected..."); + client.Register(botname, null); + System.Console.WriteLine(String.Format("Registered as {0}...", botname)); + channel1 = client.JoinChannel(channelname); + System.Console.WriteLine(String.Format("Joined channel {0}...", channelname)); + + while (!isStopped) + { + Thread.Sleep(1000); + } + + client.PartChannel(channel1, "Caught in the bitstream..."); + client.Diconnect(); + System.Console.WriteLine("Disconnected..."); + } + + public void Stop() + { + isStopped = true; + } + + public void WriteLine(string message) + { + Console.WriteLine(String.Format("Sending: {0}", message)); + channel1.Talk(message); + } + + private void ExtractMessage(string parameters, + out string message) + { + int startIndex = parameters.IndexOf(':'); + if (startIndex != -1) + { + message = parameters.Substring(startIndex + 1); + } + else + { + message = parameters; + } + } + + private void client_MessageReceived(IrcMessage message) + { + try + { + if (channel1 != null && + channel1.Name != null && + message.Parameters != null) + { + string injectMessage; + ExtractMessage(message.Parameters, out injectMessage); + if ((message.Command.ToUpper().Equals("PRIVMSG")) && + (message.Parameters.ToLower().StartsWith("#" + channel1.Name.ToLower() + " "))) + { + Console.WriteLine("Injecting: " + injectMessage); + service.InjectMessage(injectMessage); + } + else + { + Console.WriteLine("Received: " + message.Line); + } + } + else + { + Console.WriteLine("Received: " + message.Line); + } + } + catch (Exception ex) + { + Console.WriteLine(String.Format("Exception: {0}", ex)); + } + } + + private void client_ChannelUserDatabaseChanged(IrcChannel channel) + { + } + } +} diff --git a/irc/TechBot/TechBot.Library/NtStatusCommand.cs b/irc/TechBot/TechBot.Library/NtStatusCommand.cs new file mode 100644 index 00000000000..081d60a841a --- /dev/null +++ b/irc/TechBot/TechBot.Library/NtStatusCommand.cs @@ -0,0 +1,81 @@ +using System; +using System.Xml; + +namespace TechBot.Library +{ + public class NtStatusCommand : BaseCommand, ICommand + { + private IServiceOutput serviceOutput; + private string ntstatusXml; + private XmlDocument ntstatusXmlDocument; + + public NtStatusCommand(IServiceOutput serviceOutput, + string ntstatusXml) + { + this.serviceOutput = serviceOutput; + this.ntstatusXml = ntstatusXml; + ntstatusXmlDocument = new XmlDocument(); + ntstatusXmlDocument.Load(ntstatusXml); + } + + public bool CanHandle(string commandName) + { + return CanHandle(commandName, + new string[] { "ntstatus" }); + } + + public void Handle(string commandName, + string parameters) + { + string ntstatusText = parameters; + if (ntstatusText.Equals(String.Empty)) + { + serviceOutput.WriteLine("Please provide a valid NTSTATUS value."); + return; + } + + NumberParser np = new NumberParser(); + long ntstatus = np.Parse(ntstatusText); + if (np.Error) + { + serviceOutput.WriteLine(String.Format("{0} is not a valid NTSTATUS value.", + ntstatusText)); + return; + } + + string description = GetNtstatusDescription(ntstatus); + if (description != null) + { + serviceOutput.WriteLine(String.Format("{0} is {1}.", + ntstatusText, + description)); + } + else + { + serviceOutput.WriteLine(String.Format("I don't know about NTSTATUS {0}.", + ntstatusText)); + } + } + + public string Help() + { + return "!ntstatus "; + } + + private string GetNtstatusDescription(long ntstatus) + { + XmlElement root = ntstatusXmlDocument.DocumentElement; + XmlNode node = root.SelectSingleNode(String.Format("Ntstatus[@value='{0}']", + ntstatus.ToString("X8"))); + if (node != null) + { + XmlAttribute text = node.Attributes["text"]; + if (text == null) + throw new Exception("Node has no text attribute."); + return text.Value; + } + else + return null; + } + } +} diff --git a/irc/TechBot/TechBot.Library/NumberParser.cs b/irc/TechBot/TechBot.Library/NumberParser.cs new file mode 100644 index 00000000000..9d4eab5c7e7 --- /dev/null +++ b/irc/TechBot/TechBot.Library/NumberParser.cs @@ -0,0 +1,32 @@ +using System; +using System.Globalization; + +namespace TechBot.Library +{ + public class NumberParser + { + public bool Error = false; + + public long Parse(string s) + { + try + { + Error = false; + if (s.StartsWith("0x")) + return Int64.Parse(s.Substring(2), + NumberStyles.HexNumber); + else + return Int64.Parse(s); + } + catch (FormatException) + { + Error = true; + } + catch (OverflowException) + { + Error = true; + } + return -1; + } + } +} diff --git a/irc/TechBot/TechBot.Library/ServiceOutput.cs b/irc/TechBot/TechBot.Library/ServiceOutput.cs new file mode 100644 index 00000000000..c0d4474f1ae --- /dev/null +++ b/irc/TechBot/TechBot.Library/ServiceOutput.cs @@ -0,0 +1,9 @@ +using System; + +namespace TechBot.Library +{ + public interface IServiceOutput + { + void WriteLine(string message); + } +} diff --git a/irc/TechBot/TechBot.Library/SvnCommand.cs b/irc/TechBot/TechBot.Library/SvnCommand.cs new file mode 100644 index 00000000000..32c77cc380d --- /dev/null +++ b/irc/TechBot/TechBot.Library/SvnCommand.cs @@ -0,0 +1,34 @@ +using System; + +namespace TechBot.Library +{ + public class SvnCommand : BaseCommand, ICommand + { + private IServiceOutput serviceOutput; + private string svnCommand; + + public SvnCommand(IServiceOutput serviceOutput, + string svnCommand) + { + this.serviceOutput = serviceOutput; + this.svnCommand = svnCommand; + } + + public bool CanHandle(string commandName) + { + return CanHandle(commandName, + new string[] { "svn" }); + } + + public void Handle(string commandName, + string parameters) + { + serviceOutput.WriteLine(svnCommand); + } + + public string Help() + { + return "!svn"; + } + } +} diff --git a/irc/TechBot/TechBot.Library/TechBot.Library.cmbx b/irc/TechBot/TechBot.Library/TechBot.Library.cmbx new file mode 100644 index 00000000000..6b132a5e2f6 --- /dev/null +++ b/irc/TechBot/TechBot.Library/TechBot.Library.cmbx @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/irc/TechBot/TechBot.Library/TechBot.Library.prjx b/irc/TechBot/TechBot.Library/TechBot.Library.prjx new file mode 100644 index 00000000000..1f6fd18d368 --- /dev/null +++ b/irc/TechBot/TechBot.Library/TechBot.Library.prjx @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/irc/TechBot/TechBot.Library/TechBotService.cs b/irc/TechBot/TechBot.Library/TechBotService.cs new file mode 100644 index 00000000000..32a8ec360f6 --- /dev/null +++ b/irc/TechBot/TechBot.Library/TechBotService.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections; +using System.IO; +using System.Data; +using System.Threading; +using TechBot.IRCLibrary; + +namespace TechBot.Library +{ + public class TechBotService + { + private const bool IsVerbose = false; + + private IServiceOutput serviceOutput; + private string chmPath; + private string mainChm; + private string ntstatusXml; + private string winerrorXml; + private string hresultXml; + private string svnCommand; + private ArrayList commands = new ArrayList(); + + public TechBotService(IServiceOutput serviceOutput, + string chmPath, + string mainChm, + string ntstatusXml, + string winerrorXml, + string hresultXml, + string svnCommand) + { + this.serviceOutput = serviceOutput; + this.chmPath = chmPath; + this.mainChm = mainChm; + this.ntstatusXml = ntstatusXml; + this.winerrorXml = winerrorXml; + this.hresultXml = hresultXml; + this.svnCommand = svnCommand; + } + + private void WriteIfVerbose(string message) + { + if (IsVerbose) + serviceOutput.WriteLine(message); + } + + public void Run() + { + commands.Add(new HelpCommand(serviceOutput, + commands)); + commands.Add(new ApiCommand(serviceOutput, + chmPath, + mainChm)); + commands.Add(new NtStatusCommand(serviceOutput, + ntstatusXml)); + commands.Add(new WinerrorCommand(serviceOutput, + winerrorXml)); + commands.Add(new HresultCommand(serviceOutput, + hresultXml)); + commands.Add(new SvnCommand(serviceOutput, + svnCommand)); + } + + public void InjectMessage(string message) + { + if (message.StartsWith("!")) + { + ParseCommandMessage(message); + } + } + + private bool IsCommandMessage(string message) + { + return message.StartsWith("!"); + } + + public void ParseCommandMessage(string message) + { + if (!IsCommandMessage(message)) + return; + + message = message.Substring(1).Trim(); + int index = message.IndexOf(' '); + string commandName; + string parameters = ""; + if (index != -1) + { + commandName = message.Substring(0, index).Trim(); + parameters = message.Substring(index).Trim(); + } + else + commandName = message.Trim(); + + foreach (ICommand command in commands) + { + if (command.CanHandle(commandName)) + { + command.Handle(commandName, parameters); + return; + } + } + } + } +} diff --git a/irc/TechBot/TechBot.Library/WinerrorCommand.cs b/irc/TechBot/TechBot.Library/WinerrorCommand.cs new file mode 100644 index 00000000000..5e92b818cf6 --- /dev/null +++ b/irc/TechBot/TechBot.Library/WinerrorCommand.cs @@ -0,0 +1,81 @@ +using System; +using System.Xml; + +namespace TechBot.Library +{ + public class WinerrorCommand : BaseCommand, ICommand + { + private IServiceOutput serviceOutput; + private string winerrorXml; + private XmlDocument winerrorXmlDocument; + + public WinerrorCommand(IServiceOutput serviceOutput, + string winerrorXml) + { + this.serviceOutput = serviceOutput; + this.winerrorXml = winerrorXml; + winerrorXmlDocument = new XmlDocument(); + winerrorXmlDocument.Load(winerrorXml); + } + + public bool CanHandle(string commandName) + { + return CanHandle(commandName, + new string[] { "winerror" }); + } + + public void Handle(string commandName, + string parameters) + { + string winerrorText = parameters; + if (winerrorText.Equals(String.Empty)) + { + serviceOutput.WriteLine("Please provide a valid System Error Code value."); + return; + } + + NumberParser np = new NumberParser(); + long winerror = np.Parse(winerrorText); + if (np.Error) + { + serviceOutput.WriteLine(String.Format("{0} is not a valid System Error Code value.", + winerrorText)); + return; + } + + string description = GetWinerrorDescription(winerror); + if (description != null) + { + serviceOutput.WriteLine(String.Format("{0} is {1}.", + winerrorText, + description)); + } + else + { + serviceOutput.WriteLine(String.Format("I don't know about System Error Code {0}.", + winerrorText)); + } + } + + public string Help() + { + return "!winerror "; + } + + private string GetWinerrorDescription(long winerror) + { + XmlElement root = winerrorXmlDocument.DocumentElement; + XmlNode node = root.SelectSingleNode(String.Format("Winerror[@value='{0}']", + winerror)); + if (node != null) + { + XmlAttribute text = node.Attributes["text"]; + if (text == null) + throw new Exception("Node has no text attribute."); + return text.Value; + } + else + return null; + } + } +} diff --git a/irc/TechBot/TechBot.cmbx b/irc/TechBot/TechBot.cmbx new file mode 100644 index 00000000000..3e8915e180a --- /dev/null +++ b/irc/TechBot/TechBot.cmbx @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/irc/TechBot/TechBot/App.config b/irc/TechBot/TechBot/App.config new file mode 100644 index 00000000000..bdb4e734fac --- /dev/null +++ b/irc/TechBot/TechBot/App.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/irc/TechBot/TechBot/AssemblyInfo.cs b/irc/TechBot/TechBot/AssemblyInfo.cs new file mode 100644 index 00000000000..dbacda112e8 --- /dev/null +++ b/irc/TechBot/TechBot/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following +// attributes. +// +// change them to the information which is associated with the assembly +// you compile. + +[assembly: AssemblyTitle("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has following format : +// +// Major.Minor.Build.Revision +// +// You can specify all values by your own or you can build default build and revision +// numbers with the '*' character (the default): + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes specify the key for the sign of your assembly. See the +// .NET Framework documentation for more information about signing. +// This is not required, if you don't want signing let these attributes like they're. +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] diff --git a/irc/TechBot/TechBot/Default.build b/irc/TechBot/TechBot/Default.build new file mode 100644 index 00000000000..8d0f4bb4de7 --- /dev/null +++ b/irc/TechBot/TechBot/Default.build @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + diff --git a/irc/TechBot/TechBot/ServiceThread.cs b/irc/TechBot/TechBot/ServiceThread.cs new file mode 100644 index 00000000000..0fd814562ba --- /dev/null +++ b/irc/TechBot/TechBot/ServiceThread.cs @@ -0,0 +1,71 @@ +using System; +using System.Configuration; +using System.Diagnostics; +using TechBot.Library; + +namespace TechBot +{ + public class ServiceThread + { + private string IRCServerHostName; + private int IRCServerHostPort; + private string IRCChannelName; + private string IRCBotName; + private string ChmPath; + private string MainChm; + private string NtstatusXml; + private string HresultXml; + private string WinerrorXml; + private string SvnCommand; + private EventLog eventLog; + + public ServiceThread(EventLog eventLog) + { + this.eventLog = eventLog; + } + + private void SetupConfiguration() + { + IRCServerHostName = ConfigurationSettings.AppSettings["IRCServerHostName"]; + IRCServerHostPort = Int32.Parse(ConfigurationSettings.AppSettings["IRCServerHostPort"]); + IRCChannelName = ConfigurationSettings.AppSettings["IRCChannelName"]; + IRCBotName = ConfigurationSettings.AppSettings["IRCBotName"]; + ChmPath = ConfigurationSettings.AppSettings["ChmPath"]; + MainChm = ConfigurationSettings.AppSettings["MainChm"]; + NtstatusXml = ConfigurationSettings.AppSettings["NtstatusXml"]; + HresultXml = ConfigurationSettings.AppSettings["HresultXml"]; + WinerrorXml = ConfigurationSettings.AppSettings["WinerrorXml"]; + SvnCommand = ConfigurationSettings.AppSettings["SvnCommand"]; + } + + public void Run() + { + SetupConfiguration(); + System.Console.WriteLine("TechBot irc service..."); + + IrcService ircService = new IrcService(IRCServerHostName, + IRCServerHostPort, + IRCChannelName, + IRCBotName, + ChmPath, + MainChm, + NtstatusXml, + WinerrorXml, + HresultXml, + SvnCommand); + ircService.Run(); + } + + public void Start() + { + try + { + Run(); + } + catch (Exception ex) + { + eventLog.WriteEntry(String.Format("Ex. {0}", ex)); + } + } + } +} diff --git a/irc/TechBot/TechBot/TechBot.prjx b/irc/TechBot/TechBot/TechBot.prjx new file mode 100644 index 00000000000..7af6ca126aa --- /dev/null +++ b/irc/TechBot/TechBot/TechBot.prjx @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/irc/TechBot/TechBot/TechBotService.cs b/irc/TechBot/TechBot/TechBotService.cs new file mode 100644 index 00000000000..79fca2d38d3 --- /dev/null +++ b/irc/TechBot/TechBot/TechBotService.cs @@ -0,0 +1,97 @@ +using System; +using System.Threading; +using System.Collections; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.ServiceProcess; +using System.Configuration.Install; + +namespace TechBot +{ + public class TechBotService : System.ServiceProcess.ServiceBase + { + private Thread thread; + private ServiceThread threadWorker; + + public TechBotService() + { + InitializeComponents(); + } + + private void InitializeComponents() + { + this.ServiceName = "TechBot"; + } + + /// + /// This method starts the service. + /// + public static void Main() + { + System.ServiceProcess.ServiceBase.Run(new System.ServiceProcess.ServiceBase[] { + new TechBotService() // To run more than one service you have to add them here + }); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + } + + /// + /// Start this service. + /// + protected override void OnStart(string[] args) + { + try + { + threadWorker = new ServiceThread(EventLog); + thread = new Thread(new ThreadStart(threadWorker.Start)); + thread.Start(); + EventLog.WriteEntry(String.Format("TechBot service is running.")); + } + catch (Exception ex) + { + EventLog.WriteEntry(String.Format("Ex. {0}", ex)); + } + } + + /// + /// Stop this service. + /// + protected override void OnStop() + { + try + { + thread.Abort(); + thread.Join(); + thread = null; + threadWorker = null; + EventLog.WriteEntry(String.Format("TechBot service is stopped.")); + } + catch (Exception ex) + { + EventLog.WriteEntry(String.Format("Ex. {0}", ex)); + } + } + } +} + +[RunInstaller(true)] +public class ProjectInstaller : Installer +{ + public ProjectInstaller() + { + ServiceProcessInstaller spi = new ServiceProcessInstaller(); + spi.Account = ServiceAccount.LocalSystem; + + ServiceInstaller si = new ServiceInstaller(); + si.ServiceName = "TechBot"; + si.StartType = ServiceStartMode.Automatic; + Installers.AddRange(new Installer[] {spi, si}); + } +}