FTP PUT PATCH/EXTENSION

I would like to submit the extension for PUT method in the package libwww/Library/src/HTFTP.c

The current distributed version does not support any method other than the GET method , though this is not mentioned in any of the documents.

By incorporating these changes we can PUT documents on the FTP servers.

I have done minimal changes in the FTPEvent and HTFTPGetData functions. 

If you have any questions or problems with this let me know. I've been running with this fix in my code for a few days and have had no problems.

The changes are only in the following cases

FTP_BEGIN of FTPEvent()
NEED_ACTION of HTFTPGetData()
NEED_STREAM of HTFTPGetData()

The code changes are  deleimited by 'begin siddheshwark" and "end siddheshwark"
The rest of the code is same.

Thanks ..
Siddheshwar Kumar


--------------------------------------------DISTRIBUTED VERSION-----------------------------------------------------------------

PRIVATE int FTPEvent (SOCKET soc, void * pVoid, HTEventType type)
{
    ftp_ctrl * ctrl = (ftp_ctrl *) pVoid;
    ftp_data * data = (ftp_data *) HTNet_context(ctrl->dnet);
    int status = HT_ERROR;
    HTNet * cnet = ctrl->cnet;
    HTRequest * request = HTNet_request(cnet);
    HTParentAnchor * anchor = HTRequest_anchor(request);
    char * url = HTAnchor_physical(anchor);

    HTHost *host = HTNet_host(cnet);


    if (type == HTEvent_CLOSE) {         /* Interrupted */
        if (soc == HTNet_socket(cnet))
     FTPCleanup(request, HT_INTERRUPTED);
 else
     FTPCleanup(request, HT_LOADED);
 return HT_OK;
    } else if (type == HTEvent_TIMEOUT) {
 HTRequest_addError(request, ERR_FATAL, NO, HTERR_TIME_OUT,
      NULL, 0, "HTLoadHTTP");
 FTPCleanup(request, HT_TIMEOUT);
 return HT_OK;

    } else {
 ctrl = (ftp_ctrl *) HTNet_context(cnet); /* Get existing copy */
 data = (ftp_data *) HTNet_context(ctrl->dnet);
    }

    /* Now jump into the machine. We know the state from the previous run */
    while (1) {
 switch (ctrl->state) {
   case FTP_BEGIN:
       HTTRACE(PROT_TRACE, "FTP Event... now in state FTP_BEGIN\n");

       /* Only handle GET requests for now */
       if (HTRequest_method(request) != METHOD_GET) {
    HTTRACE(PROT_TRACE, "FTP Event... This module only supports the GET method\n");
    ctrl->state = FTP_ERROR;
    break;
       }

       HTFTPParseURL(request, url, ctrl, data);

       /* The following is added by Neil Griffin, GAIN Software */

       /*
       ** If the user hasn't specified a permanent transfer type, then
       ** use the transfer type specified at the end of the URL.
       */
       if (g_FTPTransferMode == FTP_DEFAULT_TRANSFER_MODE) {
    switch (data->type) {
    case 'a' : data->type = 'A'; break;
    case 'A' : data->type = 'A'; break;
    case 'i' : data->type = 'I'; break;
    case 'I' : data->type = 'I'; break;
    case 'd' : FTPListType(data, ctrl->server); break;
    case 'D' : FTPListType(data, ctrl->server); break;
    default  : data->type = 'I'; break;
    }

    /* Otherwise, use the permanent transfer type specified by the user. */
       } else {
    switch (g_FTPTransferMode) {
    case FTP_ASCII_TRANSFER_MODE  : data->type = 'A'; break;
    case FTP_BINARY_TRANSFER_MODE : data->type = 'I'; break;
    case FTP_DIR_TRANSFER_MODE    : FTPListType(data, ctrl->server); break;
    default                       : data->type = 'I'; break;
    }
       }

       HTTRACE(PROT_TRACE, "FTP Event... Transfer mode set to '%c'\n" _ data->type);

       /*
       **  See if we can get any hints to what we might expect content wise.
       */
       if (!FTP_DIR(data)) HTBind_getAnchorBindings(anchor);

              /* Ready for next state */
              ctrl->state = FTP_NEED_CCON;
              break;

 case FTP_NEED_CCON:
     HTTRACE(PROT_TRACE, "FTP Event... now in state FTP_NEED_CONN\n");
     status = HTHost_connect(host, cnet, url, FTP_PORT);
     host = HTNet_host(cnet);
     if (status == HT_OK) {

  /*
  ** Check the protocol class to see if we have connected to a
  ** the right class of server, in this case HTTP.
  */
  {
      char * s_class = HTHost_class(host);
      if (s_class && strcasecomp(s_class, "ftp")) {
   HTRequest_addError(request, ERR_FATAL, NO, HTERR_CLASS,
        NULL, 0, "HTLoadNews");
   ctrl->state = FTP_ERROR;
   break;
      }
      HTHost_setClass(host, "ftp");
  }

  /* Check persistent connection */
  if (HTNet_persistent(cnet)) {
      ctrl->server = HTHost_version(host);
      HTTRACE(PROT_TRACE, "FTP Server.. Cache says type %d server\n" _ 
    ctrl->server);
      ctrl->reset = 1;
  } else
      HTNet_setPersistent(cnet, YES, HT_TP_SINGLE);

  /* 
  ** Create the stream pipe FROM the channel to the application.
  ** The target for the input stream pipe is set up using the
  ** stream stack.
  */
  {
      HTStream * readstream = FTPStatus_new(request, ctrl, host);
      HTNet_setReadStream(cnet, readstream);
  }

  /*
  ** Create the stream pipe TO the channel from the application
  ** and hook it up to the request object
  */
  {
      HTOutputStream * output = HTNet_getOutput(cnet, NULL, 0);
      HTRequest_setInputStream(request, (HTStream *) output);
  }

  /*
  ** Set up concurrent read/write if this request isn't the
  ** source for a PUT or POST. As source we don't start reading
  ** before all destinations are ready. If destination then
  ** register the input stream and get ready for read
  */
  if (HTRequest_isPostWeb(request)) {
      HTEvent * event = HTNet_event(cnet);
      HTEvent_register(HTNet_socket(cnet), HTEvent_READ, event);
      HTRequest_linkDestination(request);
  }

  ctrl->state = FTP_NEED_LOGIN;
     } else if (status == HT_WOULD_BLOCK || status == HT_PENDING)
  return HT_OK;
     else
  ctrl->state = FTP_ERROR;        /* Error or interrupt */
     break;

   case FTP_NEED_LOGIN:
     HTTRACE(PROT_TRACE, "FTP Event... now in state FTP_NEED_LOGIN\n");
     status = HTFTPLogin(request, cnet, ctrl);
      if (status == HT_WOULD_BLOCK) return HT_OK;
     ctrl->state = (status == HT_OK) ? FTP_NEED_DCON : FTP_ERROR;
     break;

   case FTP_NEED_DCON:
     HTTRACE(PROT_TRACE, "FTP Event... now in state FTP_NEED_DCON\n");
     status = HTFTPDataConnection(request, cnet, ctrl, data);
     if (status == HT_WOULD_BLOCK) return HT_OK;
     if (status == HT_OK)
  ctrl->state = (data->type=='N') ?
      FTP_NEED_SERVER : FTP_NEED_DATA;
     else
  ctrl->state = FTP_ERROR;
     break;

   case FTP_NEED_DATA:
     HTTRACE(PROT_TRACE, "FTP Event... now in state FTP_NEED_DATA\n");
     status = HTFTPGetData(request, cnet, soc, ctrl, data);
      if (status == HT_WOULD_BLOCK) return HT_OK;
     if (status == HT_LOADED)
  ctrl->state = FTP_SUCCESS;
     else if (status == HT_OK)
  ctrl->state = FTP_NEED_DCON;
     else if (!FTP_DIR(data) && !data->stream_error) {
  FTPListType(data, ctrl->server);
  ctrl->state = FTP_NEED_SERVER;         /* Try a dir instead? */
     } else
  ctrl->state = FTP_ERROR;
     break;

   case FTP_NEED_SERVER:
     HTTRACE(PROT_TRACE, "FTP Event... now in state FTP_NEED_SERVER\n");
     status = HTFTPServerInfo(request, cnet, ctrl, data);
     if (status == HT_WOULD_BLOCK) return HT_OK;
     ctrl->state = FTP_NEED_DATA;
     break;

   case FTP_SUCCESS:
     HTTRACE(PROT_TRACE, "FTP Event... now in state FTP_SUCCESS\n");
     FTPCleanup(request, HT_LOADED);
     return HT_OK;
     break;
     
   case FTP_ERROR:
     HTTRACE(PROT_TRACE, "FTP Event... now in state FTP_ERROR\n");
     FTPCleanup(request, HT_ERROR);
     return HT_OK;
     break;
 }
    } /* End of while(1) */
}


