Imported Upstream version 17.23.5
[platform/upstream/libzypp.git] / zypp / media / CurlHelper.cc
1 #include "CurlHelper.h"
2
3 #include <zypp/PathInfo.h>
4 #include <zypp/Pathname.h>
5 #include <zypp/Target.h>
6 #include <zypp/base/Logger.h>
7 #include <zypp/base/String.h>
8 #include <zypp/media/ProxyInfo.h>
9 #include <zypp/media/MediaUserAuth.h>
10 #include <zypp/media/MediaException.h>
11 #include <list>
12
13 using std::endl;
14 using namespace zypp;
15
16 namespace internal
17 {
18
19 void globalInitCurlOnce()
20 {
21   // function-level static <=> std::call_once
22   static bool once __attribute__ ((__unused__)) = ( [] {
23     if ( curl_global_init( CURL_GLOBAL_ALL ) != 0 )
24       WAR << "curl global init failed" << std::endl;
25   } (), true );
26 }
27
28 int log_curl(CURL *curl, curl_infotype info,
29   char *ptr, size_t len, void *max_lvl)
30 {
31   std::string pfx(" ");
32   long        lvl = 0;
33   switch( info)
34   {
35     case CURLINFO_TEXT:       lvl = 1; pfx = "*"; break;
36     case CURLINFO_HEADER_IN:  lvl = 2; pfx = "<"; break;
37     case CURLINFO_HEADER_OUT: lvl = 2; pfx = ">"; break;
38     default:                                      break;
39   }
40   if( lvl > 0 && max_lvl != NULL && lvl <= *((long *)max_lvl))
41   {
42     std::string                            msg(ptr, len);
43     std::list<std::string>                 lines;
44     std::list<std::string>::const_iterator line;
45     zypp::str::split(msg, std::back_inserter(lines), "\r\n");
46     for(line = lines.begin(); line != lines.end(); ++line)
47     {
48       DBG << pfx << " " << *line << std::endl;
49     }
50   }
51   return 0;
52 }
53
54 size_t log_redirects_curl( char *ptr, size_t size, size_t nmemb, void *userdata)
55 {
56   // INT << "got header: " << string(ptr, ptr + size*nmemb) << endl;
57
58   char * lstart = ptr, * lend = ptr;
59   size_t pos = 0;
60   size_t max = size * nmemb;
61   while (pos + 1 < max)
62   {
63     // get line
64     for (lstart = lend; *lend != '\n' && pos < max; ++lend, ++pos);
65
66     // look for "Location"
67     if ( lstart[0] == 'L'
68          && lstart[1] == 'o'
69          && lstart[2] == 'c'
70          && lstart[3] == 'a'
71          && lstart[4] == 't'
72          && lstart[5] == 'i'
73          && lstart[6] == 'o'
74          && lstart[7] == 'n'
75          && lstart[8] == ':' )
76     {
77       std::string line { lstart, *(lend-1)=='\r' ? lend-1 : lend };
78       DBG << "redirecting to " << line << std::endl;
79       if ( userdata ) {
80         *reinterpret_cast<std::string *>( userdata ) = line;
81       }
82       return max;
83     }
84
85     // continue with the next line
86     if (pos + 1 < max)
87     {
88       ++lend;
89       ++pos;
90     }
91     else
92       break;
93   }
94
95   return max;
96 }
97
98 /**
99  * Fills the settings structure using options passed on the url
100  * for example ?timeout=x&proxy=foo
101  */
102 void fillSettingsFromUrl( const Url &url, media::TransferSettings &s )
103 {
104   {
105     const std::string & param { url.getQueryParam("timeout") };
106     if( ! param.empty() )
107     {
108       long num = str::strtonum<long>(param);
109       if( num >= 0 && num <= TRANSFER_TIMEOUT_MAX )
110         s.setTimeout( num );
111     }
112   }
113   {
114     std::string param { url.getUsername() };
115     if ( ! param.empty() )
116     {
117       s.setUsername( std::move(param) );
118       param = url.getPassword();
119       if ( ! param.empty() )
120         s.setPassword( std::move(param) );
121     }
122     else
123     {
124       // if there is no username, set anonymous auth
125       if ( ( url.getScheme() == "ftp" || url.getScheme() == "tftp" ) && s.username().empty() )
126         s.setAnonymousAuth();
127     }
128   }
129   if ( url.getScheme() == "https" )
130   {
131     s.setVerifyPeerEnabled( false );
132     s.setVerifyHostEnabled( false );
133
134     const std::string & verify { url.getQueryParam("ssl_verify") };
135     if( verify.empty() || verify == "yes" )
136     {
137       s.setVerifyPeerEnabled( true );
138       s.setVerifyHostEnabled( true );
139     }
140     else if ( verify == "no" )
141     {
142       s.setVerifyPeerEnabled( false );
143       s.setVerifyHostEnabled( false );
144     }
145     else
146     {
147       std::vector<std::string> flags;
148       str::split( verify, std::back_inserter(flags), "," );
149       for ( const auto & flag : flags )
150       {
151         if ( flag == "host" )
152           s.setVerifyHostEnabled( true );
153         else if ( flag == "peer" )
154           s.setVerifyPeerEnabled( true );
155         else
156           ZYPP_THROW( media::MediaBadUrlException(url, "Unknown ssl_verify flag "+flag) );
157       }
158     }
159   }
160   {
161     Pathname ca_path { url.getQueryParam("ssl_capath") };
162     if( ! ca_path.empty() )
163     {
164       if( ! PathInfo(ca_path).isDir() || ! ca_path.absolute() )
165         ZYPP_THROW(media::MediaBadUrlException(url, "Invalid ssl_capath path"));
166       else
167         s.setCertificateAuthoritiesPath( std::move(ca_path) );
168     }
169   }
170   {
171     Pathname client_cert { url.getQueryParam("ssl_clientcert") };
172     if( ! client_cert.empty() )
173     {
174       if( ! PathInfo(client_cert).isFile() || ! client_cert.absolute() )
175         ZYPP_THROW(media::MediaBadUrlException(url, "Invalid ssl_clientcert file"));
176       else
177         s.setClientCertificatePath( std::move(client_cert) );
178     }
179   }
180   {
181     Pathname client_key { url.getQueryParam("ssl_clientkey") };
182     if( ! client_key.empty() )
183     {
184       if( ! PathInfo(client_key).isFile() || ! client_key.absolute() )
185         ZYPP_THROW(media::MediaBadUrlException(url, "Invalid ssl_clientkey file"));
186       else
187         s.setClientKeyPath( std::move(client_key) );
188     }
189   }
190   {
191     std::string param { url.getQueryParam( "proxy" ) };
192     if ( ! param.empty() )
193     {
194       if ( param == EXPLICITLY_NO_PROXY ) {
195         // Workaround TransferSettings shortcoming: With an
196         // empty proxy string, code will continue to look for
197         // valid proxy settings. So set proxy to some non-empty
198         // string, to indicate it has been explicitly disabled.
199         s.setProxy(EXPLICITLY_NO_PROXY);
200         s.setProxyEnabled(false);
201       }
202       else {
203         const std::string & proxyport { url.getQueryParam( "proxyport" ) };
204         if ( ! proxyport.empty() ) {
205           param += ":";
206           param += proxyport;
207         }
208         s.setProxy( std::move(param) );
209         s.setProxyEnabled( true );
210       }
211     }
212   }
213   {
214     std::string param { url.getQueryParam( "proxyuser" ) };
215     if ( ! param.empty() )
216     {
217       s.setProxyUsername( std::move(param) );
218       s.setProxyPassword( url.getQueryParam( "proxypass" ) );
219     }
220   }
221   {
222     // HTTP authentication type
223     std::string param { url.getQueryParam("auth") };
224     if ( ! param.empty() && (url.getScheme() == "http" || url.getScheme() == "https") )
225     {
226       try
227       {
228         media::CurlAuthData::auth_type_str2long (param );       // check if we know it
229       }
230       catch ( const media::MediaException & ex_r )
231       {
232         DBG << "Rethrowing as MediaUnauthorizedException.";
233         ZYPP_THROW(media::MediaUnauthorizedException(url, ex_r.msg(), "", ""));
234       }
235       s.setAuthType( std::move(param) );
236     }
237   }
238   {
239     // workarounds
240     const std::string & param { url.getQueryParam("head_requests") };
241     if( ! param.empty() && param == "no" )
242       s.setHeadRequestsAllowed( false );
243   }
244 }
245
246 /**
247  * Reads the system proxy configuration and fills the settings
248  * structure proxy information
249  */
250 void fillSettingsSystemProxy( const Url& url, media::TransferSettings &s )
251 {
252   media::ProxyInfo proxy_info;
253   if ( proxy_info.useProxyFor( url ) )
254   {
255     // We must extract any 'user:pass' from the proxy url
256     // otherwise they won't make it into curl (.curlrc wins).
257     try {
258       Url u( proxy_info.proxy( url ) );
259       s.setProxy( u.asString( url::ViewOption::WITH_SCHEME + url::ViewOption::WITH_HOST + url::ViewOption::WITH_PORT ) );
260       // don't overwrite explicit auth settings
261       if ( s.proxyUsername().empty() )
262       {
263         s.setProxyUsername( u.getUsername( url::E_ENCODED ) );
264         s.setProxyPassword( u.getPassword( url::E_ENCODED ) );
265       }
266       s.setProxyEnabled( true );
267     }
268     catch (...) {}      // no proxy if URL is malformed
269   }
270 }
271
272
273 int env::getZYPP_MEDIA_CURL_IPRESOLVE()
274 {
275   int ret = 0;
276   if ( const char * envp = getenv( "ZYPP_MEDIA_CURL_IPRESOLVE" ) )
277   {
278     WAR << "env set: $ZYPP_MEDIA_CURL_IPRESOLVE='" << envp << "'" << std::endl;
279     if (      strcmp( envp, "4" ) == 0 )        ret = 4;
280     else if ( strcmp( envp, "6" ) == 0 )        ret = 6;
281   }
282   return ret;
283 }
284
285
286 const char * anonymousIdHeader()
287 {
288   // we need to add the release and identifier to the
289   // agent string.
290   // The target could be not initialized, and then this information
291   // is guessed.
292   static const std::string _value(
293     str::trim( str::form(
294       "X-ZYpp-AnonymousId: %s",
295       Target::anonymousUniqueId( Pathname()/*guess root*/ ).c_str() ) )
296     );
297   return _value.c_str();
298 }
299
300 const char * distributionFlavorHeader()
301 {
302   // we need to add the release and identifier to the
303   // agent string.
304   // The target could be not initialized, and then this information
305   // is guessed.
306   static const std::string _value(
307     str::trim( str::form(
308       "X-ZYpp-DistributionFlavor: %s",
309       Target::distributionFlavor( Pathname()/*guess root*/ ).c_str() ) )
310     );
311   return _value.c_str();
312 }
313
314 const char * agentString()
315 {
316   // we need to add the release and identifier to the
317   // agent string.
318   // The target could be not initialized, and then this information
319   // is guessed.
320   static const std::string _value(
321     str::form(
322       "ZYpp " LIBZYPP_VERSION_STRING " (curl %s) %s"
323       , curl_version_info(CURLVERSION_NOW)->version
324       , Target::targetDistribution( Pathname()/*guess root*/ ).c_str()
325       )
326     );
327   return _value.c_str();
328 }
329
330 void curlEscape( std::string & str_r,
331   const char char_r, const std::string & escaped_r ) {
332   for ( std::string::size_type pos = str_r.find( char_r );
333         pos != std::string::npos; pos = str_r.find( char_r, pos ) ) {
334     str_r.replace( pos, 1, escaped_r );
335   }
336 }
337
338 std::string curlEscapedPath( std::string path_r ) {
339   curlEscape( path_r, ' ', "%20" );
340   return path_r;
341 }
342
343 std::string curlUnEscape( std::string text_r ) {
344   char * tmp = curl_unescape( text_r.c_str(), 0 );
345   std::string ret( tmp );
346   curl_free( tmp );
347   return ret;
348 }
349
350 Url clearQueryString(const Url &url)
351 {
352   Url curlUrl (url);
353   curlUrl.setUsername( "" );
354   curlUrl.setPassword( "" );
355   curlUrl.setPathParams( "" );
356   curlUrl.setFragment( "" );
357   curlUrl.delQueryParam("cookies");
358   curlUrl.delQueryParam("proxy");
359   curlUrl.delQueryParam("proxyport");
360   curlUrl.delQueryParam("proxyuser");
361   curlUrl.delQueryParam("proxypass");
362   curlUrl.delQueryParam("ssl_capath");
363   curlUrl.delQueryParam("ssl_verify");
364   curlUrl.delQueryParam("ssl_clientcert");
365   curlUrl.delQueryParam("timeout");
366   curlUrl.delQueryParam("auth");
367   curlUrl.delQueryParam("username");
368   curlUrl.delQueryParam("password");
369   curlUrl.delQueryParam("mediahandler");
370   curlUrl.delQueryParam("credentials");
371   curlUrl.delQueryParam("head_requests");
372   return curlUrl;
373 }
374
375 // bsc#933839: propagate proxy settings passed in the repo URL
376 zypp::Url propagateQueryParams( zypp::Url url_r, const zypp::Url & template_r )
377 {
378   for ( std::string param : { "proxy", "proxyport", "proxyuser", "proxypass"} )
379   {
380     const std::string & value( template_r.getQueryParam( param ) );
381     if ( ! value.empty() )
382       url_r.setQueryParam( param, value );
383   }
384   return url_r;
385 }
386
387 }