/** NNTP send/recieve */
public class NNTP {
- // FIXME: command lines limited to 512 chars
+ public static final DateFormat dateFormat = new SimpleDateFormat("YYYYMMDDhhmmss");
- public static class No extends RuntimeException { int code = 0; } // 4xx response codes
- public static class Bad extends RuntimeException { int code = 0; } // 5xx response codes
+ 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 final String name;
+ public final String name; // case insensitive
public final boolean post;
public final int first;
public final int last;
- public final int count;
+ public final int count; // an approximation; must be >= actual number
}
public static class Article {
- public Article(String messageid, int num, Message message) { this.message=message;this.messageid=messageid;this.num=num;}
- public final String messageid;
+ public Article(int num, Message message) { this.message = message; this.num = num;}
public final int num;
public final Message message;
}
private int ptr = 0;
public MailboxWrapper(Mailbox root) { this.root = root; }
public Group group(String s) { ptr = 0; setgroup(s); return getgroup(s); }
+
public boolean ihave(String messageid) { /* FEATURE */ return false; }
public boolean post(Message m) { /* 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.messagenum(n,n),h,b); }
- private Article article(Query q, boolean head, boolean body) {
+ private Article article(Query q, boolean head, boolean body) {
Mailbox.Iterator it = current.iterator(q);
if (!it.next()) return null;
//Message m = body ? it.cur() : it.head();
public Listener(Login l) { this.login = l; }
private void println(String s) { Log.warn("[nntp-write]", s); conn.println(s); }
- private void println() { Log.warn("[nntp-write]", ""); conn.println(); }
+ private void println() { Log.warn("[nntp-write]", ""); conn.println(""); }
+ private void print(String s) { Log.warn("[nntp-write]", s); conn.print(s); }
private void article(String numOrMessageId, boolean head, boolean body) {
String s = numOrMessageId.trim();
return;
}
int code = (head && body) ? 220 : head ? 221 : body ? 222 : 223;
- println(code + " " + a.num + " <" + a.messageid + "> get ready for some stuff...");
+ println(code + " " + a.num + " <" + a.message.messageid + "> get ready for some stuff...");
if (head) println(a.message.headers.raw);
if (head && body) println();
- if (body) println(a.message.body);
+ if (body) {
+ Stream stream = new Stream(a.message.body);
+ while(true) {
+ s = stream.readln();
+ if (s == null) break;
+ if (s.startsWith(".")) print(".");
+ println(s);
+ }
+ }
println(".");
}
public void handleRequest(Connection conn) {
this.conn = conn;
conn.setTimeout(30 * 60 * 1000);
+ conn.setNewline("\r\n");
println("200 " + conn.vhost + " [" + NNTP.class.getName() + "]");
String user = null;
String pass = null;
StringTokenizer st = new StringTokenizer(line, " ");
String command = st.nextToken().toUpperCase();
if (command.equals("AUTHINFO")) {
+ // FIXME technically the RFC says we need to use this info to generate a SEnder: header...
String uop = st.nextToken().toUpperCase();
if (uop.equals("USER")) user = st.nextToken();
else if (uop.equals("PASS")) pass = st.nextToken();
}
if (command.equals("ARTICLE")) { article(st.hasMoreTokens() ? st.nextToken() : null, true, true);
} else if (command.equals("HEAD")) { article(st.hasMoreTokens() ? st.nextToken() : null, true, false);
- } else if (command.equals("MODE")) { println("201 Hello, you can post.");
+ } else if (command.equals("DATE")) {
+ // FIXME must be GMT
+ println("111 " + dateFormat.format(new Date()));
+ } else if (command.equals("MODE")) {
+ if (st.hasMoreTokens()) {
+ String arg = st.nextToken();
+ if (arg.equalsIgnoreCase("STREAM"));
+ streaming = true;
+ println("203 Streaming permitted");
+ } else {
+ println("201 Hello, you can post.");
+ }
} else if (command.equals("BODY")) { article(st.hasMoreTokens() ? st.nextToken() : null, false, true);
} else if (command.equals("STAT")) { article(st.hasMoreTokens() ? st.nextToken() : null, false, false);
} else if (command.equals("HELP")) { println("100 you are beyond help."); println(".");
m.headers.gets("references") + "\t" + m.size() + "\t" + m.lines);
}
println(".");
- } else if (command.equals("LAST")) { Article a = api.last(); println("223 "+a.num+" "+a.messageid+" ok");
- } else if (command.equals("NEXT")) { Article a = api.next(); println("223 "+a.num+" "+a.messageid+" ok");
+ } else if (command.equals("LAST")) { Article a = api.last(); println("223 "+a.num+" "+a.message.messageid+" ok");
+ } else if (command.equals("NEXT")) { Article a = api.next(); println("223 "+a.num+" "+a.message.messageid+" ok");
} else if (command.equals("QUIT")) { println("205 Bye."); conn.close(); return;
} else if (command.equals("GROUP")) {
Group g = api.group(st.nextToken().toLowerCase());
println("211 " + g.count + " " + g.first + " " + g.last + " " + g.name);
} else if (command.equals("NEWGROUPS") || command.equals("NEWNEWS")) {
+ // FIXME: * and ! unsupported
+ // NEWNEWS is often not supported
String groups = command.equals("NEWNEWS") ? st.nextToken() : null;
String datetime = st.nextToken() + " " + st.nextToken();
String gmt = st.nextToken();
}
} else if (command.equals("POST")) {
+ // add NNTP-Posting-Host header
// FIXME
+ // required headers: Newsgroups, Subject, Message-ID, Path, From, Date. No wildcars in newsgroups list
+ // Path header: prepend <myname>, (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 <Message-ID> (do not forward if I am unable to cancel locally)
+ // ihave/sendme: do not support
+ // newgroup <groupname> [moderated] -- body of message is a description of the group
+ // rmgroup <groupname>
+
/*
boolean postok = api.post();
if (!postok) {
for(int i=0; i<g.length; i++)
println(g[i].name + " " + g[i].last + " " + g[i].first + " " + (g[i].post ? "y" : "n"));
println(".");
+ // 412 if no group selected and numeric form used
+ // 430 if <mid> 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();
+ if (argument.equalsIgnoreCase("EXTENSIONS")) {
+ println("202 Extensions supported:");
+ println("STREAMING");
+ println("");
+ println(".");
+ } else if (argument.equals("ACTIVE")) {
+ String wildmat = st.hasMoreTokens() ? st.nextToken() : null;
+ // FIXME: deal with wildmat
+ // just like list, but only show active groups
+ } else if (argument.equals("SUBSCRIPTIONS")) {
+ // FIXME: show 215, default subscription list for new users, period
+ } else if (argument.equals("OVERVIEW.FMT")) {
+ println("215 Overview format:");
+ println("Subject:");
+ println("From:");
+ println("Date:");
+ println("Message-ID:");
+ println("References:");
+ println("Bytes:");
+ println("Lines:");
+ //println("Xref:full");
+ println(".");
+ } else if (argument.equals("NEWSGROUPS")) {
+ String wildmat = st.hasMoreTokens() ? st.nextToken() : null;
+ // respond 215, print each newsgroup, a space, and the description; end with lone period
+ } else {
+ // barf here
+ }
+ } else {
+ Group[] g = api.list();
+ println("215 list of groups follows");
+ for(int i=0; i<g.length; i++)
+ println(g[i].name + " " + g[i].last + " " + g[i].first + " " + (g[i].post ? "y" : "n"));
+ println(".");
+ }
+
+ } 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
+
+ } 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");
+ }
+
+ } 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 {
+ println("239 "+mid+" Rock on.");
+ }
} else if (command.equals("IHAVE")) {
boolean want = api.ihave(st.nextToken());
// FIXME read article here
println("235 Got it");
}
+ } else {
+ throw new Bad("wtf are you talking about?");
}
} catch (No n) { println(n.code + " " + n.getMessage());
} catch (Bad b) { println(b.code + " " + b.getMessage()); Log.warn(this, b); }