IMAP: add CURLOPT_UPLOAD_FLAGS and --upload-flags

Set properties on the uploaded resource.

Test 3209 and 3210 verify.

Closes #15970
This commit is contained in:
tiymat 2025-01-11 17:20:12 -03:30 committed by Daniel Stenberg
parent 0cd2670afb
commit 6758aa722d
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
25 changed files with 395 additions and 16 deletions

View File

@ -92,7 +92,6 @@
9. IMAP
9.1 Enhanced capability support
9.2 upload unread
10. LDAP
10.1 SASL based authentication mechanisms
@ -721,12 +720,6 @@
Add the ability, for an application that uses libcurl, to obtain the list of
capabilities returned from the CAPABILITY command.
9.2 upload unread
Uploads over IMAP currently always set the email as "read" (or "seen"). It
would be good to offer a way for users to select for uploads to remain
unread.
10. LDAP
10.1 SASL based authentication mechanisms

View File

@ -301,6 +301,7 @@ DPAGES = \
trace.md \
unix-socket.md \
upload-file.md \
upload-flags.md \
url.md \
url-query.md \
use-ascii.md \

View File

@ -31,7 +31,7 @@ filename or curl thinks that your last directory name is the remote filename
to use.
When putting the local filename at the end of the URL, curl ignores what is on
the left side of any slash (/) or backslash (\) used in the filename and only
the left side of any slash (/) or backslash (\\) used in the filename and only
appends what is on the right side of the rightmost such character.
Use the filename `-` (a single dash) to use stdin instead of a given file.

View File

@ -0,0 +1,24 @@
---
c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
SPDX-License-Identifier: curl
Long: upload-flags
Arg: <flags>
Help: IMAP upload behavior
Category: curl output
Added: 8.13.0
Multi: single
See-also:
- upload-file
Example:
- --upload-flags Flagged,!Seen --upload-file local/dir/file $URL
---
# `--upload-flags`
Specify additional behavior to apply to uploaded files. Flags are
specified as either a single flag value or a comma-separated list
of flag values. These values are case-sensitive and may be negated
by prepending them with a '-' character. Currently the following
flag values are accepted: answered, deleted, draft, flagged, and
seen. The currently-accepted flag values are used to set flags on
IMAP uploads.

View File

@ -81,7 +81,7 @@ int Curl_str_quotedword(char **linep, struct Curl_str *out, const size_t max);
~~~
Get a "quoted" word. This means everything that is provided within a leading
and an ending double character. No escaping possible.
and an ending double quote character. No escaping possible.
`max` is the longest accepted word, or it returns error.

View File

@ -1273,6 +1273,10 @@ Upload data. See CURLOPT_UPLOAD(3)
Set upload buffer size. See CURLOPT_UPLOAD_BUFFERSIZE(3)
## CURLOPT_UPLOAD_FLAGS
Set upload flags. See CURLOPT_UPLOAD_FLAGS(3)
## CURLOPT_URL
URL to work on. See CURLOPT_URL(3)

View File

