1 // -*- mode: cpp; mode: fold -*-
3 // $Id: http.cc,v 1.59 2004/05/08 19:42:35 mdz Exp $
4 /* ######################################################################
6 HTTP Acquire Method - This is the HTTP aquire method for APT.
8 It uses HTTP/1.1 and many of the fancy options there-in, such as
9 pipelining, range, if-range and so on.
11 It is based on a doubly buffered select loop. A groupe of requests are
12 fed into a single output buffer that is constantly fed out the
13 socket. This provides ideal pipelining as in many cases all of the
14 requests will fit into a single packet. The input socket is buffered
15 the same way and fed into the fd for the file (may be a pipe in future).
17 This double buffering provides fairly substantial transfer rates,
18 compared to wget the http method is about 4% faster. Most importantly,
19 when HTTP is compared with FTP as a protocol the speed difference is
20 huge. In tests over the internet from two sites to llug (via ATM) this
21 program got 230k/s sustained http transfer rates. FTP on the other
22 hand topped out at 170k/s. That combined with the time to setup the
23 FTP connection makes HTTP a vastly superior protocol.
25 ##################################################################### */
27 // Include Files /*{{{*/
28 #include <apt-pkg/fileutl.h>
29 #include <apt-pkg/acquire-method.h>
30 #include <apt-pkg/error.h>
31 #include <apt-pkg/hashes.h>
32 #include <apt-pkg/netrc.h>
34 #include <sys/sysctl.h>
51 #include <arpa/inet.h>
55 #include <CoreFoundation/CoreFoundation.h>
56 #include <CFNetwork/CFNetwork.h>
57 #include <SystemConfiguration/SystemConfiguration.h>
61 #include "rfc2553emu.h"
66 CFStringRef Firmware_
;
68 CFStringRef UniqueID_
;
70 void CfrsError(const char *name
, CFReadStreamRef rs
) {
71 CFStreamError se
= CFReadStreamGetError(rs
);
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
) {
84 case kCFStreamErrorHTTPParseFailure
:
85 _error
->Error("Parse failure");
88 case kCFStreamErrorHTTPRedirectionLoop
:
89 _error
->Error("Redirection loop");
92 case kCFStreamErrorHTTPBadURL
:
93 _error
->Error("Bad URL");
97 _error
->Error("Unknown HTTP error: %ld", se
.error
);
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
);
107 _error
->Error("Domain #%ld: %ld", se
.domain
, se
.error
);
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 AllowRedirect
= false;
120 static const CFOptionFlags kNetworkEvents
=
121 kCFStreamEventOpenCompleted
|
122 kCFStreamEventHasBytesAvailable
|
123 kCFStreamEventEndEncountered
|
124 kCFStreamEventErrorOccurred
|
127 static void CFReadStreamCallback(CFReadStreamRef stream
, CFStreamEventType event
, void *arg
) {
129 case kCFStreamEventOpenCompleted
:
132 case kCFStreamEventHasBytesAvailable
:
133 case kCFStreamEventEndEncountered
:
134 *reinterpret_cast<int *>(arg
) = 1;
135 CFRunLoopStop(CFRunLoopGetCurrent());
138 case kCFStreamEventErrorOccurred
:
139 *reinterpret_cast<int *>(arg
) = -1;
140 CFRunLoopStop(CFRunLoopGetCurrent());
145 /* http://lists.apple.com/archives/Macnetworkprog/2006/Apr/msg00014.html */
146 int CFReadStreamOpen(CFReadStreamRef stream
, double timeout
) {
147 CFStreamClientContext context
;
150 memset(&context
, 0, sizeof(context
));
151 context
.info
= &value
;
153 if (CFReadStreamSetClient(stream
, kNetworkEvents
, CFReadStreamCallback
, &context
)) {
154 CFReadStreamScheduleWithRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
155 if (CFReadStreamOpen(stream
))
156 CFRunLoopRunInMode(kCFRunLoopDefaultMode
, timeout
, false);
159 CFReadStreamSetClient(stream
, kCFStreamEventNone
, NULL
, NULL
);
165 // HttpMethod::SigTerm - Handle a fatal signal /*{{{*/
166 // ---------------------------------------------------------------------
167 /* This closes and timestamps the open file. This is neccessary to get
168 resume behavoir on user abort */
169 void HttpMethod::SigTerm(int)
177 UBuf
.actime
= FailTime
;
178 UBuf
.modtime
= FailTime
;
179 utime(FailFile
.c_str(),&UBuf
);
184 // HttpMethod::Configuration - Handle a configuration message /*{{{*/
185 // ---------------------------------------------------------------------
186 /* We stash the desired pipeline depth */
187 bool HttpMethod::Configuration(string Message
)
189 if (pkgAcqMethod::Configuration(Message
) == false)
192 AllowRedirect
= _config
->FindB("Acquire::http::AllowRedirect",true);
193 TimeOut
= _config
->FindI("Acquire::http::Timeout",TimeOut
);
194 PipelineDepth
= _config
->FindI("Acquire::http::Pipeline-Depth",
196 Debug
= _config
->FindB("Debug::Acquire::http",false);
201 // HttpMethod::Loop - Main loop /*{{{*/
202 // ---------------------------------------------------------------------
204 int HttpMethod::Loop()
206 typedef vector
<string
> StringVector
;
207 typedef vector
<string
>::iterator StringVectorIterator
;
208 map
<string
, StringVector
> Redirected
;
210 signal(SIGTERM
,SigTerm
);
211 signal(SIGINT
,SigTerm
);
213 std::set
<std::string
> cached
;
218 // We have no commands, wait for some to arrive
221 if (WaitFd(STDIN_FILENO
) == false)
225 /* Run messages, we can accept 0 (no message) if we didn't
226 do a WaitFd above.. Otherwise the FD is closed. */
227 int Result
= Run(true);
228 if (Result
!= -1 && (Result
!= 0 || Queue
== 0))
234 CFStringEncoding se
= kCFStringEncodingUTF8
;
236 char *url
= strdup(Queue
->Uri
.c_str());
238 URI uri
= std::string(url
);
239 std::string hs
= uri
.Host
;
241 if (cached
.find(hs
) != cached
.end()) {
242 _error
->Error("Cached Failure");
249 std::string urs
= uri
;
252 size_t bad
= urs
.find_first_of("+");
253 if (bad
== std::string::npos
)
256 urs
= urs
.substr(0, bad
) + "%2b" + urs
.substr(bad
+ 1);
259 CFStringRef sr
= CFStringCreateWithCString(kCFAllocatorDefault
, urs
.c_str(), se
);
260 CFURLRef ur
= CFURLCreateWithString(kCFAllocatorDefault
, sr
, NULL
);
262 CFHTTPMessageRef hm
= CFHTTPMessageCreateRequest(kCFAllocatorDefault
, CFSTR("GET"), ur
, kCFHTTPVersion1_1
);
266 if (stat(Queue
->DestFile
.c_str(), &SBuf
) >= 0 && SBuf
.st_size
> 0) {
267 sr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("bytes=%li-"), (long) SBuf
.st_size
- 1);
268 CFHTTPMessageSetHeaderFieldValue(hm
, CFSTR("Range"), sr
);
271 sr
= CFStringCreateWithCString(kCFAllocatorDefault
, TimeRFC1123(SBuf
.st_mtime
).c_str(), se
);
272 CFHTTPMessageSetHeaderFieldValue(hm
, CFSTR("If-Range"), sr
);
275 CFHTTPMessageSetHeaderFieldValue(hm
, CFSTR("Cache-Control"), CFSTR("no-cache"));
276 } else if (Queue
->LastModified
!= 0) {
277 sr
= CFStringCreateWithCString(kCFAllocatorDefault
, TimeRFC1123(Queue
->LastModified
).c_str(), se
);
278 CFHTTPMessageSetHeaderFieldValue(hm
, CFSTR("If-Modified-Since"), sr
);
281 CFHTTPMessageSetHeaderFieldValue(hm
, CFSTR("Cache-Control"), CFSTR("no-cache"));
283 CFHTTPMessageSetHeaderFieldValue(hm
, CFSTR("Cache-Control"), CFSTR("max-age=0"));
285 if (Firmware_
!= NULL
)
286 CFHTTPMessageSetHeaderFieldValue(hm
, CFSTR("X-Firmware"), Firmware_
);
288 sr
= CFStringCreateWithCString(kCFAllocatorDefault
, Machine_
, se
);
289 CFHTTPMessageSetHeaderFieldValue(hm
, CFSTR("X-Machine"), sr
);
292 if (UniqueID_
!= NULL
)
293 CFHTTPMessageSetHeaderFieldValue(hm
, CFSTR("X-Unique-ID"), UniqueID_
);
295 CFHTTPMessageSetHeaderFieldValue(hm
, CFSTR("User-Agent"), CFSTR("Telesphoreo APT-HTTP/1.0.592"));
297 CFReadStreamRef rs
= CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault
, hm
);
300 #define _kCFStreamPropertyReadTimeout CFSTR("_kCFStreamPropertyReadTimeout")
301 #define _kCFStreamPropertyWriteTimeout CFSTR("_kCFStreamPropertyWriteTimeout")
302 #define _kCFStreamPropertySocketImmediateBufferTimeOut CFSTR("_kCFStreamPropertySocketImmediateBufferTimeOut")
304 /*SInt32 to(TimeOut);
305 CFNumberRef nm(CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &to));*/
307 CFNumberRef
nm(CFNumberCreate(kCFAllocatorDefault
, kCFNumberDoubleType
, &to
));
309 CFReadStreamSetProperty(rs
, _kCFStreamPropertyReadTimeout
, nm
);
310 CFReadStreamSetProperty(rs
, _kCFStreamPropertyWriteTimeout
, nm
);
311 CFReadStreamSetProperty(rs
, _kCFStreamPropertySocketImmediateBufferTimeOut
, nm
);
314 CFDictionaryRef dr
= SCDynamicStoreCopyProxies(NULL
);
315 CFReadStreamSetProperty(rs
, kCFStreamPropertyHTTPProxy
, dr
);
318 //CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue);
319 CFReadStreamSetProperty(rs
, kCFStreamPropertyHTTPAttemptPersistentConnection
, kCFBooleanTrue
);
328 Status("Connecting to %s", hs
.c_str());
330 switch (CFReadStreamOpen(rs
, to
)) {
332 CfrsError("Open", rs
);
336 _error
->Error("Host Unreachable");
349 rd
= CFReadStreamRead(rs
, data
, sizeof(data
));
352 CfrsError(uri
.Host
.c_str(), rs
);
358 Res
.Filename
= Queue
->DestFile
;
360 hm
= (CFHTTPMessageRef
) CFReadStreamCopyProperty(rs
, kCFStreamPropertyHTTPResponseHeader
);
361 sc
= CFHTTPMessageGetResponseStatusCode(hm
);
363 if (sc
== 301 || sc
== 302) {
364 sr
= CFHTTPMessageCopyHeaderFieldValue(hm
, CFSTR("Location"));
369 size_t ln
= CFStringGetLength(sr
) + 1;
371 url
= static_cast<char *>(malloc(ln
));
373 if (!CFStringGetCString(sr
, url
, ln
, se
)) {
383 sr
= CFHTTPMessageCopyHeaderFieldValue(hm
, CFSTR("Content-Range"));
385 size_t ln
= CFStringGetLength(sr
) + 1;
388 if (!CFStringGetCString(sr
, cr
, ln
, se
)) {
395 if (sscanf(cr
, "bytes %lu-%*u/%lu", &offset
, &Res
.Size
) != 2) {
396 _error
->Error(_("The HTTP server sent an invalid Content-Range header"));
401 if (offset
> Res
.Size
) {
402 _error
->Error(_("This HTTP server has broken range support"));
407 sr
= CFHTTPMessageCopyHeaderFieldValue(hm
, CFSTR("Content-Length"));
409 Res
.Size
= CFStringGetIntValue(sr
);
414 time(&Res
.LastModified
);
416 sr
= CFHTTPMessageCopyHeaderFieldValue(hm
, CFSTR("Last-Modified"));
418 size_t ln
= CFStringGetLength(sr
) + 1;
421 if (!CFStringGetCString(sr
, cr
, ln
, se
)) {
428 if (!StrToTime(cr
, Res
.LastModified
)) {
429 _error
->Error(_("Unknown date format"));
435 if (sc
< 200 || sc
>= 300 && sc
!= 304) {
436 sr
= CFHTTPMessageCopyResponseStatusLine(hm
);
438 size_t ln
= CFStringGetLength(sr
) + 1;
441 if (!CFStringGetCString(sr
, cr
, ln
, se
)) {
448 _error
->Error("%s", cr
);
457 unlink(Queue
->DestFile
.c_str());
459 Res
.LastModified
= Queue
->LastModified
;
464 File
= new FileFd(Queue
->DestFile
, FileFd::WriteAny
);
465 if (_error
->PendingError() == true) {
472 FailFile
= Queue
->DestFile
;
473 FailFile
.c_str(); // Make sure we dont do a malloc in the signal handler
475 FailTime
= Res
.LastModified
;
477 Res
.ResumePoint
= offset
;
478 ftruncate(File
->Fd(), offset
);
481 lseek(File
->Fd(), 0, SEEK_SET
);
482 if (!hash
.AddFD(File
->Fd(), offset
)) {
483 _error
->Errno("read", _("Problem hashing file"));
491 lseek(File
->Fd(), 0, SEEK_END
);
495 read
: if (rd
== -1) {
498 } else if (rd
== 0) {
500 Res
.Size
= File
->Size();
504 UBuf
.actime
= Res
.LastModified
;
505 UBuf
.modtime
= Res
.LastModified
;
506 utime(Queue
->DestFile
.c_str(), &UBuf
);
508 Res
.TakeHashes(hash
);
515 int sz
= write(File
->Fd(), dt
, rd
);
528 rd
= CFReadStreamRead(rs
, data
, sizeof(data
));
537 CFReadStreamClose(rs
);
550 setlocale(LC_ALL
, "");
551 // ignore SIGPIPE, this can happen on write() if the socket
552 // closes the connection (this is dealt with via ServerDie())
553 signal(SIGPIPE
, SIG_IGN
);
558 sysctlbyname("hw.machine", NULL
, &size
, NULL
, 0);
559 char *machine
= new char[size
];
560 sysctlbyname("hw.machine", machine
, &size
, NULL
, 0);
563 const char *path
= "/System/Library/CoreServices/SystemVersion.plist";
564 CFURLRef url
= CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault
, (uint8_t *) path
, strlen(path
), false);
566 CFPropertyListRef plist
; {
567 CFReadStreamRef stream
= CFReadStreamCreateWithFile(kCFAllocatorDefault
, url
);
568 CFReadStreamOpen(stream
);
569 plist
= CFPropertyListCreateFromStream(kCFAllocatorDefault
, stream
, 0, kCFPropertyListImmutable
, NULL
, NULL
);
570 CFReadStreamClose(stream
);
576 Firmware_
= (CFStringRef
) CFRetain(CFDictionaryGetValue((CFDictionaryRef
) plist
, CFSTR("ProductVersion")));
580 if (UniqueID_
== NULL
)
581 if (void *libMobileGestalt
= dlopen("/usr/lib/libMobileGestalt.dylib", RTLD_GLOBAL
| RTLD_LAZY
))
582 if (CFStringRef (*$MGCopyAnswer
)(CFStringRef
) = (CFStringRef (*)(CFStringRef
)) dlsym(libMobileGestalt
, "MGCopyAnswer"))
583 UniqueID_
= $
MGCopyAnswer(CFSTR("UniqueDeviceID"));
585 if (UniqueID_
== NULL
)
586 if (void *lockdown
= lockdown_connect()) {
587 UniqueID_
= lockdown_copy_value(lockdown
, NULL
, kLockdownUniqueDeviceIDKey
);
588 lockdown_disconnect(lockdown
);