]> git.saurik.com Git - apt.git/blobdiff - methods/http.cc
merge from lp:~mvo/apt/mvo
[apt.git] / methods / http.cc
index 3c2d8a36f455be9189eac218bf2a8affdd803f4f..13f9cbe0613a01d4c102961efc6a15248c6343e8 100644 (file)
@@ -29,6 +29,7 @@
 #include <apt-pkg/acquire-method.h>
 #include <apt-pkg/error.h>
 #include <apt-pkg/hashes.h>
+#include <apt-pkg/netrc.h>
 
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <errno.h>
 #include <string.h>
 #include <iostream>
+#include <map>
 #include <apti18n.h>
 
+
 // Internet stuff
 #include <netdb.h>
 
@@ -48,7 +51,6 @@
 #include "connect.h"
 #include "rfc2553emu.h"
 #include "http.h"
-
                                                                        /*}}}*/
 using namespace std;
 
@@ -57,6 +59,7 @@ int HttpMethod::FailFd = -1;
 time_t HttpMethod::FailTime = 0;
 unsigned long PipelineDepth = 10;
 unsigned long TimeOut = 120;
+bool AllowRedirect = false;
 bool Debug = false;
 URI Proxy;
 
@@ -64,7 +67,7 @@ unsigned long CircleBuf::BwReadLimit=0;
 unsigned long CircleBuf::BwTickReadData=0;
 struct timeval CircleBuf::BwReadTick={0,0};
 const unsigned int CircleBuf::BW_HZ=10;
-  
 // CircleBuf::CircleBuf - Circular input buffer                                /*{{{*/
 // ---------------------------------------------------------------------
 /* */
@@ -309,22 +312,27 @@ bool ServerState::Open()
    Persistent = true;
    
    // Determine the proxy setting
-   if (getenv("http_proxy") == 0)
+   string SpecificProxy = _config->Find("Acquire::http::Proxy::" + ServerName.Host);
+   if (!SpecificProxy.empty())
    {
-      string DefProxy = _config->Find("Acquire::http::Proxy");
-      string SpecificProxy = _config->Find("Acquire::http::Proxy::" + ServerName.Host);
-      if (SpecificProxy.empty() == false)
-      {
-        if (SpecificProxy == "DIRECT")
-           Proxy = "";
-        else
-           Proxy = SpecificProxy;
-      }   
-      else
-        Proxy = DefProxy;
+          if (SpecificProxy == "DIRECT")
+                  Proxy = "";
+          else
+                  Proxy = SpecificProxy;
    }
    else
-      Proxy = getenv("http_proxy");
+   {
+          string DefProxy = _config->Find("Acquire::http::Proxy");
+          if (!DefProxy.empty())
+          {
+                  Proxy = DefProxy;
+          }
+          else
+          {
+                  char* result = getenv("http_proxy");
+                  Proxy = result ? result : "";
+          }
+   }
    
    // Parse no_proxy, a , separated list of domains
    if (getenv("no_proxy") != 0)
@@ -368,9 +376,9 @@ bool ServerState::Close()
                                                                        /*}}}*/
 // ServerState::RunHeaders - Get the headers before the data           /*{{{*/
 // ---------------------------------------------------------------------
-/* Returns 0 if things are OK, 1 if an IO error occursed and 2 if a header
-   parse error occured */
-int ServerState::RunHeaders()
+/* Returns 0 if things are OK, 1 if an IO error occurred and 2 if a header
+   parse error occurred */
+ServerState::RunHeadersResult ServerState::RunHeaders()
 {
    State = Header;
    
@@ -399,7 +407,7 @@ int ServerState::RunHeaders()
         string::const_iterator J = I;
         for (; J != Data.end() && *J != '\n' && *J != '\r';J++);
         if (HeaderLine(string(I,J)) == false)
-           return 2;
+           return RUN_HEADERS_PARSE_ERROR;
         I = J;
       }
 
@@ -411,11 +419,11 @@ int ServerState::RunHeaders()
       if (Encoding == Closes && HaveContent == true)
         Persistent = false;
       
-      return 0;
+      return RUN_HEADERS_OK;
    }
    while (Owner->Go(false,this) == true);
    
-   return 1;
+   return RUN_HEADERS_IO_ERROR;
 }
                                                                        /*}}}*/
 // ServerState::RunData - Transfer the data from the socket            /*{{{*/