@ -0,0 +1,100 @@
---
c: Copyright (C) Daniel Stenberg, <daniel.se>, et al.
SPDX-License-Identifier: curl
Title: CURLOPT_UPLOAD_FLAGS
Section: 3
Source: libcurl
See-also:
- CURLOPT_UPLOAD (3)
Protocol:
- IMAP
- IMAPS
Added-in: 8.13.0
---
# NAME
CURLOPT_UPLOAD_FLAGS - upload flags for IMAP
# SYNOPSIS
~~~c
#include <curl/curl.h>
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_UPLOAD_FLAGS, long bitmask);
~~~
# DESCRIPTION
Pass a long as parameter, which is set to a bitmask, to tell libcurl which
flags to send the server relating to uploaded files. The current supported
flags are **CURLULFLAG_ANSWERED**, which sets the **Answered** flag for IMAP
uploads, **CURLULFLAG_DELETED**, which sets the **Deleted** flag for IMAP
uploads, **CURLULFLAG_DRAFT**, which sets the **Draft** flag for IMAP uploads,
**CURLULFLAG_FLAGGED**, which sets the **Flagged** flag for IMAP uploads, and
**CURLULFLAG_SEEN**, which sets the **Seen** flag for IMAP uploads.
# DEFAULT
A bitmask with only the **CURLULFLAG_SEEN** flag set.
# %PROTOCOLS%
# EXAMPLE
~~~c
static size_t read_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
{
FILE *src = userdata;
/* copy as much data as possible into the 'ptr' buffer, but no more than
'size' * 'nmemb' bytes */
size_t retcode = fread(ptr, size, nmemb, src);
return retcode;
}
int main(void)
{
CURL *curl = curl_easy_init();
if(curl) {
FILE *src = fopen("local-file", "r");
curl_off_t fsize; /* set this to the size of the input file */
/* we want to use our own read function */
curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_cb);
/* enable uploading */
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
/* specify target */
curl_easy_setopt(curl, CURLOPT_URL, "imap://example.com:993/mailbox");
/* provide username */
curl_easy_setopt(curl, CURLOPT_USERNAME, "user@example.com");
/* provide password */
curl_easy_setopt(curl, CURLOPT_PASSWORD, "password");
/* specify that uploaded mail should be considered flagged */
curl_easy_setopt(curl, CURLOPT_UPLOAD_FLAGS, CURLULFLAG_FLAGGED);
/* now specify which pointer to pass to our callback */
curl_easy_setopt(curl, CURLOPT_READDATA, src);
/* Set the size of the file to upload */
curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)fsize);
/* perform the upload */
curl_easy_perform(curl);
}
}
~~~
# %AVAILABILITY%
# RETURN VALUE
curl_easy_setopt(3) returns a CURLcode indicating success or error.
CURLE_OK (0) means everything was OK, non-zero means an error occurred, see
libcurl-errors(3).

View File

@ -411,6 +411,7 @@ man_MANS = \
CURLOPT_UPKEEP_INTERVAL_MS.3 \
CURLOPT_UPLOAD.3 \
CURLOPT_UPLOAD_BUFFERSIZE.3 \
CURLOPT_UPLOAD_FLAGS.3 \
CURLOPT_URL.3 \
CURLOPT_USE_SSL.3 \
CURLOPT_USERAGENT.3 \

View File

@ -895,6 +895,7 @@ CURLOPT_UNRESTRICTED_AUTH 7.10.4
CURLOPT_UPKEEP_INTERVAL_MS 7.62.0
CURLOPT_UPLOAD 7.1
CURLOPT_UPLOAD_BUFFERSIZE 7.62.0
CURLOPT_UPLOAD_FLAGS 8.13.0
CURLOPT_URL 7.1
CURLOPT_USE_SSL 7.17.0
CURLOPT_USERAGENT 7.1
@ -1128,6 +1129,11 @@ CURLUPART_SCHEME 7.62.0
CURLUPART_URL 7.62.0
CURLUPART_USER 7.62.0
CURLUPART_ZONEID 7.65.0
CURLULFLAG_ANSWERED 8.13.0
CURLULFLAG_DELETED 8.13.0
CURLULFLAG_DRAFT 8.13.0
CURLULFLAG_FLAGGED 8.13.0
CURLULFLAG_SEEN 8.13.0
CURLUSESSL_ALL 7.17.0
CURLUSESSL_CONTROL 7.17.0
CURLUSESSL_NONE 7.17.0

View File

@ -266,6 +266,7 @@
--trace-time 7.14.0
--unix-socket 7.40.0
--upload-file (-T) 4.0
--upload-flags 8.13.0
--url 7.5
--url-query 7.87.0
--use-ascii (-B) 5.0

View File

