X-Git-Url: https://git.saurik.com/apt.git/blobdiff_plain/a1459f526ca5e2b0feb1c504814756e9bf2f5c31..106e674096a2c6da1e3fa44b88ec228a61ef7dfe:/methods/http.cc diff --git a/methods/http.cc b/methods/http.cc index ceb2cea14..ba86aa6b6 100644 --- a/methods/http.cc +++ b/methods/http.cc @@ -1,20 +1,18 @@ // -*- mode: cpp; mode: fold -*- // Description /*{{{*/ -// $Id: http.cc,v 1.23 1999/01/28 06:32:31 jgg Exp $ +// $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. 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,26 +36,32 @@ #include #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(); @@ -73,10 +77,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; } }; /*}}}*/ @@ -138,7 +142,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; @@ -182,8 +186,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; } @@ -199,13 +203,17 @@ bool CircleBuf::WriteTillEl(string &Data,bool Single) { if (Buf[I%Size] != '\n') continue; - for (I++; I < InP && Buf[I%Size] == '\r'; I++); + ++I; + if (I < InP && Buf[I%Size] == '\r') + ++I; if (Single == false) { if (Buf[I%Size] != '\n') continue; - for (I++; I < InP && Buf[I%Size] == '\r'; I++); + ++I; + if (I < InP && Buf[I%Size] == '\r') + ++I; } if (I > InP) @@ -256,8 +264,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. @@ -267,7 +273,8 @@ bool ServerState::Open() Close(); In.Reset(); Out.Reset(); - + Persistent = true; + // Determine the proxy setting if (getenv("http_proxy") == 0) { @@ -286,10 +293,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; @@ -302,55 +316,9 @@ 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 || Addr->h_addr_list[0] == 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 server - struct sockaddr_in server; - server.sin_family = AF_INET; - server.sin_port = htons(Port); - server.sin_addr = LastHostA; - SetNonBlock(ServerFd,true); - if (connect(ServerFd,(sockaddr *)&server,sizeof(server)) < 0 && - errno != EINPROGRESS) - return _error->Errno("socket","Could not create a socket"); - - /* This implements a timeout for connect by opening the connection - nonblocking */ - fd_set wfds; - FD_ZERO(&wfds); - FD_SET(ServerFd,&wfds); - struct timeval tv; - tv.tv_sec = TimeOut; - tv.tv_usec = 0; - int Res = 0; - if ((Res = select(ServerFd+1,0,&wfds,0,&tv)) < 0) - return _error->Errno("select","Select failed"); - if (Res == 0) - return _error->Error("Could not connect, connection timed out"); - unsigned int Err,Len=sizeof(Err); - if (getsockopt(ServerFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0) - return _error->Errno("getsockopt","Failed"); - if (Err != 0) - return _error->Error("Could not connect."); + // Connect to the remote server + if (Connect(Host,Port,"http",80,ServerFd,TimeOut,Owner) == false) + return false; return true; } @@ -373,7 +341,7 @@ int ServerState::RunHeaders() { State = Header; - Owner->Status("Waiting for file"); + Owner->Status(_("Waiting for headers")); Major = 0; Minor = 0; @@ -389,19 +357,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; } /*}}}*/ @@ -447,7 +427,7 @@ bool ServerState::RunData() while ((Last = Owner->Go(false,this)) == true); if (Last == false) return false; - return true; + return !_error->PendingError(); } // Transfer the block @@ -489,12 +469,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 /*{{{*/ @@ -507,32 +487,55 @@ bool ServerState::HeaderLine(string Line) // 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); + 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] == '/') { if (sscanf(Line.c_str(),"HTTP/%u.%u %u %[^\n]",&Major,&Minor, &Result,Code) != 4) - return _error->Error("The http server sent an invalid reply header"); + 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) - return _error->Error("The http server sent an invalid reply header"); + 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; } @@ -547,7 +550,7 @@ bool ServerState::HeaderLine(string Line) return true; if (sscanf(Val.c_str(),"%lu",&Size) != 1) - return _error->Error("The http server sent an invalid Content-Length header"); + return _error->Error(_("The HTTP server sent an invalid Content-Length header")); return true; } @@ -562,9 +565,9 @@ bool ServerState::HeaderLine(string Line) HaveContent = true; if (sscanf(Val.c_str(),"bytes %lu-%*u/%lu",&StartPos,&Size) != 2) - return _error->Error("The http server sent an invalid Content-Range header"); + return _error->Error(_("The HTTP server sent an invalid Content-Range header")); if ((unsigned)StartPos > Size) - return _error->Error("This http server has broken range support"); + return _error->Error(_("This HTTP server has broken range support")); return true; } @@ -572,15 +575,23 @@ 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) - return _error->Error("Unknown date format"); + return _error->Error(_("Unknown date format")); return true; } @@ -624,13 +635,13 @@ 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()); - if (_config->FindB("Acquire::http::No-Cache",false) == true) - strcat(Buf,"Cache-Control: no-cache\r\nPragma: no-cache\r\n"); - else + // only generate a cache control header if we actually want to + // use a cache + if (_config->FindB("Acquire::http::No-Cache",false) == false) { if (Itm->IndexFile == true) sprintf(Buf+strlen(Buf),"Cache-Control: max-age=%u\r\n", - _config->FindI("Acquire::http::Max-Age",60*60*24)); + _config->FindI("Acquire::http::Max-Age",0)); else { if (_config->FindB("Acquire::http::No-Store",false) == true) @@ -638,6 +649,10 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) } } } + // 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; @@ -646,7 +661,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; } @@ -663,8 +678,14 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) 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); } @@ -676,18 +697,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 @@ -701,36 +724,29 @@ 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 = TimeOut; tv.tv_usec = 0; int Res = 0; - if ((Res = select(MaxFd+1,&rfds,&wfds,&efds,&tv)) < 0) - return _error->Errno("select","Select failed"); + if ((Res = select(MaxFd+1,&rfds,&wfds,0,&tv)) < 0) + { + if (errno == EINTR) + return true; + return _error->Errno("select",_("Select failed")); + } if (Res == 0) { - _error->Error("Connection timed out"); + _error->Error(_("Connection timed out")); 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)) { @@ -750,13 +766,13 @@ bool HttpMethod::Go(bool ToFile,ServerState *Srv) if (FileFD != -1 && FD_ISSET(FileFD,&wfds)) { if (Srv->In.Write(FileFD) == false) - return _error->Errno("write","Error writing to output file"); + return _error->Errno("write",_("Error writing to output file")); } // Handle commands from APT if (FD_ISSET(STDIN_FILENO,&rfds)) { - if (Run(true) != 0) + if (Run(true) != -1) exit(100); } @@ -778,7 +794,7 @@ bool HttpMethod::Flush(ServerState *Srv) while (Srv->In.WriteSpace() == true) { if (Srv->In.Write(File->Fd()) == false) - return _error->Errno("write","Error writing to file"); + return _error->Errno("write",_("Error writing to file")); if (Srv->In.IsLimit() == true) return true; } @@ -794,6 +810,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) { @@ -801,7 +819,7 @@ bool HttpMethod::ServerDie(ServerState *Srv) while (Srv->In.WriteSpace() == true) { if (Srv->In.Write(File->Fd()) == false) - return _error->Errno("write","Error writing to the file"); + return _error->Errno("write",_("Error writing to the file")); // Done if (Srv->In.IsLimit() == true) @@ -814,9 +832,10 @@ bool HttpMethod::ServerDie(ServerState *Srv) Srv->Encoding != ServerState::Closes) { Srv->Close(); - if (errno == 0) - return _error->Error("Error reading from server Remote end closed connection"); - return _error->Errno("read","Error reading from server"); + 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 { @@ -875,7 +894,7 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) return 5; FailFile = Queue->DestFile; - FailFile.c_str(); // Make sure we don't do a malloc in the signal handler + FailFile.c_str(); // Make sure we dont do a malloc in the signal handler FailFd = File->Fd(); FailTime = Srv->Date; @@ -889,16 +908,16 @@ 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"); + _error->Errno("read",_("Problem hashing file")); return 5; } lseek(File->Fd(),0,SEEK_END); @@ -915,17 +934,16 @@ 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 /*{{{*/ @@ -940,8 +958,13 @@ 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++) + 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; @@ -952,7 +975,7 @@ bool HttpMethod::Fetch(FetchItem *) QueueBack = I->Next; SendReq(I,Server->Out); continue; - } + } } return true; @@ -969,6 +992,7 @@ bool HttpMethod::Configuration(string Message) TimeOut = _config->FindI("Acquire::http::Timeout",TimeOut); PipelineDepth = _config->FindI("Acquire::http::Pipeline-Depth", PipelineDepth); + Debug = _config->FindB("Debug::Acquire::http",false); return true; } @@ -985,13 +1009,7 @@ int HttpMethod::Loop() 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) { @@ -999,8 +1017,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) @@ -1012,7 +1032,15 @@ 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; @@ -1020,7 +1048,7 @@ int HttpMethod::Loop() // Connnect to the host if (Server->Open() == false) { - Fail(); + Fail(true); delete Server; Server = 0; continue; @@ -1038,8 +1066,9 @@ int HttpMethod::Loop() // The header data is bad case 2: { - _error->Error("Bad header Data"); - Fail(); + _error->Error(_("Bad header data")); + Fail(true); + RotateDNS(); continue; } @@ -1050,6 +1079,15 @@ int HttpMethod::Loop() FailCounter++; _error->Discard(); Server->Close(); + Server->Pipeline = false; + + if (FailCounter >= 2) + { + Fail(_("Connection failed"),true); + FailCounter = 0; + } + + RotateDNS(); continue; } }; @@ -1067,6 +1105,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; @@ -1082,12 +1125,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; } @@ -1108,7 +1151,11 @@ int HttpMethod::Loop() // Hard internal error, kill the connection and fail case 5: { + delete File; + File = 0; + Fail(); + RotateDNS(); Server->Close(); break; } @@ -1127,7 +1174,7 @@ int HttpMethod::Loop() } default: - Fail("Internal error"); + Fail(_("Internal error")); break; } @@ -1140,7 +1187,11 @@ int HttpMethod::Loop() int main() { + setlocale(LC_ALL, ""); + HttpMethod Mth; return Mth.Loop(); } + +