@@ -545,15 +553,21 @@ bool ServerState::HeaderLine(string Line)
       // Evil servers return no version
       if (Line[4] == '/')
       {
-        if (sscanf(Line.c_str(),"HTTP/%u.%u %u %[^\n]",&Major,&Minor,
-                   &Result,Code) != 4)
+        int const elements = sscanf(Line.c_str(),"HTTP/%u.%u %u%[^\n]",&Major,&Minor,&Result,Code);
+        if (elements == 3)
+        {
+           Code[0] = '\0';
+           if (Debug == true)
+              clog << "HTTP server doesn't give Reason-Phrase for " << Result << std::endl;
+        }
+        else if (elements != 4)
            return _error->Error(_("The HTTP server sent an invalid reply header"));
       }
       else
       {
         Major = 0;
         Minor = 9;
-        if (sscanf(Line.c_str(),"HTTP %u %[^\n]",&Result,Code) != 2)
+        if (sscanf(Line.c_str(),"HTTP %u%[^\n]",&Result,Code) != 2)
            return _error->Error(_("The HTTP server sent an invalid reply header"));
       }
 
@@ -623,11 +637,17 @@ bool ServerState::HeaderLine(string Line)
    
    if (stringcasecmp(Tag,"Last-Modified:") == 0)
    {
-      if (StrToTime(Val,Date) == false)
+      if (RFC1123StrToTime(Val.c_str(), Date) == false)
         return _error->Error(_("Unknown date format"));
       return true;
    }
 
+   if (stringcasecmp(Tag,"Location:") == 0)
+   {
+      Location = Val;
+      return true;
+   }
+
    return true;
 }
                                                                        /*}}}*/
@@ -668,23 +688,25 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out)
                 and a no-store directive for archives. */
       sprintf(Buf,"GET %s HTTP/1.1\r\nHost: %s\r\n",
              Itm->Uri.c_str(),ProperHost.c_str());
-      // only generate a cache control header if we actually want to 
-      // use a cache
-      if (_config->FindB("Acquire::http::No-Cache",false) == false)
+   }
+   // generate a cache control header (if needed)
+   if (_config->FindB("Acquire::http::No-Cache",false) == true) 
+   {
+      strcat(Buf,"Cache-Control: no-cache\r\nPragma: no-cache\r\n");
+   }
+   else
+   {
+      if (Itm->IndexFile == true) 
       {
-        if (Itm->IndexFile == true)
-           sprintf(Buf+strlen(Buf),"Cache-Control: max-age=%u\r\n",
-                   _config->FindI("Acquire::http::Max-Age",0));
-        else
-        {
-           if (_config->FindB("Acquire::http::No-Store",false) == true)
-              strcat(Buf,"Cache-Control: no-store\r\n");
-        }       
+        sprintf(Buf+strlen(Buf),"Cache-Control: max-age=%u\r\n",
+                _config->FindI("Acquire::http::Max-Age",0));
+      }
+      else
+      {
+        if (_config->FindB("Acquire::http::No-Store",false) == true)
+           strcat(Buf,"Cache-Control: no-store\r\n");
       }
    }
-   // generate a no-cache header if needed
-   if (_config->FindB("Acquire::http::No-Cache",false) == true)
-      strcat(Buf,"Cache-Control: no-cache\r\nPragma: no-cache\r\n");
 
    
    string Req = Buf;
@@ -711,11 +733,14 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out)
       Req += string("Proxy-Authorization: Basic ") + 
           Base64Encode(Proxy.User + ":" + Proxy.Password) + "\r\n";
 
+   maybe_add_auth (Uri, _config->FindFile("Dir::Etc::netrc"));
    if (Uri.User.empty() == false || Uri.Password.empty() == false)
+   {
       Req += string("Authorization: Basic ") + 
           Base64Encode(Uri.User + ":" + Uri.Password) + "\r\n";
-   
-   Req += "User-Agent: Debian APT-HTTP/1.3 ("VERSION")\r\n\r\n";
+   }
+   Req += "User-Agent: " + _config->Find("Acquire::http::User-Agent",
+               "Debian APT-HTTP/1.3 ("VERSION")") + "\r\n\r\n";
    
    if (Debug == true)
       cerr << Req << endl;
