[PATCH v10 9/9] imap-send: add ability to list the available folders
From: Aditya Garg <hidden>
Date: 2025-06-01 07:12:25
Subsystem:
documentation, the rest · Maintainers:
Jonathan Corbet, Linus Torvalds
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 <redacted>
---
Documentation/git-imap-send.adoc | 6 +-
imap-send.c | 98 ++++++++++++++++++++++++++------
2 files changed, 87 insertions(+), 17 deletions(-)
diff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc
index 8f221240d0..379a371c08 100644
--- a/Documentation/git-imap-send.adoc
+++ b/Documentation/git-imap-send.adoc@@ -10,6 +10,7 @@ SYNOPSIS -------- [verse] 'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] +'git imap-send' --list DESCRIPTION
@@ -54,6 +55,8 @@ OPTIONS using libcurl. Ignored if Git was built with the NO_OPENSSL option set. +--list:: + Run the IMAP LIST command to output a list of all the folders present. CONFIGURATION -------------
@@ -123,7 +126,8 @@ Alternatively, use OAuth2.0 authentication as described below. [NOTE] 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] If your Gmail account is set to another language than English, the name of the "Drafts"
diff --git a/imap-send.c b/imap-send.c
index 86d46395de..4a7130c20b 100644
--- a/imap-send.c
+++ b/imap-send.c@@ -45,15 +45,21 @@ #endif static int verbosity; +static int list_folders = 0; static int use_curl = USE_CURL_DEFAULT; static char *opt_folder = NULL; -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[] = { OPT__VERBOSITY(&verbosity), 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_BOOL(0, "list", &list_folders, "list all folders on the IMAP server"), OPT_END() };
@@ -429,7 +435,7 @@ static int buffer_gets(struct imap_buffer *b, char **s) if (b->buf[b->offset + 1] == '\n') { b->buf[b->offset] = 0; /* terminate the string */ b->offset += 2; /* next line */ - if (0 < verbosity) + if ((0 < verbosity) || (list_folders && strstr(*s, "* LIST"))) puts(*s); return 0; }
@@ -1622,6 +1628,26 @@ static int append_msgs_to_imap(struct imap_server_conf *server, 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 static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred) {
@@ -1650,11 +1676,13 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred) if (!path.len || path.buf[path.len - 1] != '/') strbuf_addch(&path, '/'); - uri_encoded_folder = curl_easy_escape(curl, srvc->folder, 0); - if (!uri_encoded_folder) - die("Failed to encode server folder."); - strbuf_addstr(&path, uri_encoded_folder); - curl_free(uri_encoded_folder); + if (!list_folders) { + uri_encoded_folder = curl_easy_escape(curl, srvc->folder, 0); + if (!uri_encoded_folder) + die("Failed to encode server folder."); + strbuf_addstr(&path, uri_encoded_folder); + curl_free(uri_encoded_folder); + } curl_easy_setopt(curl, CURLOPT_URL, path.buf); strbuf_release(&path);
@@ -1685,10 +1713,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_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")) http_trace_curl_no_data(); setup_curl_trace(curl);
@@ -1707,6 +1731,10 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server, struct credential cred = CREDENTIAL_INIT; 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); fprintf(stderr, "Sending %d message%s to %s folder...\n",
@@ -1753,6 +1781,31 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server, 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 int cmd_main(int argc, const char **argv)
@@ -1793,11 +1846,6 @@ int cmd_main(int argc, const char **argv) if (!server.port) 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.tunnel) { fprintf(stderr, "No IMAP host specified.\n");
@@ -1807,6 +1855,24 @@ int cmd_main(int argc, const char **argv) 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 */ if (strbuf_read(&all_msgs, 0, 0) < 0) { error_errno(_("Could not read from stdin."));
--
2.49.0.638.g67a2d115ec