]> git.saurik.com Git - apt.git/blobdiff - methods/https.cc
Merge remote-tracking branch 'mvo/feature/apt-show-nice' into debian/experimental...
[apt.git] / methods / https.cc
index 317c8a5872b2bf970aabc63d2d0ba0e023da6224..2a562434b42dab471ae732be2e045a607a6b1957 100644 (file)
                                                                        /*}}}*/
 using namespace std;
 
+size_t
+HttpsMethod::parse_header(void *buffer, size_t size, size_t nmemb, void *userp)
+{
+   size_t len = size * nmemb;
+   HttpsMethod *me = (HttpsMethod *)userp;
+   std::string line((char*) buffer, len);
+   for (--len; len > 0; --len)
+      if (isspace(line[len]) == 0)
+      {
+        ++len;
+        break;
+      }
+   line.erase(len);
+
+   if (line.empty() == true)
+   {
+      if (me->Server->Result != 416 && me->Server->StartPos != 0)
+        ;
+      else if (me->Server->Result == 416 && me->Server->Size == me->File->FileSize())
+      {
+         me->Server->Result = 200;
+        me->Server->StartPos = me->Server->Size;
+      }
+      else
+        me->Server->StartPos = 0;
+
+      me->File->Truncate(me->Server->StartPos);
+      me->File->Seek(me->Server->StartPos);
+   }
+   else if (me->Server->HeaderLine(line) == false)
+      return 0;
+
+   return size*nmemb;
+}
+
 size_t 
 HttpsMethod::write_data(void *buffer, size_t size, size_t nmemb, void *userp)
 {
@@ -59,10 +94,24 @@ HttpsMethod::progress_callback(void *clientp, double dltotal, double dlnow,
    return 0;
 }
 
+// HttpsServerState::HttpsServerState - Constructor                    /*{{{*/
+HttpsServerState::HttpsServerState(URI Srv,HttpsMethod *Owner) : ServerState(Srv, NULL)
+{
+   TimeOut = _config->FindI("Acquire::https::Timeout",TimeOut);
+   Reset();
+}
+                                                                       /*}}}*/
+
 void HttpsMethod::SetupProxy()                                         /*{{{*/
 {
    URI ServerName = Queue->Uri;
 
+   // Curl should never read proxy settings from the environment, as
+   // we determine which proxy to use.  Do this for consistency among
+   // methods and prevent an environment variable overriding a
+   // no-proxy ("DIRECT") setting in apt.conf.
+   curl_easy_setopt(curl, CURLOPT_PROXY, "");
+
    // Determine the proxy setting - try https first, fallback to http and use env at last
    string UseProxy = _config->Find("Acquire::https::Proxy::" + ServerName.Host,
                                   _config->Find("Acquire::http::Proxy::" + ServerName.Host).c_str());
@@ -81,7 +130,14 @@ void HttpsMethod::SetupProxy()                                      /*{{{*/
       if (getenv("no_proxy") != 0 && CheckDomainList(ServerName.Host,getenv("no_proxy")) == true)
         return;
    } else {
-      const char* result = getenv("http_proxy");
+      const char* result = getenv("https_proxy");
+      // FIXME: Fall back to http_proxy is to remain compatible with
+      // existing setups and behaviour of apt.conf.  This should be
+      // deprecated in the future (including apt.conf).  Most other
+      // programs do not fall back to http proxy settings and neither
+      // should Apt.
+      if (result == NULL)
+         result = getenv("http_proxy");
       UseProxy = result == NULL ? "" : result;
    }
 
@@ -92,6 +148,11 @@ void HttpsMethod::SetupProxy()                                      /*{{{*/
       if (Proxy.Port != 1)
         curl_easy_setopt(curl, CURLOPT_PROXYPORT, Proxy.Port);
       curl_easy_setopt(curl, CURLOPT_PROXY, Proxy.Host.c_str());
+      if (Proxy.User.empty() == false || Proxy.Password.empty() == false)
+      {
+         curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME, Proxy.User.c_str());
+         curl_easy_setopt(curl, CURLOPT_PROXYPASSWORD, Proxy.Password.c_str());
+      }
    }
 }                                                                      /*}}}*/
 // HttpsMethod::Fetch - Fetch an item                                  /*{{{*/
