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