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