@@ -100,11 +161,9 @@ void HttpsMethod::SetupProxy()                                     /*{{{*/
    depth. */
 bool HttpsMethod::Fetch(FetchItem *Itm)
 {
-   stringstream ss;
    struct stat SBuf;
    struct curl_slist *headers=NULL;  
    char curl_errorstr[CURL_ERROR_SIZE];
-   long curl_responsecode;
    URI Uri = Itm->Uri;
    string remotehost = Uri.Host;
 
@@ -120,12 +179,13 @@ bool HttpsMethod::Fetch(FetchItem *Itm)
 
    // callbacks
    curl_easy_setopt(curl, CURLOPT_URL, static_cast<string>(Uri).c_str());
+   curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, parse_header);
+   curl_easy_setopt(curl, CURLOPT_WRITEHEADER, this);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
    curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progress_callback);
    curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, this);
    curl_easy_setopt(curl, CURLOPT_NOPROGRESS, false);
-   curl_easy_setopt(curl, CURLOPT_FAILONERROR, true);
    curl_easy_setopt(curl, CURLOPT_FILETIME, true);
 
    // SSL parameters are set by default to the common (non mirror-specific) value
@@ -199,6 +259,7 @@ bool HttpsMethod::Fetch(FetchItem *Itm)
       if (_config->FindB("Acquire::https::No-Store",
                _config->FindB("Acquire::http::No-Store",false)) == true)
         headers = curl_slist_append(headers,"Cache-Control: no-store");
+      stringstream ss;
       ioprintf(ss, "Cache-Control: max-age=%u", _config->FindI("Acquire::https::Max-Age",
                _config->FindI("Acquire::http::Max-Age",0)));
       headers = curl_slist_append(headers, ss.str().c_str());
@@ -219,7 +280,7 @@ bool HttpsMethod::Fetch(FetchItem *Itm)
    curl_easy_setopt(curl, CURLOPT_USERAGENT,
        _config->Find("Acquire::https::User-Agent",
                _config->Find("Acquire::http::User-Agent",
-                       "Debian APT-CURL/1.0 ("VERSION")").c_str()).c_str());
+                       "Debian APT-CURL/1.0 (" PACKAGE_VERSION ")").c_str()).c_str());
 
    // set timeout
    int const timeout = _config->FindI("Acquire::https::Timeout",
@@ -240,17 +301,31 @@ bool HttpsMethod::Fetch(FetchItem *Itm)
       curl_easy_setopt(curl, CURLOPT_VERBOSE, true);
 
    // error handling
+   curl_errorstr[0] = '\0';
    curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
 
+   // If we ask for uncompressed files servers might respond with content-
+   // negotation which lets us end up with compressed files we do not support,
+   // see 657029, 657560 and co, so if we have no extension on the request
+   // ask for text only. As a sidenote: If there is nothing to negotate servers
+   // seem to be nice and ignore it.
+   if (_config->FindB("Acquire::https::SendAccept", _config->FindB("Acquire::http::SendAccept", true)) == true)
+   {
+      size_t const filepos = Itm->Uri.find_last_of('/');
+      string const file = Itm->Uri.substr(filepos + 1);
+      if (flExtension(file) == file)
+        headers = curl_slist_append(headers, "Accept: text/*");
+   }
+
    // if we have the file send an if-range query with a range header
    if (stat(Itm->DestFile.c_str(),&SBuf) >= 0 && SBuf.st_size > 0)
    {
       char Buf[1000];
-      sprintf(Buf,"Range: bytes=%li-\r\nIf-Range: %s\r\n",
-             (long)SBuf.st_size - 1,
-             TimeRFC1123(SBuf.st_mtime).c_str());
+      sprintf(Buf, "Range: bytes=%li-", (long) SBuf.st_size);
+      headers = curl_slist_append(headers, Buf);
+      sprintf(Buf, "If-Range: %s", TimeRFC1123(SBuf.st_mtime).c_str());
       headers = curl_slist_append(headers, Buf);
-   } 
+   }
    else if(Itm->LastModified > 0)
    {
       curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
@@ -259,71 +334,97 @@ bool HttpsMethod::Fetch(FetchItem *Itm)
 
    // go for it - if the file exists, append on it
    File = new FileFd(Itm->DestFile, FileFd::WriteAny);
-   if (File->Size() > 0)
-      File->Seek(File->Size() - 1);
-   
+   Server = new HttpsServerState(Itm->Uri, this);
+
    // keep apt updated
    Res.Filename = Itm->DestFile;
 
    // get it!
    CURLcode success = curl_easy_perform(curl);
-   curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &curl_responsecode);
 
