]> git.ipfire.org Git - thirdparty/git.git/commitdiff
imap-send: add ability to list the available folders
authorAditya Garg <gargaditya08@live.com>
Fri, 20 Jun 2025 06:40:30 +0000 (12:10 +0530)
committerJunio C Hamano <gitster@pobox.com>
Fri, 20 Jun 2025 15:11:17 +0000 (08:11 -0700)
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>
Documentation/git-imap-send.adoc
imap-send.c

index 4a0487b66e8cd6a087ecac665027623623edf281..17147f93c3da7f6817803b67419209b798e3e5a5 100644 (file)
@@ -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 @@ it. 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"
index ccf7195fd788fe33f8f7ecf4f43d792af118e7da..c88d84281968e99c9760130860338f1c73774df9 100644 (file)
 #endif
 
 static int verbosity;
+static int list_folders;
 static int use_curl = USE_CURL_DEFAULT;
 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[] = {
        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;
                        }
@@ -1572,6 +1578,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)
 {
@@ -1605,11 +1631,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);
@@ -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_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);
@@ -1662,6 +1686,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\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;
 }
+
+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)
@@ -1747,11 +1800,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");
@@ -1761,6 +1809,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"));