Index: jakarta-james/src/java/org/apache/james/nntpserver/NNTPHandler.java
===================================================================
RCS file: /home/cvs/jakarta-james/src/java/org/apache/james/nntpserver/NNTPHandler.java,v
retrieving revision 1.16
diff -u -r1.16 NNTPHandler.java
--- jakarta-james/src/java/org/apache/james/nntpserver/NNTPHandler.java	26 Aug 2002 19:46:18 -0000	1.16
+++ jakarta-james/src/java/org/apache/james/nntpserver/NNTPHandler.java	1 Oct 2002 06:08:37 -0000
@@ -15,12 +15,16 @@
 import org.apache.avalon.framework.component.ComponentManager;
 import org.apache.avalon.framework.component.Composable;
 import org.apache.avalon.framework.configuration.Configurable;
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
 import org.apache.avalon.framework.logger.Logger;
 import org.apache.james.BaseConnectionHandler;
 import org.apache.james.nntpserver.repository.NNTPArticle;
 import org.apache.james.nntpserver.repository.NNTPGroup;
 import org.apache.james.nntpserver.repository.NNTPLineReaderImpl;
 import org.apache.james.nntpserver.repository.NNTPRepository;
+import org.apache.james.services.UsersRepository;
+import org.apache.james.services.UsersStore;
 import org.apache.james.util.RFC977DateFormat;
 import org.apache.james.util.RFC2980DateFormat;
 import org.apache.james.util.SimplifiedDateFormat;
@@ -43,27 +47,77 @@
  *
  * @author Fedor Karpelevitch
  * @author Harmeet <hbedi@apache.org>
+ * @author Peter M. Goldstein <farsight@alum.mit.edu>
  */
 public class NNTPHandler extends BaseConnectionHandler
     implements ConnectionHandler, Composable, Configurable, Target {
 
-    // timeout controllers
+    /**
+     * used to calculate DATE from - see 11.3
+     */
+    private static final SimplifiedDateFormat DF_RFC977 = new RFC977DateFormat();
+
+    /**
+     * Date format for the DATE keyword - see 11.1.1
+     */
+    private static final SimplifiedDateFormat DF_RFC2980 = new RFC2980DateFormat();
+
+    /**
+     * The UTC offset for this time zone.
+     */
+    public static final long UTC_OFFSET = Calendar.getInstance().get(Calendar.ZONE_OFFSET);
+
+    /**
+     * Timeout controller
+     */
     private TimeScheduler scheduler;
 
-    // communciation.
+    /**
+     * The TCP/IP socket over which the POP3 interaction
+     * is occurring
+     */
     private Socket socket;
+
+    /**
+     * The reader associated with incoming characters.
+     */
     private BufferedReader reader;
-    private PrintWriter writer;
 
-    // authentication & authorization.
-    private AuthService auth;
+    /**
+     * The writer to which outgoing messages are written.
+     */
+    private PrintWriter writer;
 
-    // data abstractions.
+    /**
+     * The current newsgroup.
+     */
     private NNTPGroup group;
+
+    /**
+     * The repository that stores the news articles for this NNTP server.
+     */
     private NNTPRepository repo;
 
-    private static final boolean DEBUG_PROTOCOL = 
-        Boolean.getBoolean("apache.nntpserver.debug");
+    /**
+     * The repository that stores the local users.  Used for authentication.
+     */
+    private UsersRepository userRepository = null;
+
+    /**
+     * Whether authentication is required to access this NNTP server
+     */
+    private boolean authRequired = false;
+
+    /**
+     * The user id associated with the NNTP dialogue
+     */
+    private String user;
+
+    /**
+     * The password associated with the NNTP dialogue
+     */
+    private String password;
+
 
     /**
      * Pass the <code>ComponentManager</code> to the <code>composer</code>.
@@ -77,15 +131,36 @@
     public void compose( final ComponentManager componentManager )
         throws ComponentException
     {
-        //System.out.println(getClass().getName()+ ": compose - " + authRequired);
-        auth = (AuthService)componentManager.
-            lookup( "org.apache.james.nntpserver.AuthService" );
+        UsersStore usersStore = (UsersStore)componentManager.lookup(UsersStore.ROLE);
+        userRepository = usersStore.getRepository("LocalUsers");
+
         scheduler = (TimeScheduler)componentManager.
             lookup( "org.apache.avalon.cornerstone.services.scheduler.TimeScheduler" );
+
         repo = (NNTPRepository)componentManager
             .lookup("org.apache.james.nntpserver.repository.NNTPRepository");
     }
 
+    /**
+     * Pass the <code>Configuration</code> to the instance.
+     *
+     * @param configuration the class configurations.
+     * @throws ConfigurationException if an error occurs
+     */
+    public void configure(Configuration configuration)
+            throws ConfigurationException {
+        authRequired =
+            configuration.getChild("authRequired").getValueAsBoolean(false);
+    }
+
+    /**
+     * Handle a connection.
+     * This handler is responsible for processing connections as they occur.
+     *
+     * @param connection the connection
+     * @throws IOException if an error reading from socket occurs
+     * @throws ProtocolException if an error handling connection occurs
+     */
     public void handleConnection( Socket connection ) throws IOException {
         final Logger logger = getLogger();
         try {
@@ -93,8 +168,9 @@
             reader = new BufferedReader(new InputStreamReader(socket.getInputStream())) {
                     public String readLine() throws IOException {
                         String s = super.readLine();
-                        if ( DEBUG_PROTOCOL ) 
-                            logger.debug("C: " + s);
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("Command received: " + s);
+                        }
                         return s;
                     }
                 };
@@ -106,8 +182,9 @@
                     }
                     public void println(String s) {
                         super.println(s);
-                        if ( DEBUG_PROTOCOL )
-                            logger.debug("S: " + s);
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("Sent: " + s);
+                        }
                     }
                 };
             logger.info( "Connection from " + socket.getInetAddress());
