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 (C) 2015-2024 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#if LIBCURL_VERSION_NUM >= 0x072000 // 7.32.0
88
89static int xferinfo( void* aProgress, curl_off_t aDLtotal, curl_off_t aDLnow, curl_off_t aULtotal,
90 curl_off_t aULnow )
91{
93 return 1; // Should abort the operation
94
95 CURL_PROGRESS* progress = static_cast<CURL_PROGRESS*>( aProgress );
96 curl_off_t curtime = 0;
97
98 curl_easy_getinfo( progress->m_Curl->GetCurl(), CURLINFO_TOTAL_TIME, &curtime );
99
100 if( curtime - progress->m_Last_run_time >= progress->m_Interval )
101 {
102 progress->m_Last_run_time = curtime;
103 return progress->m_Callback( aDLtotal, aDLnow, aULtotal, aULnow );
104 }
105
106 return CURLE_OK;
107}
108
109#else
110
111static int progressinfo( void* aProgress, double aDLtotal, double aDLnow, double aULtotal, double aULnow )
112{
113 return xferinfo( aProgress, static_cast<curl_off_t>( aDLtotal ), static_cast<curl_off_t>( aDLnow ),
114 static_cast<curl_off_t>( aULtotal ), static_cast<curl_off_t>( aULnow ) );
115}
116
117#endif
118
119
121 m_headers( nullptr )
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#ifdef _WIN32
139 long sslOpts = CURLSSLOPT_NATIVE_CA;
140
141 POLICY_CURL_SSL_REVOKE policyState = KIPLATFORM::POLICY::GetPolicyEnum<POLICY_CURL_SSL_REVOKE>( POLICY_KEY_REQUESTS_CURL_REVOKE );
142 if( policyState == POLICY_CURL_SSL_REVOKE::BEST_EFFORT )
143 {
144 sslOpts |= CURLSSLOPT_REVOKE_BEST_EFFORT;
145 }
146 else if( policyState == POLICY_CURL_SSL_REVOKE::NONE )
147 {
148 sslOpts |= CURLSSLOPT_NO_REVOKE;
149 }
150
151 // We need this to use the Windows Certificate store
152 curl_easy_setopt( m_CURL, CURLOPT_SSL_OPTIONS, sslOpts );
153#endif
154
155 if( wxGetEnv( wxT( "KICAD_CURL_VERBOSE" ), nullptr ) )
156 {
157 // note: curl verbose will end up in stderr
158 curl_easy_setopt( m_CURL, CURLOPT_VERBOSE, 1L );
159 }
160
161 wxPlatformInfo platformInfo;
162 wxString application( wxS( "KiCad" ) );
163 wxString version( GetBuildVersion() );
164 wxString platform = wxS( "(" ) + wxGetOsDescription() + wxS( ";" ) + GetPlatformGetBitnessName();
165
166#if defined( KICAD_BUILD_ARCH_X64 )
167 platform << wxS( ";64-bit" );
168#elif defined( KICAD_BUILD_ARCH_X86 )
169 platform << wxS( ";32-bit" );
170#elif defined( KICAD_BUILD_ARCH_ARM )
171 platform << wxS( ";ARM 32-bit" );
172#elif defined( KICAD_BUILD_ARCH_ARM64 )
173 platform << wxS( ";ARM 64-bit" );
174#endif
175
176 platform << wxS( ")" );
177
178 wxString user_agent = wxS( "KiCad/" ) + version + wxS( " " ) + platform + wxS( " " ) + application;
179
180 user_agent << wxS( "/" ) << GetBuildDate();
181 setOption<const char*>( CURLOPT_USERAGENT, user_agent.ToStdString().c_str() );
182 setOption( CURLOPT_ACCEPT_ENCODING, "gzip,deflate" );
183}
184
185
187{
188 if( m_headers )
189 curl_slist_free_all( m_headers );
190
191 curl_easy_cleanup( m_CURL );
192}
193
194
196{
197 std::shared_lock lock( KICAD_CURL::Mutex(), std::try_to_lock );
198
199 // If can't lock, we should be in the process of tearing down.
200 if( !lock )
201 return CURLE_ABORTED_BY_CALLBACK;
202
203 if( m_headers )
204 curl_easy_setopt( m_CURL, CURLOPT_HTTPHEADER, m_headers );
205
206 // bonus: retain worst case memory allocation, should re-use occur
207 m_buffer.clear();
208
209 return curl_easy_perform( m_CURL );
210}
211
212
213void KICAD_CURL_EASY::SetHeader( const std::string& aName, const std::string& aValue )
214{
215 std::string header = aName + ':' + aValue;
216 m_headers = curl_slist_append( m_headers, header.c_str() );
217}
218
219
220template <typename T>
221int KICAD_CURL_EASY::setOption( int aOption, T aArg )
222{
223 return curl_easy_setopt( m_CURL, static_cast<CURLoption>( aOption ), aArg );
224}
225
226
227const std::string KICAD_CURL_EASY::GetErrorText( int aCode )
228{
229 return curl_easy_strerror( static_cast<CURLcode>( aCode ) );
230}
231
232
233bool KICAD_CURL_EASY::SetUserAgent( const std::string& aAgent )
234{
235 if( setOption<const char*>( CURLOPT_USERAGENT, aAgent.c_str() ) == CURLE_OK )
236 return true;
237
238 return false;
239}
240
241
243 const std::vector<std::pair<std::string, std::string>>& aFields )
244{
245 std::string postfields;
246
247 for( size_t i = 0; i < aFields.size(); i++ )
248 {
249 if( i > 0 )
250 postfields += "&";
251
252 postfields += Escape( aFields[i].first );
253 postfields += "=";
254 postfields += Escape( aFields[i].second );
255 }
256
257 if( setOption<const char*>( CURLOPT_COPYPOSTFIELDS, postfields.c_str() ) != CURLE_OK )
258 return false;
259
260 return true;
261}
262
263
264bool KICAD_CURL_EASY::SetPostFields( const std::string& aField )
265{
266 if( setOption<const char*>( CURLOPT_COPYPOSTFIELDS, aField.c_str() ) != CURLE_OK )
267 return false;
268
269 return true;
270}
271
272
273bool KICAD_CURL_EASY::SetURL( const std::string& aURL )
274{
275 if( setOption<const char*>( CURLOPT_URL, aURL.c_str() ) == CURLE_OK )
276 {
278
279 // Unfortunately on Windows land, proxies can be configured depending on destination url
280 // So we also check and set any proxy config here
282 {
283 curl_easy_setopt( m_CURL, CURLOPT_PROXY, static_cast<const char*>( cfg.host.c_str() ) );
284
285 if( !cfg.username.empty() )
286 {
287 curl_easy_setopt( m_CURL, CURLOPT_PROXYUSERNAME,
288 static_cast<const char*>( cfg.username.c_str() ) );
289 }
290
291 if( !cfg.password.empty() )
292 {
293 curl_easy_setopt( m_CURL, CURLOPT_PROXYPASSWORD,
294 static_cast<const char*>( cfg.password.c_str() ) );
295 }
296 }
297
298 return true;
299 }
300
301 return false;
302}
303
304
306{
307 if( setOption<long>( CURLOPT_FOLLOWLOCATION, ( aFollow ? 1 : 0 ) ) == CURLE_OK )
308 return true;
309
310 return false;
311}
312
313
314std::string KICAD_CURL_EASY::Escape( const std::string& aUrl )
315{
316 char* escaped = curl_easy_escape( m_CURL, aUrl.c_str(), aUrl.length() );
317
318 std::string ret( escaped );
319 curl_free( escaped );
320
321 return ret;
322}
323
324
325bool KICAD_CURL_EASY::SetTransferCallback( const TRANSFER_CALLBACK& aCallback, size_t aInterval )
326{
327 progress = std::make_unique<CURL_PROGRESS>( this, aCallback,
328 static_cast<curl_off_t>( aInterval ) );
329#if LIBCURL_VERSION_NUM >= 0x072000 // 7.32.0
330 setOption( CURLOPT_XFERINFOFUNCTION, xferinfo );
331 setOption( CURLOPT_XFERINFODATA, progress.get() );
332#else
333 setOption( CURLOPT_PROGRESSFUNCTION, progressinfo );
334 setOption( CURLOPT_PROGRESSDATA, progress.get() );
335#endif
336 setOption( CURLOPT_NOPROGRESS, 0L );
337 return true;
338}
339
340
341bool KICAD_CURL_EASY::SetOutputStream( const std::ostream* aOutput )
342{
343 curl_easy_setopt( m_CURL, CURLOPT_WRITEFUNCTION, stream_write_callback );
344 curl_easy_setopt( m_CURL, CURLOPT_WRITEDATA, reinterpret_cast<const void*>( aOutput ) );
345 return true;
346}
347
348
349int KICAD_CURL_EASY::GetTransferTotal( uint64_t& aDownloadedBytes ) const
350{
351#if LIBCURL_VERSION_NUM >= 0x073700 // 7.55.0
352 curl_off_t dl;
353 int result = curl_easy_getinfo( m_CURL, CURLINFO_SIZE_DOWNLOAD_T, &dl );
354 aDownloadedBytes = static_cast<uint64_t>( dl );
355#else
356 double dl;
357 int result = curl_easy_getinfo( m_CURL, CURLINFO_SIZE_DOWNLOAD, &dl );
358 aDownloadedBytes = static_cast<uint64_t>( dl );
359#endif
360 return result;
361}
362
363
365{
366 long http_code = 0;
367 curl_easy_getinfo( m_CURL, CURLINFO_RESPONSE_CODE, &http_code );
368
369 return static_cast<int>( http_code );
370}
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.
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.
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.
static bool IsShuttingDown()
Returns true if all curl operations should terminate.
Definition: kicad_curl.cpp:65
static std::shared_mutex & Mutex()
Returns the mutex for shared locking when performing curl operations.
Definition: kicad_curl.cpp:59
#define THROW_IO_ERROR(msg)
Definition: ki_exception.h:39
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.
#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