print debugging for macOS/curl issues
[ardour.git] / gtk2_ardour / ardour_http.cc
1 /*
2  * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */
18 #define ARDOURCURLDEBUG
19
20 #include <assert.h>
21 #include <stdlib.h>
22 #include <unistd.h>
23 #include <cstring>
24
25 #include <glibmm.h>
26
27 #include "pbd/compose.h"
28 #include "pbd/i18n.h"
29 #include "pbd/error.h"
30
31 #include "ardour_http.h"
32
33 #ifdef WAF_BUILD
34 #include "gtk2ardour-version.h"
35 #endif
36
37 #ifndef ARDOUR_CURL_TIMEOUT
38 #define ARDOUR_CURL_TIMEOUT (60)
39 #endif
40
41 #ifdef ARDOURCURLDEBUG
42 #define CCERR(msg) do { if (cc != CURLE_OK) { std::cerr << string_compose ("curl_easy_setopt(%1) failed: %2", msg, cc) << std::endl; } } while (0)
43 #else
44 #define CCERR(msg)
45 #endif
46
47 using namespace ArdourCurl;
48
49 const char* HttpGet::ca_path = NULL;
50 const char* HttpGet::ca_info = NULL;
51
52 void
53 HttpGet::setup_certificate_paths ()
54 {
55         /* this is only needed for Linux Bundles.
56          * (on OSX, Windows, we use system-wide ssl (darwinssl, winssl)
57          * Gnu/Linux distro will link against system-wide libcurl.
58          *
59          * but for linux-bundles we get to enjoy:
60          * https://www.happyassassin.net/2015/01/12/a-note-about-ssltls-trusted-certificate-stores-and-platforms/
61          *
62          * (we do ship curl + nss + nss-pem)
63          *
64          * Short of this mess: we could simply bundle a .crt of
65          * COMODO (ardour) and ghandi (freesound) and be done with it.
66          */
67         assert (!ca_path && !ca_info); // call once
68
69         if (Glib::file_test ("/etc/pki/tls/certs/ca-bundle.crt", Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
70                 // Fedora / RHEL, Arch
71                 ca_info = "/etc/pki/tls/certs/ca-bundle.crt";
72         }
73         else if (Glib::file_test ("/etc/ssl/certs/ca-certificates.crt", Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
74                 // Debian and derivatives
75                 ca_info = "/etc/ssl/certs/ca-certificates.crt";
76         }
77         else if (Glib::file_test ("/etc/pki/tls/cert.pem", Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
78                 // GNU/TLS can keep extra stuff here
79                 ca_info = "/etc/pki/tls/cert.pem";
80         }
81         // else NULL: use default (currently) "/etc/ssl/certs/ca-certificates.crt" if it exists
82
83         if (Glib::file_test ("/etc/pki/tls/certs/ca-bundle.crt", Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_DIR)) {
84                 // we're on RHEL // https://bugzilla.redhat.com/show_bug.cgi?id=1053882
85                 ca_path = "/nonexistent_path"; // don't try "/etc/ssl/certs" in case it's curl's default
86         }
87         else if (Glib::file_test ("/etc/ssl/certs", Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_DIR)) {
88                 // Debian and derivs + OpenSuSe
89                 ca_path = "/etc/ssl/certs";
90         } else {
91                 ca_path = "/nonexistent_path"; // don't try -- just in case:
92         }
93
94         /* If we don't set anything defaults are used. at the time of writing we compile bundled curl on debian
95          * and it'll default to  /etc/ssl/certs and /etc/ssl/certs/ca-certificates.crt
96          */
97 }
98
99 static size_t
100 WriteMemoryCallback (void *ptr, size_t size, size_t nmemb, void *data) {
101         size_t realsize = size * nmemb;
102         struct HttpGet::MemStruct *mem = (struct HttpGet::MemStruct*)data;
103
104         mem->data = (char *)realloc (mem->data, mem->size + realsize + 1);
105         if (mem->data) {
106                 memcpy (&(mem->data[mem->size]), ptr, realsize);
107                 mem->size += realsize;
108                 mem->data[mem->size] = 0;
109         }
110         return realsize;
111 }
112
113 static size_t headerCallback (char* ptr, size_t size, size_t nmemb, void* data)
114 {
115         size_t realsize = size * nmemb;
116 #ifdef ARDOURCURLDEBUG
117                 std::cerr << string_compose ("ArdourCurl HTTP-header recv %1 bytes", realsize) << std::endl;
118 #endif
119         struct HttpGet::HeaderInfo *nfo = (struct HttpGet::HeaderInfo*)data;
120         std::string header (static_cast<const char*>(ptr), realsize);
121         std::string::size_type index = header.find (':', 0);
122         if (index != std::string::npos) {
123                 std::string k = header.substr (0, index);
124                 std::string v = header.substr (index + 2);
125                 k.erase(k.find_last_not_of (" \n\r\t")+1);
126                 v.erase(v.find_last_not_of (" \n\r\t")+1);
127                 nfo->h[k] = v;
128 #ifdef ARDOURCURLDEBUG
129                 std::cerr << string_compose ("ArdourCurl HTTP-header  '%1' = '%2'", k, v) << std::endl;
130 #endif
131         }
132
133         return realsize;
134 }
135
136 HttpGet::HttpGet (bool p, bool ssl)
137         : persist (p)
138         , _status (-1)
139         , _result (-1)
140 {
141         memset (error_buffer, 0, sizeof (*error_buffer));
142         _curl = curl_easy_init ();
143
144         if (!_curl) {
145                 std::cerr << "HttpGet::HttpGet curl_easy_init() failed." << std::endl;
146                 return;
147         }
148
149         CURLcode cc;
150
151         cc = curl_easy_setopt (_curl, CURLOPT_WRITEDATA, (void *)&mem); CCERR ("CURLOPT_WRITEDATA");
152         cc = curl_easy_setopt (_curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); CCERR ("CURLOPT_WRITEFUNCTION");
153         cc = curl_easy_setopt (_curl, CURLOPT_HEADERDATA, (void *)&nfo); CCERR ("CURLOPT_HEADERDATA");
154         cc = curl_easy_setopt (_curl, CURLOPT_HEADERFUNCTION, headerCallback); CCERR ("CURLOPT_HEADERFUNCTION");
155         cc = curl_easy_setopt (_curl, CURLOPT_USERAGENT, PROGRAM_NAME VERSIONSTRING); CCERR ("CURLOPT_USERAGENT");
156         cc = curl_easy_setopt (_curl, CURLOPT_TIMEOUT, ARDOUR_CURL_TIMEOUT); CCERR ("CURLOPT_TIMEOUT");
157         cc = curl_easy_setopt (_curl, CURLOPT_NOSIGNAL, 1); CCERR ("CURLOPT_NOSIGNAL");
158         cc = curl_easy_setopt (_curl, CURLOPT_ERRORBUFFER, error_buffer); CCERR ("CURLOPT_ERRORBUFFER");
159         // cc= curl_easy_setopt (_curl, CURLOPT_FOLLOWLOCATION, 1); CCERR ("CURLOPT_FOLLOWLOCATION");
160
161         // by default use curl's default.
162         if (ssl && ca_info) {
163                 curl_easy_setopt (_curl, CURLOPT_CAINFO, ca_info);
164         }
165         if (ssl && ca_path) {
166                 curl_easy_setopt (_curl, CURLOPT_CAPATH, ca_path);
167         }
168 }
169
170 HttpGet::~HttpGet ()
171 {
172         if (_curl) {
173                 curl_easy_cleanup (_curl);
174         }
175         if (!persist) {
176                 free (mem.data);
177         }
178 }
179
180 char*
181 HttpGet::get (const char* url)
182 {
183 #ifdef ARDOURCURLDEBUG
184                 std::cerr << "HttpGet::get() ---- new request ---"<< std::endl;
185 #endif
186         _status = _result = -1;
187         if (!_curl || !url) {
188                 PBD::error << "HttpGet::get() not initialized (or NULL url)"<< endmsg;
189 #ifdef ARDOURCURLDEBUG
190                 std::cerr << "HttpGet::get() not initialized (or NULL url)"<< std::endl;
191 #endif
192                 return NULL;
193         }
194
195         if (strncmp ("http://", url, 7) && strncmp ("https://", url, 8)) {
196                 PBD::error << "HttpGet::get() not a http[s] URL"<< endmsg;
197 #ifdef ARDOURCURLDEBUG
198                 std::cerr << "HttpGet::get() not a http[s] URL"<< std::endl;
199 #endif
200                 return NULL;
201         }
202
203         if (!persist) {
204                 free (mem.data);
205         } // otherwise caller is expected to have free()d or re-used it.
206
207         memset (error_buffer, 0, sizeof (*error_buffer));
208         mem.data = NULL;
209         mem.size = 0;
210
211         CURLcode cc;
212
213         cc = curl_easy_setopt (_curl, CURLOPT_URL, url);
214         CCERR ("CURLOPT_URL");
215         _result = curl_easy_perform (_curl);
216         cc = curl_easy_getinfo (_curl, CURLINFO_RESPONSE_CODE, &_status);
217         CCERR ("CURLINFO_RESPONSE_CODE,");
218
219         if (_result) {
220                 PBD::error << string_compose (_("HTTP request failed: (%1) %2"), _result, error_buffer) << endmsg;
221 #ifdef ARDOURCURLDEBUG
222                 std::cerr << string_compose (_("HTTP request failed: (%1) %2"), _result, error_buffer) << std::endl;
223 #endif
224                 return NULL;
225         }
226         if (_status != 200) {
227                 PBD::error << string_compose (_("HTTP request status: %1"), _status) << endmsg;
228 #ifdef ARDOURCURLDEBUG
229                 std::cerr << string_compose (_("HTTP request status: %1"), _status) << std::endl;
230 #endif
231                 return NULL;
232         }
233
234         return mem.data;
235 }
236
237 std::string
238 HttpGet::error () const {
239         if (_result != 0) {
240                 return string_compose (_("HTTP request failed: (%1) %2"), _result, error_buffer);
241         }
242         if (_status != 200) {
243                 return string_compose (_("HTTP request status: %1"), _status);
244         }
245         return "No Error";
246 }
247
248 char*
249 ArdourCurl::http_get (const char* url, int* status) {
250         HttpGet h (true);
251         char* rv = h.get (url);
252         if (status) {
253                 *status = h.status ();
254         }
255         return rv;
256 }
257
258 std::string
259 ArdourCurl::http_get (const std::string& url) {
260         return HttpGet (false).get (url);
261 }