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>

Aditya Garg committed Jun 20, 2025 at 12:10 UTC 067a91b03f3216acf1248c0e540600f2af4038ae
2 files changed +87 -17
Documentation/git-imap-send.adoc
+5 -1
@@ -10,6 +10,7 @@ SYNOPSIS
10 --------
11 [verse]
12 'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]
13 +'git imap-send' --list
14
15
16 DESCRIPTION
@@ -54,6 +55,8 @@ OPTIONS
55 using libcurl. Ignored if Git was built with the NO_OPENSSL option
56 set.
57
58 +--list::
59 + Run the IMAP LIST command to output a list of all the folders present.
60
61 CONFIGURATION
62 -------------
@@ -123,7 +126,8 @@ it. Alternatively, use OAuth2.0 authentication as described below.
126
127 [NOTE]
128 You might need to instead use: `folder = "[Google Mail]/Drafts"` if you get an error
126 -that the "Folder doesn't exist".
129 +that the "Folder doesn't exist". You can also run `git imap-send --list` to get a
130 +list of available folders.
131
132 [NOTE]
133 If your Gmail account is set to another language than English, the name of the "Drafts"
imap-send.c
+82 -16
@@ -45,15 +45,21 @@
45 #endif
46
47 static int verbosity;
48 +static int list_folders;
49 static int use_curl = USE_CURL_DEFAULT;
50 static char *opt_folder;
51
51 -static const char * const imap_send_usage[] = { "git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>", NULL };
52 +static char const * const imap_send_usage[] = {
53 + N_("git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>"),
54 + "git imap-send --list",
55 + NULL
56 +};
57
58 static struct option imap_send_options[] = {
59 OPT__VERBOSITY(&verbosity),
60 OPT_BOOL(0, "curl", &use_curl, "use libcurl to communicate with the IMAP server"),
61 OPT_STRING('f', "folder", &opt_folder, "folder", "specify the IMAP folder"),
62 + OPT_BOOL(0, "list", &list_folders, "list all folders on the IMAP server"),
63 OPT_END()
64 };
65
@@ -429,7 +435,7 @@ static int buffer_gets(struct imap_buffer *b, char **s)
435 if (b->buf[b->offset + 1] == '\n') {
436 b->buf[b->offset] = 0; /* terminate the string */
437 b->offset += 2; /* next line */
432 - if (0 < verbosity)
438 + if ((0 < verbosity) || (list_folders && strstr(*s, "* LIST")))
439 puts(*s);
440 return 0;
441 }
@@ -1572,6 +1578,26 @@ static int append_msgs_to_imap(struct imap_server_conf *server,
1578 return 0;
1579 }
1580
1581 +static int list_imap_folders(struct imap_server_conf *server)
1582 +{
1583 + struct imap_store *ctx = imap_open_store(server, "INBOX");
1584 + if (!ctx) {
1585 + fprintf(stderr, "failed to connect to IMAP server\n");
1586 + return 1;
1587 + }
1588 +
1589 + fprintf(stderr, "Fetching the list of available folders...\n");
1590 + /* Issue the LIST command and print the results */
1591 + if (imap_exec(ctx, NULL, "LIST \"\" \"*\"") != RESP_OK) {
1592 + fprintf(stderr, "failed to list folders\n");
1593 + imap_close_store(ctx);
1594 + return 1;
1595 + }
1596 +
1597 + imap_close_store(ctx);
1598 + return 0;
1599 +}
1600 +
1601 #ifdef USE_CURL_FOR_IMAP_SEND
1602 static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
1603 {
@@ -1605,11 +1631,13 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
1631 if (!path.len || path.buf[path.len - 1] != '/')
1632 strbuf_addch(&path, '/');
1633
1608 - uri_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);
1609 - if (!uri_encoded_folder)
1610 - die("failed to encode server folder");
1611 - strbuf_addstr(&path, uri_encoded_folder);
1612 - curl_free(uri_encoded_folder);
1634 + if (!list_folders) {
1635 + uri_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);
1636 + if (!uri_encoded_folder)
1637 + die("failed to encode server folder");
1638 + strbuf_addstr(&path, uri_encoded_folder);
1639 + curl_free(uri_encoded_folder);
1640 + }
1641
1642 curl_easy_setopt(curl, CURLOPT_URL, path.buf);
1643 strbuf_release(&path);
@@ -1640,10 +1668,6 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
1668 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, srvc->ssl_verify);
1669 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, srvc->ssl_verify);
1670
1643 - curl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);
1644 -
1645 - curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
1646 -
1671 if (0 < verbosity || getenv("GIT_CURL_VERBOSE"))
1672 http_trace_curl_no_data();
1673 setup_curl_trace(curl);
@@ -1662,6 +1686,10 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,
1686 struct credential cred = CREDENTIAL_INIT;
1687
1688 curl = setup_curl(server, &cred);
1689 +
1690 + curl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);
1691 + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
1692 +
1693 curl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);
1694
1695 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,
1735
1736 return res != CURLE_OK;
1737 }
1738 +
1739 +static int curl_list_imap_folders(struct imap_server_conf *server)
1740 +{
1741 + CURL *curl;
1742 + CURLcode res = CURLE_OK;
1743 + struct credential cred = CREDENTIAL_INIT;
1744 +
1745 + fprintf(stderr, "Fetching the list of available folders...\n");
1746 + curl = setup_curl(server, &cred);
1747 + res = curl_easy_perform(curl);
1748 +
1749 + curl_easy_cleanup(curl);
1750 + curl_global_cleanup();
1751 +
1752 + if (cred.username) {
1753 + if (res == CURLE_OK)
1754 + credential_approve(the_repository, &cred);
1755 + else if (res == CURLE_LOGIN_DENIED)
1756 + credential_reject(the_repository, &cred);
1757 + }
1758 +
1759 + credential_clear(&cred);
1760 +
1761 + return res != CURLE_OK;
1762 +}
1763 #endif
1764
1765 int cmd_main(int argc, const char **argv)
@@ -1747,11 +1800,6 @@ int cmd_main(int argc, const char **argv)
1800 if (!server.port)
1801 server.port = server.use_ssl ? 993 : 143;
1802
1750 - if (!server.folder) {
1751 - fprintf(stderr, "no imap store specified\n");
1752 - ret = 1;
1753 - goto out;
1754 - }
1803 if (!server.host) {
1804 if (!server.tunnel) {
1805 fprintf(stderr, "no imap host specified\n");
@@ -1761,6 +1809,24 @@ int cmd_main(int argc, const char **argv)
1809 server.host = xstrdup("tunnel");
1810 }
1811
1812 + if (list_folders) {
1813 + if (server.tunnel)
1814 + ret = list_imap_folders(&server);
1815 +#ifdef USE_CURL_FOR_IMAP_SEND
1816 + else if (use_curl)
1817 + ret = curl_list_imap_folders(&server);
1818 +#endif
1819 + else
1820 + ret = list_imap_folders(&server);
1821 + goto out;
1822 + }
1823 +
1824 + if (!server.folder) {
1825 + fprintf(stderr, "no imap store specified\n");
1826 + ret = 1;
1827 + goto out;
1828 + }
1829 +
1830 /* read the messages */
1831 if (strbuf_read(&all_msgs, 0, 0) < 0) {
1832 error_errno(_("could not read from stdin"));