Commit a6315359 authored by Daniel Stenberg's avatar Daniel Stenberg
Browse files

Max Katsev reported that when doing a libcurl FTP request with

CURLOPT_NOBODY enabled but not CURLOPT_HEADER, libcurl wouldn't do TYPE
before it does SIZE which makes it less useful. I walked over the code and
made it do this properly, and added test case 542 to verify it.
parent 96613013
Loading
Loading
Loading
Loading
+6 −0
Original line number Original line Diff line number Diff line
@@ -6,6 +6,12 @@


                                  Changelog
                                  Changelog


Daniel S (26 September 2007)
- Max Katsev reported that when doing a libcurl FTP request with
  CURLOPT_NOBODY enabled but not CURLOPT_HEADER, libcurl wouldn't do TYPE
  before it does SIZE which makes it less useful. I walked over the code and
  made it do this properly, and added test case 542 to verify it.

Daniel S (24 September 2007)
Daniel S (24 September 2007)
- Immanuel Gregoire fixed KNOWN_BUGS #44: --ftp-method nocwd did not handle
- Immanuel Gregoire fixed KNOWN_BUGS #44: --ftp-method nocwd did not handle
  URLs ending with a slash properly (it should list the contents of that
  URLs ending with a slash properly (it should list the contents of that
+3 −1
Original line number Original line Diff line number Diff line
@@ -23,6 +23,8 @@ This release includes the following bugfixes:
 o no HOME and no key given caused SSH auth failure
 o no HOME and no key given caused SSH auth failure
 o Negotiate authentication over proxy
 o Negotiate authentication over proxy
 o --ftp-method nocwd on directory listings
 o --ftp-method nocwd on directory listings
 o FTP, CURLOPT_NOBODY enabled and CURLOPT_HEADER disabled now does TYPE
   before SIZE


This release includes the following known bugs:
This release includes the following known bugs:


@@ -40,6 +42,6 @@ This release would not have looked like this without help, code, reports and
advice from friends like these:
advice from friends like these:


 Dan Fandrich, Michal Marek, Gnter Knauf, Rob Crittenden, Immanuel Gregoire,
 Dan Fandrich, Michal Marek, Gnter Knauf, Rob Crittenden, Immanuel Gregoire,
 Mark Davies
 Mark Davies, Max Katsev
 
 
        Thanks! (and sorry if I forgot to mention someone)
        Thanks! (and sorry if I forgot to mention someone)
+34 −29
Original line number Original line Diff line number Diff line
@@ -1212,7 +1212,7 @@ static CURLcode ftp_state_post_rest(struct connectdata *conn)
  struct FTP *ftp = conn->data->reqdata.proto.ftp;
  struct FTP *ftp = conn->data->reqdata.proto.ftp;
  struct SessionHandle *data = conn->data;
  struct SessionHandle *data = conn->data;


  if(ftp->no_transfer) {
  if(ftp->transfer != FTPTRANSFER_BODY) {
    /* doesn't transfer any data */
    /* doesn't transfer any data */


    /* still possibly do PRE QUOTE jobs */
    /* still possibly do PRE QUOTE jobs */
@@ -1235,7 +1235,7 @@ static CURLcode ftp_state_post_size(struct connectdata *conn)
  CURLcode result = CURLE_OK;
  CURLcode result = CURLE_OK;
  struct FTP *ftp = conn->data->reqdata.proto.ftp;
  struct FTP *ftp = conn->data->reqdata.proto.ftp;


  if(ftp->no_transfer && ftp->file) {
  if((ftp->transfer != FTPTRANSFER_BODY) && ftp->file) {
    /* if a "head"-like request is being made (on a file) */
    /* if a "head"-like request is being made (on a file) */


    /* Determine if server can respond to REST command and therefore
    /* Determine if server can respond to REST command and therefore
@@ -1255,7 +1255,7 @@ static CURLcode ftp_state_post_type(struct connectdata *conn)
  CURLcode result = CURLE_OK;
  CURLcode result = CURLE_OK;
  struct FTP *ftp = conn->data->reqdata.proto.ftp;
  struct FTP *ftp = conn->data->reqdata.proto.ftp;


  if(ftp->no_transfer && ftp->file) {
  if((ftp->transfer == FTPTRANSFER_INFO) && ftp->file) {
    /* if a "head"-like request is being made (on a file) */
    /* if a "head"-like request is being made (on a file) */


    /* we know ftp->file is a valid pointer to a file name */
    /* we know ftp->file is a valid pointer to a file name */
@@ -1362,13 +1362,14 @@ static CURLcode ftp_state_post_mdtm(struct connectdata *conn)
  /* If we have selected NOBODY and HEADER, it means that we only want file
  /* If we have selected NOBODY and HEADER, it means that we only want file
     information. Which in FTP can't be much more than the file size and
     information. Which in FTP can't be much more than the file size and
     date. */
     date. */
  if(conn->bits.no_body && data->set.include_header && ftp->file &&
  if(conn->bits.no_body && ftp->file &&
     ftp_need_type(conn, data->set.prefer_ascii)) {
     ftp_need_type(conn, data->set.prefer_ascii)) {
    /* The SIZE command is _not_ RFC 959 specified, and therefor many servers
    /* The SIZE command is _not_ RFC 959 specified, and therefor many servers
       may not support it! It is however the only way we have to get a file's
       may not support it! It is however the only way we have to get a file's
       size! */
       size! */


    ftp->no_transfer = TRUE; /* this means no actual transfer will be made */
    ftp->transfer = FTPTRANSFER_INFO;
    /* this means no actual transfer will be made */


    /* Some servers return different sizes for different modes, and thus we
    /* Some servers return different sizes for different modes, and thus we
       must set the proper type before we check the size */
       must set the proper type before we check the size */
@@ -1475,9 +1476,9 @@ static CURLcode ftp_state_ul_setup(struct connectdata *conn,
        /* no data to transfer */
        /* no data to transfer */
        result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
        result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);


        /* Set no_transfer so that we won't get any error in
        /* Set ->transfer so that we won't get any error in
         * Curl_ftp_done() because we didn't transfer anything! */
         * Curl_ftp_done() because we didn't transfer anything! */
        ftp->no_transfer = TRUE;
        ftp->transfer = FTPTRANSFER_NONE;


        state(conn, FTP_STOP);
        state(conn, FTP_STOP);
        return CURLE_OK;
        return CURLE_OK;
@@ -1547,7 +1548,7 @@ static CURLcode ftp_state_quote(struct connectdata *conn,
      result = ftp_state_cwd(conn);
      result = ftp_state_cwd(conn);
      break;
      break;
    case FTP_RETR_PREQUOTE:
    case FTP_RETR_PREQUOTE:
      if (ftp->no_transfer)
      if (ftp->transfer != FTPTRANSFER_BODY)
        state(conn, FTP_STOP);
        state(conn, FTP_STOP);
      else {
      else {
        NBFTPSENDF(conn, "SIZE %s", ftp->file);
        NBFTPSENDF(conn, "SIZE %s", ftp->file);
@@ -1877,7 +1878,6 @@ static CURLcode ftp_state_mdtm_resp(struct connectdata *conn,
         we "emulate" a HTTP-style header in our output. */
         we "emulate" a HTTP-style header in our output. */


      if(conn->bits.no_body &&
      if(conn->bits.no_body &&
         data->set.include_header &&
         ftp->file &&
         ftp->file &&
         data->set.get_filetime &&
         data->set.get_filetime &&
         (data->info.filetime>=0) ) {
         (data->info.filetime>=0) ) {
@@ -1922,7 +1922,7 @@ static CURLcode ftp_state_mdtm_resp(struct connectdata *conn,
      default:
      default:
        if(data->info.filetime <= data->set.timevalue) {
        if(data->info.filetime <= data->set.timevalue) {
          infof(data, "The requested document is not new enough\n");
          infof(data, "The requested document is not new enough\n");
          ftp->no_transfer = TRUE; /* mark this to not transfer data */
          ftp->transfer = FTPTRANSFER_NONE; /* mark this to not transfer data */
          state(conn, FTP_STOP);
          state(conn, FTP_STOP);
          return CURLE_OK;
          return CURLE_OK;
        }
        }
@@ -1930,7 +1930,7 @@ static CURLcode ftp_state_mdtm_resp(struct connectdata *conn,
      case CURL_TIMECOND_IFUNMODSINCE:
      case CURL_TIMECOND_IFUNMODSINCE:
        if(data->info.filetime > data->set.timevalue) {
        if(data->info.filetime > data->set.timevalue) {
          infof(data, "The requested document is not old enough\n");
          infof(data, "The requested document is not old enough\n");
          ftp->no_transfer = TRUE; /* mark this to not transfer data */
          ftp->transfer = FTPTRANSFER_NONE; /* mark this to not transfer data */
          state(conn, FTP_STOP);
          state(conn, FTP_STOP);
          return CURLE_OK;
          return CURLE_OK;
        }
        }
@@ -2034,9 +2034,9 @@ static CURLcode ftp_state_post_retr_size(struct connectdata *conn,
      result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
      result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
      infof(data, "File already completely downloaded\n");
      infof(data, "File already completely downloaded\n");


      /* Set no_transfer so that we won't get any error in Curl_ftp_done()
      /* Set ->transfer so that we won't get any error in Curl_ftp_done()
       * because we didn't transfer the any file */
       * because we didn't transfer the any file */
      ftp->no_transfer = TRUE;
      ftp->transfer = FTPTRANSFER_NONE;
      state(conn, FTP_STOP);
      state(conn, FTP_STOP);
      return CURLE_OK;
      return CURLE_OK;
    }
    }
@@ -2291,7 +2291,7 @@ static CURLcode ftp_state_get_resp(struct connectdata *conn,
  else {
  else {
    if((instate == FTP_LIST) && (ftpcode == 450)) {
    if((instate == FTP_LIST) && (ftpcode == 450)) {
      /* simply no matching files in the dir listing */
      /* simply no matching files in the dir listing */
      ftp->no_transfer = TRUE; /* don't download anything */
      ftp->transfer = FTPTRANSFER_NONE; /* don't download anything */
      state(conn, FTP_STOP); /* this phase is over */
      state(conn, FTP_STOP); /* this phase is over */
    }
    }
    else {
    else {
@@ -3121,7 +3121,7 @@ CURLcode Curl_ftp_done(struct connectdata *conn, CURLcode status,


  conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
  conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;


  if(!ftp->no_transfer && !status && !premature) {
  if((ftp->transfer == FTPTRANSFER_BODY) && !status && !premature) {
    /*
    /*
     * Let's see what the server says about the transfer we just performed,
     * Let's see what the server says about the transfer we just performed,
     * but lower the timeout as sometimes this connection has died while the
     * but lower the timeout as sometimes this connection has died while the
@@ -3162,7 +3162,7 @@ CURLcode Curl_ftp_done(struct connectdata *conn, CURLcode status,
    if((-1 != data->set.infilesize) &&
    if((-1 != data->set.infilesize) &&
       (data->set.infilesize != *ftp->bytecountp) &&
       (data->set.infilesize != *ftp->bytecountp) &&
       !data->set.crlf &&
       !data->set.crlf &&
       !ftp->no_transfer) {
       (ftp->transfer == FTPTRANSFER_BODY)) {
      failf(data, "Uploaded unaligned file size (%" FORMAT_OFF_T
      failf(data, "Uploaded unaligned file size (%" FORMAT_OFF_T
            " out of %" FORMAT_OFF_T " bytes)",
            " out of %" FORMAT_OFF_T " bytes)",
            *ftp->bytecountp, data->set.infilesize);
            *ftp->bytecountp, data->set.infilesize);
@@ -3192,7 +3192,7 @@ CURLcode Curl_ftp_done(struct connectdata *conn, CURLcode status,
  }
  }


  /* clear these for next connection */
  /* clear these for next connection */
  ftp->no_transfer = FALSE;
  ftp->transfer = FTPTRANSFER_BODY;
  ftpc->dont_check = FALSE;
  ftpc->dont_check = FALSE;


  /* Send any post-transfer QUOTE strings? */
  /* Send any post-transfer QUOTE strings? */
@@ -3375,12 +3375,12 @@ CURLcode Curl_ftp_nextconnect(struct connectdata *conn)


  DEBUGF(infof(data, "DO-MORE phase starts\n"));
  DEBUGF(infof(data, "DO-MORE phase starts\n"));


  if(!ftp->no_transfer) {
  if(ftp->transfer <= FTPTRANSFER_INFO) {
    /* a transfer is about to take place */
    /* a transfer is about to take place, or if not a file name was given
       so we'll do a SIZE on it later and then we need the right TYPE first */


    if(data->set.upload) {
    if(data->set.upload) {
      result = ftp_nb_type(conn, data->set.prefer_ascii,
      result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_STOR_TYPE);
                                      FTP_STOR_TYPE);
      if (result)
      if (result)
        return result;
        return result;
    }
    }
@@ -3391,14 +3391,19 @@ CURLcode Curl_ftp_nextconnect(struct connectdata *conn)
      result = ftp_range(conn);
      result = ftp_range(conn);
      if(result)
      if(result)
        ;
        ;
      else if((data->set.ftp_list_only) || !ftp->file) {
      else if(data->set.ftp_list_only || !ftp->file) {
        /* The specified path ends with a slash, and therefore we think this
        /* The specified path ends with a slash, and therefore we think this
           is a directory that is requested, use LIST. But before that we
           is a directory that is requested, use LIST. But before that we
           need to set ASCII transfer mode. */
           need to set ASCII transfer mode. */

        /* But only if a body transfer was requested. */
        if(ftp->transfer == FTPTRANSFER_BODY) {
          result = ftp_nb_type(conn, 1, FTP_LIST_TYPE);
          result = ftp_nb_type(conn, 1, FTP_LIST_TYPE);
          if (result)
          if (result)
            return result;
            return result;
        }
        }
        /* otherwise just fall through */
      }
      else {
      else {
        result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_RETR_TYPE);
        result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_RETR_TYPE);
        if (result)
        if (result)
@@ -3408,7 +3413,7 @@ CURLcode Curl_ftp_nextconnect(struct connectdata *conn)
    result = ftp_easy_statemach(conn);
    result = ftp_easy_statemach(conn);
  }
  }


  if(ftp->no_transfer)
  if(ftp->transfer != FTPTRANSFER_BODY)
    /* no data to transfer. FIX: it feels like a kludge to have this here
    /* no data to transfer. FIX: it feels like a kludge to have this here
       too! */
       too! */
    result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
    result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
@@ -3442,7 +3447,7 @@ CURLcode ftp_perform(struct connectdata *conn,
  if(conn->bits.no_body) {
  if(conn->bits.no_body) {
    /* requested no body means no transfer... */
    /* requested no body means no transfer... */
    struct FTP *ftp = conn->data->reqdata.proto.ftp;
    struct FTP *ftp = conn->data->reqdata.proto.ftp;
    ftp->no_transfer = TRUE;
    ftp->transfer = FTPTRANSFER_INFO;
  }
  }




@@ -3865,7 +3870,7 @@ CURLcode ftp_parse_url_path(struct connectdata *conn)
    ftp->file=NULL; /* instead of point to a zero byte, we make it a NULL
    ftp->file=NULL; /* instead of point to a zero byte, we make it a NULL
                       pointer */
                       pointer */


  if(data->set.upload && !ftp->file && !ftp->no_transfer) {
  if(data->set.upload && !ftp->file && (ftp->transfer == FTPTRANSFER_BODY)) {
    /* We need a file name when uploading. Return error! */
    /* We need a file name when uploading. Return error! */
    failf(data, "Uploading to a URL without a file name!");
    failf(data, "Uploading to a URL without a file name!");
    return CURLE_URL_MALFORMAT;
    return CURLE_URL_MALFORMAT;
@@ -3912,7 +3917,7 @@ static CURLcode ftp_dophase_done(struct connectdata *conn,
    return result;
    return result;
  }
  }


  if(ftp->no_transfer)
  if(ftp->transfer != FTPTRANSFER_BODY)
    /* no data to transfer */
    /* no data to transfer */
    result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
    result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
  else if(!connected)
  else if(!connected)
+12 −3
Original line number Original line Diff line number Diff line
@@ -362,6 +362,13 @@ typedef enum {
  FTPFILE_SINGLECWD = 3  /* make one CWD, then SIZE / RETR / STOR on the file */
  FTPFILE_SINGLECWD = 3  /* make one CWD, then SIZE / RETR / STOR on the file */
} curl_ftpfile;
} curl_ftpfile;


typedef enum {
  FTPTRANSFER_BODY, /* yes do transfer a body */
  FTPTRANSFER_INFO, /* do still go through to get info/headers */
  FTPTRANSFER_NONE, /* don't get anything and don't get info */
  FTPTRANSFER_LAST  /* end of list marker, never used */
} curl_ftptransfer;

/* This FTP struct is used in the SessionHandle. All FTP data that is
/* This FTP struct is used in the SessionHandle. All FTP data that is
   connection-oriented must be in FTP_conn to properly deal with the fact that
   connection-oriented must be in FTP_conn to properly deal with the fact that
   perhaps the SessionHandle is changed between the times the connection is
   perhaps the SessionHandle is changed between the times the connection is
@@ -372,8 +379,10 @@ struct FTP {
  char *passwd;  /* password string */
  char *passwd;  /* password string */
  char *urlpath; /* the originally given path part of the URL */
  char *urlpath; /* the originally given path part of the URL */
  char *file;    /* decoded file */
  char *file;    /* decoded file */
  bool no_transfer; /* nothing was transfered, (possibly because a resumed

                       transfer already was complete) */
  /* transfer a file/body or not, done as a typedefed enum just to make
     debuggers display the full symbol and not just the numerical value */
  curl_ftptransfer transfer;
  curl_off_t downloadsize;
  curl_off_t downloadsize;
};
};


tests/data/test542

0 → 100644
+57 −0
Original line number Original line Diff line number Diff line
<testcase>
<info>
<keywords>
FTP
PASV
RETR
</keywords>
</info>
# Server-side
<reply>
<data>
data
    to
      see
that FTP
works
  so does it?
</data>
<datacheck>
Content-Length: 51
Accept-ranges: bytes
</datacheck>
</reply>

# Client-side
<client>
<server>
ftp
</server>
<tool>
lib542
</tool>
 <name>
FTP a file with NOBODY yes and HEADER no
 </name>
 <command>
ftp://%HOSTIP:%FTPPORT/542
</command>

</client>

# Verify data after the test has been "shot"
#
# There's no MTDM in the protocol here since this code doesn't ask for the
# time/date of the file
<verify>
<protocol>
USER anonymous
PASS ftp@example.com
PWD
TYPE I
SIZE 542
REST 0
QUIT
</protocol>
</verify>
</testcase>
Loading