imap-send: add ability to list the available folders

Various IMAP servers have different ways to name common folders.
For example, the folder where all deleted messages are stored is often
named "[Gmail]/Trash" on Gmail servers, and "Deleted" on Outlook.
Similarly, the Drafts folder is simply named "Drafts" on Outlook, but
on Gmail it is named "[Gmail]/Drafts".

This commit adds a `--list` command to the `imap-send` tool that lists
the available folders on the IMAP server, allowing users to see
which folders are available and how they are named. A sample output
looks like this when run against a Gmail server:

    Fetching the list of available folders...
    * LIST (\HasNoChildren) "/" "INBOX"
    * LIST (\HasChildren \Noselect) "/" "[Gmail]"
    * LIST (\All \HasNoChildren) "/" "[Gmail]/All Mail"
    * LIST (\Drafts \HasNoChildren) "/" "[Gmail]/Drafts"
    * LIST (\HasNoChildren \Important) "/" "[Gmail]/Important"
    * LIST (\HasNoChildren \Sent) "/" "[Gmail]/Sent Mail"
    * LIST (\HasNoChildren \Junk) "/" "[Gmail]/Spam"
    * LIST (\Flagged \HasNoChildren) "/" "[Gmail]/Starred"
    * LIST (\HasNoChildren \Trash) "/" "[Gmail]/Trash"

For OpenSSL, this is achived by running the 'IMAP LIST' command and
parsing the response. This command is specified in RFC6154:
https://datatracker.ietf.org/doc/html/rfc6154#section-5.1

For libcurl, the example code published in the libcurl documentation
is used to implement this functionality:
https://curl.se/libcurl/c/imap-list.html

Signed-off-by: Aditya Garg <gargaditya08@live.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Aditya Garg
2025-06-20 12:10:30 +05:30
committed by Junio C Hamano
parent 3168514e6b
commit 067a91b03f
2 changed files with 87 additions and 17 deletions

View File

@@ -10,6 +10,7 @@ SYNOPSIS
-------- --------
[verse] [verse]
'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] 'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]
'git imap-send' --list
DESCRIPTION DESCRIPTION
@@ -54,6 +55,8 @@ OPTIONS
using libcurl. Ignored if Git was built with the NO_OPENSSL option using libcurl. Ignored if Git was built with the NO_OPENSSL option
set. set.
--list::
Run the IMAP LIST command to output a list of all the folders present.
CONFIGURATION CONFIGURATION
------------- -------------
@@ -123,7 +126,8 @@ it. Alternatively, use OAuth2.0 authentication as described below.
[NOTE] [NOTE]
You might need to instead use: `folder = "[Google Mail]/Drafts"` if you get an error You might need to instead use: `folder = "[Google Mail]/Drafts"` if you get an error
that the "Folder doesn't exist". that the "Folder doesn't exist". You can also run `git imap-send --list` to get a
list of available folders.
[NOTE] [NOTE]
If your Gmail account is set to another language than English, the name of the "Drafts" If your Gmail account is set to another language than English, the name of the "Drafts"

View File

