1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/trunk/com/so/news/NNTPConnection.java Tue Jan 20 10:21:03 2009 +0100
1.3 @@ -0,0 +1,395 @@
1.4 +/*
1.5 + * StarOffice News Server
1.6 + * see AUTHORS for the list of contributors
1.7 + *
1.8 + * This program is free software: you can redistribute it and/or modify
1.9 + * it under the terms of the GNU General Public License as published by
1.10 + * the Free Software Foundation, either version 3 of the License, or
1.11 + * (at your option) any later version.
1.12 + *
1.13 + * This program is distributed in the hope that it will be useful,
1.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.16 + * GNU General Public License for more details.
1.17 + *
1.18 + * You should have received a copy of the GNU General Public License
1.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
1.20 + */
1.21 +
1.22 +package com.so.news;
1.23 +
1.24 +import java.io.BufferedReader;
1.25 +import java.io.BufferedWriter;
1.26 +import java.io.File;
1.27 +import java.io.InputStreamReader;
1.28 +import java.io.IOException;
1.29 +import java.io.OutputStreamWriter;
1.30 +import java.net.Socket;
1.31 +import java.net.SocketException;
1.32 +import java.text.SimpleDateFormat;
1.33 +import java.util.Date;
1.34 +import java.util.LinkedList;
1.35 +import java.util.List;
1.36 +import com.so.news.command.ArticleCommand;
1.37 +import com.so.news.command.GroupCommand;
1.38 +import com.so.news.command.ListCommand;
1.39 +import com.so.news.command.PostCommand;
1.40 +import com.so.news.command.OverCommand;
1.41 +import com.so.news.storage.Article;
1.42 +import com.so.news.storage.Group;
1.43 +
1.44 +/**
1.45 + * Represents the connection between the server and one client.
1.46 + * @author Christian Lins (christian.lins@web.de)
1.47 + */
1.48 +public class NNTPConnection extends Thread
1.49 +{
1.50 + public static final String NEWLINE = "\r\n";
1.51 + public static final String MESSAGE_ID_PATTERN = "<[^>]+>";
1.52 +
1.53 + private boolean debug
1.54 + = Boolean.parseBoolean(Config.getInstance().get("n3tpd.debug", "false"));
1.55 + private Socket socket;
1.56 + private boolean exit = false;
1.57 + private BufferedWriter out;
1.58 + private BufferedReader in;
1.59 + private Article currentArticle = null;
1.60 + private Group currentGroup = null;
1.61 +
1.62 + /**
1.63 + * Creates a new NNTPConnection instance using the given connected Socket.
1.64 + * @param socket
1.65 + * @throws java.io.IOException
1.66 + */
1.67 + public NNTPConnection(Socket socket)
1.68 + throws IOException
1.69 + {
1.70 + this.socket = socket;
1.71 + this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
1.72 + this.out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
1.73 + // TODO: The output stream should be of type PrintStream so that many
1.74 + // of the printX() methods of this class can go to trash
1.75 +
1.76 + setDaemon(true); // Exits if the main thread is killed
1.77 + }
1.78 +
1.79 + /**
1.80 + * Closes the associated socket end exits the Thread.
1.81 + */
1.82 + public void exit()
1.83 + {
1.84 + try
1.85 + {
1.86 + exit = true;
1.87 + socket.close();
1.88 + }
1.89 + catch (Exception e)
1.90 + {
1.91 + e.printStackTrace();
1.92 + }
1.93 + }
1.94 +
1.95 + /**
1.96 + * Prints a CharSequence to the sockets output stream.
1.97 + */
1.98 + public void print(CharSequence s) throws IOException
1.99 + {
1.100 + out.append(s);
1.101 + }
1.102 +
1.103 + public void println(CharSequence s) throws IOException
1.104 + {
1.105 + print(s);
1.106 + print(NEWLINE);
1.107 + if (debug)
1.108 + System.out.println("<<< " + s);
1.109 + }
1.110 +
1.111 + public void printStatus(int code, String description) throws IOException
1.112 + {
1.113 + println("" + code + " " + description);
1.114 + flush();
1.115 + }
1.116 +
1.117 + public void printTextLine(CharSequence line) throws IOException
1.118 + {
1.119 + if (line.length() > 0 && line.charAt(0) == '.')
1.120 + print("..");
1.121 + println(line);
1.122 + }
1.123 +
1.124 + public void printTextPart(CharSequence text) throws IOException
1.125 + {
1.126 + String[] lines = text.toString().split(NEWLINE);
1.127 + for (String line : lines)
1.128 + printTextLine(line);
1.129 + }
1.130 +
1.131 + public void printText(CharSequence text) throws IOException
1.132 + {
1.133 + printTextPart(text);
1.134 + println(".");
1.135 + flush();
1.136 + }
1.137 +
1.138 + public void flush() throws IOException
1.139 + {
1.140 + out.flush();
1.141 + }
1.142 +
1.143 + public String readln() throws IOException
1.144 + {
1.145 + String s = in.readLine();
1.146 + if (s == null)
1.147 + throw new IOException("Socket closed");
1.148 + if (debug)
1.149 + System.out.println(">>> " + s);
1.150 + return s;
1.151 + }
1.152 +
1.153 + public String[] readCommand() throws IOException
1.154 + {
1.155 + return readln().split("[ ]+");
1.156 + }
1.157 +
1.158 + public List<String> readText() throws IOException
1.159 + {
1.160 + List<String> l = new LinkedList<String>();
1.161 + String s;
1.162 + do
1.163 + {
1.164 + s = readln();
1.165 + if (!s.equals("."))
1.166 + {
1.167 + if (s.startsWith(".."))
1.168 + s = s.substring(1);
1.169 + l.add(s);
1.170 + }
1.171 + }
1.172 + while (!s.equals("."));
1.173 + return l;
1.174 + }
1.175 +
1.176 + public String readTextLine() throws IOException
1.177 + {
1.178 + String s = null;
1.179 + do
1.180 + {
1.181 + s = readln();
1.182 + }
1.183 + while (s == null);
1.184 + if (s.equals("."))
1.185 + return null;
1.186 + if (s.startsWith(".."))
1.187 + s = s.substring(1);
1.188 + return s;
1.189 + }
1.190 +
1.191 + public void setCurrentArticle(Article current)
1.192 + {
1.193 + currentArticle = current;
1.194 + }
1.195 +
1.196 + public Article getCurrentArticle()
1.197 + {
1.198 + return currentArticle;
1.199 + }
1.200 +
1.201 + public void setCurrentGroup(Group current)
1.202 + {
1.203 + currentGroup = current;
1.204 + }
1.205 +
1.206 + public Group getCurrentGroup()
1.207 + {
1.208 + return currentGroup;
1.209 + }
1.210 +
1.211 + private void processCommand(String[] command)
1.212 + throws Exception
1.213 + {
1.214 + if (command.length == 0)
1.215 + return; // TODO Error
1.216 +
1.217 + String commandName = command[0];
1.218 +
1.219 + // RFC977
1.220 + // TODO HELP command
1.221 + // TODO NEWGROUPS command
1.222 + // TODO NEWNEWS command
1.223 +
1.224 + // RFC2980
1.225 + // TODO LIST ACTIVE command
1.226 + // TODO LIST ACTIVE.TIMES command
1.227 + // TODO LIST DISTRIBUTIONS command
1.228 + // TODO LIST DISTRIB.PATS command
1.229 + // TODO XGTITLE command
1.230 + // TODO XHDR command
1.231 + // TODO XPAT command
1.232 + // TODO XPATH command
1.233 + // TODO XROVER command
1.234 + // TODO XTHREAD command
1.235 + // TODO AUTHINFO command
1.236 +
1.237 + // STANDARD COMMANDS
1.238 + if (commandName.equalsIgnoreCase("ARTICLE")
1.239 + || commandName.equalsIgnoreCase("STAT")
1.240 + || commandName.equalsIgnoreCase("HEAD")
1.241 + || commandName.equalsIgnoreCase("BODY"))
1.242 + {
1.243 + ArticleCommand cmd = new ArticleCommand(this);
1.244 + cmd.process(command);
1.245 + }
1.246 +
1.247 + else if (commandName.equalsIgnoreCase("LIST"))
1.248 + {
1.249 + ListCommand cmd = new ListCommand(this);
1.250 + cmd.process(command);
1.251 + }
1.252 +
1.253 + else if (commandName.equalsIgnoreCase("GROUP"))
1.254 + {
1.255 + GroupCommand cmd = new GroupCommand(this);
1.256 + cmd.process(command);
1.257 + }
1.258 +
1.259 + else if(commandName.equalsIgnoreCase("POST"))
1.260 + {
1.261 + PostCommand cmd = new PostCommand(this);
1.262 + cmd.process(command);
1.263 + }
1.264 +
1.265 + else if (commandName.equalsIgnoreCase("CHECK")
1.266 + || commandName.equalsIgnoreCase("TAKETHIS"))
1.267 + {
1.268 + // untested, RFC2980 compliant
1.269 + printStatus(400, "not accepting articles");
1.270 + return;
1.271 + }
1.272 +
1.273 + else if (commandName.equalsIgnoreCase("IHAVE")
1.274 + || commandName.equalsIgnoreCase("XREPLIC"))
1.275 + {
1.276 + // untested, RFC977 compliant
1.277 + printStatus(435, "article not wanted - do not send it");
1.278 + return;
1.279 + }
1.280 +
1.281 + else if (commandName.equalsIgnoreCase("XCREATEGROUP"))
1.282 + {
1.283 + return;
1.284 + }
1.285 +
1.286 + else if (commandName.equalsIgnoreCase("SLAVE"))
1.287 + {
1.288 + // untested, RFC977 compliant
1.289 + printStatus(202, "slave status noted");
1.290 + return;
1.291 + }
1.292 +
1.293 + else if (commandName.equalsIgnoreCase("XINDEX"))
1.294 + {
1.295 + // untested, RFC2980 compliant
1.296 + printStatus(418, "no tin-style index is available for this news group");
1.297 + return;
1.298 + }
1.299 +
1.300 + else if (commandName.equalsIgnoreCase("DATE"))
1.301 + {
1.302 + printStatus(111, new SimpleDateFormat("yyyyMMddHHmmss")
1.303 + .format(new Date()));
1.304 + return;
1.305 + }
1.306 +
1.307 + else if (commandName.equalsIgnoreCase("MODE"))
1.308 + {
1.309 + if (command[1].equalsIgnoreCase("READER"))
1.310 + {
1.311 + // untested, RFC2980 compliant
1.312 + printStatus(200, "Hello, you can post");
1.313 + }
1.314 + else if (command[1].equalsIgnoreCase("STREAM"))
1.315 + {
1.316 + printStatus(203, "Streaming is OK");
1.317 + }
1.318 + else
1.319 + printStatus(501, "Command not supported");
1.320 + }
1.321 +
1.322 + else if (commandName.equalsIgnoreCase("QUIT"))
1.323 + {
1.324 + // untested, RFC977 compliant
1.325 + printStatus(205, "closing connection - goodbye!");
1.326 + exit();
1.327 + return;
1.328 + }
1.329 +
1.330 + else if (commandName.equalsIgnoreCase("XSHUTDOWN"))
1.331 + {
1.332 + printStatus(205, "closing connection - goodbye!");
1.333 + exit();
1.334 + return;
1.335 + }
1.336 +
1.337 + // X COMMANDS
1.338 + else if(commandName.equalsIgnoreCase("XOVER")
1.339 + || commandName.equalsIgnoreCase("OVER"))
1.340 + {
1.341 + OverCommand cmd = new OverCommand(this);
1.342 + cmd.process(command);
1.343 + }
1.344 +
1.345 + else
1.346 + printStatus(501, "Command not supported");
1.347 + }
1.348 +
1.349 + /**
1.350 + * Runloop of this Thread.
1.351 + * @throws RuntimeException if this method is called directly.
1.352 + */
1.353 + @Override
1.354 + public void run()
1.355 + {
1.356 + assert !this.equals(Thread.currentThread());
1.357 +
1.358 + try
1.359 + {
1.360 + printStatus(200, Config.getInstance().get("n3tpd.hostname", "localhost")
1.361 + + " " + Main.VERSION + " news server ready - (posting ok).");
1.362 + }
1.363 + catch (IOException e1)
1.364 + {
1.365 + exit();
1.366 + }
1.367 +
1.368 + while (!exit)
1.369 + {
1.370 + try
1.371 + {
1.372 + processCommand(readCommand());
1.373 + }
1.374 + catch (SocketException e)
1.375 + {
1.376 + if (exit)
1.377 + return;
1.378 + exit();
1.379 + e.printStackTrace();
1.380 + }
1.381 + catch (IOException e)
1.382 + {
1.383 + if (exit)
1.384 + return;
1.385 + exit();
1.386 + e.printStackTrace();
1.387 + }
1.388 + catch (Throwable e)
1.389 + {
1.390 + if (exit)
1.391 + return;
1.392 + e.printStackTrace();
1.393 + // silently ignore
1.394 + }
1.395 + }
1.396 + }
1.397 +
1.398 +}