+ // FIXME: untested. Do we really want to duplicate all the old headers???
+ public Message reply(Fountain body, Address from, boolean includeReInSubject) throws Malformed {
+ return reply(new String[0], body, from, includeReInSubject);
+ }
+ public Message reply(String[] keyval, Fountain body, Address envelopeFrom, boolean includeReInSubject) throws Malformed {
+ Address to = null;
+ if (to==null) to = Address.parse(headers.get("reply-to"));
+ if (to==null) to = Address.parse(headers.get("from"));
+ if (to==null) to = this.envelopeFrom;
+ if (to==null) throw new Malformed("cannot reply to a message without a return address");
+ String references = headers.get("references");
+ String subject = this.subject;
+ if (includeReInSubject && subject!=null && !subject.toLowerCase().trim().startsWith("re:"))
+ subject = "Re: "+subject;
+ Headers h = new Headers(new Headers(new String[] {
+ "To", to.toString(true),
+ "Message-Id", generateFreshMessageId(),
+ "Date", new Date()+"" /*FIXME!!!*/,
+ "Subject", subject,
+ "In-Reply-To", messageid,
+ "References", messageid + (references==null?"":(" "+references))
+ }), keyval);
+ return newMessageFromHeadersAndBody(h, body, from, to);
+ }
+
+ // this is belived to be compliant with QSBMF (http://cr.yp.to/proto/qsbmf.txt)
+ public Message bounce(String reason) {
+ if (envelopeFrom==null || envelopeFrom.toString().equals("")) return null;
+
+ // FIXME: limit bounce body size
+ // FIXME: include headers from bounced message
+ Log.warn(Message.class, "bouncing message due to: " + reason);
+ Headers h = new Headers(headers, new String[] {
+ "Envelope-To", envelopeFrom.toString(),
+ "Return-Path", "<>",
+ "From", "MAILER-DAEMON <>",
+ "To", envelopeFrom.toString(),
+ "Subject", "failure notice"
+ });
+
+ String error =
+ "\r\n"+
+ "Hi. This is the Ibex Mail Server. I'm afraid I wasn't able to deliver\r\n"+
+ "your message to the following addresses. This is a permanent error;\r\n"+
+ "I've given up. Sorry it didn't work out\r\n."+
+ "\r\n"+
+ "<"+envelopeTo.toString()+">:\r\n"+
+ reason+"\r\n"+
+ "\r\n"+
+ "--- Below this line is a copy of the message.\r\n"+
+ "\r\n";
+
+ try {
+ return newMessage(Fountain.Util.concat(h, Fountain.Util.create(error), getBody()));
+ } catch (Message.Malformed e) {
+ Log.error(this, "caught Message.Malformed in Message.bounce(); this should never happen");
+ Log.error(this, e);
+ return null;
+ }
+ }
+
+ public String toString() { throw new RuntimeException("Message.toString() called"); }
+ public final String summary() { return "[" + envelopeFrom + " -> " + envelopeTo + "] " + subject; }
+
+ public static class Malformed extends MailException { public Malformed(String s) { super(s); } }
+
+ /** reads an SMTP-style dot-escaped message */
+ static Message readDotEncodedMessage(Stream conn) {
+ StringBuffer buf = new StringBuffer();
+ while(true) {
+ String s = conn.readln();
+ if (s == null) throw new RuntimeException("connection closed");
+ if (s.equals(".")) break;
+ if (s.startsWith(".")) s = s.substring(1);
+ buf.append(s);
+ buf.append("\r\n");
+ }
+ return Message.newMessage(new Fountain.StringFountain(buf.toString()));
+ }