X-Git-Url: https://git.saurik.com/apt.git/blobdiff_plain/352c27685a2b9f3b5e0b9eb7b828af099fbdb934..0a5e4a031a039329cc0d27268be6a623e892c452:/methods/http.cc diff --git a/methods/http.cc b/methods/http.cc index be7e8610a..1c3f61caa 100644 --- a/methods/http.cc +++ b/methods/http.cc @@ -1,20 +1,18 @@ // -*- mode: cpp; mode: fold -*- // Description /*{{{*/ -// $Id: http.cc,v 1.7 1998/11/23 21:28:43 jgg Exp $ +// $Id: http.cc,v 1.52 2001/05/27 23:53:55 jgg Exp $ /* ###################################################################### HTTP Aquire 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. It accepts on the command line - a list of url destination pairs and writes to stdout the status of the - operation as defined in the APT method spec. - - It is based on a doubly buffered select loop. All the requests are + pipelining, range, if-range and so on. + + It is based on a doubly buffered select loop. A groupe of requests are fed into a single output buffer that is constantly fed out the socket. This provides ideal pipelining as in many cases all of the requests will fit into a single packet. The input socket is buffered - the same way and fed into the fd for the file. + the same way and fed into the fd for the file (may be a pipe in future). This double buffering provides fairly substantial transfer rates, compared to wget the http method is about 4% faster. Most importantly, @@ -30,7 +28,7 @@ #include #include #include -#include +#include #include #include @@ -38,24 +36,31 @@ #include #include #include +#include +#include +#include // Internet stuff -#include -#include -#include #include +#include "connect.h" +#include "rfc2553emu.h" #include "http.h" + /*}}}*/ +using namespace std; string HttpMethod::FailFile; int HttpMethod::FailFd = -1; time_t HttpMethod::FailTime = 0; +unsigned long PipelineDepth = 10; +unsigned long TimeOut = 120; +bool Debug = false; // CircleBuf::CircleBuf - Circular input buffer /*{{{*/ // --------------------------------------------------------------------- /* */ -CircleBuf::CircleBuf(unsigned long Size) : Size(Size), MD5(0) +CircleBuf::CircleBuf(unsigned long Size) : Size(Size), Hash(0) { Buf = new unsigned char[Size]; Reset(); @@ -71,10 +76,10 @@ void CircleBuf::Reset() StrPos = 0; MaxGet = (unsigned int)-1; OutQueue = string(); - if (MD5 != 0) + if (Hash != 0) { - delete MD5; - MD5 = new MD5Summation; + delete Hash; + Hash = new Hashes; } }; /*}}}*/ @@ -136,7 +141,7 @@ void CircleBuf::FillOut() unsigned long Sz = LeftRead(); if (OutQueue.length() - StrPos < Sz) Sz = OutQueue.length() - StrPos; - memcpy(Buf + (InP%Size),OutQueue.begin() + StrPos,Sz); + memcpy(Buf + (InP%Size),OutQueue.c_str() + StrPos,Sz); // Advance StrPos += Sz; @@ -180,8 +185,8 @@ bool CircleBuf::Write(int Fd) return false; } - if (MD5 != 0) - MD5->Add(Buf + (OutP%Size),Res); + if (Hash != 0) + Hash->Add(Buf + (OutP%Size),Res); OutP += Res; } @@ -245,7 +250,7 @@ void CircleBuf::Stats() // --------------------------------------------------------------------- /* */ ServerState::ServerState(URI Srv,HttpMethod *Owner) : Owner(Owner), - In(64*1024), Out(1*1024), + In(64*1024), Out(4*1024), ServerName(Srv) { Reset(); @@ -254,8 +259,6 @@ ServerState::ServerState(URI Srv,HttpMethod *Owner) : Owner(Owner), // ServerState::Open - Open a connection to the server /*{{{*/ // --------------------------------------------------------------------- /* This opens a connection to the server. */ -string LastHost; -in_addr LastHostA; bool ServerState::Open() { // Use the already open connection if possible. @@ -265,9 +268,10 @@ bool ServerState::Open() Close(); In.Reset(); Out.Reset(); - + Persistent = true; + // Determine the proxy setting - if (getenv("http_proxy") != 0) + if (getenv("http_proxy") == 0) { string DefProxy = _config->Find("Acquire::http::Proxy"); string SpecificProxy = _config->Find("Acquire::http::Proxy::" + ServerName.Host); @@ -284,10 +288,17 @@ bool ServerState::Open() else Proxy = getenv("http_proxy"); + // Parse no_proxy, a , separated list of domains + if (getenv("no_proxy") != 0) + { + if (CheckDomainList(ServerName.Host,getenv("no_proxy")) == true) + Proxy = ""; + } + // Determine what host and port to use based on the proxy settings - int Port = 80; + int Port = 0; string Host; - if (Proxy.empty() == true) + if (Proxy.empty() == true || Proxy.Host.empty() == true) { if (ServerName.Port != 0) Port = ServerName.Port; @@ -300,36 +311,10 @@ bool ServerState::Open() Host = Proxy.Host; } - /* We used a cached address record.. Yes this is against the spec but - the way we have setup our rotating dns suggests that this is more - sensible */ - if (LastHost != Host) - { - Owner->Status("Connecting to %s",Host.c_str()); - - // Lookup the host - hostent *Addr = gethostbyname(Host.c_str()); - if (Addr == 0) - return _error->Error("Could not resolve '%s'",Host.c_str()); - LastHost = Host; - LastHostA = *(in_addr *)(Addr->h_addr_list[0]); - } - - Owner->Status("Connecting to %s (%s)",Host.c_str(),inet_ntoa(LastHostA)); - - // Get a socket - if ((ServerFd = socket(AF_INET,SOCK_STREAM,0)) < 0) - return _error->Errno("socket","Could not create a socket"); + // Connect to the remote server + if (Connect(Host,Port,"http",80,ServerFd,TimeOut,Owner) == false) + return false; - // Connect to the server - struct sockaddr_in server; - server.sin_family = AF_INET; - server.sin_port = htons(Port); - server.sin_addr = LastHostA; - if (connect(ServerFd,(sockaddr *)&server,sizeof(server)) < 0) - return _error->Errno("socket","Could not create a socket"); - - SetNonBlock(ServerFd,true); return true; } /*}}}*/ @@ -367,19 +352,31 @@ int ServerState::RunHeaders() string Data; if (In.WriteTillEl(Data) == false) continue; + + if (Debug == true) + clog << Data; for (string::const_iterator I = Data.begin(); I < Data.end(); I++) { string::const_iterator J = I; for (; J != Data.end() && *J != '\n' && *J != '\r';J++); - if (HeaderLine(string(I,J-I)) == false) + if (HeaderLine(string(I,J)) == false) return 2; I = J; } + + // 100 Continue is a Nop... + if (Result == 100) + continue; + + // Tidy up the connection persistance state. + if (Encoding == Closes && HaveContent == true) + Persistent = false; + return 0; } while (Owner->Go(false,this) == true); - + return 1; } /*}}}*/ @@ -425,7 +422,7 @@ bool ServerState::RunData() while ((Last = Owner->Go(false,this)) == true); if (Last == false) return false; - return true; + return !_error->PendingError(); } // Transfer the block @@ -467,12 +464,12 @@ bool ServerState::RunData() continue; In.Limit(-1); - return true; + return !_error->PendingError(); } while (Owner->Go(true,this) == true); } - return Owner->Flush(this); + return Owner->Flush(this) && !_error->PendingError(); } /*}}}*/ // ServerState::HeaderLine - Process a header line /*{{{*/ @@ -482,19 +479,30 @@ bool ServerState::HeaderLine(string Line) { if (Line.empty() == true) return true; - + // The http server might be trying to do something evil. if (Line.length() >= MAXLEN) return _error->Error("Got a single header line over %u chars",MAXLEN); string::size_type Pos = Line.find(' '); if (Pos == string::npos || Pos+1 > Line.length()) - return _error->Error("Bad header line"); - - string Tag = string(Line,0,Pos); - string Val = string(Line,Pos+1); + { + // Blah, some servers use "connection:closes", evil. + Pos = Line.find(':'); + if (Pos == string::npos || Pos + 2 > Line.length()) + return _error->Error("Bad header line"); + Pos++; + } - if (stringcasecmp(Tag.begin(),Tag.begin()+4,"HTTP") == 0) + // Parse off any trailing spaces between the : and the next word. + string::size_type Pos2 = Pos; + while (Pos2 < Line.length() && isspace(Line[Pos2]) != 0) + Pos2++; + + string Tag = string(Line,0,Pos); + string Val = string(Line,Pos2); + + if (stringcasecmp(Tag.c_str(),Tag.c_str()+4,"HTTP") == 0) { // Evil servers return no version if (Line[4] == '/') @@ -510,7 +518,19 @@ bool ServerState::HeaderLine(string Line) if (sscanf(Line.c_str(),"HTTP %u %[^\n]",&Result,Code) != 2) return _error->Error("The http server sent an invalid reply header"); } - + + /* Check the HTTP response header to get the default persistance + state. */ + if (Major < 1) + Persistent = false; + else + { + if (Major == 1 && Minor <= 0) + Persistent = false; + else + Persistent = true; + } + return true; } @@ -550,11 +570,19 @@ bool ServerState::HeaderLine(string Line) { HaveContent = true; if (stringcasecmp(Val,"chunked") == 0) - Encoding = Chunked; - + Encoding = Chunked; return true; } + if (stringcasecmp(Tag,"Connection:") == 0) + { + if (stringcasecmp(Val,"close") == 0) + Persistent = false; + if (stringcasecmp(Val,"keep-alive") == 0) + Persistent = true; + return true; + } + if (stringcasecmp(Tag,"Last-Modified:") == 0) { if (StrToTime(Val,Date) == false) @@ -572,9 +600,9 @@ bool ServerState::HeaderLine(string Line) void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) { URI Uri = Itm->Uri; - + // The HTTP server expects a hostname with a trailing :port - char Buf[300]; + char Buf[1000]; string ProperHost = Uri.Host; if (Uri.Port != 0) { @@ -582,6 +610,10 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) ProperHost += Buf; } + // Just in case. + if (Itm->Uri.length() >= sizeof(Buf)) + abort(); + /* Build the request. We include a keep-alive header only for non-proxy requests. This is to tweak old http/1.0 servers that do support keep-alive but not HTTP/1.1 automatic keep-alive. Doing this with a proxy server @@ -590,10 +622,29 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) and we expect the proxy to do this */ if (Proxy.empty() == true) sprintf(Buf,"GET %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n", - Uri.Path.c_str(),ProperHost.c_str()); + QuoteString(Uri.Path,"~").c_str(),ProperHost.c_str()); else + { + /* Generate a cache control header if necessary. We place a max + cache age on index files, optionally set a no-cache directive + 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()); + 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) + sprintf(Buf+strlen(Buf),"Cache-Control: max-age=%u\r\n", + _config->FindI("Acquire::http::Max-Age",60*60*24)); + else + { + if (_config->FindB("Acquire::http::No-Store",false) == true) + strcat(Buf,"Cache-Control: no-store\r\n"); + } + } + } + string Req = Buf; // Check for a partial file @@ -601,7 +652,7 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) if (stat(Itm->DestFile.c_str(),&SBuf) >= 0 && SBuf.st_size > 0) { // In this case we send an if-range query with a range header - sprintf(Buf,"Range: bytes=%li-\r\nIf-Range: %s\r\n",SBuf.st_size - 1, + sprintf(Buf,"Range: bytes=%li-\r\nIf-Range: %s\r\n",(long)SBuf.st_size - 1, TimeRFC1123(SBuf.st_mtime).c_str()); Req += Buf; } @@ -614,12 +665,19 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) } } -/* if (ProxyAuth.empty() == false) - Req += string("Proxy-Authorization: Basic ") + Base64Encode(ProxyAuth) + "\r\n";*/ + if (Proxy.User.empty() == false || Proxy.Password.empty() == false) + Req += string("Proxy-Authorization: Basic ") + + Base64Encode(Proxy.User + ":" + Proxy.Password) + "\r\n"; - Req += "User-Agent: Debian APT-HTTP/1.2\r\n\r\n"; -// cerr << Req << endl; + 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\r\n\r\n"; + + if (Debug == true) + cerr << Req << endl; + Out.Read(Req); } /*}}}*/ @@ -630,18 +688,20 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) bool HttpMethod::Go(bool ToFile,ServerState *Srv) { // Server has closed the connection - if (Srv->ServerFd == -1 && Srv->In.WriteSpace() == false) + if (Srv->ServerFd == -1 && (Srv->In.WriteSpace() == false || + ToFile == false)) return false; - fd_set rfds,wfds,efds; + fd_set rfds,wfds; FD_ZERO(&rfds); FD_ZERO(&wfds); - FD_ZERO(&efds); - // Add the server - if (Srv->Out.WriteSpace() == true && Srv->ServerFd != -1) + /* Add the server. We only send more requests if the connection will + be persisting */ + if (Srv->Out.WriteSpace() == true && Srv->ServerFd != -1 + && Srv->Persistent == true) FD_SET(Srv->ServerFd,&wfds); - if (Srv->In.ReadSpace() == true && Srv->ServerFd != -1) + if (Srv->In.ReadSpace() == true && Srv->ServerFd != -1) FD_SET(Srv->ServerFd,&rfds); // Add the file @@ -655,23 +715,17 @@ bool HttpMethod::Go(bool ToFile,ServerState *Srv) // Add stdin FD_SET(STDIN_FILENO,&rfds); - // Error Set - if (FileFD != -1) - FD_SET(FileFD,&efds); - if (Srv->ServerFd != -1) - FD_SET(Srv->ServerFd,&efds); - // Figure out the max fd int MaxFd = FileFD; if (MaxFd < Srv->ServerFd) MaxFd = Srv->ServerFd; - + // Select struct timeval tv; - tv.tv_sec = 120; + tv.tv_sec = TimeOut; tv.tv_usec = 0; int Res = 0; - if ((Res = select(MaxFd+1,&rfds,&wfds,&efds,&tv)) < 0) + if ((Res = select(MaxFd+1,&rfds,&wfds,0,&tv)) < 0) return _error->Errno("select","Select failed"); if (Res == 0) @@ -680,11 +734,6 @@ bool HttpMethod::Go(bool ToFile,ServerState *Srv) return ServerDie(Srv); } - // Some kind of exception (error) on the sockets, die - if ((FileFD != -1 && FD_ISSET(FileFD,&efds)) || - (Srv->ServerFd != -1 && FD_ISSET(Srv->ServerFd,&efds))) - return _error->Error("Socket Exception"); - // Handle server IO if (Srv->ServerFd != -1 && FD_ISSET(Srv->ServerFd,&rfds)) { @@ -710,7 +759,7 @@ bool HttpMethod::Go(bool ToFile,ServerState *Srv) // Handle commands from APT if (FD_ISSET(STDIN_FILENO,&rfds)) { - if (Run(true) != 0) + if (Run(true) != -1) exit(100); } @@ -748,6 +797,8 @@ bool HttpMethod::Flush(ServerState *Srv) /* */ bool HttpMethod::ServerDie(ServerState *Srv) { + unsigned int LErrno = errno; + // Dump the buffer to the file if (Srv->State == ServerState::Data) { @@ -767,8 +818,10 @@ bool HttpMethod::ServerDie(ServerState *Srv) if (Srv->In.IsLimit() == false && Srv->State != ServerState::Header && Srv->Encoding != ServerState::Closes) { - if (errno == 0) + Srv->Close(); + if (LErrno == 0) return _error->Error("Error reading from server Remote end closed connection"); + errno = LErrno; return _error->Errno("read","Error reading from server"); } else @@ -828,6 +881,7 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) return 5; FailFile = Queue->DestFile; + FailFile.c_str(); // Make sure we dont do a malloc in the signal handler FailFd = File->Fd(); FailTime = Srv->Date; @@ -841,14 +895,14 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) // Set the start point lseek(File->Fd(),0,SEEK_END); - delete Srv->In.MD5; - Srv->In.MD5 = new MD5Summation; + delete Srv->In.Hash; + Srv->In.Hash = new Hashes; - // Fill the MD5 Hash if the file is non-empty (resume) + // Fill the Hash if the file is non-empty (resume) if (Srv->StartPos > 0) { lseek(File->Fd(),0,SEEK_SET); - if (Srv->In.MD5->AddFD(File->Fd(),Srv->StartPos) == false) + if (Srv->In.Hash->AddFD(File->Fd(),Srv->StartPos) == false) { _error->Errno("read","Problem hashing file"); return 5; @@ -867,17 +921,67 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) void HttpMethod::SigTerm(int) { if (FailFd == -1) - exit(100); + _exit(100); close(FailFd); // Timestamp struct utimbuf UBuf; - time(&UBuf.actime); UBuf.actime = FailTime; UBuf.modtime = FailTime; utime(FailFile.c_str(),&UBuf); - exit(100); + _exit(100); +} + /*}}}*/ +// HttpMethod::Fetch - Fetch an item /*{{{*/ +// --------------------------------------------------------------------- +/* This adds an item to the pipeline. We keep the pipeline at a fixed + depth. */ +bool HttpMethod::Fetch(FetchItem *) +{ + if (Server == 0) + return true; + + // Queue the requests + int Depth = -1; + bool Tail = false; + for (FetchItem *I = Queue; I != 0 && Depth < (signed)PipelineDepth; + I = I->Next, Depth++) + { + // If pipelining is disabled, we only queue 1 request + if (Server->Pipeline == false && Depth >= 0) + break; + + // Make sure we stick with the same server + if (Server->Comp(I->Uri) == false) + break; + if (QueueBack == I) + Tail = true; + if (Tail == true) + { + QueueBack = I->Next; + SendReq(I,Server->Out); + continue; + } + } + + return true; +}; + /*}}}*/ +// HttpMethod::Configuration - Handle a configuration message /*{{{*/ +// --------------------------------------------------------------------- +/* We stash the desired pipeline depth */ +bool HttpMethod::Configuration(string Message) +{ + if (pkgAcqMethod::Configuration(Message) == false) + return false; + + TimeOut = _config->FindI("Acquire::http::Timeout",TimeOut); + PipelineDepth = _config->FindI("Acquire::http::Pipeline-Depth", + PipelineDepth); + Debug = _config->FindB("Debug::Acquire::http",false); + + return true; } /*}}}*/ // HttpMethod::Loop - Main loop /*{{{*/ @@ -888,17 +992,11 @@ int HttpMethod::Loop() signal(SIGTERM,SigTerm); signal(SIGINT,SigTerm); - ServerState *Server = 0; + Server = 0; int FailCounter = 0; while (1) - { - if (FailCounter >= 2) - { - Fail("Massive Server Brain Damage"); - FailCounter = 0; - } - + { // We have no commands, wait for some to arrive if (Queue == 0) { @@ -906,8 +1004,10 @@ int HttpMethod::Loop() return 0; } - // Run messages - if (Run(true) != 0) + /* Run messages, we can accept 0 (no message) if we didn't + do a WaitFd above.. Otherwise the FD is closed. */ + int Result = Run(true); + if (Result != -1 && (Result != 0 || Queue == 0)) return 100; if (Queue == 0) @@ -919,17 +1019,31 @@ 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 + since we don't have to wait for the close sequence to + complete */ + if (Server->Persistent == false) + Server->Close(); + + // Reset the pipeline + if (Server->ServerFd == -1) + QueueBack = Queue; + // Connnect to the host if (Server->Open() == false) { - Fail(); + Fail(true); + delete Server; + Server = 0; continue; } - - // Queue the request - SendReq(Queue,Server->Out); + // Fill the pipeline. + Fetch(0); + // Fetch the next URL header data from the server. switch (Server->RunHeaders()) { @@ -940,7 +1054,8 @@ int HttpMethod::Loop() case 2: { _error->Error("Bad header Data"); - Fail(); + Fail(true); + RotateDNS(); continue; } @@ -949,12 +1064,21 @@ int HttpMethod::Loop() case 1: { FailCounter++; - _error->DumpErrors(); + _error->Discard(); Server->Close(); + Server->Pipeline = false; + + if (FailCounter >= 2) + { + Fail("Connection failed",true); + FailCounter = 0; + } + + RotateDNS(); continue; } }; - + // Decide what to do. FetchResult Res; Res.Filename = Queue->DestFile; @@ -968,6 +1092,11 @@ int HttpMethod::Loop() // Run the data bool Result = Server->RunData(); + /* If the server is sending back sizeless responses then fill in + the size now */ + if (Res.Size == 0) + Res.Size = File->Size(); + // Close the file, destroy the FD object and timestamp it FailFd = -1; delete File; @@ -983,12 +1112,12 @@ int HttpMethod::Loop() // Send status to APT if (Result == true) { - Res.MD5Sum = Server->In.MD5->Result(); + Res.TakeHashes(*Server->In.Hash); URIDone(Res); } else - Fail(); - + Fail(true); + break; } @@ -1010,6 +1139,7 @@ int HttpMethod::Loop() case 5: { Fail(); + RotateDNS(); Server->Close(); break; }