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