it compiles!
[org.ibex.mail.git] / src / org / ibex / mail / Message.java
1 package org.ibex.mail;
2 import org.ibex.crypto.*;
3 import org.ibex.js.*;
4 import org.ibex.util.*;
5 import java.util.*;
6 import java.net.*;
7 import java.io.*;
8
9 // FIXME MIME: RFC2045, 2046, 2049
10 // NOTE: always use Win32 line endings
11 // hard line limit: 998 chars
12 // soft line limit (suggested): 78 chars
13 // header fields: ascii 33-126 (but no colon)
14 // field body: anything ASCII except CRLF
15 // folded headers: can insert CRLF anywhere that whitespace appears (before the whitespace)
16 // body needs CRLF; one or the other alone is not acceptable
17 // date/time parsing: see 3.3
18
19 // FEATURE: PGP-signature-parsing
20 // FEATURE: mailing list header parsing
21 // FEATURE: delivery status notification (and the sneaky variety)
22 // FEATURE: threading as in http://www.jwz.org/doc/threading.html
23
24 public class Message extends JSReflection {
25
26     public final String allHeaders;   // pristine headers
27     public final Hashtable headers;   // hash of headers (not including resent's and traces)
28     public final String body;         // entire body
29
30     // parsed header fields
31     public final Date date;
32     public final Address to;
33     public final Address from;        // if multiple From entries, this is sender
34     public final Address replyto;     // if none provided, this is equal to sender
35     public final String subject;
36     public final String messageid;
37     public final Address[] cc;
38     public final Address[] bcc;
39     public final Hashtable[] resent;
40     public final Trace[] traces;
41
42     // envelope fields
43     public final Address envelopeFrom;
44     public final Address[] envelopeTo;
45
46     public void dump(OutputStream os) {
47         Log.error(this, "not implemented");
48     }
49
50     public static class StoredMessage extends Message {
51         public StoredMessage(/*ReadStream rs*/BufferedReader rs, boolean dotTerminatedLikeSMTP) throws IOException {
52             super(rs, dotTerminatedLikeSMTP); uid = -1; }
53         public final int uid;
54         public boolean deleted = false;
55         public boolean read = false;
56         public boolean answered = false;
57         public String dumpStoredForm() { throw new Error("StoredMessage.dumpStoredForm() not implemented"); };
58         public static StoredMessage undump(InputStream os) {
59             Log.error(StoredMessage.class, "not implemented");
60             return null;
61         }
62     }
63
64     public static class Address extends JSReflection {
65         public String coerceToString() {
66             if (description == null || description.equals("")) return user +"@"+ host;
67             return description + " " + "<" + user +"@"+ host + ">";
68         }
69         public final String user;
70         public final String host;
71         public final String description;
72         public Address(String user, String host, String description) {
73             this.user = user; this.host = host; this.description = description;
74         }
75         public Address(String s) {
76             s = s.trim();
77             String descrip = null;
78             if (s.indexOf('<') != -1) {
79                 if (s.indexOf('>') == -1) { /* FIXME */ }
80                 descrip = s.substring(0, s.indexOf('<')) + s.substring(s.indexOf('>') + 1);
81                 s = s.substring(s.indexOf('<') + 1, s.indexOf('>'));
82             }
83             if (s.indexOf('@') == -1) { /* FIXME */ }
84             description = descrip;
85             user = s.substring(0, s.indexOf('@'));
86             host = s.substring(s.indexOf('@')+1);
87         }
88     }
89
90     public class Trace {
91         String returnPath = null;
92         Element[] elements;
93         public class Element {
94             // FIXME final
95             String fromDomain;
96             String fromIP;
97             String toDomain;
98             String forWhom;
99             Date date;
100         }
101     }
102
103     // FIXME: support dotTerminatedLikeSMTP
104     public Message(/*ReadStream rs*/BufferedReader rs, boolean dotTerminatedLikeSMTP) throws IOException {
105         String key = null;
106         StringBuffer all = new StringBuffer();
107         String lastKey = null;
108         replyto = null;
109         subject = null;
110         messageid = null;
111         cc = null;
112         bcc = null;
113         resent = null;
114         traces = null;
115         envelopeFrom = null;
116         envelopeTo = null;
117
118         headers = new Hashtable();
119         date = null; // FIXME
120         to = null;
121         from = null;
122         for(String s = rs.readLine(); s != null && !s.equals(""); s = rs.readLine()) {
123             all.append(s);
124             all.append("\r\n");
125             if (Character.isSpace(s.charAt(0))) {
126                 if (lastKey == null) { /* FIXME */ }
127                 headers.put(lastKey, headers.get(lastKey) + s);
128                 continue;
129             }
130             if (s.indexOf(':') == -1) { /* FIXME */ }
131
132             key = s.substring(0, s.indexOf(':'));
133             String val = s.substring(0, s.indexOf(':') + 1);
134             while(Character.isSpace(val.charAt(0))) val = val.substring(1);
135
136             if (headers.get(key) != null)
137                 if (key.startsWith("Resent-")) {
138                     // FIXME: multi-resent headers
139                 } else if (key.startsWith("Return-Path:")) {
140                     // FIXME: parse traces, see RFC2821, section 4.4                    
141                 } else if (key.startsWith("Recieved:")) {
142                     // FIXME: parse traces, see RFC2821, section 4.4                    
143                 } else {
144                     // just append it to the previous one; valid for Comments/Keywords
145                     val = headers.get(key) + " " + val;
146                 } 
147             
148             headers.put(key, val);
149         }
150         allHeaders = all.toString();
151         StringBuffer body = new StringBuffer();
152         for(String s = rs.readLine(); s != null && !s.equals(""); s = rs.readLine()) body.append(s);
153         this.body = body.toString();
154     }
155
156     // http://www.jwz.org/doc/mid.html
157     public static String generateFreshMessageId() {
158         StringBuffer ret = new StringBuffer();
159         ret.append('<');
160         ret.append(Base36.encode(System.currentTimeMillis()));
161         ret.append('.');
162         ret.append(Base36.encode(random.nextLong()));
163         ret.append('.');
164         try { ret.append(InetAddress.getLocalHost().getHostName()); } catch (UnknownHostException e) { /* DELIBERATE */ }
165         ret.append('>');
166         return ret.toString();
167     }
168
169     private static final Random random = new Random();
170 }