X-Git-Url: https://git.saurik.com/apt.git/blobdiff_plain/a99e1914169e0dfb1367d5a04a4611ead9839d02..410ea6525d613fe91029d5a6526254739fc5880b:/methods/http.cc diff --git a/methods/http.cc b/methods/http.cc index 068d26978..13f9cbe06 100644 --- a/methods/http.cc +++ b/methods/http.cc @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -39,8 +40,10 @@ #include #include #include +#include #include + // Internet stuff #include @@ -48,7 +51,6 @@ #include "connect.h" #include "rfc2553emu.h" #include "http.h" - /*}}}*/ using namespace std; @@ -57,6 +59,7 @@ 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; @@ -64,7 +67,7 @@ unsigned long CircleBuf::BwReadLimit=0; unsigned long CircleBuf::BwTickReadData=0; struct timeval CircleBuf::BwReadTick={0,0}; const unsigned int CircleBuf::BW_HZ=10; - + // CircleBuf::CircleBuf - Circular input buffer /*{{{*/ // --------------------------------------------------------------------- /* */ @@ -309,22 +312,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) @@ -368,9 +376,9 @@ 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 */ -int ServerState::RunHeaders() +/* Returns 0 if things are OK, 1 if an IO error occurred and 2 if a header + parse error occurred */ +ServerState::RunHeadersResult ServerState::RunHeaders() { State = Header; @@ -399,7 +407,7 @@ int ServerState::RunHeaders() string::const_iterator J = I; for (; J != Data.end() && *J != '\n' && *J != '\r';J++); if (HeaderLine(string(I,J)) == false) - return 2; + return RUN_HEADERS_PARSE_ERROR; I = J; } @@ -411,11 +419,11 @@ int ServerState::RunHeaders() if (Encoding == Closes && HaveContent == true) Persistent = false; - return 0; + return RUN_HEADERS_OK; } while (Owner->Go(false,this) == true); - return 1; + return RUN_HEADERS_IO_ERROR; } /*}}}*/ // ServerState::RunData - Transfer the data from the socket /*{{{*/ @@ -545,15 +553,21 @@ bool ServerState::HeaderLine(string Line) // Evil servers return no version if (Line[4] == '/') { - if (sscanf(Line.c_str(),"HTTP/%u.%u %u %[^\n]",&Major,&Minor, - &Result,Code) != 4) + int const elements = sscanf(Line.c_str(),"HTTP/%u.%u %u%[^\n]",&Major,&Minor,&Result,Code); + if (elements == 3) + { + Code[0] = '\0'; + if (Debug == true) + clog << "HTTP server doesn't give Reason-Phrase for " << Result << std::endl; + } + else if (elements != 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) + if (sscanf(Line.c_str(),"HTTP %u%[^\n]",&Result,Code) != 2) return _error->Error(_("The HTTP server sent an invalid reply header")); } @@ -623,11 +637,17 @@ bool ServerState::HeaderLine(string Line) if (stringcasecmp(Tag,"Last-Modified:") == 0) { - if (StrToTime(Val,Date) == false) + if (RFC1123StrToTime(Val.c_str(), Date) == false) return _error->Error(_("Unknown date format")); return true; } + if (stringcasecmp(Tag,"Location:") == 0) + { + Location = Val; + return true; + } + return true; } /*}}}*/ @@ -668,23 +688,25 @@ 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()); - // only generate a cache control header if we actually want to - // use a cache - if (_config->FindB("Acquire::http::No-Cache",false) == false) + } + // generate a cache control header (if needed) + 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) { - if (Itm->IndexFile == true) - sprintf(Buf+strlen(Buf),"Cache-Control: max-age=%u\r\n", - _config->FindI("Acquire::http::Max-Age",0)); - else - { - if (_config->FindB("Acquire::http::No-Store",false) == true) - strcat(Buf,"Cache-Control: no-store\r\n"); - } + sprintf(Buf+strlen(Buf),"Cache-Control: max-age=%u\r\n", + _config->FindI("Acquire::http::Max-Age",0)); + } + else + { + if (_config->FindB("Acquire::http::No-Store",false) == true) + strcat(Buf,"Cache-Control: no-store\r\n"); } } - // 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; @@ -711,11 +733,14 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) Req += string("Proxy-Authorization: Basic ") + Base64Encode(Proxy.User + ":" + Proxy.Password) + "\r\n"; + maybe_add_auth (Uri, _config->FindFile("Dir::Etc::netrc")); 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 ("VERSION")\r\n\r\n"; + } + Req += "User-Agent: " + _config->Find("Acquire::http::User-Agent", + "Debian APT-HTTP/1.3 ("VERSION")") + "\r\n\r\n"; if (Debug == true) cerr << Req << endl; @@ -753,9 +778,10 @@ bool HttpMethod::Go(bool ToFile,ServerState *Srv) if (Srv->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); // Figure out the max fd int MaxFd = FileFD; @@ -895,13 +921,10 @@ bool HttpMethod::ServerDie(ServerState *Srv) // HttpMethod::DealWithHeaders - Handle the retrieved header data /*{{{*/ // --------------------------------------------------------------------- /* 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 - 4 - Error with error content page - 5 - Unrecoverable non-server error (close the connection) */ -int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) + to do. Returns DealWithHeadersResult (see http.h for details). + */ +HttpMethod::DealWithHeadersResult +HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) { // Not Modified if (Srv->Result == 304) @@ -909,17 +932,57 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) unlink(Queue->DestFile.c_str()); Res.IMSHit = true; Res.LastModified = Queue->LastModified; - return 1; + return IMS_HIT; } + /* 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() == true); + else if (Srv->Location[0] == '/' && Queue->Uri.empty() == false) + { + URI Uri = Queue->Uri; + if (Uri.Host.empty() == false) + { + if (Uri.Port != 0) + strprintf(NextURI, "http://%s:%u", Uri.Host.c_str(), Uri.Port); + else + NextURI = "http://" + Uri.Host; + } + else + NextURI.clear(); + NextURI.append(DeQuoteString(Srv->Location)); + return TRY_AGAIN_OR_REDIRECT; + } + else + { + NextURI = DeQuoteString(Srv->Location); + return TRY_AGAIN_OR_REDIRECT; + } + /* 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) { + char err[255]; + snprintf(err,sizeof(err)-1,"HttpError%i",Srv->Result); + SetFailReason(err); _error->Error("%u %s",Srv->Result,Srv->Code); if (Srv->HaveContent == true) - return 4; - return 3; + return ERROR_WITH_CONTENT_PAGE; + return ERROR_UNRECOVERABLE; } // This is some sort of 2xx 'data follows' reply @@ -930,7 +993,7 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) delete File; File = new FileFd(Queue->DestFile,FileFd::WriteAny); if (_error->PendingError() == true) - return 5; + return ERROR_NOT_FROM_SERVER; FailFile = Queue->DestFile; FailFile.c_str(); // Make sure we dont do a malloc in the signal handler @@ -941,7 +1004,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 @@ -957,13 +1021,13 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) if (Srv->In.Hash->AddFD(File->Fd(),Srv->StartPos) == false) { _error->Errno("read",_("Problem hashing file")); - return 5; + return ERROR_NOT_FROM_SERVER; } lseek(File->Fd(),0,SEEK_END); } SetNonBlock(File->Fd(),true); - return 0; + return FILE_IS_OPEN; } /*}}}*/ // HttpMethod::SigTerm - Handle a fatal signal /*{{{*/ @@ -1025,11 +1089,16 @@ 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); Debug = _config->FindB("Debug::Acquire::http",false); - + AutoDetectProxyCmd = _config->Find("Acquire::http::ProxyAutoDetect"); + + // Get the proxy to use + AutoDetectProxy(); + return true; } /*}}}*/ @@ -1038,6 +1107,10 @@ bool HttpMethod::Configuration(string Message) /* */ int HttpMethod::Loop() { + typedef vector StringVector; + typedef vector::iterator StringVectorIterator; + map Redirected; + signal(SIGTERM,SigTerm); signal(SIGINT,SigTerm); @@ -1057,7 +1130,13 @@ int HttpMethod::Loop() do a WaitFd above.. Otherwise the FD is closed. */ int Result = Run(true); if (Result != -1 && (Result != 0 || Queue == 0)) - return 100; + { + if(FailReason.empty() == false || + _config->FindB("Acquire::http::DependOnSTDIN", true) == true) + return 100; + else + return 0; + } if (Queue == 0) continue; @@ -1095,11 +1174,11 @@ int HttpMethod::Loop() // Fetch the next URL header data from the server. switch (Server->RunHeaders()) { - case 0: + case ServerState::RUN_HEADERS_OK: break; // The header data is bad - case 2: + case ServerState::RUN_HEADERS_PARSE_ERROR: { _error->Error(_("Bad header data")); Fail(true); @@ -1109,7 +1188,7 @@ int HttpMethod::Loop() // The server closed a connection during the header get.. default: - case 1: + case ServerState::RUN_HEADERS_IO_ERROR: { FailCounter++; _error->Discard(); @@ -1133,7 +1212,7 @@ int HttpMethod::Loop() switch (DealWithHeaders(Res,Server)) { // Ok, the file is Open - case 0: + case FILE_IS_OPEN: { URIStart(Res); @@ -1164,44 +1243,43 @@ int HttpMethod::Loop() URIDone(Res); } else - { - if (Server->ServerFd == -1) + { + if (Server->ServerFd == -1) + { + FailCounter++; + _error->Discard(); + Server->Close(); + + if (FailCounter >= 2) { - FailCounter++; - _error->Discard(); - Server->Close(); - - if (FailCounter >= 2) - { - Fail(_("Connection failed"),true); - FailCounter = 0; - } - - QueueBack = Queue; + Fail(_("Connection failed"),true); + FailCounter = 0; } - else - Fail(true); - } - + + QueueBack = Queue; + } + else + Fail(true); + } break; } // IMS hit - case 1: + case IMS_HIT: { URIDone(Res); break; } // Hard server error, not found or something - case 3: + case ERROR_UNRECOVERABLE: { Fail(); break; } // Hard internal error, kill the connection and fail - case 5: + case ERROR_NOT_FROM_SERVER: { delete File; File = 0; @@ -1213,7 +1291,7 @@ int HttpMethod::Loop() } // We need to flush the data, the header is like a 404 w/ error text - case 4: + case ERROR_WITH_CONTENT_PAGE: { Fail(); @@ -1225,6 +1303,46 @@ int HttpMethod::Loop() break; } + // Try again with a new URL + case TRY_AGAIN_OR_REDIRECT: + { + // 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; @@ -1236,14 +1354,57 @@ int HttpMethod::Loop() return 0; } /*}}}*/ - -int main() +// HttpMethod::AutoDetectProxy - auto detect proxy /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool HttpMethod::AutoDetectProxy() { - setlocale(LC_ALL, ""); + if (AutoDetectProxyCmd.empty()) + return true; - HttpMethod Mth; - - return Mth.Loop(); + if (Debug) + clog << "Using auto proxy detect command: " << AutoDetectProxyCmd << endl; + + 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); + } + char buf[512]; + int InFd = Pipes[0]; + close(Pipes[1]); + int res = read(InFd, buf, sizeof(buf)); + 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; } + /*}}}*/