Merging from head
[beagle.git] / Util / camel.cs
blobd499207e37812fb6ceec4787639b1b7ddd9ed9ca
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 override string ToString ()
69 if (header == null)
70 return "No header read";
71 else
72 return header.ToString ();
75 public IEnumerator GetEnumerator () {
76 return new SummaryEnumerator (this);
79 protected abstract SummaryHeader ReadHeader (FileStream f);
80 protected abstract MessageInfo ReadMessageInfo (FileStream f);
82 private class SummaryEnumerator : IEnumerator, IDisposable {
83 FileStream f = null;
84 SummaryHeader header;
85 Summary s;
86 int index;
87 MessageInfo info;
88 public SummaryEnumerator (Summary s) {
89 this.s = s;
90 index = -1;
93 public void Dispose ()
95 if (f != null) {
96 f.Close ();
97 f = null;
99 GC.SuppressFinalize (this);
102 ~SummaryEnumerator ()
104 if (f != null) {
105 f.Close ();
109 public bool MoveNext () {
110 ++index;
111 if (index == 0) {
112 f = File.OpenRead (s.filename);
113 header = s.ReadHeader (f);
116 if (index >= header.count) {
117 if (f != null) {
118 f.Close ();
119 f = null;
121 return false;
122 } else {
123 info = null;
124 while (info == null && index < header.count) {
125 try {
126 info = s.ReadMessageInfo (f);
127 } catch (Exception e) {
128 Log.Warn ("Skipping bogus message " +
129 "[file={0}, index={1}, error={2}]",
130 s.filename, index, e.ToString());
132 info = null;
133 ++index;
137 return (info != null);
141 public object Current {
142 get { return info; }
145 public void Reset ()
147 f.Close ();
148 f = null;
149 index = -1;
154 public class MBoxSummary: Summary {
155 public MBoxSummary ()
159 protected override SummaryHeader ReadHeader (FileStream f)
161 return new MBoxSummaryHeader (f);
164 protected override MessageInfo ReadMessageInfo (FileStream f)
166 return new MBoxMessageInfo (f);
170 public class ImapSummary: Summary {
171 public ImapSummary ()
175 protected override SummaryHeader ReadHeader (FileStream f)
177 return new ImapSummaryHeader (f);
180 protected override MessageInfo ReadMessageInfo (FileStream f)
182 return new ImapMessageInfo (f, true);
186 public class Imap4Summary : Summary {
187 public Imap4Summary ()
191 protected override SummaryHeader ReadHeader (FileStream f)
193 return new ImapSummaryHeader (f);
196 protected override MessageInfo ReadMessageInfo (FileStream f)
198 return new ImapMessageInfo (f, false);
202 public class MessageInfo {
203 public string uid, subject, from, to, cc, mlist;
204 public uint size, flags;
205 public DateTime sent, received;
207 private void SkipContentInfo (FileStream f)
209 Decode.SkipToken (f); // type
210 Decode.SkipToken (f); // subtype
211 uint count = Decode.UInt (f); // count
212 for (int i = 0; i < count; ++i) {
213 Decode.SkipToken (f); // name
214 Decode.SkipToken (f); // value
216 Decode.SkipToken (f); // id
217 Decode.SkipToken (f); // description
218 Decode.SkipToken (f); // encoding
219 Decode.UInt (f); // size
221 count = Decode.UInt (f); // child count
222 for (int i = 0; i < count; ++i) // recursively skip children
223 SkipContentInfo (f);
226 public MessageInfo (FileStream f)
228 uid = Decode.String (f);
229 flags = Decode.UInt (f);
230 size = Decode.UInt (f);
231 sent = Decode.Time (f);
232 received = Decode.Time (f);
233 subject = Decode.String (f);
234 from = Decode.String (f);
235 to = Decode.String (f);
236 cc = Decode.String (f);
237 mlist = Decode.String (f);
239 Decode.SkipFixedInt (f);
240 Decode.SkipFixedInt (f);
242 uint count;
244 // references
245 count = Decode.UInt (f);
246 if (count > 0) {
247 for (int i = 0; i < count; i++) {
248 Decode.SkipFixedInt (f);
249 Decode.SkipFixedInt (f);
253 // user flags
254 count = Decode.UInt (f);
255 if (count > 0) {
256 for (int i = 0; i < count; i++) {
257 Decode.SkipString (f);
261 // user tags
262 count = Decode.UInt (f);
263 if (count > 0){
264 for (int i = 0; i < count; i++){
265 Decode.SkipString (f);
266 Decode.SkipString (f);
270 // FIXME: How do we know if there is content info in there?
271 // SkipContentInfo (f);
274 public override string ToString ()
276 return String.Format ("From: {0}\nTo: {1}\nSubject: {2}\nUID: {3}\n", from, to, subject, uid);
279 public DateTime SentDate {
280 get { return sent; }
283 public DateTime ReceivedDate {
284 get { return received; }
287 private bool CheckFlag (CamelFlags test)
289 return (flags & (uint) test) == (uint) test;
292 public bool IsAnswered {
293 get { return CheckFlag (CamelFlags.Answered); }
296 public bool IsDeleted {
297 get { return CheckFlag (CamelFlags.Deleted); }
300 public bool IsDraft {
301 get { return CheckFlag (CamelFlags.Draft); }
304 public bool IsFlagged {
305 get { return CheckFlag (CamelFlags.Flagged); }
308 public bool IsSeen {
309 get { return CheckFlag (CamelFlags.Seen); }
312 public bool HasAttachments {
313 get { return CheckFlag (CamelFlags.Attachments); }
316 public bool IsAnsweredAll {
317 get { return CheckFlag (CamelFlags.AnsweredAll); }
320 public bool IsJunk {
321 get { return CheckFlag (CamelFlags.Junk); }
324 public bool IsSecure {
325 get { return CheckFlag (CamelFlags.Secure); }
329 public class MBoxMessageInfo : MessageInfo {
330 public uint from_pos;
332 public MBoxMessageInfo (FileStream f) : base (f)
334 from_pos = Decode.Offset (f);
337 public override string ToString ()
339 return String.Format ("From: {0}\nTo: {1}\nSubject: {2}\nPos: {3} Size: {4}\n", from, to, subject,
340 from_pos, size);
344 public class ImapMessageInfo : MessageInfo {
345 public uint server_flags;
347 public ImapMessageInfo (FileStream f, bool content_info_load) : base (f)
349 server_flags = Decode.UInt (f);
351 if (content_info_load)
352 PerformContentInfoLoad (f);
355 public override string ToString ()
357 return String.Format ("From: {0}\nTo: {1}\nSubject: {2}\nSize: {3}\n", from, to, subject, size);
360 private bool PerformContentInfoLoad (FileStream f)
362 bool ci = ContentInfoLoad (f);
363 if (!ci)
364 return false;
366 uint count = Decode.UInt (f);
368 if (count > 500) {
369 return false;
372 for (int i = 0; i < count; i++) {
374 bool part = PerformContentInfoLoad (f);
375 if (!part)
376 throw new Exception ();
378 return true;
381 private bool ContentInfoLoad (FileStream f)
383 if (f.ReadByte () == 0)
384 return true;
386 // type
387 Decode.SkipToken (f);
388 // subtype
389 Decode.SkipToken (f);
391 uint count;
392 count = Decode.UInt (f);
393 if (count > 500)
394 return false;
396 for (int i = 0; i < count; i++) {
397 // Name
398 Decode.SkipToken (f);
399 // Value
400 Decode.SkipToken (f);
403 // id
404 Decode.SkipToken (f);
406 // description
407 Decode.SkipToken (f);
409 // encoding
410 Decode.SkipToken (f);
412 // size
413 Decode.UInt (f);
414 return true;
418 public class SummaryHeader {
419 public int version;
420 public int flags;
421 public int nextuid;
422 public DateTime time;
423 public int count;
424 public int unread;
425 public int deleted;
426 public int junk;
428 public SummaryHeader (FileStream f)
430 bool legacy;
432 version = Decode.FixedInt (f);
434 if (version > 0xff && (version & 0xff) < 12)
435 throw new Exception ("Summary header version too low");
437 if (version < 0x100 && version >= 13)
438 legacy = false;
439 else
440 legacy = true;
442 flags = Decode.FixedInt (f);
443 nextuid = Decode.FixedInt (f);
444 time = Decode.Time (f);
445 count = Decode.FixedInt (f);
447 if (!legacy) {
448 unread = Decode.FixedInt (f);
449 deleted = Decode.FixedInt (f);
450 junk = Decode.FixedInt (f);
454 public override string ToString ()
456 return String.Format ("version={0} flags={1} nextuid={2} time={3} count={4} unread={5} deleted={6} junk={7}",
457 version, flags, nextuid, time, count, unread, deleted, junk);
461 public class MBoxSummaryHeader : SummaryHeader {
462 public int local_version;
463 public int mbox_version;
464 public int folder_size;
466 public MBoxSummaryHeader (FileStream f) : base (f)
468 local_version = Decode.FixedInt (f);
469 mbox_version = Decode.FixedInt (f);
470 folder_size = Decode.FixedInt (f);
473 public override string ToString ()
475 return String.Format ("{0} local_version={1} mbox_version={2} folder_size={3}",
476 base.ToString (), local_version, mbox_version, folder_size);
480 public class ImapSummaryHeader : SummaryHeader {
481 private int imap_version;
483 public ImapSummaryHeader (FileStream f) : base (f)
485 // Check for legacy version
486 if (base.version != 0x30c) { // 780
487 imap_version = Decode.FixedInt (f);
489 if (imap_version < 0)
490 throw new Exception ("IMAP summary version too low");
492 // Right now we only support summary versions 1 through 3
493 if (imap_version > 3)
494 throw new Exception (String.Format ("Reported summary version ({0}) is too new", imap_version));
496 if (imap_version == 2)
497 Decode.FixedInt (f);
499 // validity
500 Decode.SkipFixedInt (f);
501 } else {
502 // validity
503 Decode.UInt (f);
507 public override string ToString ()
509 return String.Format ("{0} imap_version={1}", base.ToString (), imap_version);
513 public class Decode {
514 static Encoding e = Encoding.UTF8;
515 static long UnixBaseTicks;
517 static Decode ()
519 //UnixBaseTicks = DateTimeUtil.UnixToDateTimeUtc (0).Ticks;
520 UnixBaseTicks = new DateTime (1970, 1, 1, 0, 0, 0).Ticks;
523 public static string Token (FileStream f)
525 int len = (int) UInt (f);
526 if (len < 32) {
527 if (len <= 0)
528 return "NULL";
530 // Ok, this is a token from the list, we can ignore it
531 return "token_from_list";
532 } else if (len < 0 || len > 10240) {
533 throw new Exception ();
534 } else {
535 len -= 32;
536 byte [] buffer = new byte [len];
537 f.Read (buffer, 0, (int) len);
538 return new System.String (e.GetChars (buffer, 0, len));
542 public static void SkipToken (FileStream f)
544 int len = (int) UInt (f);
545 len -= 32;
546 if (len > 0)
547 f.Seek (len, SeekOrigin.Current);
550 public static string String (FileStream f)
552 int len = (int) UInt (f);
553 len--;
555 if (len < 0 || len > 65535)
556 throw new Exception ();
557 byte [] buffer = new byte [len];
558 f.Read (buffer, 0, (int) len);
559 return new System.String (e.GetChars (buffer, 0, len));
562 public static void SkipString (FileStream f)
564 int len = (int) UInt (f);
565 --len;
566 if (len > 0)
567 f.Seek (len, SeekOrigin.Current);
570 public static uint UInt (FileStream f)
572 uint value = 0;
573 int v;
575 while (((v = f.ReadByte ()) & 0x80) == 0 && v != -1) {
576 value |= (byte) v;
577 value <<= 7;
580 if (v == -1)
581 throw new Exception ("Unexpected end of file");
583 return value | ((byte)(v & 0x7f));
586 public static int FixedInt (FileStream f)
588 byte [] b = new byte [4];
590 f.Read (b, 0, 4);
592 return (b [0] << 24) | (b [1] << 16) | (b [2] << 8) | b [3];
595 public static void SkipFixedInt (FileStream f)
597 f.Seek (4, SeekOrigin.Current);
600 public static DateTime Time (FileStream f)
602 long seconds = 0;
604 // FIXME: Is it safe to assume that sizeof (time_t) == IntPtr.Size? Probably not.
605 for (int i = IntPtr.Size - 1; i >= 0; i--) {
606 int v = f.ReadByte ();
608 if (v == -1)
609 throw new Exception ("Unexpected end of file");
611 seconds |= (uint) v << (i * 8);
614 if (seconds == 0)
615 return new DateTime (0);
617 return new DateTime (UnixBaseTicks).AddSeconds (seconds);
620 public static uint Offset (FileStream f)
622 byte [] b = new byte [4];
624 f.Read (b, 0, 4);
626 return (uint)((b [0] << 24) | (b [1] << 16) | (b [2] << 8) | b [3]);
631 #if false
632 class Test {
633 public static void Main (string [] args)
635 string file;
637 if (args.Length == 0)
638 file = "./summary";
639 else
640 file = args [0];
642 Summary s = Summary.LoadImapSummary (file);
643 Console.WriteLine (s);
644 Console.WriteLine ();
645 foreach (MessageInfo m in s) {
646 Console.WriteLine(m);
652 #endif