@@ -45,15 +45,21 @@
#endif #endif
static int verbosity; static int verbosity;
static int list_folders;
static int use_curl = USE_CURL_DEFAULT; static int use_curl = USE_CURL_DEFAULT;
static char *opt_folder; static char *opt_folder;
static const char * const imap_send_usage[] = { "git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>", NULL }; static char const * const imap_send_usage[] = {
N_("git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>"),
"git imap-send --list",
NULL
};
static struct option imap_send_options[] = { static struct option imap_send_options[] = {
OPT__VERBOSITY(&verbosity), OPT__VERBOSITY(&verbosity),
OPT_BOOL(0, "curl", &use_curl, "use libcurl to communicate with the IMAP server"), OPT_BOOL(0, "curl", &use_curl, "use libcurl to communicate with the IMAP server"),
OPT_STRING('f', "folder", &opt_folder, "folder", "specify the IMAP folder"), OPT_STRING('f', "folder", &opt_folder, "folder", "specify the IMAP folder"),
OPT_BOOL(0, "list", &list_folders, "list all folders on the IMAP server"),
OPT_END() OPT_END()
}; };
@@ -429,7 +435,7 @@ static int buffer_gets(struct imap_buffer *b, char **s)
if (b->buf[b->offset + 1] == '\n') { if (b->buf[b->offset + 1] == '\n') {
b->buf[b->offset] = 0; /* terminate the string */ b->buf[b->offset] = 0; /* terminate the string */
b->offset += 2; /* next line */ b->offset += 2; /* next line */
if (0 < verbosity) if ((0 < verbosity) || (list_folders && strstr(*s, "* LIST")))
puts(*s); puts(*s);
return 0; return 0;
} }
@@ -1572,6 +1578,26 @@ static int append_msgs_to_imap(struct imap_server_conf *server,
return 0; return 0;
} }
static int list_imap_folders(struct imap_server_conf *server)
{
struct imap_store *ctx = imap_open_store(server, "INBOX");
if (!ctx) {
fprintf(stderr, "failed to connect to IMAP server\n");
return 1;
}
fprintf(stderr, "Fetching the list of available folders...\n");
/* Issue the LIST command and print the results */
if (imap_exec(ctx, NULL, "LIST \"\" \"*\"") != RESP_OK) {
fprintf(stderr, "failed to list folders\n");
imap_close_store(ctx);
return 1;
}
imap_close_store(ctx);
return 0;
}
#ifdef USE_CURL_FOR_IMAP_SEND #ifdef USE_CURL_FOR_IMAP_SEND
static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred) static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
{ {
@@ -1605,11 +1631,13 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
if (!path.len || path.buf[path.len - 1] != '/') if (!path.len || path.buf[path.len - 1] != '/')
strbuf_addch(&path, '/'); strbuf_addch(&path, '/');
if (!list_folders) {
uri_encoded_folder = curl_easy_escape(curl, srvc->folder, 0); uri_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);
if (!uri_encoded_folder) if (!uri_encoded_folder)
die("failed to encode server folder"); die("failed to encode server folder");
strbuf_addstr(&path, uri_encoded_folder); strbuf_addstr(&path, uri_encoded_folder);
curl_free(uri_encoded_folder); curl_free(uri_encoded_folder);
}
curl_easy_setopt(curl, CURLOPT_URL, path.buf); curl_easy_setopt(curl, CURLOPT_URL, path.buf);
strbuf_release(&path); strbuf_release(&path);
@@ -1640,10 +1668,6 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, srvc->ssl_verify); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, srvc->ssl_verify);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, srvc->ssl_verify); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, srvc->ssl_verify);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
if (0 < verbosity || getenv("GIT_CURL_VERBOSE")) if (0 < verbosity || getenv("GIT_CURL_VERBOSE"))
http_trace_curl_no_data(); http_trace_curl_no_data();
setup_curl_trace(curl); setup_curl_trace(curl);
@@ -1662,6 +1686,10 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,
struct credential cred = CREDENTIAL_INIT; struct credential cred = CREDENTIAL_INIT;
curl = setup_curl(server, &cred); curl = setup_curl(server, &cred);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf); curl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);
fprintf(stderr, "sending %d message%s\n", total, (total != 1) ? "s" : ""); fprintf(stderr, "sending %d message%s\n", total, (total != 1) ? "s" : "");
@@ -1707,6 +1735,31 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,
return res != CURLE_OK; return res != CURLE_OK;
} }
static int curl_list_imap_folders(struct imap_server_conf *server)
{
CURL *curl;
CURLcode res = CURLE_OK;
struct credential cred = CREDENTIAL_INIT;
fprintf(stderr, "Fetching the list of available folders...\n");
curl = setup_curl(server, &cred);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
curl_global_cleanup();
if (cred.username) {
if (res == CURLE_OK)
credential_approve(the_repository, &cred);
else if (res == CURLE_LOGIN_DENIED)
credential_reject(the_repository, &cred);
}
credential_clear(&cred);
return res != CURLE_OK;
}
#endif #endif
int cmd_main(int argc, const char **argv) int cmd_main(int argc, const char **argv)
@@ -1747,11 +1800,6 @@ int cmd_main(int argc, const char **argv)
if (!server.port) if (!server.port)
server.port = server.use_ssl ? 993 : 143; server.port = server.use_ssl ? 993 : 143;
if (!server.folder) {
fprintf(stderr, "no imap store specified\n");
ret = 1;
goto out;
}
if (!server.host) { if (!server.host) {
if (!server.tunnel) { if (!server.tunnel) {
fprintf(stderr, "no imap host specified\n"); fprintf(stderr, "no imap host specified\n");
@@ -1761,6 +1809,24 @@ int cmd_main(int argc, const char **argv)
server.host = xstrdup("tunnel"); server.host = xstrdup("tunnel");
} }
if (list_folders) {
if (server.tunnel)
ret = list_imap_folders(&server);
#ifdef USE_CURL_FOR_IMAP_SEND
else if (use_curl)
ret = curl_list_imap_folders(&server);
#endif
else
ret = list_imap_folders(&server);
goto out;
}
if (!server.folder) {
fprintf(stderr, "no imap store specified\n");
ret = 1;
goto out;
}
/* read the messages */ /* read the messages */
if (strbuf_read(&all_msgs, 0, 0) < 0) { if (strbuf_read(&all_msgs, 0, 0) < 0) {
error_errno(_("could not read from stdin")); error_errno(_("could not read from stdin"));