From baa5babcd9f34cc1d8d021d1c055d9b4eaaffb5f Mon Sep 17 00:00:00 2001
From: Max Dymond <cmeister2@gmail.com>
Date: Thu, 25 Feb 2021 15:35:25 +0000
Subject: [PATCH] Add support for curl_easy_upkeep

Add feature-gated support for curl_easy_upkeep, introduced in 7.62.0
---
 Cargo.toml          |  1 +
 README.md           |  1 +
 curl-sys/Cargo.toml |  1 +
 curl-sys/lib.rs     |  3 +++
 src/easy/handle.rs  | 12 ++++++++++++
 src/easy/handler.rs | 15 +++++++++++++++
 tests/easy.rs       | 21 +++++++++++++++++++++
 7 files changed, 54 insertions(+)

diff --git a/Cargo.toml b/Cargo.toml
index 8a0e715992..9130a3ec24 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -48,6 +48,7 @@ static-ssl = ["curl-sys/static-ssl"]
 force-system-lib-on-osx = ['curl-sys/force-system-lib-on-osx']
 protocol-ftp = ["curl-sys/protocol-ftp"]
 zlib-ng-compat = ["curl-sys/zlib-ng-compat", "static-curl"]
+upkeep_7_62_0 = ["curl-sys/upkeep_7_62_0"]
 
 [[test]]
 name = "atexit"
diff --git a/README.md b/README.md
index 252785ddfb..34bd93ee60 100644
--- a/README.md
+++ b/README.md
@@ -128,6 +128,7 @@ with various Cargo features:
 - `static-curl`: Use a bundled libcurl version and statically link to it. Disabled by default.
 - `static-ssl`: Use a bundled OpenSSL version and statically link to it. Only applies on platforms that use OpenSSL. Disabled by default.
 - `spnego`: Enable SPNEGO support. Disabled by default.
+- `upkeep_7_62_0`: Enable curl_easy_upkeep() support, introduced in curl 7.62.0. Disabled by default.
 
 ## Version Support
 
diff --git a/curl-sys/Cargo.toml b/curl-sys/Cargo.toml
index 3fb97444f7..50c2bd7a8b 100644
--- a/curl-sys/Cargo.toml
+++ b/curl-sys/Cargo.toml
@@ -52,3 +52,4 @@ spnego = []
 force-system-lib-on-osx = []
 protocol-ftp = []
 zlib-ng-compat = ["libz-sys/zlib-ng", "static-curl"]
+upkeep_7_62_0 = []
\ No newline at end of file
diff --git a/curl-sys/lib.rs b/curl-sys/lib.rs
index cf039d09f4..71c09c2e4e 100644
--- a/curl-sys/lib.rs
+++ b/curl-sys/lib.rs
@@ -1030,6 +1030,9 @@ extern "C" {
         n: *mut size_t,
     ) -> CURLcode;
 
+    #[cfg(feature = "upkeep_7_62_0")]
+    pub fn curl_easy_upkeep(curl: *mut CURL) -> CURLcode;
+
     pub fn curl_multi_init() -> *mut CURLM;
     pub fn curl_multi_add_handle(multi_handle: *mut CURLM, curl_handle: *mut CURL) -> CURLMcode;
     pub fn curl_multi_remove_handle(multi_handle: *mut CURLM, curl_handle: *mut CURL) -> CURLMcode;
diff --git a/src/easy/handle.rs b/src/easy/handle.rs
index b59430ebbb..b3eb64b3be 100644
--- a/src/easy/handle.rs
+++ b/src/easy/handle.rs
@@ -1256,6 +1256,12 @@ impl Easy {
         }
     }
 
+    /// Same as [`Easy2::upkeep`](struct.Easy2.html#method.upkeep)
+    #[cfg(feature = "upkeep_7_62_0")]
+    pub fn upkeep(&self) -> Result<(), Error> {
+        self.inner.upkeep()
+    }
+
     /// Same as [`Easy2::unpause_read`](struct.Easy2.html#method.unpause_read)
     pub fn unpause_read(&self) -> Result<(), Error> {
         self.inner.unpause_read()
@@ -1504,6 +1510,12 @@ impl<'easy, 'data> Transfer<'easy, 'data> {
         self.easy.do_perform()
     }
 
+    /// Same as `Easy::upkeep`
+    #[cfg(feature = "upkeep_7_62_0")]
+    pub fn upkeep(&self) -> Result<(), Error> {
+        self.easy.upkeep()
+    }
+
     /// Same as `Easy::unpause_read`.
     pub fn unpause_read(&self) -> Result<(), Error> {
         self.easy.unpause_read()
diff --git a/src/easy/handler.rs b/src/easy/handler.rs
index afb5b86243..91181b1c8e 100644
--- a/src/easy/handler.rs
+++ b/src/easy/handler.rs
@@ -2737,6 +2737,21 @@ impl<H> Easy2<H> {
         ret
     }
 
+    /// Some protocols have "connection upkeep" mechanisms. These mechanisms
+    /// usually send some traffic on existing connections in order to keep them
+    /// alive; this can prevent connections from being closed due to overzealous
+    /// firewalls, for example.
+    ///
+    /// Currently the only protocol with a connection upkeep mechanism is
+    /// HTTP/2: when the connection upkeep interval is exceeded and upkeep() is
+    /// called, an HTTP/2 PING frame is sent on the connection.
+    #[cfg(feature = "upkeep_7_62_0")]
+    pub fn upkeep(&self) -> Result<(), Error> {
+        let ret = unsafe { self.cvt(curl_sys::curl_easy_upkeep(self.inner.handle)) };
+        panic::propagate();
+        return ret;
+    }
+
     /// Unpause reading on a connection.
     ///
     /// Using this function, you can explicitly unpause a connection that was
diff --git a/tests/easy.rs b/tests/easy.rs
index d9ab00faa3..d2d158b47f 100644
--- a/tests/easy.rs
+++ b/tests/easy.rs
@@ -828,3 +828,24 @@ fn check_unix_socket() {
     t!(h.post_fields_copy(b"data\n"));
     t!(h.perform());
 }
+
+#[cfg(feature = "upkeep_7_62_0")]
+#[test]
+fn test_upkeep() {
+    let s = Server::new();
+    s.receive(
+        "\
+         GET / HTTP/1.1\r\n\
+         Host: 127.0.0.1:$PORT\r\n\
+         Accept: */*\r\n\
+         \r\n",
+    );
+    s.send("HTTP/1.1 200 OK\r\n\r\n");
+
+    let mut handle = handle();
+    t!(handle.url(&s.url("/")));
+    t!(handle.perform());
+
+    // Ensure that upkeep can be called on the handle without problem.
+    t!(handle.upkeep());
+}