[evolution-data-server] I#319 - SMTP: Extra empty line added at the end of the message



commit bd644b708188f9c5f9735ac92066a83e23cedd33
Author: Milan Crha <mcrha redhat com>
Date:   Tue Mar 30 11:42:57 2021 +0200

    I#319 - SMTP: Extra empty line added at the end of the message
    
    Closes https://gitlab.gnome.org/GNOME/evolution-data-server/-/issues/319

 docs/reference/camel/camel-docs.sgml.in         |  4 ++
 src/camel/camel-filter-input-stream.c           |  2 +-
 src/camel/camel-mime-filter-crlf.c              | 69 ++++++++++++++++++
 src/camel/camel-mime-filter-crlf.h              |  5 ++
 src/camel/providers/smtp/camel-smtp-transport.c |  6 +-
 src/camel/tests/mime-filter/test-crlf.c         | 96 +++++++++++++++++++++++--
 6 files changed, 175 insertions(+), 7 deletions(-)
---
diff --git a/docs/reference/camel/camel-docs.sgml.in b/docs/reference/camel/camel-docs.sgml.in
index 360e5c9a1..4f54beb54 100644
--- a/docs/reference/camel/camel-docs.sgml.in
+++ b/docs/reference/camel/camel-docs.sgml.in
@@ -298,6 +298,10 @@
     <title>Index of deprecated symbols</title>
     <xi:include href="xml/api-index-deprecated.xml"><xi:fallback /></xi:include>
   </index>
+  <index id="api-index-3-42" role="3.42">
+    <title>Index of new symbols in 3.42</title>
+    <xi:include href="xml/api-index-3.42.xml"><xi:fallback /></xi:include>
+  </index>
   <index id="api-index-3-40" role="3.40">
     <title>Index of new symbols in 3.40</title>
     <xi:include href="xml/api-index-3.40.xml"><xi:fallback /></xi:include>
