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