]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
ftp: do lineend conversions in client writer
authorStefan Eissing <stefan@eissing.org>
Tue, 6 Feb 2024 10:56:35 +0000 (11:56 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Thu, 8 Feb 2024 22:20:56 +0000 (23:20 +0100)
- remove the ftp special handling from sendf.c
- let ftp_do() add a client writer that does
  the linened conversions
- change the lineend conversion to no longer
  modify the passed buffer, but write smaller
  chunks to the next cwriter instead. The
  inefficiency of this will be mitigated once
  we add output buffering for all client writes.

Closes #12878

lib/ftp.c
lib/sendf.c
lib/urldata.h

index 9c0f6fb177044e42d59e48462f75106bf89d675c..e0590a61eb8c79d43d95b11188add6d271154802 100644 (file)
--- a/lib/ftp.c
+++ b/lib/ftp.c
@@ -255,6 +255,98 @@ static void freedirs(struct ftp_conn *ftpc)
   Curl_safefree(ftpc->newhost);
 }
 
+#ifdef CURL_DO_LINEEND_CONV
+/***********************************************************************
+ *
+ * Lineend Conversions
+ * On ASCII transfers, e.g. directory listings, we might get lines
+ * ending in '\r\n' and we prefer just '\n'.
+ * We might also get a lonely '\r' which we convert into a '\n'.
+ */
+struct ftp_cw_lc_ctx {
+  struct Curl_cwriter super;
+  bool newline_pending;
+};
+
+static CURLcode ftp_cw_lc_write(struct Curl_easy *data,
+                                struct Curl_cwriter *writer, int type,
+                                const char *buf, size_t blen)
+{
+  static char nl = '\n';
+  struct ftp_cw_lc_ctx *ctx = (struct ftp_cw_lc_ctx *)writer;
+
+  if(!(type & CLIENTWRITE_BODY) ||
+     data->conn->proto.ftpc.transfertype != 'A')
+    return Curl_cwriter_write(data, writer->next, type, buf, blen);
+
+  /* ASCII mode BODY data, convert lineends */
+  while(blen) {
+    /* do not pass EOS when writing parts */
+    int chunk_type = (type & ~CLIENTWRITE_EOS);
+    const char *cp;
+    size_t chunk_len;
+    CURLcode result;
+
+    if(ctx->newline_pending) {
+      if(buf[0] != '\n') {
+        /* previous chunk ended in '\r' and we do not see a '\n' in this one,
+         * need to write a newline. */
+        result = Curl_cwriter_write(data, writer->next, chunk_type, &nl, 1);
+        if(result)
+          return result;
+      }
+      /* either we just wrote the newline or it is part of the next
+       * chunk of bytes we write. */
+      data->state.crlf_conversions++;
+      ctx->newline_pending = FALSE;
+    }
+
+    cp = memchr(buf, '\r', blen);
+    if(!cp)
+      break;
+
+    /* write the bytes before the '\r', excluding the '\r' */
+    chunk_len = cp - buf;
+    if(chunk_len) {
+      result = Curl_cwriter_write(data, writer->next, chunk_type,
+                                  buf, chunk_len);
+      if(result)
+        return result;
+    }
+    /* skip the '\r', we now have a newline pending */
+    buf = cp + 1;
+    blen = blen - chunk_len - 1;
+    ctx->newline_pending = TRUE;
+  }
+
+  /* Any remaining data does not contain a '\r' */
+  if(blen) {
+    DEBUGASSERT(!ctx->newline_pending);
+    return Curl_cwriter_write(data, writer->next, type, buf, blen);
+  }
+  else if(type & CLIENTWRITE_EOS) {
+    /* EndOfStream, if we have a trailing cr, now is the time to write it */
+    if(ctx->newline_pending) {
+      ctx->newline_pending = FALSE;
+      data->state.crlf_conversions++;
+      return Curl_cwriter_write(data, writer->next, type, &nl, 1);
+    }
+    /* Always pass on the EOS type indicator */
+    return Curl_cwriter_write(data, writer->next, type, buf, 0);
+  }
+  return CURLE_OK;
+}
+
+static const struct Curl_cwtype ftp_cw_lc = {
+  "ftp-lineconv",
+  NULL,
+  Curl_cwriter_def_init,
+  ftp_cw_lc_write,
+  Curl_cwriter_def_close,
+  sizeof(struct ftp_cw_lc_ctx)
+};
+
+#endif /* CURL_DO_LINEEND_CONV */
 /***********************************************************************
  *
  * AcceptServerConnect()
@@ -4035,6 +4127,24 @@ static CURLcode ftp_do(struct Curl_easy *data, bool *done)
   *done = FALSE; /* default to false */
   ftpc->wait_data_conn = FALSE; /* default to no such wait */
 