diff --git a/src/camel/camel-filter-input-stream.c b/src/camel/camel-filter-input-stream.c
index a7511d7b0..437ba1a00 100644
--- a/src/camel/camel-filter-input-stream.c
+++ b/src/camel/camel-filter-input-stream.c
@@ -144,7 +144,7 @@ filter_input_stream_read (GInputStream *stream,
 
        if (n_bytes_read == 0) {
                camel_mime_filter_complete (
-                       filter, priv->filtered, priv->filtered_length, presize,
+                       filter, priv->filtered ? priv->filtered : "", priv->filtered ? priv->filtered_length 
: 0, presize,
                        &priv->filtered, &priv->filtered_length, &presize);
 
                n_bytes_read = priv->filtered_length;
diff --git a/src/camel/camel-mime-filter-crlf.c b/src/camel/camel-mime-filter-crlf.c
index 5f942d7f2..d8f93bde3 100644
--- a/src/camel/camel-mime-filter-crlf.c
+++ b/src/camel/camel-mime-filter-crlf.c
@@ -26,6 +26,8 @@ struct _CamelMimeFilterCRLFPrivate {
        gboolean saw_cr;
        gboolean saw_lf;
        gboolean saw_dot;
+       gboolean ends_with_lf;
+       gboolean ensure_crlf_end;
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE (CamelMimeFilterCRLF, camel_mime_filter_crlf, CAMEL_TYPE_MIME_FILTER)
@@ -74,6 +76,9 @@ mime_filter_crlf_filter (CamelMimeFilter *mime_filter,
 
                        *outptr++ = *inptr++;
                }
+
+               if (len > 0)
+                       priv->ends_with_lf = in[len - 1] == '\n';
        } else {
                /* Output can "grow" by one byte if priv->saw_cr was set as
                 * a carry-over from the previous invocation. This will happen
@@ -130,10 +135,34 @@ mime_filter_crlf_complete (CamelMimeFilter *mime_filter,
                            gsize *outlen,
                            gsize *outprespace)
 {
+       CamelMimeFilterCRLF *crlf_filter;
+
        if (len)
                mime_filter_crlf_filter (
                        mime_filter, in, len, prespace,
                        out, outlen, outprespace);
+       else
+               *outlen = 0;
+
+       crlf_filter = CAMEL_MIME_FILTER_CRLF (mime_filter);
+
+       if (crlf_filter->priv->direction == CAMEL_MIME_FILTER_CRLF_ENCODE &&
+           crlf_filter->priv->ensure_crlf_end &&
+           !crlf_filter->priv->ends_with_lf) {
+               gchar *outptr;
+
+               camel_mime_filter_set_size (mime_filter, *outlen + 2, FALSE);
+
+               outptr = mime_filter->outbuf + *outlen;
+               *outptr++ = '\r';
+               *outptr++ = '\n';
+
+               crlf_filter->priv->ends_with_lf = TRUE;
+
+               *out = mime_filter->outbuf;
+               *outlen = outptr - mime_filter->outbuf;
+               *outprespace = mime_filter->outpre;
+       }
 }
 
 static void
@@ -146,6 +175,7 @@ mime_filter_crlf_reset (CamelMimeFilter *mime_filter)
        priv->saw_cr = FALSE;
        priv->saw_lf = TRUE;
        priv->saw_dot = FALSE;
+       priv->ends_with_lf = FALSE;
 }
 
 static void
@@ -167,6 +197,8 @@ camel_mime_filter_crlf_init (CamelMimeFilterCRLF *filter)
        filter->priv->saw_cr = FALSE;
        filter->priv->saw_lf = TRUE;
        filter->priv->saw_dot = FALSE;
+       filter->priv->ends_with_lf = FALSE;
+       filter->priv->ensure_crlf_end = FALSE;
 }
 
 /**
@@ -193,3 +225,40 @@ camel_mime_filter_crlf_new (CamelMimeFilterCRLFDirection direction,
 
        return filter;
 }
+
+/**
+ * camel_mime_filter_crlf_set_ensure_crlf_end:
+ * @filter: a #CamelMimeFilterCRLF
+ * @ensure_crlf_end: value to set
+ *
+ * When set to true, the filter will ensure that the output stream will
+ * end with CRLF, in case it does not. The default is to not do that.
+ * The option is used only when encoding the stream.
+ *
+ * Since: 3.42
+ **/
+void
+camel_mime_filter_crlf_set_ensure_crlf_end (CamelMimeFilterCRLF *filter,
+                                           gboolean ensure_crlf_end)
+{
+       g_return_if_fail (CAMEL_IS_MIME_FILTER_CRLF (filter));
+
+       filter->priv->ensure_crlf_end = ensure_crlf_end;
+}
+
+/**
+ * camel_mime_filter_crlf_get_ensure_crlf_end:
+ * @filter: a #CamelMimeFilterCRLF
+ *
+ * Returns: whether the filter will ensure that the output stream will
+ *    end with CRLF
+ *
+ * Since: 3.42
+ **/
+gboolean
+camel_mime_filter_crlf_get_ensure_crlf_end (CamelMimeFilterCRLF *filter)
+{
+       g_return_val_if_fail (CAMEL_IS_MIME_FILTER_CRLF (filter), FALSE);
+
+       return filter->priv->ensure_crlf_end;
+}
diff --git a/src/camel/camel-mime-filter-crlf.h b/src/camel/camel-mime-filter-crlf.h
index 47dc50266..65671417b 100644
--- a/src/camel/camel-mime-filter-crlf.h
+++ b/src/camel/camel-mime-filter-crlf.h
@@ -69,6 +69,11 @@ GType                camel_mime_filter_crlf_get_type (void);
 CamelMimeFilter *
                camel_mime_filter_crlf_new      (CamelMimeFilterCRLFDirection direction,
                                                 CamelMimeFilterCRLFMode mode);
+void           camel_mime_filter_crlf_set_ensure_crlf_end
+                                               (CamelMimeFilterCRLF *filter,
+                                                gboolean ensure_crlf_end);
+gboolean       camel_mime_filter_crlf_get_ensure_crlf_end
+                                               (CamelMimeFilterCRLF *filter);
 
 G_END_DECLS
 
diff --git a/src/camel/providers/smtp/camel-smtp-transport.c b/src/camel/providers/smtp/camel-smtp-transport.c
index c6b5aebee..117811977 100644
--- a/src/camel/providers/smtp/camel-smtp-transport.c
+++ b/src/camel/providers/smtp/camel-smtp-transport.c
@@ -1824,6 +1824,7 @@ smtp_data (CamelSmtpTransport *transport,
        filter = camel_mime_filter_crlf_new (
                CAMEL_MIME_FILTER_CRLF_ENCODE,
                CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
+       camel_mime_filter_crlf_set_ensure_crlf_end (CAMEL_MIME_FILTER_CRLF (filter), TRUE);
        camel_stream_filter_add (
                CAMEL_STREAM_FILTER (filtered_stream), filter);
        g_object_unref (filter);
@@ -1843,6 +1844,7 @@ smtp_data (CamelSmtpTransport *transport,
                filter = camel_mime_filter_crlf_new (
                        CAMEL_MIME_FILTER_CRLF_ENCODE,
                        CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
+               camel_mime_filter_crlf_set_ensure_crlf_end (CAMEL_MIME_FILTER_CRLF (filter), TRUE);
                camel_stream_filter_add (CAMEL_STREAM_FILTER (sec_filtered_stream), filter);
                g_object_unref (filter);
 
@@ -1892,9 +1894,9 @@ smtp_data (CamelSmtpTransport *transport,
 
        /* terminate the message body */
 
-       d (fprintf (stderr, "[SMTP] sending: \\r\\n.\\r\\n\n"));
+       d (fprintf (stderr, "[SMTP] sending: .\\r\\n\n"));
 
-       if (camel_stream_write (ostream, "\r\n.\r\n", 5, cancellable, error) == -1) {
+       if (camel_stream_write (ostream, ".\r\n", 3, cancellable, error) == -1) {
                g_prefix_error (error, _("DATA command failed: "));
                camel_service_disconnect_sync (
                        CAMEL_SERVICE (transport),
diff --git a/src/camel/tests/mime-filter/test-crlf.c b/src/camel/tests/mime-filter/test-crlf.c
index cff46ddf1..77cc7f083 100644
--- a/src/camel/tests/mime-filter/test-crlf.c
+++ b/src/camel/tests/mime-filter/test-crlf.c
@@ -176,23 +176,111 @@ test_case (gint test_num)
        camel_test_pull ();
 }
 
+static gboolean
+test_case_ensure_crlf_end_run (const gchar *in,
+                              const gchar *expected,
+                              gboolean ensure_crlf_end)
+{
+       CamelMimeFilter *filter;
+       GInputStream *input_stream;
+       GInputStream *filter_stream;
+       gchar bytes[64];
+       gsize bytes_read = 0;
+       gboolean success = FALSE;
+
+       if (strlen (expected) >= sizeof (bytes) - 1) {
+               camel_test_fail ("Local buffer too small (%u bytes) to cover %u bytes", sizeof (bytes), 
strlen (expected));
+               return FALSE;
+       }
+
+       input_stream = g_memory_input_stream_new_from_data (in, strlen (in), NULL);
+
+       filter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, 
CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
+       camel_mime_filter_crlf_set_ensure_crlf_end (CAMEL_MIME_FILTER_CRLF (filter), ensure_crlf_end);
+       filter_stream = camel_filter_input_stream_new (input_stream, filter);
+       g_object_unref (filter);
+
+       if (g_input_stream_read_all (filter_stream, bytes, sizeof (bytes) - 1, &bytes_read, NULL, NULL)) {
+               bytes[bytes_read] = '\0';
+
+               if (bytes_read == strlen (expected)) {
+                       success = memcmp (bytes, expected, bytes_read) == 0;
+
+                       if (!success)
+                               camel_test_fail ("Returned text '%s' and expected text '%s' do not match", 
bytes, expected);
+               } else {
+                       camel_test_fail ("Read %u bytes, but expected %u bytes", bytes_read, strlen 
(expected));
+               }
+       } else {
+               camel_test_fail ("Failed to read up to %u bytes from the input stream", sizeof (bytes));
+       }
+
+       g_object_unref (filter_stream);
+       g_object_unref (input_stream);
+
+       return success;
+}
+
+static void
+test_case_ensure_crlf_end (void)
+{
+       struct _data {
+               const gchar *in;
+               const gchar *out_without;
+               const gchar *out_with;
+       } data[] = {
+               { "", "", "\r\n" },
+               { "a", "a", "a\r\n" },
+               { "a\n", "a\r\n", "a\r\n" },
+               { "a\r\n", "a\r\n", "a\r\n" },
+               { "a\r\nb", "a\r\nb", "a\r\nb\r\n" },
+               { "a\nb", "a\r\nb", "a\r\nb\r\n" },
+               { "a\r\nb\n", "a\r\nb\r\n", "a\r\nb\r\n" },
+               { "a\n\nb", "a\r\n\r\nb", "a\r\n\r\nb\r\n" }
+       };
+       guint ii;
+
+       camel_test_push ("Test encode with used ensure-crlf-end");
+
+       for (ii = 0; ii < G_N_ELEMENTS (data); ii++) {
+               camel_test_push ("case %d/a (without set option)", ii);
+               if (!test_case_ensure_crlf_end_run (data[ii].in, data[ii].out_without, FALSE)) {
+                       camel_test_pull ();
+                       break;
+               }
+
+               camel_test_pull ();
+               camel_test_push ("case %d/b (with set option)", ii);
+
+               if (!test_case_ensure_crlf_end_run (data[ii].in, data[ii].out_with, TRUE)) {
+                       camel_test_pull ();
+                       break;
+               }
+
+               camel_test_pull ();
+       }
+
+       camel_test_pull ();
+}
+
 gint
 main (gint argc,
       gchar **argv)
 {
-       gchar *work;
        gint ii;
 
        camel_test_init (argc, argv);
 
-       work = g_strdup_printf ("CRLF/DOT filter, test case %d", 0);
-       camel_test_start (work);
-       g_free (work);
+       camel_test_start ("CRLF/DOT filter, test case 0");
 
        for (ii = CRLF_ENCODE; ii < CRLF_DONE; ii++)
                test_case (ii);
 
        camel_test_end ();
 
+       camel_test_start ("CRLF/DOT filter, test case 1");
+       test_case_ensure_crlf_end ();
+       camel_test_end ();
+
        return 0;
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]