@@ -753,9 +778,10 @@ bool HttpMethod::Go(bool ToFile,ServerState *Srv)
    
    if (Srv->In.WriteSpace() == true && ToFile == true && FileFD != -1)
       FD_SET(FileFD,&wfds);
-   
+
    // Add stdin
-   FD_SET(STDIN_FILENO,&rfds);
+   if (_config->FindB("Acquire::http::DependOnSTDIN", true) == true)
+      FD_SET(STDIN_FILENO,&rfds);
          
    // Figure out the max fd
    int MaxFd = FileFD;
@@ -895,13 +921,10 @@ bool HttpMethod::ServerDie(ServerState *Srv)
 // HttpMethod::DealWithHeaders - Handle the retrieved header data      /*{{{*/
 // ---------------------------------------------------------------------
 /* We look at the header data we got back from the server and decide what
-   to do. Returns 
-     0 - File is open,
-     1 - IMS hit
-     3 - Unrecoverable error 
-     4 - Error with error content page
-     5 - Unrecoverable non-server error (close the connection) */
-int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv)
+   to do. Returns DealWithHeadersResult (see http.h for details).
+ */
+HttpMethod::DealWithHeadersResult
+HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv)
 {
    // Not Modified
    if (Srv->Result == 304)
@@ -909,17 +932,57 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv)
       unlink(Queue->DestFile.c_str());
       Res.IMSHit = true;
       Res.LastModified = Queue->LastModified;
-      return 1;
+      return IMS_HIT;
    }
    
+   /* Redirect
+    *
+    * Note that it is only OK for us to treat all redirection the same
+    * because we *always* use GET, not other HTTP methods.  There are
+    * three redirection codes for which it is not appropriate that we
+    * redirect.  Pass on those codes so the error handling kicks in.
+    */
+   if (AllowRedirect
+       && (Srv->Result > 300 && Srv->Result < 400)
+       && (Srv->Result != 300       // Multiple Choices
+           && Srv->Result != 304    // Not Modified
+           && Srv->Result != 306))  // (Not part of HTTP/1.1, reserved)
+   {
+      if (Srv->Location.empty() == true);
+      else if (Srv->Location[0] == '/' && Queue->Uri.empty() == false)
+      {
+        URI Uri = Queue->Uri;
+        if (Uri.Host.empty() == false)
+        {
+           if (Uri.Port != 0)
+              strprintf(NextURI, "http://%s:%u", Uri.Host.c_str(), Uri.Port);
+           else
+              NextURI = "http://" + Uri.Host;
+        }
+        else
+           NextURI.clear();
+        NextURI.append(DeQuoteString(Srv->Location));
+        return TRY_AGAIN_OR_REDIRECT;
+      }
+      else
+      {
+         NextURI = DeQuoteString(Srv->Location);
+         return TRY_AGAIN_OR_REDIRECT;
+      }
+      /* else pass through for error message */
+   }
    /* We have a reply we dont handle. This should indicate a perm server
       failure */
    if (Srv->Result < 200 || Srv->Result >= 300)
    {
+      char err[255];
+      snprintf(err,sizeof(err)-1,"HttpError%i",Srv->Result);
+      SetFailReason(err);
       _error->Error("%u %s",Srv->Result,Srv->Code);
       if (Srv->HaveContent == true)
-        return 4;
-      return 3;
+        return ERROR_WITH_CONTENT_PAGE;
+      return ERROR_UNRECOVERABLE;
    }
 
    // This is some sort of 2xx 'data follows' reply
@@ -930,7 +993,7 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv)
    delete File;
    File = new FileFd(Queue->DestFile,FileFd::WriteAny);
    if (_error->PendingError() == true)
-      return 5;
+      return ERROR_NOT_FROM_SERVER;
 
    FailFile = Queue->DestFile;
    FailFile.c_str();   // Make sure we dont do a malloc in the signal handler
@@ -941,7 +1004,8 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv)
    if (Srv->StartPos >= 0)
    {
       Res.ResumePoint = Srv->StartPos;
-      ftruncate(File->Fd(),Srv->StartPos);
+      if (ftruncate(File->Fd(),Srv->StartPos) < 0)
+        _error->Errno("ftruncate", _("Failed to truncate file"));
    }
       
    // Set the start point
