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 Diff line number Diff line
@@ -6,6 +6,12 @@

                                  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)
- 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
+3 −1
Original line number 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 Negotiate authentication over proxy
 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:

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

 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)
+34 −29
Original line number 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 SessionHandle *data = conn->data;

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

    /* still possibly do PRE QUOTE jobs */
@@ -1235,7 +1235,7 @@ static CURLcode ftp_state_post_size(struct connectdata *conn)
  CURLcode result = CURLE_OK;
  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) */

    /* 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;
  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) */

    /* 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
     information. Which in FTP can't be much more than the file size and
     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)) {
    /* 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
       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
       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 */
        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! */
        ftp->no_transfer = TRUE;
        ftp->transfer = FTPTRANSFER_NONE;

        state(conn, FTP_STOP);
        return CURLE_OK;
@@ -1547,7 +1548,7 @@ static CURLcode ftp_state_quote(struct connectdata *conn,
      result = ftp_state_cwd(conn);
      break;
    case FTP_RETR_PREQUOTE:
      if (ftp->no_transfer)
      if (ftp->transfer != FTPTRANSFER_BODY)
        state(conn, FTP_STOP);
      else {
        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. */

      if(conn->bits.no_body &&
         data->set.include_header &&
         ftp->file &&
         data->set.get_filetime &&
         (data->info.filetime>=0) ) {
@@ -1922,7 +1922,7 @@ static CURLcode ftp_state_mdtm_resp(struct connectdata *conn,
      default:
        if(data->info.filetime <= data->set.timevalue) {
          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);
          return CURLE_OK;
        }
@@ -1930,7 +1930,7 @@ static CURLcode ftp_state_mdtm_resp(struct connectdata *conn,
      case CURL_TIMECOND_IFUNMODSINCE:
        if(data->info.filetime > data->set.timevalue) {
          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);
          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);
      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 */
      ftp->no_transfer = TRUE;
      ftp->transfer = FTPTRANSFER_NONE;
      state(conn, FTP_STOP);
      return CURLE_OK;
    }
@@ -2291,7 +2291,7 @@ static CURLcode ftp_state_get_resp(struct connectdata *conn,
  else {
    if((instate == FTP_LIST) && (ftpcode == 450)) {
      /* 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 */
    }
    else {
@@ -3121,7 +3121,7 @@ CURLcode Curl_ftp_done(struct connectdata *conn, CURLcode status,

  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,
     * 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) &&
       (data->set.infilesize != *ftp->bytecountp) &&
       !data->set.crlf &&
       !ftp->no_transfer) {
       (ftp->transfer == FTPTRANSFER_BODY)) {
      failf(data, "Uploaded unaligned file size (%" FORMAT_OFF_T
            " out of %" FORMAT_OFF_T " bytes)",
            *ftp->bytecountp, data->set.infilesize);
@@ -3192,7 +3192,7 @@ CURLcode Curl_ftp_done(struct connectdata *conn, CURLcode status,
  }

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

  /* 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"));

  if(!ftp->no_transfer) {
    /* a transfer is about to take place */
  if(ftp->transfer <= FTPTRANSFER_INFO) {
    /* 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) {
      result = ftp_nb_type(conn, data->set.prefer_ascii,
                                      FTP_STOR_TYPE);
      result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_STOR_TYPE);
      if (result)
        return result;
    }
@@ -3391,14 +3391,19 @@ CURLcode Curl_ftp_nextconnect(struct connectdata *conn)
      result = ftp_range(conn);
      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
           is a directory that is requested, use LIST. But before that we
           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);
          if (result)
            return result;
        }
        /* otherwise just fall through */
      }
      else {
        result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_RETR_TYPE);
        if (result)
@@ -3408,7 +3413,7 @@ CURLcode Curl_ftp_nextconnect(struct connectdata *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
       too! */
    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) {
    /* requested no body means no transfer... */
    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
                       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! */
    failf(data, "Uploading to a URL without a file name!");
    return CURLE_URL_MALFORMAT;
@@ -3912,7 +3917,7 @@ static CURLcode ftp_dophase_done(struct connectdata *conn,
    return result;
  }

  if(ftp->no_transfer)
  if(ftp->transfer != FTPTRANSFER_BODY)
    /* no data to transfer */
    result=Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
  else if(!connected)
+12 −3
Original line number Diff line number Diff line
@@ -362,6 +362,13 @@ typedef enum {
  FTPFILE_SINGLECWD = 3  /* make one CWD, then SIZE / RETR / STOR on the file */
} 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
   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
@@ -372,8 +379,10 @@ struct FTP {
  char *passwd;  /* password string */
  char *urlpath; /* the originally given path part of the URL */
  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;
};

tests/data/test542

0 → 100644
+57 −0
Original line number 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