]> git.saurik.com Git - apt.git/blobdiff - methods/http.cc
merged from the jackyf-devel branch
[apt.git] / methods / http.cc
index cb63ada499f9df866f96f143a0eec53608fbf7ad..1eba0f279694119a8823405962963d3c3b81c279 100644 (file)
@@ -3,7 +3,7 @@
 // $Id: http.cc,v 1.59 2004/05/08 19:42:35 mdz Exp $
 /* ######################################################################
 
-   HTTP Aquire Method - This is the HTTP aquire method for APT.
+   HTTP Acquire Method - This is the HTTP aquire method for APT.
    
    It uses HTTP/1.1 and many of the fancy options there-in, such as
    pipelining, range, if-range and so on. 
 #include <errno.h>
 #include <string.h>
 #include <iostream>
+#include <map>
 #include <apti18n.h>
 
 // Internet stuff
 #include <netdb.h>
 
+#include "config.h"
 #include "connect.h"
 #include "rfc2553emu.h"
 #include "http.h"
@@ -56,7 +58,9 @@ 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;
 
 unsigned long CircleBuf::BwReadLimit=0;
 unsigned long CircleBuf::BwTickReadData=0;
@@ -307,22 +311,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)
@@ -366,8 +375,8 @@ 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 */
+/* Returns 0 if things are OK, 1 if an IO error occurred and 2 if a header
+   parse error occurred */
 int ServerState::RunHeaders()
 {
    State = Header;
@@ -626,6 +635,12 @@ bool ServerState::HeaderLine(string Line)
       return true;
    }
 
+   if (stringcasecmp(Tag,"Location:") == 0)
+   {
+      Location = Val;
+      return true;
+   }
+
    return true;
 }
                                                                        /*}}}*/
@@ -656,7 +671,7 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out)
       will glitch HTTP/1.0 proxies because they do not filter it out and 
       pass it on, HTTP/1.1 says the connection should default to keep alive
       and we expect the proxy to do this */
-   if (Proxy.empty() == true)
+   if (Proxy.empty() == true || Proxy.Host.empty())
       sprintf(Buf,"GET %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n",
              QuoteString(Uri.Path,"~").c_str(),ProperHost.c_str());
    else
@@ -713,7 +728,7 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out)
       Req += string("Authorization: Basic ") + 
           Base64Encode(Uri.User + ":" + Uri.Password) + "\r\n";
    
-   Req += "User-Agent: Debian APT-HTTP/1.3\r\n\r\n";
+   Req += "User-Agent: Debian APT-HTTP/1.3 ("VERSION")\r\n\r\n";
    
    if (Debug == true)
       cerr << Req << endl;
@@ -898,7 +913,9 @@ bool HttpMethod::ServerDie(ServerState *Srv)
      1 - IMS hit
      3 - Unrecoverable error 
      4 - Error with error content page
-     5 - Unrecoverable non-server error (close the connection) */
+     5 - Unrecoverable non-server error (close the connection) 
+     6 - Try again with a new or changed URI
+ */
 int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv)
 {
    // Not Modified
@@ -910,6 +927,27 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv)
       return 1;
    }
    
+   /* 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())
+      {
+         NextURI = Srv->Location;
+         return 6;
+      }
+      /* 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)
@@ -939,7 +977,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
@@ -994,7 +1033,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++)
    {
@@ -1006,8 +1044,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);
@@ -1026,6 +1062,7 @@ 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);
@@ -1039,6 +1076,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);
    
@@ -1069,7 +1110,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
@@ -1166,8 +1206,24 @@ 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;
         }
         
@@ -1210,6 +1266,46 @@ int HttpMethod::Loop()
            break;
         }
         
+         // Try again with a new URL
+         case 6:
+         {
+            // 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;
@@ -1225,9 +1321,11 @@ int HttpMethod::Loop()
 int main()
 {
    setlocale(LC_ALL, "");
+   // ignore SIGPIPE, this can happen on write() if the socket
+   // closes the connection (this is dealt with via ServerDie())
+   signal(SIGPIPE, SIG_IGN);
 
    HttpMethod Mth;
-   
    return Mth.Loop();
 }