+#ifdef CURL_DO_LINEEND_CONV
+  {
+    /* FTP data may need conversion. */
+    struct Curl_cwriter *ftp_lc_writer;
+
+    result = Curl_cwriter_create(&ftp_lc_writer, data, &ftp_cw_lc,
+                                 CURL_CW_CONTENT_DECODE);
+    if(result)
+      return result;
+
+    result = Curl_cwriter_add(data, ftp_lc_writer);
+    if(result) {
+      Curl_cwriter_free(data, ftp_lc_writer);
+      return result;
+    }
+  }
+#endif /* CURL_DO_LINEEND_CONV */
+
   if(data->state.wildcardmatch) {
     result = wc_statemach(data);
     if(data->wildcard->state == CURLWC_SKIP ||
index 60ac0742cccdc5fabc63a6b750b3da1021558a6a..8accc179648847ba04bc4a06768f5a670a6518ed 100644 (file)
 
 static CURLcode do_init_stack(struct Curl_easy *data);
 
-#if defined(CURL_DO_LINEEND_CONV) && !defined(CURL_DISABLE_FTP)
-/*
- * convert_lineends() changes CRLF (\r\n) end-of-line markers to a single LF
- * (\n), with special processing for CRLF sequences that are split between two
- * blocks of data.  Remaining, bare CRs are changed to LFs.  The possibly new
- * size of the data is returned.
- */
-static size_t convert_lineends(struct Curl_easy *data,
-                               char *startPtr, size_t size)
-{
-  char *inPtr, *outPtr;
-
-  /* sanity check */
-  if(!startPtr || (size < 1)) {
-    return size;
-  }
-
-  if(data->state.prev_block_had_trailing_cr) {
-    /* The previous block of incoming data
-       had a trailing CR, which was turned into a LF. */
-    if(*startPtr == '\n') {
-      /* This block of incoming data starts with the
-         previous block's LF so get rid of it */
-      memmove(startPtr, startPtr + 1, size-1);
-      size--;
-      /* and it wasn't a bare CR but a CRLF conversion instead */
-      data->state.crlf_conversions++;
-    }
-    data->state.prev_block_had_trailing_cr = FALSE; /* reset the flag */
-  }
-
-  /* find 1st CR, if any */
-  inPtr = outPtr = memchr(startPtr, '\r', size);
-  if(inPtr) {
-    /* at least one CR, now look for CRLF */
-    while(inPtr < (startPtr + size-1)) {
-      /* note that it's size-1, so we'll never look past the last byte */
-      if(memcmp(inPtr, "\r\n", 2) == 0) {
-        /* CRLF found, bump past the CR and copy the NL */
-        inPtr++;
-        *outPtr = *inPtr;
-        /* keep track of how many CRLFs we converted */
-        data->state.crlf_conversions++;
-      }
-      else {
-        if(*inPtr == '\r') {
-          /* lone CR, move LF instead */
-          *outPtr = '\n';
-        }
-        else {
-          /* not a CRLF nor a CR, just copy whatever it is */
-          *outPtr = *inPtr;
-        }
-      }
-      outPtr++;
-      inPtr++;
-    } /* end of while loop */
-
-    if(inPtr < startPtr + size) {
-      /* handle last byte */
-      if(*inPtr == '\r') {
-        /* deal with a CR at the end of the buffer */
-        *outPtr = '\n'; /* copy a NL instead */
-        /* note that a CRLF might be split across two blocks */
-        data->state.prev_block_had_trailing_cr = TRUE;
-      }
-      else {
-        /* copy last byte */
-        *outPtr = *inPtr;
-      }
-      outPtr++;
-    }
-    if(outPtr < startPtr + size)
-      /* tidy up by null terminating the now shorter data */
-      *outPtr = '\0';
-
-    return (outPtr - startPtr);
-  }
-  return size;
-}
-#endif /* CURL_DO_LINEEND_CONV && !CURL_DISABLE_FTP */
-
 /*
  * Curl_nwrite() is an internal write function that sends data to the
  * server. Works with a socket index for the connection.
@@ -376,25 +294,12 @@ static CURLcode chop_write(struct Curl_easy *data,
 
    The bit pattern defines to what "streams" to write to. Body and/or header.
    The defines are in sendf.h of course.
-
-   If CURL_DO_LINEEND_CONV is enabled, data is converted IN PLACE to the
-   local character encoding.  This is a problem and should be changed in
-   the future to leave the original data alone.
  */
 CURLcode Curl_client_write(struct Curl_easy *data,
                            int type, char *buf, size_t blen)
 {
   CURLcode result;
 
-#if !defined(CURL_DISABLE_FTP) && defined(CURL_DO_LINEEND_CONV)
-  /* FTP data may need conversion. */
-  if((type & CLIENTWRITE_BODY) &&
-     (data->conn->handler->protocol & PROTO_FAMILY_FTP) &&
-     data->conn->proto.ftpc.transfertype == 'A') {
-    /* convert end-of-line markers */
-    blen = convert_lineends(data, buf, blen);
-  }
-#endif
   /* it is one of those, at least */
   DEBUGASSERT(type & (CLIENTWRITE_BODY|CLIENTWRITE_HEADER|CLIENTWRITE_INFO));
   /* BODY is only BODY (with optional EOS) */
index 9dcccc7039bf060b3489f00d3a81fe14d979326c..49aed15fda7647bf55b66aff56c18674549dde9f 100644 (file)
@@ -1387,8 +1387,6 @@ struct UrlState {
 #if !defined(_WIN32) && !defined(MSDOS) && !defined(__EMX__)
 /* do FTP line-end conversions on most platforms */
 #define CURL_DO_LINEEND_CONV
-  /* for FTP downloads: track CRLF sequences that span blocks */
-  BIT(prev_block_had_trailing_cr);
   /* for FTP downloads: how many CRLFs did we converted to LFs? */
   curl_off_t crlf_conversions;
 #endif