/// <summary> /// Opens or creates specified text database. /// </summary> /// <param name="file">Text database file name with optional path.</param> public void OpenOrCreate(string file) { m_pDatabaseStream = OpenOrCreateDb(file); m_pReader = new StreamLineReader(m_pDatabaseStream); m_Open = true; }
/// <summary> /// Does period handling. /// </summary> /// <param name="strm">Input stream.</param> /// <param name="add_Remove">If true add periods, else removes periods.</param> /// <param name="setStrmPosTo0">If true sets stream position to 0.</param> /// <returns></returns> public static MemoryStream DoPeriodHandling(Stream strm, bool add_Remove, bool setStrmPosTo0) { MemoryStream replyData = new MemoryStream(); byte[] crlf = new byte[] { (byte)'\r', (byte)'\n' }; if (setStrmPosTo0) { strm.Position = 0; } StreamLineReader r = new StreamLineReader(strm); byte[] line = r.ReadLine(); // Loop through all lines while (line != null) { if (line.Length > 0) { if (line[0] == (byte)'.') { /* Add period Rfc 2821 4.5.2 * - Before sending a line of mail text, the SMTP client checks the * first character of the line. If it is a period, one additional * period is inserted at the beginning of the line. */ if (add_Remove) { replyData.WriteByte((byte)'.'); replyData.Write(line, 0, line.Length); } /* Remove period Rfc 2821 4.5.2 * If the first character is a period , the first characteris deleted. */ else { replyData.Write(line, 1, line.Length - 1); } } else { replyData.Write(line, 0, line.Length); } } replyData.Write(crlf, 0, crlf.Length); // Read next line line = r.ReadLine(); } replyData.Position = 0; return(replyData); }
/// <summary> /// Opens specified text database. /// </summary> /// <param name="file">Text database file name with optional path.</param> public void Open(string file) { if(!File.Exists(file)){ throw new Exception("Specified database file doesn't exist !"); } m_pDatabaseStream = OpenOrCreateDb(file); m_pReader = new StreamLineReader(m_pDatabaseStream); m_Open = true; }
/// <summary> /// Deletes specified message from recycle bin. /// </summary> /// <param name="messageID">Message ID which to restore.</param> public static void DeleteRecycleBinMessage(string messageID) { using(FileStream fs = GetFile()){ int delRowCount = 0; StreamLineReader r = new StreamLineReader(fs); long pos = fs.Position; string line = r.ReadLineString(); while(line != null){ // Skip comment lines if(!line.StartsWith("#")){ // Skip deleted row if(line.StartsWith("\0")){ delRowCount++; } else{ string[] row = TextUtils.SplitQuotedString(line,' '); // Delete row if(row[0] == messageID){ string user = row[2]; string folder = TextUtils.UnQuoteString(row[3]); // Delete message File.Delete(m_RecycleBinPath + messageID + ".eml"); // Delete row byte[] linebytes = new byte[fs.Position - pos - 2]; fs.Position = pos; fs.Write(linebytes,0,linebytes.Length); fs.Position += 2; // CRLF delRowCount++; break; } } } pos = fs.Position; line = r.ReadLineString(); } // There are many deleted rows, vacuum(remove deleted rows) flags database. if(delRowCount > 500){ Vacuum(fs); } } }
/// <summary> /// Deletes specified user message flags on specified message. /// </summary> /// <param name="userID">User ID.</param> /// <param name="folder">Folder.</param> /// <param name="uid">Message UID.</param> public static void DeleteFlags(string userID,string folder,int uid) { using(FileStream fs = GetFlagsFile(userID,folder)){ int delRowCount = 0; StreamLineReader r = new StreamLineReader(fs); long pos = fs.Position; string line = r.ReadLineString(); while(line != null){ // Skip comment lines if(!line.StartsWith("#")){ // Skip deleted row if(line.StartsWith("\0")){ delRowCount++; } else{ string[] userID_uid_flags = line.Split(' '); // Delete row if(userID_uid_flags[1] == userID && Convert.ToInt32(userID_uid_flags[1]) == uid){ byte[] linebytes = new byte[fs.Position - pos - 2]; fs.Position = pos; fs.Write(linebytes,0,linebytes.Length); fs.Position += 2; // CRLF delRowCount++; } } } pos = fs.Position; line = r.ReadLineString(); } // There are many deleted rows, vacuum(remove deleted rows) flags database. if(delRowCount > 500){ Vacuum(fs); } } }
/// <summary> /// Default constructor. /// </summary> /// <param name="fs">Message file stream.</param> public _InternalHeader(FileStream fs) { m_pFile = fs; StreamLineReader r = new StreamLineReader(fs); string line = r.ReadLineString(); if(line!= null && line.ToLower() == "<internalheader>"){ line = r.ReadLineString(); while(line.ToLower() != "</internalheader>"){ if(line.ToLower().StartsWith("#-messageflags:")){ m_MessageFlags = (IMAP_MessageFlags)Enum.Parse(typeof(IMAP_MessageFlags),line.Substring(15).Trim()); } else if(line.ToLower().StartsWith("#-envelope:")){ m_Envelope = line.Substring(11).Trim(); } else if(line.ToLower().StartsWith("#-body:")){ m_Body = line.Substring(7).Trim(); } line = r.ReadLineString(); } // Remove internal header if(fs.CanWrite){ byte[] data = new byte[fs.Length - fs.Position]; fs.Read(data,0,data.Length); fs.Position = 0; fs.Write(data,0,data.Length); fs.SetLength(data.Length); fs.Position = 0; } } // Internal header doesn't exist else{ fs.Position = 0; } }
/// <summary> /// Vacuums flags database, deletes deleted rows empty used space from file. /// </summary> /// <param name="fs">Database file stream.</param> private static void Vacuum(FileStream fs) { MemoryStream buffer = new MemoryStream(); fs.Position = 0; StreamLineReader r = new StreamLineReader(fs); string line = r.ReadLineString(); while(line != null){ // Skip deleted rows if(!line.StartsWith("\0")){ byte[] lineBytes = System.Text.Encoding.ASCII.GetBytes(line + "\r\n"); buffer.Write(lineBytes,0,lineBytes.Length); } line = r.ReadLineString(); } fs.SetLength(buffer.Length); fs.Position = 0; buffer.WriteTo(fs); }
/// <summary> /// Restores specified message from recycle bin. /// </summary> /// <param name="messageID">Message ID which to restore.</param> /// <param name="api">Reference to API.</param> public static void RestoreFromRecycleBin(string messageID,IMailServerApi api) { using(FileStream fs = GetFile()){ int delRowCount = 0; StreamLineReader r = new StreamLineReader(fs); long pos = fs.Position; string line = r.ReadLineString(); while(line != null){ // Skip comment lines if(!line.StartsWith("#")){ // Skip deleted row if(line.StartsWith("\0")){ delRowCount++; } else{ string[] row = TextUtils.SplitQuotedString(line,' '); // Delete row if(row[0] == messageID){ string user = row[2]; string folder = TextUtils.UnQuoteString(row[3]); // Store message back to original user folder using(FileStream stream = File.OpenRead(m_RecycleBinPath + messageID + ".eml")){ // If folder doesn't exist, create it if(!api.FolderExists(user + "/" + folder)){ api.CreateFolder("system",user,folder); } api.StoreMessage("system",user,folder,stream,DateTime.Now,new string[]{"Recent"}); } // Delete row byte[] linebytes = new byte[fs.Position - pos - 2]; fs.Position = pos; fs.Write(linebytes,0,linebytes.Length); fs.Position += 2; // CRLF delRowCount++; // Delete recycle bin message File.Delete(m_RecycleBinPath + messageID + ".eml"); break; } } } pos = fs.Position; line = r.ReadLineString(); } // There are many deleted rows, vacuum(remove deleted rows) flags database. if(delRowCount > 500){ Vacuum(fs); } } }
/// <summary> /// Gets recycle bin message stream. NOTE: This method caller must take care of closing stream. /// </summary> /// <param name="messageID">Message ID if of message what to get.</param> /// <returns></returns> public static Stream GetRecycleBinMessage(string messageID) { using(FileStream fs = GetFile()){ int delRowCount = 0; StreamLineReader r = new StreamLineReader(fs); long pos = fs.Position; string line = r.ReadLineString(); while(line != null){ // Skip comment lines if(!line.StartsWith("#")){ // Skip deleted row if(line.StartsWith("\0")){ delRowCount++; } else{ string[] row = TextUtils.SplitQuotedString(line,' '); // Delete row if(row[0] == messageID){ string user = row[2]; string folder = TextUtils.UnQuoteString(row[3]); // Store message back to original user folder FileStream stream = File.OpenRead(m_RecycleBinPath + messageID + ".eml"); return stream; } } } pos = fs.Position; line = r.ReadLineString(); } } throw new Exception("Specified message doesn't exist !"); }
/// <summary> /// Parses relay info from stream. /// </summary> /// <param name="relayMsgStrm"></param> private void ReadRelayInfo(Stream relayMsgStrm) { StreamLineReader reader = new StreamLineReader(relayMsgStrm); string relayHead = System.Text.Encoding.ASCII.GetString(reader.ReadLine()); if(relayHead != null && relayHead.StartsWith("RelayInfo:")){ relayHead = relayHead.Replace("RelayInfo:",""); relayHead = relayHead.Trim(); string[] param = relayHead.Split(new char[]{'\t'}); if(param.Length == 4){ m_IsWSent = Convert.ToBoolean(Convert.ToInt32(param[0])); m_To = param[1]; m_From = param[2]; m_MsgDate = DateTime.ParseExact(param[3],"r",System.Globalization.DateTimeFormatInfo.InvariantInfo); } m_MsgStartPos = (int)relayMsgStrm.Position; } }
/// <summary> /// Does period handling. /// </summary> /// <param name="strm">Input stream.</param> /// <param name="add_Remove">If true add periods, else removes periods.</param> /// <param name="setStrmPosTo0">If true sets stream position to 0.</param> /// <returns></returns> public static MemoryStream DoPeriodHandling(Stream strm,bool add_Remove,bool setStrmPosTo0) { MemoryStream replyData = new MemoryStream(); byte[] crlf = new byte[]{(byte)'\r',(byte)'\n'}; if(setStrmPosTo0){ strm.Position = 0; } StreamLineReader r = new StreamLineReader(strm); byte[] line = r.ReadLine(); // Loop through all lines while(line != null){ if(line.Length > 0){ if(line[0] == (byte)'.'){ /* Add period Rfc 281 4.5.2 - Before sending a line of mail text, the SMTP client checks the first character of the line. If it is a period, one additional period is inserted at the beginning of the line. */ if(add_Remove){ replyData.WriteByte((byte)'.'); replyData.Write(line,0,line.Length); } /* Remove period Rfc 281 4.5.2 If the first character is a period , the first characteris deleted. */ else{ replyData.Write(line,1,line.Length-1); } } else{ replyData.Write(line,0,line.Length); } } // write enc stuff here miceli replyData.Write(crlf,0,crlf.Length); // Read next line line = r.ReadLine(); } replyData.Position = 0; return replyData; }
/// <summary> /// Scans invalid CR or LF combination in stream. Returns true if contains invalid CR or LF combination. /// </summary> /// <param name="strm">Stream which to check.</param> /// <returns>Returns true if contains invalid CR or LF combination.</returns> public static bool ScanInvalid_CR_or_LF(Stream strm) { StreamLineReader lineReader = new StreamLineReader(strm); byte[] line = lineReader.ReadLine(); while(line != null){ foreach(byte b in line){ // Contains CR or LF. It cannot conatian such sumbols, because CR must be paired with LF // and we currently reading lines with CRLF combination. if(b == 10 || b == 13){ return true; } } line = lineReader.ReadLine(); } return false; }
/// <summary> /// Sets specified message flags for specified user. /// </summary> /// <param name="userID">User ID.</param> /// <param name="folder">Folder.</param> /// <param name="uid">Message UID.</param> /// <param name="flags">Message flags.</param> public static void SetFlags(string userID,string folder,int uid,IMAP_MessageFlags flags) { using(FileStream fs = GetFlagsFile(userID,folder)){ StreamLineReader r = new StreamLineReader(fs); long pos = fs.Position; string line = r.ReadLineString(); while(line != null){ // Skip comment lines if(!line.StartsWith("#")){ string[] userID_uid_flags = line.Split(' '); // Update user message flags if(userID_uid_flags[0] == userID && Convert.ToInt32(userID_uid_flags[1]) == uid){ fs.Position = pos; byte[] record1 = System.Text.Encoding.ASCII.GetBytes(userID + " " + uid.ToString("d10") + " " + ((int)flags).ToString("d4") + "\r\n"); fs.Write(record1,0,record1.Length); return; } } pos = fs.Position; line = r.ReadLineString(); } // If we reach here, then specified user has no flags for specified message, add new record. byte[] record = System.Text.Encoding.ASCII.GetBytes(userID + " " + uid.ToString("d10") + " " + ((int)flags).ToString("d4") + "\r\n"); fs.Write(record,0,record.Length); } }
/// <summary> /// Adds column to db file. /// </summary> /// <param name="column"></param> internal void AddColumn(LDB_DataColumn column) { // Find free column data area // Set position over version, free data pages count and data page data area size m_pDbFile.Position = 68; long freeColumnPosition = -1; StreamLineReader r = new StreamLineReader(m_pDbFile); // Loop all columns data areas, see it there any free left for(int i=0;i<100;i++){ byte[] columnInfo = r.ReadLine(); if(columnInfo == null){ throw new Exception("Invalid columns data area length !"); } // We found unused column data area if(columnInfo[0] == '\0'){ freeColumnPosition = m_pDbFile.Position; break; } } m_FilePosition = m_pDbFile.Position; if(freeColumnPosition != -1){ // TODO: If there is data ??? // Move to row start SetFilePosition(GetFilePosition() - 102); // Store column byte[] columnData = column.ToColumnInfo(); WriteToFile(columnData,0,columnData.Length); } else{ throw new Exception("Couldn't find free column space ! "); } }
/// <summary> /// Opens specified data file. /// </summary> /// <param name="fileName">File name.</param> /// <param name="waitTime">If data base file is exclusively locked, then how many seconds to wait file to unlock before raising a error.</param> public void Open(string fileName,int waitTime) { DateTime lockExpireTime = DateTime.Now.AddSeconds(waitTime); while(true){ try{ m_pDbFile = File.Open(fileName,FileMode.Open,FileAccess.ReadWrite,FileShare.ReadWrite); break; } catch(IOException x){ // Make this because to get rid of "The variable 'x' is declared but never used" string dummy = x.Message; System.Threading.Thread.Sleep(15); // Lock wait time timed out if(DateTime.Now > lockExpireTime){ throw new Exception("Database file is locked and lock wait time expired !"); } } } /* Table structure: 50 bytes - version 2 bytes - CRLF 8 bytes - free datapages count 2 bytes - CRLF 4 bytes - datapage data area size 2 bytes - CRLF 100 x 500 bytes - 100 columns info store 2 bytes - CRLF ... data pages */ m_DbFileName = fileName; StreamLineReader r = new StreamLineReader(m_pDbFile); // TODO: check if LDB file // Read version line (50 bytes + CRLF) byte[] version = r.ReadLine(); // Skip free data pages count byte[] freeDataPagesCount = new byte[10]; m_pDbFile.Read(freeDataPagesCount,0,freeDataPagesCount.Length); // 4 bytes datapage data area size + CRLF byte[] dataPageDataAreaSize = new byte[6]; m_pDbFile.Read(dataPageDataAreaSize,0,dataPageDataAreaSize.Length); m_DataPageDataAreaSize = ldb_Utils.ByteToInt(dataPageDataAreaSize,0); // Read 100 column lines (500 + CRLF bytes each) for(int i=0;i<100;i++){ byte[] columnInfo = r.ReadLine(); if(columnInfo == null){ throw new Exception("Invalid columns data area length !"); } if(columnInfo[0] != '\0'){ m_pColumns.Parse(columnInfo); } } // Header terminator \0 m_pDbFile.Position++; // No we have rows start offset m_DatapagesStartOffset = m_pDbFile.Position; // Store file length and position m_FileLength = m_pDbFile.Length; m_FilePosition = m_pDbFile.Position; }