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: }