From 77ce3e8c1af12bf829f02b07351003a4f60bbca5 Mon Sep 17 00:00:00 2001 From: John Reese Date: Mon, 28 Oct 2013 16:55:18 -0700 Subject: [PATCH] Add support for using curl instead of CSocket When compiling with USE_CURL defined, znc push will use libcurl for making http requests instead of ZNC's builtin CSocket library. This enables some advanced features, such as supporting HTTP proxies for outbound requests. Currently, building with curl support requires running the following: make curl=yes clean install libcurl must already be installed and accessible by default. Otherwise, it is up to the user to populate CXXFLAGS with the appropriate values so that znc-buildmod can appropriately find and link libcurl to the module. --- Makefile | 9 +++- README.md | 10 ++++ push.cpp | 136 +++++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 132 insertions(+), 23 deletions(-) diff --git a/Makefile b/Makefile index d1f5f96..2e295ba 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,15 @@ version := $(shell git describe --dirty) +curl=no + +ifneq ($(curl),no) + flags=-DUSE_CURL -lcurl +else + flags= +endif push.so: push.cpp sed -i -e "s|PUSHVERSION \".*\"|PUSHVERSION \"$(version)\"|" push.cpp - znc-buildmod push.cpp + CXXFLAGS="$(CXXFLAGS) $(flags)" znc-buildmod push.cpp sed -i -e "s|PUSHVERSION \".*\"|PUSHVERSION \"dev\"|" push.cpp install: push.so diff --git a/README.md b/README.md index bab1fd8..1208fe9 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,16 @@ Otherwise, run the full command: $ znc-buildmod push.cpp +### Advanced + +If you would like to compile ZNC Push using libcurl for http requests, you must use: + + $ make curl=yes + +If libcurl is not in the default system library paths, you will need to populate `$CXXFLAGS` +with the appropriate GCC flags so that it can find and link ZNC Push with libcurl. + + Installation ------------ diff --git a/push.cpp b/push.cpp index e9988d0..4c16281 100644 --- a/push.cpp +++ b/push.cpp @@ -21,9 +21,25 @@ #include "time.h" #include +#ifdef USE_CURL +#include +#endif // USE_CURL + // Forward declaration class CPushMod; +/** + * Shorthand for encoding a string for a URL. + * + * @param str String to be encoded + * @return Encoded string + */ +CString urlencode(const CString& str) +{ + return str.Escape_n(CString::EASCII, CString::EURL); +} + +#ifndef USE_CURL /** * Socket class for generating HTTP requests. */ @@ -54,17 +70,13 @@ class CPushSocket : public CSocket // User agent to use CString user_agent; - /** - * Shorthand for encoding a string for a URL. - * - * @param str String to be encoded - * @return Encoded string - */ - CString urlencode(const CString& str) - { - return str.Escape_n(CString::EASCII, CString::EURL); - } }; +#else +// forward declaration +CURLcode make_curl_request(const CString& service_host, const CString& service_url, + const CString& service_auth, MCString& params, int port, + bool use_ssl, bool use_post); +#endif // USE_CURL /** * Push notification module. @@ -98,6 +110,10 @@ class CPushMod : public CModule public: MODCONSTRUCTOR(CPushMod) { +#ifdef USE_CURL + curl_global_init(CURL_GLOBAL_DEFAULT); +#endif + app = "ZNC"; idle_time = time(NULL); @@ -135,7 +151,12 @@ class CPushMod : public CModule defaults["query_conditions"] = "all"; defaults["debug"] = "off"; } - virtual ~CPushMod() {} + + virtual ~CPushMod() { +#ifdef USE_CURL + curl_global_cleanup(); +#endif + } public: @@ -441,11 +462,15 @@ class CPushMod : public CModule return; } +#ifdef USE_CURL + make_curl_request(service_host, service_url, service_auth, params, use_port, use_ssl, use_post); +#else // Create the socket connection, write to it, and add it to the queue CPushSocket *sock = new CPushSocket(this); sock->Connect(service_host, use_port, use_ssl); sock->Request(use_post, service_host, service_url, params, service_auth); AddSocket(sock); +#endif } /** @@ -1380,11 +1405,15 @@ class CPushMod : public CModule return; } +#ifdef USE_CURL + make_curl_request(service_host, service_url, service_auth, params, use_port, use_ssl, use_post); +#else // Create the socket connection, write to it, and add it to the queue CPushSocket *sock = new CPushSocket(this); sock->Connect(service_host, use_port, use_ssl); sock->Request(use_post, service_host, service_url, params, service_auth); AddSocket(sock); +#endif PutModule("Ok"); } @@ -1420,24 +1449,18 @@ class CPushMod : public CModule }; /** - * Send an HTTP request. + * Build a query string from a dictionary of request parameters. * - * @param post POST command - * @param host Host domain - * @param url Resource path - * @param parameters Query parameters - * @param auth Basic authentication string + * @param params Request parameters + * @return query string */ -void CPushSocket::Request(bool post, const CString& host, const CString& url, MCString& parameters, const CString& auth) +CString build_query_string(MCString& params) { - parent->PutDebug("Building notification to " + host + url + "..."); - - // query string for the request bool more = false; CString query; CString key; CString value; - for (MCString::iterator param = parameters.begin(); param != parameters.end(); param++) + for (MCString::iterator param = params.begin(); param != params.end(); param++) { key = urlencode(param->first); value = urlencode(param->second); @@ -1453,6 +1476,74 @@ void CPushSocket::Request(bool post, const CString& host, const CString& url, MC } } + return query; +} + +#ifdef USE_CURL +/** + * Send an HTTP request using libcurl. + * + * @param service_host Host domain + * @param service_url Host path + * @param service_auth Basic auth string + * @param params Request parameters + * @param port Port number + * @param use_ssl Use SSL + * @param use_post Use POST method + */ +CURLcode make_curl_request(const CString& service_host, const CString& service_url, + const CString& service_auth, MCString& params, int port, + bool use_ssl, bool use_post) +{ + CURL *curl; + CURLcode result; + + curl = curl_easy_init(); + + CString url = CString(use_ssl ? "https" : "http") + "://" + service_host + service_url; + CString query = build_query_string(params); + + curl_easy_setopt(curl, CURLOPT_URL, url.data()); + curl_easy_setopt(curl, CURLOPT_PORT, port); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "ZNC Push"); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3); // three seconds ought to be good enough for anyone, eh? + + if (service_auth != "") + { + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + curl_easy_setopt(curl, CURLOPT_USERPWD, service_auth.data()); + } + + if (use_post) + { + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, query.data()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, query.length()); + } + + result = curl_easy_perform(curl); + curl_easy_cleanup(curl); + + return result; +} + +#else + +/** + * Send an HTTP request. + * + * @param post POST command + * @param host Host domain + * @param url Resource path + * @param parameters Query parameters + * @param auth Basic authentication string + */ +void CPushSocket::Request(bool post, const CString& host, const CString& url, MCString& parameters, const CString& auth) +{ + parent->PutDebug("Building notification to " + host + url + "..."); + + CString query = build_query_string(parameters); + parent->PutDebug("Query string: " + query); // Request headers and POST body @@ -1514,5 +1605,6 @@ void CPushSocket::Disconnected() parent->PutDebug("Disconnected."); Close(CSocket::CLT_AFTERWRITE); } +#endif // USE_CURL MODULEDEFS(CPushMod, "Send highlights and personal messages to a push notification service")