@@ -957,13 +1021,13 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv)
       if (Srv->In.Hash->AddFD(File->Fd(),Srv->StartPos) == false)
       {
         _error->Errno("read",_("Problem hashing file"));
-        return 5;
+        return ERROR_NOT_FROM_SERVER;
       }
       lseek(File->Fd(),0,SEEK_END);
    }
    
    SetNonBlock(File->Fd(),true);
-   return 0;
+   return FILE_IS_OPEN;
 }
                                                                        /*}}}*/
 // HttpMethod::SigTerm - Handle a fatal signal                         /*{{{*/
@@ -996,7 +1060,6 @@ bool HttpMethod::Fetch(FetchItem *)
 
    // Queue the requests
    int Depth = -1;
-   bool Tail = false;
    for (FetchItem *I = Queue; I != 0 && Depth < (signed)PipelineDepth; 
        I = I->Next, Depth++)
    {
@@ -1008,8 +1071,6 @@ bool HttpMethod::Fetch(FetchItem *)
       if (Server->Comp(I->Uri) == false)
         break;
       if (QueueBack == I)
-        Tail = true;
-      if (Tail == true)
       {
         QueueBack = I->Next;
         SendReq(I,Server->Out);
@@ -1028,11 +1089,16 @@ bool HttpMethod::Configuration(string Message)
    if (pkgAcqMethod::Configuration(Message) == false)
       return false;
    
+   AllowRedirect = _config->FindB("Acquire::http::AllowRedirect",true);
    TimeOut = _config->FindI("Acquire::http::Timeout",TimeOut);
    PipelineDepth = _config->FindI("Acquire::http::Pipeline-Depth",
                                  PipelineDepth);
    Debug = _config->FindB("Debug::Acquire::http",false);
-   
+   AutoDetectProxyCmd = _config->Find("Acquire::http::ProxyAutoDetect");
+
+   // Get the proxy to use
+   AutoDetectProxy();
+
    return true;
 }
                                                                        /*}}}*/
@@ -1041,6 +1107,10 @@ bool HttpMethod::Configuration(string Message)
 /* */
 int HttpMethod::Loop()
 {
+   typedef vector<string> StringVector;
+   typedef vector<string>::iterator StringVectorIterator;
+   map<string, StringVector> Redirected;
+
    signal(SIGTERM,SigTerm);
    signal(SIGINT,SigTerm);
    
@@ -1060,7 +1130,13 @@ int HttpMethod::Loop()
          do a WaitFd above.. Otherwise the FD is closed. */
       int Result = Run(true);
       if (Result != -1 && (Result != 0 || Queue == 0))
-        return 100;
+      {
+        if(FailReason.empty() == false ||
+           _config->FindB("Acquire::http::DependOnSTDIN", true) == true)
+           return 100;
+        else
+           return 0;
+      }
 
       if (Queue == 0)
         continue;
@@ -1071,7 +1147,6 @@ int HttpMethod::Loop()
         delete Server;
         Server = new ServerState(Queue->Uri,this);
       }
-      
       /* If the server has explicitly said this is the last connection
          then we pre-emptively shut down the pipeline and tear down 
         the connection. This will speed up HTTP/1.0 servers a tad
@@ -1099,11 +1174,11 @@ int HttpMethod::Loop()
       // Fetch the next URL header data from the server.
       switch (Server->RunHeaders())
       {
-        case 0:
+        case ServerState::RUN_HEADERS_OK:
         break;
         
         // The header data is bad
-        case 2:
+        case ServerState::RUN_HEADERS_PARSE_ERROR:
         {
            _error->Error(_("Bad header data"));
            Fail(true);
@@ -1113,7 +1188,7 @@ int HttpMethod::Loop()
         
         // The server closed a connection during the header get..
         default:
-        case 1:
+        case ServerState::RUN_HEADERS_IO_ERROR:
         {
            FailCounter++;
            _error->Discard();
@@ -1137,7 +1212,7 @@ int HttpMethod::Loop()
       switch (DealWithHeaders(Res,Server))
       {
         // Ok, the file is Open
-        case 0:
+        case FILE_IS_OPEN:
         {
            URIStart(Res);
 
@@ -1168,27 +1243,43 @@ int HttpMethod::Loop()
               URIDone(Res);
            }
            else
-              Fail(true);
-           
+           {
+              if (Server->ServerFd == -1)
+              {
+                 FailCounter++;
+                 _error->Discard();
+                 Server->Close();
+                 
+                 if (FailCounter >= 2)
+                 {
+                    Fail(_("Connection failed"),true);
+                    FailCounter = 0;
+                 }
+                 
+                 QueueBack = Queue;
+              }
+              else
+                 Fail(true);
+           }
            break;
         }
         
         // IMS hit
-        case 1:
+        case IMS_HIT:
         {
            URIDone(Res);
            break;
         }
         
         // Hard server error, not found or something
-        case 3:
+        case ERROR_UNRECOVERABLE:
         {
            Fail();
            break;
         }
          
         // Hard internal error, kill the connection and fail
-        case 5:
+        case ERROR_NOT_FROM_SERVER:
         {
            delete File;
            File = 0;
@@ -1200,7 +1291,7 @@ int HttpMethod::Loop()
         }
 
         // We need to flush the data, the header is like a 404 w/ error text
-        case 4:
+        case ERROR_WITH_CONTENT_PAGE:
         {
            Fail();
            
@@ -1212,6 +1303,46 @@ int HttpMethod::Loop()
            break;
         }
         
+         // Try again with a new URL
+         case TRY_AGAIN_OR_REDIRECT:
+         {
+            // Clear rest of response if there is content
+            if (Server->HaveContent)
+            {
+               File = new FileFd("/dev/null",FileFd::WriteExists);
+               Server->RunData();
+               delete File;
+               File = 0;
+            }
+
+            /* Detect redirect loops.  No more redirects are allowed
+               after the same URI is seen twice in a queue item. */
+            StringVector &R = Redirected[Queue->DestFile];
+            bool StopRedirects = false;
+            if (R.size() == 0)
+               R.push_back(Queue->Uri);
+            else if (R[0] == "STOP" || R.size() > 10)
+               StopRedirects = true;
+            else
+            {
+               for (StringVectorIterator I = R.begin(); I != R.end(); I++)
+                  if (Queue->Uri == *I)
+                  {
+                     R[0] = "STOP";
+                     break;
+                  }
+               R.push_back(Queue->Uri);
+            }
+            if (StopRedirects == false)
+               Redirect(NextURI);
+            else
+               Fail();
+            break;
+         }
+
         default:
         Fail(_("Internal error"));
         break;
