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
151 std::optional<POLICY_CURL_SSL_REVOKE> policyState =
153
154 if( policyState )
155 {
156 if( *policyState == POLICY_CURL_SSL_REVOKE::BEST_EFFORT )
157 sslOpts |= CURLSSLOPT_REVOKE_BEST_EFFORT;
158 else if( *policyState == POLICY_CURL_SSL_REVOKE::NONE )
159 sslOpts |= CURLSSLOPT_NO_REVOKE;
160 }
161 else
162 {
163 // Some networks/countries may restrict access to revocation servers,
164 // so use best-effort policy if not configured
165 sslOpts |= CURLSSLOPT_REVOKE_BEST_EFFORT;
166 }
167
168 // We need this to use the Windows Certificate store
169 curl_easy_setopt( m_CURL, CURLOPT_SSL_OPTIONS, sslOpts );
170#endif
171
172 if( wxGetEnv( wxT( "KICAD_CURL_VERBOSE" ), nullptr ) )
173 {
174 // note: curl verbose will end up in stderr
175 curl_easy_setopt( m_CURL, CURLOPT_VERBOSE, 1L );
176 }
177
178 wxString application( wxS( "KiCad" ) );
179 wxString version( GetBuildVersion() );
180 wxString platform = wxS( "(" ) + wxGetOsDescription() + wxS( ";" ) + GetPlatformGetBitnessName();
181
182#if defined( KICAD_BUILD_ARCH_X64 )
183 platform << wxS( ";64-bit" );
184#elif defined( KICAD_BUILD_ARCH_X86 )
185 platform << wxS( ";32-bit" );
186#elif defined( KICAD_BUILD_ARCH_ARM )
187 platform << wxS( ";ARM 32-bit" );
188#elif defined( KICAD_BUILD_ARCH_ARM64 )
189 platform << wxS( ";ARM 64-bit" );
190#endif
191
192 platform << wxS( ")" );
193
194 wxString user_agent = wxS( "KiCad/" ) + version + wxS( " " ) + platform + wxS( " " ) + application;
195
196 user_agent << wxS( "/" ) << GetBuildDate();
197 setOption<const char*>( CURLOPT_USERAGENT, user_agent.ToStdString().c_str() );
198 setOption( CURLOPT_ACCEPT_ENCODING, "gzip,deflate" );
199}
200
201
203{
204 if( m_headers )
205 curl_slist_free_all( m_headers );
206
207 curl_easy_cleanup( m_CURL );
208}
209
210
212{
213 if( m_headers )
214 curl_easy_setopt( m_CURL, CURLOPT_HTTPHEADER, m_headers );
215
216 // bonus: retain worst case memory allocation, should re-use occur
217 m_buffer.clear();
218
219 return curl_easy_perform( m_CURL );
220}
221
222
223void KICAD_CURL_EASY::SetHeader( const std::string& aName, const std::string& aValue )
224{
225 std::string header = aName + ':' + aValue;
226 m_headers = curl_slist_append( m_headers, header.c_str() );
227}
228
229
230template <typename T>
231int KICAD_CURL_EASY::setOption( int aOption, T aArg )
232{
233 return curl_easy_setopt( m_CURL, static_cast<CURLoption>( aOption ), aArg );
234}
235
236
237const std::string KICAD_CURL_EASY::GetErrorText( int aCode )
238{
239 return curl_easy_strerror( static_cast<CURLcode>( aCode ) );
240}
241
242
243bool KICAD_CURL_EASY::SetUserAgent( const std::string& aAgent )
244{
245 if( setOption<const char*>( CURLOPT_USERAGENT, aAgent.c_str() ) == CURLE_OK )
246 return true;
247
248 return false;
249}
250
251
252bool KICAD_CURL_EASY::SetPostFields( const std::vector<std::pair<std::string, std::string>>& aFields )
253{
254 std::string postfields;
255
256 for( size_t i = 0; i < aFields.size(); i++ )
257 {
258 if( i > 0 )
259 postfields += "&";
260
261 postfields += Escape( aFields[i].first );
262 postfields += "=";
263 postfields += Escape( aFields[i].second );
264 }
265
266 if( setOption<const char*>( CURLOPT_COPYPOSTFIELDS, postfields.c_str() ) != CURLE_OK )
267 return false;
268
269 return true;
270}
271
272
273bool KICAD_CURL_EASY::SetPostFields( const std::string& aField )
274{
275 if( setOption<const char*>( CURLOPT_COPYPOSTFIELDS, aField.c_str() ) != CURLE_OK )
276 return false;
277
278 return true;
279}
280
281
282bool KICAD_CURL_EASY::SetURL( const std::string& aURL )
283{
284 if( setOption<const char*>( CURLOPT_URL, aURL.c_str() ) == CURLE_OK )
285 {
287
288 // Unfortunately on Windows land, proxies can be configured depending on destination url
289 // So we also check and set any proxy config here
291 {
292 curl_easy_setopt( m_CURL, CURLOPT_PROXY, static_cast<const char*>( cfg.host.c_str() ) );
293
294 if( !cfg.username.empty() )
295 {
296 curl_easy_setopt( m_CURL, CURLOPT_PROXYUSERNAME,
297 static_cast<const char*>( cfg.username.c_str() ) );
298 }
299
300 if( !cfg.password.empty() )
301 {
302 curl_easy_setopt( m_CURL, CURLOPT_PROXYPASSWORD,
303 static_cast<const char*>( cfg.password.c_str() ) );
304 }
305 }
306
307 return true;
308 }
309
310 return false;
311}
312
313
315{
316 if( setOption<long>( CURLOPT_FOLLOWLOCATION, ( aFollow ? 1 : 0 ) ) == CURLE_OK )
317 return true;
318
319 return false;
320}
321
322
323std::string KICAD_CURL_EASY::Escape( const std::string& aUrl )
324{
325 char* escaped = curl_easy_escape( m_CURL, aUrl.c_str(), aUrl.length() );
326
327 std::string ret( escaped );
328 curl_free( escaped );
329
330 return ret;
331}
332
333
334bool KICAD_CURL_EASY::SetTransferCallback( const TRANSFER_CALLBACK& aCallback, size_t aInterval )
335{
336 progress = std::make_unique<CURL_PROGRESS>( this, aCallback, static_cast<curl_off_t>( aInterval ) );
337
338#if LIBCURL_VERSION_NUM >= 0x072000 // 7.32.0
339 setOption( CURLOPT_XFERINFOFUNCTION, xferinfo );
340 setOption( CURLOPT_XFERINFODATA, progress.get() );
341#else
342 setOption( CURLOPT_PROGRESSFUNCTION, progressinfo );
343 setOption( CURLOPT_PROGRESSDATA, progress.get() );
344#endif
345
346 setOption( CURLOPT_NOPROGRESS, 0L );
347 return true;
348}
349
350
351bool KICAD_CURL_EASY::SetOutputStream( const std::ostream* aOutput )
352{
353 curl_easy_setopt( m_CURL, CURLOPT_WRITEFUNCTION, stream_write_callback );
354 curl_easy_setopt( m_CURL, CURLOPT_WRITEDATA, reinterpret_cast<const void*>( aOutput ) );
355 return true;
356}
357
358
359bool KICAD_CURL_EASY::SetConnectTimeout( long aTimeoutSecs )
360{
361 return setOption( CURLOPT_CONNECTTIMEOUT, aTimeoutSecs ) == CURLE_OK;
362}
363
364
365int KICAD_CURL_EASY::GetTransferTotal( uint64_t& aDownloadedBytes ) const
366{
367#if LIBCURL_VERSION_NUM >= 0x073700 // 7.55.0
368 curl_off_t dl;
369 int result = curl_easy_getinfo( m_CURL, CURLINFO_SIZE_DOWNLOAD_T, &dl );
370 aDownloadedBytes = static_cast<uint64_t>( dl );
371#else
372 double dl;
373 int result = curl_easy_getinfo( m_CURL, CURLINFO_SIZE_DOWNLOAD, &dl );
374 aDownloadedBytes = static_cast<uint64_t>( dl );
375#endif
376
377 return result;
378}
379
380
382{
383 long http_code = 0;
384 curl_easy_getinfo( m_CURL, CURLINFO_RESPONSE_CODE, &http_code );
385
386 return static_cast<int>( http_code );
387}
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.
std::optional< T > GetPolicyEnum(const wxString &aKey)
Definition policy.h:45
#define POLICY_KEY_REQUESTS_CURL_REVOKE
Definition policy_keys.h:32
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.