@@ -127,8 +204,7 @@
                         .append(helloName)
                         .append(" NNTP Service Ready, posting prohibited");
                 writer.println(respBuffer.toString());
-            }
-            else {
+            } else {
                 StringBuffer respBuffer =
                     new StringBuffer(128)
                             .append("200 ")
@@ -145,8 +221,6 @@
             logger.info("Connection closed");
         } catch (Exception e) {
             doQUIT();
-            //writer.println("502 Error closing connection.");
-            //writer.flush();
             logger.error( "Exception during connection:" + e.getMessage(), e );
         } finally {
             try {
@@ -171,6 +245,13 @@
         }
     }
 
+    /**
+     * Callback method called when the the PeriodicTimeTrigger in 
+     * handleConnection is triggered.  In this case the trigger is
+     * being used as a timeout, so the method simply closes the connection.
+     *
+     * @param triggerName the name of the trigger
+     */
     public void targetTriggered( final String triggerName ) {
         getLogger().error("Connection timeout on socket");
         try {
@@ -195,101 +276,125 @@
         if (commandRaw == null) {
             return false;
         }
-        if (getLogger().isInfoEnabled()) {
-            getLogger().info("Command received: " + commandRaw);
+        if (getLogger().isDebugEnabled()) {
+            getLogger().debug("Command received: " + commandRaw);
         }
 
-        StringTokenizer tokens = new StringTokenizer(commandRaw);
-        if (!tokens.hasMoreTokens())
-            return false;
-        final String command = tokens.nextToken().toUpperCase(Locale.US);
+        String command = commandRaw.trim();
+        String argument = null;
+        int spaceIndex = command.indexOf(" ");
+        if (spaceIndex >= 0) {
+            argument = command.substring(spaceIndex + 1);
+            command = command.substring(0, spaceIndex);
+        }
+        command = command.toUpperCase(Locale.US);
 
-        if (!auth.isAuthorized(command) ) {
+        if (!isAuthorized(command) ) {
             writer.println("502 User is not authenticated");
             getLogger().debug("Command not allowed.");
             return true;
         }
-        if ( command.equals("MODE") && tokens.hasMoreTokens() &&
-             tokens.nextToken().toUpperCase(Locale.US).equals("READER") )
+        if ((command.equals("MODE")) && (argument != null) &&
+            argument.toUpperCase(Locale.US).equals("READER")) {
             doMODEREADER();
-        else if ( command.equals("LIST") && tokens.hasMoreTokens() &&
-                  tokens.nextToken().toUpperCase(Locale.US).equals("EXTENSIONS") )
-            doLISTEXTENSIONS();
-        else if ( command.equals("LIST") && tokens.hasMoreTokens() &&
-                  tokens.nextToken().toUpperCase(Locale.US).equals("OVERVIEW.FMT") )
-            doLISTOVERVIEWFMT();
-        else if ( command.equals("GROUP") )
-            doGROUP(tokens.hasMoreTokens()?tokens.nextToken():null);
-        else if ( command.equals("NEXT") )
+        } else if ( command.equals("LIST")) {
+            doLIST(argument);
+        } else if ( command.equals("GROUP") ) {
+            doGROUP(argument);
+        } else if ( command.equals("NEXT") ) {
             doNEXT();
-        else if ( command.equals("LAST") )
+        } else if ( command.equals("LAST") ) {
             doLAST();
-        else if ( command.equals("ARTICLE") )
-            doARTICLE(tokens.hasMoreTokens()?tokens.nextToken():null);
-        else if ( command.equals("HEAD") )
-            doHEAD(tokens.hasMoreTokens()?tokens.nextToken():null);
-        else if ( command.equals("BODY") )
-            doBODY(tokens.hasMoreTokens()?tokens.nextToken():null);
-        else if ( command.equals("STAT") )
-            doSTAT(tokens.hasMoreTokens()?tokens.nextToken():null);
-        else if ( command.equals("POST") )
+        } else if ( command.equals("ARTICLE") ) {
+            doARTICLE(argument);
+        } else if ( command.equals("HEAD") ) {
+            doHEAD(argument);
+        } else if ( command.equals("BODY") ) {
+            doBODY(argument);
+        } else if ( command.equals("STAT") ) {
+            doSTAT(argument);
+        } else if ( command.equals("POST") ) {
             doPOST();
-        else if ( command.equals("IHAVE") )
-            doIHAVE(tokens.hasMoreTokens()?tokens.nextToken():null);
-        else if ( command.equals("LIST") )
-            doLIST(tokens);
-        else if ( command.equals("QUIT") )
+        } else if ( command.equals("IHAVE") ) {
+            doIHAVE(argument);
+        } else if ( command.equals("QUIT") ) {
             doQUIT();
-        else if ( command.equals("DATE") )
+        } else if ( command.equals("DATE") ) {
             doDATE();
-        else if ( command.equals("HELP") )
+        } else if ( command.equals("HELP") ) {
             doHELP();
-        else if ( command.equals("NEWGROUPS") )
-            doNEWGROUPS(tokens);
-        else if ( command.equals("NEWNEWS") )
-            doNEWNEWS(tokens);
-        else if ( command.equals("LISTGROUP") )
-            doLISTGROUP(tokens.hasMoreTokens()?tokens.nextToken():null);
-        else if ( command.equals("OVER") )
-            doOVER(tokens.hasMoreTokens()?tokens.nextToken():null);
-        else if ( command.equals("XOVER") )
-            doXOVER(tokens.hasMoreTokens()?tokens.nextToken():null);
-        else if ( command.equals("PAT") )
+        } else if ( command.equals("NEWGROUPS") ) {
+            doNEWGROUPS(argument);
+        } else if ( command.equals("NEWNEWS") ) {
+            doNEWNEWS(argument);
+        } else if ( command.equals("LISTGROUP") ) {
+            doLISTGROUP(argument);
+        } else if ( command.equals("OVER") ) {
+            doOVER(argument);
+        } else if ( command.equals("XOVER") ) {
+            doXOVER(argument);
+        } else if ( command.equals("PAT") ) {
             doPAT();
-        else if ( command.equals("HDR") )
-            doHDR(tokens);
-        else if ( command.equals("XHDR") )
-            doXHDR(tokens);
-        else if ( command.equals("AUTHINFO") )
-            doAUTHINFO(tokens);
-        else
+        } else if ( command.equals("HDR") ) {
+            doHDR(argument);
+        } else if ( command.equals("XHDR") ) {
+            doXHDR(argument);
+        } else if ( command.equals("AUTHINFO") ) {
+            doAUTHINFO(argument);
+        } else {
             writer.println("501 Syntax error");
+        }
         return (command.equals("QUIT") == false);
     }
 
-    // implements only the originnal AUTHINFO
-    // for simple and generic AUTHINFO, 501 is sent back. This is as
-    // per article 3.1.3 of RFC 2980
-    private void doAUTHINFO(StringTokenizer tok) {
-        String command = tok.nextToken().toUpperCase(Locale.US);
+    /**
+     * Implements only the originnal AUTHINFO.
+     * for simple and generic AUTHINFO, 501 is sent back. This is as
+     * per article 3.1.3 of RFC 2980
+     *
+     * @param argument the argument passed in with the AUTHINFO command
+     */
+    private void doAUTHINFO(String argument) {
+        String command = null;
+        String value = null;
+        if (argument != null) {
+            int spaceIndex = argument.indexOf(" ");
+            if (spaceIndex >= 0) {
+                command = argument.substring(0, spaceIndex);
+                value = argument.substring(spaceIndex + 1);
+            }
+        }
+        if (command == null) {
+            // TODO: Take care of error condition here.
+            return;
+        }
         if ( command.equals("USER") ) {
-            auth.setUser(tok.nextToken());
+            user = value;
             writer.println("381 More authentication information required");
         } else if ( command.equals("PASS") ) {
-            auth.setPassword(tok.nextToken());
-            if ( auth.isAuthenticated() ) {
+            password = value;
+            if ( isAuthenticated() ) {
                 writer.println("281 Authentication accepted");
-            }
-            else {
+            } else {
                 writer.println("482 Authentication rejected");
+                // Clear bad authentication
+                user = null;
+                password = null;
             }
         }
     }
 
-    private void doNEWNEWS(StringTokenizer tok) {
+    /**
+     * Lists the articles posted since the date passed in as
+     * an argument.
+     *
+     * @param argument the argument passed in with the NEWNEWS command.
+     *                 Should be a date.
+     */
+    private void doNEWNEWS(String argument) {
         // see section 11.4
         writer.println("230 list of new articles by message-id follows");
-        Iterator iter = repo.getArticlesSince(getDateFrom(tok));
+        Iterator iter = repo.getArticlesSince(getDateFrom(argument));
         while ( iter.hasNext() ) {
             StringBuffer iterBuffer =
                 new StringBuffer(64)
@@ -300,7 +405,15 @@
         }
         writer.println(".");
     }
-    private void doNEWGROUPS(StringTokenizer tok) {
+
+    /**
+     * Lists the groups added since the date passed in as
+     * an argument.
+     *
+     * @param argument the argument passed in with the NEWNEWS command.
+     *                 Should be a date.
+     */
+    private void doNEWGROUPS(String argument) {
         // see section 11.3
         // there seeem to be few differences.
         // draft-ietf-nntpext-base-15.txt mentions 231 in section 11.3.1, 
@@ -310,7 +423,7 @@
         // '<group name> <last article> <first article> <posting allowed>'
         // NOTE: following INN over either document.
         writer.println("231 list of new newsgroups follows");
-        Iterator iter = repo.getGroupsSince(getDateFrom(tok));
+        Iterator iter = repo.getGroupsSince(getDateFrom(argument));
         while ( iter.hasNext() ) {
             NNTPGroup group = (NNTPGroup)iter.next();
             StringBuffer iterBuffer =
@@ -326,51 +439,20 @@
         }
         writer.println(".");
     }
-    // returns the date from @param input.
-    // The input tokens are assumed to be in format date time [GMT|UTC] .
-    // 'date' is in format [XX]YYMMDD. 'time' is in format 'HHMMSS'
-    // NOTE: This routine could do with some format checks.
-    private Date getDateFrom(StringTokenizer tok) {
-        String date = tok.nextToken();
-        String time = tok.nextToken();
-        boolean  utc = ( tok.hasMoreTokens() );
-        Date d = new Date();
-        try {
-            StringBuffer dateStringBuffer =
-                new StringBuffer(64)
-                    .append(date)
-                    .append(" ")
-                    .append(time);
-            Date dt = DF_RFC977.parse(dateStringBuffer.toString());
-            if ( utc )
-                dt = new Date(dt.getTime()+UTC_OFFSET);
-            return dt;
-        } catch ( ParseException pe ) {
-            StringBuffer exceptionBuffer =
-                new StringBuffer(128)
-                    .append("date extraction failed: ")
-                    .append(date)
-                    .append(",")
-                    .append(time)
-                    .append(",")
-                    .append(utc);
-            throw new NNTPException(exceptionBuffer.toString());
-        }
-    }
 
+    /**
+     * Lists the help text for the service.
+     *
+     * @param argument the argument passed in with the HELP command.
+     */
     private void doHELP() {
         writer.println("100 Help text follows");
         writer.println(".");
     }
 
-    // used to calculate DATE from - see 11.3
-    public static final SimplifiedDateFormat DF_RFC977 = new RFC977DateFormat();
-
-    // Date format for the DATE keyword - see 11.1.1
-    public static final SimplifiedDateFormat DF_RFC2980 = new RFC2980DateFormat();
-
-    public static final long UTC_OFFSET = Calendar.getInstance().get(Calendar.ZONE_OFFSET);
-
+    /**
+     * Returns the current date according to the news server.
+     */
     private void doDATE() {
         //Calendar c = Calendar.getInstance();
         //long UTC_OFFSET = c.get(c.ZONE_OFFSET) + c.get(c.DST_OFFSET);
@@ -379,34 +461,57 @@
         writer.println("111 "+dtStr);
     }
 
+    /**
+     * Quits the transaction.
+     */
     private void doQUIT() {
         writer.println("205 closing connection");
     }
 
-    private void doLIST(StringTokenizer tok) {
+    /**
+     * Handles the LIST command and its assorted extensions.
+     *
+     * @param argument the argument passed in with the LIST command.
+     */
+    private void doLIST(String argument) {
+
         // see section 9.4.1
         String wildmat = "*";
-        LISTGroup output = LISTGroup.Factory.ACTIVE(writer);
-        if ( tok.hasMoreTokens() ) {
-            String param = tok.nextToken().toUpperCase(Locale.US);
-            // list of variations not supported - 9.4.2.1, 9.4.3.1, 9.4.4.1
-            String[] notSupported = { "ACTIVE.TIMES", "DISTRIBUTIONS", "DISTRIB.PATS" };
-            // TODO: I don't understand what this loop is trying to accomplish -- PMG
-            for ( int i = 0 ; i < notSupported.length ; i++ ) {
-                if ( param.equals("ACTIVE.TIMES") ) {
-                    writer.println("503 program error, function not performed");
-                    return;
-                }
-            }
-            if ( param.equals("NEWSGROUPS") ) {
-                output = LISTGroup.Factory.NEWSGROUPS(writer);
-            }
-            else {
-                check(param,param.equals("ACTIVE"));
-            }
-            if ( tok.hasMoreTokens() ) {
-                wildmat = tok.nextToken();
-            }
+        LISTGroup output = null;
+
+        String extension = argument;
+        if (argument != null) {
+            int spaceIndex = argument.indexOf(" ");
+            if (spaceIndex >= 0) {
+                extension = argument.substring(0, spaceIndex);
+                wildmat = argument.substring(spaceIndex + 1);
+            }
+            extension = extension.toUpperCase(Locale.US);
+        }
+
+        if ((extension == null) || (extension.equals("ACTIVE"))) {
+            output = LISTGroup.Factory.ACTIVE(writer);
+        } else if (extension.equals("NEWSGROUPS") ) {
+            output = LISTGroup.Factory.NEWSGROUPS(writer);
+        } else if (extension.equals("EXTENSIONS") ) {
+            doLISTEXTENSIONS();
+        } else if (extension.equals("OVERVIEW.FMT") ) {
+            doLISTOVERVIEWFMT();
+        } else if (extension.equals("ACTIVE.TIMES") ) {
+            // not supported - 9.4.2.1, 9.4.3.1, 9.4.4.1
+            writer.println("503 program error, function not performed");
+            return;
+        } else if (extension.equals("DISTRIBUTIONS") ) {
+            // not supported - 9.4.2.1, 9.4.3.1, 9.4.4.1
+            writer.println("503 program error, function not performed");
+            return;
+        } else if (extension.equals("DISTRIB.PATS") ) {
+            // not supported - 9.4.2.1, 9.4.3.1, 9.4.4.1
+            writer.println("503 program error, function not performed");
+            return;
+        } else {
+            writer.println("501 Syntax error");
+            return;
         }
         Iterator iter = repo.getMatchedGroups(wildmat);
         writer.println("215 list of newsgroups follows");
@@ -415,24 +520,32 @@
         }
         writer.println(".");
     }
-    private void check(String id,boolean b) {
-        if ( b == false ) {
-            throw new RuntimeException(id);
-        }
-    }
+
+    /**
+     * Informs the server that the client has an article with the specified
+     * message-ID.
+     *
+     * @param id the message id
+     */
     private void doIHAVE(String id) {
         // see section 9.3.2.1
-        check(id,id.startsWith("<") && id.endsWith(">"));
+        if (!(id.startsWith("<") && id.endsWith(">"))) {
+            writer.println("501 command syntax error");
+            return;
+        }
         NNTPArticle article = repo.getArticleFromID(id);
-        if ( article != null )
+        if ( article != null ) {
             writer.println("435 article not wanted - do not send it");
-        else {
+        } else {
             writer.println("335 send article to be transferred. End with <CR-LF>.<CR-LF>");
             createArticle();
             writer.println("235 article received ok");
         }
     }
 
+    /**
+     * Posts an article to the news server.
+     */
     private void doPOST() {
         // see section 9.3.1.1
         writer.println("340 send article to be posted. End with <CR-LF>.<CR-LF>");
@@ -440,22 +553,50 @@
         writer.println("240 article received ok");
     }
 
-    private void createArticle() {
-        repo.createArticle(new NNTPLineReaderImpl(reader));
-    }
-
+    /**
+     * Sets the current article pointer.
+     *
+     * @param the argument passed in to the HEAD command,
+     *        which should be an article number or message id.
+     *        If no parameter is provided, the current selected
+     *        article is used.
+     */
     private void doSTAT(String param) {
         doARTICLE(param,ArticleWriter.Factory.STAT(writer));
     }
 
+    /**
+     * Displays the body of a particular article.
+     *
+     * @param the argument passed in to the HEAD command,
+     *        which should be an article number or message id.
+     *        If no parameter is provided, the current selected
+     *        article is used.
+     */
     private void doBODY(String param) {
         doARTICLE(param,ArticleWriter.Factory.BODY(writer));
     }
 
+    /**
+     * Displays the header of a particular article.
+     *
+     * @param the argument passed in to the HEAD command,
+     *        which should be an article number or message id.
+     *        If no parameter is provided, the current selected
+     *        article is used.
+     */
     private void doHEAD(String param) {
         doARTICLE(param,ArticleWriter.Factory.HEAD(writer));
     }
 
+    /**
+     * Displays the header and body of a particular article.
+     *
+     * @param the argument passed in to the HEAD command,
+     *        which should be an article number or message id.
+     *        If no parameter is provided, the current selected
+     *        article is used.
+     */
     private void doARTICLE(String param) {
         doARTICLE(param,ArticleWriter.Factory.ARTICLE(writer));
     }
@@ -465,9 +606,9 @@
         NNTPArticle article = null;
         if ( (param != null) && param.startsWith("<") && param.endsWith(">") ) {
             article = repo.getArticleFromID(param);
-            if ( article == null )
+            if ( article == null ) {
                 writer.println("430 no such article");
-            else {
+            } else {
                 StringBuffer respBuffer =
                     new StringBuffer(64)
                             .append("220 0 ")
@@ -475,22 +616,22 @@
                             .append(" article retrieved and follows");
                 writer.println(respBuffer.toString());
             }
-        }
-        else {
-            if ( group == null )
+        } else {
+            if ( group == null ) {
                 writer.println("412 no newsgroup selected");
-            else {
+            } else {
                 if ( param == null ) {
-                    if ( group.getCurrentArticleNumber() < 0 )
+                    if ( group.getCurrentArticleNumber() < 0 ) {
                         writer.println("420 no current article selected");
-                    else
+                    } else {
                         article = group.getCurrentArticle();
+                    }
                 }
                 else
                     article = group.getArticle(Integer.parseInt(param));
-                if ( article == null )
+                if ( article == null ) {
                     writer.println("423 no such article number in this group");
-                else {
+                } else {
                     StringBuffer respBuffer =
                         new StringBuffer(128)
                                 .append("220 ")
@@ -502,19 +643,23 @@
                 }
             }
         }
-        if ( article != null )
+        if ( article != null ) {
             articleWriter.write(article);
+        }   
     }
 
+    /**
+     * Advances the current article pointer to the next article in the group.
+     */
     private void doNEXT() {
         // section 9.1.1.3.1
-        if ( group == null )
+        if ( group == null ) {
             writer.println("412 no newsgroup selected");
-        else if ( group.getCurrentArticleNumber() < 0 )
+        } else if ( group.getCurrentArticleNumber() < 0 ) {
             writer.println("420 no current article has been selected");
-        else if ( group.getCurrentArticleNumber() >= group.getLastArticleNumber() )
+        } else if ( group.getCurrentArticleNumber() >= group.getLastArticleNumber() ) {
             writer.println("421 no next article in this group");
-        else {
+        } else {
             group.setCurrentArticleNumber(group.getCurrentArticleNumber()+1);
             NNTPArticle article = group.getCurrentArticle();
             StringBuffer respBuffer =
@@ -527,15 +672,19 @@
         }
     }
 
+    /**
+     * Advances the currently selected article pointer to the last article
+     * in the selected group.
+     */
     private void doLAST() {
         // section 9.1.1.2.1
-        if ( group == null )
+        if ( group == null ) {
             writer.println("412 no newsgroup selected");
-        else if ( group.getCurrentArticleNumber() < 0 )
+        } else if ( group.getCurrentArticleNumber() < 0 ) {
             writer.println("420 no current article has been selected");
-        else if ( group.getCurrentArticleNumber() <= group.getFirstArticleNumber() )
+        } else if ( group.getCurrentArticleNumber() <= group.getFirstArticleNumber() ) {
             writer.println("422 no previous article in this group");
-        else {
+        } else {
             group.setCurrentArticleNumber(group.getCurrentArticleNumber()-1);
             NNTPArticle article = group.getCurrentArticle();
             StringBuffer respBuffer =
@@ -548,12 +697,18 @@
         }
     }
 
+    /**
+     * Selects a group to be the current newsgroup.
+     *
+     * @param group the name of the group being selected.
+     */
     private void doGROUP(String groupName) {
-        group = repo.getGroup(groupName);
+        NNTPGroup newGroup = repo.getGroup(groupName);
         // section 9.1.1.1
-        if ( group == null )
+        if ( newGroup == null ) {
             writer.println("411 no such newsgroup");
-        else {
+        } else {
+            group = newGroup;
             // if the number of articles in group == 0
             // then the server may return this information in 3 ways, 
             // The clients must honor all those 3 ways.
@@ -577,6 +732,9 @@
         }
     }
 
+    /**
+     * Lists the extensions supported by this news server.
+     */
     private void doLISTEXTENSIONS() {
         // 8.1.1
         writer.println("202 Extensions supported:");
@@ -589,28 +747,41 @@
         writer.println(".");
     }
 
+    /**
+     * Informs the server that the client is a newsreader.
+     */
     private void doMODEREADER() {
         // 7.2
         writer.println(repo.isReadOnly()
                        ? "201 Posting Not Permitted" : "200 Posting Permitted");
     }
 
+    /**
+     * Gets a listing of article numbers in specified group name
+     * or in the already selected group if the groupName is null.
+     *
+     * @param groupName the name of the group to list
+     */
     private void doLISTGROUP(String groupName) {
         // 9.5.1.1.1
-        NNTPGroup group = null;
         if (groupName==null) {
-            if ( group == null )
+            if ( group == null) {
                 writer.println("412 not currently in newsgroup");
+                return;
+            }
         }
         else {
             group = repo.getGroup(groupName);
-            if ( group == null )
+            if ( group == null ) {
                 writer.println("411 no such newsgroup");
+                return;
+            }
         }
         if ( group != null ) {
             writer.println("211 list of article numbers follow");
 
-            for (Iterator iter = group.getArticles();iter.hasNext();) {
+            Iterator iter = group.getArticles();
+            while (iter.hasNext()) {
                 NNTPArticle article = (NNTPArticle)iter.next();
                 writer.println(article.getArticleNumber());
             }
@@ -620,33 +791,57 @@
         }
     }
 
+    /**
+     * Handles the LIST OVERVIEW.FMT command.  Not supported.
+     */
     private void doLISTOVERVIEWFMT() {
         // 9.5.3.1.1
         // 503 means information is not available as per 9.5.2.1
         writer.println("503 program error, function not performed");
     }
+
     private void doPAT() {
         // 9.5.3.1.1 in draft-12
         writer.println("500 Command not recognized");
     }
-    private void doXHDR(StringTokenizer tok) {
-        doHDR(tok);
+
+    /**
+     * Get the values of the headers for the selected newsgroup, 
+     * with an optional range modifier.
+     *
+     * @param argument the argument passed in with the XHDR command.
+     */
+    private void doXHDR(String argument) {
+        doHDR(argument);
     }
-    private void doHDR(StringTokenizer tok) {
+
+    /**
+     * Get the values of the headers for the selected newsgroup, 
+     * with an optional range modifier.
+     *
+     * @param argument the argument passed in with the HDR command.
+     */
+    private void doHDR(String argument) {
         // 9.5.3
-        String hdr = tok.nextToken();
-        String range = tok.hasMoreTokens() ? tok.nextToken() : null;
+        String hdr = argument;
+        String range = null;
+        int spaceIndex = argument.indexOf(" ");
+        if (spaceIndex >= 0 ) {
+            range = hdr.substring(spaceIndex + 1);
+            hdr = hdr.substring(0, spaceIndex);
+        }
         NNTPArticle[] article = getRange(range);
-        if ( article == null )
+        if ( article == null ) {
             writer.println("412 no newsgroup selected");
-        else if ( article.length == 0 )
+        } else if ( article.length == 0 ) {
             writer.println("430 no such article");
-        else {
+        } else {
             writer.println("221 Header follows");
             for ( int i = 0 ; i < article.length ; i++ ) {
                 String val = article[i].getHeader(hdr);
-                if ( val == null )
+                if ( val == null ) {
                     val = "";
+                }
                 StringBuffer hdrBuffer =
                     new StringBuffer(128)
                             .append(article[i].getArticleNumber())
@@ -657,9 +852,80 @@
             writer.println(".");
         }
     }
-    // returns the list of articles that match the range.
-    // @return null indicates insufficient information to
-    // fetch the list of articles
+
+    private void doXOVER(String range) {
+        doOVER(range);
+    }
+
+    private void doOVER(String range) {
+        // 9.5.2.2.1
+        if ( group == null ) {
+            writer.println("412 No newsgroup selected");
+            return;
+        }
+        NNTPArticle[] article = getRange(range);
+        ArticleWriter articleWriter = ArticleWriter.Factory.OVER(writer);
+        if ( article.length == 0 ) {
+            writer.println("420 No article(s) selected");
+        } else {
+            writer.println("224 Overview information follows");
+            for ( int i = 0 ; i < article.length ; i++ ) {
+                articleWriter.write(article[i]);
+            }
+            writer.println(".");
+        }
+    }
+
+    /**
+     * Handles the transaction for getting the article data.
+     */
+    private void createArticle() {
+        repo.createArticle(new NNTPLineReaderImpl(reader));
+    }
+
+    /**
+     * Returns the date from @param input.
+     * The input tokens are assumed to be in format date time [GMT|UTC] .
+     * 'date' is in format [XX]YYMMDD. 'time' is in format 'HHMMSS'
+     * NOTE: This routine could do with some format checks.
+     *
+     * @param argument the date string
+     */
+    private Date getDateFrom(String argument) {
+        StringTokenizer tok = new StringTokenizer(argument, " ");
+        String date = tok.nextToken();
+        String time = tok.nextToken();
+        boolean utc = ( tok.hasMoreTokens() );
+        Date d = new Date();
+        try {
+            StringBuffer dateStringBuffer =
+                new StringBuffer(64)
+                    .append(date)
+                    .append(" ")
+                    .append(time);
+            Date dt = DF_RFC977.parse(dateStringBuffer.toString());
+            if ( utc ) {
+                dt = new Date(dt.getTime()+UTC_OFFSET);
+            }
+            return dt;
+        } catch ( ParseException pe ) {
+            StringBuffer exceptionBuffer =
+                new StringBuffer(128)
+                    .append("date extraction failed: ")
+                    .append(date)
+                    .append(",")
+                    .append(time)
+                    .append(",")
+                    .append(utc);
+            throw new NNTPException(exceptionBuffer.toString());
+        }
+    }
+
+    /**
+     * Returns the list of articles that match the range.
+     * @return null indicates insufficient information to
+     * fetch the list of articles
+     */
     private NNTPArticle[] getRange(String range) {
         // check for msg id
         if ( range != null && range.startsWith("<") ) {
@@ -668,10 +934,12 @@
                 ? new NNTPArticle[0] : new NNTPArticle[] { article };
         }
 
-        if ( group == null )
+        if ( group == null ) {
             return null;
-        if ( range == null )
-            range = ""+group.getCurrentArticleNumber();
+        }
+        if ( range == null ) {
+            range = "" + group.getCurrentArticleNumber();
+        }
 
         int start = -1;
         int end = -1;
@@ -680,10 +948,11 @@
             start = end = Integer.parseInt(range);
         } else {
             start = Integer.parseInt(range.substring(0,idx));
-            if ( idx+1 == range.length() )
+            if ( idx+1 == range.length() ) {
                 end = group.getLastArticleNumber();
-            else
+            } else {
                 end = Integer.parseInt(range.substring(idx+1));
+            }
         }
         List list = new ArrayList();
         for ( int i = start ; i <= end ; i++ ) {
@@ -695,27 +964,40 @@
         return (NNTPArticle[])list.toArray(new NNTPArticle[0]);
     }
 
-    private void doXOVER(String range) {
-        doOVER(range);
+    /**
+     * Return whether the user associated with the connection (possibly no
+     * user) is authorized to execute the command.
+     *
+     * @param the command being tested
+     * @return whether the command is authorized
+     */
+    public boolean isAuthorized(String command) {
+        boolean allowed = isAuthenticated();
+        if (allowed) {
+            return true;
+        }
+        // some commands are authorized, even if the user is not authenticated
+        allowed = allowed || command.equals("AUTHINFO");
+        allowed = allowed || command.equals("MODE");
+        allowed = allowed || command.equals("QUIT");
+        return allowed;
     }
 
-    private void doOVER(String range) {
-        // 9.5.2.2.1
-        if ( group == null ) {
-            writer.println("412 No newsgroup selected");
-            return;
-        }
-        NNTPArticle[] article = getRange(range);
-        ArticleWriter articleWriter = ArticleWriter.Factory.OVER(writer);
-        if ( article.length == 0 ) {
-            writer.println("420 No article(s) selected");
-        }
-        else {
-            writer.println("224 Overview information follows");
-            for ( int i = 0 ; i < article.length ; i++ ) {
-                articleWriter.write(article[i]);
+    /**
+     * Return whether the connection has been authenticated.
+     *
+     * @return whether the connection has been authenticated.
+     */
+    public boolean isAuthenticated() {
+        if ( authRequired ) {
+            if  ((user != null) && (password != null) && (userRepository != null)) {
+                return userRepository.test(user,password);
+            } else {
+                return false;
             }
-            writer.println(".");
+        } else {
+            return true;
         }
     }
+
 }