-   long curl_servdate;
-   curl_easy_getinfo(curl, CURLINFO_FILETIME, &curl_servdate);
+   // If the server returns 200 OK but the If-Modified-Since condition is not
+   // met, CURLINFO_CONDITION_UNMET will be set to 1
+   long curl_condition_unmet = 0;
+   curl_easy_getinfo(curl, CURLINFO_CONDITION_UNMET, &curl_condition_unmet);
 
    File->Close();
+   curl_slist_free_all(headers);
 
    // cleanup
-   if(success != 0) 
+   if (success != 0)
    {
       _error->Error("%s", curl_errorstr);
-      // unlink, no need keep 401/404 page content in partial/
       unlink(File->Name().c_str());
-      Fail();
+      return false;
+   }
+
+   // server says file not modified
+   if (Server->Result == 304 || curl_condition_unmet == 1)
+   {
+      unlink(File->Name().c_str());
+      Res.IMSHit = true;
+      Res.LastModified = Itm->LastModified;
+      Res.Size = 0;
+      URIDone(Res);
       return true;
    }
+   Res.IMSHit = false;
 
-   // Timestamp
-   struct utimbuf UBuf;
-   if (curl_servdate != -1) {
-       UBuf.actime = curl_servdate;
-       UBuf.modtime = curl_servdate;
-       utime(File->Name().c_str(),&UBuf);
+   if (Server->Result != 200 && // OK
+        Server->Result != 206 && // Partial
+        Server->Result != 416) // invalid Range
+   {
+      char err[255];
+      snprintf(err, sizeof(err) - 1, "HttpError%i", Server->Result);
+      SetFailReason(err);
+      _error->Error("%s", err);
+      // unlink, no need keep 401/404 page content in partial/
+      unlink(File->Name().c_str());
+      return false;
    }
 
-   // check the downloaded result
-   struct stat Buf;
-   if (stat(File->Name().c_str(),&Buf) == 0)
+   struct stat resultStat;
+   if (unlikely(stat(File->Name().c_str(), &resultStat) != 0))
    {
-      Res.Filename = File->Name();
-      Res.LastModified = Buf.st_mtime;
-      Res.IMSHit = false;
-      if (curl_responsecode == 304)
-      {
-        unlink(File->Name().c_str());
-        Res.IMSHit = true;
-        Res.LastModified = Itm->LastModified;
-        Res.Size = 0;
-        URIDone(Res);
-        return true;
-      }
-      Res.Size = Buf.st_size;
+      _error->Errno("stat", "Unable to access file %s", File->Name().c_str());
+      return false;
+   }
+   Res.Size = resultStat.st_size;
+
+   // invalid range-request
+   if (Server->Result == 416)
+   {
+      unlink(File->Name().c_str());
+      Res.Size = 0;
+      delete File;
+      Redirect(Itm->Uri);
+      return true;
+   }
+
+   // Timestamp
+   curl_easy_getinfo(curl, CURLINFO_FILETIME, &Res.LastModified);
+   if (Res.LastModified != -1)
+   {
+      struct utimbuf UBuf;
+      UBuf.actime = Res.LastModified;
+      UBuf.modtime = Res.LastModified;
+      utime(File->Name().c_str(),&UBuf);
    }
+   else
+      Res.LastModified = resultStat.st_mtime;
 
    // take hashes
    Hashes Hash;
    FileFd Fd(Res.Filename, FileFd::ReadOnly);
    Hash.AddFD(Fd);
    Res.TakeHashes(Hash);
-   
+
    // keep apt updated
    URIDone(Res);
 
    // cleanup
    Res.Size = 0;
    delete File;
-   curl_slist_free_all(headers);
 
    return true;
 };
@@ -338,4 +439,3 @@ int main()
    return Mth.Run();
 }
 
-