DONE: consider callback-aborted transfers premature
authorDaniel Stenberg <daniel@haxx.se>
Fri, 8 Feb 2013 12:48:56 +0000 (13:48 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 8 Feb 2013 12:57:01 +0000 (13:57 +0100)
This bug report properly identified that when doing SMTP and aborting
the transfer with a callback, it must be considered aborted prematurely
by the code to avoid QUIT etc to be attempted as that would cause a
hang.

The new test case 1507 verifies this behavior.

Reported by: Patricia Muscalu
Bug: http://curl.haxx.se/bug/view.cgi?id=1184

lib/url.c
tests/data/Makefile.am
tests/data/test1507 [new file with mode: 0644]
tests/libtest/Makefile.inc
tests/libtest/lib1507.c [new file with mode: 0644]

index 918ce58..df70b69 100644 (file)
--- a/lib/url.c
+++ b/lib/url.c
@@ -5219,6 +5219,13 @@ CURLcode Curl_done(struct connectdata **connp,
     conn->dns_entry = NULL;
   }
 
+  if(status == CURLE_ABORTED_BY_CALLBACK)
+    /* When we're aborted due to a callback return code it basically have to
+       be counted as premature as there is trouble ahead if we don't. We have
+       many callbacks and protocols work differently, we could potentially do
+       this more fine-grained in the future. */
+    premature = TRUE;
+
   /* this calls the protocol-specific function pointer previously set */
   if(conn->handler->done)
     result = conn->handler->done(conn, status, premature);
index d82534d..ae59643 100644 (file)
@@ -93,7 +93,7 @@ test1379 test1380 test1381 test1382 test1383 test1384 test1385 test1386 \
 test1387 test1388 test1389 test1390 test1391 test1392 test1393 \
 test1400 test1401 test1402 test1403 test1404 test1405 test1406 test1407 \
 test1408 test1409 test1410 test1411 test1412 test1413 \
-test1500 test1501 test1502 test1503 test1504 test1505 test1506 \
+test1500 test1501 test1502 test1503 test1504 test1505 test1506 test1507 \
 test2000 test2001 test2002 test2003 test2004 test2005 test2006 test2007 \
 test2008 test2009 test2010 test2011 test2012 test2013 test2014 test2015 \
 test2016 test2017 test2018 test2019 test2020 test2021 test2022 \
diff --git a/tests/data/test1507 b/tests/data/test1507
new file mode 100644 (file)
index 0000000..76ec532
--- /dev/null
@@ -0,0 +1,51 @@
+<testcase>
+<info>
+<keywords>
+SMTP
+multi
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+smtp
+</server>
+<tool>
+lib1507
+</tool>
+
+# based on bug report #1184
+ <name>
+SMTP with multi interface and CURLE_ABORTED_BY_CALLBACK
+ </name>
+<stdin>
+From: different
+To: another
+
+body
+</stdin>
+ <command>
+smtp://%HOSTIP:%SMTPPORT/user
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+EHLO user\r
+MAIL FROM:<1507-realuser@example.com>\r
+RCPT TO:<1507-recipient@example.com>\r
+DATA\r
+</protocol>
+<upload>
+</upload>
+</verify>
+</testcase>
index 82c265d..41bb45f 100644 (file)
@@ -23,7 +23,7 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect \
                 lib582 lib583        lib585 lib586 lib587               \
   lib590 lib591                                    lib597 lib598 lib599 \
   \
-  lib1500 lib1501 lib1502 lib1503 lib1504 lib1505 lib1506
+  lib1500 lib1501 lib1502 lib1503 lib1504 lib1505 lib1506 lib1507
 
 chkhostname_SOURCES = chkhostname.c ../../lib/curl_gethostname.c
 chkhostname_LDADD = @CURL_NETWORK_LIBS@
@@ -312,3 +312,7 @@ lib1505_CPPFLAGS = $(AM_CPPFLAGS) -DLIB1505
 lib1506_SOURCES = lib1506.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
 lib1506_LDADD = $(TESTUTIL_LIBS)
 lib1506_CPPFLAGS = $(AM_CPPFLAGS) -DLIB1506
+
+lib1507_SOURCES = lib1507.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
+lib1507_LDADD = $(TESTUTIL_LIBS)
+lib1507_CPPFLAGS = $(AM_CPPFLAGS) -DLIB1507
diff --git a/tests/libtest/lib1507.c b/tests/libtest/lib1507.c
new file mode 100644 (file)
index 0000000..7c4e6ed
--- /dev/null
@@ -0,0 +1,167 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+#include "test.h"
+
+#include "testutil.h"
+#include "warnless.h"
+#include "memdebug.h"
+
+/*
+ * This is the list of basic details you need to tweak to get things right.
+ */
+#define USERNAME "user@example.com"
+#define PASSWORD "123qwerty"
+#define RECIPIENT "<1507-recipient@example.com>"
+#define MAILFROM "<1507-realuser@example.com>"
+
+#define MULTI_PERFORM_HANG_TIMEOUT 60 * 1000
+
+static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp)
+{
+  (void)ptr;
+  (void)size;
+  (void)nmemb;
+  (void)userp;
+  return CURL_READFUNC_ABORT;
+}
+
+static struct timeval tvnow(void)
+{
+  /*
+  ** time() returns the value of time in seconds since the Epoch.
+  */
+  struct timeval now;
+  now.tv_sec = (long)time(NULL);
+  now.tv_usec = 0;
+  return now;
+}
+
+static long tvdiff(struct timeval newer, struct timeval older)
+{
+  return (newer.tv_sec-older.tv_sec)*1000+
+    (newer.tv_usec-older.tv_usec)/1000;
+}
+
+int test(char *URL)
+{
+   CURL *curl;
+   CURLM *mcurl;
+   int still_running = 1;
+   struct timeval mp_start;
+   struct curl_slist* rcpt_list = NULL;
+
+   curl_global_init(CURL_GLOBAL_DEFAULT);
+
+   curl = curl_easy_init();
+   if(!curl)
+     return 1;
+
+   mcurl = curl_multi_init();
+   if(!mcurl)
+     return 2;
+
+   rcpt_list = curl_slist_append(rcpt_list, RECIPIENT);
+   /* more addresses can be added here
+      rcpt_list = curl_slist_append(rcpt_list, "<others@example.com>");
+   */
+
+   curl_easy_setopt(curl, CURLOPT_URL, URL);
+#if 0
+   curl_easy_setopt(curl, CURLOPT_USERNAME, USERNAME);
+   curl_easy_setopt(curl, CURLOPT_PASSWORD, PASSWORD);
+#endif
+   curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);
+   curl_easy_setopt(curl, CURLOPT_MAIL_FROM, MAILFROM);
+   curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, rcpt_list);
+   curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+   curl_multi_add_handle(mcurl, curl);
+
+   mp_start = tvnow();
+
+  /* we start some action by calling perform right away */
+  curl_multi_perform(mcurl, &still_running);
+
+  while(still_running) {
+    struct timeval timeout;
+    int rc; /* select() return code */
+
+    fd_set fdread;
+    fd_set fdwrite;
+    fd_set fdexcep;
+    int maxfd = -1;
+
+    long curl_timeo = -1;
+
+    FD_ZERO(&fdread);
+    FD_ZERO(&fdwrite);
+    FD_ZERO(&fdexcep);
+
+    /* set a suitable timeout to play around with */
+    timeout.tv_sec = 1;
+    timeout.tv_usec = 0;
+
+    curl_multi_timeout(mcurl, &curl_timeo);
+    if(curl_timeo >= 0) {
+      timeout.tv_sec = curl_timeo / 1000;
+      if(timeout.tv_sec > 1)
+        timeout.tv_sec = 1;
+      else
+        timeout.tv_usec = (curl_timeo % 1000) * 1000;
+    }
+
+    /* get file descriptors from the transfers */
+    curl_multi_fdset(mcurl, &fdread, &fdwrite, &fdexcep, &maxfd);
+
+    /* In a real-world program you OF COURSE check the return code of the
+       function calls.  On success, the value of maxfd is guaranteed to be
+       greater or equal than -1.  We call select(maxfd + 1, ...), specially in
+       case of (maxfd == -1), we call select(0, ...), which is basically equal
+       to sleep. */
+
+    rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
+
+    if (tvdiff(tvnow(), mp_start) > MULTI_PERFORM_HANG_TIMEOUT) {
+      fprintf(stderr, "ABORTING TEST, since it seems "
+              "that it would have run forever.\n");
+      break;
+    }
+
+    switch(rc) {
+    case -1:
+      /* select error */
+      break;
+    case 0: /* timeout */
+    default: /* action */
+      curl_multi_perform(mcurl, &still_running);
+      break;
+    }
+  }
+
+  curl_slist_free_all(rcpt_list);
+  curl_multi_remove_handle(mcurl, curl);
+  curl_multi_cleanup(mcurl);
+  curl_easy_cleanup(curl);
+  curl_global_cleanup();
+  return 0;
+}
+
+