1 // -*- mode: cpp; mode: fold -*-
3 // $Id: http.cc,v 1.59 2004/05/08 19:42:35 mdz Exp $
4 /* ######################################################################
6 HTTP Aquire Method - This is the HTTP aquire method for APT.
8 It uses HTTP/1.1 and many of the fancy options there-in, such as
9 pipelining, range, if-range and so on.
11 It is based on a doubly buffered select loop. A groupe of requests are
12 fed into a single output buffer that is constantly fed out the
13 socket. This provides ideal pipelining as in many cases all of the
14 requests will fit into a single packet. The input socket is buffered
15 the same way and fed into the fd for the file (may be a pipe in future).
17 This double buffering provides fairly substantial transfer rates,
18 compared to wget the http method is about 4% faster. Most importantly,
19 when HTTP is compared with FTP as a protocol the speed difference is
20 huge. In tests over the internet from two sites to llug (via ATM) this
21 program got 230k/s sustained http transfer rates. FTP on the other
22 hand topped out at 170k/s. That combined with the time to setup the
23 FTP connection makes HTTP a vastly superior protocol.
25 ##################################################################### */
27 // Include Files /*{{{*/
28 #include <apt-pkg/fileutl.h>
29 #include <apt-pkg/acquire-method.h>
30 #include <apt-pkg/error.h>
31 #include <apt-pkg/hashes.h>
46 #include <arpa/inet.h>
48 #include <CoreFoundation/CoreFoundation.h>
49 #include <CoreServices/CoreServices.h>
50 #include <SystemConfiguration/SystemConfiguration.h>
53 #include "rfc2553emu.h"
59 void CfrsError(const char *name, CFReadStreamRef rs) {
60 CFStreamError se = CFReadStreamGetError(rs);
62 if (se.domain == kCFStreamErrorDomainCustom) {
63 } else if (se.domain == kCFStreamErrorDomainPOSIX) {
64 _error->Error("POSIX: %s", strerror(se.error));
65 } else if (se.domain == kCFStreamErrorDomainMacOSStatus) {
66 _error->Error("MacOSStatus: %ld", se.error);
67 } else if (se.domain == kCFStreamErrorDomainNetDB) {
68 _error->Error("NetDB: %s %s", name, gai_strerror(se.error));
69 } else if (se.domain == kCFStreamErrorDomainMach) {
70 _error->Error("Mach: %ld", se.error);
71 } else if (se.domain == kCFStreamErrorDomainHTTP) {
73 case kCFStreamErrorHTTPParseFailure:
74 _error->Error("Parse failure");
77 case kCFStreamErrorHTTPRedirectionLoop:
78 _error->Error("Redirection loop");
81 case kCFStreamErrorHTTPBadURL:
82 _error->Error("Bad URL");
86 _error->Error("Unknown HTTP error: %ld", se.error);
89 } else if (se.domain == kCFStreamErrorDomainSOCKS) {
90 _error->Error("SOCKS: %ld", se.error);
91 } else if (se.domain == kCFStreamErrorDomainSystemConfiguration) {
92 _error->Error("SystemConfiguration: %ld", se.error);
93 } else if (se.domain == kCFStreamErrorDomainSSL) {
94 _error->Error("SSL: %ld", se.error);
96 _error->Error("Domain #%ld: %ld", se.domain, se.error);
100 string HttpMethod::FailFile;
101 int HttpMethod::FailFd = -1;
102 time_t HttpMethod::FailTime = 0;
103 unsigned long PipelineDepth = 10;
104 unsigned long TimeOut = 120;
107 unsigned long CircleBuf::BwReadLimit=0;
108 unsigned long CircleBuf::BwTickReadData=0;
109 struct timeval CircleBuf::BwReadTick={0,0};
110 const unsigned int CircleBuf::BW_HZ=10;
112 // CircleBuf::CircleBuf - Circular input buffer /*{{{*/
113 // ---------------------------------------------------------------------
115 CircleBuf::CircleBuf(unsigned long Size) : Size(Size), Hash(0)
117 Buf = new unsigned char[Size];
120 CircleBuf::BwReadLimit = _config->FindI("Acquire::http::Dl-Limit",0)*1024;
123 // CircleBuf::Reset - Reset to the default state /*{{{*/
124 // ---------------------------------------------------------------------
126 void CircleBuf::Reset()
131 MaxGet = (unsigned int)-1;
140 // CircleBuf::Read - Read from a FD into the circular buffer /*{{{*/
141 // ---------------------------------------------------------------------
142 /* This fills up the buffer with as much data as is in the FD, assuming it
144 bool CircleBuf::Read(int Fd)
146 unsigned long BwReadMax;
150 // Woops, buffer is full
151 if (InP - OutP == Size)
154 // what's left to read in this tick
155 BwReadMax = CircleBuf::BwReadLimit/BW_HZ;
157 if(CircleBuf::BwReadLimit) {
159 gettimeofday(&now,0);
161 unsigned long d = (now.tv_sec-CircleBuf::BwReadTick.tv_sec)*1000000 +
162 now.tv_usec-CircleBuf::BwReadTick.tv_usec;
163 if(d > 1000000/BW_HZ) {
164 CircleBuf::BwReadTick = now;
165 CircleBuf::BwTickReadData = 0;
168 if(CircleBuf::BwTickReadData >= BwReadMax) {
169 usleep(1000000/BW_HZ);
174 // Write the buffer segment
176 if(CircleBuf::BwReadLimit) {
177 Res = read(Fd,Buf + (InP%Size),
178 BwReadMax > LeftRead() ? LeftRead() : BwReadMax);
180 Res = read(Fd,Buf + (InP%Size),LeftRead());
182 if(Res > 0 && BwReadLimit > 0)
183 CircleBuf::BwTickReadData += Res;
195 gettimeofday(&Start,0);
200 // CircleBuf::Read - Put the string into the buffer /*{{{*/
201 // ---------------------------------------------------------------------
202 /* This will hold the string in and fill the buffer with it as it empties */
203 bool CircleBuf::Read(string Data)
210 // CircleBuf::FillOut - Fill the buffer from the output queue /*{{{*/
211 // ---------------------------------------------------------------------
213 void CircleBuf::FillOut()
215 if (OutQueue.empty() == true)
219 // Woops, buffer is full
220 if (InP - OutP == Size)
223 // Write the buffer segment
224 unsigned long Sz = LeftRead();
225 if (OutQueue.length() - StrPos < Sz)
226 Sz = OutQueue.length() - StrPos;
227 memcpy(Buf + (InP%Size),OutQueue.c_str() + StrPos,Sz);
232 if (OutQueue.length() == StrPos)
241 // CircleBuf::Write - Write from the buffer into a FD /*{{{*/
242 // ---------------------------------------------------------------------
243 /* This empties the buffer into the FD. */
244 bool CircleBuf::Write(int Fd)
250 // Woops, buffer is empty
257 // Write the buffer segment
259 Res = write(Fd,Buf + (OutP%Size),LeftWrite());
272 Hash->Add(Buf + (OutP%Size),Res);
278 // CircleBuf::WriteTillEl - Write from the buffer to a string /*{{{*/
279 // ---------------------------------------------------------------------
280 /* This copies till the first empty line */
281 bool CircleBuf::WriteTillEl(string &Data,bool Single)
283 // We cheat and assume it is unneeded to have more than one buffer load
284 for (unsigned long I = OutP; I < InP; I++)
286 if (Buf[I%Size] != '\n')
292 if (I < InP && Buf[I%Size] == '\r')
294 if (I >= InP || Buf[I%Size] != '\n')
302 unsigned long Sz = LeftWrite();
307 Data += string((char *)(Buf + (OutP%Size)),Sz);
315 // CircleBuf::Stats - Print out stats information /*{{{*/
316 // ---------------------------------------------------------------------
318 void CircleBuf::Stats()
324 gettimeofday(&Stop,0);
325 /* float Diff = Stop.tv_sec - Start.tv_sec +
326 (float)(Stop.tv_usec - Start.tv_usec)/1000000;
327 clog << "Got " << InP << " in " << Diff << " at " << InP/Diff << endl;*/
331 // ServerState::ServerState - Constructor /*{{{*/
332 // ---------------------------------------------------------------------
334 ServerState::ServerState(URI Srv,HttpMethod *Owner) : Owner(Owner),
335 In(64*1024), Out(4*1024),
341 // ServerState::Open - Open a connection to the server /*{{{*/
342 // ---------------------------------------------------------------------
343 /* This opens a connection to the server. */
344 bool ServerState::Open()
346 // Use the already open connection if possible.
355 // Determine the proxy setting
356 if (getenv("http_proxy") == 0)
358 string DefProxy = _config->Find("Acquire::http::Proxy");
359 string SpecificProxy = _config->Find("Acquire::http::Proxy::" + ServerName.Host);
360 if (SpecificProxy.empty() == false)
362 if (SpecificProxy == "DIRECT")
365 Proxy = SpecificProxy;
371 Proxy = getenv("http_proxy");
373 // Parse no_proxy, a , separated list of domains
374 if (getenv("no_proxy") != 0)
376 if (CheckDomainList(ServerName.Host,getenv("no_proxy")) == true)
380 // Determine what host and port to use based on the proxy settings
383 if (Proxy.empty() == true || Proxy.Host.empty() == true)
385 if (ServerName.Port != 0)
386 Port = ServerName.Port;
387 Host = ServerName.Host;
396 // Connect to the remote server
397 if (Connect(Host,Port,"http",80,ServerFd,TimeOut,Owner) == false)
403 // ServerState::Close - Close a connection to the server /*{{{*/
404 // ---------------------------------------------------------------------
406 bool ServerState::Close()
413 // ServerState::RunHeaders - Get the headers before the data /*{{{*/
414 // ---------------------------------------------------------------------
415 /* Returns 0 if things are OK, 1 if an IO error occursed and 2 if a header
416 parse error occured */
417 int ServerState::RunHeaders()
421 Owner->Status(_("Waiting for headers"));
435 if (In.WriteTillEl(Data) == false)
441 for (string::const_iterator I = Data.begin(); I < Data.end(); I++)
443 string::const_iterator J = I;
444 for (; J != Data.end() && *J != '\n' && *J != '\r';J++);
445 if (HeaderLine(string(I,J)) == false)
450 // 100 Continue is a Nop...
454 // Tidy up the connection persistance state.
455 if (Encoding == Closes && HaveContent == true)
460 while (Owner->Go(false,this) == true);
465 // ServerState::RunData - Transfer the data from the socket /*{{{*/
466 // ---------------------------------------------------------------------
468 bool ServerState::RunData()
472 // Chunked transfer encoding is fun..
473 if (Encoding == Chunked)
477 // Grab the block size
483 if (In.WriteTillEl(Data,true) == true)
486 while ((Last = Owner->Go(false,this)) == true);
491 // See if we are done
492 unsigned long Len = strtol(Data.c_str(),0,16);
497 // We have to remove the entity trailer
501 if (In.WriteTillEl(Data,true) == true && Data.length() <= 2)
504 while ((Last = Owner->Go(false,this)) == true);
507 return !_error->PendingError();
510 // Transfer the block
512 while (Owner->Go(true,this) == true)
513 if (In.IsLimit() == true)
517 if (In.IsLimit() == false)
520 // The server sends an extra new line before the next block specifier..
525 if (In.WriteTillEl(Data,true) == true)
528 while ((Last = Owner->Go(false,this)) == true);
535 /* Closes encoding is used when the server did not specify a size, the
536 loss of the connection means we are done */
537 if (Encoding == Closes)
540 In.Limit(Size - StartPos);
542 // Just transfer the whole block.
545 if (In.IsLimit() == false)
549 return !_error->PendingError();
551 while (Owner->Go(true,this) == true);
554 return Owner->Flush(this) && !_error->PendingError();
557 // ServerState::HeaderLine - Process a header line /*{{{*/
558 // ---------------------------------------------------------------------
560 bool ServerState::HeaderLine(string Line)
562 if (Line.empty() == true)
565 // The http server might be trying to do something evil.
566 if (Line.length() >= MAXLEN)
567 return _error->Error(_("Got a single header line over %u chars"),MAXLEN);
569 string::size_type Pos = Line.find(' ');
570 if (Pos == string::npos || Pos+1 > Line.length())
572 // Blah, some servers use "connection:closes", evil.
573 Pos = Line.find(':');
574 if (Pos == string::npos || Pos + 2 > Line.length())
575 return _error->Error(_("Bad header line"));
579 // Parse off any trailing spaces between the : and the next word.
580 string::size_type Pos2 = Pos;
581 while (Pos2 < Line.length() && isspace(Line[Pos2]) != 0)
584 string Tag = string(Line,0,Pos);
585 string Val = string(Line,Pos2);
587 if (stringcasecmp(Tag.c_str(),Tag.c_str()+4,"HTTP") == 0)
589 // Evil servers return no version
592 if (sscanf(Line.c_str(),"HTTP/%u.%u %u %[^\n]",&Major,&Minor,
594 return _error->Error(_("The HTTP server sent an invalid reply header"));
600 if (sscanf(Line.c_str(),"HTTP %u %[^\n]",&Result,Code) != 2)
601 return _error->Error(_("The HTTP server sent an invalid reply header"));
604 /* Check the HTTP response header to get the default persistance
610 if (Major == 1 && Minor <= 0)
619 if (stringcasecmp(Tag,"Content-Length:") == 0)
621 if (Encoding == Closes)
625 // The length is already set from the Content-Range header
629 if (sscanf(Val.c_str(),"%lu",&Size) != 1)
630 return _error->Error(_("The HTTP server sent an invalid Content-Length header"));
634 if (stringcasecmp(Tag,"Content-Type:") == 0)
640 if (stringcasecmp(Tag,"Content-Range:") == 0)
644 if (sscanf(Val.c_str(),"bytes %lu-%*u/%lu",&StartPos,&Size) != 2)
645 return _error->Error(_("The HTTP server sent an invalid Content-Range header"));
646 if ((unsigned)StartPos > Size)
647 return _error->Error(_("This HTTP server has broken range support"));
651 if (stringcasecmp(Tag,"Transfer-Encoding:") == 0)
654 if (stringcasecmp(Val,"chunked") == 0)
659 if (stringcasecmp(Tag,"Connection:") == 0)
661 if (stringcasecmp(Val,"close") == 0)
663 if (stringcasecmp(Val,"keep-alive") == 0)
668 if (stringcasecmp(Tag,"Last-Modified:") == 0)
670 if (StrToTime(Val,Date) == false)
671 return _error->Error(_("Unknown date format"));
679 // HttpMethod::SendReq - Send the HTTP request /*{{{*/
680 // ---------------------------------------------------------------------
681 /* This places the http request in the outbound buffer */
682 void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out)
686 // The HTTP server expects a hostname with a trailing :port
688 string ProperHost = Uri.Host;
691 sprintf(Buf,":%u",Uri.Port);
696 if (Itm->Uri.length() >= sizeof(Buf))
699 /* Build the request. We include a keep-alive header only for non-proxy
700 requests. This is to tweak old http/1.0 servers that do support keep-alive
701 but not HTTP/1.1 automatic keep-alive. Doing this with a proxy server
702 will glitch HTTP/1.0 proxies because they do not filter it out and
703 pass it on, HTTP/1.1 says the connection should default to keep alive
704 and we expect the proxy to do this */
705 if (Proxy.empty() == true || Proxy.Host.empty())
706 sprintf(Buf,"GET %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n",
707 QuoteString(Uri.Path,"~").c_str(),ProperHost.c_str());
710 /* Generate a cache control header if necessary. We place a max
711 cache age on index files, optionally set a no-cache directive
712 and a no-store directive for archives. */
713 sprintf(Buf,"GET %s HTTP/1.1\r\nHost: %s\r\n",
714 Itm->Uri.c_str(),ProperHost.c_str());
715 // only generate a cache control header if we actually want to
717 if (_config->FindB("Acquire::http::No-Cache",false) == false)
719 if (Itm->IndexFile == true)
720 sprintf(Buf+strlen(Buf),"Cache-Control: max-age=%u\r\n",
721 _config->FindI("Acquire::http::Max-Age",0));
724 if (_config->FindB("Acquire::http::No-Store",false) == true)
725 strcat(Buf,"Cache-Control: no-store\r\n");
729 // generate a no-cache header if needed
730 if (_config->FindB("Acquire::http::No-Cache",false) == true)
731 strcat(Buf,"Cache-Control: no-cache\r\nPragma: no-cache\r\n");
736 // Check for a partial file
738 if (stat(Itm->DestFile.c_str(),&SBuf) >= 0 && SBuf.st_size > 0)
740 // In this case we send an if-range query with a range header
741 sprintf(Buf,"Range: bytes=%li-\r\nIf-Range: %s\r\n",(long)SBuf.st_size - 1,
742 TimeRFC1123(SBuf.st_mtime).c_str());
747 if (Itm->LastModified != 0)
749 sprintf(Buf,"If-Modified-Since: %s\r\n",TimeRFC1123(Itm->LastModified).c_str());
754 if (Proxy.User.empty() == false || Proxy.Password.empty() == false)
755 Req += string("Proxy-Authorization: Basic ") +
756 Base64Encode(Proxy.User + ":" + Proxy.Password) + "\r\n";
758 if (Uri.User.empty() == false || Uri.Password.empty() == false)
759 Req += string("Authorization: Basic ") +
760 Base64Encode(Uri.User + ":" + Uri.Password) + "\r\n";
762 Req += "User-Agent: Debian APT-HTTP/1.3\r\n\r\n";
770 // HttpMethod::Go - Run a single loop /*{{{*/
771 // ---------------------------------------------------------------------
772 /* This runs the select loop over the server FDs, Output file FDs and
774 bool HttpMethod::Go(bool ToFile,ServerState *Srv)
776 // Server has closed the connection
777 if (Srv->ServerFd == -1 && (Srv->In.WriteSpace() == false ||
785 /* Add the server. We only send more requests if the connection will
787 if (Srv->Out.WriteSpace() == true && Srv->ServerFd != -1
788 && Srv->Persistent == true)
789 FD_SET(Srv->ServerFd,&wfds);
790 if (Srv->In.ReadSpace() == true && Srv->ServerFd != -1)
791 FD_SET(Srv->ServerFd,&rfds);
798 if (Srv->In.WriteSpace() == true && ToFile == true && FileFD != -1)
799 FD_SET(FileFD,&wfds);
802 FD_SET(STDIN_FILENO,&rfds);
804 // Figure out the max fd
806 if (MaxFd < Srv->ServerFd)
807 MaxFd = Srv->ServerFd;
814 if ((Res = select(MaxFd+1,&rfds,&wfds,0,&tv)) < 0)
818 return _error->Errno("select",_("Select failed"));
823 _error->Error(_("Connection timed out"));
824 return ServerDie(Srv);
828 if (Srv->ServerFd != -1 && FD_ISSET(Srv->ServerFd,&rfds))
831 if (Srv->In.Read(Srv->ServerFd) == false)
832 return ServerDie(Srv);
835 if (Srv->ServerFd != -1 && FD_ISSET(Srv->ServerFd,&wfds))
838 if (Srv->Out.Write(Srv->ServerFd) == false)
839 return ServerDie(Srv);
842 // Send data to the file
843 if (FileFD != -1 && FD_ISSET(FileFD,&wfds))
845 if (Srv->In.Write(FileFD) == false)
846 return _error->Errno("write",_("Error writing to output file"));
849 // Handle commands from APT
850 if (FD_ISSET(STDIN_FILENO,&rfds))
859 // HttpMethod::Flush - Dump the buffer into the file /*{{{*/
860 // ---------------------------------------------------------------------
861 /* This takes the current input buffer from the Server FD and writes it
863 bool HttpMethod::Flush(ServerState *Srv)
867 // on GNU/kFreeBSD, apt dies on /dev/null because non-blocking
869 if (File->Name() != "/dev/null")
870 SetNonBlock(File->Fd(),false);
871 if (Srv->In.WriteSpace() == false)
874 while (Srv->In.WriteSpace() == true)
876 if (Srv->In.Write(File->Fd()) == false)
877 return _error->Errno("write",_("Error writing to file"));
878 if (Srv->In.IsLimit() == true)
882 if (Srv->In.IsLimit() == true || Srv->Encoding == ServerState::Closes)
888 // HttpMethod::ServerDie - The server has closed the connection. /*{{{*/
889 // ---------------------------------------------------------------------
891 bool HttpMethod::ServerDie(ServerState *Srv)
893 unsigned int LErrno = errno;
895 // Dump the buffer to the file
896 if (Srv->State == ServerState::Data)
898 // on GNU/kFreeBSD, apt dies on /dev/null because non-blocking
900 if (File->Name() != "/dev/null")
901 SetNonBlock(File->Fd(),false);
902 while (Srv->In.WriteSpace() == true)
904 if (Srv->In.Write(File->Fd()) == false)
905 return _error->Errno("write",_("Error writing to the file"));
908 if (Srv->In.IsLimit() == true)
913 // See if this is because the server finished the data stream
914 if (Srv->In.IsLimit() == false && Srv->State != ServerState::Header &&
915 Srv->Encoding != ServerState::Closes)
919 return _error->Error(_("Error reading from server. Remote end closed connection"));
921 return _error->Errno("read",_("Error reading from server"));
927 // Nothing left in the buffer
928 if (Srv->In.WriteSpace() == false)
931 // We may have got multiple responses back in one packet..
939 // HttpMethod::DealWithHeaders - Handle the retrieved header data /*{{{*/
940 // ---------------------------------------------------------------------
941 /* We look at the header data we got back from the server and decide what
945 3 - Unrecoverable error
946 4 - Error with error content page
947 5 - Unrecoverable non-server error (close the connection) */
948 int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv)
951 if (Srv->Result == 304)
953 unlink(Queue->DestFile.c_str());
955 Res.LastModified = Queue->LastModified;
959 /* We have a reply we dont handle. This should indicate a perm server
961 if (Srv->Result < 200 || Srv->Result >= 300)
963 _error->Error("%u %s",Srv->Result,Srv->Code);
964 if (Srv->HaveContent == true)
969 // This is some sort of 2xx 'data follows' reply
970 Res.LastModified = Srv->Date;
971 Res.Size = Srv->Size;
975 File = new FileFd(Queue->DestFile,FileFd::WriteAny);
976 if (_error->PendingError() == true)
979 FailFile = Queue->DestFile;
980 FailFile.c_str(); // Make sure we dont do a malloc in the signal handler
982 FailTime = Srv->Date;
984 // Set the expected size
985 if (Srv->StartPos >= 0)
987 Res.ResumePoint = Srv->StartPos;
988 ftruncate(File->Fd(),Srv->StartPos);
991 // Set the start point
992 lseek(File->Fd(),0,SEEK_END);
995 Srv->In.Hash = new Hashes;
997 // Fill the Hash if the file is non-empty (resume)
998 if (Srv->StartPos > 0)
1000 lseek(File->Fd(),0,SEEK_SET);
1001 if (Srv->In.Hash->AddFD(File->Fd(),Srv->StartPos) == false)
1003 _error->Errno("read",_("Problem hashing file"));
1006 lseek(File->Fd(),0,SEEK_END);
1009 SetNonBlock(File->Fd(),true);
1013 // HttpMethod::SigTerm - Handle a fatal signal /*{{{*/
1014 // ---------------------------------------------------------------------
1015 /* This closes and timestamps the open file. This is neccessary to get
1016 resume behavoir on user abort */
1017 void HttpMethod::SigTerm(int)
1024 struct utimbuf UBuf;
1025 UBuf.actime = FailTime;
1026 UBuf.modtime = FailTime;
1027 utime(FailFile.c_str(),&UBuf);
1032 // HttpMethod::Fetch - Fetch an item /*{{{*/
1033 // ---------------------------------------------------------------------
1034 /* This adds an item to the pipeline. We keep the pipeline at a fixed
1036 bool HttpMethod::Fetch(FetchItem *)
1041 // Queue the requests
1044 for (FetchItem *I = Queue; I != 0 && Depth < (signed)PipelineDepth;
1045 I = I->Next, Depth++)
1047 // If pipelining is disabled, we only queue 1 request
1048 if (Server->Pipeline == false && Depth >= 0)
1051 // Make sure we stick with the same server
1052 if (Server->Comp(I->Uri) == false)
1058 QueueBack = I->Next;
1059 SendReq(I,Server->Out);
1067 // HttpMethod::Configuration - Handle a configuration message /*{{{*/
1068 // ---------------------------------------------------------------------
1069 /* We stash the desired pipeline depth */
1070 bool HttpMethod::Configuration(string Message)
1072 if (pkgAcqMethod::Configuration(Message) == false)
1075 TimeOut = _config->FindI("Acquire::http::Timeout",TimeOut);
1076 PipelineDepth = _config->FindI("Acquire::http::Pipeline-Depth",
1078 Debug = _config->FindB("Debug::Acquire::http",false);
1083 // HttpMethod::Loop - Main loop /*{{{*/
1084 // ---------------------------------------------------------------------
1086 int HttpMethod::Loop()
1088 signal(SIGTERM,SigTerm);
1089 signal(SIGINT,SigTerm);
1093 int FailCounter = 0;
1096 // We have no commands, wait for some to arrive
1099 if (WaitFd(STDIN_FILENO) == false)
1103 /* Run messages, we can accept 0 (no message) if we didn't
1104 do a WaitFd above.. Otherwise the FD is closed. */
1105 int Result = Run(true);
1106 if (Result != -1 && (Result != 0 || Queue == 0))
1112 CFStringEncoding se = kCFStringEncodingUTF8;
1114 char *url = strdup(Queue->Uri.c_str());
1116 URI uri = Queue->Uri;
1117 std::string hs = uri.Host;
1119 struct hostent *he = gethostbyname(hs.c_str());
1120 if (he == NULL || he->h_addr_list[0] == NULL) {
1121 _error->Error(hstrerror(h_errno));
1126 uri.Host = inet_ntoa(* (struct in_addr *) he->h_addr_list[0]);
1128 std::string urs = uri;
1130 CFStringRef sr = CFStringCreateWithCString(kCFAllocatorDefault, urs.c_str(), se);
1131 CFURLRef ur = CFURLCreateWithString(kCFAllocatorDefault, sr, NULL);
1133 CFHTTPMessageRef hm = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), ur, kCFHTTPVersion1_1);
1137 if (stat(Queue->DestFile.c_str(), &SBuf) >= 0 && SBuf.st_size > 0) {
1138 sr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("bytes=%li-"), (long) SBuf.st_size - 1);
1139 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Range"), sr);
1142 sr = CFStringCreateWithCString(kCFAllocatorDefault, TimeRFC1123(SBuf.st_mtime).c_str(), se);
1143 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("If-Range"), sr);
1145 } else if (Queue->LastModified != 0) {
1146 sr = CFStringCreateWithCString(kCFAllocatorDefault, TimeRFC1123(SBuf.st_mtime).c_str(), se);
1147 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("If-Modified-Since"), sr);
1151 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("User-Agent"), CFSTR("Telesphoreo APT-HTTP/1.0.98"));
1153 sr = CFStringCreateWithCString(kCFAllocatorDefault, hs.c_str(), se);
1154 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Host"), sr);
1157 CFReadStreamRef rs = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, hm);
1160 CFDictionaryRef dr = SCDynamicStoreCopyProxies(NULL);
1161 CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPProxy, dr);
1164 //CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue);
1165 CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
1171 uint8_t data[10240];
1174 Status("Connecting to %s", hs.c_str());
1176 if (!CFReadStreamOpen(rs)) {
1177 CfrsError("Open", rs);
1182 rd = CFReadStreamRead(rs, data, sizeof(data));
1185 CfrsError(uri.Host.c_str(), rs);
1190 Res.Filename = Queue->DestFile;
1192 hm = (CFHTTPMessageRef) CFReadStreamCopyProperty(rs, kCFStreamPropertyHTTPResponseHeader);
1193 sc = CFHTTPMessageGetResponseStatusCode(hm);
1195 if (sc == 301 || sc == 302) {
1196 sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Location"));
1201 size_t ln = CFStringGetLength(sr) + 1;
1203 url = static_cast<char *>(malloc(ln));
1205 if (!CFStringGetCString(sr, url, ln, se)) {
1215 sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Content-Range"));
1217 size_t ln = CFStringGetLength(sr) + 1;
1220 if (!CFStringGetCString(sr, cr, ln, se)) {
1227 if (sscanf(cr, "bytes %lu-%*u/%lu", &offset, &Res.Size) != 2) {
1228 _error->Error(_("The HTTP server sent an invalid Content-Range header"));
1233 if (offset > Res.Size) {
1234 _error->Error(_("This HTTP server has broken range support"));
1239 sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Content-Length"));
1241 Res.Size = CFStringGetIntValue(sr);
1246 time(&Res.LastModified);
1248 sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Last-Modified"));
1250 size_t ln = CFStringGetLength(sr) + 1;
1253 if (!CFStringGetCString(sr, cr, ln, se)) {
1260 if (!StrToTime(cr, Res.LastModified)) {
1261 _error->Error(_("Unknown date format"));
1270 unlink(Queue->DestFile.c_str());
1272 Res.LastModified = Queue->LastModified;
1274 } else if (sc < 200 || sc >= 300)
1279 File = new FileFd(Queue->DestFile, FileFd::WriteAny);
1280 if (_error->PendingError() == true) {
1287 FailFile = Queue->DestFile;
1288 FailFile.c_str(); // Make sure we dont do a malloc in the signal handler
1289 FailFd = File->Fd();
1290 FailTime = Res.LastModified;
1292 Res.ResumePoint = offset;
1293 ftruncate(File->Fd(), offset);
1296 lseek(File->Fd(), 0, SEEK_SET);
1297 if (!hash.AddFD(File->Fd(), offset)) {
1298 _error->Errno("read", _("Problem hashing file"));
1306 lseek(File->Fd(), 0, SEEK_END);
1310 read: if (rd == -1) {
1311 CfrsError("rd", rs);
1313 } else if (rd == 0) {
1315 Res.Size = File->Size();
1317 struct utimbuf UBuf;
1319 UBuf.actime = Res.LastModified;
1320 UBuf.modtime = Res.LastModified;
1321 utime(Queue->DestFile.c_str(), &UBuf);
1323 Res.TakeHashes(hash);
1330 int sz = write(File->Fd(), dt, rd);
1343 rd = CFReadStreamRead(rs, data, sizeof(data));
1352 CFReadStreamClose(rs);
1365 setlocale(LC_ALL, "");