From 7522062c47ac41e9275c7c9ee22d308e6b0d1641 Mon Sep 17 00:00:00 2001 From: adam Date: Mon, 9 Jul 2007 04:56:52 +0000 Subject: [PATCH] revamp NNTP support darcs-hash:20070709045652-5007d-02f474a68f9a0d4b874710978ab35d7bb9e4fddc.gz --- src/org/ibex/mail/NNTP.java | 227 ++++++++++++++++++++----------------------- 1 file changed, 106 insertions(+), 121 deletions(-) diff --git a/src/org/ibex/mail/NNTP.java b/src/org/ibex/mail/NNTP.java index d758d73..f8bd76d 100644 --- a/src/org/ibex/mail/NNTP.java +++ b/src/org/ibex/mail/NNTP.java @@ -8,7 +8,11 @@ // Xref header // LIST EXTENSIONS is probably incomplete // pull mode (ie suck) -// control message processing? +// FEATURE: control message processing? +// cancel (do not forward if I am unable to cancel locally) +// ihave/sendme: do not support +// newgroup [moderated] -- body of message is a description of the group +// rmgroup package org.ibex.mail; import org.ibex.util.*; @@ -24,13 +28,26 @@ import java.text.*; /** NNTP send/recieve */ public class NNTP { - public static final DateFormat dateFormat = new SimpleDateFormat("yyyyMMDDhhmmss"); + public static final DateFormat serverDateFormat = new SimpleDateFormat("yyyyMMDDhhmmss"); + public static final DateFormat shortNewNewsDateFormat = new SimpleDateFormat("yyMMDD HHMMSS"); + public static final DateFormat longNewNewsDateFormat = new SimpleDateFormat("yyyyMMDD HHMMSS"); + static { + serverDateFormat.setTimeZone(new SimpleTimeZone(SimpleTimeZone.UTC_TIME, "GMT")); + shortNewNewsDateFormat.setTimeZone(new SimpleTimeZone(SimpleTimeZone.UTC_TIME, "GMT")); + longNewNewsDateFormat.setTimeZone(new SimpleTimeZone(SimpleTimeZone.UTC_TIME, "GMT")); + } public static class No extends RuntimeException { int code = 400; } // 4xx response codes public static class Bad extends RuntimeException { int code = 500; public Bad(String s) { super(s); } } // 5xx response codes public static class Group { - public Group(String n, boolean p, int f, int l, int c) { this.name=n;this.post=p;this.first=f;this.last=l;this.count=c;} + public Group(String n, boolean p, int f, int l, int c) { + this.name=n; + this.post=p; + this.first=f; + this.last=l; + this.count=c; + } public final String name; // case insensitive public final boolean post; public final int first; @@ -44,51 +61,63 @@ public class NNTP { public final Message message; } + + /** + * The API exposed by an NNTP server; remote NNTP servers appear + * as instances of this class, and implementations of this class + * may be exported as NNTP servers. + */ public static interface Server { public Group group(String s); public boolean ihave(String messageid); + public boolean want(String messageid); public Article next(); public Article last(); public boolean postok(); - public void post(Message m) throws IOException; + public void post(Message m); public Article article(String messageid, boolean head, boolean body); public Article article(int messagenum, boolean head, boolean body); public Group[] list(); - public Group[] newgroups(Date d, String[] distributions); - public String[] newnews(String[] groups, Date d, String[] distributions); + public Group[] newgroups(Date d); + public String[] newnews(String[] groups, Date d); } - public static class MailboxWrapper implements Server { + public static class MailboxServer implements Server { private final MailTree root; - private Mailbox current; - private int ptr = 0; - private boolean post; - public MailboxWrapper(MailTree root) { this(root, false); } - public MailboxWrapper(MailTree root, boolean post) { this.root = root; this.post = post; } - public boolean postok() { return post; } - public void post(Message m) throws IOException { current.post(m); } + private Mailbox current = null; + private int currentMessageNumber = 0; + public MailboxServer(MailTree root) { this.root = root; } + public boolean postok() { return true; } + public void post(Message m) { current.post(m); } public Group group(String s) { - ptr = 0; - Group g = getgroup(s); - if (g==null) return null; - setgroup(s); - return g; + currentMessageNumber = 0; + MailTree ncurrent = resolve(s); + if (ncurrent == null) return null; + current = ncurrent.getMailbox(); + return new Group(s, true, 1, current.count(Query.all()), current.count(Query.all())); + } + public boolean ihave(String messageid) { /* FEATURE */ return false; } + public boolean want(String messageid) { /* FEATURE */ return true; } + public Group[] newgroups(Date d) { /* FEATURE */ return new Group[] { }; } + public Article next() { return article(currentMessageNumber++, false, false); } + public Article last() { return article(currentMessageNumber--, false, false); } + public String[] newnews(String[] groups, Date d) { + Vec ret = new Vec(); + for(String g : groups) { + Mailbox group = resolve(g).getMailbox(); + for(Mailbox.Iterator mit = group.iterator(Query.arrival(d, null)); + mit.next();) { + ret.add(mit.head().get("message-id")); + } + } + return (String[])ret.copyInto(new String[ret.size()]); } - - public boolean ihave(String messageid) { /* FEATURE */ return false; } - - public Article next() { return article(ptr++, false, false); } - public Article last() { return article(ptr--, false, false); } public Article article(String i, boolean h, boolean b) { return article(Query.header("message-id",i),h,b); } - public Article article(int n, boolean h, boolean b) { ptr = n; return article(Query.nntpNumber(n,n),h,b); } + public Article article(int n, boolean h, boolean b) { currentMessageNumber = n; return article(Query.nntpNumber(n,n),h,b); } private Article article(Query q, boolean head, boolean body) { Mailbox.Iterator it = current.iterator(q); if (!it.next()) return null; - try { - // FIXME: UGLY! - Message m = body ? it.cur() : Message.newMessage(new Fountain.StringFountain(it.head() + "\r\n")); - return new Article(it.nntpNumber(), m); - } catch (Exception e) { return null; } + return new Article(it.nntpNumber(), body ? it.cur() : Message.newMessage(it.head())); } public Group[] list() { return list(root, ""); } private Group[] list(MailTree who, String prefix) { @@ -96,33 +125,34 @@ public class NNTP { if (who == null) who = root; String[] s = who.children(); for(int i=0; i, (any punctuation separates the list) - // Expires header: the date when expiration happens (??) should we ignore this? - // Control header: body is the command. Inteprert posts to all.all.ctl as control messages, - // use Subject line if no Cntrol line - // "Approved" line is used for moderaion - // Xref: drop this header if you see it - - // Control messages - // cancel (do not forward if I am unable to cancel locally) - // ihave/sendme: do not support - // newgroup [moderated] -- body of message is a description of the group - // rmgroup - boolean postok = api.postok(); if (!postok) { println("440 no posting allowed"); } else { println("340 send the article"); - StringBuffer buf = new StringBuffer(); - // FIXME: streaming? - 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 + "\r\n"); - } - String body = buf.toString(); try { - Message m = Message.newMessage(new Fountain.StringFountain(body)); + Message m = Message.readDotEncodedMessage(conn); if (m.headers.get("newsgroups")==null) println("441 posted messages must have a Newsgroups header per RFC 977"); else if (m.headers.get("newsgroups").indexOf('*')!=-1) println("441 Newsgroups header in posted messages may not contain wildcards (*) per RFC 977"); else if (m.headers.get("subject")==null) println("441 posted messages must have a Subject header per RFC 977"); - // else if (m.headers.get("path")==null) - //println("441 posted messages must have a Path header per RFC 977"); else if (m.headers.get("from")==null) println("441 posted messages must have a From header per RFC 977"); else if (m.headers.get("date")==null) @@ -331,7 +318,8 @@ public class NNTP { } else if (command.equals("XROVER")) { // equivalent to "XHDR References" - } else if (command.equals("XHDR")) { + + } else if (command.equals("XHDR") || (command.equals("HDR"))) { // argument: header name // argument: 1 | 1- | 1-2 | | nothing (use current article) println("221 yep"); @@ -340,11 +328,13 @@ public class NNTP { // 412 if no group selected and numeric form used // 430 if and not found // 420 if no messages in range + } else if (command.equals("XPAT")) { // just like XHDR, but a pattern follows the last argument (may contain whitespace) println("221 yep"); // print println("."); + } else if (command.equals("LIST")) { if (st.hasMoreTokens()) { String argument = st.nextToken().toUpperCase(); @@ -387,33 +377,28 @@ public class NNTP { } else if (command.equals("LISTGROUP")) { String groupname = st.hasMoreTokens() ? st.nextToken() : null; - // 211, all article numbers in group, period. Set article ptr to first item in group + // 211, all article numbers in group, period. Set article currentMessageNumber to first item in group } else if (command.equals("XGTITLE")) { String wildmat = st.hasMoreTokens() ? st.nextToken() : null; // 282, then identical to LIST NEWSGROUP } else if (command.equals("CHECK")) { - // FIXME: may be pipelined; must spawn threads String mid = st.nextToken(); - boolean want = api.ihave(mid); - if (!want) { - println("438 "+ mid+" No thanks"); - } else { - println("238 "+mid+" Yes, I'd like that"); - } + boolean want = api.want(mid); + if (!want) println("438 "+ mid+" No thanks"); + else println("238 "+mid+" Yes, I'd like that"); } else if (command.equals("TAKETHIS")) { - // FIXME: may be pipelined String mid = st.nextToken(); - // MUST read message here - /* - if (!want) { - println("439 "+ mid+" Transfer failed"); - } else { + boolean want = api.want(mid); + Message m = Message.readDotEncodedMessage(conn); + if (want) { + api.post(m); println("239 "+mid+" Rock on."); + } else { + println("439 "+ mid+" I really didn't want that; don't send it again."); } - */ } else if (command.equals("IHAVE")) { boolean want = api.ihave(st.nextToken()); @@ -421,7 +406,7 @@ public class NNTP { println("435 No thanks"); } else { println("335 Proceed"); - // FIXME read article here + api.post(Message.readDotEncodedMessage(conn)); println("235 Got it"); } } else { -- 1.7.10.4