Package me.mabra.hellonzb.nntpclient.nioengine

Source Code of me.mabra.hellonzb.nntpclient.nioengine.NettyNioClientHandler

/*******************************************************************************
* HelloNzb -- The Binary Usenet Tool
* Copyright (C) 2010-2013 Matthias F. Brandstetter
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package me.mabra.hellonzb.nntpclient.nioengine;

import me.mabra.hellonzb.HelloNzbConstants;
import me.mabra.hellonzb.util.MyLogger;
import me.mabra.hellonzb.util.StringLocaler;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.*;
import org.jboss.netty.handler.ssl.SslHandler;

import java.nio.charset.Charset;
import java.util.Arrays;


public class NettyNioClientHandler extends SimpleChannelUpstreamHandler
{
  /** Constants of NNTP status codes as byte arrays */
  private static final Charset DEFAULT_CHARSET = Charset.defaultCharset();
  private enum StatusCodes
  {
    STATUS_200 ("200 ".getBytes(DEFAULT_CHARSET)),
    STATUS_201 ("201 ".getBytes(DEFAULT_CHARSET)),
    STATUS_211 ("211 ".getBytes(DEFAULT_CHARSET)),
    STATUS_221 ("221 ".getBytes(DEFAULT_CHARSET)),
    STATUS_281 ("281 ".getBytes(DEFAULT_CHARSET)),
    STATUS_381 ("381 ".getBytes(DEFAULT_CHARSET)),
    STATUS_502 ("502 ".getBytes(DEFAULT_CHARSET)),
    STATUS_22x ("22".getBytes(DEFAULT_CHARSET));

    byte [] bytes;

    StatusCodes(byte [] b)
    {
      bytes = b;
    }
  }

  /** The Netty NIO client object */
  private NettyNioClient nettyNioClient;
   
  /** The StringLocaler object of the main application */
  private StringLocaler localer;

  /** central logger object */
  private MyLogger logger;
 
  /** This object is used to handle all SocketChannels and relevant meta information */
  private NettyChannelManager ncMgr;
 
  /** Usenet server username */
  private String username;
   
  /** "use SSL" flag */
  private boolean useSSL;
 
 
  /**
   * Class constructor.
   *
   * @param client The NettyNioClient object (parent of this client handler)
   * @param loc The StringLocaler object to use
   * @param ncMgr The NettyChannelManager object to use
   * @param user The username to use for NNTP authentication
   */
  public NettyNioClientHandler(NettyNioClient client, StringLocaler loc, MyLogger logger,
      NettyChannelManager ncMgr, String user, boolean useSSL)
  {
    this.nettyNioClient = client;
    this.localer = loc;
    this.logger = logger;
    this.ncMgr = ncMgr;
    this.username = user;
    this.useSSL = useSSL;
  }
 
  @Override
  public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception
  {
    // handle upstream event
    super.handleUpstream(ctx, e);
  }
 
  @Override
  public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
  {
    // add the newly opened channel to the global channel group
    nettyNioClient.getChannelGroup().add(e.getChannel());
  }
 
  @Override
  public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception
  {
    if(useSSL)
    {
      // get SSL handler from pipeline
      SslHandler sslHandler = ctx.getPipeline().get(SslHandler.class);
     
      // begin handshake
      ChannelFuture future = sslHandler.handshake();
      future.awaitUninterruptibly();
      if(!future.isSuccess())
      {
        logger.msg("SSL handshake error", MyLogger.SEV_FATAL);
        ctx.getChannel().close().awaitUninterruptibly();
      }
    }
  }
 
  @Override
  public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception
  {
    // get SSL handler from pipeline and close it
    if(useSSL)
    {
      SslHandler sslHandler = ctx.getPipeline().get(SslHandler.class);
      sslHandler.close().awaitUninterruptibly();
    }
  }
 
  @Override
  public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
  {
    ChannelBuffer buf = (ChannelBuffer) e.getMessage();
    int numBytes = buf.readableBytes();
    if(numBytes > 0)
    {
      // inform client about amount of loaded bytes
      nettyNioClient.addToDownloadedBytes(numBytes);

      // process fetched data
      byte [] data = buf.array();
      handleResponse(e.getChannel(), data);
    }
  }
 
  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
  {
    Throwable t = e.getCause();
    if(HelloNzbConstants.DEBUG)
    {
      logger.printStackTrace(t);
      return;
    }

    String msg = t.getMessage();
    if(msg != null &&
        !msg.endsWith("An existing connection was forcibly closed by the remote host") &&
        !msg.endsWith("Connection reset by peer"))
    {
      logger.printStackTrace(t);
    }

    // TODO: handle exception (??)
  }
 
  /**
   * Handle a response from a socket channel.
   *
   * @param channel The Netty channel which received the data
   * @param rspData The data received / to process as byte array
   */
  private void handleResponse(Channel channel, byte [] rspData)
  {
    // process server response depending on current channel status
    ChannelStatus status = ncMgr.getNCStatus(channel);
    String response = HelloNzbConstants.DEBUG ? new String(rspData) : null;
   
    if(status == null)
      return;
   
    switch(status)
    {
      case INIT:
        // initial connection reply
        if(this.username == null || this.username.length() == 0)
        {
          if(procReply(channel, rspData, ChannelStatus.IDLE, RspHandler.ERR_CONN,
                 StatusCodes.STATUS_200, StatusCodes.STATUS_201))
          {
            nettyNioClient.updThreadView(channel, localer.getBundleText("ThreadViewStatusConnected"));
          }
 
        }
        else
          procReply(channel, rspData, ChannelStatus.W_AUTH_USER, RspHandler.ERR_CONN,
                StatusCodes.STATUS_200, StatusCodes.STATUS_201);

        nettyNioClient.printDebugMsg(response, channel);
        break;
       
      case R_AUTH_USER:
        // reply from "AUTHINFO USER" command
        procReply(channel, rspData, ChannelStatus.W_AUTH_PASS, RspHandler.ERR_AUTH,
              StatusCodes.STATUS_381, StatusCodes.STATUS_502);
        nettyNioClient.printDebugMsg(response, channel);
        break;
       
      case R_AUTH_PASS:
        // reply from "AUTHINFO PASS" command
        if(procReply(channel, rspData, ChannelStatus.IDLE, RspHandler.ERR_AUTH,
               StatusCodes.STATUS_281, StatusCodes.STATUS_502))
        {
          nettyNioClient.updThreadView(channel, localer.getBundleText("ThreadViewStatusConnected"));
        }
         
        nettyNioClient.printDebugMsg(response, channel);
        break;
   
      case GROUP_SENT:
        // reply from "GROUP" command
        if(procReply(channel, rspData, ChannelStatus.READY, RspHandler.ERR_GROUP, StatusCodes.STATUS_211))
        {
          nettyNioClient.setGroupCmdSuccessful(true);
        }
       
        nettyNioClient.printDebugMsg(response, channel);
        break;
       
      case START_RECEIVE:
      case RECEIVING_DATA:
        nettyNioClient.printDebugMsg(response, channel);
       
        // TODO: RspHandler data reset (auf 0 setzen) und download neu starten,
        // wenn wir nach einem verbindungsabbruch waehrend RECEIVING_DATA nicht
        // mehr wirklich weitere daten eines artikels bekommen (weil stattdessen
        // neu authentifiziert werden muesste, und stattdessen eine fehlermeldung
        // an diesen punkt kommt)
       
        if(!nettyNioClient.isTestSegAvailability())
        {
          boolean err = false;
          if(status == ChannelStatus.START_RECEIVE)
          {
            if(!procReply(channel, rspData, ChannelStatus.RECEIVING_DATA, RspHandler.ERR_FETCH, StatusCodes.STATUS_22x))
              err = true;
          }
         
          if(!err)
          {
            // Look up the handler for this channel
            RspHandler handler = ncMgr.getRspHandler(channel);
 
            // send new data package to the handler object
            handler.handleResponse(rspData);
           
            // article finished?
            if(articleFinished(rspData, channel))
            {
              // article data complete
              ncMgr.removeMD(channel);
              ncMgr.removeRspHandler(channel);
 
              // update thread view
              nettyNioClient.updThreadView(channel,
                  localer.getBundleText("ThreadViewStatusConnected"));
 
              handler.setFinished();
              ncMgr.setNCStatus(channel, ChannelStatus.IDLE);
              nettyNioClient.newIdleChannel();
            }
            else
              // more data to come
              ncMgr.setNCStatus(channel, ChannelStatus.RECEIVING_DATA);
          }
        }
        else
        {
          if(response == null)
            response = new String(rspData);

          // segment availability check
          if(status == ChannelStatus.START_RECEIVE)
          {
            boolean good = procReply(
                channel, rspData, ChannelStatus.READY, RspHandler.ERR_FETCH, StatusCodes.STATUS_221);
           
            if(good && !response.endsWith(".\r\n"))
              // more data to come (from HEAD command)
              ncMgr.setNCStatus(channel, ChannelStatus.RECEIVING_DATA);
          }
          else if(response.endsWith(".\r\n"))
            ncMgr.setNCStatus(channel, ChannelStatus.READY);
        }
        break;

      case FINISHED:
        // after QUIT command
        nettyNioClient.printDebugMsg(response, channel);
     
        // update thread view
        nettyNioClient.updThreadView(channel, localer.getBundleText("ThreadViewStatusIdle"));
           
        // remove this channel from all global lists
        ncMgr.cleanup(channel, true);
           
        if(nettyNioClient.isTestSegAvailability())
          nettyNioClient.shutdown(false, 0, false);
       
        break;
    }
  }

  /**
   * Process the server reply.
   * Helper method for the handleResponse() method.
   *
   * @param response The server response to parse
   * @param validStatusCodes An array of valid status codes
   * @param status The status to set this channel if response was good
   * @param handlerErrorCode The error code to set in case of non-good server reply
   * @return true if the response was good, false otherwise
   */
  private boolean procReply(Channel channel, byte [] response, ChannelStatus status, int handlerErrorCode,
                StatusCodes... validStatusCodes)
  {
    boolean good = false;
    byte [] responsePrefix;

    for(StatusCodes validCode : validStatusCodes)
    {
      responsePrefix = Arrays.copyOfRange(response, 0, validCode.bytes.length);
      if(Arrays.equals(responsePrefix, validCode.bytes))
      {
        good = true;
        break;
      }
    }

    if(good)
      // response = good
      ncMgr.setNCStatus(channel, status);
   
    else
    {
      // response = bad
      String responseString = new String(response);
     
      // special case: 430 no such article
      if(handle430Error(channel, responseString))
        return good;
     
      // all other errors
      ncMgr.setNCStatus(channel, ChannelStatus.SERVER_ERROR);
      nettyNioClient.updThreadView(channel, responseString);
      RspHandler handler = ncMgr.getRspHandler(channel);
      if(handler != null)
      {
        handler.setError(handlerErrorCode, responseString);
        handler.setFinished();
      }
      else if(nettyNioClient.isTestAuthFlag())
      {
        nettyNioClient.authTestError();
        ncMgr.setNCStatus(channel, ChannelStatus.TO_QUIT);
      }
      else
        logger.msg("No RspHandler found!", MyLogger.SEV_DEBUG);
    }

    return good;
  }
 
  /**
   * Handle a "430 No such article" error.
   *
   * @param channel The current channel
   * @param response The response from the server as a string
   * @return True if an 430 error was encountered, false otherwise
   */
  private boolean handle430Error(Channel channel, String response)
  {
    if(!response.startsWith("430 "))
      return false;
   
    RspHandler handler = ncMgr.getRspHandler(channel);
   
    ncMgr.removeMD(channel);
    ncMgr.removeRspHandler(channel);

    // update thread view and log
    nettyNioClient.updThreadView(channel, localer.getBundleText("ThreadViewStatusConnected"));

    handler.setError(RspHandler.ERR_FETCH_430, response);
    handler.setFinished();
    ncMgr.setNCStatus(channel, ChannelStatus.IDLE);
    nettyNioClient.newIdleChannel();
   
    return true;
  }

  /**
   * Check if the data received ended with a line containing only a single dot.
   *
   * @param data The data to parse
   * @return whether or not the data ended with the single dot
   */
  private boolean articleFinished(byte[] data, Channel channel)
  {
    int l = data.length;
    byte [] toCheck = null;
    byte [] l4b = ncMgr.getLast4Bytes(channel);
   
    // enough bytes to process in this method?
    if(l >= 5)
    {
      // yes, so save the last 4 byte for the next iteration
      l4b[0] = data[l-4];
      l4b[1] = data[l-3];
      l4b[2] = data[l-2];
      l4b[3] = data[l-1];
      ncMgr.setLast4Bytes(channel, l4b);
     
      toCheck = new byte[] { data[l-5], data[l-4], data[l-3], data[l-2], data[l-1] };
    }
    else
    {
      // not enough bytes to process, so concatenate the last 4 plus the few from now
      toCheck = new byte[l + 4];
      for(int i = 0; i < 4; i++)
        toCheck[i] = l4b[i];
      for(int i = 0; i < l; i++)
        toCheck[i+4] = data[i];
     
      // also save last 4 bytes again
      l = toCheck.length;
      l4b[0] = toCheck[l-4];
      l4b[1] = toCheck[l-3];
      l4b[2] = toCheck[l-2];
      l4b[3] = toCheck[l-1];
      ncMgr.setLast4Bytes(channel, l4b);
    }
         
    // CR LF . CR LF (checked in reverse order)
    l = toCheck.length;
    iftoCheck[l-1] == 10 && toCheck[l-2] == 13 && toCheck[l-3] == 46 &&
      toCheck[l-4] == 10 && toCheck[l-5] == 13)
      return true;
    else
      return false;
  }
}

































TOP

Related Classes of me.mabra.hellonzb.nntpclient.nioengine.NettyNioClientHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.