@ -1024,6 +1024,12 @@ typedef enum {
#define CURLALTSVC_H2 (1<<4)
#define CURLALTSVC_H3 (1<<5)
/* bitmask values for CURLOPT_UPLOAD_FLAGS */
#define CURLULFLAG_ANSWERED (1L<<0)
#define CURLULFLAG_DELETED (1L<<1)
#define CURLULFLAG_DRAFT (1L<<2)
#define CURLULFLAG_FLAGGED (1L<<3)
#define CURLULFLAG_SEEN (1L<<4)
struct curl_hstsentry {
char *name;
@ -2237,6 +2243,8 @@ typedef enum {
/* maximum number of keepalive probes (Linux, *BSD, macOS, etc.) */
CURLOPT(CURLOPT_TCP_KEEPCNT, CURLOPTTYPE_LONG, 326),
CURLOPT(CURLOPT_UPLOAD_FLAGS, CURLOPTTYPE_LONG, 327),
CURLOPT_LASTENTRY /* the last unused */
} CURLoption;

View File

@ -353,6 +353,7 @@ struct curl_easyoption Curl_easyopts[] = {
{"UPKEEP_INTERVAL_MS", CURLOPT_UPKEEP_INTERVAL_MS, CURLOT_LONG, 0},
{"UPLOAD", CURLOPT_UPLOAD, CURLOT_LONG, 0},
{"UPLOAD_BUFFERSIZE", CURLOPT_UPLOAD_BUFFERSIZE, CURLOT_LONG, 0},
{"UPLOAD_FLAGS", CURLOPT_UPLOAD_FLAGS, CURLOT_LONG, 0},
{"URL", CURLOPT_URL, CURLOT_STRING, 0},
{"USERAGENT", CURLOPT_USERAGENT, CURLOT_STRING, 0},
{"USERNAME", CURLOPT_USERNAME, CURLOT_STRING, 0},
@ -377,6 +378,6 @@ struct curl_easyoption Curl_easyopts[] = {
*/
int Curl_easyopts_check(void)
{
return (CURLOPT_LASTENTRY % 10000) != (326 + 1);
return (CURLOPT_LASTENTRY % 10000) != (327 + 1);
}
#endif

View File

@ -36,6 +36,7 @@
***************************************************************************/
#include "curl_setup.h"
#include "dynbuf.h"
#ifndef CURL_DISABLE_IMAP
@ -192,6 +193,10 @@ static const struct SASLproto saslimap = {
SASL_FLAG_BASE64 /* Configuration flags */
};
struct ulbits {
int bit;
const char *flag;
};
/***********************************************************************
*
@ -760,6 +765,7 @@ static CURLcode imap_perform_append(struct Curl_easy *data)
CURLcode result = CURLE_OK;
struct IMAP *imap = data->req.p.imap;
char *mailbox;
struct dynbuf flags;
/* Check we have a mailbox */
if(!imap->mailbox) {
@ -808,10 +814,43 @@ static CURLcode imap_perform_append(struct Curl_easy *data)
if(!mailbox)
return CURLE_OUT_OF_MEMORY;
/* Send the APPEND command */
result = imap_sendf(data, "APPEND %s (\\Seen) {%" FMT_OFF_T "}",
mailbox, data->state.infilesize);
/* Generate flags string and send the APPEND command */
Curl_dyn_init(&flags, 100);
if(data->set.upload_flags) {
int i;
struct ulbits ulflag[] = {
{CURLULFLAG_ANSWERED, "Answered"},
{CURLULFLAG_DELETED, "Deleted"},
{CURLULFLAG_DRAFT, "Draft"},
{CURLULFLAG_FLAGGED, "Flagged"},
{CURLULFLAG_SEEN, "Seen"},
{0, NULL}
};
result = CURLE_OUT_OF_MEMORY;
if(Curl_dyn_add(&flags, " (")) {
goto cleanup;
}
for(i = 0; ulflag[i].bit; i++) {
if(data->set.upload_flags & ulflag[i].bit) {
if((Curl_dyn_len(&flags) > 2 && Curl_dyn_add(&flags, " ")) ||
Curl_dyn_add(&flags, "\\") || Curl_dyn_add(&flags, ulflag[i].flag))
goto cleanup;
}
}
if(Curl_dyn_add(&flags, ")"))
goto cleanup;
}
else if(Curl_dyn_add(&flags, ""))
goto cleanup;
result = imap_sendf(data, "APPEND %s%s {%" FMT_OFF_T "}",
mailbox, Curl_dyn_ptr(&flags), data->state.infilesize);
cleanup:
Curl_dyn_free(&flags);
free(mailbox);
if(!result)

View File

@ -1403,7 +1403,9 @@ static CURLcode setopt_long(struct Curl_easy *data, CURLoption option,
*/
Curl_safefree(data->set.str[STRING_SSL_ENGINE]);
return Curl_ssl_set_engine_default(data);
case CURLOPT_UPLOAD_FLAGS:
data->set.upload_flags = (unsigned char)arg;
break;
default:
/* unknown option */
return CURLE_UNKNOWN_OPTION;

View File

@ -1723,6 +1723,7 @@ struct UserDefined {
to be used in the library's request(s) */
unsigned char ipver; /* the CURL_IPRESOLVE_* defines in the public header
file 0 - whatever, 1 - v2, 2 - v6 */
unsigned char upload_flags; /* flags set by CURLOPT_UPLOAD_FLAGS */
#ifdef HAVE_GSSAPI
/* GSS-API credential delegation, see the documentation of
CURLOPT_GSSAPI_DELEGATION */

View File

@ -45,6 +45,7 @@ void config_init(struct OperationConfig *config)
config->http09_allowed = FALSE;
config->ftp_skip_ip = TRUE;
config->file_clobber_mode = CLOBBER_DEFAULT;
config->upload_flags = CURLULFLAG_SEEN;
curlx_dyn_init(&config->postdata, MAX_FILE2MEMORY);
}

View File

@ -216,6 +216,7 @@ struct OperationConfig {
CLOBBER_NEVER, /* If the file exists, always fail */
CLOBBER_ALWAYS /* If the file exists, always overwrite it */
} file_clobber_mode;
unsigned char upload_flags; /* Bitmask for --upload-flags */
unsigned short porttouse;
BIT(remote_time);
BIT(cookiesession); /* new session? */

View File

@ -339,6 +339,7 @@ static const struct LongShort aliases[]= {
{"trace-time", ARG_BOOL, ' ', C_TRACE_TIME},
{"unix-socket", ARG_FILE, ' ', C_UNIX_SOCKET},
{"upload-file", ARG_FILE, 'T', C_UPLOAD_FILE},
{"upload-flags", ARG_STRG, ' ', C_UPLOAD_FLAGS},
{"url", ARG_STRG, ' ', C_URL},
{"url-query", ARG_STRG, ' ', C_URL_QUERY},
{"use-ascii", ARG_BOOL, 'B', C_USE_ASCII},
@ -1622,6 +1623,65 @@ static ParameterError parse_time_cond(struct GlobalConfig *global,
return err;
}
struct flagmap {
const char *name;
size_t len;
unsigned char flag;
};
static const struct flagmap flag_table[] = {
{"answered", 8, CURLULFLAG_ANSWERED},
{"deleted", 7, CURLULFLAG_DELETED},
{"draft", 5, CURLULFLAG_DRAFT},
{"flagged", 7, CURLULFLAG_FLAGGED},
{"seen", 4, CURLULFLAG_SEEN},
{NULL, 0, 0}
};
static ParameterError parse_upload_flags(struct OperationConfig *config,
char *nextarg)
{
char *flag;
ParameterError err = PARAM_OK;
char *tmp = strdup(nextarg);
if(!tmp)
return PARAM_NO_MEM;
flag = tmp;
while(flag) {
bool negate;
const struct flagmap *map;
char *next = strchr(flag, ','); /* Find next comma or end */
if(next)
*next++ = '\0';
negate = (*flag == '-');
if(negate)
flag++;
for(map = flag_table; map->name; map++) {
if(!strncmp(flag, map->name, map->len) && flag[map->len] == '\0') {
if(negate)
config->upload_flags &= (unsigned char)~map->flag;
else
config->upload_flags |= map->flag;
break;
}
}
if(!map->name) {
err = PARAM_OPTION_UNKNOWN;
break;
}
flag = next;
}
free(tmp);
return err;
}
ParameterError getparameter(const char *flag, /* f or -long-flag */
char *nextarg, /* NULL if unset */
argv_item_t cleararg1,
@ -2909,6 +2969,9 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
case C_MPTCP: /* --mptcp */
config->mptcp = TRUE;
break;
case C_UPLOAD_FLAGS: /* --upload-flags */
err = parse_upload_flags(config, nextarg);
break;
default: /* unknown flag */
err = PARAM_OPTION_UNKNOWN;
break;

View File

@ -293,6 +293,7 @@ typedef enum {
C_IP_TOS,
C_UNIX_SOCKET,
C_UPLOAD_FILE,
C_UPLOAD_FLAGS,
C_URL,
C_URL_QUERY,
C_USE_ASCII,

View File

@ -808,6 +808,9 @@ const struct helptxt helptext[] = {
{"-T, --upload-file <file>",
"Transfer local FILE to destination",
CURLHELP_IMPORTANT | CURLHELP_UPLOAD},
{" --upload-flags <flags>",
"IMAP upload behavior",
CURLHELP_CURL | CURLHELP_OUTPUT},
{" --url <url/file>",
"URL(s) to work with",
CURLHELP_CURL},

View File

@ -1749,6 +1749,9 @@ static CURLcode config2setopts(struct GlobalConfig *global,
}
#endif
}
/* new in 8.13.0 */
if(config->upload_flags)
my_setopt(curl, CURLOPT_UPLOAD_FLAGS, (long)config->upload_flags);
return result;
}

View File

@ -164,6 +164,7 @@ static const struct NameValue setopt_nv_CURLNONZERODEFAULTS[] = {
NV1(CURLOPT_PROXY_SSL_VERIFYPEER, 1),
NV1(CURLOPT_PROXY_SSL_VERIFYHOST, 1),
NV1(CURLOPT_SOCKS5_AUTH, 1),
NV1(CURLOPT_UPLOAD_FLAGS, CURLULFLAG_SEEN),
NVEND
};

View File

@ -273,6 +273,7 @@ test3032 \
\
test3100 test3101 test3102 test3103 test3104 test3105 \
test3200 \
test3201 test3202 test3203 test3204 test3205 test3207 test3208
test3201 test3202 test3203 test3204 test3205 test3207 test3208 test3209 \
test3210 \
\
EXTRA_DIST = $(TESTCASES) DISABLED

62
tests/data/test3209 Normal file
View File

@ -0,0 +1,62 @@
<testcase>
<info>
<keywords>
IMAP
Clear Text
APPEND
UPLOAD
</keywords>
</info>
#
# Server-side
<reply>
</reply>
#
# Client-side
<client>
<server>
imap
</server>
<name>
Upload message via IMAP with upload flags
</name>
<command>
imap://%HOSTIP:%IMAPPORT/%TESTNUMBER -T %LOGDIR/upload%TESTNUMBER -u user:secret --upload-flags answered,deleted,draft,flagged,seen
</command>
<file name="%LOGDIR/upload%TESTNUMBER">
Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)
From: Fred Foobar <foobar@example.COM>
Subject: afternoon meeting
To: joe@example.com
Message-Id: <B27397-0100000@example.COM>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; CHARSET=US-ASCII
Hello Joe, do you think we can meet at 3:30 tomorrow?
</file>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol crlf="yes">
A001 CAPABILITY
A002 LOGIN user secret
A003 APPEND %TESTNUMBER (\Answered \Deleted \Draft \Flagged \Seen) {286}
A004 LOGOUT
</protocol>
<upload>
Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)
From: Fred Foobar <foobar@example.COM>
Subject: afternoon meeting
To: joe@example.com
Message-Id: <B27397-0100000@example.COM>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; CHARSET=US-ASCII
Hello Joe, do you think we can meet at 3:30 tomorrow?
</upload>
</verify>
</testcase>

62
tests/data/test3210 Normal file
View File

@ -0,0 +1,62 @@
<testcase>
<info>
<keywords>
IMAP
Clear Text
APPEND
UPLOAD
</keywords>
</info>
#
# Server-side
<reply>
</reply>
#
# Client-side
<client>
<server>
imap
</server>
<name>
Upload message unread via IMAP
</name>
<command>
imap://%HOSTIP:%IMAPPORT/%TESTNUMBER -T %LOGDIR/upload%TESTNUMBER -u user:secret --upload-flags -seen
</command>
<file name="%LOGDIR/upload%TESTNUMBER">
Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)
From: Fred Foobar <foobar@example.COM>
Subject: afternoon meeting
To: joe@example.com
Message-Id: <B27397-0100000@example.COM>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; CHARSET=US-ASCII
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</file>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol crlf="yes">
A001 CAPABILITY
A002 LOGIN user secret
A003 APPEND %TESTNUMBER {356}
A004 LOGOUT
</protocol>
<upload>
Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)
From: Fred Foobar <foobar@example.COM>
Subject: afternoon meeting
To: joe@example.com
Message-Id: <B27397-0100000@example.COM>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; CHARSET=US-ASCII
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</upload>
</verify>
</testcase>