X-Git-Url: https://git.hcoop.net/ntk/apt.git/blobdiff_plain/be4401bfa4a240bbc894e1bfeb1e1e8d63fc7b18..1ae93c94429de697fb17f7067367fbf32fd3b6fc:/methods/http.cc?ds=sidebyside diff --git a/methods/http.cc b/methods/http.cc index 6418a771..7a9a97a1 100644 --- a/methods/http.cc +++ b/methods/http.cc @@ -1,6 +1,6 @@ // -*- mode: cpp; mode: fold -*- // Description /*{{{*/ -// $Id: http.cc,v 1.1 1998/11/01 05:30:47 jgg Exp $ +// $Id: http.cc,v 1.43 1999/12/10 23:40:29 jgg Exp $ /* ###################################################################### HTTP Aquire Method - This is the HTTP aquire method for APT. @@ -36,17 +36,26 @@ #include #include #include +#include #include +#include // Internet stuff -#include -#include -#include #include +#include "connect.h" +#include "rfc2553emu.h" #include "http.h" + /*}}}*/ +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 /*{{{*/ // --------------------------------------------------------------------- /* */ @@ -240,7 +249,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(); @@ -250,52 +259,77 @@ ServerState::ServerState(URI Srv,HttpMethod *Owner) : Owner(Owner), // --------------------------------------------------------------------- /* This opens a connection to the server. */ string LastHost; -in_addr LastHostA; +int LastPort = 0; +struct addrinfo *LastHostAddr = 0; bool ServerState::Open() { + // Use the already open connection if possible. + if (ServerFd != -1) + return true; + Close(); + In.Reset(); + Out.Reset(); + + // Determine the proxy setting + if (getenv("http_proxy") == 0) + { + 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; + } + else + Proxy = getenv("http_proxy"); - int Port; - string Host; + // Parse no_proxy, a , seperated list of hosts + if (getenv("no_proxy") != 0) + { + const char *Start = getenv("no_proxy"); + for (const char *Cur = Start; true ; Cur++) + { + if (*Cur != ',' && *Cur != 0) + continue; + if (stringcasecmp(ServerName.Host.begin(),ServerName.Host.end(), + Start,Cur) == 0) + { + Proxy = ""; + break; + } + + Start = Cur + 1; + if (*Cur == 0) + break; + } + } - if (Proxy.empty() == false) + // Determine what host and port to use based on the proxy settings + int Port = 0; + string Host; + if (Proxy.empty() == true) { - Port = ServerName.Port; + if (ServerName.Port != 0) + Port = ServerName.Port; Host = ServerName.Host; } else { - Port = Proxy.Port; + if (Proxy.Port != 0) + Port = Proxy.Port; Host = Proxy.Host; } - 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->Errno("gethostbyname","Could not lookup host %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; } /*}}}*/ @@ -306,15 +340,14 @@ bool ServerState::Close() { close(ServerFd); ServerFd = -1; - In.Reset(); - Out.Reset(); return true; } /*}}}*/ // ServerState::RunHeaders - Get the headers before the data /*{{{*/ // --------------------------------------------------------------------- -/* */ -bool ServerState::RunHeaders() +/* Returns 0 if things are OK, 1 if an IO error occursed and 2 if a header + parse error occured */ +int ServerState::RunHeaders() { State = Header; @@ -325,7 +358,8 @@ bool ServerState::RunHeaders() Result = 0; Size = 0; StartPos = 0; - Encoding = Closes; + Encoding = Closes; + HaveContent = false; time(&Date); do @@ -339,14 +373,14 @@ bool ServerState::RunHeaders() string::const_iterator J = I; for (; J != Data.end() && *J != '\n' && *J != '\r';J++); if (HeaderLine(string(I,J-I)) == false) - return false; + return 2; I = J; } - return true; + return 0; } while (Owner->Go(false,this) == true); - - return false; + + return 1; } /*}}}*/ // ServerState::RunData - Transfer the data from the socket /*{{{*/ @@ -391,7 +425,7 @@ bool ServerState::RunData() while ((Last = Owner->Go(false,this)) == true); if (Last == false) return false; - return true; + return !_error->PendingError(); } // Transfer the block @@ -415,7 +449,7 @@ bool ServerState::RunData() while ((Last = Owner->Go(false,this)) == true); if (Last == false) return false; - } + } } else { @@ -433,12 +467,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 /*{{{*/ @@ -448,19 +482,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,"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.begin(),Tag.begin()+4,"HTTP") == 0) { // Evil servers return no version if (Line[4] == '/') @@ -480,10 +525,11 @@ bool ServerState::HeaderLine(string Line) return true; } - if (stringcasecmp(Tag,"Content-Length:")) + if (stringcasecmp(Tag,"Content-Length:") == 0) { if (Encoding == Closes) Encoding = Stream; + HaveContent = true; // The length is already set from the Content-Range header if (StartPos != 0) @@ -494,8 +540,16 @@ bool ServerState::HeaderLine(string Line) return true; } - if (stringcasecmp(Tag,"Content-Range:")) + if (stringcasecmp(Tag,"Content-Type:") == 0) + { + HaveContent = true; + return true; + } + + if (stringcasecmp(Tag,"Content-Range:") == 0) { + 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"); if ((unsigned)StartPos > Size) @@ -503,14 +557,16 @@ bool ServerState::HeaderLine(string Line) return true; } - if (stringcasecmp(Tag,"Transfer-Encoding:")) + if (stringcasecmp(Tag,"Transfer-Encoding:") == 0) { - if (stringcasecmp(Val,"chunked")) + HaveContent = true; + if (stringcasecmp(Val,"chunked") == 0) Encoding = Chunked; + return true; } - if (stringcasecmp(Tag,"Last-Modified:")) + if (stringcasecmp(Tag,"Last-Modified:") == 0) { if (StrToTime(Val,Date) == false) return _error->Error("Unknown date format"); @@ -527,9 +583,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) { @@ -537,21 +593,49 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) ProperHost += Buf; } - // Build the request + // 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 + 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) 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()); - string Req = Buf; + 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 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, + sprintf(Buf,"Range: bytes=%li-\r\nIf-Range: %s\r\n",(long)SBuf.st_size - 1, TimeRFC1123(SBuf.st_mtime).c_str()); Req += Buf; } @@ -564,10 +648,15 @@ 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"; + + if (Debug == true) + cerr << Req << endl; + Out.Read(Req); } /*}}}*/ @@ -578,13 +667,13 @@ 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) @@ -603,23 +692,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) @@ -628,11 +711,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)) { @@ -658,7 +736,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); } @@ -681,6 +759,8 @@ bool HttpMethod::Flush(ServerState *Srv) { if (Srv->In.Write(File->Fd()) == false) return _error->Errno("write","Error writing to file"); + if (Srv->In.IsLimit() == true) + return true; } if (Srv->In.IsLimit() == true || Srv->Encoding == ServerState::Closes) @@ -694,6 +774,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) { @@ -702,6 +784,10 @@ bool HttpMethod::ServerDie(ServerState *Srv) { if (Srv->In.Write(File->Fd()) == false) return _error->Errno("write","Error writing to the file"); + + // Done + if (Srv->In.IsLimit() == true) + return true; } } @@ -709,8 +795,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 @@ -735,7 +823,9 @@ bool HttpMethod::ServerDie(ServerState *Srv) to do. Returns 0 - File is open, 1 - IMS hit - 3 - Unrecoverable error */ + 3 - Unrecoverable error + 4 - Error with error content page + 5 - Unrecoverable non-server error (close the connection) */ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) { // Not Modified @@ -752,6 +842,8 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) if (Srv->Result < 200 || Srv->Result >= 300) { _error->Error("%u %s",Srv->Result,Srv->Code); + if (Srv->HaveContent == true) + return 4; return 3; } @@ -763,8 +855,13 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) delete File; File = new FileFd(Queue->DestFile,FileFd::WriteAny); if (_error->PendingError() == true) - return 3; - + 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; + // Set the expected size if (Srv->StartPos >= 0) { @@ -785,7 +882,7 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) if (Srv->In.MD5->AddFD(File->Fd(),Srv->StartPos) == false) { _error->Errno("read","Problem hashing file"); - return 3; + return 5; } lseek(File->Fd(),0,SEEK_END); } @@ -794,15 +891,89 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) return 0; } /*}}}*/ -// HttpMethod::Loop /*{{{*/ +// HttpMethod::SigTerm - Handle a fatal signal /*{{{*/ +// --------------------------------------------------------------------- +/* This closes and timestamps the open file. This is neccessary to get + resume behavoir on user abort */ +void HttpMethod::SigTerm(int) +{ + if (FailFd == -1) + _exit(100); + close(FailFd); + + // Timestamp + struct utimbuf UBuf; + UBuf.actime = FailTime; + UBuf.modtime = FailTime; + utime(FailFile.c_str(),&UBuf); + + _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 /*{{{*/ // --------------------------------------------------------------------- /* */ int HttpMethod::Loop() { - ServerState *Server = 0; + signal(SIGTERM,SigTerm); + signal(SIGINT,SigTerm); + + Server = 0; + int FailCounter = 0; while (1) - { + { // We have no commands, wait for some to arrive if (Queue == 0) { @@ -810,8 +981,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) @@ -824,25 +997,58 @@ int HttpMethod::Loop() Server = new ServerState(Queue->Uri,this); } + // 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->In); - // Handle the header data - if (Server->RunHeaders() == false) - { - Fail(); - continue; - } + // Fill the pipeline. + Fetch(0); + // Fetch the next URL header data from the server. + switch (Server->RunHeaders()) + { + case 0: + break; + + // The header data is bad + case 2: + { + _error->Error("Bad header Data"); + Fail(true); + continue; + } + + // The server closed a connection during the header get.. + default: + case 1: + { + FailCounter++; + _error->Discard(); + Server->Close(); + Server->Pipeline = false; + + if (FailCounter >= 2) + { + Fail("Connection failed",true); + FailCounter = 0; + } + + continue; + } + }; + // Decide what to do. FetchResult Res; + Res.Filename = Queue->DestFile; switch (DealWithHeaders(Res,Server)) { // Ok, the file is Open @@ -851,12 +1057,29 @@ int HttpMethod::Loop() URIStart(Res); // Run the data - if (Server->RunData() == false) - Fail(); - - Res.MD5Sum = Srv->In.MD5->Result(); + bool Result = Server->RunData(); + + // Close the file, destroy the FD object and timestamp it + FailFd = -1; delete File; File = 0; + + // Timestamp + struct utimbuf UBuf; + time(&UBuf.actime); + UBuf.actime = Server->Date; + UBuf.modtime = Server->Date; + utime(Queue->DestFile.c_str(),&UBuf); + + // Send status to APT + if (Result == true) + { + Res.MD5Sum = Server->In.MD5->Result(); + URIDone(Res); + } + else + Fail(true); + break; } @@ -873,11 +1096,34 @@ int HttpMethod::Loop() Fail(); break; } + + // Hard internal error, kill the connection and fail + case 5: + { + Fail(); + Server->Close(); + break; + } + + // We need to flush the data, the header is like a 404 w/ error text + case 4: + { + Fail(); + + // Send to content to dev/null + File = new FileFd("/dev/null",FileFd::WriteExists); + Server->RunData(); + delete File; + File = 0; + break; + } default: Fail("Internal error"); break; - } + } + + FailCounter = 0; } return 0;