trunk/com/so/news/NNTPConnection.java
author chris <chris@marvin>
Tue, 20 Jan 2009 10:21:03 +0100
changeset 0 f907866f0e4b
permissions -rw-r--r--
Initial import.
     1 /*
     2  *   StarOffice News Server
     3  *   see AUTHORS for the list of contributors
     4  *
     5  *   This program is free software: you can redistribute it and/or modify
     6  *   it under the terms of the GNU General Public License as published by
     7  *   the Free Software Foundation, either version 3 of the License, or
     8  *   (at your option) any later version.
     9  *
    10  *   This program is distributed in the hope that it will be useful,
    11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  *   GNU General Public License for more details.
    14  *
    15  *   You should have received a copy of the GNU General Public License
    16  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  */
    18 
    19 package com.so.news;
    20 
    21 import java.io.BufferedReader;
    22 import java.io.BufferedWriter;
    23 import java.io.File;
    24 import java.io.InputStreamReader;
    25 import java.io.IOException;
    26 import java.io.OutputStreamWriter;
    27 import java.net.Socket;
    28 import java.net.SocketException;
    29 import java.text.SimpleDateFormat;
    30 import java.util.Date;
    31 import java.util.LinkedList;
    32 import java.util.List;
    33 import com.so.news.command.ArticleCommand;
    34 import com.so.news.command.GroupCommand;
    35 import com.so.news.command.ListCommand;
    36 import com.so.news.command.PostCommand;
    37 import com.so.news.command.OverCommand;
    38 import com.so.news.storage.Article;
    39 import com.so.news.storage.Group;
    40 
    41 /**
    42  * Represents the connection between the server and one client.
    43  * @author Christian Lins (christian.lins@web.de)
    44  */
    45 public class NNTPConnection extends Thread
    46 {
    47   public static final String NEWLINE            = "\r\n";
    48   public static final String MESSAGE_ID_PATTERN = "<[^>]+>";
    49   
    50   private boolean            debug              
    51     = Boolean.parseBoolean(Config.getInstance().get("n3tpd.debug", "false"));
    52   private Socket             socket;
    53   private boolean            exit               = false;
    54   private BufferedWriter     out;
    55   private BufferedReader     in;
    56   private Article            currentArticle     = null;
    57   private Group              currentGroup       = null;
    58 
    59   /**
    60    * Creates a new NNTPConnection instance using the given connected Socket.
    61    * @param socket
    62    * @throws java.io.IOException
    63    */
    64   public NNTPConnection(Socket socket) 
    65     throws IOException
    66   {
    67     this.socket = socket;
    68     this.in     = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    69     this.out    = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
    70     // TODO: The output stream should be of type PrintStream so that many
    71     // of the printX() methods of this class can go to trash
    72     
    73     setDaemon(true); // Exits if the main thread is killed
    74   }
    75 
    76   /**
    77    * Closes the associated socket end exits the Thread.
    78    */
    79   public void exit()
    80   {
    81     try
    82     {
    83       exit = true;
    84       socket.close();
    85     }
    86     catch (Exception e)
    87     {
    88       e.printStackTrace();
    89     }
    90   }
    91 
    92   /**
    93    * Prints a CharSequence to the sockets output stream.
    94    */
    95   public void print(CharSequence s) throws IOException
    96   {
    97     out.append(s);
    98   }
    99 
   100   public void println(CharSequence s) throws IOException
   101   {
   102     print(s);
   103     print(NEWLINE);
   104     if (debug)
   105       System.out.println("<<< " + s);
   106   }
   107 
   108   public void printStatus(int code, String description) throws IOException
   109   {
   110     println("" + code + " " + description);
   111     flush();
   112   }
   113 
   114   public void printTextLine(CharSequence line) throws IOException
   115   {
   116     if (line.length() > 0 && line.charAt(0) == '.')
   117       print("..");
   118     println(line);
   119   }
   120 
   121   public void printTextPart(CharSequence text) throws IOException
   122   {
   123     String[] lines = text.toString().split(NEWLINE);
   124     for (String line : lines)
   125       printTextLine(line);
   126   }
   127 
   128   public void printText(CharSequence text) throws IOException
   129   {
   130     printTextPart(text);
   131     println(".");
   132     flush();
   133   }
   134 
   135   public void flush() throws IOException
   136   {
   137     out.flush();
   138   }
   139 
   140   public String readln() throws IOException
   141   {
   142     String s = in.readLine();
   143     if (s == null)
   144       throw new IOException("Socket closed");
   145     if (debug)
   146       System.out.println(">>> " + s);
   147     return s;
   148   }
   149 
   150   public String[] readCommand() throws IOException
   151   {
   152     return readln().split("[ ]+");
   153   }
   154 
   155   public List<String> readText() throws IOException
   156   {
   157     List<String> l = new LinkedList<String>();
   158     String s;
   159     do
   160     {
   161       s = readln();
   162       if (!s.equals("."))
   163       {
   164         if (s.startsWith(".."))
   165           s = s.substring(1);
   166         l.add(s);
   167       }
   168     }
   169     while (!s.equals("."));
   170     return l;
   171   }
   172 
   173   public String readTextLine() throws IOException
   174   {
   175     String s = null;
   176     do
   177     {
   178       s = readln();
   179     }
   180     while (s == null);
   181     if (s.equals("."))
   182       return null;
   183     if (s.startsWith(".."))
   184       s = s.substring(1);
   185     return s;
   186   }
   187 
   188   public void setCurrentArticle(Article current)
   189   {
   190     currentArticle = current;
   191   }
   192 
   193   public Article getCurrentArticle()
   194   {
   195     return currentArticle;
   196   }
   197 
   198   public void setCurrentGroup(Group current)
   199   {
   200     currentGroup = current;
   201   }
   202 
   203   public Group getCurrentGroup()
   204   {
   205     return currentGroup;
   206   }
   207 
   208   private void processCommand(String[] command) 
   209     throws Exception
   210   {
   211     if (command.length == 0)
   212       return; // TODO Error
   213 
   214     String commandName = command[0];
   215 
   216     // RFC977
   217     // TODO HELP command
   218     // TODO NEWGROUPS command
   219     // TODO NEWNEWS command
   220 
   221     // RFC2980
   222     // TODO LIST ACTIVE command
   223     // TODO LIST ACTIVE.TIMES command
   224     // TODO LIST DISTRIBUTIONS command
   225     // TODO LIST DISTRIB.PATS command
   226     // TODO XGTITLE command
   227     // TODO XHDR command
   228     // TODO XPAT command
   229     // TODO XPATH command
   230     // TODO XROVER command
   231     // TODO XTHREAD command
   232     // TODO AUTHINFO command
   233 
   234     // STANDARD COMMANDS
   235     if (commandName.equalsIgnoreCase("ARTICLE")
   236         || commandName.equalsIgnoreCase("STAT")
   237         || commandName.equalsIgnoreCase("HEAD") 
   238         || commandName.equalsIgnoreCase("BODY"))
   239     {
   240       ArticleCommand cmd = new ArticleCommand(this);
   241       cmd.process(command);
   242     }
   243     
   244     else if (commandName.equalsIgnoreCase("LIST"))
   245     {
   246       ListCommand cmd = new ListCommand(this);
   247       cmd.process(command);
   248     }
   249 
   250     else if (commandName.equalsIgnoreCase("GROUP"))
   251     {
   252       GroupCommand cmd = new GroupCommand(this);
   253       cmd.process(command);
   254     }
   255     
   256     else if(commandName.equalsIgnoreCase("POST"))
   257     {
   258       PostCommand cmd = new PostCommand(this);
   259       cmd.process(command);
   260     }
   261 
   262     else if (commandName.equalsIgnoreCase("CHECK")
   263         || commandName.equalsIgnoreCase("TAKETHIS"))
   264     {
   265       // untested, RFC2980 compliant
   266       printStatus(400, "not accepting articles");
   267       return;
   268     }
   269 
   270     else if (commandName.equalsIgnoreCase("IHAVE")
   271         || commandName.equalsIgnoreCase("XREPLIC"))
   272     {
   273       // untested, RFC977 compliant
   274       printStatus(435, "article not wanted - do not send it");
   275       return;
   276     }
   277 
   278     else if (commandName.equalsIgnoreCase("XCREATEGROUP"))
   279     {
   280       return;
   281     }
   282 
   283     else if (commandName.equalsIgnoreCase("SLAVE"))
   284     {
   285       // untested, RFC977 compliant
   286       printStatus(202, "slave status noted");
   287       return;
   288     }
   289 
   290     else if (commandName.equalsIgnoreCase("XINDEX"))
   291     {
   292       // untested, RFC2980 compliant
   293       printStatus(418, "no tin-style index is available for this news group");
   294       return;
   295     }
   296 
   297     else if (commandName.equalsIgnoreCase("DATE"))
   298     {
   299       printStatus(111, new SimpleDateFormat("yyyyMMddHHmmss")
   300           .format(new Date()));
   301       return;
   302     }
   303 
   304     else if (commandName.equalsIgnoreCase("MODE"))
   305     {
   306       if (command[1].equalsIgnoreCase("READER"))
   307       {
   308         // untested, RFC2980 compliant
   309         printStatus(200, "Hello, you can post");
   310       }
   311       else if (command[1].equalsIgnoreCase("STREAM"))
   312       {
   313         printStatus(203, "Streaming is OK");
   314       }
   315       else
   316         printStatus(501, "Command not supported");
   317     }
   318 
   319     else if (commandName.equalsIgnoreCase("QUIT"))
   320     {
   321       // untested, RFC977 compliant
   322       printStatus(205, "closing connection - goodbye!");
   323       exit();
   324       return;
   325     }  
   326     
   327     else if (commandName.equalsIgnoreCase("XSHUTDOWN"))
   328     {
   329       printStatus(205, "closing connection - goodbye!");
   330       exit();
   331       return;
   332     }
   333 
   334     // X COMMANDS
   335     else if(commandName.equalsIgnoreCase("XOVER")
   336        || commandName.equalsIgnoreCase("OVER"))
   337     {
   338       OverCommand cmd = new OverCommand(this);
   339       cmd.process(command);
   340     }
   341 
   342     else
   343       printStatus(501, "Command not supported");
   344   }
   345 
   346   /**
   347    * Runloop of this Thread.
   348    * @throws RuntimeException if this method is called directly.
   349    */
   350   @Override
   351   public void run()
   352   {
   353     assert !this.equals(Thread.currentThread());
   354 
   355     try
   356     {
   357       printStatus(200, Config.getInstance().get("n3tpd.hostname", "localhost")
   358           + " " + Main.VERSION + " news server ready - (posting ok).");
   359     }
   360     catch (IOException e1)
   361     {
   362       exit();
   363     }
   364     
   365     while (!exit)
   366     {
   367       try
   368       {
   369         processCommand(readCommand());
   370       }
   371       catch (SocketException e)
   372       {
   373         if (exit)
   374           return;
   375         exit();
   376         e.printStackTrace();
   377       }
   378       catch (IOException e)
   379       {
   380         if (exit)
   381           return;
   382         exit();
   383         e.printStackTrace();
   384       }
   385       catch (Throwable e)
   386       {
   387         if (exit)
   388           return;
   389         e.printStackTrace();
   390         // silently ignore
   391       }
   392     }
   393   }
   394 
   395 }