X-Git-Url: https://git.saurik.com/apt.git/blobdiff_plain/be4401bfa4a240bbc894e1bfeb1e1e8d63fc7b18..34984adbae646b922c4759849097f047461c4a60:/methods/http.cc?ds=sidebyside diff --git a/methods/http.cc b/methods/http.cc index 6418a7719..c734d3799 100644 --- a/methods/http.cc +++ b/methods/http.cc @@ -1,20 +1,18 @@ // -*- mode: cpp; mode: fold -*- // Description /*{{{*/ -// $Id: http.cc,v 1.1 1998/11/01 05:30:47 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. + HTTP Acquire Method - This is the HTTP acquire 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, @@ -27,33 +25,50 @@ ##################################################################### */ /*}}}*/ // Include Files /*{{{*/ +#include + #include #include +#include #include -#include - +#include +#include +#include + +#include +#include +#include +#include #include #include -#include #include #include +#include +#include +#include -// Internet stuff -#include -#include -#include -#include - +#include "config.h" +#include "connect.h" #include "http.h" + +#include /*}}}*/ +using namespace std; + +unsigned long long CircleBuf::BwReadLimit=0; +unsigned long long CircleBuf::BwTickReadData=0; +struct timeval CircleBuf::BwReadTick={0,0}; +const unsigned int CircleBuf::BW_HZ=10; // CircleBuf::CircleBuf - Circular input buffer /*{{{*/ // --------------------------------------------------------------------- /* */ -CircleBuf::CircleBuf(unsigned long Size) : Size(Size), MD5(0) +CircleBuf::CircleBuf(unsigned long long Size) : Size(Size), Hash(0) { Buf = new unsigned char[Size]; Reset(); + + CircleBuf::BwReadLimit = _config->FindI("Acquire::http::Dl-Limit",0)*1024; } /*}}}*/ // CircleBuf::Reset - Reset to the default state /*{{{*/ @@ -64,14 +79,14 @@ void CircleBuf::Reset() InP = 0; OutP = 0; StrPos = 0; - MaxGet = (unsigned int)-1; + MaxGet = (unsigned long long)-1; OutQueue = string(); - if (MD5 != 0) + if (Hash != 0) { - delete MD5; - MD5 = new MD5Summation; - } -}; + delete Hash; + Hash = new Hashes; + } +} /*}}}*/ // CircleBuf::Read - Read from a FD into the circular buffer /*{{{*/ // --------------------------------------------------------------------- @@ -84,11 +99,38 @@ bool CircleBuf::Read(int Fd) // Woops, buffer is full if (InP - OutP == Size) return true; - + + // what's left to read in this tick + unsigned long long const BwReadMax = CircleBuf::BwReadLimit/BW_HZ; + + if(CircleBuf::BwReadLimit) { + struct timeval now; + gettimeofday(&now,0); + + unsigned long long d = (now.tv_sec-CircleBuf::BwReadTick.tv_sec)*1000000 + + now.tv_usec-CircleBuf::BwReadTick.tv_usec; + if(d > 1000000/BW_HZ) { + CircleBuf::BwReadTick = now; + CircleBuf::BwTickReadData = 0; + } + + if(CircleBuf::BwTickReadData >= BwReadMax) { + usleep(1000000/BW_HZ); + return true; + } + } + // Write the buffer segment - int Res; - Res = read(Fd,Buf + (InP%Size),LeftRead()); + ssize_t Res; + if(CircleBuf::BwReadLimit) { + Res = read(Fd,Buf + (InP%Size), + BwReadMax > LeftRead() ? LeftRead() : BwReadMax); + } else + Res = read(Fd,Buf + (InP%Size),LeftRead()); + if(Res > 0 && BwReadLimit > 0) + CircleBuf::BwTickReadData += Res; + if (Res == 0) return false; if (Res < 0) @@ -128,10 +170,10 @@ void CircleBuf::FillOut() return; // Write the buffer segment - unsigned long Sz = LeftRead(); + unsigned long 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; @@ -162,7 +204,7 @@ bool CircleBuf::Write(int Fd) return true; // Write the buffer segment - int Res; + ssize_t Res; Res = write(Fd,Buf + (OutP%Size),LeftWrite()); if (Res == 0) @@ -175,8 +217,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; } @@ -188,29 +230,28 @@ bool CircleBuf::Write(int Fd) bool CircleBuf::WriteTillEl(string &Data,bool Single) { // We cheat and assume it is unneeded to have more than one buffer load - for (unsigned long I = OutP; I < InP; I++) + for (unsigned long long I = OutP; I < InP; I++) { if (Buf[I%Size] != '\n') continue; - for (I++; I < InP && Buf[I%Size] == '\r'; I++); + ++I; if (Single == false) { - if (Buf[I%Size] != '\n') - continue; - for (I++; I < InP && Buf[I%Size] == '\r'; I++); + if (I < InP && Buf[I%Size] == '\r') + ++I; + if (I >= InP || Buf[I%Size] != '\n') + continue; + ++I; } - if (I > InP) - I = InP; - Data = ""; while (OutP < I) { - unsigned long Sz = LeftWrite(); + unsigned long long Sz = LeftWrite(); if (Sz == 0) return false; - if (I - OutP < LeftWrite()) + if (I - OutP < Sz) Sz = I - OutP; Data += string((char *)(Buf + (OutP%Size)),Sz); OutP += Sz; @@ -235,124 +276,98 @@ void CircleBuf::Stats() clog << "Got " << InP << " in " << Diff << " at " << InP/Diff << endl;*/ } /*}}}*/ +CircleBuf::~CircleBuf() +{ + delete [] Buf; + delete Hash; +} -// ServerState::ServerState - Constructor /*{{{*/ -// --------------------------------------------------------------------- -/* */ -ServerState::ServerState(URI Srv,HttpMethod *Owner) : Owner(Owner), - In(64*1024), Out(1*1024), - ServerName(Srv) +// HttpServerState::HttpServerState - Constructor /*{{{*/ +HttpServerState::HttpServerState(URI Srv,HttpMethod *Owner) : ServerState(Srv, Owner), In(64*1024), Out(4*1024) { + TimeOut = _config->FindI("Acquire::http::Timeout",TimeOut); Reset(); } /*}}}*/ -// ServerState::Open - Open a connection to the server /*{{{*/ +// HttpServerState::Open - Open a connection to the server /*{{{*/ // --------------------------------------------------------------------- /* This opens a connection to the server. */ -string LastHost; -in_addr LastHostA; -bool ServerState::Open() +bool HttpServerState::Open() { - Close(); + // Use the already open connection if possible. + if (ServerFd != -1) + return true; - int Port; - string Host; + Close(); + In.Reset(); + Out.Reset(); + Persistent = true; - if (Proxy.empty() == false) + // Determine the proxy setting + string SpecificProxy = _config->Find("Acquire::http::Proxy::" + ServerName.Host); + if (!SpecificProxy.empty()) { - Port = ServerName.Port; - Host = ServerName.Host; + if (SpecificProxy == "DIRECT") + Proxy = ""; + else + Proxy = SpecificProxy; } else { - Port = Proxy.Port; - Host = Proxy.Host; + string DefProxy = _config->Find("Acquire::http::Proxy"); + if (!DefProxy.empty()) + { + Proxy = DefProxy; + } + else + { + char* result = getenv("http_proxy"); + Proxy = result ? result : ""; + } } - if (LastHost != Host) + // Parse no_proxy, a , separated list of domains + if (getenv("no_proxy") != 0) { - Owner->Status("Connecting to %s",Host.c_str()); - - // Lookup the host - hostent *Addr = gethostbyname(Host.c_str()); - if (Addr == 0) - return _error->Errno("gethostbyname","Could not lookup host %s",Host.c_str()); - LastHost = Host; - LastHostA = *(in_addr *)(Addr->h_addr_list[0]); + if (CheckDomainList(ServerName.Host,getenv("no_proxy")) == true) + Proxy = ""; } - Owner->Status("Connecting to %s (%s)",Host.c_str(),inet_ntoa(LastHostA)); + // Determine what host and port to use based on the proxy settings + int Port = 0; + string Host; + if (Proxy.empty() == true || Proxy.Host.empty() == true) + { + if (ServerName.Port != 0) + Port = ServerName.Port; + Host = ServerName.Host; + } + else + { + if (Proxy.Port != 0) + Port = Proxy.Port; + Host = Proxy.Host; + } - // 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; } /*}}}*/ -// ServerState::Close - Close a connection to the server /*{{{*/ +// HttpServerState::Close - Close a connection to the server /*{{{*/ // --------------------------------------------------------------------- /* */ -bool ServerState::Close() +bool HttpServerState::Close() { close(ServerFd); ServerFd = -1; - In.Reset(); - Out.Reset(); return true; } /*}}}*/ -// ServerState::RunHeaders - Get the headers before the data /*{{{*/ -// --------------------------------------------------------------------- -/* */ -bool ServerState::RunHeaders() -{ - State = Header; - - Owner->Status("Waiting for file"); - - Major = 0; - Minor = 0; - Result = 0; - Size = 0; - StartPos = 0; - Encoding = Closes; - time(&Date); - - do - { - string Data; - if (In.WriteTillEl(Data) == false) - continue; - - 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) - return false; - I = J; - } - return true; - } - while (Owner->Go(false,this) == true); - - return false; -} - /*}}}*/ -// ServerState::RunData - Transfer the data from the socket /*{{{*/ -// --------------------------------------------------------------------- -/* */ -bool ServerState::RunData() +// HttpServerState::RunData - Transfer the data from the socket /*{{{*/ +bool HttpServerState::RunData(FileFd * const File) { State = Data; @@ -370,13 +385,13 @@ bool ServerState::RunData() if (In.WriteTillEl(Data,true) == true) break; } - while ((Last = Owner->Go(false,this)) == true); + while ((Last = Go(false, File)) == true); if (Last == false) return false; // See if we are done - unsigned long Len = strtol(Data.c_str(),0,16); + unsigned long long Len = strtoull(Data.c_str(),0,16); if (Len == 0) { In.Limit(-1); @@ -388,15 +403,15 @@ bool ServerState::RunData() if (In.WriteTillEl(Data,true) == true && Data.length() <= 2) break; } - while ((Last = Owner->Go(false,this)) == true); + while ((Last = Go(false, File)) == true); if (Last == false) return false; - return true; + return !_error->PendingError(); } // Transfer the block In.Limit(Len); - while (Owner->Go(true,this) == true) + while (Go(true, File) == true) if (In.IsLimit() == true) break; @@ -412,10 +427,10 @@ bool ServerState::RunData() if (In.WriteTillEl(Data,true) == true) break; } - while ((Last = Owner->Go(false,this)) == true); + while ((Last = Go(false, File)) == true); if (Last == false) return false; - } + } } else { @@ -433,460 +448,390 @@ bool ServerState::RunData() continue; In.Limit(-1); - return true; + return !_error->PendingError(); } - while (Owner->Go(true,this) == true); + while (Go(true, File) == true); } - return Owner->Flush(this); + return Owner->Flush() && !_error->PendingError(); } /*}}}*/ -// ServerState::HeaderLine - Process a header line /*{{{*/ -// --------------------------------------------------------------------- -/* */ -bool ServerState::HeaderLine(string Line) +bool HttpServerState::ReadHeaderLines(std::string &Data) /*{{{*/ { - 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); + return In.WriteTillEl(Data); +} + /*}}}*/ +bool HttpServerState::LoadNextResponse(bool const ToFile, FileFd * const File)/*{{{*/ +{ + return Go(ToFile, File); +} + /*}}}*/ +bool HttpServerState::WriteResponse(const std::string &Data) /*{{{*/ +{ + return Out.Read(Data); +} + /*}}}*/ +APT_PURE bool HttpServerState::IsOpen() /*{{{*/ +{ + return (ServerFd != -1); +} + /*}}}*/ +bool HttpServerState::InitHashes(FileFd &File) /*{{{*/ +{ + delete In.Hash; + In.Hash = new Hashes; - 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); + // Set the expected size and read file for the hashes + File.Truncate(StartPos); + return In.Hash->AddFD(File, StartPos); +} + /*}}}*/ +APT_PURE Hashes * HttpServerState::GetHashes() /*{{{*/ +{ + return In.Hash; +} + /*}}}*/ +// HttpServerState::Die - The server has closed the connection. /*{{{*/ +bool HttpServerState::Die(FileFd &File) +{ + unsigned int LErrno = errno; - if (stringcasecmp(Tag,"HTTP") == 0) + // Dump the buffer to the file + if (State == ServerState::Data) { - // Evil servers return no version - if (Line[4] == '/') + // on GNU/kFreeBSD, apt dies on /dev/null because non-blocking + // can't be set + if (File.Name() != "/dev/null") + SetNonBlock(File.Fd(),false); + while (In.WriteSpace() == true) { - 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"); - } - 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"); + if (In.Write(File.Fd()) == false) + return _error->Errno("write",_("Error writing to the file")); + + // Done + if (In.IsLimit() == true) + return true; } - - return true; - } - - if (stringcasecmp(Tag,"Content-Length:")) - { - if (Encoding == Closes) - Encoding = Stream; - - // The length is already set from the Content-Range header - if (StartPos != 0) - return true; - - if (sscanf(Val.c_str(),"%lu",&Size) != 1) - return _error->Error("The http server sent an invalid Content-Length header"); - return true; } - if (stringcasecmp(Tag,"Content-Range:")) - { - if (sscanf(Val.c_str(),"bytes %lu-%*u/%lu",&StartPos,&Size) != 2) - 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 true; + // See if this is because the server finished the data stream + if (In.IsLimit() == false && State != HttpServerState::Header && + Encoding != HttpServerState::Closes) + { + 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")); } - - if (stringcasecmp(Tag,"Transfer-Encoding:")) + else { - if (stringcasecmp(Val,"chunked")) - Encoding = Chunked; - return true; - } + In.Limit(-1); - if (stringcasecmp(Tag,"Last-Modified:")) - { - if (StrToTime(Val,Date) == false) - return _error->Error("Unknown date format"); + // Nothing left in the buffer + if (In.WriteSpace() == false) + return false; + + // We may have got multiple responses back in one packet.. + Close(); return true; } - return true; + return false; } /*}}}*/ - -// HttpMethod::SendReq - Send the HTTP request /*{{{*/ +// HttpServerState::Flush - Dump the buffer into the file /*{{{*/ // --------------------------------------------------------------------- -/* This places the http request in the outbound buffer */ -void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) +/* This takes the current input buffer from the Server FD and writes it + into the file */ +bool HttpServerState::Flush(FileFd * const File) { - URI Uri = Itm->Uri; - - // The HTTP server expects a hostname with a trailing :port - char Buf[300]; - string ProperHost = Uri.Host; - if (Uri.Port != 0) + if (File != NULL) { - sprintf(Buf,":%u",Uri.Port); - ProperHost += Buf; - } + // on GNU/kFreeBSD, apt dies on /dev/null because non-blocking + // can't be set + if (File->Name() != "/dev/null") + SetNonBlock(File->Fd(),false); + if (In.WriteSpace() == false) + return true; - // Build the request - 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()); - else - sprintf(Buf,"GET %s HTTP/1.1\r\nHost: %s\r\n", - Itm->Uri.c_str(),ProperHost.c_str()); - string Req = Buf; - - // Check for a partial file - struct stat SBuf; - 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, - TimeRFC1123(SBuf.st_mtime).c_str()); - Req += Buf; - } - else - { - if (Itm->LastModified != 0) + while (In.WriteSpace() == true) { - sprintf(Buf,"If-Modified-Since: %s\r\n",TimeRFC1123(Itm->LastModified).c_str()); - Req += Buf; + if (In.Write(File->Fd()) == false) + return _error->Errno("write",_("Error writing to file")); + if (In.IsLimit() == true) + return true; } - } -/* if (ProxyAuth.empty() == false) - Req += string("Proxy-Authorization: Basic ") + Base64Encode(ProxyAuth) + "\r\n";*/ - - Req += "User-Agent: Debian APT-HTTP/1.2\r\n\r\n"; - Out.Read(Req); + if (In.IsLimit() == true || Encoding == ServerState::Closes) + return true; + } + return false; } /*}}}*/ -// HttpMethod::Go - Run a single loop /*{{{*/ +// HttpServerState::Go - Run a single loop /*{{{*/ // --------------------------------------------------------------------- /* This runs the select loop over the server FDs, Output file FDs and stdin. */ -bool HttpMethod::Go(bool ToFile,ServerState *Srv) +bool HttpServerState::Go(bool ToFile, FileFd * const File) { // Server has closed the connection - if (Srv->ServerFd == -1 && Srv->In.WriteSpace() == false) + if (ServerFd == -1 && (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) - FD_SET(Srv->ServerFd,&wfds); - if (Srv->In.ReadSpace() == true && Srv->ServerFd != -1) - FD_SET(Srv->ServerFd,&rfds); + /* Add the server. We only send more requests if the connection will + be persisting */ + if (Out.WriteSpace() == true && ServerFd != -1 + && Persistent == true) + FD_SET(ServerFd,&wfds); + if (In.ReadSpace() == true && ServerFd != -1) + FD_SET(ServerFd,&rfds); // Add the file int FileFD = -1; - if (File != 0) + if (File != NULL) FileFD = File->Fd(); - if (Srv->In.WriteSpace() == true && ToFile == true && FileFD != -1) + if (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); - // 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; - + if (MaxFd < ServerFd) + MaxFd = 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) - 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"); - return ServerDie(Srv); + _error->Error(_("Connection timed out")); + return Die(*File); } - // 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)) + if (ServerFd != -1 && FD_ISSET(ServerFd,&rfds)) { errno = 0; - if (Srv->In.Read(Srv->ServerFd) == false) - return ServerDie(Srv); + if (In.Read(ServerFd) == false) + return Die(*File); } - if (Srv->ServerFd != -1 && FD_ISSET(Srv->ServerFd,&wfds)) + if (ServerFd != -1 && FD_ISSET(ServerFd,&wfds)) { errno = 0; - if (Srv->Out.Write(Srv->ServerFd) == false) - return ServerDie(Srv); + if (Out.Write(ServerFd) == false) + return Die(*File); } // Send data to the file if (FileFD != -1 && FD_ISSET(FileFD,&wfds)) { - if (Srv->In.Write(FileFD) == false) - return _error->Errno("write","Error writing to output file"); + if (In.Write(FileFD) == false) + return _error->Errno("write",_("Error writing to output file")); } // Handle commands from APT if (FD_ISSET(STDIN_FILENO,&rfds)) { - if (Run(true) != 0) + if (Owner->Run(true) != -1) exit(100); } return true; } /*}}}*/ -// HttpMethod::Flush - Dump the buffer into the file /*{{{*/ -// --------------------------------------------------------------------- -/* This takes the current input buffer from the Server FD and writes it - into the file */ -bool HttpMethod::Flush(ServerState *Srv) -{ - if (File != 0) - { - SetNonBlock(File->Fd(),false); - if (Srv->In.WriteSpace() == false) - return true; - - while (Srv->In.WriteSpace() == true) - { - if (Srv->In.Write(File->Fd()) == false) - return _error->Errno("write","Error writing to file"); - } - if (Srv->In.IsLimit() == true || Srv->Encoding == ServerState::Closes) - return true; - } - return false; -} - /*}}}*/ -// HttpMethod::ServerDie - The server has closed the connection. /*{{{*/ +// HttpMethod::SendReq - Send the HTTP request /*{{{*/ // --------------------------------------------------------------------- -/* */ -bool HttpMethod::ServerDie(ServerState *Srv) +/* This places the http request in the outbound buffer */ +void HttpMethod::SendReq(FetchItem *Itm) { - // Dump the buffer to the file - if (Srv->State == ServerState::Data) - { - SetNonBlock(File->Fd(),false); - while (Srv->In.WriteSpace() == true) - { - if (Srv->In.Write(File->Fd()) == false) - return _error->Errno("write","Error writing to the file"); - } - } - - // See if this is because the server finished the data stream - if (Srv->In.IsLimit() == false && Srv->State != ServerState::Header && - Srv->Encoding != ServerState::Closes) - { - if (errno == 0) - return _error->Error("Error reading from server Remote end closed connection"); - return _error->Errno("read","Error reading from server"); - } + URI Uri = Itm->Uri; + + // The HTTP server expects a hostname with a trailing :port + std::stringstream Req; + string ProperHost; + + if (Uri.Host.find(':') != string::npos) + ProperHost = '[' + Uri.Host + ']'; else - { - Srv->In.Limit(-1); + ProperHost = Uri.Host; + + /* RFC 2616 §5.1.2 requires absolute URIs for requests to proxies, + but while its a must for all servers to accept absolute URIs, + it is assumed clients will sent an absolute path for non-proxies */ + std::string requesturi; + if (Server->Proxy.empty() == true || Server->Proxy.Host.empty()) + requesturi = Uri.Path; + else + requesturi = Itm->Uri; - // Nothing left in the buffer - if (Srv->In.WriteSpace() == false) - return false; - - // We may have got multiple responses back in one packet.. - Srv->Close(); - return true; + // The "+" is encoded as a workaround for a amazon S3 bug + // see LP bugs #1003633 and #1086997. + requesturi = QuoteString(requesturi, "+~ "); + + /* Build the request. No keep-alive is included as it is the default + in 1.1, can cause problems with proxies, and we are an HTTP/1.1 + client anyway. + C.f. https://tools.ietf.org/wg/httpbis/trac/ticket/158 */ + Req << "GET " << requesturi << " HTTP/1.1\r\n"; + if (Uri.Port != 0) + Req << "Host: " << ProperHost << ":" << Uri.Port << "\r\n"; + else + Req << "Host: " << ProperHost << "\r\n"; + + // generate a cache control header (if needed) + if (_config->FindB("Acquire::http::No-Cache",false) == true) + Req << "Cache-Control: no-cache\r\n" + << "Pragma: no-cache\r\n"; + else if (Itm->IndexFile == true) + Req << "Cache-Control: max-age=" << _config->FindI("Acquire::http::Max-Age",0) << "\r\n"; + else if (_config->FindB("Acquire::http::No-Store",false) == true) + Req << "Cache-Control: no-store\r\n"; + + // If we ask for uncompressed files servers might respond with content- + // negotiation 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::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) + Req << "Accept: text/*\r\n"; } - - return false; + + // Check for a partial file and send if-queries accordingly + struct stat SBuf; + if (stat(Itm->DestFile.c_str(),&SBuf) >= 0 && SBuf.st_size > 0) + Req << "Range: bytes=" << SBuf.st_size << "-\r\n" + << "If-Range: " << TimeRFC1123(SBuf.st_mtime) << "\r\n"; + else if (Itm->LastModified != 0) + Req << "If-Modified-Since: " << TimeRFC1123(Itm->LastModified).c_str() << "\r\n"; + + if (Server->Proxy.User.empty() == false || Server->Proxy.Password.empty() == false) + Req << "Proxy-Authorization: Basic " + << Base64Encode(Server->Proxy.User + ":" + Server->Proxy.Password) << "\r\n"; + + maybe_add_auth (Uri, _config->FindFile("Dir::Etc::netrc")); + if (Uri.User.empty() == false || Uri.Password.empty() == false) + Req << "Authorization: Basic " + << Base64Encode(Uri.User + ":" + Uri.Password) << "\r\n"; + + Req << "User-Agent: " << _config->Find("Acquire::http::User-Agent", + "Debian APT-HTTP/1.3 (" PACKAGE_VERSION ")") << "\r\n"; + + Req << "\r\n"; + + if (Debug == true) + cerr << Req << endl; + + Server->WriteResponse(Req.str()); } /*}}}*/ -// HttpMethod::DealWithHeaders - Handle the retrieved header data /*{{{*/ +// HttpMethod::Configuration - Handle a configuration message /*{{{*/ // --------------------------------------------------------------------- -/* 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 */ -int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) +/* We stash the desired pipeline depth */ +bool HttpMethod::Configuration(string Message) { - // Not Modified - if (Srv->Result == 304) - { - unlink(Queue->DestFile.c_str()); - Res.IMSHit = true; - Res.LastModified = Queue->LastModified; - return 1; - } - - /* We have a reply we dont handle. This should indicate a perm server - failure */ - if (Srv->Result < 200 || Srv->Result >= 300) - { - _error->Error("%u %s",Srv->Result,Srv->Code); - return 3; - } + if (ServerMethod::Configuration(Message) == false) + return false; - // This is some sort of 2xx 'data follows' reply - Res.LastModified = Srv->Date; - Res.Size = Srv->Size; - - // Open the file - delete File; - File = new FileFd(Queue->DestFile,FileFd::WriteAny); - if (_error->PendingError() == true) - return 3; - - // Set the expected size - if (Srv->StartPos >= 0) - { - Res.ResumePoint = Srv->StartPos; - ftruncate(File->Fd(),Srv->StartPos); - } - - // Set the start point - lseek(File->Fd(),0,SEEK_END); + AllowRedirect = _config->FindB("Acquire::http::AllowRedirect",true); + PipelineDepth = _config->FindI("Acquire::http::Pipeline-Depth", + PipelineDepth); + Debug = _config->FindB("Debug::Acquire::http",false); - delete Srv->In.MD5; - Srv->In.MD5 = new MD5Summation; - - // Fill the MD5 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) - { - _error->Errno("read","Problem hashing file"); - return 3; - } - lseek(File->Fd(),0,SEEK_END); - } - - SetNonBlock(File->Fd(),true); - return 0; + // Get the proxy to use + AutoDetectProxy(); + + return true; } /*}}}*/ -// HttpMethod::Loop /*{{{*/ +// HttpMethod::AutoDetectProxy - auto detect proxy /*{{{*/ // --------------------------------------------------------------------- /* */ -int HttpMethod::Loop() +bool HttpMethod::AutoDetectProxy() { - ServerState *Server = 0; - - while (1) - { - // We have no commands, wait for some to arrive - if (Queue == 0) - { - if (WaitFd(STDIN_FILENO) == false) - return 0; - } - - // Run messages - if (Run(true) != 0) - return 100; + // option is "Acquire::http::Proxy-Auto-Detect" but we allow the old + // name without the dash ("-") + AutoDetectProxyCmd = _config->Find("Acquire::http::Proxy-Auto-Detect", + _config->Find("Acquire::http::ProxyAutoDetect")); - if (Queue == 0) - continue; - - // Connect to the server - if (Server == 0 || Server->Comp(Queue->Uri) == false) - { - delete Server; - Server = new ServerState(Queue->Uri,this); - } - - // Connnect to the host - if (Server->Open() == false) - { - Fail(); - continue; - } - - // Queue the request - SendReq(Queue,Server->In); + if (AutoDetectProxyCmd.empty()) + return true; - // Handle the header data - if (Server->RunHeaders() == false) - { - Fail(); - continue; - } - - // Decide what to do. - FetchResult Res; - switch (DealWithHeaders(Res,Server)) - { - // Ok, the file is Open - case 0: - { - URIStart(Res); + if (Debug) + clog << "Using auto proxy detect command: " << AutoDetectProxyCmd << endl; - // Run the data - if (Server->RunData() == false) - Fail(); - - Res.MD5Sum = Srv->In.MD5->Result(); - delete File; - File = 0; - break; - } - - // IMS hit - case 1: - { - URIDone(Res); - break; - } - - // Hard server error, not found or something - case 3: - { - Fail(); - break; - } - - default: - Fail("Internal error"); - break; - } + 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); } - - return 0; + char buf[512]; + int InFd = Pipes[0]; + close(Pipes[1]); + int res = read(InFd, buf, sizeof(buf)-1); + 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; } /*}}}*/ - -int main() +ServerState * HttpMethod::CreateServerState(URI uri) /*{{{*/ { - HttpMethod Mth; - - return Mth.Loop(); + return new HttpServerState(uri, this); } + /*}}}*/ +void HttpMethod::RotateDNS() /*{{{*/ +{ + ::RotateDNS(); +} + /*}}}*/