]> git.saurik.com Git - apt-legacy.git/blame - methods/http.cc
Fixed some dependencies, removed mDNS fix for 2.0, and factored for multi-arch support.
[apt-legacy.git] / methods / http.cc
CommitLineData
854e5ff1
JF
1extern "C" {
2 #include <mach-o/nlist.h>
3}
4
da6ee469
JF
5// -*- mode: cpp; mode: fold -*-
6// Description /*{{{*/
7// $Id: http.cc,v 1.59 2004/05/08 19:42:35 mdz Exp $
8/* ######################################################################
9
10 HTTP Aquire Method - This is the HTTP aquire method for APT.
11
12 It uses HTTP/1.1 and many of the fancy options there-in, such as
13 pipelining, range, if-range and so on.
14
15 It is based on a doubly buffered select loop. A groupe of requests are
16 fed into a single output buffer that is constantly fed out the
17 socket. This provides ideal pipelining as in many cases all of the
18 requests will fit into a single packet. The input socket is buffered
19 the same way and fed into the fd for the file (may be a pipe in future).
20
21 This double buffering provides fairly substantial transfer rates,
22 compared to wget the http method is about 4% faster. Most importantly,
23 when HTTP is compared with FTP as a protocol the speed difference is
24 huge. In tests over the internet from two sites to llug (via ATM) this
25 program got 230k/s sustained http transfer rates. FTP on the other
26 hand topped out at 170k/s. That combined with the time to setup the
27 FTP connection makes HTTP a vastly superior protocol.
28
29 ##################################################################### */
30 /*}}}*/
31// Include Files /*{{{*/
32#include <apt-pkg/fileutl.h>
33#include <apt-pkg/acquire-method.h>
34#include <apt-pkg/error.h>
35#include <apt-pkg/hashes.h>
36
37#include <sys/stat.h>
38#include <sys/time.h>
39#include <utime.h>
40#include <unistd.h>
41#include <signal.h>
42#include <stdio.h>
43#include <errno.h>
44#include <string.h>
45#include <iostream>
46#include <apti18n.h>
47
48// Internet stuff
49#include <netdb.h>
fe23a696 50#include <arpa/inet.h>
da6ee469 51
253ba34a
JF
52#include <CoreFoundation/CoreFoundation.h>
53#include <CoreServices/CoreServices.h>
985ed269 54#include <SystemConfiguration/SystemConfiguration.h>
253ba34a 55
da6ee469
JF
56#include "connect.h"
57#include "rfc2553emu.h"
58#include "http.h"
59
60 /*}}}*/
61using namespace std;
62
fe23a696 63void CfrsError(const char *name, CFReadStreamRef rs) {
985ed269
JF
64 CFStreamError se = CFReadStreamGetError(rs);
65
66 if (se.domain == kCFStreamErrorDomainCustom) {
67 } else if (se.domain == kCFStreamErrorDomainPOSIX) {
68 _error->Error("POSIX: %s", strerror(se.error));
69 } else if (se.domain == kCFStreamErrorDomainMacOSStatus) {
70 _error->Error("MacOSStatus: %ld", se.error);
71 } else if (se.domain == kCFStreamErrorDomainNetDB) {
fe23a696 72 _error->Error("NetDB: %s %s", name, gai_strerror(se.error));
985ed269
JF
73 } else if (se.domain == kCFStreamErrorDomainMach) {
74 _error->Error("Mach: %ld", se.error);
75 } else if (se.domain == kCFStreamErrorDomainHTTP) {
76 switch (se.error) {
77 case kCFStreamErrorHTTPParseFailure:
78 _error->Error("Parse failure");
79 break;
80
81 case kCFStreamErrorHTTPRedirectionLoop:
82 _error->Error("Redirection loop");
83 break;
84
85 case kCFStreamErrorHTTPBadURL:
86 _error->Error("Bad URL");
87 break;
88
89 default:
90 _error->Error("Unknown HTTP error: %ld", se.error);
91 break;
92 }
93 } else if (se.domain == kCFStreamErrorDomainSOCKS) {
94 _error->Error("SOCKS: %ld", se.error);
95 } else if (se.domain == kCFStreamErrorDomainSystemConfiguration) {
96 _error->Error("SystemConfiguration: %ld", se.error);
97 } else if (se.domain == kCFStreamErrorDomainSSL) {
98 _error->Error("SSL: %ld", se.error);
99 } else {
b7a89731 100 _error->Error("Domain #%ld: %ld", se.domain, se.error);
985ed269
JF
101 }
102}
103
da6ee469
JF
104string HttpMethod::FailFile;
105int HttpMethod::FailFd = -1;
106time_t HttpMethod::FailTime = 0;
107unsigned long PipelineDepth = 10;
108unsigned long TimeOut = 120;
109bool Debug = false;
110
111unsigned long CircleBuf::BwReadLimit=0;
112unsigned long CircleBuf::BwTickReadData=0;
113struct timeval CircleBuf::BwReadTick={0,0};
114const unsigned int CircleBuf::BW_HZ=10;
115
116// CircleBuf::CircleBuf - Circular input buffer /*{{{*/
117// ---------------------------------------------------------------------
118/* */
119CircleBuf::CircleBuf(unsigned long Size) : Size(Size), Hash(0)
120{
121 Buf = new unsigned char[Size];
122 Reset();
123
124 CircleBuf::BwReadLimit = _config->FindI("Acquire::http::Dl-Limit",0)*1024;
125}
126 /*}}}*/
127// CircleBuf::Reset - Reset to the default state /*{{{*/
128// ---------------------------------------------------------------------
129/* */
130void CircleBuf::Reset()
131{
132 InP = 0;
133 OutP = 0;
134 StrPos = 0;
135 MaxGet = (unsigned int)-1;
136 OutQueue = string();
137 if (Hash != 0)
138 {
139 delete Hash;
140 Hash = new Hashes;
141 }
142};
143 /*}}}*/
144// CircleBuf::Read - Read from a FD into the circular buffer /*{{{*/
145// ---------------------------------------------------------------------
146/* This fills up the buffer with as much data as is in the FD, assuming it
147 is non-blocking.. */
148bool CircleBuf::Read(int Fd)
149{
150 unsigned long BwReadMax;
151
152 while (1)
153 {
154 // Woops, buffer is full
155 if (InP - OutP == Size)
156 return true;
157
158 // what's left to read in this tick
159 BwReadMax = CircleBuf::BwReadLimit/BW_HZ;
160
161 if(CircleBuf::BwReadLimit) {
162 struct timeval now;
163 gettimeofday(&now,0);
164
165 unsigned long d = (now.tv_sec-CircleBuf::BwReadTick.tv_sec)*1000000 +
166 now.tv_usec-CircleBuf::BwReadTick.tv_usec;
167 if(d > 1000000/BW_HZ) {
168 CircleBuf::BwReadTick = now;
169 CircleBuf::BwTickReadData = 0;
170 }
171
172 if(CircleBuf::BwTickReadData >= BwReadMax) {
173 usleep(1000000/BW_HZ);
174 return true;
175 }
176 }
177
178 // Write the buffer segment
179 int Res;
180 if(CircleBuf::BwReadLimit) {
181 Res = read(Fd,Buf + (InP%Size),
182 BwReadMax > LeftRead() ? LeftRead() : BwReadMax);
183 } else
184 Res = read(Fd,Buf + (InP%Size),LeftRead());
185
186 if(Res > 0 && BwReadLimit > 0)
187 CircleBuf::BwTickReadData += Res;
188
189 if (Res == 0)
190 return false;
191 if (Res < 0)
192 {
193 if (errno == EAGAIN)
194 return true;
195 return false;
196 }
197
198 if (InP == 0)
199 gettimeofday(&Start,0);
200 InP += Res;
201 }
202}
203 /*}}}*/
204// CircleBuf::Read - Put the string into the buffer /*{{{*/
205// ---------------------------------------------------------------------
206/* This will hold the string in and fill the buffer with it as it empties */
207bool CircleBuf::Read(string Data)
208{
209 OutQueue += Data;
210 FillOut();
211 return true;
212}
213 /*}}}*/
214// CircleBuf::FillOut - Fill the buffer from the output queue /*{{{*/
215// ---------------------------------------------------------------------
216/* */
217void CircleBuf::FillOut()
218{
219 if (OutQueue.empty() == true)
220 return;
221 while (1)
222 {
223 // Woops, buffer is full
224 if (InP - OutP == Size)
225 return;
226
227 // Write the buffer segment
228 unsigned long Sz = LeftRead();
229 if (OutQueue.length() - StrPos < Sz)
230 Sz = OutQueue.length() - StrPos;
231 memcpy(Buf + (InP%Size),OutQueue.c_str() + StrPos,Sz);
232
233 // Advance
234 StrPos += Sz;
235 InP += Sz;
236 if (OutQueue.length() == StrPos)
237 {
238 StrPos = 0;
239 OutQueue = "";
240 return;
241 }
242 }
243}
244 /*}}}*/
245// CircleBuf::Write - Write from the buffer into a FD /*{{{*/
246// ---------------------------------------------------------------------
247/* This empties the buffer into the FD. */
248bool CircleBuf::Write(int Fd)
249{
250 while (1)
251 {
252 FillOut();
253
254 // Woops, buffer is empty
255 if (OutP == InP)
256 return true;
257
258 if (OutP == MaxGet)
259 return true;
260
261 // Write the buffer segment
262 int Res;
263 Res = write(Fd,Buf + (OutP%Size),LeftWrite());
264
265 if (Res == 0)
266 return false;
267 if (Res < 0)
268 {
269 if (errno == EAGAIN)
270 return true;
271
272 return false;
273 }
274
275 if (Hash != 0)
276 Hash->Add(Buf + (OutP%Size),Res);
277
278 OutP += Res;
279 }
280}
281 /*}}}*/
282// CircleBuf::WriteTillEl - Write from the buffer to a string /*{{{*/
283// ---------------------------------------------------------------------
284/* This copies till the first empty line */
285bool CircleBuf::WriteTillEl(string &Data,bool Single)
286{
287 // We cheat and assume it is unneeded to have more than one buffer load
288 for (unsigned long I = OutP; I < InP; I++)
289 {
290 if (Buf[I%Size] != '\n')
291 continue;
292 ++I;
293
294 if (Single == false)
295 {
296 if (I < InP && Buf[I%Size] == '\r')
297 ++I;
298 if (I >= InP || Buf[I%Size] != '\n')
299 continue;
300 ++I;
301 }
302
303 Data = "";
304 while (OutP < I)
305 {
306 unsigned long Sz = LeftWrite();
307 if (Sz == 0)
308 return false;
309 if (I - OutP < Sz)
310 Sz = I - OutP;
311 Data += string((char *)(Buf + (OutP%Size)),Sz);
312 OutP += Sz;
313 }
314 return true;
315 }
316 return false;
317}
318 /*}}}*/
319// CircleBuf::Stats - Print out stats information /*{{{*/
320// ---------------------------------------------------------------------
321/* */
322void CircleBuf::Stats()
323{
324 if (InP == 0)
325 return;
326
327 struct timeval Stop;
328 gettimeofday(&Stop,0);
329/* float Diff = Stop.tv_sec - Start.tv_sec +
330 (float)(Stop.tv_usec - Start.tv_usec)/1000000;
331 clog << "Got " << InP << " in " << Diff << " at " << InP/Diff << endl;*/
332}
333 /*}}}*/
334
335// ServerState::ServerState - Constructor /*{{{*/
336// ---------------------------------------------------------------------
337/* */
338ServerState::ServerState(URI Srv,HttpMethod *Owner) : Owner(Owner),
339 In(64*1024), Out(4*1024),
340 ServerName(Srv)
341{
342 Reset();
343}
344 /*}}}*/
345// ServerState::Open - Open a connection to the server /*{{{*/
346// ---------------------------------------------------------------------
347/* This opens a connection to the server. */
348bool ServerState::Open()
349{
350 // Use the already open connection if possible.
351 if (ServerFd != -1)
352 return true;
353
354 Close();
355 In.Reset();
356 Out.Reset();
357 Persistent = true;
358
359 // Determine the proxy setting
360 if (getenv("http_proxy") == 0)
361 {
362 string DefProxy = _config->Find("Acquire::http::Proxy");
363 string SpecificProxy = _config->Find("Acquire::http::Proxy::" + ServerName.Host);
364 if (SpecificProxy.empty() == false)
365 {
366 if (SpecificProxy == "DIRECT")
367 Proxy = "";
368 else
369 Proxy = SpecificProxy;
370 }
371 else
372 Proxy = DefProxy;
373 }
374 else
375 Proxy = getenv("http_proxy");
376
377 // Parse no_proxy, a , separated list of domains
378 if (getenv("no_proxy") != 0)
379 {
380 if (CheckDomainList(ServerName.Host,getenv("no_proxy")) == true)
381 Proxy = "";
382 }
383
384 // Determine what host and port to use based on the proxy settings
385 int Port = 0;
386 string Host;
387 if (Proxy.empty() == true || Proxy.Host.empty() == true)
388 {
389 if (ServerName.Port != 0)
390 Port = ServerName.Port;
391 Host = ServerName.Host;
392 }
393 else
394 {
395 if (Proxy.Port != 0)
396 Port = Proxy.Port;
397 Host = Proxy.Host;
398 }
399
400 // Connect to the remote server
401 if (Connect(Host,Port,"http",80,ServerFd,TimeOut,Owner) == false)
402 return false;
403
404 return true;
405}
406 /*}}}*/
407// ServerState::Close - Close a connection to the server /*{{{*/
408// ---------------------------------------------------------------------
409/* */
410bool ServerState::Close()
411{
412 close(ServerFd);
413 ServerFd = -1;
414 return true;
415}
416 /*}}}*/
417// ServerState::RunHeaders - Get the headers before the data /*{{{*/
418// ---------------------------------------------------------------------
419/* Returns 0 if things are OK, 1 if an IO error occursed and 2 if a header
420 parse error occured */
421int ServerState::RunHeaders()
422{
423 State = Header;
424
425 Owner->Status(_("Waiting for headers"));
426
427 Major = 0;
428 Minor = 0;
429 Result = 0;
430 Size = 0;
431 StartPos = 0;
432 Encoding = Closes;
433 HaveContent = false;
434 time(&Date);
435
436 do
437 {
438 string Data;
439 if (In.WriteTillEl(Data) == false)
440 continue;
441
442 if (Debug == true)
443 clog << Data;
444
445 for (string::const_iterator I = Data.begin(); I < Data.end(); I++)
446 {
447 string::const_iterator J = I;
448 for (; J != Data.end() && *J != '\n' && *J != '\r';J++);
449 if (HeaderLine(string(I,J)) == false)
450 return 2;
451 I = J;
452 }
453
454 // 100 Continue is a Nop...
455 if (Result == 100)
456 continue;
457
458 // Tidy up the connection persistance state.
459 if (Encoding == Closes && HaveContent == true)
460 Persistent = false;
461
462 return 0;
463 }
464 while (Owner->Go(false,this) == true);
465
466 return 1;
467}
468 /*}}}*/
469// ServerState::RunData - Transfer the data from the socket /*{{{*/
470// ---------------------------------------------------------------------
471/* */
472bool ServerState::RunData()
473{
474 State = Data;
475
476 // Chunked transfer encoding is fun..
477 if (Encoding == Chunked)
478 {
479 while (1)
480 {
481 // Grab the block size
482 bool Last = true;
483 string Data;
484 In.Limit(-1);
485 do
486 {
487 if (In.WriteTillEl(Data,true) == true)
488 break;
489 }
490 while ((Last = Owner->Go(false,this)) == true);
491
492 if (Last == false)
493 return false;
494
495 // See if we are done
496 unsigned long Len = strtol(Data.c_str(),0,16);
497 if (Len == 0)
498 {
499 In.Limit(-1);
500
501 // We have to remove the entity trailer
502 Last = true;
503 do
504 {
505 if (In.WriteTillEl(Data,true) == true && Data.length() <= 2)
506 break;
507 }
508 while ((Last = Owner->Go(false,this)) == true);
509 if (Last == false)
510 return false;
511 return !_error->PendingError();
512 }
513
514 // Transfer the block
515 In.Limit(Len);
516 while (Owner->Go(true,this) == true)
517 if (In.IsLimit() == true)
518 break;
519
520 // Error
521 if (In.IsLimit() == false)
522 return false;
523
524 // The server sends an extra new line before the next block specifier..
525 In.Limit(-1);
526 Last = true;
527 do
528 {
529 if (In.WriteTillEl(Data,true) == true)
530 break;
531 }
532 while ((Last = Owner->Go(false,this)) == true);
533 if (Last == false)
534 return false;
535 }
536 }
537 else
538 {
539 /* Closes encoding is used when the server did not specify a size, the
540 loss of the connection means we are done */
541 if (Encoding == Closes)
542 In.Limit(-1);
543 else
544 In.Limit(Size - StartPos);
545
546 // Just transfer the whole block.
547 do
548 {
549 if (In.IsLimit() == false)
550 continue;
551
552 In.Limit(-1);
553 return !_error->PendingError();
554 }
555 while (Owner->Go(true,this) == true);
556 }
557
558 return Owner->Flush(this) && !_error->PendingError();
559}
560 /*}}}*/
561// ServerState::HeaderLine - Process a header line /*{{{*/
562// ---------------------------------------------------------------------
563/* */
564bool ServerState::HeaderLine(string Line)
565{
566 if (Line.empty() == true)
567 return true;
568
569 // The http server might be trying to do something evil.
570 if (Line.length() >= MAXLEN)
571 return _error->Error(_("Got a single header line over %u chars"),MAXLEN);
572
573 string::size_type Pos = Line.find(' ');
574 if (Pos == string::npos || Pos+1 > Line.length())
575 {
576 // Blah, some servers use "connection:closes", evil.
577 Pos = Line.find(':');
578 if (Pos == string::npos || Pos + 2 > Line.length())
579 return _error->Error(_("Bad header line"));
580 Pos++;
581 }
582
583 // Parse off any trailing spaces between the : and the next word.
584 string::size_type Pos2 = Pos;
585 while (Pos2 < Line.length() && isspace(Line[Pos2]) != 0)
586 Pos2++;
587
588 string Tag = string(Line,0,Pos);
589 string Val = string(Line,Pos2);
590
591 if (stringcasecmp(Tag.c_str(),Tag.c_str()+4,"HTTP") == 0)
592 {
593 // Evil servers return no version
594 if (Line[4] == '/')
595 {
596 if (sscanf(Line.c_str(),"HTTP/%u.%u %u %[^\n]",&Major,&Minor,
597 &Result,Code) != 4)
598 return _error->Error(_("The HTTP server sent an invalid reply header"));
599 }
600 else
601 {
602 Major = 0;
603 Minor = 9;
604 if (sscanf(Line.c_str(),"HTTP %u %[^\n]",&Result,Code) != 2)
605 return _error->Error(_("The HTTP server sent an invalid reply header"));
606 }
607
608 /* Check the HTTP response header to get the default persistance
609 state. */
610 if (Major < 1)
611 Persistent = false;
612 else
613 {
614 if (Major == 1 && Minor <= 0)
615 Persistent = false;
616 else
617 Persistent = true;
618 }
619
620 return true;
621 }
622
623 if (stringcasecmp(Tag,"Content-Length:") == 0)
624 {
625 if (Encoding == Closes)
626 Encoding = Stream;
627 HaveContent = true;
628
629 // The length is already set from the Content-Range header
630 if (StartPos != 0)
631 return true;
632
633 if (sscanf(Val.c_str(),"%lu",&Size) != 1)
634 return _error->Error(_("The HTTP server sent an invalid Content-Length header"));
635 return true;
636 }
637
638 if (stringcasecmp(Tag,"Content-Type:") == 0)
639 {
640 HaveContent = true;
641 return true;
642 }
643
644 if (stringcasecmp(Tag,"Content-Range:") == 0)
645 {
646 HaveContent = true;
647
648 if (sscanf(Val.c_str(),"bytes %lu-%*u/%lu",&StartPos,&Size) != 2)
649 return _error->Error(_("The HTTP server sent an invalid Content-Range header"));
650 if ((unsigned)StartPos > Size)
651 return _error->Error(_("This HTTP server has broken range support"));
652 return true;
653 }
654
655 if (stringcasecmp(Tag,"Transfer-Encoding:") == 0)
656 {
657 HaveContent = true;
658 if (stringcasecmp(Val,"chunked") == 0)
659 Encoding = Chunked;
660 return true;
661 }
662
663 if (stringcasecmp(Tag,"Connection:") == 0)
664 {
665 if (stringcasecmp(Val,"close") == 0)
666 Persistent = false;
667 if (stringcasecmp(Val,"keep-alive") == 0)
668 Persistent = true;
669 return true;
670 }
671
672 if (stringcasecmp(Tag,"Last-Modified:") == 0)
673 {
674 if (StrToTime(Val,Date) == false)
675 return _error->Error(_("Unknown date format"));
676 return true;
677 }
678
679 return true;
680}
681 /*}}}*/
682
683// HttpMethod::SendReq - Send the HTTP request /*{{{*/
684// ---------------------------------------------------------------------
685/* This places the http request in the outbound buffer */
686void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out)
687{
688 URI Uri = Itm->Uri;
689
690 // The HTTP server expects a hostname with a trailing :port
691 char Buf[1000];
692 string ProperHost = Uri.Host;
693 if (Uri.Port != 0)
694 {
695 sprintf(Buf,":%u",Uri.Port);
696 ProperHost += Buf;
697 }
698
699 // Just in case.
700 if (Itm->Uri.length() >= sizeof(Buf))
701 abort();
702
703 /* Build the request. We include a keep-alive header only for non-proxy
704 requests. This is to tweak old http/1.0 servers that do support keep-alive
705 but not HTTP/1.1 automatic keep-alive. Doing this with a proxy server
706 will glitch HTTP/1.0 proxies because they do not filter it out and
707 pass it on, HTTP/1.1 says the connection should default to keep alive
708 and we expect the proxy to do this */
709 if (Proxy.empty() == true || Proxy.Host.empty())
710 sprintf(Buf,"GET %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n",
711 QuoteString(Uri.Path,"~").c_str(),ProperHost.c_str());
712 else
713 {
714 /* Generate a cache control header if necessary. We place a max
715 cache age on index files, optionally set a no-cache directive
716 and a no-store directive for archives. */
717 sprintf(Buf,"GET %s HTTP/1.1\r\nHost: %s\r\n",
718 Itm->Uri.c_str(),ProperHost.c_str());
719 // only generate a cache control header if we actually want to
720 // use a cache
721 if (_config->FindB("Acquire::http::No-Cache",false) == false)
722 {
723 if (Itm->IndexFile == true)
724 sprintf(Buf+strlen(Buf),"Cache-Control: max-age=%u\r\n",
725 _config->FindI("Acquire::http::Max-Age",0));
726 else
727 {
728 if (_config->FindB("Acquire::http::No-Store",false) == true)
729 strcat(Buf,"Cache-Control: no-store\r\n");
730 }
731 }
732 }
733 // generate a no-cache header if needed
734 if (_config->FindB("Acquire::http::No-Cache",false) == true)
735 strcat(Buf,"Cache-Control: no-cache\r\nPragma: no-cache\r\n");
736
737
738 string Req = Buf;
739
740 // Check for a partial file
741 struct stat SBuf;
742 if (stat(Itm->DestFile.c_str(),&SBuf) >= 0 && SBuf.st_size > 0)
743 {
744 // In this case we send an if-range query with a range header
745 sprintf(Buf,"Range: bytes=%li-\r\nIf-Range: %s\r\n",(long)SBuf.st_size - 1,
746 TimeRFC1123(SBuf.st_mtime).c_str());
747 Req += Buf;
748 }
749 else
750 {
751 if (Itm->LastModified != 0)
752 {
753 sprintf(Buf,"If-Modified-Since: %s\r\n",TimeRFC1123(Itm->LastModified).c_str());
754 Req += Buf;
755 }
756 }
757
758 if (Proxy.User.empty() == false || Proxy.Password.empty() == false)
759 Req += string("Proxy-Authorization: Basic ") +
760 Base64Encode(Proxy.User + ":" + Proxy.Password) + "\r\n";
761
762 if (Uri.User.empty() == false || Uri.Password.empty() == false)
763 Req += string("Authorization: Basic ") +
764 Base64Encode(Uri.User + ":" + Uri.Password) + "\r\n";
765
766 Req += "User-Agent: Debian APT-HTTP/1.3\r\n\r\n";
767
768 if (Debug == true)
769 cerr << Req << endl;
770
771 Out.Read(Req);
772}
773 /*}}}*/
774// HttpMethod::Go - Run a single loop /*{{{*/
775// ---------------------------------------------------------------------
776/* This runs the select loop over the server FDs, Output file FDs and
777 stdin. */
778bool HttpMethod::Go(bool ToFile,ServerState *Srv)
779{
780 // Server has closed the connection
781 if (Srv->ServerFd == -1 && (Srv->In.WriteSpace() == false ||
782 ToFile == false))
783 return false;
784
785 fd_set rfds,wfds;
786 FD_ZERO(&rfds);
787 FD_ZERO(&wfds);
788
789 /* Add the server. We only send more requests if the connection will
790 be persisting */
791 if (Srv->Out.WriteSpace() == true && Srv->ServerFd != -1
792 && Srv->Persistent == true)
793 FD_SET(Srv->ServerFd,&wfds);
794 if (Srv->In.ReadSpace() == true && Srv->ServerFd != -1)
795 FD_SET(Srv->ServerFd,&rfds);
796
797 // Add the file
798 int FileFD = -1;
799 if (File != 0)
800 FileFD = File->Fd();
801
802 if (Srv->In.WriteSpace() == true && ToFile == true && FileFD != -1)
803 FD_SET(FileFD,&wfds);
804
805 // Add stdin
806 FD_SET(STDIN_FILENO,&rfds);
807
808 // Figure out the max fd
809 int MaxFd = FileFD;
810 if (MaxFd < Srv->ServerFd)
811 MaxFd = Srv->ServerFd;
812
813 // Select
814 struct timeval tv;
815 tv.tv_sec = TimeOut;
816 tv.tv_usec = 0;
817 int Res = 0;
818 if ((Res = select(MaxFd+1,&rfds,&wfds,0,&tv)) < 0)
819 {
820 if (errno == EINTR)
821 return true;
822 return _error->Errno("select",_("Select failed"));
823 }
824
825 if (Res == 0)
826 {
827 _error->Error(_("Connection timed out"));
828 return ServerDie(Srv);
829 }
830
831 // Handle server IO
832 if (Srv->ServerFd != -1 && FD_ISSET(Srv->ServerFd,&rfds))
833 {
834 errno = 0;
835 if (Srv->In.Read(Srv->ServerFd) == false)
836 return ServerDie(Srv);
837 }
838
839 if (Srv->ServerFd != -1 && FD_ISSET(Srv->ServerFd,&wfds))
840 {
841 errno = 0;
842 if (Srv->Out.Write(Srv->ServerFd) == false)
843 return ServerDie(Srv);
844 }
845
846 // Send data to the file
847 if (FileFD != -1 && FD_ISSET(FileFD,&wfds))
848 {
849 if (Srv->In.Write(FileFD) == false)
850 return _error->Errno("write",_("Error writing to output file"));
851 }
852
853 // Handle commands from APT
854 if (FD_ISSET(STDIN_FILENO,&rfds))
855 {
856 if (Run(true) != -1)
857 exit(100);
858 }
859
860 return true;
861}
862 /*}}}*/
863// HttpMethod::Flush - Dump the buffer into the file /*{{{*/
864// ---------------------------------------------------------------------
865/* This takes the current input buffer from the Server FD and writes it
866 into the file */
867bool HttpMethod::Flush(ServerState *Srv)
868{
869 if (File != 0)
870 {
871 // on GNU/kFreeBSD, apt dies on /dev/null because non-blocking
872 // can't be set
873 if (File->Name() != "/dev/null")
874 SetNonBlock(File->Fd(),false);
875 if (Srv->In.WriteSpace() == false)
876 return true;
877
878 while (Srv->In.WriteSpace() == true)
879 {
880 if (Srv->In.Write(File->Fd()) == false)
881 return _error->Errno("write",_("Error writing to file"));
882 if (Srv->In.IsLimit() == true)
883 return true;
884 }
885
886 if (Srv->In.IsLimit() == true || Srv->Encoding == ServerState::Closes)
887 return true;
888 }
889 return false;
890}
891 /*}}}*/
892// HttpMethod::ServerDie - The server has closed the connection. /*{{{*/
893// ---------------------------------------------------------------------
894/* */
895bool HttpMethod::ServerDie(ServerState *Srv)
896{
897 unsigned int LErrno = errno;
898
899 // Dump the buffer to the file
900 if (Srv->State == ServerState::Data)
901 {
902 // on GNU/kFreeBSD, apt dies on /dev/null because non-blocking
903 // can't be set
904 if (File->Name() != "/dev/null")
905 SetNonBlock(File->Fd(),false);
906 while (Srv->In.WriteSpace() == true)
907 {
908 if (Srv->In.Write(File->Fd()) == false)
909 return _error->Errno("write",_("Error writing to the file"));
910
911 // Done
912 if (Srv->In.IsLimit() == true)
913 return true;
914 }
915 }
916
917 // See if this is because the server finished the data stream
918 if (Srv->In.IsLimit() == false && Srv->State != ServerState::Header &&
919 Srv->Encoding != ServerState::Closes)
920 {
921 Srv->Close();
922 if (LErrno == 0)
923 return _error->Error(_("Error reading from server. Remote end closed connection"));
924 errno = LErrno;
925 return _error->Errno("read",_("Error reading from server"));
926 }
927 else
928 {
929 Srv->In.Limit(-1);
930
931 // Nothing left in the buffer
932 if (Srv->In.WriteSpace() == false)
933 return false;
934
935 // We may have got multiple responses back in one packet..
936 Srv->Close();
937 return true;
938 }
939
940 return false;
941}
942 /*}}}*/
943// HttpMethod::DealWithHeaders - Handle the retrieved header data /*{{{*/
944// ---------------------------------------------------------------------
945/* We look at the header data we got back from the server and decide what
946 to do. Returns
947 0 - File is open,
948 1 - IMS hit
949 3 - Unrecoverable error
950 4 - Error with error content page
951 5 - Unrecoverable non-server error (close the connection) */
952int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv)
953{
954 // Not Modified
955 if (Srv->Result == 304)
956 {
957 unlink(Queue->DestFile.c_str());
958 Res.IMSHit = true;
959 Res.LastModified = Queue->LastModified;
960 return 1;
961 }
962
963 /* We have a reply we dont handle. This should indicate a perm server
964 failure */
965 if (Srv->Result < 200 || Srv->Result >= 300)
966 {
967 _error->Error("%u %s",Srv->Result,Srv->Code);
968 if (Srv->HaveContent == true)
969 return 4;
970 return 3;
971 }
972
973 // This is some sort of 2xx 'data follows' reply
974 Res.LastModified = Srv->Date;
975 Res.Size = Srv->Size;
976
977 // Open the file
978 delete File;
979 File = new FileFd(Queue->DestFile,FileFd::WriteAny);
980 if (_error->PendingError() == true)
981 return 5;
982
983 FailFile = Queue->DestFile;
984 FailFile.c_str(); // Make sure we dont do a malloc in the signal handler
985 FailFd = File->Fd();
986 FailTime = Srv->Date;
987
988 // Set the expected size
989 if (Srv->StartPos >= 0)
990 {
991 Res.ResumePoint = Srv->StartPos;
992 ftruncate(File->Fd(),Srv->StartPos);
993 }
994
995 // Set the start point
996 lseek(File->Fd(),0,SEEK_END);
997
998 delete Srv->In.Hash;
999 Srv->In.Hash = new Hashes;
1000
1001 // Fill the Hash if the file is non-empty (resume)
1002 if (Srv->StartPos > 0)
1003 {
1004 lseek(File->Fd(),0,SEEK_SET);
1005 if (Srv->In.Hash->AddFD(File->Fd(),Srv->StartPos) == false)
1006 {
1007 _error->Errno("read",_("Problem hashing file"));
1008 return 5;
1009 }
1010 lseek(File->Fd(),0,SEEK_END);
1011 }
1012
1013 SetNonBlock(File->Fd(),true);
1014 return 0;
1015}
1016 /*}}}*/
1017// HttpMethod::SigTerm - Handle a fatal signal /*{{{*/
1018// ---------------------------------------------------------------------
1019/* This closes and timestamps the open file. This is neccessary to get
1020 resume behavoir on user abort */
1021void HttpMethod::SigTerm(int)
1022{
1023 if (FailFd == -1)
1024 _exit(100);
1025 close(FailFd);
1026
1027 // Timestamp
1028 struct utimbuf UBuf;
1029 UBuf.actime = FailTime;
1030 UBuf.modtime = FailTime;
1031 utime(FailFile.c_str(),&UBuf);
1032
1033 _exit(100);
1034}
1035 /*}}}*/
1036// HttpMethod::Fetch - Fetch an item /*{{{*/
1037// ---------------------------------------------------------------------
1038/* This adds an item to the pipeline. We keep the pipeline at a fixed
1039 depth. */
1040bool HttpMethod::Fetch(FetchItem *)
1041{
1042 if (Server == 0)
1043 return true;
1044
1045 // Queue the requests
1046 int Depth = -1;
1047 bool Tail = false;
1048 for (FetchItem *I = Queue; I != 0 && Depth < (signed)PipelineDepth;
1049 I = I->Next, Depth++)
1050 {
1051 // If pipelining is disabled, we only queue 1 request
1052 if (Server->Pipeline == false && Depth >= 0)
1053 break;
1054
1055 // Make sure we stick with the same server
1056 if (Server->Comp(I->Uri) == false)
1057 break;
1058 if (QueueBack == I)
1059 Tail = true;
1060 if (Tail == true)
1061 {
1062 QueueBack = I->Next;
1063 SendReq(I,Server->Out);
1064 continue;
1065 }
1066 }
1067
1068 return true;
1069};
1070 /*}}}*/
1071// HttpMethod::Configuration - Handle a configuration message /*{{{*/
1072// ---------------------------------------------------------------------
1073/* We stash the desired pipeline depth */
1074bool HttpMethod::Configuration(string Message)
1075{
1076 if (pkgAcqMethod::Configuration(Message) == false)
1077 return false;
1078
1079 TimeOut = _config->FindI("Acquire::http::Timeout",TimeOut);
1080 PipelineDepth = _config->FindI("Acquire::http::Pipeline-Depth",
1081 PipelineDepth);
1082 Debug = _config->FindB("Debug::Acquire::http",false);
1083
1084 return true;
1085}
1086 /*}}}*/
1087// HttpMethod::Loop - Main loop /*{{{*/
1088// ---------------------------------------------------------------------
1089/* */
1090int HttpMethod::Loop()
1091{
1092 signal(SIGTERM,SigTerm);
1093 signal(SIGINT,SigTerm);
1094
1095 Server = 0;
1096
1097 int FailCounter = 0;
1098 while (1)
1099 {
1100 // We have no commands, wait for some to arrive
1101 if (Queue == 0)
1102 {
1103 if (WaitFd(STDIN_FILENO) == false)
1104 return 0;
1105 }
1106
1107 /* Run messages, we can accept 0 (no message) if we didn't
1108 do a WaitFd above.. Otherwise the FD is closed. */
1109 int Result = Run(true);
1110 if (Result != -1 && (Result != 0 || Queue == 0))
1111 return 100;
1112
1113 if (Queue == 0)
1114 continue;
253ba34a
JF
1115
1116 CFStringEncoding se = kCFStringEncodingUTF8;
1117
b7a89731
JF
1118 char *url = strdup(Queue->Uri.c_str());
1119 url:
fe23a696
JF
1120 URI uri = Queue->Uri;
1121 std::string hs = uri.Host;
1122
1123 struct hostent *he = gethostbyname(hs.c_str());
1124 if (he == NULL || he->h_addr_list[0] == NULL) {
1125 _error->Error(hstrerror(h_errno));
1126 Fail(true);
1127 free(url);
1128 }
1129
1130 uri.Host = inet_ntoa(* (struct in_addr *) he->h_addr_list[0]);
1131
1132 std::string urs = uri;
1133
1134 CFStringRef sr = CFStringCreateWithCString(kCFAllocatorDefault, urs.c_str(), se);
253ba34a
JF
1135 CFURLRef ur = CFURLCreateWithString(kCFAllocatorDefault, sr, NULL);
1136 CFRelease(sr);
1137 CFHTTPMessageRef hm = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), ur, kCFHTTPVersion1_1);
1138 CFRelease(ur);
1139
1140 struct stat SBuf;
1141 if (stat(Queue->DestFile.c_str(), &SBuf) >= 0 && SBuf.st_size > 0) {
1142 sr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("bytes=%li-"), (long) SBuf.st_size - 1);
1143 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Range"), sr);
1144 CFRelease(sr);
1145
1146 sr = CFStringCreateWithCString(kCFAllocatorDefault, TimeRFC1123(SBuf.st_mtime).c_str(), se);
1147 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("If-Range"), sr);
1148 CFRelease(sr);
1149 } else if (Queue->LastModified != 0) {
1150 sr = CFStringCreateWithCString(kCFAllocatorDefault, TimeRFC1123(SBuf.st_mtime).c_str(), se);
1151 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("If-Modified-Since"), sr);
1152 CFRelease(sr);
da6ee469 1153 }
253ba34a
JF
1154
1155 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("User-Agent"), CFSTR("Telesphoreo APT-HTTP/1.0.98"));
fe23a696
JF
1156
1157 sr = CFStringCreateWithCString(kCFAllocatorDefault, hs.c_str(), se);
1158 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Host"), sr);
1159 CFRelease(sr);
1160
253ba34a
JF
1161 CFReadStreamRef rs = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, hm);
1162 CFRelease(hm);
1163
985ed269
JF
1164 CFDictionaryRef dr = SCDynamicStoreCopyProxies(NULL);
1165 CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPProxy, dr);
1166 CFRelease(dr);
1167
b7a89731 1168 //CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue);
253ba34a
JF
1169 CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
1170
910f26ec 1171 FetchResult Res;
b7a89731
JF
1172 CFIndex rd;
1173 UInt32 sc;
910f26ec
JF
1174
1175 uint8_t data[10240];
1176 size_t offset = 0;
1177
fe23a696 1178 Status("Connecting to %s", hs.c_str());
253ba34a
JF
1179
1180 if (!CFReadStreamOpen(rs)) {
fe23a696 1181 CfrsError("Open", rs);
253ba34a 1182 Fail(true);
910f26ec 1183 goto done;
da6ee469
JF
1184 }
1185
b7a89731 1186 rd = CFReadStreamRead(rs, data, sizeof(data));
da6ee469 1187
910f26ec 1188 if (rd == -1) {
fe23a696 1189 CfrsError(uri.Host.c_str(), rs);
910f26ec
JF
1190 Fail(true);
1191 goto done;
1192 }
1193
da6ee469 1194 Res.Filename = Queue->DestFile;
da6ee469 1195
253ba34a 1196 hm = (CFHTTPMessageRef) CFReadStreamCopyProperty(rs, kCFStreamPropertyHTTPResponseHeader);
b7a89731
JF
1197 sc = CFHTTPMessageGetResponseStatusCode(hm);
1198
1199 if (sc == 302) {
1200 sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Location"));
1201 if (sr == NULL) {
1202 Fail();
1203 goto done_;
1204 } else {
1205 size_t ln = CFStringGetLength(sr) + 1;
1206 free(url);
1207 url = static_cast<char *>(malloc(ln));
1208
1209 if (!CFStringGetCString(sr, url, ln, se)) {
1210 Fail();
1211 goto done_;
1212 }
1213
1214 CFRelease(sr);
1215 goto url;
1216 }
1217 }
253ba34a 1218
253ba34a
JF
1219 sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Content-Range"));
1220 if (sr != NULL) {
1221 size_t ln = CFStringGetLength(sr) + 1;
1222 char cr[ln];
1223
1224 if (!CFStringGetCString(sr, cr, ln, se)) {
1225 Fail();
985ed269 1226 goto done_;
253ba34a
JF
1227 }
1228
1229 CFRelease(sr);
1230
1231 if (sscanf(cr, "bytes %lu-%*u/%lu", &offset, &Res.Size) != 2) {
1232 _error->Error(_("The HTTP server sent an invalid Content-Range header"));
1233 Fail();
985ed269 1234 goto done_;
253ba34a
JF
1235 }
1236
1237 if (offset > Res.Size) {
1238 _error->Error(_("This HTTP server has broken range support"));
1239 Fail();
985ed269 1240 goto done_;
253ba34a
JF
1241 }
1242 } else {
1243 sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Content-Length"));
1244 if (sr != NULL) {
1245 Res.Size = CFStringGetIntValue(sr);
1246 CFRelease(sr);
1247 }
1248 }
da6ee469 1249
253ba34a
JF
1250 time(&Res.LastModified);
1251
1252 sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Last-Modified"));
1253 if (sr != NULL) {
1254 size_t ln = CFStringGetLength(sr) + 1;
1255 char cr[ln];
1256
1257 if (!CFStringGetCString(sr, cr, ln, se)) {
1258 Fail();
985ed269 1259 goto done_;
253ba34a
JF
1260 }
1261
1262 CFRelease(sr);
1263
1264 if (!StrToTime(cr, Res.LastModified)) {
1265 _error->Error(_("Unknown date format"));
1266 Fail();
985ed269 1267 goto done_;
253ba34a
JF
1268 }
1269 }
1270
1271 CFRelease(hm);
1272
1273 if (sc == 304) {
1274 unlink(Queue->DestFile.c_str());
1275 Res.IMSHit = true;
1276 Res.LastModified = Queue->LastModified;
1277 URIDone(Res);
1278 } else if (sc < 200 || sc >= 300)
1279 Fail();
1280 else {
1281 Hashes hash;
1282
1283 File = new FileFd(Queue->DestFile, FileFd::WriteAny);
1284 if (_error->PendingError() == true) {
1285 delete File;
1286 File = NULL;
1287 Fail();
1288 goto done;
1289 }
1290
1291 FailFile = Queue->DestFile;
1292 FailFile.c_str(); // Make sure we dont do a malloc in the signal handler
1293 FailFd = File->Fd();
1294 FailTime = Res.LastModified;
1295
1296 Res.ResumePoint = offset;
1297 ftruncate(File->Fd(), offset);
1298
1299 if (offset != 0) {
1300 lseek(File->Fd(), 0, SEEK_SET);
1301 if (!hash.AddFD(File->Fd(), offset)) {
1302 _error->Errno("read", _("Problem hashing file"));
1303 delete File;
1304 File = NULL;
1305 Fail();
1306 goto done;
1307 }
1308 }
1309
1310 lseek(File->Fd(), 0, SEEK_END);
1311
1312 URIStart(Res);
1313
910f26ec 1314 read: if (rd == -1) {
fe23a696 1315 CfrsError("rd", rs);
253ba34a 1316 Fail(true);
910f26ec 1317 } else if (rd == 0) {
da6ee469
JF
1318 if (Res.Size == 0)
1319 Res.Size = File->Size();
253ba34a 1320
da6ee469
JF
1321 struct utimbuf UBuf;
1322 time(&UBuf.actime);
253ba34a
JF
1323 UBuf.actime = Res.LastModified;
1324 UBuf.modtime = Res.LastModified;
1325 utime(Queue->DestFile.c_str(), &UBuf);
da6ee469 1326
253ba34a 1327 Res.TakeHashes(hash);
da6ee469 1328 URIDone(Res);
253ba34a
JF
1329 } else {
1330 hash.Add(data, rd);
da6ee469 1331
253ba34a
JF
1332 uint8_t *dt = data;
1333 while (rd != 0) {
1334 int sz = write(File->Fd(), dt, rd);
da6ee469 1335
253ba34a
JF
1336 if (sz == -1) {
1337 delete File;
1338 File = NULL;
1339 Fail();
1340 goto done;
1341 }
1342
1343 dt += sz;
1344 rd -= sz;
1345 }
1346
1347 rd = CFReadStreamRead(rs, data, sizeof(data));
1348 goto read;
1349 }
da6ee469 1350 }
253ba34a 1351
985ed269
JF
1352 goto done;
1353 done_:
1354 CFRelease(hm);
253ba34a 1355 done:
985ed269 1356 CFReadStreamClose(rs);
253ba34a 1357 CFRelease(rs);
b7a89731 1358 free(url);
253ba34a 1359
da6ee469
JF
1360 FailCounter = 0;
1361 }
1362
1363 return 0;
1364}
1365 /*}}}*/
1366
4c8eb365 1367int main()
da6ee469 1368{
fe23a696 1369#if !defined(__ENVIRONMENT_ASPEN_VERSION_MIN_REQUIRED__) || __ENVIRONMENT_ASPEN_VERSION_MIN_REQUIRED__ < 10200
854e5ff1
JF
1370 struct nlist nl[2];
1371 memset(nl, 0, sizeof(nl));
3d6f1076 1372 nl[0].n_un.n_name = (char *) "_useMDNSResponder";
854e5ff1
JF
1373 nlist("/usr/lib/libc.dylib", nl);
1374 if (nl[0].n_type != N_UNDF)
1375 *(int *) nl[0].n_value = 0;
fe23a696 1376#endif
854e5ff1 1377
da6ee469
JF
1378 setlocale(LC_ALL, "");
1379
1380 HttpMethod Mth;
1381
1382 return Mth.Loop();
1383}
1384
1385