git-imap-send: use libcurl for implementation

Use libcurl's high-level API functions to implement git-imap-send instead of the previous low-level OpenSSL-based functions. Since version 7.30.0, libcurl's API has been able to communicate with IMAP servers. Using those high-level functions instead of the current ones would reduce imap-send.c by some 1200 lines of code. For now, the old ones are wrapped in #ifdefs, and the new functions are enabled by make if curl's version is >= 7.34.0, from which version on curl's CURLOPT_LOGIN_OPTIONS (enabling IMAP authentication) parameter has been available. The low-level functions will still be used for tunneling into the server for now. As I don't have access to that many IMAP servers, I haven't been able to test the new code with a wide variety of parameter combinations. I did test both secure and insecure (imaps:// and imap://) connections and values of "PLAIN" and "LOGIN" for the authMethod. In order to suppress a sparse warning about "using sizeof on a function", we use the same solution used in commit 9371322a6 ("sparse: suppress some "using sizeof on a function" warnings", 06-10-2013) which solved exactly this problem for the other commands using libcurl. Helped-by: Ramsay Jones <ramsay@ramsay1.demon.co.uk> Signed-off-by: Bernhard Reiter <ockham@raz.or.at> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Bernhard Reiter committed Nov 9, 2014 at 15:55 UTC 1e16b255b95b45374db0b0cf69ef0ecce768ac27
4 files changed +191 -37
Documentation/git-imap-send.txt
+13 -2
@@ -9,7 +9,7 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder
9 SYNOPSIS
10 --------
11 [verse]
12 -'git imap-send' [-v] [-q]
12 +'git imap-send' [-v] [-q] [--[no-]curl]
13
14
15 DESCRIPTION
@@ -37,6 +37,15 @@ OPTIONS
37 --quiet::
38 Be quiet.
39
40 +--curl::
41 + Use libcurl to communicate with the IMAP server, unless tunneling
42 + into it. Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND
43 + option set.
44 +
45 +--no-curl::
46 + Talk to the IMAP server using git's own IMAP routines instead of
47 + using libcurl.
48 +
49
50 CONFIGURATION
51 -------------
@@ -87,7 +96,9 @@ imap.preformattedHTML::
96
97 imap.authMethod::
98 Specify authenticate method for authentication with IMAP server.
90 - Current supported method is 'CRAM-MD5' only. If this is not set
99 + If Git was built with the NO_CURL option, or if your curl version is older
100 + than 7.34.0, or if you're running git-imap-send with the `--no-curl`
101 + option, the only supported method is 'CRAM-MD5'. If this is not set
102 then 'git imap-send' uses the basic IMAP plaintext LOGIN command.
103
104 Examples
INSTALL
+9 -6
@@ -108,18 +108,21 @@ Issues of note:
108 so you might need to install additional packages other than Perl
109 itself, e.g. Time::HiRes.
110
111 - - "openssl" library is used by git-imap-send to use IMAP over SSL.
112 - If you don't need it, use NO_OPENSSL.
111 + - git-imap-send needs the OpenSSL library to talk IMAP over SSL if
112 + you are using libcurl older than 7.34.0. Otherwise you can use
113 + NO_OPENSSL without losing git-imap-send.
114
115 By default, git uses OpenSSL for SHA1 but it will use its own
116 library (inspired by Mozilla's) with either NO_OPENSSL or
117 BLK_SHA1. Also included is a version optimized for PowerPC
118 (PPC_SHA1).
119
119 - - "libcurl" library is used by git-http-fetch and git-fetch. You
120 - might also want the "curl" executable for debugging purposes.
121 - If you do not use http:// or https:// repositories, you do not
122 - have to have them (use NO_CURL).
120 + - "libcurl" library is used by git-http-fetch, git-fetch, and, if
121 + the curl version >= 7.34.0, for git-imap-send. You might also
122 + want the "curl" executable for debugging purposes. If you do not
123 + use http:// or https:// repositories, and do not want to put
124 + patches into an IMAP mailbox, you do not have to have them
125 + (use NO_CURL).
126
127 - "expat" library; git-http-push uses it for remote lock
128 management over DAV. Similar to "curl" above, this is optional
Makefile
+15 -3
@@ -995,6 +995,9 @@ ifdef HAVE_ALLOCA_H
995 BASIC_CFLAGS += -DHAVE_ALLOCA_H
996 endif
997
998 +IMAP_SEND_BUILDDEPS =
999 +IMAP_SEND_LDFLAGS = $(OPENSSL_LINK) $(OPENSSL_LIBSSL) $(LIB_4_CRYPTO)
1000 +
1001 ifdef NO_CURL
1002 BASIC_CFLAGS += -DNO_CURL
1003 REMOTE_CURL_PRIMARY =
@@ -1029,6 +1032,15 @@ else
1032 PROGRAM_OBJS += http-push.o
1033 endif
1034 endif
1035 + curl_check := $(shell (echo 072200; curl-config --vernum) 2>/dev/null | sort -r | sed -ne 2p)
1036 + ifeq "$(curl_check)" "072200"
1037 + USE_CURL_FOR_IMAP_SEND = YesPlease
1038 + endif
1039 + ifdef USE_CURL_FOR_IMAP_SEND
1040 + BASIC_CFLAGS += -DUSE_CURL_FOR_IMAP_SEND
1041 + IMAP_SEND_BUILDDEPS = http.o
1042 + IMAP_SEND_LDFLAGS += $(CURL_LIBCURL)
1043 + endif
1044 ifndef NO_EXPAT
1045 ifdef EXPATDIR
1046 BASIC_CFLAGS += -I$(EXPATDIR)/include
@@ -1874,7 +1886,7 @@ gettext.sp gettext.s gettext.o: GIT-PREFIX
1886 gettext.sp gettext.s gettext.o: EXTRA_CPPFLAGS = \
1887 -DGIT_LOCALE_PATH='"$(localedir_SQ)"'
1888
1877 -http-push.sp http.sp http-walker.sp remote-curl.sp: SPARSE_FLAGS += \
1889 +http-push.sp http.sp http-walker.sp remote-curl.sp imap-send.sp: SPARSE_FLAGS += \
1890 -DCURL_DISABLE_TYPECHECK
1891
1892 ifdef NO_EXPAT
@@ -1895,9 +1907,9 @@ endif
1907 git-%$X: %.o GIT-LDFLAGS $(GITLIBS)
1908 $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
1909
1898 -git-imap-send$X: imap-send.o GIT-LDFLAGS $(GITLIBS)
1910 +git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS)
1911 $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
1900 - $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) $(LIB_4_CRYPTO)
1912 + $(LIBS) $(IMAP_SEND_LDFLAGS)
1913
1914 git-http-fetch$X: http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS)
1915 $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
imap-send.c
+154 -26
@@ -30,13 +30,18 @@
30 #ifdef NO_OPENSSL
31 typedef void *SSL;
32 #endif
33 +#ifdef USE_CURL_FOR_IMAP_SEND
34 +#include "http.h"
35 +#endif
36
37 static int verbosity;
38 +static int use_curl; /* strictly opt in */
39
36 -static const char * const imap_send_usage[] = { "git imap-send [-v] [-q] < <mbox>", NULL };
40 +static const char * const imap_send_usage[] = { "git imap-send [-v] [-q] [--[no-]curl] < <mbox>", NULL };
41
42 static struct option imap_send_options[] = {
43 OPT__VERBOSITY(&verbosity),
44 + OPT_BOOL(0, "curl", &use_curl, "use libcurl to communicate with the IMAP server"),
45 OPT_END()
46 };
47
@@ -1344,14 +1349,145 @@ static void git_imap_config(void)
1349 git_config_get_string("imap.authmethod", &server.auth_method);
1350 }
1351
1347 -int main(int argc, char **argv)
1352 +static int append_msgs_to_imap(struct imap_server_conf *server,
1353 + struct strbuf* all_msgs, int total)
1354 {
1349 - struct strbuf all_msgs = STRBUF_INIT;
1355 struct strbuf msg = STRBUF_INIT;
1356 struct imap_store *ctx = NULL;
1357 int ofs = 0;
1358 int r;
1354 - int total, n = 0;
1359 + int n = 0;
1360 +
1361 + ctx = imap_open_store(server, server->folder);
1362 + if (!ctx) {
1363 + fprintf(stderr, "failed to open store\n");
1364 + return 1;
1365 + }
1366 + ctx->name = server->folder;
1367 +
1368 + fprintf(stderr, "sending %d message%s\n", total, (total != 1) ? "s" : "");
1369 + while (1) {
1370 + unsigned percent = n * 100 / total;
1371 +
1372 + fprintf(stderr, "%4u%% (%d/%d) done\r", percent, n, total);
1373 +
1374 + if (!split_msg(all_msgs, &msg, &ofs))
1375 + break;
1376 + if (server->use_html)
1377 + wrap_in_html(&msg);
1378 + r = imap_store_msg(ctx, &msg);
1379 + if (r != DRV_OK)
1380 + break;
1381 + n++;
1382 + }
1383 + fprintf(stderr, "\n");
1384 +
1385 + imap_close_store(ctx);
1386 +
1387 + return 0;
1388 +}
1389 +
1390 +#ifdef USE_CURL_FOR_IMAP_SEND
1391 +static CURL *setup_curl(struct imap_server_conf *srvc)
1392 +{
1393 + CURL *curl;
1394 + struct strbuf path = STRBUF_INIT;
1395 +
1396 + if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
1397 + die("curl_global_init failed");
1398 +
1399 + curl = curl_easy_init();
1400 +
1401 + if (!curl)
1402 + die("curl_easy_init failed");
1403 +
1404 + curl_easy_setopt(curl, CURLOPT_USERNAME, server.user);
1405 + curl_easy_setopt(curl, CURLOPT_PASSWORD, server.pass);
1406 +
1407 + strbuf_addstr(&path, server.host);
1408 + if (!path.len || path.buf[path.len - 1] != '/')
1409 + strbuf_addch(&path, '/');
1410 + strbuf_addstr(&path, server.folder);
1411 +
1412 + curl_easy_setopt(curl, CURLOPT_URL, path.buf);
1413 + strbuf_release(&path);
1414 + curl_easy_setopt(curl, CURLOPT_PORT, server.port);
1415 +
1416 + if (server.auth_method) {
1417 + struct strbuf auth = STRBUF_INIT;
1418 + strbuf_addstr(&auth, "AUTH=");
1419 + strbuf_addstr(&auth, server.auth_method);
1420 + curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);
1421 + strbuf_release(&auth);
1422 + }
1423 +
1424 + if (server.use_ssl)
1425 + curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL);
1426 +
1427 + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, server.ssl_verify);
1428 + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, server.ssl_verify);
1429 +
1430 + curl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);
1431 +
1432 + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
1433 +
1434 + if (0 < verbosity)
1435 + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
1436 +
1437 + return curl;
1438 +}
1439 +
1440 +static int curl_append_msgs_to_imap(struct imap_server_conf *server,
1441 + struct strbuf* all_msgs, int total) {
1442 + int ofs = 0;
1443 + int n = 0;
1444 + struct buffer msgbuf = { STRBUF_INIT, 0 };
1445 + CURL *curl;
1446 + CURLcode res = CURLE_OK;
1447 +
1448 + curl = setup_curl(server);
1449 + curl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);
1450 +
1451 + fprintf(stderr, "sending %d message%s\n", total, (total != 1) ? "s" : "");
1452 + while (1) {
1453 + unsigned percent = n * 100 / total;
1454 + int prev_len;
1455 +
1456 + fprintf(stderr, "%4u%% (%d/%d) done\r", percent, n, total);
1457 +
1458 + prev_len = msgbuf.buf.len;
1459 + if (!split_msg(all_msgs, &msgbuf.buf, &ofs))
1460 + break;
1461 + if (server->use_html)
1462 + wrap_in_html(&msgbuf.buf);
1463 + lf_to_crlf(&msgbuf.buf);
1464 +
1465 + curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE,
1466 + (curl_off_t)(msgbuf.buf.len-prev_len));
1467 +
1468 + res = curl_easy_perform(curl);
1469 +
1470 + if(res != CURLE_OK) {
1471 + fprintf(stderr, "curl_easy_perform() failed: %s\n",
1472 + curl_easy_strerror(res));
1473 + break;
1474 + }
1475 +
1476 + n++;
1477 + }
1478 + fprintf(stderr, "\n");
1479 +
1480 + curl_easy_cleanup(curl);
1481 + curl_global_cleanup();
1482 +
1483 + return 0;
1484 +}
1485 +#endif
1486 +
1487 +int main(int argc, char **argv)
1488 +{
1489 + struct strbuf all_msgs = STRBUF_INIT;
1490 + int total;
1491 int nongit_ok;
1492
1493 git_extract_argv0_path(argv[0]);
@@ -1366,6 +1502,13 @@ int main(int argc, char **argv)
1502 if (argc)
1503 usage_with_options(imap_send_usage, imap_send_options);
1504
1505 +#ifndef USE_CURL_FOR_IMAP_SEND
1506 + if (use_curl) {
1507 + warning("--use-curl not supported in this build");
1508 + use_curl = 0;
1509 + }
1510 +#endif
1511 +
1512 if (!server.port)
1513 server.port = server.use_ssl ? 993 : 143;
1514
@@ -1399,29 +1542,14 @@ int main(int argc, char **argv)
1542 }
1543
1544 /* write it to the imap server */
1402 - ctx = imap_open_store(&server, server.folder);
1403 - if (!ctx) {
1404 - fprintf(stderr, "failed to open store\n");
1405 - return 1;
1406 - }
1407 -
1408 - fprintf(stderr, "sending %d message%s\n", total, (total != 1) ? "s" : "");
1409 - while (1) {
1410 - unsigned percent = n * 100 / total;
1545
1412 - fprintf(stderr, "%4u%% (%d/%d) done\r", percent, n, total);
1413 - if (!split_msg(&all_msgs, &msg, &ofs))
1414 - break;
1415 - if (server.use_html)
1416 - wrap_in_html(&msg);
1417 - r = imap_store_msg(ctx, &msg);
1418 - if (r != DRV_OK)
1419 - break;
1420 - n++;
1421 - }
1422 - fprintf(stderr, "\n");
1546 + if (server.tunnel)
1547 + return append_msgs_to_imap(&server, &all_msgs, total);
1548
1424 - imap_close_store(ctx);
1549 +#ifdef USE_CURL_FOR_IMAP_SEND
1550 + if (use_curl)
1551 + return curl_append_msgs_to_imap(&server, &all_msgs, total);
1552 +#endif
1553
1426 - return 0;
1554 + return append_msgs_to_imap(&server, &all_msgs, total);
1555 }