]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
POP3: fix multi-line responses
authorStefan Eissing <stefan@eissing.org>
Wed, 28 Aug 2024 09:09:43 +0000 (11:09 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 30 Aug 2024 13:38:25 +0000 (15:38 +0200)
Some POP3 commands are multi-line, e.g. have responses terminated by a
last line with '.', but some are not. Define the known command
properties and fix response handling.

Add test case for STAT.

Fixes #14677
Reported-by: ralfjunker on github
Closes #14707

lib/pop3.c
tests/data/Makefile.am
tests/data/test997 [new file with mode: 0644]

index 47271252e8b01ebdd8f6331cc9b7a2fc784b1387..1d12a71632cfcee4fc1c6fcb0217be2a22534248 100644 (file)
 #include "curl_memory.h"
 #include "memdebug.h"
 
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0]))
+#endif
+
 /* Local API functions */
 static CURLcode pop3_regular_transfer(struct Curl_easy *data, bool *done);
 static CURLcode pop3_do(struct Curl_easy *data, bool *done);
@@ -199,6 +203,53 @@ static void pop3_to_pop3s(struct connectdata *conn)
 #define pop3_to_pop3s(x) Curl_nop_stmt
 #endif
 
+struct pop3_cmd {
+  const char *name;
+  unsigned short nlen;
+  BIT(multiline); /* response is multi-line with last '.' line */
+  BIT(multiline_with_args); /* is multi-line when command has args */
+};
+
+static const struct pop3_cmd pop3cmds[] = {
+  { "APOP", 4, FALSE, FALSE },
+  { "AUTH", 4, FALSE, FALSE },
+  { "CAPA", 4, TRUE, TRUE },
+  { "DELE", 4, FALSE, FALSE },
+  { "LIST", 4, TRUE, TRUE },
+  { "MSG",  3, TRUE, TRUE },
+  { "NOOP", 4, FALSE, FALSE },
+  { "PASS", 4, FALSE, FALSE },
+  { "QUIT", 4, FALSE, FALSE },
+  { "RETR", 4, TRUE, TRUE },
+  { "RSET", 4, FALSE, FALSE },
+  { "STAT", 4, FALSE, FALSE },
+  { "STLS", 4, FALSE, FALSE },
+  { "TOP",  3, TRUE, TRUE },
+  { "UIDL", 4, TRUE, FALSE },
+  { "USER", 4, FALSE, FALSE },
+  { "UTF8", 4, FALSE, FALSE },
+  { "XTND", 4, TRUE, TRUE },
+};
+
+/* Return iff a command is defined as "multi-line" (RFC 1939),
+ * has a response terminated by a last line with a '.'.
+ */
+static bool pop3_is_multiline(const char *cmdline)
+{
+  size_t i;
+  for(i = 0; i < ARRAYSIZE(pop3cmds); ++i) {
+    if(strncasecompare(pop3cmds[i].name, cmdline, pop3cmds[i].nlen)) {
+      if(!cmdline[pop3cmds[i].nlen])
+        return pop3cmds[i].multiline;
+      else if(cmdline[pop3cmds[i].nlen] == ' ')
+        return pop3cmds[i].multiline_with_args;
+    }
+  }
+  /* Unknown command, assume multi-line for backward compatibility with
+   * earlier curl versions that only could do multi-line responses. */
+  return TRUE;
+}
+
 /***********************************************************************
  *
  * pop3_endofresp()
@@ -614,18 +665,20 @@ static CURLcode pop3_perform_command(struct Curl_easy *data)
   else
     command = "RETR";
 
+  if(pop3->custom && pop3->custom[0] != '\0')
+    command = pop3->custom;
+
   /* Send the command */
   if(pop3->id[0] != '\0')
     result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s %s",
-                           (pop3->custom && pop3->custom[0] != '\0' ?
-                            pop3->custom : command), pop3->id);
+                           command, pop3->id);
   else
-    result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s",
-                           (pop3->custom && pop3->custom[0] != '\0' ?
-                            pop3->custom : command));
+    result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s", command);
 
-  if(!result)
+  if(!result) {
     pop3_state(data, POP3_COMMAND);
+    data->req.no_body = !pop3_is_multiline(command);
+  }
 
   return result;
 }
index 3a893e8ead550ce12ce69728722dccf86b470975..54efa41909735122d0e2045330b7f7117a0b97a6 100644 (file)
@@ -131,7 +131,7 @@ test961 test962 test963 test964 test965 test966 test967 test968 test969 \
 test970 test971 test972 test973 test974 test975 test976 test977 test978 \
 test979 test980 test981 test982 test983 test984 test985 test986 test987 \
 test988 test989 test990 test991 test992 test993 test994 test995 test996 \
-\
+test997 \
 test1000 test1001 test1002 test1003 test1004 test1005 test1006 test1007 \
 test1008 test1009 test1010 test1011 test1012 test1013 test1014 test1015 \
 test1016 test1017 test1018 test1019 test1020 test1021 test1022 test1023 \
diff --git a/tests/data/test997 b/tests/data/test997
new file mode 100644 (file)
index 0000000..2883330
--- /dev/null
@@ -0,0 +1,47 @@
+<testcase>
+<info>
+<keywords>
+POP3
+Clear Text
+STAT
+CUSTOMREQUEST
+RFC2449
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<servercmd>\r
+CAPA TOP USER\r
+</servercmd>\r
+<data>\r
+</data>\r
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+pop3
+</server>
+<name>
+POP3 retrieve STAT (CUSTOMREQUEST)
+</name>
+<command>
+pop3://%HOSTIP:%POP3PORT -u user:secret -X 'STAT'
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+CAPA\r
+USER user\r
+PASS secret\r
+STAT\r
+QUIT\r
+</protocol>
+</verify>
+</testcase>