@@ -1223,14 +1354,57 @@ int HttpMethod::Loop()
    return 0;
 }
                                                                        /*}}}*/
-
-int main()
+// HttpMethod::AutoDetectProxy - auto detect proxy                     /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool HttpMethod::AutoDetectProxy()
 {
-   setlocale(LC_ALL, "");
+   if (AutoDetectProxyCmd.empty())
+      return true;
 
-   HttpMethod Mth;
-   
-   return Mth.Loop();
+   if (Debug)
+      clog << "Using auto proxy detect command: " << AutoDetectProxyCmd << endl;
+
+   int Pipes[2] = {-1,-1};
+   if (pipe(Pipes) != 0)
+      return _error->Errno("pipe", "Failed to create Pipe");
+
+   pid_t Process = ExecFork();
+   if (Process == 0)
+   {
+      close(Pipes[0]);
+      dup2(Pipes[1],STDOUT_FILENO);
+      SetCloseExec(STDOUT_FILENO,false);
+
+      const char *Args[2];
+      Args[0] = AutoDetectProxyCmd.c_str();
+      Args[1] = 0;
+      execv(Args[0],(char **)Args);
+      cerr << "Failed to exec method " << Args[0] << endl;
+      _exit(100);
+   }
+   char buf[512];
+   int InFd = Pipes[0];
+   close(Pipes[1]);
+   int res = read(InFd, buf, sizeof(buf));
+   ExecWait(Process, "ProxyAutoDetect", true);
+
+   if (res < 0)
+      return _error->Errno("read", "Failed to read");
+   if (res == 0)
+      return _error->Warning("ProxyAutoDetect returned no data");
+
+   // add trailing \0
+   buf[res] = 0;
+
+   if (Debug)
+      clog << "auto detect command returned: '" << buf << "'" << endl;
+
+   if (strstr(buf, "http://") == buf)
+      _config->Set("Acquire::http::proxy", _strstrip(buf));
+
+   return true;
 }
+                                                                       /*}}}*/