PRIVATE int HTFTPGetData (HTRequest *request, HTNet *cnet, SOCKET sockfd,
     ftp_ctrl *ctrl, ftp_data *data)
{
    int status;
    char *segment = NULL;
    HTNet *dnet = ctrl->dnet;
    BOOL data_is_active = (sockfd == HTNet_socket(dnet));
    typedef enum _state {
 SUB_ERROR = -2,
 SUB_SUCCESS = -1,
 NEED_SELECT = 0,
 NEED_CONNECT,
 NEED_ACCEPT,
 NEED_ACTION,
        NEED_CWD,
 NEED_SEGMENT,
 NEED_STREAM,
 NEED_BODY
    } state;

    /* Jump into a second level state machine */
    while (1) {
 switch ((state) ctrl->substate) {
   case NEED_SELECT:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state NEED_SELECT\n");
     ctrl->substate = data->pasv ? NEED_CONNECT : NEED_ACTION;
     break;

   case NEED_CONNECT:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state NEED_CONNECT\n");
     status = HTHost_connect(HTNet_host(dnet), dnet, data->host, FTP_DATA);
     if (status == HT_WOULD_BLOCK)
  return HT_WOULD_BLOCK;
     else if (status == HT_OK) {
  HTTRACE(PROT_TRACE, "FTP Get Data.... Active data socket %d\n" _ 
       HTNet_socket(dnet));
#if 0
  /*  HTNet_setPersistent(dnet, YES, HT_TP_INTERLEAVE); */
  HTNet_setPersistent(dnet, YES, HT_TP_SINGLE);
#endif
  ctrl->substate = NEED_ACTION;
     } else {       /* Swap to PORT on the fly */
  NETCLOSE(HTNet_socket(dnet));
  HTNet_setSocket(dnet, INVSOC);
  HTTRACE(PROT_TRACE, "FTP Get Data Swap to PORT on the fly\n");
  ctrl->substate = NEED_SELECT;
  HT_FREE(segment);
  return HT_OK;
     }
     break;

   case NEED_ACCEPT:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state NEED_ACCEPT\n");
     {
  status = HTDoAccept(ctrl->dnet, &ctrl->dnet);
  dnet = ctrl->dnet;
  if (status == HT_WOULD_BLOCK)
      return HT_WOULD_BLOCK;
  else if (status == HT_OK) {
      HTTRACE(PROT_TRACE, "FTP Get Data.... Passive data socket %d\n" _ 
    HTNet_socket(dnet));
      ctrl->substate = NEED_STREAM;
  } else
      ctrl->substate = SUB_ERROR;
     }
     break;

   case NEED_ACTION:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state NEED_ACTION\n");
     if (!ctrl->sent) {
  char *cmd = (data->type=='L') ? "LIST" :
      (data->type=='N') ? "NLST" : "RETR";
  StrAllocCopy(segment, data->offset);
  HTUnEscape(segment);
  HTCleanTelnetString(segment);
  status = SendCommand(request, ctrl, cmd, segment);
  HT_FREE(segment);
  if (status == HT_WOULD_BLOCK)
      return HT_WOULD_BLOCK;
  else if (status == HT_ERROR)
      ctrl->substate = SUB_ERROR;
  ctrl->sent = YES;
     } else {
  status = HTHost_read(HTNet_host(cnet), cnet);
  if (status == HT_WOULD_BLOCK)
      return HT_WOULD_BLOCK;
  else if (status == HT_LOADED) {
      int code = ctrl->repcode;
      if (code==125 || code==150 || code==225)
   ctrl->substate = data->pasv ? NEED_STREAM : NEED_ACCEPT;
      else if (code/100==5 && !ctrl->cwd)
   ctrl->substate = NEED_SEGMENT;
/* begin _GM_ */
/* Note: libwww bug ID: GM9 */
      /* else */
      /*  ctrl->substate = SUB_ERROR; */
      else {
   if (ctrl->repcode == 550) {
       HTTRACE(PROT_TRACE, "FTP Get Data no such file or directory\n");
       data->stream_error = YES;
   }
       ctrl->substate = SUB_ERROR;
      }
/* end _GM_ */
  } else
      ctrl->substate = SUB_ERROR;
  ctrl->sent = NO;
     }
     break;

   case NEED_SEGMENT:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state NEED_SEGMENT\n");
     {
  char *ptr;
  if (data->offset == data->file) {
      if (ctrl->server == FTP_VMS) {    /* Change to root */
   if ((segment = (char  *) HT_MALLOC(strlen(ctrl->uid)+3)) == NULL)
       HT_OUTOFMEM("segment ");
   sprintf(segment, "[%s]", ctrl->uid);
      } else
   StrAllocCopy(segment, "/");
      data->offset++;
      ctrl->substate = NEED_CWD;
  } else {
      if ((ptr = strchr(data->offset, '/'))) {
   *ptr='\0';
   StrAllocCopy(segment, data->offset);
   *ptr='/';
   data->offset = ++ptr;
   HTUnEscape(segment);
   HTCleanTelnetString(segment);
   ctrl->substate = NEED_CWD;
      } else
   ctrl->substate = NEED_ACTION;
  }
     }
     break;

   case NEED_CWD:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state NEED_CWD\n");
     if (!ctrl->sent) {
  status = SendCommand(request, ctrl, "CWD", segment);
  HT_FREE(segment);
  if (status == HT_WOULD_BLOCK)
      return HT_WOULD_BLOCK;
  else if (status == HT_ERROR)
      ctrl->substate = SUB_ERROR;
  ctrl->cwd = YES;
  ctrl->sent = YES;
     } else {
  status = HTHost_read(HTNet_host(cnet), cnet);
  if (status == HT_WOULD_BLOCK)
      return HT_WOULD_BLOCK;
  else if (status == HT_LOADED) {
      if (ctrl->repcode/100 == 2)
   ctrl->substate = NEED_SEGMENT;
      else
   ctrl->substate = SUB_ERROR;
  } else
      ctrl->substate = SUB_ERROR;
  ctrl->sent = NO;
     }
     break;

 case NEED_STREAM:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state NEED_STREAM\n");
     /* 
     ** Create the stream pipe FROM the channel to the application.
     ** The target for the input stream pipe is set up using the
     ** stream stack.
     */
     {
  HTStream * target = FTP_DIR(data) ?
      HTFTPDir_new(request, ctrl->server, data->type) :
      HTStreamStack(HTAnchor_format(HTRequest_anchor(request)),
      HTRequest_outputFormat(request),
      HTRequest_outputStream(request),
      request, YES);
  HTNet_setReadStream(dnet, target);
     }
     HTRequest_setOutputConnected(request, YES);
     data_is_active = YES;
     ctrl->substate = NEED_BODY;
     break;

   case NEED_BODY:
       HTTRACE(PROT_TRACE, "FTP Get Data now in state NEED_BODY\n");
       if (data_is_active) {
    status = HTHost_read(HTNet_host(dnet), dnet);
    if (status == HT_WOULD_BLOCK)
        return HT_WOULD_BLOCK;
    else if (status == HT_LOADED || status == HT_CLOSED) {
        data->complete |= 1; 
        if (data->complete >= 3)
     ctrl->substate = SUB_SUCCESS;
        else
     data_is_active = NO;
    } else {
        ctrl->substate = SUB_ERROR;
        data->stream_error = YES;
    }
       } else {
    status = HTHost_read(HTNet_host(cnet), cnet);
    if (status == HT_WOULD_BLOCK)
        return HT_WOULD_BLOCK;
    else if (status == HT_LOADED || status == HT_CLOSED) {
        if (ctrl->repcode/100 == 2) {
     data->complete |= 2;
     if (data->complete >= 3)
         ctrl->substate = SUB_SUCCESS;
     else
         data_is_active = YES;
        } else
     ctrl->substate = SUB_ERROR;
    } else
        ctrl->substate = SUB_ERROR;
       }
       break;

   case SUB_ERROR:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state SUB_ERROR\n");
     HTRequest_addError(request, ERR_FATAL, NO, HTERR_NOT_FOUND,
          NULL, 0, "HTFTPGetData");
     ctrl->substate = 0;
     HT_FREE(segment);
     return HT_ERROR;
     break;

   case SUB_SUCCESS:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state SUB_SUCCESS\n");
     ctrl->substate = 0;
     HT_FREE(segment);
     return HT_LOADED;
     break;
 }
    }
}

