2005-08-16 Gabor Kelemen <kelemeng@gnome.hu>
[beagle.git] / Util / camel.cs
blob14f9eb0a3f6fba75ffd13a19b5b12df47c467a09
1 //
2 // camel.cs: Parser for Evolution mbox summary files.
3 //
4 // Authors:
5 // Miguel de Icaza <miguel@ximian.com>
6 //
7 // Imap support by Erik Bågfors <erik@bagfors.nu>
8 //
10 using System.IO;
11 using System;
12 using System.Collections;
13 using System.Globalization;
14 using System.Text;
16 namespace Beagle.Util {
17 namespace Camel {
19 public enum CamelFlags : uint {
20 Answered = 1 << 0,
21 Deleted = 1 << 1,
22 Draft = 1 << 2,
23 Flagged = 1 << 3,
24 Seen = 1 << 4,
25 Attachments = 1 << 5,
26 AnsweredAll = 1 << 6,
27 Junk = 1 << 7,
28 Secure = 1 << 8
31 public abstract class Summary : IEnumerable {
32 public SummaryHeader header;
33 internal string filename;
35 public static Summary LoadMBoxSummary (string file)
37 Summary s = new MBoxSummary ();
38 s.Load (file);
40 return s;
43 public static Summary LoadImapSummary (string file)
45 Summary s = new ImapSummary ();
46 s.Load (file);
48 return s;
51 public static Summary LoadImap4Summary (string file)
53 Summary s = new Imap4Summary ();
54 s.Load (file);
56 return s;
59 private void Load (string file)
61 this.filename = file;
62 FileStream f = File.OpenRead (file);
63 this.header = this.ReadHeader (f);
64 f.Close ();
67 public IEnumerator GetEnumerator () {
68 return new SummaryEnumerator (this);
71 protected abstract SummaryHeader ReadHeader (FileStream f);
72 protected abstract MessageInfo ReadMessageInfo (FileStream f);
74 private class SummaryEnumerator : IEnumerator, IDisposable {
75 FileStream f = null;
76 SummaryHeader header;
77 Summary s;
78 int index;
79 MessageInfo info;
80 public SummaryEnumerator (Summary s) {
81 this.s = s;
82 index = -1;
85 public void Dispose ()
87 if (f != null) {
88 f.Close ();
89 f = null;
91 GC.SuppressFinalize (this);
94 ~SummaryEnumerator ()
96 if (f != null) {
97 f.Close ();
101 public bool MoveNext () {
102 ++index;
103 if (index == 0) {
104 f = File.OpenRead (s.filename);
105 header = s.ReadHeader (f);
108 if (index >= header.count) {
109 if (f != null) {
110 f.Close ();
111 f = null;
113 return false;
114 } else {
115 info = null;
116 while (info == null && index < header.count) {
117 try {
118 info = s.ReadMessageInfo (f);
119 } catch (Exception e) {
120 Logger.Log.Warn ("Skipping bogus message " +
121 "[file={0}, index={1}, error={2}]",
122 s.filename, index, e.ToString());
124 info = null;
125 ++index;
129 return (info != null);
133 public object Current {
134 get { return info; }
137 public void Reset ()
139 f.Close ();
140 f = null;
141 index = -1;
146 public class MBoxSummary: Summary {
147 public MBoxSummary ()
151 protected override SummaryHeader ReadHeader (FileStream f)
153 return new MBoxSummaryHeader (f);
156 protected override MessageInfo ReadMessageInfo (FileStream f)
158 return new MBoxMessageInfo (f);
162 public class ImapSummary: Summary {
163 public ImapSummary ()
167 protected override SummaryHeader ReadHeader (FileStream f)
169 return new ImapSummaryHeader (f);
172 protected override MessageInfo ReadMessageInfo (FileStream f)
174 return new ImapMessageInfo (f, true);
178 public class Imap4Summary : Summary {
179 public Imap4Summary ()
183 protected override SummaryHeader ReadHeader (FileStream f)
185 return new ImapSummaryHeader (f);
188 protected override MessageInfo ReadMessageInfo (FileStream f)
190 return new ImapMessageInfo (f, false);
194 public class MessageInfo {
195 public string uid, subject, from, to, cc, mlist;
196 public uint size, flags;
197 public DateTime sent, received;
199 private void SkipContentInfo (FileStream f)
201 Decode.SkipToken (f); // type
202 Decode.SkipToken (f); // subtype
203 uint count = Decode.UInt (f); // count
204 for (int i = 0; i < count; ++i) {
205 Decode.SkipToken (f); // name
206 Decode.SkipToken (f); // value
208 Decode.SkipToken (f); // id
209 Decode.SkipToken (f); // description
210 Decode.SkipToken (f); // encoding
211 Decode.UInt (f); // size
213 count = Decode.UInt (f); // child count
214 for (int i = 0; i < count; ++i) // recursively skip children
215 SkipContentInfo (f);
218 public MessageInfo (FileStream f)
220 uid = Decode.String (f);
221 flags = Decode.UInt (f);
222 size = Decode.UInt (f);
223 sent = Decode.Time (f);
224 received = Decode.Time (f);
225 subject = Decode.String (f);
226 from = Decode.String (f);
227 to = Decode.String (f);
228 cc = Decode.String (f);
229 mlist = Decode.String (f);
231 Decode.SkipFixedInt (f);
232 Decode.SkipFixedInt (f);
234 uint count;
236 // references
237 count = Decode.UInt (f);
238 if (count > 0) {
239 for (int i = 0; i < count; i++) {
240 Decode.SkipFixedInt (f);
241 Decode.SkipFixedInt (f);
245 // user flags
246 count = Decode.UInt (f);
247 if (count > 0) {
248 for (int i = 0; i < count; i++) {
249 Decode.SkipString (f);
253 // user tags
254 count = Decode.UInt (f);
255 if (count > 0){
256 for (int i = 0; i < count; i++){
257 Decode.SkipString (f);
258 Decode.SkipString (f);
262 // FIXME: How do we know if there is content info in there?
263 // SkipContentInfo (f);
266 public override string ToString ()
268 return String.Format ("From: {0}\nTo: {1}\nSubject: {2}\nUID: {3}\n", from, to, subject, uid);
271 public DateTime Date {
272 get { return received.Ticks != 0 ? received : sent; }
275 private bool CheckFlag (CamelFlags test)
277 return (flags & (uint) test) == (uint) test;
280 public bool IsAnswered {
281 get { return CheckFlag (CamelFlags.Answered); }
284 public bool IsDeleted {
285 get { return CheckFlag (CamelFlags.Deleted); }
288 public bool IsDraft {
289 get { return CheckFlag (CamelFlags.Draft); }
292 public bool IsFlagged {
293 get { return CheckFlag (CamelFlags.Flagged); }
296 public bool IsSeen {
297 get { return CheckFlag (CamelFlags.Seen); }
300 public bool HasAttachments {
301 get { return CheckFlag (CamelFlags.Attachments); }
304 public bool IsAnsweredAll {
305 get { return CheckFlag (CamelFlags.AnsweredAll); }
308 public bool IsJunk {
309 get { return CheckFlag (CamelFlags.Junk); }
312 public bool IsSecure {
313 get { return CheckFlag (CamelFlags.Secure); }
317 public class MBoxMessageInfo : MessageInfo {
318 public uint from_pos;
320 public MBoxMessageInfo (FileStream f) : base (f)
322 from_pos = Decode.Offset (f);
325 public override string ToString ()
327 return String.Format ("From: {0}\nTo: {1}\nSubject: {2}\nPos: {3} Size: {4}\n", from, to, subject,
328 from_pos, size);
334 public class ImapMessageInfo : MessageInfo {
335 public uint server_flags;
337 public ImapMessageInfo (FileStream f, bool content_info_load) : base (f)
339 server_flags = Decode.UInt (f);
341 if (content_info_load)
342 PerformContentInfoLoad (f);
345 public override string ToString ()
347 return String.Format ("From: {0}\nTo: {1}\nSubject: {2}\nSize: {3}\n", from, to, subject, size);
350 private bool PerformContentInfoLoad (FileStream f)
352 bool ci = ContentInfoLoad (f);
353 if (!ci)
354 return false;
356 uint count = Decode.UInt (f);
357 if (count == -1 || count > 500) {
358 return false;
361 for (int i = 0; i < count; i++) {
363 bool part = PerformContentInfoLoad (f);
364 if (!part)
365 throw new Exception ();
367 return true;
370 private bool ContentInfoLoad (FileStream f)
372 if (f.ReadByte () == 0)
373 return true;
375 // type
376 Decode.SkipToken (f);
377 // subtype
378 Decode.SkipToken (f);
380 uint count;
381 count = Decode.UInt (f);
382 if (count == -1 || count > 500)
383 return false;
384 for (int i = 0; i < count; i++) {
385 // Name
386 Decode.SkipToken (f);
387 // Value
388 Decode.SkipToken (f);
391 // id
392 Decode.SkipToken (f);
394 // description
395 Decode.SkipToken (f);
397 // encoding
398 Decode.SkipToken (f);
400 // size
401 Decode.UInt (f);
402 return true;
406 public class SummaryHeader {
407 public int version;
408 public int flags;
409 public int nextuid;
410 public DateTime time;
411 public int count;
412 public int unread;
413 public int deleted;
414 public int junk;
416 public SummaryHeader (FileStream f)
418 bool legacy;
420 version = Decode.FixedInt (f);
422 if (version > 0xff && (version & 0xff) < 12)
423 throw new Exception ("Summary header version too low");
425 if (version < 0x100 && version >= 13)
426 legacy = false;
427 else
428 legacy = true;
430 flags = Decode.FixedInt (f);
431 nextuid = Decode.FixedInt (f);
432 time = Decode.Time (f);
433 count = Decode.FixedInt (f);
435 if (!legacy) {
436 unread = Decode.FixedInt (f);
437 deleted = Decode.FixedInt (f);
438 junk = Decode.FixedInt (f);
441 //Console.WriteLine ("V={0} ({1}) time={2}, count={3} unread={4} deleted={5} junk={6}", version, version & 0xff, time, count, unread, deleted, junk);
445 public class MBoxSummaryHeader : SummaryHeader {
446 public int local_version;
447 public int mbox_version;
448 public int folder_size;
450 public MBoxSummaryHeader (FileStream f) : base (f)
452 local_version = Decode.FixedInt (f);
453 mbox_version = Decode.FixedInt (f);
454 folder_size = Decode.FixedInt (f);
456 //Console.WriteLine ("local_version={0} mbox_version={1} folder_size={2}", local_version, mbox_version, folder_size);
460 public class ImapSummaryHeader : SummaryHeader {
462 public ImapSummaryHeader (FileStream f) : base (f)
464 // Check for legacy version
465 if (base.version != 0x30c) { // 780
466 int version = Decode.FixedInt (f);
468 //Console.WriteLine ("imap version={0}", version);
470 if (version < 0)
471 throw new Exception ("IMAP summary version too low");
473 // Right now we only support summary versions 1 through 3
474 if (version > 3)
475 throw new Exception (String.Format ("Reported summary version ({0}) is too new", version));
477 if (version == 2)
478 Decode.FixedInt (f);
480 // validity
481 Decode.SkipFixedInt (f);
482 } else {
483 // validity
484 Decode.UInt (f);
489 public class Decode {
490 static Encoding e = Encoding.UTF8;
491 static long UnixBaseTicks;
493 static Decode ()
495 UnixBaseTicks = new DateTime (1970, 1, 1, 0, 0, 0).Ticks;
498 public static string Token (FileStream f)
500 int len = (int) UInt (f);
501 if (len < 32) {
502 if (len <= 0)
503 return "NULL";
505 // Ok, this is a token from the list, we can ignore it
506 return "token_from_list";
507 } else if (len < 0 || len > 10240) {
508 throw new Exception ();
509 } else {
510 len -= 32;
511 byte [] buffer = new byte [len];
512 f.Read (buffer, 0, (int) len);
513 return new System.String (e.GetChars (buffer, 0, len));
517 public static void SkipToken (FileStream f)
519 int len = (int) UInt (f);
520 len -= 32;
521 if (len > 0)
522 f.Seek (len, SeekOrigin.Current);
525 public static string String (FileStream f)
527 int len = (int) UInt (f);
528 len--;
530 if (len < 0 || len > 65535)
531 throw new Exception ();
532 byte [] buffer = new byte [len];
533 f.Read (buffer, 0, (int) len);
534 return new System.String (e.GetChars (buffer, 0, len));
537 public static void SkipString (FileStream f)
539 int len = (int) UInt (f);
540 --len;
541 if (len > 0)
542 f.Seek (len, SeekOrigin.Current);
545 public static uint UInt (FileStream f)
547 uint value = 0;
548 int v;
550 while (((v = f.ReadByte ()) & 0x80) == 0 && v != -1){
551 value |= (byte) v;
552 value <<= 7;
554 return value | ((byte)(v & 0x7f));
557 public static int FixedInt (FileStream f)
559 byte [] b = new byte [4];
561 f.Read (b, 0, 4);
563 return (b [0] << 24) | (b [1] << 16) | (b [2] << 8) | b [3];
566 public static void SkipFixedInt (FileStream f)
568 f.Seek (4, SeekOrigin.Current);
571 public static DateTime Time (FileStream f)
573 long seconds = 0;
575 // FIXME: Is it safe to assume that sizeof (time_t) == IntPtr.Size? Probably not.
576 for (int i = IntPtr.Size - 1; i >= 0; i--) {
577 int v = f.ReadByte ();
579 seconds |= v << (i * 8);
582 if (seconds == 0)
583 return new DateTime (0);
585 return new DateTime (UnixBaseTicks).AddSeconds (seconds);
588 public static uint Offset (FileStream f)
590 byte [] b = new byte [4];
592 f.Read (b, 0, 4);
594 return (uint)((b [0] << 24) | (b [1] << 16) | (b [2] << 8) | b [3]);
598 #if false
599 class Test {
600 void Main (string [] args)
602 string file;
604 if (args.Length == 0)
605 file = "./summary";
606 else
607 file = args [0];
609 Summary s = Summary.LoadMBoxSummary (file);
610 foreach (MessageInfo m in s) {
611 Console.WriteLine(m);
617 #endif