KiCad PCB EDA Suite
Loading...
Searching...
No Matches
kicad_curl_easy.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2015 Mark Roszko <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 3
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25// kicad_curl_easy.h **must be** included before any wxWidgets header to avoid conflicts
26// at least on Windows/msys2
27#include <curl/curl.h>
28#include <curl/easy.h>
31
32#include <cstdarg>
33#include <cstddef>
34#include <exception>
35#include <sstream>
36#include <shared_mutex>
37#include <wx/app.h>
38
39#include <build_version.h>
40#include <ki_exception.h> // THROW_IO_ERROR
41#include <kiplatform/app.h>
43
44#include <kiplatform/policy.h>
45#include <policy_keys.h>
46
48{
51 curl_off_t m_Last_run_time;
52 curl_off_t m_Interval;
53
54 CURL_PROGRESS( KICAD_CURL_EASY* aCURL, TRANSFER_CALLBACK aCallback, curl_off_t aInterval ) :
55 m_Curl( aCURL ),
56 m_Callback( aCallback ),
57 m_Last_run_time( 0 ),
58 m_Interval( aInterval )
59 {
60 }
61};
62
63
64static size_t write_callback( void* aContents, size_t aSize, size_t aNmemb, void* aUserp )
65{
66 size_t realsize = aSize * aNmemb;
67
68 std::string* p = static_cast<std::string*>( aUserp );
69
70 p->append( static_cast<const char*>( aContents ), realsize );
71
72 return realsize;
73}
74
75
76static size_t stream_write_callback( void* aContents, size_t aSize, size_t aNmemb, void* aUserp )
77{
78 size_t realsize = aSize * aNmemb;
79
80 std::ostream* p = static_cast<std::ostream*>( aUserp );
81
82 p->write( static_cast<const char*>( aContents ), realsize );
83
84 return realsize;
85}
86
87
88#if LIBCURL_VERSION_NUM >= 0x072000 // 7.32.0
89
90static int xferinfo( void* aProgress, curl_off_t aDLtotal, curl_off_t aDLnow, curl_off_t aULtotal,
91 curl_off_t aULnow )
92{
94 return 1; // Should abort the operation
95
96 CURL_PROGRESS* progress = static_cast<CURL_PROGRESS*>( aProgress );
97 curl_off_t curtime = 0;
98
99 curl_easy_getinfo( progress->m_Curl->GetCurl(), CURLINFO_TOTAL_TIME, &curtime );
100
101 if( curtime - progress->m_Last_run_time >= progress->m_Interval )
102 {
103 progress->m_Last_run_time = curtime;
104 return progress->m_Callback( aDLtotal, aDLnow, aULtotal, aULnow );
105 }
106
107 return CURLE_OK;
108}
109
110#else
111
112static int progressinfo( void* aProgress, double aDLtotal, double aDLnow, double aULtotal,
113 double aULnow )
114{
115 return xferinfo( aProgress, static_cast<curl_off_t>( aDLtotal ),
116 static_cast<curl_off_t>( aDLnow ), static_cast<curl_off_t>( aULtotal ),
117 static_cast<curl_off_t>( aULnow ) );
118}
119
120#endif
121
122
124 m_headers( nullptr ),
125 m_curlSharedLock( KICAD_CURL::Mutex() )
126{
127 m_CURL = curl_easy_init();
128
129 if( !m_CURL )
130 THROW_IO_ERROR( "Unable to initialize CURL session" );
131
132 curl_easy_setopt( m_CURL, CURLOPT_WRITEFUNCTION, write_callback );
133 curl_easy_setopt( m_CURL, CURLOPT_WRITEDATA, static_cast<void*>( &m_buffer ) );
134
135 // Only allow HTTP and HTTPS protocols
136#if LIBCURL_VERSION_NUM >= 0x075500 // version 7.85.0
137 curl_easy_setopt(m_CURL, CURLOPT_PROTOCOLS_STR, "http,https");
138#else
139 curl_easy_setopt( m_CURL, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS );
140#endif
141
142 // Set connection timeout to prevent long freezes when network is unreachable.
143 // Without this, CURL can wait up to 5 minutes for DNS resolution or connection
144 // establishment, which causes the UI to freeze on macOS when behind a proxy with
145 // restricted internet access.
146 curl_easy_setopt( m_CURL, CURLOPT_CONNECTTIMEOUT, 30L );
147
148#ifdef _WIN32
149 long sslOpts = CURLSSLOPT_NATIVE_CA;
150
153
154 if( policyState == POLICY_CURL_SSL_REVOKE::BEST_EFFORT )
155 {
156 sslOpts |= CURLSSLOPT_REVOKE_BEST_EFFORT;
157 }
158 else if( policyState == POLICY_CURL_SSL_REVOKE::NONE )
159 {
160 sslOpts |= CURLSSLOPT_NO_REVOKE;
161 }
162
163 // We need this to use the Windows Certificate store
164 curl_easy_setopt( m_CURL, CURLOPT_SSL_OPTIONS, sslOpts );
165#endif
166
167 if( wxGetEnv( wxT( "KICAD_CURL_VERBOSE" ), nullptr ) )
168 {
169 // note: curl verbose will end up in stderr
170 curl_easy_setopt( m_CURL, CURLOPT_VERBOSE, 1L );
171 }
172
173 wxString application( wxS( "KiCad" ) );
174 wxString version( GetBuildVersion() );
175 wxString platform = wxS( "(" ) + wxGetOsDescription() + wxS( ";" ) + GetPlatformGetBitnessName();
176
177#if defined( KICAD_BUILD_ARCH_X64 )
178 platform << wxS( ";64-bit" );
179#elif defined( KICAD_BUILD_ARCH_X86 )
180 platform << wxS( ";32-bit" );
181#elif defined( KICAD_BUILD_ARCH_ARM )
182 platform << wxS( ";ARM 32-bit" );
183#elif defined( KICAD_BUILD_ARCH_ARM64 )
184 platform << wxS( ";ARM 64-bit" );
185#endif
186
187 platform << wxS( ")" );
188
189 wxString user_agent = wxS( "KiCad/" ) + version + wxS( " " ) + platform + wxS( " " ) + application;
190
191 user_agent << wxS( "/" ) << GetBuildDate();
192 setOption<const char*>( CURLOPT_USERAGENT, user_agent.ToStdString().c_str() );
193 setOption( CURLOPT_ACCEPT_ENCODING, "gzip,deflate" );
194}
195
196
198{
199 if( m_headers )
200 curl_slist_free_all( m_headers );
201
202 curl_easy_cleanup( m_CURL );
203}
204
205
207{
208 if( m_headers )
209 curl_easy_setopt( m_CURL, CURLOPT_HTTPHEADER, m_headers );
210
211 // bonus: retain worst case memory allocation, should re-use occur
212 m_buffer.clear();
213
214 return curl_easy_perform( m_CURL );
215}
216
217
218void KICAD_CURL_EASY::SetHeader( const std::string& aName, const std::string& aValue )
219{
220 std::string header = aName + ':' + aValue;
221 m_headers = curl_slist_append( m_headers, header.c_str() );
222}
223
224
225template <typename T>
226int KICAD_CURL_EASY::setOption( int aOption, T aArg )
227{
228 return curl_easy_setopt( m_CURL, static_cast<CURLoption>( aOption ), aArg );
229}
230
231
232const std::string KICAD_CURL_EASY::GetErrorText( int aCode )
233{
234 return curl_easy_strerror( static_cast<CURLcode>( aCode ) );
235}
236
237
238bool KICAD_CURL_EASY::SetUserAgent( const std::string& aAgent )
239{
240 if( setOption<const char*>( CURLOPT_USERAGENT, aAgent.c_str() ) == CURLE_OK )
241 return true;
242
243 return false;
244}
245
246
247bool KICAD_CURL_EASY::SetPostFields( const std::vector<std::pair<std::string, std::string>>& aFields )
248{
249 std::string postfields;
250
251 for( size_t i = 0; i < aFields.size(); i++ )
252 {
253 if( i > 0 )
254 postfields += "&";
255
256 postfields += Escape( aFields[i].first );
257 postfields += "=";
258 postfields += Escape( aFields[i].second );
259 }
260
261 if( setOption<const char*>( CURLOPT_COPYPOSTFIELDS, postfields.c_str() ) != CURLE_OK )
262 return false;
263
264 return true;
265}
266
267
268bool KICAD_CURL_EASY::SetPostFields( const std::string& aField )
269{
270 if( setOption<const char*>( CURLOPT_COPYPOSTFIELDS, aField.c_str() ) != CURLE_OK )
271 return false;
272
273 return true;
274}
275
276
277bool KICAD_CURL_EASY::SetURL( const std::string& aURL )
278{
279 if( setOption<const char*>( CURLOPT_URL, aURL.c_str() ) == CURLE_OK )
280 {
282
283 // Unfortunately on Windows land, proxies can be configured depending on destination url
284 // So we also check and set any proxy config here
286 {
287 curl_easy_setopt( m_CURL, CURLOPT_PROXY, static_cast<const char*>( cfg.host.c_str() ) );
288
289 if( !cfg.username.empty() )
290 {
291 curl_easy_setopt( m_CURL, CURLOPT_PROXYUSERNAME,
292 static_cast<const char*>( cfg.username.c_str() ) );
293 }
294
295 if( !cfg.password.empty() )
296 {
297 curl_easy_setopt( m_CURL, CURLOPT_PROXYPASSWORD,
298 static_cast<const char*>( cfg.password.c_str() ) );
299 }
300 }
301
302 return true;
303 }
304
305 return false;
306}
307
308
310{
311 if( setOption<long>( CURLOPT_FOLLOWLOCATION, ( aFollow ? 1 : 0 ) ) == CURLE_OK )
312 return true;
313
314 return false;
315}
316
317
318std::string KICAD_CURL_EASY::Escape( const std::string& aUrl )
319{
320 char* escaped = curl_easy_escape( m_CURL, aUrl.c_str(), aUrl.length() );
321
322 std::string ret( escaped );
323 curl_free( escaped );
324
325 return ret;
326}
327
328
329bool KICAD_CURL_EASY::SetTransferCallback( const TRANSFER_CALLBACK& aCallback, size_t aInterval )
330{
331 progress = std::make_unique<CURL_PROGRESS>( this, aCallback, static_cast<curl_off_t>( aInterval ) );
332
333#if LIBCURL_VERSION_NUM >= 0x072000 // 7.32.0
334 setOption( CURLOPT_XFERINFOFUNCTION, xferinfo );
335 setOption( CURLOPT_XFERINFODATA, progress.get() );
336#else
337 setOption( CURLOPT_PROGRESSFUNCTION, progressinfo );
338 setOption( CURLOPT_PROGRESSDATA, progress.get() );
339#endif
340
341 setOption( CURLOPT_NOPROGRESS, 0L );
342 return true;
343}
344
345
346bool KICAD_CURL_EASY::SetOutputStream( const std::ostream* aOutput )
347{
348 curl_easy_setopt( m_CURL, CURLOPT_WRITEFUNCTION, stream_write_callback );
349 curl_easy_setopt( m_CURL, CURLOPT_WRITEDATA, reinterpret_cast<const void*>( aOutput ) );
350 return true;
351}
352
353
354bool KICAD_CURL_EASY::SetConnectTimeout( long aTimeoutSecs )
355{
356 return setOption( CURLOPT_CONNECTTIMEOUT, aTimeoutSecs ) == CURLE_OK;
357}
358
359
360int KICAD_CURL_EASY::GetTransferTotal( uint64_t& aDownloadedBytes ) const
361{
362#if LIBCURL_VERSION_NUM >= 0x073700 // 7.55.0
363 curl_off_t dl;
364 int result = curl_easy_getinfo( m_CURL, CURLINFO_SIZE_DOWNLOAD_T, &dl );
365 aDownloadedBytes = static_cast<uint64_t>( dl );
366#else
367 double dl;
368 int result = curl_easy_getinfo( m_CURL, CURLINFO_SIZE_DOWNLOAD, &dl );
369 aDownloadedBytes = static_cast<uint64_t>( dl );
370#endif
371
372 return result;
373}
374
375
377{
378 long http_code = 0;
379 curl_easy_getinfo( m_CURL, CURLINFO_RESPONSE_CODE, &http_code );
380
381 return static_cast<int>( http_code );
382}
wxString GetBuildVersion()
Get the full KiCad version string.
wxString GetPlatformGetBitnessName()
wxString GetBuildDate()
Get the build date as a string.
std::string m_buffer
int Perform()
Equivalent to curl_easy_perform.
bool SetPostFields(const std::vector< std::pair< std::string, std::string > > &aFields)
Set fields for application/x-www-form-urlencoded POST request.
std::unique_ptr< CURL_PROGRESS > progress
std::string Escape(const std::string &aUrl)
Escapes a string for use as a URL.
bool SetUserAgent(const std::string &aAgent)
Set the request user agent.
std::shared_lock< std::shared_mutex > m_curlSharedLock
curl_slist * m_headers
void SetHeader(const std::string &aName, const std::string &aValue)
Set an arbitrary header for the HTTP(s) request.
bool SetTransferCallback(const TRANSFER_CALLBACK &aCallback, size_t aInterval)
bool SetURL(const std::string &aURL)
Set the request URL.
bool SetFollowRedirects(bool aFollow)
Enable the following of HTTP(s) and other redirects, by default curl does not follow redirects.
bool SetConnectTimeout(long aTimeoutSecs)
Set the connection timeout in seconds.
int GetTransferTotal(uint64_t &aDownloadedBytes) const
int setOption(int aOption, T aArg)
Set a curl option, only supports single parameter curl options.
bool SetOutputStream(const std::ostream *aOutput)
const std::string GetErrorText(int aCode)
Fetch CURL's "friendly" error string for a given error code.
Simple wrapper class to call curl_global_init and curl_global_cleanup for KiCad.
Definition kicad_curl.h:62
static bool IsShuttingDown()
Returns true if all curl operations should terminate.
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
static int progressinfo(void *aProgress, double aDLtotal, double aDLnow, double aULtotal, double aULnow)
static size_t write_callback(void *aContents, size_t aSize, size_t aNmemb, void *aUserp)
static size_t stream_write_callback(void *aContents, size_t aSize, size_t aNmemb, void *aUserp)
std::function< int(size_t, size_t, size_t, size_t)> TRANSFER_CALLBACK
Wrapper interface around the curl_easy API/.
bool GetSystemProxyConfig(const wxString &aURL, PROXY_CONFIG &aCfg)
Retrieves platform level proxying requirements to reach the given url.
T GetPolicyEnum(const wxString &aKey)
Definition policy.h:44
#define POLICY_KEY_REQUESTS_CURL_REVOKE
Definition policy_keys.h:32
POLICY_CURL_SSL_REVOKE
Definition policy_keys.h:35
curl_off_t m_Interval
CURL_PROGRESS(KICAD_CURL_EASY *aCURL, TRANSFER_CALLBACK aCallback, curl_off_t aInterval)
TRANSFER_CALLBACK m_Callback
KICAD_CURL_EASY * m_Curl
curl_off_t m_Last_run_time
wxString result
Test unit parsing edge cases and error handling.