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>
47 #include <CoreFoundation/CoreFoundation.h>
48 #include <CoreServices/CoreServices.h>
51 #include "rfc2553emu.h"
57 string HttpMethod::FailFile;
58 int HttpMethod::FailFd = -1;
59 time_t HttpMethod::FailTime = 0;
60 unsigned long PipelineDepth = 10;
61 unsigned long TimeOut = 120;
64 unsigned long CircleBuf::BwReadLimit=0;
65 unsigned long CircleBuf::BwTickReadData=0;
66 struct timeval CircleBuf::BwReadTick={0,0};
67 const unsigned int CircleBuf::BW_HZ=10;
69 // CircleBuf::CircleBuf - Circular input buffer /*{{{*/
70 // ---------------------------------------------------------------------
72 CircleBuf::CircleBuf(unsigned long Size) : Size(Size), Hash(0)
74 Buf = new unsigned char[Size];
77 CircleBuf::BwReadLimit = _config->FindI("Acquire::http::Dl-Limit",0)*1024;
80 // CircleBuf::Reset - Reset to the default state /*{{{*/
81 // ---------------------------------------------------------------------
83 void CircleBuf::Reset()
88 MaxGet = (unsigned int)-1;
97 // CircleBuf::Read - Read from a FD into the circular buffer /*{{{*/
98 // ---------------------------------------------------------------------
99 /* This fills up the buffer with as much data as is in the FD, assuming it
101 bool CircleBuf::Read(int Fd)
103 unsigned long BwReadMax;
107 // Woops, buffer is full
108 if (InP - OutP == Size)
111 // what's left to read in this tick
112 BwReadMax = CircleBuf::BwReadLimit/BW_HZ;
114 if(CircleBuf::BwReadLimit) {
116 gettimeofday(&now,0);
118 unsigned long d = (now.tv_sec-CircleBuf::BwReadTick.tv_sec)*1000000 +
119 now.tv_usec-CircleBuf::BwReadTick.tv_usec;
120 if(d > 1000000/BW_HZ) {
121 CircleBuf::BwReadTick = now;
122 CircleBuf::BwTickReadData = 0;
125 if(CircleBuf::BwTickReadData >= BwReadMax) {
126 usleep(1000000/BW_HZ);
131 // Write the buffer segment
133 if(CircleBuf::BwReadLimit) {
134 Res = read(Fd,Buf + (InP%Size),
135 BwReadMax > LeftRead() ? LeftRead() : BwReadMax);
137 Res = read(Fd,Buf + (InP%Size),LeftRead());
139 if(Res > 0 && BwReadLimit > 0)
140 CircleBuf::BwTickReadData += Res;
152 gettimeofday(&Start,0);
157 // CircleBuf::Read - Put the string into the buffer /*{{{*/
158 // ---------------------------------------------------------------------
159 /* This will hold the string in and fill the buffer with it as it empties */
160 bool CircleBuf::Read(string Data)
167 // CircleBuf::FillOut - Fill the buffer from the output queue /*{{{*/
168 // ---------------------------------------------------------------------
170 void CircleBuf::FillOut()
172 if (OutQueue.empty() == true)
176 // Woops, buffer is full
177 if (InP - OutP == Size)
180 // Write the buffer segment
181 unsigned long Sz = LeftRead();
182 if (OutQueue.length() - StrPos < Sz)
183 Sz = OutQueue.length() - StrPos;
184 memcpy(Buf + (InP%Size),OutQueue.c_str() + StrPos,Sz);
189 if (OutQueue.length() == StrPos)
198 // CircleBuf::Write - Write from the buffer into a FD /*{{{*/
199 // ---------------------------------------------------------------------
200 /* This empties the buffer into the FD. */
201 bool CircleBuf::Write(int Fd)
207 // Woops, buffer is empty
214 // Write the buffer segment
216 Res = write(Fd,Buf + (OutP%Size),LeftWrite());
229 Hash->Add(Buf + (OutP%Size),Res);
235 // CircleBuf::WriteTillEl - Write from the buffer to a string /*{{{*/
236 // ---------------------------------------------------------------------
237 /* This copies till the first empty line */
238 bool CircleBuf::WriteTillEl(string &Data,bool Single)
240 // We cheat and assume it is unneeded to have more than one buffer load
241 for (unsigned long I = OutP; I < InP; I++)
243 if (Buf[I%Size] != '\n')
249 if (I < InP && Buf[I%Size] == '\r')
251 if (I >= InP || Buf[I%Size] != '\n')
259 unsigned long Sz = LeftWrite();
264 Data += string((char *)(Buf + (OutP%Size)),Sz);
272 // CircleBuf::Stats - Print out stats information /*{{{*/
273 // ---------------------------------------------------------------------
275 void CircleBuf::Stats()
281 gettimeofday(&Stop,0);
282 /* float Diff = Stop.tv_sec - Start.tv_sec +
283 (float)(Stop.tv_usec - Start.tv_usec)/1000000;
284 clog << "Got " << InP << " in " << Diff << " at " << InP/Diff << endl;*/
288 // ServerState::ServerState - Constructor /*{{{*/
289 // ---------------------------------------------------------------------
291 ServerState::ServerState(URI Srv,HttpMethod *Owner) : Owner(Owner),
292 In(64*1024), Out(4*1024),
298 // ServerState::Open - Open a connection to the server /*{{{*/
299 // ---------------------------------------------------------------------
300 /* This opens a connection to the server. */
301 bool ServerState::Open()
303 // Use the already open connection if possible.
312 // Determine the proxy setting
313 if (getenv("http_proxy") == 0)
315 string DefProxy = _config->Find("Acquire::http::Proxy");
316 string SpecificProxy = _config->Find("Acquire::http::Proxy::" + ServerName.Host);
317 if (SpecificProxy.empty() == false)
319 if (SpecificProxy == "DIRECT")
322 Proxy = SpecificProxy;
328 Proxy = getenv("http_proxy");
330 // Parse no_proxy, a , separated list of domains
331 if (getenv("no_proxy") != 0)
333 if (CheckDomainList(ServerName.Host,getenv("no_proxy")) == true)
337 // Determine what host and port to use based on the proxy settings
340 if (Proxy.empty() == true || Proxy.Host.empty() == true)
342 if (ServerName.Port != 0)
343 Port = ServerName.Port;
344 Host = ServerName.Host;
353 // Connect to the remote server
354 if (Connect(Host,Port,"http",80,ServerFd,TimeOut,Owner) == false)
360 // ServerState::Close - Close a connection to the server /*{{{*/
361 // ---------------------------------------------------------------------
363 bool ServerState::Close()
370 // ServerState::RunHeaders - Get the headers before the data /*{{{*/
371 // ---------------------------------------------------------------------
372 /* Returns 0 if things are OK, 1 if an IO error occursed and 2 if a header
373 parse error occured */
374 int ServerState::RunHeaders()
378 Owner->Status(_("Waiting for headers"));
392 if (In.WriteTillEl(Data) == false)
398 for (string::const_iterator I = Data.begin(); I < Data.end(); I++)
400 string::const_iterator J = I;
401 for (; J != Data.end() && *J != '\n' && *J != '\r';J++);
402 if (HeaderLine(string(I,J)) == false)
407 // 100 Continue is a Nop...
411 // Tidy up the connection persistance state.
412 if (Encoding == Closes && HaveContent == true)
417 while (Owner->Go(false,this) == true);
422 // ServerState::RunData - Transfer the data from the socket /*{{{*/
423 // ---------------------------------------------------------------------
425 bool ServerState::RunData()
429 // Chunked transfer encoding is fun..
430 if (Encoding == Chunked)
434 // Grab the block size
440 if (In.WriteTillEl(Data,true) == true)
443 while ((Last = Owner->Go(false,this)) == true);
448 // See if we are done
449 unsigned long Len = strtol(Data.c_str(),0,16);
454 // We have to remove the entity trailer
458 if (In.WriteTillEl(Data,true) == true && Data.length() <= 2)
461 while ((Last = Owner->Go(false,this)) == true);
464 return !_error->PendingError();
467 // Transfer the block
469 while (Owner->Go(true,this) == true)
470 if (In.IsLimit() == true)
474 if (In.IsLimit() == false)
477 // The server sends an extra new line before the next block specifier..
482 if (In.WriteTillEl(Data,true) == true)
485 while ((Last = Owner->Go(false,this)) == true);
492 /* Closes encoding is used when the server did not specify a size, the
493 loss of the connection means we are done */
494 if (Encoding == Closes)
497 In.Limit(Size - StartPos);
499 // Just transfer the whole block.
502 if (In.IsLimit() == false)
506 return !_error->PendingError();
508 while (Owner->Go(true,this) == true);
511 return Owner->Flush(this) && !_error->PendingError();
514 // ServerState::HeaderLine - Process a header line /*{{{*/
515 // ---------------------------------------------------------------------
517 bool ServerState::HeaderLine(string Line)
519 if (Line.empty() == true)
522 // The http server might be trying to do something evil.
523 if (Line.length() >= MAXLEN)
524 return _error->Error(_("Got a single header line over %u chars"),MAXLEN);
526 string::size_type Pos = Line.find(' ');
527 if (Pos == string::npos || Pos+1 > Line.length())
529 // Blah, some servers use "connection:closes", evil.
530 Pos = Line.find(':');
531 if (Pos == string::npos || Pos + 2 > Line.length())
532 return _error->Error(_("Bad header line"));
536 // Parse off any trailing spaces between the : and the next word.
537 string::size_type Pos2 = Pos;
538 while (Pos2 < Line.length() && isspace(Line[Pos2]) != 0)
541 string Tag = string(Line,0,Pos);
542 string Val = string(Line,Pos2);
544 if (stringcasecmp(Tag.c_str(),Tag.c_str()+4,"HTTP") == 0)
546 // Evil servers return no version
549 if (sscanf(Line.c_str(),"HTTP/%u.%u %u %[^\n]",&Major,&Minor,
551 return _error->Error(_("The HTTP server sent an invalid reply header"));
557 if (sscanf(Line.c_str(),"HTTP %u %[^\n]",&Result,Code) != 2)
558 return _error->Error(_("The HTTP server sent an invalid reply header"));
561 /* Check the HTTP response header to get the default persistance
567 if (Major == 1 && Minor <= 0)
576 if (stringcasecmp(Tag,"Content-Length:") == 0)
578 if (Encoding == Closes)
582 // The length is already set from the Content-Range header
586 if (sscanf(Val.c_str(),"%lu",&Size) != 1)
587 return _error->Error(_("The HTTP server sent an invalid Content-Length header"));
591 if (stringcasecmp(Tag,"Content-Type:") == 0)
597 if (stringcasecmp(Tag,"Content-Range:") == 0)
601 if (sscanf(Val.c_str(),"bytes %lu-%*u/%lu",&StartPos,&Size) != 2)
602 return _error->Error(_("The HTTP server sent an invalid Content-Range header"));
603 if ((unsigned)StartPos > Size)
604 return _error->Error(_("This HTTP server has broken range support"));
608 if (stringcasecmp(Tag,"Transfer-Encoding:") == 0)
611 if (stringcasecmp(Val,"chunked") == 0)
616 if (stringcasecmp(Tag,"Connection:") == 0)
618 if (stringcasecmp(Val,"close") == 0)
620 if (stringcasecmp(Val,"keep-alive") == 0)
625 if (stringcasecmp(Tag,"Last-Modified:") == 0)
627 if (StrToTime(Val,Date) == false)
628 return _error->Error(_("Unknown date format"));
636 // HttpMethod::SendReq - Send the HTTP request /*{{{*/
637 // ---------------------------------------------------------------------
638 /* This places the http request in the outbound buffer */
639 void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out)
643 // The HTTP server expects a hostname with a trailing :port
645 string ProperHost = Uri.Host;
648 sprintf(Buf,":%u",Uri.Port);
653 if (Itm->Uri.length() >= sizeof(Buf))
656 /* Build the request. We include a keep-alive header only for non-proxy
657 requests. This is to tweak old http/1.0 servers that do support keep-alive
658 but not HTTP/1.1 automatic keep-alive. Doing this with a proxy server
659 will glitch HTTP/1.0 proxies because they do not filter it out and
660 pass it on, HTTP/1.1 says the connection should default to keep alive
661 and we expect the proxy to do this */
662 if (Proxy.empty() == true || Proxy.Host.empty())
663 sprintf(Buf,"GET %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n",
664 QuoteString(Uri.Path,"~").c_str(),ProperHost.c_str());
667 /* Generate a cache control header if necessary. We place a max
668 cache age on index files, optionally set a no-cache directive
669 and a no-store directive for archives. */
670 sprintf(Buf,"GET %s HTTP/1.1\r\nHost: %s\r\n",
671 Itm->Uri.c_str(),ProperHost.c_str());
672 // only generate a cache control header if we actually want to
674 if (_config->FindB("Acquire::http::No-Cache",false) == false)
676 if (Itm->IndexFile == true)
677 sprintf(Buf+strlen(Buf),"Cache-Control: max-age=%u\r\n",
678 _config->FindI("Acquire::http::Max-Age",0));
681 if (_config->FindB("Acquire::http::No-Store",false) == true)
682 strcat(Buf,"Cache-Control: no-store\r\n");
686 // generate a no-cache header if needed
687 if (_config->FindB("Acquire::http::No-Cache",false) == true)
688 strcat(Buf,"Cache-Control: no-cache\r\nPragma: no-cache\r\n");
693 // Check for a partial file
695 if (stat(Itm->DestFile.c_str(),&SBuf) >= 0 && SBuf.st_size > 0)
697 // In this case we send an if-range query with a range header
698 sprintf(Buf,"Range: bytes=%li-\r\nIf-Range: %s\r\n",(long)SBuf.st_size - 1,
699 TimeRFC1123(SBuf.st_mtime).c_str());
704 if (Itm->LastModified != 0)
706 sprintf(Buf,"If-Modified-Since: %s\r\n",TimeRFC1123(Itm->LastModified).c_str());
711 if (Proxy.User.empty() == false || Proxy.Password.empty() == false)
712 Req += string("Proxy-Authorization: Basic ") +
713 Base64Encode(Proxy.User + ":" + Proxy.Password) + "\r\n";
715 if (Uri.User.empty() == false || Uri.Password.empty() == false)
716 Req += string("Authorization: Basic ") +
717 Base64Encode(Uri.User + ":" + Uri.Password) + "\r\n";
719 Req += "User-Agent: Debian APT-HTTP/1.3\r\n\r\n";
727 // HttpMethod::Go - Run a single loop /*{{{*/
728 // ---------------------------------------------------------------------
729 /* This runs the select loop over the server FDs, Output file FDs and
731 bool HttpMethod::Go(bool ToFile,ServerState *Srv)
733 // Server has closed the connection
734 if (Srv->ServerFd == -1 && (Srv->In.WriteSpace() == false ||
742 /* Add the server. We only send more requests if the connection will
744 if (Srv->Out.WriteSpace() == true && Srv->ServerFd != -1
745 && Srv->Persistent == true)
746 FD_SET(Srv->ServerFd,&wfds);
747 if (Srv->In.ReadSpace() == true && Srv->ServerFd != -1)
748 FD_SET(Srv->ServerFd,&rfds);
755 if (Srv->In.WriteSpace() == true && ToFile == true && FileFD != -1)
756 FD_SET(FileFD,&wfds);
759 FD_SET(STDIN_FILENO,&rfds);
761 // Figure out the max fd
763 if (MaxFd < Srv->ServerFd)
764 MaxFd = Srv->ServerFd;
771 if ((Res = select(MaxFd+1,&rfds,&wfds,0,&tv)) < 0)
775 return _error->Errno("select",_("Select failed"));
780 _error->Error(_("Connection timed out"));
781 return ServerDie(Srv);
785 if (Srv->ServerFd != -1 && FD_ISSET(Srv->ServerFd,&rfds))
788 if (Srv->In.Read(Srv->ServerFd) == false)
789 return ServerDie(Srv);
792 if (Srv->ServerFd != -1 && FD_ISSET(Srv->ServerFd,&wfds))
795 if (Srv->Out.Write(Srv->ServerFd) == false)
796 return ServerDie(Srv);
799 // Send data to the file
800 if (FileFD != -1 && FD_ISSET(FileFD,&wfds))
802 if (Srv->In.Write(FileFD) == false)
803 return _error->Errno("write",_("Error writing to output file"));
806 // Handle commands from APT
807 if (FD_ISSET(STDIN_FILENO,&rfds))
816 // HttpMethod::Flush - Dump the buffer into the file /*{{{*/
817 // ---------------------------------------------------------------------
818 /* This takes the current input buffer from the Server FD and writes it
820 bool HttpMethod::Flush(ServerState *Srv)
824 // on GNU/kFreeBSD, apt dies on /dev/null because non-blocking
826 if (File->Name() != "/dev/null")
827 SetNonBlock(File->Fd(),false);
828 if (Srv->In.WriteSpace() == false)
831 while (Srv->In.WriteSpace() == true)
833 if (Srv->In.Write(File->Fd()) == false)
834 return _error->Errno("write",_("Error writing to file"));
835 if (Srv->In.IsLimit() == true)
839 if (Srv->In.IsLimit() == true || Srv->Encoding == ServerState::Closes)
845 // HttpMethod::ServerDie - The server has closed the connection. /*{{{*/
846 // ---------------------------------------------------------------------
848 bool HttpMethod::ServerDie(ServerState *Srv)
850 unsigned int LErrno = errno;
852 // Dump the buffer to the file
853 if (Srv->State == ServerState::Data)
855 // on GNU/kFreeBSD, apt dies on /dev/null because non-blocking
857 if (File->Name() != "/dev/null")
858 SetNonBlock(File->Fd(),false);
859 while (Srv->In.WriteSpace() == true)
861 if (Srv->In.Write(File->Fd()) == false)
862 return _error->Errno("write",_("Error writing to the file"));
865 if (Srv->In.IsLimit() == true)
870 // See if this is because the server finished the data stream
871 if (Srv->In.IsLimit() == false && Srv->State != ServerState::Header &&
872 Srv->Encoding != ServerState::Closes)
876 return _error->Error(_("Error reading from server. Remote end closed connection"));
878 return _error->Errno("read",_("Error reading from server"));
884 // Nothing left in the buffer
885 if (Srv->In.WriteSpace() == false)
888 // We may have got multiple responses back in one packet..
896 // HttpMethod::DealWithHeaders - Handle the retrieved header data /*{{{*/
897 // ---------------------------------------------------------------------
898 /* We look at the header data we got back from the server and decide what
902 3 - Unrecoverable error
903 4 - Error with error content page
904 5 - Unrecoverable non-server error (close the connection) */
905 int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv)
908 if (Srv->Result == 304)
910 unlink(Queue->DestFile.c_str());
912 Res.LastModified = Queue->LastModified;
916 /* We have a reply we dont handle. This should indicate a perm server
918 if (Srv->Result < 200 || Srv->Result >= 300)
920 _error->Error("%u %s",Srv->Result,Srv->Code);
921 if (Srv->HaveContent == true)
926 // This is some sort of 2xx 'data follows' reply
927 Res.LastModified = Srv->Date;
928 Res.Size = Srv->Size;
932 File = new FileFd(Queue->DestFile,FileFd::WriteAny);
933 if (_error->PendingError() == true)
936 FailFile = Queue->DestFile;
937 FailFile.c_str(); // Make sure we dont do a malloc in the signal handler
939 FailTime = Srv->Date;
941 // Set the expected size
942 if (Srv->StartPos >= 0)
944 Res.ResumePoint = Srv->StartPos;
945 ftruncate(File->Fd(),Srv->StartPos);
948 // Set the start point
949 lseek(File->Fd(),0,SEEK_END);
952 Srv->In.Hash = new Hashes;
954 // Fill the Hash if the file is non-empty (resume)
955 if (Srv->StartPos > 0)
957 lseek(File->Fd(),0,SEEK_SET);
958 if (Srv->In.Hash->AddFD(File->Fd(),Srv->StartPos) == false)
960 _error->Errno("read",_("Problem hashing file"));
963 lseek(File->Fd(),0,SEEK_END);
966 SetNonBlock(File->Fd(),true);
970 // HttpMethod::SigTerm - Handle a fatal signal /*{{{*/
971 // ---------------------------------------------------------------------
972 /* This closes and timestamps the open file. This is neccessary to get
973 resume behavoir on user abort */
974 void HttpMethod::SigTerm(int)
982 UBuf.actime = FailTime;
983 UBuf.modtime = FailTime;
984 utime(FailFile.c_str(),&UBuf);
989 // HttpMethod::Fetch - Fetch an item /*{{{*/
990 // ---------------------------------------------------------------------
991 /* This adds an item to the pipeline. We keep the pipeline at a fixed
993 bool HttpMethod::Fetch(FetchItem *)
998 // Queue the requests
1001 for (FetchItem *I = Queue; I != 0 && Depth < (signed)PipelineDepth;
1002 I = I->Next, Depth++)
1004 // If pipelining is disabled, we only queue 1 request
1005 if (Server->Pipeline == false && Depth >= 0)
1008 // Make sure we stick with the same server
1009 if (Server->Comp(I->Uri) == false)
1015 QueueBack = I->Next;
1016 SendReq(I,Server->Out);
1024 // HttpMethod::Configuration - Handle a configuration message /*{{{*/
1025 // ---------------------------------------------------------------------
1026 /* We stash the desired pipeline depth */
1027 bool HttpMethod::Configuration(string Message)
1029 if (pkgAcqMethod::Configuration(Message) == false)
1032 TimeOut = _config->FindI("Acquire::http::Timeout",TimeOut);
1033 PipelineDepth = _config->FindI("Acquire::http::Pipeline-Depth",
1035 Debug = _config->FindB("Debug::Acquire::http",false);
1040 // HttpMethod::Loop - Main loop /*{{{*/
1041 // ---------------------------------------------------------------------
1043 int HttpMethod::Loop()
1045 signal(SIGTERM,SigTerm);
1046 signal(SIGINT,SigTerm);
1050 int FailCounter = 0;
1053 // We have no commands, wait for some to arrive
1056 if (WaitFd(STDIN_FILENO) == false)
1060 /* Run messages, we can accept 0 (no message) if we didn't
1061 do a WaitFd above.. Otherwise the FD is closed. */
1062 int Result = Run(true);
1063 if (Result != -1 && (Result != 0 || Queue == 0))
1069 CFStringEncoding se = kCFStringEncodingUTF8;
1071 CFStringRef sr = CFStringCreateWithCString(kCFAllocatorDefault, Queue->Uri.c_str(), se);
1072 CFURLRef ur = CFURLCreateWithString(kCFAllocatorDefault, sr, NULL);
1074 CFHTTPMessageRef hm = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), ur, kCFHTTPVersion1_1);
1078 if (stat(Queue->DestFile.c_str(), &SBuf) >= 0 && SBuf.st_size > 0) {
1079 sr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("bytes=%li-"), (long) SBuf.st_size - 1);
1080 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Range"), sr);
1083 sr = CFStringCreateWithCString(kCFAllocatorDefault, TimeRFC1123(SBuf.st_mtime).c_str(), se);
1084 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("If-Range"), sr);
1086 } else if (Queue->LastModified != 0) {
1087 sr = CFStringCreateWithCString(kCFAllocatorDefault, TimeRFC1123(SBuf.st_mtime).c_str(), se);
1088 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("If-Modified-Since"), sr);
1092 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("User-Agent"), CFSTR("Telesphoreo APT-HTTP/1.0.98"));
1093 CFReadStreamRef rs = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, hm);
1096 CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue);
1097 CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
1099 URI uri = Queue->Uri;
1103 uint8_t data[10240];
1106 Status("Connecting to %s", uri.Host.c_str());
1108 if (!CFReadStreamOpen(rs)) {
1109 _error->Error("Unable to open stream");
1114 CFIndex rd = CFReadStreamRead(rs, data, sizeof(data));
1117 _error->Error("Stream read failure");
1122 Res.Filename = Queue->DestFile;
1124 hm = (CFHTTPMessageRef) CFReadStreamCopyProperty(rs, kCFStreamPropertyHTTPResponseHeader);
1125 UInt32 sc = CFHTTPMessageGetResponseStatusCode(hm);
1127 sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Content-Range"));
1129 size_t ln = CFStringGetLength(sr) + 1;
1132 if (!CFStringGetCString(sr, cr, ln, se)) {
1139 if (sscanf(cr, "bytes %lu-%*u/%lu", &offset, &Res.Size) != 2) {
1140 _error->Error(_("The HTTP server sent an invalid Content-Range header"));
1145 if (offset > Res.Size) {
1146 _error->Error(_("This HTTP server has broken range support"));
1151 sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Content-Length"));
1153 Res.Size = CFStringGetIntValue(sr);
1158 time(&Res.LastModified);
1160 sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Last-Modified"));
1162 size_t ln = CFStringGetLength(sr) + 1;
1165 if (!CFStringGetCString(sr, cr, ln, se)) {
1172 if (!StrToTime(cr, Res.LastModified)) {
1173 _error->Error(_("Unknown date format"));
1182 unlink(Queue->DestFile.c_str());
1184 Res.LastModified = Queue->LastModified;
1186 } else if (sc < 200 || sc >= 300)
1191 File = new FileFd(Queue->DestFile, FileFd::WriteAny);
1192 if (_error->PendingError() == true) {
1199 FailFile = Queue->DestFile;
1200 FailFile.c_str(); // Make sure we dont do a malloc in the signal handler
1201 FailFd = File->Fd();
1202 FailTime = Res.LastModified;
1204 Res.ResumePoint = offset;
1205 ftruncate(File->Fd(), offset);
1208 lseek(File->Fd(), 0, SEEK_SET);
1209 if (!hash.AddFD(File->Fd(), offset)) {
1210 _error->Errno("read", _("Problem hashing file"));
1218 lseek(File->Fd(), 0, SEEK_END);
1222 read: if (rd == -1) {
1223 _error->Error("Stream read failure");
1225 } else if (rd == 0) {
1227 Res.Size = File->Size();
1229 struct utimbuf UBuf;
1231 UBuf.actime = Res.LastModified;
1232 UBuf.modtime = Res.LastModified;
1233 utime(Queue->DestFile.c_str(), &UBuf);
1235 Res.TakeHashes(hash);
1242 int sz = write(File->Fd(), dt, rd);
1255 rd = CFReadStreamRead(rs, data, sizeof(data));
1272 setlocale(LC_ALL, "");