KiCad PCB EDA Suite
Loading...
Searching...
No Matches
remote_symbol_download_manager.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 The KiCad Developers, see AUTHORS.txt for contributors.
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
21
22#include <curl/curl.h>
24#include <picosha2.h>
26#include <wx/intl.h>
27
28
29namespace
30{
31bool validateAssetUrl( const REMOTE_PROVIDER_METADATA& aProvider, const wxString& aUrl,
32 wxString& aError )
33{
34 if( !ValidateRemoteUrlSecurity( aUrl, aProvider.allow_insecure_localhost, aError,
35 _( "Remote asset URL" ) ) )
36 {
37 return false;
38 }
39
40 const wxString assetOrigin = NormalizedUrlOrigin( aUrl );
41 const wxString apiOrigin = NormalizedUrlOrigin( aProvider.api_base_url );
42 const wxString panelOrigin = NormalizedUrlOrigin( aProvider.panel_url );
43 const bool originAllowed =
44 ( !apiOrigin.IsEmpty() && assetOrigin == apiOrigin )
45 || ( !panelOrigin.IsEmpty() && assetOrigin == panelOrigin );
46
47 if( assetOrigin.IsEmpty() || !originAllowed )
48 {
49 aError = _( "Remote asset URL origin must match the selected provider." );
50 return false;
51 }
52
53 return true;
54}
55
56
57bool defaultFetchHandler( const wxString& aUrl, REMOTE_SYMBOL_FETCH_RESPONSE& aResponse, wxString& aError )
58{
59 KICAD_CURL_EASY curl;
60 curl.SetUserAgent( "KiCad-RemoteProvider/1.0" );
61 curl.SetFollowRedirects( false );
62 curl.SetConnectTimeout( 10 );
63
64 if( !curl.SetURL( aUrl.ToStdString() ) )
65 {
66 aError = wxString::Format( _( "Unable to set download URL '%s'." ), aUrl );
67 return false;
68 }
69
70 const int result = curl.Perform();
71
72 if( result != CURLE_OK )
73 {
74 aError = wxString::FromUTF8( curl.GetErrorText( result ).c_str() );
75 return false;
76 }
77
78 aResponse.status_code = curl.GetResponseStatusCode();
79
80 char* contentType = nullptr;
81 curl_easy_getinfo( static_cast<CURL*>( curl.GetCurl() ), CURLINFO_CONTENT_TYPE, &contentType );
82
83 if( contentType )
84 aResponse.content_type = wxString::FromUTF8( contentType );
85
86 const std::string& buffer = curl.GetBuffer();
87 aResponse.payload.assign( buffer.begin(), buffer.end() );
88 return true;
89}
90
91
92wxString sha256Hex( const std::vector<uint8_t>& aPayload )
93{
94 std::string hashHex;
95 picosha2::hash256_hex_string( aPayload.begin(), aPayload.end(), hashHex );
96 return wxString::FromUTF8( hashHex.c_str() );
97}
98} // namespace
99
100
105
106
111
112
114 const REMOTE_PROVIDER_PART_ASSET& aAsset,
115 long long aRemainingBudget,
117 wxString& aError ) const
118{
119 aFetched = REMOTE_SYMBOL_FETCHED_ASSET();
120 aError.clear();
121
122 if( aAsset.size_bytes <= 0 )
123 {
124 aError = _( "Remote asset manifest declared an invalid size." );
125 return false;
126 }
127
128 if( aAsset.sha256.IsEmpty() )
129 {
130 aError = _( "Remote asset manifest must declare sha256 for URL-based downloads." );
131 return false;
132 }
133
134 if( !validateAssetUrl( aProvider, aAsset.download_url, aError ) )
135 return false;
136
137 if( aRemainingBudget >= 0 && aAsset.size_bytes > aRemainingBudget )
138 {
139 aError = _( "Remote asset exceeds the provider download limit." );
140 return false;
141 }
142
144
145 if( !m_handler( aAsset.download_url, response, aError ) )
146 return false;
147
148 if( response.status_code < 200 || response.status_code >= 300 )
149 {
150 aError = wxString::Format( _( "Remote download failed with HTTP %d." ), response.status_code );
151 return false;
152 }
153
154 if( response.content_type != aAsset.content_type )
155 {
156 aError = _( "Remote asset content type did not match the manifest." );
157 return false;
158 }
159
160 if( static_cast<long long>( response.payload.size() ) != aAsset.size_bytes )
161 {
162 aError = _( "Remote asset size did not match the manifest." );
163 return false;
164 }
165
166 if( aRemainingBudget >= 0 && static_cast<long long>( response.payload.size() ) > aRemainingBudget )
167 {
168 aError = _( "Remote asset exceeds the remaining download limit." );
169 return false;
170 }
171
172 if( !aAsset.sha256.IsEmpty() && !sha256Hex( response.payload ).IsSameAs( aAsset.sha256, false ) )
173 {
174 aError = _( "Remote asset digest did not match the manifest." );
175 return false;
176 }
177
178 aFetched.content_type = response.content_type;
179 aFetched.payload = std::move( response.payload );
180 return true;
181}
int Perform()
Equivalent to curl_easy_perform.
bool SetUserAgent(const std::string &aAgent)
Set the request user agent.
const std::string & GetBuffer()
Return a reference to the received data buffer.
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.
const std::string GetErrorText(int aCode)
Fetch CURL's "friendly" error string for a given error code.
std::function< bool(const wxString &, REMOTE_SYMBOL_FETCH_RESPONSE &, wxString &)> FETCH_HANDLER
bool DownloadAndVerify(const REMOTE_PROVIDER_METADATA &aProvider, const REMOTE_PROVIDER_PART_ASSET &aAsset, long long aRemainingBudget, REMOTE_SYMBOL_FETCHED_ASSET &aFetched, wxString &aError) const
#define _(s)
void CURL
STL namespace.
wxString NormalizedUrlOrigin(const wxString &aUrl)
Return a normalized scheme://host:port origin string for aUrl.
bool ValidateRemoteUrlSecurity(const wxString &aUrl, bool aAllowInsecureLocalhost, wxString &aError, const wxString &aLabel)
Validate that aUrl uses HTTPS, or HTTP on a loopback address when aAllowInsecureLocalhost is true.
wxString result
Test unit parsing edge cases and error handling.