--------------------------------------------------------  MY VERSION-------------------------------------------------------------------------------

PRIVATE int FTPEvent (SOCKET soc, void * pVoid, HTEventType type)
{
    ftp_ctrl * ctrl = (ftp_ctrl *) pVoid;
    ftp_data * data = (ftp_data *) HTNet_context(ctrl->dnet);
    int status = HT_ERROR;
    HTNet * cnet = ctrl->cnet;
    HTRequest * request = HTNet_request(cnet);
    HTParentAnchor * anchor = HTRequest_anchor(request);
    char * url = HTAnchor_physical(anchor);

    HTHost *host = HTNet_host(cnet);
 
  if (type == HTEvent_CLOSE) 
  {         /* Interrupted */
   if (soc == HTNet_socket(cnet))  FTPCleanup(request, HT_INTERRUPTED);
   else
    FTPCleanup(request, HT_LOADED);
   return HT_OK;
  }
  else if (type == HTEvent_TIMEOUT) 
  {
   HTRequest_addError(request, ERR_FATAL, NO, HTERR_TIME_OUT,
        NULL, 0, "HTLoadHTTP");
   FTPCleanup(request, HT_TIMEOUT);
   return HT_OK;
  }
  else 
  {
   ctrl = (ftp_ctrl *) HTNet_context(cnet); /* Get existing copy */
   data = (ftp_data *) HTNet_context(ctrl->dnet);
  }

  /* Now jump into the machine. We know the state from the previous run */
  while (1) 
  {
   switch (ctrl->state) 
   {
    case FTP_BEGIN:
    {
     HTTRACE(PROT_TRACE, "FTP Event... now in state FTP_BEGIN\n");
    
/* begin siddheshwark */
     /* Only handle GET and PUT requests for now */
     if ((HTRequest_method(request) != METHOD_GET) && (HTRequest_method(request) != METHOD_PUT))
     {
     HTTRACE(PROT_TRACE, "FTP Event... This module only supports the GET method\n");
     ctrl->state = FTP_ERROR;
     break;
     }
     
/* end siddheshwark */

     HTFTPParseURL(request, url, ctrl, data);

      /* The following is added by Neil Griffin, GAIN Software */

      /*
      ** If the user hasn't specified a permanent transfer type, then
      ** use the transfer type specified at the end of the URL.
      */
     if (g_FTPTransferMode == FTP_DEFAULT_TRANSFER_MODE) 
     {
      switch (data->type)
      {
       case 'a' : data->type = 'A'; break;
       case 'A' : data->type = 'A'; break;
       case 'i' : data->type = 'I'; break;
       case 'I' : data->type = 'I'; break;
       case 'd' : FTPListType(data, ctrl->server); break;
       case 'D' : FTPListType(data, ctrl->server); break;
     //  default  : data->type = 'I'; break;
      } 
      /* Otherwise, use the permanent transfer type specified by the user. */
     } 
     else 
     {
      switch (g_FTPTransferMode) 
      {
       case FTP_ASCII_TRANSFER_MODE  : data->type = 'A'; break;
       case FTP_BINARY_TRANSFER_MODE : data->type = 'I'; break;
       case FTP_DIR_TRANSFER_MODE    : FTPListType(data, ctrl->server); break;
       default                       : data->type = 'I'; break;
      }
     }

     HTTRACE(PROT_TRACE, "FTP Event... Transfer mode set to '%c'\n" _ data->type);

      /*
      **  See if we can get any hints to what we might expect content wise.
      */
     if (!FTP_DIR(data)) HTBind_getAnchorBindings(anchor);

     /* Ready for next state */
     ctrl->state = FTP_NEED_CCON;
     break;
    } /* end FTP_BEGIN*/

    case FTP_NEED_CCON:
    {
     HTTRACE(PROT_TRACE, "FTP Event... now in state FTP_NEED_CONN\n");
     status = HTHost_connect(host, cnet, url, FTP_PORT);
     host = HTNet_host(cnet);
     if (status == HT_OK) 
     {

       /*
       ** Check the protocol class to see if we have connected to a
       ** the right class of server, in this case HTTP.
      */
      {
       char * s_class = HTHost_class(host);
       if (s_class && strcasecomp(s_class, "ftp")) 
       {
        HTRequest_addError(request, ERR_FATAL, NO, HTERR_CLASS,
          NULL, 0, "HTLoadNews");
        ctrl->state = FTP_ERROR;
        break;
       }
       HTHost_setClass(host, "ftp");
      }

      /* Check persistent connection */
      if (HTNet_persistent(cnet)) 
      {
       ctrl->server = HTHost_version(host);
       HTTRACE(PROT_TRACE, "FTP Server.. Cache says type %d server\n" _ 
         ctrl->server);
       ctrl->reset = 1;
      }
      else
       HTNet_setPersistent(cnet, YES, HT_TP_SINGLE);

      /* 
      ** Create the stream pipe FROM the channel to the application.
      ** The target for the input stream pipe is set up using the
      ** stream stack.
      */
      {
       HTStream * readstream = FTPStatus_new(request, ctrl, host);
       HTNet_setReadStream(cnet, readstream);
      }

      /*
      ** Create the stream pipe TO the channel from the application
      ** and hook it up to the request object
      */
      {
       HTOutputStream * output = HTNet_getOutput(cnet, NULL, 0);
       HTRequest_setInputStream(request, (HTStream *) output);
      }

      /*
      ** Set up concurrent read/write if this request isn't the
      ** source for a PUT or POST. As source we don't start reading
      ** before all destinations are ready. If destination then
      ** register the input stream and get ready for read
      */
      if (HTRequest_isPostWeb(request)) 
      {
       HTEvent * event = HTNet_event(cnet);
       HTEvent_register(HTNet_socket(cnet), HTEvent_READ, event);
       HTRequest_linkDestination(request);
      }

      ctrl->state = FTP_NEED_LOGIN;
     } 
     else if (status == HT_WOULD_BLOCK || status == HT_PENDING)
      return HT_OK;
     else
      ctrl->state = FTP_ERROR;        /* Error or interrupt */
     break;
    }
    case FTP_NEED_LOGIN:
    {
     HTTRACE(PROT_TRACE, "FTP Event... now in state FTP_NEED_LOGIN\n");
     status = HTFTPLogin(request, cnet, ctrl);
      if (status == HT_WOULD_BLOCK) return HT_OK;
     ctrl->state = (status == HT_OK) ? FTP_NEED_DCON : FTP_ERROR;
     break;
    }
    case FTP_NEED_DCON:
    {
     HTTRACE(PROT_TRACE, "FTP Event... now in state FTP_NEED_DCON\n");
     status = HTFTPDataConnection(request, cnet, ctrl, data);
     if (status == HT_WOULD_BLOCK) return HT_OK;
     if (status == HT_OK)
      ctrl->state = (data->type=='N') ? FTP_NEED_SERVER : FTP_NEED_DATA;
     else
      ctrl->state = FTP_ERROR;
     break;
    }
    case FTP_NEED_DATA:
    {
     HTTRACE(PROT_TRACE, "FTP Event... now in state FTP_NEED_DATA\n");
     status = HTFTPGetData(request, cnet, soc, ctrl, data);
      if (status == HT_WOULD_BLOCK) return HT_OK;
     if (status == HT_LOADED)
      ctrl->state = FTP_SUCCESS;
     else if (status == HT_OK)
      ctrl->state = FTP_NEED_DCON;
     else if (!FTP_DIR(data) && !data->stream_error) 
     {
      FTPListType(data, ctrl->server);
      ctrl->state = FTP_NEED_SERVER;         /* Try a dir instead? */
     }
     else
      ctrl->state = FTP_ERROR;
     break;
    }
    case FTP_NEED_SERVER:
    {
        HTTRACE(PROT_TRACE, "FTP Event... now in state FTP_NEED_SERVER\n");
     status = HTFTPServerInfo(request, cnet, ctrl, data);
     if (status == HT_WOULD_BLOCK) return HT_OK;
     ctrl->state = FTP_NEED_DATA;
     break;
    }
    case FTP_SUCCESS:
    {
     HTTRACE(PROT_TRACE, "FTP Event... now in state FTP_SUCCESS\n");
     FTPCleanup(request, HT_LOADED);
     return HT_OK;
     break;
    }
    case FTP_ERROR:
    {
     HTTRACE(PROT_TRACE, "FTP Event... now in state FTP_ERROR\n");
     FTPCleanup(request, HT_ERROR);
     return HT_OK;
     break;
    }
   } /* end of switch*/
  } /* End of while(1) */
 
}

