KiCad PCB EDA Suite
Loading...
Searching...
No Matches
http_lib_connection.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) 2023 Andre F. K. Iwers <[email protected]>
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <wx/log.h>
21#include <fmt/core.h>
22#include <wx/translation.h>
23
24#include <boost/algorithm/string.hpp>
25#include <nlohmann/json.hpp>
26#include <wx/base64.h>
27
29#include <curl/curl.h>
30
32
33const char* const traceHTTPLib = "KICAD_HTTP_LIB";
34
35
36HTTP_LIB_CONNECTION::HTTP_LIB_CONNECTION( const HTTP_LIB_SOURCE& aSource, bool aTestConnectionNow )
37{
38 m_rootURL = aSource.root_url;
39 m_token = aSource.token;
40 m_sourceType = aSource.type;
41
42 if( aTestConnectionNow )
43 {
45 }
46}
47
48
50{
51 // Do nothing
52}
53
54
56{
57 m_endpointValid = false;
58 std::string res = "";
59
60 std::unique_ptr<KICAD_CURL_EASY> curl = createCurlEasyObject();
61 curl->SetURL( m_rootURL );
62
63 try
64 {
65 curl->Perform();
66
67 res = curl->GetBuffer();
68
69 if( !checkServerResponse( curl ) )
70 return false;
71
72 if( res.length() == 0 )
73 {
74 m_lastError += wxString::Format( _( "KiCad received an empty response!" ) + "\n" );
75 }
76 else
77 {
78 nlohmann::json response = nlohmann::json::parse( res );
79
80 // Check that the endpoints exist, if not fail.
81 if( !response.at( http_endpoint_categories ).empty()
82 && !response.at( http_endpoint_parts ).empty() )
83 {
84 m_endpointValid = true;
85 }
86 }
87 }
88 catch( const std::exception& e )
89 {
90 m_lastError += wxString::Format( _( "Error: %s" ) + "\n" + _( "API Response: %s" ) + "\n",
91 e.what(), res );
92
93 wxLogTrace( traceHTTPLib,
94 wxT( "ValidateHTTPLibraryEndpoints: Exception occurred while testing the API "
95 "connection: %s" ),
97
98 m_endpointValid = false;
99 }
100
101 if( m_endpointValid )
102 {
104 }
105
106 return m_endpointValid;
107}
108
109
111{
112 return m_endpointValid;
113}
114
115
117{
118 if( !IsValidEndpoint() )
119 {
120 wxLogTrace( traceHTTPLib, wxT( "syncCategories: without valid connection!" ) );
121 return false;
122 }
123
124 std::string res = "";
125
126 std::unique_ptr<KICAD_CURL_EASY> curl = createCurlEasyObject();
127 curl->SetURL( m_rootURL + http_endpoint_categories + ".json" );
128
129 try
130 {
131 curl->Perform();
132
133 res = curl->GetBuffer();
134
135 if( !checkServerResponse( curl ) )
136 {
137 return false;
138 }
139
140 nlohmann::json response = nlohmann::json::parse( res );
141
142 // collect the categories in vector
143 for( const auto& item : response.items() )
144 {
145 HTTP_LIB_CATEGORY category;
146
147 category.id = item.value()["id"].get<std::string>();
148 category.name = item.value()["name"].get<std::string>();
149
150 m_categories.push_back( category );
151 }
152 }
153 catch( const std::exception& e )
154 {
155 m_lastError += wxString::Format( _( "Error: %s" ) + "\n" + _( "API Response: %s" ) + "\n",
156 e.what(), res );
157
158 wxLogTrace( traceHTTPLib,
159 wxT( "syncCategories: Exception occurred while syncing categories: %s" ),
160 m_lastError );
161
162 m_categories.clear();
163
164 return false;
165 }
166
167 return true;
168}
169
170
171bool HTTP_LIB_CONNECTION::SelectOne( const std::string& aPartID, HTTP_LIB_PART& aFetchedPart )
172{
173 if( !IsValidEndpoint() )
174 {
175 wxLogTrace( traceHTTPLib, wxT( "SelectOne: without valid connection!" ) );
176 return false;
177 }
178
179 // if the same part is selected again, use cached part
180 // instead to minimise http requests.
181 if( m_cached_part.id == aPartID )
182 {
183 aFetchedPart = m_cached_part;
184 return true;
185 }
186
187 std::string res = "";
188
189 std::unique_ptr<KICAD_CURL_EASY> curl = createCurlEasyObject();
190 curl->SetURL( m_rootURL + fmt::format( http_endpoint_parts + "/{}.json", aPartID ) );
191
192 try
193 {
194 curl->Perform();
195
196 res = curl->GetBuffer();
197
198 if( !checkServerResponse( curl ) )
199 {
200 return false;
201 }
202
203 nlohmann::json response = nlohmann::json::parse( res );
204 std::string key = "";
205 std::string value = "";
206
207 // the id used to identify the part, the name is needed to show a human-readable
208 // part descirption to the user inside the symbol chooser dialog
209 aFetchedPart.id = response.at( "id" );
210
211 // API might not want to return an optional name.
212 if( response.contains( "name" ) )
213 {
214 aFetchedPart.name = response.at( "name" );
215 }
216 else
217 {
218 aFetchedPart.name = aFetchedPart.id;
219 }
220
221 aFetchedPart.symbolIdStr = response.at( "symbolIdStr" );
222
223 // initially assume no exclusion
224 std::string exclude;
225
226 if( response.contains( "exclude_from_bom" ) )
227 {
228 // if key value doesn't exists default to false
229 exclude = response.at( "exclude_from_bom" );
230 aFetchedPart.exclude_from_bom = boolFromString( exclude, false );
231 }
232
233 // initially assume no exclusion
234 if( response.contains( "exclude_from_board" ) )
235 {
236 // if key value doesn't exists default to false
237 exclude = response.at( "exclude_from_board" );
238 aFetchedPart.exclude_from_board = boolFromString( exclude, false );
239 }
240
241 // initially assume no exclusion
242 if( response.contains( "exclude_from_sim" ) )
243 {
244 // if key value doesn't exists default to false
245 exclude = response.at( "exclude_from_sim" );
246 aFetchedPart.exclude_from_sim = boolFromString( exclude, false );
247 }
248
249 // Extract available fields
250 for( const auto& field : response.at( "fields" ).items() )
251 {
252 bool visible = true;
253
254 // name of the field
255 key = field.key();
256
257 // this is a dict
258 auto& properties = field.value();
259
260 value = properties.at( "value" );
261
262 // check if user wants to display field in schematic
263 if( properties.contains( "visible" ) )
264 {
265 std::string vis = properties.at( "visible" );
266 visible = boolFromString( vis, true );
267 }
268
269 // Add field to fields list
270 if( key.length() )
271 {
272 aFetchedPart.fields[key] = std::make_tuple( value, visible );
273 }
274 }
275 }
276 catch( const std::exception& e )
277 {
278 m_lastError += wxString::Format( _( "Error: %s" ) + "\n" + _( "API Response: %s" ) + "\n",
279 e.what(), res );
280
281 wxLogTrace( traceHTTPLib,
282 wxT( "SelectOne: Exception occurred while retrieving part from REST API: %s" ),
283 m_lastError );
284
285 return false;
286 }
287
288 m_cached_part = aFetchedPart;
289
290 return true;
291}
292
293
295 std::vector<HTTP_LIB_PART>& aParts )
296{
297 if( !IsValidEndpoint() )
298 {
299 wxLogTrace( traceHTTPLib, wxT( "SelectAll: without valid connection!" ) );
300 return false;
301 }
302
303 std::string res = "";
304
305 std::unique_ptr<KICAD_CURL_EASY> curl = createCurlEasyObject();
306 curl->SetURL( m_rootURL
307 + fmt::format( http_endpoint_parts + "/category/{}.json", aCategory.id ) );
308
309 try
310 {
311 curl->Perform();
312
313 res = curl->GetBuffer();
314
315 nlohmann::json response = nlohmann::json::parse( res );
316 std::string key = "";
317 std::string value = "";
318
319 for( nlohmann::json& item : response )
320 {
321 //PART result;
322 HTTP_LIB_PART part;
323
324 part.id = item.at( "id" );
325
326 if( item.contains( "description" ) )
327 {
328 // At this point we don't display anything so just set it to false
329 part.fields["description"] = std::make_tuple( item.at( "description" ), false );
330 }
331
332 // API might not want to return an optional name.
333 if( item.contains( "name" ) )
334 {
335 part.name = item.at( "name" );
336 }
337 else
338 {
339 part.name = part.id;
340 }
341
342 // add to cache
343 m_cache[part.name] = std::make_tuple( part.id, aCategory.id );
344
345 aParts.emplace_back( std::move( part ) );
346 }
347 }
348 catch( const std::exception& e )
349 {
350 m_lastError += wxString::Format( _( "Error: %s" ) + "\n" + _( "API Response: %s" ) + "\n",
351 e.what(), res );
352
353 wxLogTrace( traceHTTPLib, wxT( "Exception occurred while syncing parts from REST API: %s" ),
354 m_lastError );
355
356 return false;
357 }
358
359 return true;
360}
361
362
363bool HTTP_LIB_CONNECTION::checkServerResponse( std::unique_ptr<KICAD_CURL_EASY>& aCurl )
364{
365
366 long http_code = 0;
367
368 curl_easy_getinfo( aCurl->GetCurl(), CURLINFO_RESPONSE_CODE, &http_code );
369
370 if( http_code != 200 )
371 {
372 m_lastError += wxString::Format( _( "API responded with error code: %s" ) + "\n",
373 httpErrorCodeDescription( http_code ) );
374 return false;
375 }
376
377 return true;
378}
379
380
381bool HTTP_LIB_CONNECTION::boolFromString( const std::any& aVal, bool aDefaultValue )
382{
383 try
384 {
385 wxString strval( std::any_cast<std::string>( aVal ).c_str(), wxConvUTF8 );
386
387 if( strval.IsEmpty() )
388 return aDefaultValue;
389
390 strval.MakeLower();
391
392 for( const auto& trueVal : { wxS( "true" ), wxS( "yes" ), wxS( "y" ), wxS( "1" ) } )
393 {
394 if( strval.Matches( trueVal ) )
395 return true;
396 }
397
398 for( const auto& falseVal : { wxS( "false" ), wxS( "no" ), wxS( "n" ), wxS( "0" ) } )
399 {
400 if( strval.Matches( falseVal ) )
401 return false;
402 }
403 }
404 catch( const std::bad_any_cast& )
405 {
406 }
407
408 return aDefaultValue;
409}
410
411/*
412* HTTP response status codes indicate whether a specific HTTP request has been successfully completed.
413* Responses are grouped in five classes:
414* Informational responses (100 ? 199)
415* Successful responses (200 ? 299)
416* Redirection messages (300 ? 399)
417* Client error responses (400 ? 499)
418* Server error responses (500 ? 599)
419*
420* see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
421*/
423{
424 auto codeDescription =
425 []( uint16_t aCode ) -> wxString
426 {
427 switch( aCode )
428 {
429 case 100: return wxS( "Continue" );
430 case 101: return wxS( "Switching Protocols" );
431 case 102: return wxS( "Processing" );
432 case 103: return wxS( "Early Hints" );
433
434 case 200: return wxS( "OK" );
435 case 201: return wxS( "Created" );
436 case 203: return wxS( "Non-Authoritative Information" );
437 case 204: return wxS( "No Content" );
438 case 205: return wxS( "Reset Content" );
439 case 206: return wxS( "Partial Content" );
440 case 207: return wxS( "Multi-Status" );
441 case 208: return wxS( "Already Reporte" );
442 case 226: return wxS( "IM Used" );
443
444 case 300: return wxS( "Multiple Choices" );
445 case 301: return wxS( "Moved Permanently" );
446 case 302: return wxS( "Found" );
447 case 303: return wxS( "See Other" );
448 case 304: return wxS( "Not Modified" );
449 case 305: return wxS( "Use Proxy (Deprecated)" );
450 case 306: return wxS( "Unused" );
451 case 307: return wxS( "Temporary Redirect" );
452 case 308: return wxS( "Permanent Redirect" );
453
454 case 400: return wxS( "Bad Request" );
455 case 401: return wxS( "Unauthorized" );
456 case 402: return wxS( "Payment Required (Experimental)" );
457 case 403: return wxS( "Forbidden" );
458 case 404: return wxS( "Not Found" );
459 case 405: return wxS( "Method Not Allowed" );
460 case 406: return wxS( "Not Acceptable" );
461 case 407: return wxS( "Proxy Authentication Required" );
462 case 408: return wxS( "Request Timeout" );
463 case 409: return wxS( "Conflict" );
464 case 410: return wxS( "Gone" );
465 case 411: return wxS( "Length Required" );
466 case 412: return wxS( "Payload Too Large" );
467 case 414: return wxS( "URI Too Long" );
468 case 415: return wxS( "Unsupported Media Type" );
469 case 416: return wxS( "Range Not Satisfiable" );
470 case 417: return wxS( "Expectation Failed" );
471 case 418: return wxS( "I'm a teapot" );
472 case 421: return wxS( "Misdirected Request" );
473 case 422: return wxS( "Unprocessable Conten" );
474 case 423: return wxS( "Locked" );
475 case 424: return wxS( "Failed Dependency" );
476 case 425: return wxS( "Too Early (Experimental)" );
477 case 426: return wxS( "Upgrade Required" );
478 case 428: return wxS( "Precondition Required" );
479 case 429: return wxS( "Too Many Requests" );
480 case 431: return wxS( "Request Header Fields Too Large" );
481 case 451: return wxS( "Unavailable For Legal Reasons" );
482
483 case 500: return wxS( "Internal Server Error" );
484 case 501: return wxS( "Not Implemented" );
485 case 502: return wxS( "Bad Gateway" );
486 case 503: return wxS( "Service Unavailable" );
487 case 504: return wxS( "Gateway Timeout" );
488 case 505: return wxS( "HTTP Version Not Supported" );
489 case 506: return wxS( "Variant Also Negotiates" );
490 case 507: return wxS( "Insufficient Storag" );
491 case 508: return wxS( "Loop Detecte" );
492 case 510: return wxS( "Not Extended" );
493 case 511: return wxS( "Network Authentication Required" );
494 default: return wxS( "Unknown" );
495 }
496 };
497
498 return wxString::Format( wxS( "%d: %s" ), aHttpCode, codeDescription( aHttpCode ) );
499}
const std::string http_endpoint_parts
bool checkServerResponse(std::unique_ptr< KICAD_CURL_EASY > &aCurl)
const std::string http_endpoint_categories
std::unique_ptr< KICAD_CURL_EASY > createCurlEasyObject()
bool boolFromString(const std::any &aVal, bool aDefaultValue=false)
bool SelectAll(const HTTP_LIB_CATEGORY &aCategory, std::vector< HTTP_LIB_PART > &aParts)
Retrieves all parts from a specific category from the HTTP library.
HTTP_LIB_CONNECTION(const HTTP_LIB_SOURCE &aSource, bool aTestConnectionNow)
std::map< std::string, std::tuple< std::string, std::string > > m_cache
HTTP_LIB_SOURCE_TYPE m_sourceType
std::vector< HTTP_LIB_CATEGORY > m_categories
bool SelectOne(const std::string &aPartID, HTTP_LIB_PART &aFetchedPart)
Retrieves a single part with full details from the HTTP library.
wxString httpErrorCodeDescription(uint16_t aHttpCode)
HTTP_LIB_PART m_cached_part
#define _(s)
const char *const traceHTTPLib
const char *const traceHTTPLib
std::string id
id of category
std::string name
name of category
std::string name
std::string symbolIdStr
std::string id
std::map< std::string, std::tuple< std::string, bool > > fields
additional generic fields
HTTP_LIB_SOURCE_TYPE type
std::string root_url
VECTOR3I res