chris@0: /*
chris@0: * StarOffice News Server
chris@0: * see AUTHORS for the list of contributors
chris@0: *
chris@0: * This program is free software: you can redistribute it and/or modify
chris@0: * it under the terms of the GNU General Public License as published by
chris@0: * the Free Software Foundation, either version 3 of the License, or
chris@0: * (at your option) any later version.
chris@0: *
chris@0: * This program is distributed in the hope that it will be useful,
chris@0: * but WITHOUT ANY WARRANTY; without even the implied warranty of
chris@0: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
chris@0: * GNU General Public License for more details.
chris@0: *
chris@0: * You should have received a copy of the GNU General Public License
chris@0: * along with this program. If not, see .
chris@0: */
chris@0:
chris@0: package com.so.news;
chris@0:
chris@0: import java.io.BufferedReader;
chris@0: import java.io.BufferedWriter;
chris@0: import java.io.File;
chris@0: import java.io.InputStreamReader;
chris@0: import java.io.IOException;
chris@0: import java.io.OutputStreamWriter;
chris@0: import java.net.Socket;
chris@0: import java.net.SocketException;
chris@0: import java.text.SimpleDateFormat;
chris@0: import java.util.Date;
chris@0: import java.util.LinkedList;
chris@0: import java.util.List;
chris@0: import com.so.news.command.ArticleCommand;
chris@0: import com.so.news.command.GroupCommand;
chris@0: import com.so.news.command.ListCommand;
chris@0: import com.so.news.command.PostCommand;
chris@0: import com.so.news.command.OverCommand;
chris@0: import com.so.news.storage.Article;
chris@0: import com.so.news.storage.Group;
chris@0:
chris@0: /**
chris@0: * Represents the connection between the server and one client.
chris@0: * @author Christian Lins (christian.lins@web.de)
chris@0: */
chris@0: public class NNTPConnection extends Thread
chris@0: {
chris@0: public static final String NEWLINE = "\r\n";
chris@0: public static final String MESSAGE_ID_PATTERN = "<[^>]+>";
chris@0:
chris@0: private boolean debug
chris@0: = Boolean.parseBoolean(Config.getInstance().get("n3tpd.debug", "false"));
chris@0: private Socket socket;
chris@0: private boolean exit = false;
chris@0: private BufferedWriter out;
chris@0: private BufferedReader in;
chris@0: private Article currentArticle = null;
chris@0: private Group currentGroup = null;
chris@0:
chris@0: /**
chris@0: * Creates a new NNTPConnection instance using the given connected Socket.
chris@0: * @param socket
chris@0: * @throws java.io.IOException
chris@0: */
chris@0: public NNTPConnection(Socket socket)
chris@0: throws IOException
chris@0: {
chris@0: this.socket = socket;
chris@0: this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
chris@0: this.out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
chris@0: // TODO: The output stream should be of type PrintStream so that many
chris@0: // of the printX() methods of this class can go to trash
chris@0:
chris@0: setDaemon(true); // Exits if the main thread is killed
chris@0: }
chris@0:
chris@0: /**
chris@0: * Closes the associated socket end exits the Thread.
chris@0: */
chris@0: public void exit()
chris@0: {
chris@0: try
chris@0: {
chris@0: exit = true;
chris@0: socket.close();
chris@0: }
chris@0: catch (Exception e)
chris@0: {
chris@0: e.printStackTrace();
chris@0: }
chris@0: }
chris@0:
chris@0: /**
chris@0: * Prints a CharSequence to the sockets output stream.
chris@0: */
chris@0: public void print(CharSequence s) throws IOException
chris@0: {
chris@0: out.append(s);
chris@0: }
chris@0:
chris@0: public void println(CharSequence s) throws IOException
chris@0: {
chris@0: print(s);
chris@0: print(NEWLINE);
chris@0: if (debug)
chris@0: System.out.println("<<< " + s);
chris@0: }
chris@0:
chris@0: public void printStatus(int code, String description) throws IOException
chris@0: {
chris@0: println("" + code + " " + description);
chris@0: flush();
chris@0: }
chris@0:
chris@0: public void printTextLine(CharSequence line) throws IOException
chris@0: {
chris@0: if (line.length() > 0 && line.charAt(0) == '.')
chris@0: print("..");
chris@0: println(line);
chris@0: }
chris@0:
chris@0: public void printTextPart(CharSequence text) throws IOException
chris@0: {
chris@0: String[] lines = text.toString().split(NEWLINE);
chris@0: for (String line : lines)
chris@0: printTextLine(line);
chris@0: }
chris@0:
chris@0: public void printText(CharSequence text) throws IOException
chris@0: {
chris@0: printTextPart(text);
chris@0: println(".");
chris@0: flush();
chris@0: }
chris@0:
chris@0: public void flush() throws IOException
chris@0: {
chris@0: out.flush();
chris@0: }
chris@0:
chris@0: public String readln() throws IOException
chris@0: {
chris@0: String s = in.readLine();
chris@0: if (s == null)
chris@0: throw new IOException("Socket closed");
chris@0: if (debug)
chris@0: System.out.println(">>> " + s);
chris@0: return s;
chris@0: }
chris@0:
chris@0: public String[] readCommand() throws IOException
chris@0: {
chris@0: return readln().split("[ ]+");
chris@0: }
chris@0:
chris@0: public List readText() throws IOException
chris@0: {
chris@0: List l = new LinkedList();
chris@0: String s;
chris@0: do
chris@0: {
chris@0: s = readln();
chris@0: if (!s.equals("."))
chris@0: {
chris@0: if (s.startsWith(".."))
chris@0: s = s.substring(1);
chris@0: l.add(s);
chris@0: }
chris@0: }
chris@0: while (!s.equals("."));
chris@0: return l;
chris@0: }
chris@0:
chris@0: public String readTextLine() throws IOException
chris@0: {
chris@0: String s = null;
chris@0: do
chris@0: {
chris@0: s = readln();
chris@0: }
chris@0: while (s == null);
chris@0: if (s.equals("."))
chris@0: return null;
chris@0: if (s.startsWith(".."))
chris@0: s = s.substring(1);
chris@0: return s;
chris@0: }
chris@0:
chris@0: public void setCurrentArticle(Article current)
chris@0: {
chris@0: currentArticle = current;
chris@0: }
chris@0:
chris@0: public Article getCurrentArticle()
chris@0: {
chris@0: return currentArticle;
chris@0: }
chris@0:
chris@0: public void setCurrentGroup(Group current)
chris@0: {
chris@0: currentGroup = current;
chris@0: }
chris@0:
chris@0: public Group getCurrentGroup()
chris@0: {
chris@0: return currentGroup;
chris@0: }
chris@0:
chris@0: private void processCommand(String[] command)
chris@0: throws Exception
chris@0: {
chris@0: if (command.length == 0)
chris@0: return; // TODO Error
chris@0:
chris@0: String commandName = command[0];
chris@0:
chris@0: // RFC977
chris@0: // TODO HELP command
chris@0: // TODO NEWGROUPS command
chris@0: // TODO NEWNEWS command
chris@0:
chris@0: // RFC2980
chris@0: // TODO LIST ACTIVE command
chris@0: // TODO LIST ACTIVE.TIMES command
chris@0: // TODO LIST DISTRIBUTIONS command
chris@0: // TODO LIST DISTRIB.PATS command
chris@0: // TODO XGTITLE command
chris@0: // TODO XHDR command
chris@0: // TODO XPAT command
chris@0: // TODO XPATH command
chris@0: // TODO XROVER command
chris@0: // TODO XTHREAD command
chris@0: // TODO AUTHINFO command
chris@0:
chris@0: // STANDARD COMMANDS
chris@0: if (commandName.equalsIgnoreCase("ARTICLE")
chris@0: || commandName.equalsIgnoreCase("STAT")
chris@0: || commandName.equalsIgnoreCase("HEAD")
chris@0: || commandName.equalsIgnoreCase("BODY"))
chris@0: {
chris@0: ArticleCommand cmd = new ArticleCommand(this);
chris@0: cmd.process(command);
chris@0: }
chris@0:
chris@0: else if (commandName.equalsIgnoreCase("LIST"))
chris@0: {
chris@0: ListCommand cmd = new ListCommand(this);
chris@0: cmd.process(command);
chris@0: }
chris@0:
chris@0: else if (commandName.equalsIgnoreCase("GROUP"))
chris@0: {
chris@0: GroupCommand cmd = new GroupCommand(this);
chris@0: cmd.process(command);
chris@0: }
chris@0:
chris@0: else if(commandName.equalsIgnoreCase("POST"))
chris@0: {
chris@0: PostCommand cmd = new PostCommand(this);
chris@0: cmd.process(command);
chris@0: }
chris@0:
chris@0: else if (commandName.equalsIgnoreCase("CHECK")
chris@0: || commandName.equalsIgnoreCase("TAKETHIS"))
chris@0: {
chris@0: // untested, RFC2980 compliant
chris@0: printStatus(400, "not accepting articles");
chris@0: return;
chris@0: }
chris@0:
chris@0: else if (commandName.equalsIgnoreCase("IHAVE")
chris@0: || commandName.equalsIgnoreCase("XREPLIC"))
chris@0: {
chris@0: // untested, RFC977 compliant
chris@0: printStatus(435, "article not wanted - do not send it");
chris@0: return;
chris@0: }
chris@0:
chris@0: else if (commandName.equalsIgnoreCase("XCREATEGROUP"))
chris@0: {
chris@0: return;
chris@0: }
chris@0:
chris@0: else if (commandName.equalsIgnoreCase("SLAVE"))
chris@0: {
chris@0: // untested, RFC977 compliant
chris@0: printStatus(202, "slave status noted");
chris@0: return;
chris@0: }
chris@0:
chris@0: else if (commandName.equalsIgnoreCase("XINDEX"))
chris@0: {
chris@0: // untested, RFC2980 compliant
chris@0: printStatus(418, "no tin-style index is available for this news group");
chris@0: return;
chris@0: }
chris@0:
chris@0: else if (commandName.equalsIgnoreCase("DATE"))
chris@0: {
chris@0: printStatus(111, new SimpleDateFormat("yyyyMMddHHmmss")
chris@0: .format(new Date()));
chris@0: return;
chris@0: }
chris@0:
chris@0: else if (commandName.equalsIgnoreCase("MODE"))
chris@0: {
chris@0: if (command[1].equalsIgnoreCase("READER"))
chris@0: {
chris@0: // untested, RFC2980 compliant
chris@0: printStatus(200, "Hello, you can post");
chris@0: }
chris@0: else if (command[1].equalsIgnoreCase("STREAM"))
chris@0: {
chris@0: printStatus(203, "Streaming is OK");
chris@0: }
chris@0: else
chris@0: printStatus(501, "Command not supported");
chris@0: }
chris@0:
chris@0: else if (commandName.equalsIgnoreCase("QUIT"))
chris@0: {
chris@0: // untested, RFC977 compliant
chris@0: printStatus(205, "closing connection - goodbye!");
chris@0: exit();
chris@0: return;
chris@0: }
chris@0:
chris@0: else if (commandName.equalsIgnoreCase("XSHUTDOWN"))
chris@0: {
chris@0: printStatus(205, "closing connection - goodbye!");
chris@0: exit();
chris@0: return;
chris@0: }
chris@0:
chris@0: // X COMMANDS
chris@0: else if(commandName.equalsIgnoreCase("XOVER")
chris@0: || commandName.equalsIgnoreCase("OVER"))
chris@0: {
chris@0: OverCommand cmd = new OverCommand(this);
chris@0: cmd.process(command);
chris@0: }
chris@0:
chris@0: else
chris@0: printStatus(501, "Command not supported");
chris@0: }
chris@0:
chris@0: /**
chris@0: * Runloop of this Thread.
chris@0: * @throws RuntimeException if this method is called directly.
chris@0: */
chris@0: @Override
chris@0: public void run()
chris@0: {
chris@0: assert !this.equals(Thread.currentThread());
chris@0:
chris@0: try
chris@0: {
chris@0: printStatus(200, Config.getInstance().get("n3tpd.hostname", "localhost")
chris@0: + " " + Main.VERSION + " news server ready - (posting ok).");
chris@0: }
chris@0: catch (IOException e1)
chris@0: {
chris@0: exit();
chris@0: }
chris@0:
chris@0: while (!exit)
chris@0: {
chris@0: try
chris@0: {
chris@0: processCommand(readCommand());
chris@0: }
chris@0: catch (SocketException e)
chris@0: {
chris@0: if (exit)
chris@0: return;
chris@0: exit();
chris@0: e.printStackTrace();
chris@0: }
chris@0: catch (IOException e)
chris@0: {
chris@0: if (exit)
chris@0: return;
chris@0: exit();
chris@0: e.printStackTrace();
chris@0: }
chris@0: catch (Throwable e)
chris@0: {
chris@0: if (exit)
chris@0: return;
chris@0: e.printStackTrace();
chris@0: // silently ignore
chris@0: }
chris@0: }
chris@0: }
chris@0:
chris@0: }