KiCad PCB EDA Suite
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-2021 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>
29 #include <kicad_curl/kicad_curl.h>
31 
32 #include <cstdarg>
33 #include <cstddef>
34 #include <exception>
35 #include <sstream>
36 #include <wx/app.h>
37 
38 #include <build_version.h>
39 #include <ki_exception.h> // THROW_IO_ERROR
40 #include <kiplatform/app.h>
41 #include <kiplatform/environment.h>
42 #include <pgm_base.h>
43 
44 
46 {
49  curl_off_t last_run_time;
50  curl_off_t interval;
52  curl( c ), callback( cb ), last_run_time( 0 ), interval( i )
53  {
54  }
55 };
56 
57 
58 static size_t write_callback( void* contents, size_t size, size_t nmemb, void* userp )
59 {
60  size_t realsize = size * nmemb;
61 
62  std::string* p = static_cast<std::string*>( userp );
63 
64  p->append( static_cast<const char*>( contents ), realsize );
65 
66  return realsize;
67 }
68 
69 
70 static size_t stream_write_callback( void* contents, size_t size, size_t nmemb, void* userp )
71 {
72  size_t realsize = size * nmemb;
73 
74  std::ostream* p = (std::ostream*) userp;
75 
76  p->write( static_cast<const char*>( contents ), realsize );
77 
78  return realsize;
79 }
80 
81 
82 static int xferinfo( void* p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal,
83  curl_off_t ulnow )
84 {
85  CURL_PROGRESS* progress = static_cast<CURL_PROGRESS*>( p );
86  curl_off_t curtime = 0;
87 
88  curl_easy_getinfo( progress->curl->GetCurl(), CURLINFO_TOTAL_TIME, &curtime );
89 
90  if( curtime - progress->last_run_time >= progress->interval )
91  {
92  progress->last_run_time = curtime;
93  return progress->callback( dltotal, dlnow, ultotal, ulnow );
94  }
95 
96  return CURLE_OK;
97 }
98 
99 
100 static int progressinfo( void* p, double dltotal, double dlnow, double ultotal, double ulnow )
101 {
102  return xferinfo( p, static_cast<curl_off_t>( dltotal ), static_cast<curl_off_t>( dlnow ),
103  static_cast<curl_off_t>( ultotal ), static_cast<curl_off_t>( ulnow ) );
104 }
105 
106 
107 KICAD_CURL_EASY::KICAD_CURL_EASY() : m_headers( nullptr )
108 {
109  // Call KICAD_CURL::Init() from in here every time, but only the first time
110  // will incur any overhead. This strategy ensures that libcurl is never loaded
111  // unless it is needed.
112 
114 
115  m_CURL = curl_easy_init();
116 
117  if( !m_CURL )
118  {
119  THROW_IO_ERROR( wxT( "Unable to initialize CURL session" ) );
120  }
121 
122  curl_easy_setopt( m_CURL, CURLOPT_WRITEFUNCTION, write_callback );
123  curl_easy_setopt( m_CURL, CURLOPT_WRITEDATA, (void*) &m_buffer );
124 
125  // Only allow HTTP and HTTPS protocols
126  curl_easy_setopt( m_CURL, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS );
127 
128  #ifdef _WIN32
129  // We need to this to allow using the Windows Certificate store
130  curl_easy_setopt( m_CURL, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA );
131  #endif
132 
133  wxPlatformInfo platformInfo;
134  wxString application( Pgm().App().GetAppName() );
135  wxString version( GetBuildVersion() );
136  wxString platform = wxT( "(" ) + wxGetOsDescription() + wxT( ";" ) + GetPlatformGetBitnessName();
137 
138 #if defined( KICAD_BUILD_ARCH_X64 )
139  platform << wxT( ";64-bit" );
140 #elif defined( KICAD_BUILD_ARCH_X86 )
141  platform << wxT( ";32-bit" );
142 #elif defined( KICAD_BUILD_ARCH_ARM )
143  platform << wxT( ";ARM 32-bit" );
144 #elif defined( KICAD_BUILD_ARCH_ARM64 )
145  platform << wxT( ";ARM 64-bit" );
146 #endif
147 
148  platform << wxT( ")" );
149 
150  wxString user_agent = wxT( "KiCad/" ) + version + wxT( " " ) + platform + wxT( " " ) + application;
151 
152  user_agent << wxT( "/" ) << GetBuildDate();
153  setOption<const char*>( CURLOPT_USERAGENT, user_agent.ToStdString().c_str() );
154  setOption( CURLOPT_ACCEPT_ENCODING, "gzip,deflate" );
155 }
156 
157 
159 {
160  if( m_headers )
161  curl_slist_free_all( m_headers );
162 
163  curl_easy_cleanup( m_CURL );
164 }
165 
166 
168 {
169  if( m_headers )
170  {
171  curl_easy_setopt( m_CURL, CURLOPT_HTTPHEADER, m_headers );
172  }
173 
174  // bonus: retain worst case memory allocation, should re-use occur
175  m_buffer.clear();
176 
177  CURLcode res = curl_easy_perform( m_CURL );
178 
179  return res;
180 }
181 
182 
183 void KICAD_CURL_EASY::SetHeader( const std::string& aName, const std::string& aValue )
184 {
185  std::string header = aName + ':' + aValue;
186  m_headers = curl_slist_append( m_headers, header.c_str() );
187 }
188 
189 
190 template <typename T>
191 int KICAD_CURL_EASY::setOption( int aOption, T aArg )
192 {
193  return curl_easy_setopt( m_CURL, static_cast<CURLoption>( aOption ), aArg );
194 }
195 
196 
197 const std::string KICAD_CURL_EASY::GetErrorText( int aCode )
198 {
199  return curl_easy_strerror( static_cast<CURLcode>( aCode ) );
200 }
201 
202 
203 bool KICAD_CURL_EASY::SetUserAgent( const std::string& aAgent )
204 {
205  if( setOption<const char*>( CURLOPT_USERAGENT, aAgent.c_str() ) == CURLE_OK )
206  {
207  return true;
208  }
209 
210  return false;
211 }
212 
213 
214 bool KICAD_CURL_EASY::SetURL( const std::string& aURL )
215 {
216  if( setOption<const char*>( CURLOPT_URL, aURL.c_str() ) == CURLE_OK )
217  {
219 
220  // Unforunately on Windows land, proxies can be configured depending on destination url
221  // So we also check and set any proxy config here
222  if( KIPLATFORM::ENV::GetSystemProxyConfig( aURL, cfg ) )
223  {
224  curl_easy_setopt( m_CURL, CURLOPT_PROXY, static_cast<const char*>( cfg.host.c_str() ) );
225  if( !cfg.username.empty() )
226  {
227  curl_easy_setopt( m_CURL, CURLOPT_PROXYUSERNAME,
228  static_cast<const char*>( cfg.username.c_str() ) );
229  }
230 
231  if( !cfg.password.empty() )
232  {
233  curl_easy_setopt( m_CURL, CURLOPT_PROXYPASSWORD,
234  static_cast<const char*>( cfg.password.c_str() ) );
235  }
236  }
237 
238  return true;
239  }
240 
241  return false;
242 }
243 
244 
246 {
247  if( setOption<long>( CURLOPT_FOLLOWLOCATION, ( aFollow ? 1 : 0 ) ) == CURLE_OK )
248  {
249  return true;
250  }
251 
252  return false;
253 }
254 
255 
256 std::string KICAD_CURL_EASY::Escape( const std::string& aUrl )
257 {
258  char* escaped = curl_easy_escape( m_CURL, aUrl.c_str(), aUrl.length() );
259 
260  std::string ret( escaped );
261  curl_free( escaped );
262 
263  return ret;
264 }
265 
266 
267 bool KICAD_CURL_EASY::SetTransferCallback( const TRANSFER_CALLBACK& aCallback, size_t aInterval )
268 {
269  progress = std::make_unique<CURL_PROGRESS>( this, aCallback,
270  static_cast<curl_off_t>( aInterval ) );
271 #ifdef CURLOPT_XFERINFOFUNCTION
272  setOption( CURLOPT_XFERINFOFUNCTION, xferinfo );
273  setOption( CURLOPT_XFERINFODATA, progress.get() );
274 #else
275  setOption( CURLOPT_PROGRESSFUNCTION, progressinfo );
276  setOption( CURLOPT_PROGRESSDATA, progress.get() );
277 #endif
278  setOption( CURLOPT_NOPROGRESS, 0L );
279  return true;
280 }
281 
282 
283 bool KICAD_CURL_EASY::SetOutputStream( const std::ostream* aOutput )
284 {
285  curl_easy_setopt( m_CURL, CURLOPT_WRITEFUNCTION, stream_write_callback );
286  curl_easy_setopt( m_CURL, CURLOPT_WRITEDATA, reinterpret_cast<const void*>( aOutput ) );
287  return true;
288 }
289 
290 
291 int KICAD_CURL_EASY::GetTransferTotal( uint64_t& aDownloadedBytes ) const
292 {
293 #ifdef CURLINFO_SIZE_DOWNLOAD_T
294  curl_off_t dl;
295  int result = curl_easy_getinfo( m_CURL, CURLINFO_SIZE_DOWNLOAD_T, &dl );
296  aDownloadedBytes = static_cast<uint64_t>( dl );
297 #else
298  double dl;
299  int result = curl_easy_getinfo( m_CURL, CURLINFO_SIZE_DOWNLOAD, &dl );
300  aDownloadedBytes = static_cast<uint64_t>( dl );
301 #endif
302  return result;
303 }
bool SetUserAgent(const std::string &aAgent)
Set the request user agent.
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp)
bool SetFollowRedirects(bool aFollow)
Enable the following of HTTP(s) and other redirects, by default curl does not follow redirects.
static int progressinfo(void *p, double dltotal, double dlnow, double ultotal, double ulnow)
static void Init()
Call curl_global_init for the application.
Definition: kicad_curl.cpp:50
int setOption(int aOption, T aArg)
Set a curl option, only supports single parameter curl options.
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:106
std::unique_ptr< CURL_PROGRESS > progress
int GetTransferTotal(uint64_t &aDownloadedBytes) const
bool GetSystemProxyConfig(const wxString &aURL, PROXY_CONFIG &aCfg)
Retrieves platform level proxying requirements to reach the given url.
curl_slist * m_headers
wxString GetBuildVersion()
Get the full KiCad version string.
wxString GetPlatformGetBitnessName()
std::string Escape(const std::string &aUrl)
Escapes a string for use as a URL.
std::function< int(size_t, size_t, size_t, size_t)> TRANSFER_CALLBACK
Wrapper interface around the curl_easy API/.
CURL_PROGRESS(KICAD_CURL_EASY *c, TRANSFER_CALLBACK cb, curl_off_t i)
int Perform()
Equivalent to curl_easy_perform.
const std::string GetErrorText(int aCode)
Fetch CURL's "friendly" error string for a given error code.
curl_off_t last_run_time
bool SetTransferCallback(const TRANSFER_CALLBACK &aCallback, size_t aInterval)
bool SetURL(const std::string &aURL)
Set the request URL.
std::string m_buffer
wxString GetBuildDate()
Get the build date as a string.
see class PGM_BASE
TRANSFER_CALLBACK callback
curl_off_t interval
bool SetOutputStream(const std::ostream *aOutput)
KICAD_CURL_EASY * curl
static size_t stream_write_callback(void *contents, size_t size, size_t nmemb, void *userp)
void SetHeader(const std::string &aName, const std::string &aValue)
Set an arbitrary header for the HTTP(s) request.
#define THROW_IO_ERROR(msg)
Definition: ki_exception.h:38
static int xferinfo(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)