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