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 <mark.roszko@gmail.com>
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 <pgm_base.h>
42 
43 
45 {
48  curl_off_t last_run_time;
49  curl_off_t interval;
51  curl( c ), callback( cb ), last_run_time( 0 ), interval( i )
52  {
53  }
54 };
55 
56 
57 static size_t write_callback( void* contents, size_t size, size_t nmemb, void* userp )
58 {
59  size_t realsize = size * nmemb;
60 
61  std::string* p = (std::string*) userp;
62 
63  p->append( (const char*) contents, realsize );
64 
65  return realsize;
66 }
67 
68 
69 static size_t stream_write_callback( void* contents, size_t size, size_t nmemb, void* userp )
70 {
71  size_t realsize = size * nmemb;
72 
73  std::ostream* p = (std::ostream*) userp;
74 
75  p->write( (const char*) contents, realsize );
76 
77  return realsize;
78 }
79 
80 
81 static int xferinfo( void* p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal,
82  curl_off_t ulnow )
83 {
84  CURL_PROGRESS* progress = (CURL_PROGRESS*) p;
85  curl_off_t curtime = 0;
86 
87  curl_easy_getinfo( progress->curl->GetCurl(), CURLINFO_TOTAL_TIME, &curtime );
88 
89  if( curtime - progress->last_run_time >= progress->interval )
90  {
91  progress->last_run_time = curtime;
92  return progress->callback( dltotal, dlnow, ultotal, ulnow );
93  }
94 
95  return CURLE_OK;
96 }
97 
98 
99 KICAD_CURL_EASY::KICAD_CURL_EASY() : m_headers( nullptr )
100 {
101  // Call KICAD_CURL::Init() from in here every time, but only the first time
102  // will incur any overhead. This strategy ensures that libcurl is never loaded
103  // unless it is needed.
104 
106 
107  m_CURL = curl_easy_init();
108 
109  if( !m_CURL )
110  {
111  THROW_IO_ERROR( "Unable to initialize CURL session" );
112  }
113 
114  curl_easy_setopt( m_CURL, CURLOPT_WRITEFUNCTION, write_callback );
115  curl_easy_setopt( m_CURL, CURLOPT_WRITEDATA, (void*) &m_buffer );
116 
117  // Only allow HTTP and HTTPS protocols
118  curl_easy_setopt( m_CURL, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS );
119 
120  wxPlatformInfo platformInfo;
121  wxString application( Pgm().App().GetAppName() );
122  wxString version( GetBuildVersion() );
123  wxString platform = "(" + wxGetOsDescription() + ";" + platformInfo.GetArchName();
124 
125 #if defined( KICAD_BUILD_ARCH_X64 )
126  platform << ";64-bit";
127 #elif defined( KICAD_BUILD_ARCH_X86 )
128  platform << ";32-bit";
129 #elif defined( KICAD_BUILD_ARCH_ARM )
130  platform << ";ARM 32-bit";
131 #elif defined( KICAD_BUILD_ARCH_ARM64 )
132  platform << ";ARM 64-bit";
133 #endif
134 
135  platform << ")";
136 
137  wxString user_agent = "KiCad/" + version + " " + platform + " " + application;
138 
139  user_agent << "/" << GetBuildDate();
140  setOption<const char*>( CURLOPT_USERAGENT, user_agent.ToStdString().c_str() );
141  setOption( CURLOPT_ACCEPT_ENCODING, "gzip,deflate" );
142 }
143 
144 
146 {
147  if( m_headers )
148  curl_slist_free_all( m_headers );
149 
150  curl_easy_cleanup( m_CURL );
151 }
152 
153 
155 {
156  if( m_headers )
157  {
158  curl_easy_setopt( m_CURL, CURLOPT_HTTPHEADER, m_headers );
159  }
160 
161  // bonus: retain worst case memory allocation, should re-use occur
162  m_buffer.clear();
163 
164  CURLcode res = curl_easy_perform( m_CURL );
165 
166  return res;
167 }
168 
169 
170 void KICAD_CURL_EASY::SetHeader( const std::string& aName, const std::string& aValue )
171 {
172  std::string header = aName + ':' + aValue;
173  m_headers = curl_slist_append( m_headers, header.c_str() );
174 }
175 
176 
177 template <typename T>
178 int KICAD_CURL_EASY::setOption( int aOption, T aArg )
179 {
180  return curl_easy_setopt( m_CURL, (CURLoption) aOption, aArg );
181 }
182 
183 
184 const std::string KICAD_CURL_EASY::GetErrorText( int aCode )
185 {
186  return curl_easy_strerror( (CURLcode) aCode );
187 }
188 
189 
190 bool KICAD_CURL_EASY::SetUserAgent( const std::string& aAgent )
191 {
192  if( setOption<const char*>( CURLOPT_USERAGENT, aAgent.c_str() ) == CURLE_OK )
193  {
194  return true;
195  }
196 
197  return false;
198 }
199 
200 
201 bool KICAD_CURL_EASY::SetURL( const std::string& aURL )
202 {
203  if( setOption<const char*>( CURLOPT_URL, aURL.c_str() ) == CURLE_OK )
204  {
205  return true;
206  }
207 
208  return false;
209 }
210 
211 
213 {
214  if( setOption<long>( CURLOPT_FOLLOWLOCATION, ( aFollow ? 1 : 0 ) ) == CURLE_OK )
215  {
216  return true;
217  }
218 
219  return false;
220 }
221 
222 
223 std::string KICAD_CURL_EASY::Escape( const std::string& aUrl )
224 {
225  char* escaped = curl_easy_escape( m_CURL, aUrl.c_str(), aUrl.length() );
226 
227  std::string ret( escaped );
228  curl_free( escaped );
229 
230  return ret;
231 }
232 
233 
234 bool KICAD_CURL_EASY::SetTransferCallback( const TRANSFER_CALLBACK& aCallback, size_t aInterval )
235 {
236  progress = std::make_unique<CURL_PROGRESS>( this, aCallback, (curl_off_t) aInterval );
237  setOption( CURLOPT_XFERINFOFUNCTION, xferinfo );
238  setOption( CURLOPT_XFERINFODATA, progress.get() );
239  setOption( CURLOPT_NOPROGRESS, 0L );
240  return true;
241 }
242 
243 
244 bool KICAD_CURL_EASY::SetOutputStream( const std::ostream* aOutput )
245 {
246  curl_easy_setopt( m_CURL, CURLOPT_WRITEFUNCTION, stream_write_callback );
247  curl_easy_setopt( m_CURL, CURLOPT_WRITEDATA, (void*) aOutput );
248  return true;
249 }
250 
251 
252 int KICAD_CURL_EASY::GetTransferTotal( uint64_t& aDownloadedBytes ) const
253 {
254  curl_off_t dl;
255  int result = curl_easy_getinfo( m_CURL, CURLINFO_SIZE_DOWNLOAD_T, &dl );
256  aDownloadedBytes = (uint64_t) dl;
257  return result;
258 }
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 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
curl_slist * m_headers
wxString GetBuildVersion()
Get the full KiCad version string.
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)