PRIVATE int HTFTPGetData (HTRequest *request, HTNet *cnet, SOCKET sockfd,
     ftp_ctrl *ctrl, ftp_data *data)
{
    int status;
    char *segment = NULL;
    HTNet *dnet = ctrl->dnet;
    BOOL data_is_active = (sockfd == HTNet_socket(dnet));
    typedef enum _state {
 SUB_ERROR = -2,
 SUB_SUCCESS = -1,
 NEED_SELECT = 0,
 NEED_CONNECT,
 NEED_ACCEPT,
 NEED_ACTION,
        NEED_CWD,
 NEED_SEGMENT,
 NEED_STREAM,
 NEED_BODY
    } state;

    /* Jump into a second level state machine */
    while (1) {
 switch ((state) ctrl->substate) {
   case NEED_SELECT:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state NEED_SELECT\n");
     ctrl->substate = data->pasv ? NEED_CONNECT : NEED_ACTION;
     break;

   case NEED_CONNECT:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state NEED_CONNECT\n");
     status = HTHost_connect(HTNet_host(dnet), dnet, data->host, FTP_DATA);
     if (status == HT_WOULD_BLOCK)
  return HT_WOULD_BLOCK;
     else if (status == HT_OK) {
  HTTRACE(PROT_TRACE, "FTP Get Data.... Active data socket %d\n" _ 
       HTNet_socket(dnet));
#if 0
  /*  HTNet_setPersistent(dnet, YES, HT_TP_INTERLEAVE); */
  HTNet_setPersistent(dnet, YES, HT_TP_SINGLE);
#endif
  ctrl->substate = NEED_ACTION;
     } else {       /* Swap to PORT on the fly */
  NETCLOSE(HTNet_socket(dnet));
  HTNet_setSocket(dnet, INVSOC);
  HTTRACE(PROT_TRACE, "FTP Get Data Swap to PORT on the fly\n");
  ctrl->substate = NEED_SELECT;
  HT_FREE(segment);
  return HT_OK;
     }
     break;

   case NEED_ACCEPT:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state NEED_ACCEPT\n");
     {
  status = HTDoAccept(ctrl->dnet, &ctrl->dnet);
  dnet = ctrl->dnet;
  if (status == HT_WOULD_BLOCK)
      return HT_WOULD_BLOCK;
  else if (status == HT_OK) {
      HTTRACE(PROT_TRACE, "FTP Get Data.... Passive data socket %d\n" _ 
    HTNet_socket(dnet));
      ctrl->substate = NEED_STREAM;
  } else
      ctrl->substate = SUB_ERROR;
     }
     break;

   case NEED_ACTION:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state NEED_ACTION\n");
     if (!ctrl->sent) {

/* begin siddheshwark */
 /* Issue the command according to the request method */

  char *cmd = NULL;
  if(HTRequest_method(request) == METHOD_GET)
  {
   cmd = (data->type=='L') ? "LIST" :
      (data->type=='N') ? "NLST" : "RETR"; 
  }
  else if(HTRequest_method(request) == METHOD_PUT)
  {
   cmd = "STOR";
  }

/* end siddheshwark */

  StrAllocCopy(segment, data->offset);
  HTUnEscape(segment);
  HTCleanTelnetString(segment);
  status = SendCommand(request, ctrl, cmd, segment);
  HT_FREE(segment);
  if (status == HT_WOULD_BLOCK)
      return HT_WOULD_BLOCK;
  else if (status == HT_ERROR)
      ctrl->substate = SUB_ERROR;
  ctrl->sent = YES;
     } else {
  status = HTHost_read(HTNet_host(cnet), cnet);
  if (status == HT_WOULD_BLOCK)
      return HT_WOULD_BLOCK;
  else if (status == HT_LOADED) {
      int code = ctrl->repcode;
      if (code==125 || code==150 || code==225)
   ctrl->substate = data->pasv ? NEED_STREAM : NEED_ACCEPT;
      else if (code/100==5 && !ctrl->cwd)
   ctrl->substate = NEED_SEGMENT;
/* begin _GM_ */
/* Note: libwww bug ID: GM9 */
      /* else */
      /*  ctrl->substate = SUB_ERROR; */
      else {
   if (ctrl->repcode == 550) {
       HTTRACE(PROT_TRACE, "FTP Get Data no such file or directory\n");
       data->stream_error = YES;
   }
       ctrl->substate = SUB_ERROR;
      }
/* end _GM_ */
  } else
      ctrl->substate = SUB_ERROR;
  ctrl->sent = NO;
     }
     break;

   case NEED_SEGMENT:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state NEED_SEGMENT\n");
     {
  char *ptr;
  if (data->offset == data->file) {
      if (ctrl->server == FTP_VMS) {    /* Change to root */
   if ((segment = (char  *) HT_MALLOC(strlen(ctrl->uid)+3)) == NULL)
       HT_OUTOFMEM("segment ");
   sprintf(segment, "[%s]", ctrl->uid);
      } else
   StrAllocCopy(segment, "/");
      data->offset++;
      ctrl->substate = NEED_CWD;
  } else {
      if ((ptr = strchr(data->offset, '/'))) {
   *ptr='\0';
   StrAllocCopy(segment, data->offset);
   *ptr='/';
   data->offset = ++ptr;
   HTUnEscape(segment);
   HTCleanTelnetString(segment);
   ctrl->substate = NEED_CWD;
      } else
   ctrl->substate = NEED_ACTION;
  }
     }
     break;

   case NEED_CWD:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state NEED_CWD\n");
     if (!ctrl->sent) {
  status = SendCommand(request, ctrl, "CWD", segment);
  HT_FREE(segment);
  if (status == HT_WOULD_BLOCK)
      return HT_WOULD_BLOCK;
  else if (status == HT_ERROR)
      ctrl->substate = SUB_ERROR;
  ctrl->cwd = YES;
  ctrl->sent = YES;
     } else {
  status = HTHost_read(HTNet_host(cnet), cnet);
  if (status == HT_WOULD_BLOCK)
      return HT_WOULD_BLOCK;
  else if (status == HT_LOADED) {
      if (ctrl->repcode/100 == 2)
   ctrl->substate = NEED_SEGMENT;
      else
   ctrl->substate = SUB_ERROR;
  } else
      ctrl->substate = SUB_ERROR;
  ctrl->sent = NO;
     }
     break;

 case NEED_STREAM:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state NEED_STREAM\n");
     /* 
     ** Create the stream pipe FROM the channel to the application.
     ** The target for the input stream pipe is set up using the
     ** stream stack.
     */
/* begin siddheshwark */
  if(HTRequest_method(request) == METHOD_GET)
  {
   {
   HTStream * target = FTP_DIR(data) ?
    HTFTPDir_new(request, ctrl->server, data->type) :
    HTStreamStack(HTAnchor_format(HTRequest_anchor(request)),
       HTRequest_outputFormat(request),
       HTRequest_outputStream(request),
       request, YES);
   HTNet_setReadStream(dnet, target);
   }
   HTRequest_setOutputConnected(request, YES);
   data_is_active = YES;
   ctrl->substate = NEED_BODY;
  }
  else if(HTRequest_method(request) == METHOD_PUT)
  {
   /* Connect the request input stream to channel output stream
   and launch the post Callback function 
   */
   /* Every put method in the HTAccess module has a postCallback function.
   Therfore it is safe to assume its presence. If the function is not 
   present we raise an error
   */
   HTHost * host = HTNet_host(dnet);
   HTStream * app = NULL;
  
   HTPostCallback * pcbf = HTRequest_postCallback(request );
   HTChannel * channel = HTHost_channel(host);

   HTOutputStream * output = HTChannel_getChannelOStream(channel);
   
  
   HTRequest_setInputStream(request, (HTStream*) output);
   
   app = HTRequest_inputStream(request);
   
   status = HTRequest_flush(request) ? 
      HTHost_forceFlush(host) : (*app->isa->flush)(app); 
   
   if (pcbf) {
    
    status = (*pcbf)(request, app);
    HTRequest_flush(request) ? 
      HTHost_forceFlush(host) : (*app->isa->flush)(app); 
    return status;
   }
   else
   {
    ctrl->substate = SUB_ERROR;
   }
/* end siddheshwark */
    
  }
     break;

   case NEED_BODY:
       HTTRACE(PROT_TRACE, "FTP Get Data now in state NEED_BODY\n");
       if (data_is_active) {
    status = HTHost_read(HTNet_host(dnet), dnet);
    if (status == HT_WOULD_BLOCK)
        return HT_WOULD_BLOCK;
    else if (status == HT_LOADED || status == HT_CLOSED) {
        data->complete |= 1; 
        if (data->complete >= 3)
     ctrl->substate = SUB_SUCCESS;
        else
     data_is_active = NO;
    } else {
        ctrl->substate = SUB_ERROR;
        data->stream_error = YES;
    }
       } else {
    status = HTHost_read(HTNet_host(cnet), cnet);
    if (status == HT_WOULD_BLOCK)
        return HT_WOULD_BLOCK;
    else if (status == HT_LOADED || status == HT_CLOSED) {
        if (ctrl->repcode/100 == 2) {
     data->complete |= 2;
     if (data->complete >= 3)
         ctrl->substate = SUB_SUCCESS;
     else
         data_is_active = YES;
        } else
     ctrl->substate = SUB_ERROR;
    } else
        ctrl->substate = SUB_ERROR;
       }
       break;

   case SUB_ERROR:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state SUB_ERROR\n");
     HTRequest_addError(request, ERR_FATAL, NO, HTERR_NOT_FOUND,
          NULL, 0, "HTFTPGetData");
     ctrl->substate = 0;
     HT_FREE(segment);
     return HT_ERROR;
     break;

   case SUB_SUCCESS:
     HTTRACE(PROT_TRACE, "FTP Get Data now in state SUB_SUCCESS\n");
     ctrl->substate = 0;
     HT_FREE(segment);
     return HT_LOADED;
     break;
 }
    }
}

Received on Tuesday, 13 June 2000 06:27:34 UTC