MediaCurl.cc

Go to the documentation of this file.
00001 /*---------------------------------------------------------------------\
00002 |                          ____ _   __ __ ___                          |
00003 |                         |__  / \ / / . \ . \                         |
00004 |                           / / \ V /|  _/  _/                         |
00005 |                          / /__ | | | | | |                           |
00006 |                         /_____||_| |_| |_|                           |
00007 |                                                                      |
00008 \---------------------------------------------------------------------*/
00013 #include <iostream>
00014 #include <list>
00015 
00016 #include "zypp/base/Logger.h"
00017 #include "zypp/ExternalProgram.h"
00018 #include "zypp/base/String.h"
00019 #include "zypp/base/Gettext.h"
00020 #include "zypp/base/Sysconfig.h"
00021 
00022 #include "zypp/media/MediaCurl.h"
00023 #include "zypp/media/proxyinfo/ProxyInfos.h"
00024 #include "zypp/media/ProxyInfo.h"
00025 #include "zypp/media/CurlConfig.h"
00026 #include "zypp/thread/Once.h"
00027 #include <cstdlib>
00028 #include <sys/types.h>
00029 #include <sys/stat.h>
00030 #include <sys/mount.h>
00031 #include <errno.h>
00032 #include <dirent.h>
00033 #include <unistd.h>
00034 
00035 #include "config.h"
00036 
00037 #define  DETECT_DIR_INDEX       0
00038 #define  CONNECT_TIMEOUT        60
00039 #define  TRANSFER_TIMEOUT       60 * 3
00040 #define  TRANSFER_TIMEOUT_MAX   60 * 60
00041 
00042 
00043 using namespace std;
00044 using namespace zypp::base;
00045 
00046 namespace
00047 {
00048   zypp::thread::OnceFlag g_InitOnceFlag = PTHREAD_ONCE_INIT;
00049   zypp::thread::OnceFlag g_FreeOnceFlag = PTHREAD_ONCE_INIT;
00050 
00051   extern "C" void _do_free_once()
00052   {
00053     curl_global_cleanup();
00054   }
00055 
00056   extern "C" void globalFreeOnce()
00057   {
00058     zypp::thread::callOnce(g_FreeOnceFlag, _do_free_once);
00059   }
00060 
00061   extern "C" void _do_init_once()
00062   {
00063     CURLcode ret = curl_global_init( CURL_GLOBAL_ALL );
00064     if ( ret != 0 )
00065     {
00066       WAR << "curl global init failed" << endl;
00067     }
00068 
00069     //
00070     // register at exit handler ?
00071     // this may cause trouble, because we can protect it
00072     // against ourself only.
00073     // if the app sets an atexit handler as well, it will
00074     // cause a double free while the second of them runs.
00075     //
00076     //std::atexit( globalFreeOnce);
00077   }
00078 
00079   inline void globalInitOnce()
00080   {
00081     zypp::thread::callOnce(g_InitOnceFlag, _do_init_once);
00082   }
00083 
00084   int log_curl(CURL *curl, curl_infotype info,
00085                char *ptr, size_t len, void *max_lvl)
00086   {
00087     std::string pfx(" ");
00088     long        lvl = 0;
00089     switch( info)
00090     {
00091       case CURLINFO_TEXT:       lvl = 1; pfx = "*"; break;
00092       case CURLINFO_HEADER_IN:  lvl = 2; pfx = "<"; break;
00093       case CURLINFO_HEADER_OUT: lvl = 2; pfx = ">"; break;
00094       default:                                      break;
00095     }
00096     if( lvl > 0 && max_lvl != NULL && lvl <= *((long *)max_lvl))
00097     {
00098       std::string                            msg(ptr, len);
00099       std::list<std::string>                 lines;
00100       std::list<std::string>::const_iterator line;
00101       zypp::str::split(msg, std::back_inserter(lines), "\r\n");
00102       for(line = lines.begin(); line != lines.end(); ++line)
00103       {
00104         DBG << pfx << " " << *line << endl;
00105       }
00106     }
00107     return 0;
00108   }
00109 }
00110 
00111 namespace zypp {
00112   namespace media {
00113 
00114   namespace {
00115     struct ProgressData
00116     {
00117       ProgressData(const long _timeout, const zypp::Url &_url = zypp::Url(),
00118                    callback::SendReport<DownloadProgressReport> *_report=NULL)
00119         : timeout(_timeout)
00120         , reached(false)
00121         , report(_report)
00122         , ltime( time(NULL))
00123         , dload( 0)
00124         , uload( 0)
00125         , url(_url)
00126       {}
00127       long                                          timeout;
00128       bool                                          reached;
00129       callback::SendReport<DownloadProgressReport> *report;
00130       time_t                                        ltime;
00131       double                                        dload;
00132       double                                        uload;
00133       zypp::Url                                     url;
00134     };
00135   }
00136 
00137 Pathname    MediaCurl::_cookieFile = "/var/lib/YaST2/cookies";
00138 std::string MediaCurl::_agent = "Novell ZYPP Installer";
00139 
00141 
00142 static inline void escape( string & str_r,
00143                            const char char_r, const string & escaped_r ) {
00144   for ( string::size_type pos = str_r.find( char_r );
00145         pos != string::npos; pos = str_r.find( char_r, pos ) ) {
00146     str_r.replace( pos, 1, escaped_r );
00147   }
00148 }
00149 
00150 static inline string escapedPath( string path_r ) {
00151   escape( path_r, ' ', "%20" );
00152   return path_r;
00153 }
00154 
00155 static inline string unEscape( string text_r ) {
00156   char * tmp = curl_unescape( text_r.c_str(), 0 );
00157   string ret( tmp );
00158   curl_free( tmp );
00159   return ret;
00160 }
00161 
00163 //
00164 //        CLASS NAME : MediaCurl
00165 //
00167 
00168 MediaCurl::MediaCurl( const Url &      url_r,
00169                       const Pathname & attach_point_hint_r )
00170     : MediaHandler( url_r, attach_point_hint_r,
00171                     "/", // urlpath at attachpoint
00172                     true ), // does_download
00173       _curl( NULL )
00174 {
00175   _curlError[0] = '\0';
00176   _curlDebug = 0L;
00177 
00178   MIL << "MediaCurl::MediaCurl(" << url_r << ", " << attach_point_hint_r << ")" << endl;
00179 
00180   globalInitOnce();
00181 
00182   if( !attachPoint().empty())
00183   {
00184     PathInfo ainfo(attachPoint());
00185     Pathname apath(attachPoint() + "XXXXXX");
00186     char    *atemp = ::strdup( apath.asString().c_str());
00187     char    *atest = NULL;
00188     if( !ainfo.isDir() || !ainfo.userMayRWX() ||
00189          atemp == NULL || (atest=::mkdtemp(atemp)) == NULL)
00190     {
00191       WAR << "attach point " << ainfo.path()
00192           << " is not useable for " << url_r.getScheme() << endl;
00193       setAttachPoint("", true);
00194     }
00195     else if( atest != NULL)
00196       ::rmdir(atest);
00197 
00198     if( atemp != NULL)
00199       ::free(atemp);
00200   }
00201 }
00202 
00203 void MediaCurl::setCookieFile( const Pathname &fileName )
00204 {
00205   _cookieFile = fileName;
00206 }
00207 
00209 //
00210 //
00211 //        METHOD NAME : MediaCurl::attachTo
00212 //        METHOD TYPE : PMError
00213 //
00214 //        DESCRIPTION : Asserted that not already attached, and attachPoint is a directory.
00215 //
00216 void MediaCurl::attachTo (bool next)
00217 {
00218   if ( next )
00219     ZYPP_THROW(MediaNotSupportedException(_url));
00220 
00221   if ( !_url.isValid() )
00222     ZYPP_THROW(MediaBadUrlException(_url));
00223 
00224   CurlConfig curlconf;
00225   CurlConfig::parseConfig(curlconf); // parse ~/.curlrc
00226 
00227   curl_version_info_data *curl_info = NULL;
00228   curl_info = curl_version_info(CURLVERSION_NOW);
00229   // curl_info does not need any free (is static)
00230   if (curl_info->protocols)
00231   {
00232     const char * const *proto;
00233     std::string        scheme( _url.getScheme());
00234     bool               found = false;
00235     for(proto=curl_info->protocols; !found && *proto; ++proto)
00236     {
00237       if( scheme == std::string((const char *)*proto))
00238         found = true;
00239     }
00240     if( !found)
00241     {
00242       std::string msg("Unsupported protocol '");
00243       msg += scheme;
00244       msg += "'";
00245       ZYPP_THROW(MediaBadUrlException(_url, msg));
00246     }
00247   }
00248 
00249   if( !isUseableAttachPoint(attachPoint()))
00250   {
00251     std::string mountpoint = createAttachPoint().asString();
00252 
00253     if( mountpoint.empty())
00254       ZYPP_THROW( MediaBadAttachPointException(url()));
00255 
00256     setAttachPoint( mountpoint, true);
00257   }
00258 
00259   disconnectFrom(); // clean _curl if needed
00260   _curl = curl_easy_init();
00261   if ( !_curl ) {
00262     ZYPP_THROW(MediaCurlInitException(_url));
00263   }
00264 
00265   {
00266     char *ptr = getenv("ZYPP_MEDIA_CURL_DEBUG");
00267     _curlDebug = (ptr && *ptr) ? str::strtonum<long>( ptr) : 0L;
00268     if( _curlDebug > 0)
00269     {
00270       curl_easy_setopt( _curl, CURLOPT_VERBOSE, 1);
00271       curl_easy_setopt( _curl, CURLOPT_DEBUGFUNCTION, log_curl);
00272       curl_easy_setopt( _curl, CURLOPT_DEBUGDATA, &_curlDebug);
00273     }
00274   }
00275 
00276   CURLcode ret = curl_easy_setopt( _curl, CURLOPT_ERRORBUFFER, _curlError );
00277   if ( ret != 0 ) {
00278     disconnectFrom();
00279     ZYPP_THROW(MediaCurlSetOptException(_url, "Error setting error buffer"));
00280   }
00281 
00282   ret = curl_easy_setopt( _curl, CURLOPT_FAILONERROR, true );
00283   if ( ret != 0 ) {
00284     disconnectFrom();
00285     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00286   }
00287 
00288   ret = curl_easy_setopt( _curl, CURLOPT_NOSIGNAL, 1 );
00289   if ( ret != 0 ) {
00290     disconnectFrom();
00291     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00292   }
00293 
00297   {
00298     _xfer_timeout = TRANSFER_TIMEOUT;
00299 
00300     std::string param(_url.getQueryParam("timeout"));
00301     if( !param.empty())
00302     {
00303       long num = str::strtonum<long>( param);
00304       if( num >= 0 && num <= TRANSFER_TIMEOUT_MAX)
00305         _xfer_timeout = num;
00306     }
00307   }
00308 
00309   /*
00310   ** Connect timeout
00311   */
00312   ret = curl_easy_setopt( _curl, CURLOPT_CONNECTTIMEOUT, CONNECT_TIMEOUT);
00313   if ( ret != 0 ) {
00314     disconnectFrom();
00315     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00316   }
00317 
00318   if ( _url.getScheme() == "http" ) {
00319     // follow any Location: header that the server sends as part of
00320     // an HTTP header (#113275)
00321     ret = curl_easy_setopt ( _curl, CURLOPT_FOLLOWLOCATION, true );
00322     if ( ret != 0) {
00323       disconnectFrom();
00324       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00325     }
00326     ret = curl_easy_setopt ( _curl, CURLOPT_MAXREDIRS, 3L );
00327     if ( ret != 0) {
00328       disconnectFrom();
00329       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00330     }
00331     ret = curl_easy_setopt ( _curl, CURLOPT_USERAGENT, _agent.c_str() );
00332     if ( ret != 0) {
00333       disconnectFrom();
00334       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00335     }
00336   }
00337 
00338   if ( _url.getScheme() == "https" )
00339   {
00340     bool verify_peer = false;
00341     bool verify_host = false;
00342 
00343     std::string verify( _url.getQueryParam("ssl_verify"));
00344     if( verify.empty() ||
00345         verify == "yes")
00346     {
00347       verify_peer = true;
00348       verify_host = true;
00349     }
00350     else
00351     if( verify == "no")
00352     {
00353       verify_peer = false;
00354       verify_host = false;
00355     }
00356     else
00357     {
00358       std::vector<std::string>                 flags;
00359       std::vector<std::string>::const_iterator flag;
00360       str::split( verify, std::back_inserter(flags), ",");
00361       for(flag = flags.begin(); flag != flags.end(); ++flag)
00362       {
00363         if( *flag == "host")
00364         {
00365           verify_host = true;
00366         }
00367         else
00368         if( *flag == "peer")
00369         {
00370           verify_peer = true;
00371         }
00372         else
00373         {
00374                 disconnectFrom();
00375           ZYPP_THROW(MediaBadUrlException(_url, "Unknown ssl_verify flag"));
00376         }
00377       }
00378     }
00379 
00380     _ca_path = Pathname(_url.getQueryParam("ssl_capath")).asString();
00381     if( _ca_path.empty())
00382     {
00383         _ca_path = "/etc/ssl/certs/";
00384     }
00385     else
00386     if( !PathInfo(_ca_path).isDir() || !Pathname(_ca_path).absolute())
00387     {
00388         disconnectFrom();
00389         ZYPP_THROW(MediaBadUrlException(_url, "Invalid ssl_capath path"));
00390     }
00391 
00392     if( verify_peer || verify_host)
00393     {
00394       ret = curl_easy_setopt( _curl, CURLOPT_CAPATH, _ca_path.c_str());
00395       if ( ret != 0 ) {
00396         disconnectFrom();
00397         ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00398       }
00399     }
00400 
00401     ret = curl_easy_setopt( _curl, CURLOPT_SSL_VERIFYPEER, verify_peer ? 1L : 0L);
00402     if ( ret != 0 ) {
00403       disconnectFrom();
00404       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00405     }
00406     ret = curl_easy_setopt( _curl, CURLOPT_SSL_VERIFYHOST, verify_host ? 2L : 0L);
00407     if ( ret != 0 ) {
00408       disconnectFrom();
00409       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00410     }
00411 
00412     ret = curl_easy_setopt ( _curl, CURLOPT_USERAGENT, _agent.c_str() );
00413     if ( ret != 0) {
00414       disconnectFrom();
00415       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00416     }
00417   }
00418 
00419 
00420   /*---------------------------------------------------------------*
00421    CURLOPT_USERPWD: [user name]:[password]
00422 
00423    Url::username/password -> CURLOPT_USERPWD
00424    If not provided, anonymous FTP identification
00425    *---------------------------------------------------------------*/
00426 
00427   if ( _url.getUsername().empty() ) {
00428     if ( _url.getScheme() == "ftp" ) {
00429       string id = "yast2@";
00430       id += VERSION;
00431       DBG << "Anonymous FTP identification: '" << id << "'" << endl;
00432       _userpwd = "anonymous:" + id;
00433     }
00434   } else {
00435     _userpwd = _url.getUsername();
00436     if ( _url.getPassword().size() ) {
00437       _userpwd += ":" + _url.getPassword();
00438     }
00439   }
00440 
00441   if ( _userpwd.size() ) {
00442     _userpwd = unEscape( _userpwd );
00443     ret = curl_easy_setopt( _curl, CURLOPT_USERPWD, _userpwd.c_str() );
00444     if ( ret != 0 ) {
00445       disconnectFrom();
00446       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00447     }
00448 
00449     if(_url.getScheme() == "http" || _url.getScheme() == "https")
00450     {
00451       std::vector<std::string>                 list;
00452       std::vector<std::string>::const_iterator it;
00453 
00454       string use_auth = _url.getQueryParam("auth");
00455       if( use_auth.empty())
00456         use_auth = "digest,basic";
00457 
00458       str::split(use_auth, std::back_inserter(list), ",");
00459 
00460       long auth = CURLAUTH_NONE;
00461       for(it = list.begin(); it != list.end(); ++it)
00462       {
00463         if(*it == "basic")
00464         {
00465           auth |= CURLAUTH_BASIC;
00466         }
00467         else
00468         if(*it == "digest")
00469         {
00470           auth |= CURLAUTH_DIGEST;
00471         }
00472         else
00473         if((curl_info && (curl_info->features & CURL_VERSION_NTLM)) &&
00474            (*it == "ntlm"))
00475         {
00476           auth |= CURLAUTH_NTLM;
00477         }
00478         else
00479         if((curl_info && (curl_info->features & CURL_VERSION_SPNEGO)) &&
00480            (*it == "spnego" || *it == "negotiate"))
00481         {
00482           // there is no separate spnego flag for auth
00483           auth |= CURLAUTH_GSSNEGOTIATE;
00484         }
00485         else
00486         if((curl_info && (curl_info->features & CURL_VERSION_GSSNEGOTIATE)) &&
00487            (*it == "gssnego" || *it == "negotiate"))
00488         {
00489           auth |= CURLAUTH_GSSNEGOTIATE;
00490         }
00491         else
00492         {
00493           std::string msg("Unsupported HTTP authentication method '");
00494           msg += *it;
00495           msg += "'";
00496                 disconnectFrom();
00497           ZYPP_THROW(MediaBadUrlException(_url, msg));
00498         }
00499       }
00500 
00501       if( auth != CURLAUTH_NONE)
00502       {
00503         DBG << "Enabling HTTP authentication methods: " << use_auth
00504           << " (CURLOPT_HTTPAUTH=" << auth << ")" << std::endl;
00505 
00506         ret = curl_easy_setopt( _curl, CURLOPT_HTTPAUTH, auth);
00507         if ( ret != 0 ) {
00508                 disconnectFrom();
00509           ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00510         }
00511       }
00512     }
00513   }
00514 
00515   /*---------------------------------------------------------------*
00516    CURLOPT_PROXY: host[:port]
00517 
00518    Url::option(proxy and proxyport) -> CURLOPT_PROXY
00519    If not provided, /etc/sysconfig/proxy is evaluated
00520    *---------------------------------------------------------------*/
00521 
00522   _proxy = _url.getQueryParam( "proxy" );
00523 
00524   if ( ! _proxy.empty() ) {
00525     string proxyport( _url.getQueryParam( "proxyport" ) );
00526     if ( ! proxyport.empty() ) {
00527       _proxy += ":" + proxyport;
00528     }
00529   } else {
00530 
00531     ProxyInfo proxy_info (ProxyInfo::ImplPtr(new ProxyInfoSysconfig("proxy")));
00532 
00533     if ( proxy_info.enabled())
00534     {
00535       bool useproxy = true;
00536 
00537       std::list<std::string> nope = proxy_info.noProxy();
00538       for (ProxyInfo::NoProxyIterator it = proxy_info.noProxyBegin();
00539            it != proxy_info.noProxyEnd();
00540            it++)
00541       {
00542         std::string host( str::toLower(_url.getHost()));
00543         std::string temp( str::toLower(*it));
00544 
00545         // no proxy if it points to a suffix
00546         // preceeded by a '.', that maches
00547         // the trailing portion of the host.
00548         if( temp.size() > 1 && temp.at(0) == '.')
00549         {
00550           if(host.size() > temp.size() &&
00551              host.compare(host.size() - temp.size(), temp.size(), temp) == 0)
00552           {
00553             DBG << "NO_PROXY: '" << *it  << "' matches host '"
00554                                  << host << "'" << endl;
00555             useproxy = false;
00556             break;
00557           }
00558         }
00559         else
00560         // no proxy if we have an exact match
00561         if( host == temp)
00562         {
00563           DBG << "NO_PROXY: '" << *it  << "' matches host '"
00564                                << host << "'" << endl;
00565           useproxy = false;
00566           break;
00567         }
00568       }
00569 
00570       if ( useproxy ) {
00571         _proxy = proxy_info.proxy(_url.getScheme());
00572       }
00573     }
00574   }
00575 
00576 
00577   DBG << "Proxy: " << (_proxy.empty() ? "-none-" : _proxy) << endl;
00578 
00579   if ( ! _proxy.empty() ) {
00580 
00581     ret = curl_easy_setopt( _curl, CURLOPT_PROXY, _proxy.c_str() );
00582     if ( ret != 0 ) {
00583       disconnectFrom();
00584       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00585     }
00586 
00587     /*---------------------------------------------------------------*
00588      CURLOPT_PROXYUSERPWD: [user name]:[password]
00589 
00590      Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD
00591      If not provided, $HOME/.curlrc is evaluated
00592      *---------------------------------------------------------------*/
00593 
00594     _proxyuserpwd = _url.getQueryParam( "proxyuser" );
00595 
00596     if ( ! _proxyuserpwd.empty() ) {
00597       string proxypassword( _url.getQueryParam( "proxypassword" ) );
00598       if ( ! proxypassword.empty() ) {
00599         _proxyuserpwd += ":" + proxypassword;
00600       }
00601     } else {
00602       if (curlconf.proxyuserpwd.empty())
00603         DBG << "~/.curlrc does not contain the proxy-user option" << endl;
00604       else
00605       {
00606         _proxyuserpwd = curlconf.proxyuserpwd;
00607         DBG << "using proxy-user from ~/.curlrc" << endl;
00608       }
00609     }
00610 
00611     _proxyuserpwd = unEscape( _proxyuserpwd );
00612     ret = curl_easy_setopt( _curl, CURLOPT_PROXYUSERPWD, _proxyuserpwd.c_str() );
00613     if ( ret != 0 ) {
00614       disconnectFrom();
00615       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00616     }
00617   }
00618 
00619   /*---------------------------------------------------------------*
00620    *---------------------------------------------------------------*/
00621 
00622   _currentCookieFile = _cookieFile.asString();
00623 
00624   ret = curl_easy_setopt( _curl, CURLOPT_COOKIEFILE,
00625                           _currentCookieFile.c_str() );
00626   if ( ret != 0 ) {
00627     disconnectFrom();
00628     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00629   }
00630 
00631   ret = curl_easy_setopt( _curl, CURLOPT_COOKIEJAR,
00632                           _currentCookieFile.c_str() );
00633   if ( ret != 0 ) {
00634     disconnectFrom();
00635     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00636   }
00637 
00638   ret = curl_easy_setopt( _curl, CURLOPT_PROGRESSFUNCTION,
00639                           &progressCallback );
00640   if ( ret != 0 ) {
00641     disconnectFrom();
00642     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00643   }
00644 
00645   ret = curl_easy_setopt( _curl, CURLOPT_NOPROGRESS, false );
00646   if ( ret != 0 ) {
00647     disconnectFrom();
00648     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
00649   }
00650 
00651   // FIXME: need a derived class to propelly compare url's
00652   MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString()));
00653   setMediaSource(media);
00654 }
00655 
00656 bool
00657 MediaCurl::checkAttachPoint(const Pathname &apoint) const
00658 {
00659   return MediaHandler::checkAttachPoint( apoint, true, true);
00660 }
00661 
00663 //
00664 //
00665 //        METHOD NAME : MediaCurl::disconnectFrom
00666 //        METHOD TYPE : PMError
00667 //
00668 void MediaCurl::disconnectFrom()
00669 {
00670   if ( _curl )
00671   {
00672     curl_easy_cleanup( _curl );
00673     _curl = NULL;
00674   }
00675 }
00676 
00678 //
00679 //
00680 //        METHOD NAME : MediaCurl::releaseFrom
00681 //        METHOD TYPE : PMError
00682 //
00683 //        DESCRIPTION : Asserted that media is attached.
00684 //
00685 void MediaCurl::releaseFrom( bool eject )
00686 {
00687   disconnect();
00688 }
00689 
00690 
00692 //
00693 //        METHOD NAME : MediaCurl::getFile
00694 //        METHOD TYPE : PMError
00695 //
00696 
00697 void MediaCurl::getFile( const Pathname & filename ) const
00698 {
00699     // Use absolute file name to prevent access of files outside of the
00700     // hierarchy below the attach point.
00701     getFileCopy(filename, localPath(filename).absolutename());
00702 }
00703 
00704 
00705 void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target) const
00706 {
00707   callback::SendReport<DownloadProgressReport> report;
00708 
00709   Url url( _url );
00710 
00711   try {
00712     doGetFileCopy(filename, target, report);
00713   }
00714   catch (MediaException & excpt_r)
00715   {
00716     // FIXME: this will not match the first URL
00717     // FIXME: error number fix
00718     report->finish(url, zypp::media::DownloadProgressReport::NOT_FOUND, excpt_r.msg());
00719     ZYPP_RETHROW(excpt_r);
00720   }
00721   report->finish(url, zypp::media::DownloadProgressReport::NO_ERROR, "");
00722 }
00723 
00724 bool MediaCurl::getDoesFileExist( const Pathname & filename ) const
00725 {
00726   DBG << filename.asString() << endl;
00727 
00728   if(!_url.isValid())
00729     ZYPP_THROW(MediaBadUrlException(_url));
00730 
00731   if(_url.getHost().empty())
00732     ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
00733 
00734   string path = _url.getPathName();
00735   if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
00736         filename.absolute() ) {
00737       // If url has a path with trailing slash, remove the leading slash from
00738       // the absolute file name
00739     path += filename.asString().substr( 1, filename.asString().size() - 1 );
00740   } else if ( filename.relative() ) {
00741       // Add trailing slash to path, if not already there
00742     if ( !path.empty() && *path.rbegin() != '/' ) path += "/";
00743     // Remove "./" from begin of relative file name
00744     path += filename.asString().substr( 2, filename.asString().size() - 2 );
00745   } else {
00746     path += filename.asString();
00747   }
00748 
00749   Url url( _url );
00750   url.setPathName( path );
00751 
00752   DBG << "URL: " << url.asString() << endl;
00753     // Use URL without options and without username and passwd
00754     // (some proxies dislike them in the URL).
00755     // Curl seems to need the just scheme, hostname and a path;
00756     // the rest was already passed as curl options (in attachTo).
00757   Url curlUrl( url );
00758 
00759     // Use asString + url::ViewOptions instead?
00760   curlUrl.setUsername( "" );
00761   curlUrl.setPassword( "" );
00762   curlUrl.setPathParams( "" );
00763   curlUrl.setQueryString( "" );
00764   curlUrl.setFragment( "" );
00765 
00766   //
00767     // See also Bug #154197 and ftp url definition in RFC 1738:
00768     // The url "ftp://user@host/foo/bar/file" contains a path,
00769     // that is relative to the user's home.
00770     // The url "ftp://user@host//foo/bar/file" (or also with
00771     // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
00772     // contains an absolute path.
00773   //
00774   string urlBuffer( curlUrl.asString());
00775   CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
00776                                    urlBuffer.c_str() );
00777   if ( ret != 0 ) {
00778     ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
00779   }
00780 
00781   // set no data, because we only want to check if the file exists
00782   //ret = curl_easy_setopt( _curl, CURLOPT_NOBODY, 1 );
00783   //if ( ret != 0 ) {
00784   //    ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
00785   //}
00786 
00787   // instead of returning no data with NOBODY, we return
00788   // little data, that works with broken servers, and
00789   // works for ftp as well, because retrieving only headers
00790   // ftp will return always OK code ?
00791   ret = curl_easy_setopt( _curl, CURLOPT_RANGE, "0-1" );
00792   if ( ret != 0 ) {
00793       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
00794   }
00795 
00796   FILE *file = ::fopen( "/dev/null", "w" );
00797   if ( !file ) {
00798       ::fclose(file);
00799       ERR << "fopen failed for /dev/null" << endl;
00800       curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
00801       if ( ret != 0 ) {
00802           ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
00803       }
00804       ZYPP_THROW(MediaWriteException("/dev/null"));
00805   }
00806 
00807   ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
00808   if ( ret != 0 ) {
00809       ::fclose(file);
00810       std::string err( _curlError);
00811       curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
00812       if ( ret != 0 ) {
00813           ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
00814       }
00815       ZYPP_THROW(MediaCurlSetOptException(url, err));
00816   }
00817     // Set callback and perform.
00818   //ProgressData progressData(_xfer_timeout, url, &report);
00819   //report->start(url, dest);
00820   //if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
00821   //  WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;;
00822   //}
00823 
00824   CURLcode ok = curl_easy_perform( _curl );
00825   MIL << "perform code: " << ok << " [ " << curl_easy_strerror(ok) << " ]" << endl;
00826 
00827   // reset curl settings
00828   ret = curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
00829   if ( ret != 0 )
00830   {
00831     ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
00832   }
00833 
00834   if ( ok != 0 )
00835   {
00836     ::fclose( file );
00837 
00838     std::string err;
00839     try
00840     {
00841       bool err_file_not_found = false;
00842       switch ( ok )
00843       {
00844       case CURLE_FTP_COULDNT_RETR_FILE:
00845       case CURLE_FTP_ACCESS_DENIED:
00846         err_file_not_found = true;
00847         break;
00848       case CURLE_HTTP_RETURNED_ERROR:
00849         {
00850           long httpReturnCode = 0;
00851           CURLcode infoRet = curl_easy_getinfo( _curl,
00852                                                 CURLINFO_RESPONSE_CODE,
00853                                                 &httpReturnCode );
00854           if ( infoRet == CURLE_OK )
00855           {
00856             string msg = "HTTP response: " +
00857                           str::numstring( httpReturnCode );
00858             if ( httpReturnCode == 401 )
00859             {
00860               err = " Login failed";
00861             }
00862             else
00863             if ( httpReturnCode == 403)
00864             {
00865                string msg403;
00866                if (url.asString().find("novell.com") != string::npos)
00867                  msg403 = str::form(_(
00868                    "Permission to access '%s' denied.\n\n"
00869                    "Visit the Novell Customer Center to check whether"
00870                    " your registration is valid and has not expired."),
00871                    url.asString().c_str());
00872 
00873                ZYPP_THROW(MediaForbiddenException(url, msg403));
00874             }
00875             else
00876             if ( httpReturnCode == 404)
00877             {
00878                err_file_not_found = true;
00879                break;
00880             }
00881 
00882             msg += err;
00883             DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
00884             ZYPP_THROW(MediaCurlException(url, msg, _curlError));
00885           }
00886           else
00887           {
00888             string msg = "Unable to retrieve HTTP response:";
00889             msg += err;
00890             DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
00891             ZYPP_THROW(MediaCurlException(url, msg, _curlError));
00892           }
00893         }
00894         break;
00895       case CURLE_UNSUPPORTED_PROTOCOL:
00896       case CURLE_URL_MALFORMAT:
00897       case CURLE_URL_MALFORMAT_USER:
00898       case CURLE_BAD_PASSWORD_ENTERED:
00899       case CURLE_FTP_USER_PASSWORD_INCORRECT:
00900       case CURLE_COULDNT_RESOLVE_PROXY:
00901       case CURLE_COULDNT_RESOLVE_HOST:
00902       case CURLE_COULDNT_CONNECT:
00903       case CURLE_FTP_CANT_GET_HOST:
00904       case CURLE_WRITE_ERROR:
00905       case CURLE_ABORTED_BY_CALLBACK:
00906       case CURLE_SSL_PEER_CERTIFICATE:
00907       default:
00908         err = curl_easy_strerror(ok);
00909         if (err.empty())
00910           err = "Unrecognized error";
00911         break;
00912       }
00913 
00914       if( err_file_not_found)
00915       {
00916         // file does not exists
00917         return false;
00918       }
00919       else
00920       {
00921         // there was an error
00922         ZYPP_THROW(MediaCurlException(url, string(), _curlError));
00923       }
00924     }
00925     catch (const MediaException & excpt_r)
00926     {
00927       ZYPP_RETHROW(excpt_r);
00928     }
00929   }
00930 
00931   // exists
00932   return ( ok == CURLE_OK );
00933   //if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
00934   //  WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;;
00935   //}
00936 }
00937 
00938 void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report) const
00939 {
00940     DBG << filename.asString() << endl;
00941 
00942     if(!_url.isValid())
00943       ZYPP_THROW(MediaBadUrlException(_url));
00944 
00945     if(_url.getHost().empty())
00946       ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
00947 
00948     string path = _url.getPathName();
00949     if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
00950          filename.absolute() ) {
00951       // If url has a path with trailing slash, remove the leading slash from
00952       // the absolute file name
00953       path += filename.asString().substr( 1, filename.asString().size() - 1 );
00954     } else if ( filename.relative() ) {
00955       // Add trailing slash to path, if not already there
00956       if ( !path.empty() && *path.rbegin() != '/' ) path += "/";
00957       // Remove "./" from begin of relative file name
00958       path += filename.asString().substr( 2, filename.asString().size() - 2 );
00959     } else {
00960       path += filename.asString();
00961     }
00962 
00963     Url url( _url );
00964     url.setPathName( path );
00965 
00966     Pathname dest = target.absolutename();
00967     if( assert_dir( dest.dirname() ) )
00968     {
00969       DBG << "assert_dir " << dest.dirname() << " failed" << endl;
00970       ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) );
00971     }
00972 
00973     DBG << "URL: " << url.asString() << endl;
00974     // Use URL without options and without username and passwd
00975     // (some proxies dislike them in the URL).
00976     // Curl seems to need the just scheme, hostname and a path;
00977     // the rest was already passed as curl options (in attachTo).
00978     Url curlUrl( url );
00979 
00980     // Use asString + url::ViewOptions instead?
00981     curlUrl.setUsername( "" );
00982     curlUrl.setPassword( "" );
00983     curlUrl.setPathParams( "" );
00984     curlUrl.setQueryString( "" );
00985     curlUrl.setFragment( "" );
00986 
00987     //
00988     // See also Bug #154197 and ftp url definition in RFC 1738:
00989     // The url "ftp://user@host/foo/bar/file" contains a path,
00990     // that is relative to the user's home.
00991     // The url "ftp://user@host//foo/bar/file" (or also with
00992     // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
00993     // contains an absolute path.
00994     //
00995     string urlBuffer( curlUrl.asString());
00996     CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
00997                                      urlBuffer.c_str() );
00998     if ( ret != 0 ) {
00999       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01000     }
01001 
01002     string destNew = target.asString() + ".new.zypp.XXXXXX";
01003     char *buf = ::strdup( destNew.c_str());
01004     if( !buf)
01005     {
01006       ERR << "out of memory for temp file name" << endl;
01007       ZYPP_THROW(MediaSystemException(
01008         url, "out of memory for temp file name"
01009       ));
01010     }
01011 
01012     int tmp_fd = ::mkstemp( buf );
01013     if( tmp_fd == -1)
01014     {
01015       free( buf);
01016       ERR << "mkstemp failed for file '" << destNew << "'" << endl;
01017       ZYPP_THROW(MediaWriteException(destNew));
01018     }
01019     destNew = buf;
01020     free( buf);
01021 
01022     FILE *file = ::fdopen( tmp_fd, "w" );
01023     if ( !file ) {
01024       ::close( tmp_fd);
01025       filesystem::unlink( destNew );
01026       ERR << "fopen failed for file '" << destNew << "'" << endl;
01027       ZYPP_THROW(MediaWriteException(destNew));
01028     }
01029 
01030     DBG << "dest: " << dest << endl;
01031     DBG << "temp: " << destNew << endl;
01032 
01033     ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
01034     if ( ret != 0 ) {
01035       ::fclose( file );
01036       filesystem::unlink( destNew );
01037       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
01038     }
01039 
01040     // Set callback and perform.
01041     ProgressData progressData(_xfer_timeout, url, &report);
01042     report->start(url, dest);
01043     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
01044       WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;;
01045     }
01046 
01047     ret = curl_easy_perform( _curl );
01048 
01049     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
01050       WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;;
01051     }
01052 
01053     if ( ret != 0 ) {
01054       ERR << "curl error: " << ret << ": " << _curlError
01055           << ", temp file size " << PathInfo(destNew).size()
01056           << " byte." << endl;
01057 
01058       ::fclose( file );
01059       filesystem::unlink( destNew );
01060 
01061       std::string err;
01062       try {
01063        bool err_file_not_found = false;
01064        switch ( ret ) {
01065         case CURLE_UNSUPPORTED_PROTOCOL:
01066         case CURLE_URL_MALFORMAT:
01067         case CURLE_URL_MALFORMAT_USER:
01068           err = " Bad URL";
01069         case CURLE_HTTP_RETURNED_ERROR:
01070           {
01071             long httpReturnCode = 0;
01072             CURLcode infoRet = curl_easy_getinfo( _curl,
01073                                                   CURLINFO_RESPONSE_CODE,
01074                                                   &httpReturnCode );
01075             if ( infoRet == CURLE_OK ) {
01076               string msg = "HTTP response: " +
01077                            str::numstring( httpReturnCode );
01078               if ( httpReturnCode == 401 )
01079               {
01080                 err = " Login failed";
01081               }
01082               else
01083               if ( httpReturnCode == 403)
01084               {
01085                  string msg403;
01086                  if (url.asString().find("novell.com") != string::npos)
01087                    msg403 = str::form(_(
01088                      "Permission to access '%s' denied.\n\n"
01089                      "Visit the Novell Customer Center to check whether"
01090                      " your registration is valid and has not expired."),
01091                      url.asString().c_str());
01092 
01093                  ZYPP_THROW(MediaForbiddenException(url, msg403));
01094               }
01095               else
01096               if ( httpReturnCode == 404)
01097               {
01098                  ZYPP_THROW(MediaFileNotFoundException(_url, filename));
01099               }
01100 
01101               msg += err;
01102               DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
01103               ZYPP_THROW(MediaCurlException(url, msg, _curlError));
01104             }
01105             else
01106             {
01107               string msg = "Unable to retrieve HTTP response:";
01108               msg += err;
01109               DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
01110               ZYPP_THROW(MediaCurlException(url, msg, _curlError));
01111             }
01112           }
01113           break;
01114         case CURLE_FTP_COULDNT_RETR_FILE:
01115         case CURLE_FTP_ACCESS_DENIED:
01116           err = "File not found";
01117           err_file_not_found = true;
01118           break;
01119         case CURLE_BAD_PASSWORD_ENTERED:
01120         case CURLE_FTP_USER_PASSWORD_INCORRECT:
01121           err = "Login failed";
01122           break;
01123         case CURLE_COULDNT_RESOLVE_PROXY:
01124         case CURLE_COULDNT_RESOLVE_HOST:
01125         case CURLE_COULDNT_CONNECT:
01126         case CURLE_FTP_CANT_GET_HOST:
01127           err = "Connection failed";
01128           break;
01129         case CURLE_WRITE_ERROR:
01130           err = "Write error";
01131           break;
01132         case CURLE_ABORTED_BY_CALLBACK:
01133           if( progressData.reached)
01134           {
01135             err  = "Timeout reached";
01136           }
01137           else
01138           {
01139             err = "User abort";
01140           }
01141           break;
01142         case CURLE_SSL_PEER_CERTIFICATE:
01143         default:
01144           err = "Unrecognized error";
01145           break;
01146        }
01147        if( err_file_not_found)
01148        {
01149          ZYPP_THROW(MediaFileNotFoundException(_url, filename));
01150        }
01151        else
01152        {
01153          ZYPP_THROW(MediaCurlException(url, err, _curlError));
01154        }
01155       }
01156       catch (const MediaException & excpt_r)
01157       {
01158         ZYPP_RETHROW(excpt_r);
01159       }
01160     }
01161 #if DETECT_DIR_INDEX
01162     else
01163     if(curlUrl.getScheme() == "http" ||
01164        curlUrl.getScheme() == "https")
01165     {
01166       //
01167       // try to check the effective url and set the not_a_file flag
01168       // if the url path ends with a "/", what usually means, that
01169       // we've received a directory index (index.html content).
01170       //
01171       // Note: This may be dangerous and break file retrieving in
01172       //       case of some server redirections ... ?
01173       //
01174       bool      not_a_file = false;
01175       char     *ptr = NULL;
01176       CURLcode  ret = curl_easy_getinfo( _curl,
01177                                          CURLINFO_EFFECTIVE_URL,
01178                                          &ptr);
01179       if ( ret == CURLE_OK && ptr != NULL)
01180       {
01181         try
01182         {
01183           Url         eurl( ptr);
01184           std::string path( eurl.getPathName());
01185           if( !path.empty() && path != "/" && *path.rbegin() == '/')
01186           {
01187             DBG << "Effective url ("
01188                 << eurl
01189                 << ") seems to provide the index of a directory"
01190                 << endl;
01191             not_a_file = true;
01192           }
01193         }
01194         catch( ... )
01195         {}
01196       }
01197 
01198       if( not_a_file)
01199       {
01200         ::fclose( file );
01201         filesystem::unlink( destNew );
01202         ZYPP_THROW(MediaNotAFileException(_url, filename));
01203       }
01204     }
01205 #endif // DETECT_DIR_INDEX
01206 
01207     mode_t mask;
01208     // getumask() would be fine, but does not exist
01209     // [ the linker can't find it in glibc :-( ].
01210     mask = ::umask(0022); ::umask(mask);
01211     if ( ::fchmod( ::fileno(file), 0644 & ~mask))
01212     {
01213       ERR << "Failed to chmod file " << destNew << endl;
01214     }
01215     ::fclose( file );
01216 
01217     if ( rename( destNew, dest ) != 0 ) {
01218       ERR << "Rename failed" << endl;
01219       ZYPP_THROW(MediaWriteException(dest));
01220     }
01221     DBG << "done: " << PathInfo(dest) << endl;
01222 }
01223 
01224 
01226 //
01227 //
01228 //        METHOD NAME : MediaCurl::getDir
01229 //        METHOD TYPE : PMError
01230 //
01231 //        DESCRIPTION : Asserted that media is attached
01232 //
01233 void MediaCurl::getDir( const Pathname & dirname, bool recurse_r ) const
01234 {
01235   filesystem::DirContent content;
01236   getDirInfo( content, dirname, /*dots*/false );
01237 
01238   for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it ) {
01239       Pathname filename = dirname + it->name;
01240       int res = 0;
01241 
01242       switch ( it->type ) {
01243       case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
01244       case filesystem::FT_FILE:
01245         getFile( filename );
01246         break;
01247       case filesystem::FT_DIR: // newer directory.yast contain at least directory info
01248         if ( recurse_r ) {
01249           getDir( filename, recurse_r );
01250         } else {
01251           res = assert_dir( localPath( filename ) );
01252           if ( res ) {
01253             WAR << "Ignore error (" << res <<  ") on creating local directory '" << localPath( filename ) << "'" << endl;
01254           }
01255         }
01256         break;
01257       default:
01258         // don't provide devices, sockets, etc.
01259         break;
01260       }
01261   }
01262 }
01263 
01265 //
01266 //
01267 //        METHOD NAME : MediaCurl::getDirInfo
01268 //        METHOD TYPE : PMError
01269 //
01270 //        DESCRIPTION : Asserted that media is attached and retlist is empty.
01271 //
01272 void MediaCurl::getDirInfo( std::list<std::string> & retlist,
01273                                const Pathname & dirname, bool dots ) const
01274 {
01275   getDirectoryYast( retlist, dirname, dots );
01276 }
01277 
01279 //
01280 //
01281 //        METHOD NAME : MediaCurl::getDirInfo
01282 //        METHOD TYPE : PMError
01283 //
01284 //        DESCRIPTION : Asserted that media is attached and retlist is empty.
01285 //
01286 void MediaCurl::getDirInfo( filesystem::DirContent & retlist,
01287                             const Pathname & dirname, bool dots ) const
01288 {
01289   getDirectoryYast( retlist, dirname, dots );
01290 }
01291 
01293 //
01294 //
01295 //        METHOD NAME : MediaCurl::progressCallback
01296 //        METHOD TYPE : int
01297 //
01298 //        DESCRIPTION : Progress callback triggered from MediaCurl::getFile
01299 //
01300 int MediaCurl::progressCallback( void *clientp, double dltotal, double dlnow,
01301                                  double ultotal, double ulnow )
01302 {
01303   ProgressData *pdata = reinterpret_cast<ProgressData *>(clientp);
01304   if( pdata)
01305   {
01306     // send progress report first, abort transfer if requested
01307     if( pdata->report)
01308     {
01309       if (! (*(pdata->report))->progress(int( dlnow * 100 / dltotal ), pdata->url))
01310       {
01311         return 1; // abort transfer
01312       }
01313     }
01314 
01315     // check if we there is a timeout set
01316     if( pdata->timeout > 0)
01317     {
01318       time_t now = time(NULL);
01319       if( now > 0)
01320       {
01321         bool progress = false;
01322 
01323         // reset time of last change in case initial time()
01324         // failed or the time was adjusted (goes backward)
01325         if( pdata->ltime <= 0 || pdata->ltime > now)
01326           pdata->ltime = now;
01327 
01328         // update download data if changed, mark progress
01329         if( dlnow != pdata->dload)
01330         {
01331           progress     = true;
01332           pdata->dload = dlnow;
01333           pdata->ltime = now;
01334         }
01335         // update upload data if changed, mark progress
01336         if( ulnow != pdata->uload)
01337         {
01338           progress     = true;
01339           pdata->uload = ulnow;
01340           pdata->ltime = now;
01341         }
01342 
01343         if( !progress && (now >= (pdata->ltime + pdata->timeout)))
01344         {
01345           pdata->reached = true;
01346           return 1; // aborts transfer
01347         }
01348       }
01349     }
01350   }
01351   return 0;
01352 }
01353 
01354 
01355   } // namespace media
01356 } // namespace zypp

Generated on Sat Sep 5 12:40:19 2009 for zypp by  doxygen 1.4.6