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, see <https://www.gnu.org/licenses/>.
19 */
20
21// kicad_curl_easy.h **must be** included before any wxWidgets header to avoid conflicts
22// at least on Windows/msys2
23#include <curl/curl.h>
24#include <curl/easy.h>
27
28#include <cstdarg>
29#include <cstddef>
30#include <exception>
31#include <sstream>
32#include <shared_mutex>
33#include <wx/app.h>
34
35#include <build_version.h>
36#include <ki_exception.h> // THROW_IO_ERROR
37#include <kiplatform/app.h>
39
40#include <kiplatform/policy.h>
41#include <policy_keys.h>
42
44{
47 curl_off_t m_Last_run_time;
48 curl_off_t m_Interval;
49
50 CURL_PROGRESS( KICAD_CURL_EASY* aCURL, TRANSFER_CALLBACK aCallback, curl_off_t aInterval ) :
51 m_Curl( aCURL ),
52 m_Callback( aCallback ),
53 m_Last_run_time( 0 ),
54 m_Interval( aInterval )
55 {
56 }
57};
58
59
60static size_t write_callback( void* aContents, size_t aSize, size_t aNmemb, void* aUserp )
61{
62 size_t realsize = aSize * aNmemb;
63
64 std::string* p = static_cast<std::string*>( aUserp );
65
66 p->append( static_cast<const char*>( aContents ), realsize );
67
68 return realsize;
69}
70
71
72static size_t stream_write_callback( void* aContents, size_t aSize, size_t aNmemb, void* aUserp )
73{
74 size_t realsize = aSize * aNmemb;
75
76 std::ostream* p = static_cast<std::ostream*>( aUserp );
77
78 p->write( static_cast<const char*>( aContents ), realsize );
79
80 return realsize;
81}
82
83
84#if LIBCURL_VERSION_NUM >= 0x072000 // 7.32.0
85
86static int xferinfo( void* aProgress, curl_off_t aDLtotal, curl_off_t aDLnow, curl_off_t aULtotal,
87 curl_off_t aULnow )
88{
90 return 1; // Should abort the operation
91
92 CURL_PROGRESS* progress = static_cast<CURL_PROGRESS*>( aProgress );
93 curl_off_t curtime = 0;
94
95 curl_easy_getinfo( progress->m_Curl->GetCurl(), CURLINFO_TOTAL_TIME, &curtime );
96
97 if( curtime - progress->m_Last_run_time >= progress->m_Interval )
98 {
99 progress->m_Last_run_time = curtime;
100 return progress->m_Callback( aDLtotal, aDLnow, aULtotal, aULnow );
101 }
102
103 return CURLE_OK;
104}
105
106#else
107
108static int progressinfo( void* aProgress, double aDLtotal, double aDLnow, double aULtotal,
109 double aULnow )
110{
111 return xferinfo( aProgress, static_cast<curl_off_t>( aDLtotal ),
112 static_cast<curl_off_t>( aDLnow ), static_cast<curl_off_t>( aULtotal ),
113 static_cast<curl_off_t>( aULnow ) );
114}
115
116#endif
117
118
120 m_headers( nullptr ),
121 m_curlSharedLock( KICAD_CURL::Mutex() )
122{
123 m_CURL = curl_easy_init();
124
125 if( !m_CURL )
126 THROW_IO_ERROR( "Unable to initialize CURL session" );
127
128 curl_easy_setopt( m_CURL, CURLOPT_WRITEFUNCTION, write_callback );
129 curl_easy_setopt( m_CURL, CURLOPT_WRITEDATA, static_cast<void*>( &m_buffer ) );
130
131 // Only allow HTTP and HTTPS protocols
132#if LIBCURL_VERSION_NUM >= 0x075500 // version 7.85.0
133 curl_easy_setopt(m_CURL, CURLOPT_PROTOCOLS_STR, "http,https");
134#else
135 curl_easy_setopt( m_CURL, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS );
136#endif
137
138 // Set connection timeout to prevent long freezes when network is unreachable.
139 // Without this, CURL can wait up to 5 minutes for DNS resolution or connection
140 // establishment, which causes the UI to freeze on macOS when behind a proxy with
141 // restricted internet access.
142 curl_easy_setopt( m_CURL, CURLOPT_CONNECTTIMEOUT, 30L );
143
144#ifdef _WIN32
145 long sslOpts = CURLSSLOPT_NATIVE_CA;
146
147 std::optional<POLICY_CURL_SSL_REVOKE> policyState =
149
150 if( policyState )
151 {
152 if( *policyState == POLICY_CURL_SSL_REVOKE::BEST_EFFORT )
153 sslOpts |= CURLSSLOPT_REVOKE_BEST_EFFORT;
154 else if( *policyState == POLICY_CURL_SSL_REVOKE::NONE )
155 sslOpts |= CURLSSLOPT_NO_REVOKE;
156 }
157 else
158 {
159 // Some networks/countries may restrict access to revocation servers,
160 // so use best-effort policy if not configured
161 sslOpts |= CURLSSLOPT_REVOKE_BEST_EFFORT;
162 }
163
164 // We need this to use the Windows Certificate store
165 curl_easy_setopt( m_CURL, CURLOPT_SSL_OPTIONS, sslOpts );
166#endif
167
168 if( wxGetEnv( wxT( "KICAD_CURL_VERBOSE" ), nullptr ) )
169 {
170 // note: curl verbose will end up in stderr
171 curl_easy_setopt( m_CURL, CURLOPT_VERBOSE, 1L );
172 }
173
174 wxString application( wxS( "KiCad" ) );
175 wxString version( GetBuildVersion() );
176 wxString platform = wxS( "(" ) + wxGetOsDescription() + wxS( ";" ) + GetPlatformGetBitnessName();
177
178#if defined( KICAD_BUILD_ARCH_X64 )
179 platform << wxS( ";64-bit" );
180#elif defined( KICAD_BUILD_ARCH_X86 )
181 platform << wxS( ";32-bit" );
182#elif defined( KICAD_BUILD_ARCH_ARM )
183 platform << wxS( ";ARM 32-bit" );
184#elif defined( KICAD_BUILD_ARCH_ARM64 )
185 platform << wxS( ";ARM 64-bit" );
186#endif
187
188 platform << wxS( ")" );
189
190 wxString user_agent = wxS( "KiCad/" ) + version + wxS( " " ) + platform + wxS( " " ) + application;
191
192 user_agent << wxS( "/" ) << GetBuildDate();
193 setOption<const char*>( CURLOPT_USERAGENT, user_agent.ToStdString().c_str() );
194 setOption( CURLOPT_ACCEPT_ENCODING, "gzip,deflate" );
195}
196
197
199{
200 if( m_headers )
201 curl_slist_free_all( m_headers );
202
203 curl_easy_cleanup( m_CURL );
204}
205
206
208{
209 if( m_headers )
210 curl_easy_setopt( m_CURL, CURLOPT_HTTPHEADER, m_headers );
211
212 // bonus: retain worst case memory allocation, should re-use occur
213 m_buffer.clear();
214
215 return curl_easy_perform( m_CURL );
216}
217
218
219void KICAD_CURL_EASY::SetHeader( const std::string& aName, const std::string& aValue )
220{
221 std::string header = aName + ':' + aValue;
222 m_headers = curl_slist_append( m_headers, header.c_str() );
223}
224
225
226template <typename T>
227int KICAD_CURL_EASY::setOption( int aOption, T aArg )
228{
229 return curl_easy_setopt( m_CURL, static_cast<CURLoption>( aOption ), aArg );
230}
231
232
233const std::string KICAD_CURL_EASY::GetErrorText( int aCode )
234{
235 return curl_easy_strerror( static_cast<CURLcode>( aCode ) );
236}
237
238
239bool KICAD_CURL_EASY::SetUserAgent( const std::string& aAgent )
240{
241 if( setOption<const char*>( CURLOPT_USERAGENT, aAgent.c_str() ) == CURLE_OK )
242 return true;
243
244 return false;
245}
246
247
248bool KICAD_CURL_EASY::SetPostFields( const std::vector<std::pair<std::string, std::string>>& aFields )
249{
250 std::string postfields;
251
252 for( size_t i = 0; i < aFields.size(); i++ )
253 {
254 if( i > 0 )
255 postfields += "&";
256
257 postfields += Escape( aFields[i].first );
258 postfields += "=";
259 postfields += Escape( aFields[i].second );
260 }
261
262 if( setOption<const char*>( CURLOPT_COPYPOSTFIELDS, postfields.c_str() ) != CURLE_OK )
263 return false;
264
265 return true;
266}
267
268
269bool KICAD_CURL_EASY::SetPostFields( const std::string& aField )
270{
271 if( setOption<const char*>( CURLOPT_COPYPOSTFIELDS, aField.c_str() ) != CURLE_OK )
272 return false;
273
274 return true;
275}
276
277
278bool KICAD_CURL_EASY::SetURL( const std::string& aURL )
279{
280 if( setOption<const char*>( CURLOPT_URL, aURL.c_str() ) == CURLE_OK )
281 {
283
284 // Unfortunately on Windows land, proxies can be configured depending on destination url
285 // So we also check and set any proxy config here
287 {
288 curl_easy_setopt( m_CURL, CURLOPT_PROXY, static_cast<const char*>( cfg.host.c_str() ) );
289
290 if( !cfg.username.empty() )
291 {
292 curl_easy_setopt( m_CURL, CURLOPT_PROXYUSERNAME,
293 static_cast<const char*>( cfg.username.c_str() ) );
294 }
295
296 if( !cfg.password.empty() )
297 {
298 curl_easy_setopt( m_CURL, CURLOPT_PROXYPASSWORD,
299 static_cast<const char*>( cfg.password.c_str() ) );
300 }
301 }
302
303 return true;
304 }
305
306 return false;
307}
308
309
311{
312 if( setOption<long>( CURLOPT_FOLLOWLOCATION, ( aFollow ? 1 : 0 ) ) == CURLE_OK )
313 return true;
314
315 return false;
316}
317
318
319std::string KICAD_CURL_EASY::Escape( const std::string& aUrl )
320{
321 char* escaped = curl_easy_escape( m_CURL, aUrl.c_str(), aUrl.length() );
322
323 std::string ret( escaped );
324 curl_free( escaped );
325
326 return ret;
327}
328
329
330bool KICAD_CURL_EASY::SetTransferCallback( const TRANSFER_CALLBACK& aCallback, size_t aInterval )
331{
332 progress = std::make_unique<CURL_PROGRESS>( this, aCallback, static_cast<curl_off_t>( aInterval ) );
333
334#if LIBCURL_VERSION_NUM >= 0x072000 // 7.32.0
335 setOption( CURLOPT_XFERINFOFUNCTION, xferinfo );
336 setOption( CURLOPT_XFERINFODATA, progress.get() );
337#else
338 setOption( CURLOPT_PROGRESSFUNCTION, progressinfo );
339 setOption( CURLOPT_PROGRESSDATA, progress.get() );
340#endif
341
342 setOption( CURLOPT_NOPROGRESS, 0L );
343 return true;
344}
345
346
347bool KICAD_CURL_EASY::SetOutputStream( const std::ostream* aOutput )
348{
349 curl_easy_setopt( m_CURL, CURLOPT_WRITEFUNCTION, stream_write_callback );
350 curl_easy_setopt( m_CURL, CURLOPT_WRITEDATA, reinterpret_cast<const void*>( aOutput ) );
351 return true;
352}
353
354
355bool KICAD_CURL_EASY::SetConnectTimeout( long aTimeoutSecs )
356{
357 return setOption( CURLOPT_CONNECTTIMEOUT, aTimeoutSecs ) == CURLE_OK;
358}
359
360
361bool KICAD_CURL_EASY::SetTimeout( long aTimeoutSecs )
362{
363 return setOption( CURLOPT_TIMEOUT, aTimeoutSecs ) == CURLE_OK;
364}
365
366
367bool KICAD_CURL_EASY::SetStallTimeout( long aMinBytesPerSec, long aDurationSecs )
368{
369 bool ok = setOption( CURLOPT_LOW_SPEED_LIMIT, aMinBytesPerSec ) == CURLE_OK;
370 ok &= setOption( CURLOPT_LOW_SPEED_TIME, aDurationSecs ) == CURLE_OK;
371 return ok;
372}
373
374
375int KICAD_CURL_EASY::GetTransferTotal( uint64_t& aDownloadedBytes ) const
376{
377#if LIBCURL_VERSION_NUM >= 0x073700 // 7.55.0
378 curl_off_t dl;
379 int result = curl_easy_getinfo( m_CURL, CURLINFO_SIZE_DOWNLOAD_T, &dl );
380 aDownloadedBytes = static_cast<uint64_t>( dl );
381#else
382 double dl;
383 int result = curl_easy_getinfo( m_CURL, CURLINFO_SIZE_DOWNLOAD, &dl );
384 aDownloadedBytes = static_cast<uint64_t>( dl );
385#endif
386
387 return result;
388}
389
390
392{
393 long http_code = 0;
394 curl_easy_getinfo( m_CURL, CURLINFO_RESPONSE_CODE, &http_code );
395
396 return static_cast<int>( http_code );
397}
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.
bool SetStallTimeout(long aMinBytesPerSec, long aDurationSecs)
Detect stalled transfers without limiting overall transfer time.
int GetTransferTotal(uint64_t &aDownloadedBytes) const
int setOption(int aOption, T aArg)
Set a curl option, only supports single parameter curl options.
bool SetTimeout(long aTimeoutSecs)
Set the total operation timeout in seconds.
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:58
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.
std::optional< T > GetPolicyEnum(const wxString &aKey)
Definition policy.h:45
#define POLICY_KEY_REQUESTS_CURL_REVOKE
Definition policy_keys.h:28
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
std::vector< std::string > header
wxString result
Test unit parsing edge cases and error handling.