]> git.saurik.com Git - apt-legacy.git/blame - methods/http.cc
Fix compilation of http when embedding into Cydia.
[apt-legacy.git] / methods / http.cc
CommitLineData
da6ee469
JF
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
3// $Id: http.cc,v 1.59 2004/05/08 19:42:35 mdz Exp $
4/* ######################################################################
5
00ec24d0 6 HTTP Acquire Method - This is the HTTP aquire method for APT.
da6ee469
JF
7
8 It uses HTTP/1.1 and many of the fancy options there-in, such as
9 pipelining, range, if-range and so on.
10
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).
16
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.
24
25 ##################################################################### */
26 /*}}}*/
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>
0e5943eb 32#include <apt-pkg/netrc.h>
da6ee469 33
ca652fea 34#include <sys/sysctl.h>
da6ee469
JF
35#include <sys/stat.h>
36#include <sys/time.h>
37#include <utime.h>
38#include <unistd.h>
39#include <signal.h>
40#include <stdio.h>
41#include <errno.h>
42#include <string.h>
43#include <iostream>
0e5943eb 44#include <map>
788c5b99 45#include <set>
0e5943eb
JF
46#include <apti18n.h>
47
da6ee469
JF
48
49// Internet stuff
50#include <netdb.h>
fe23a696 51#include <arpa/inet.h>
da6ee469 52
acc2d4c9 53#include <dlfcn.h>
4db93271 54#include <lockdown.h>
253ba34a 55#include <CoreFoundation/CoreFoundation.h>
25d34b29 56#include <CFNetwork/CFNetwork.h>
985ed269 57#include <SystemConfiguration/SystemConfiguration.h>
253ba34a 58
00ec24d0 59#include "config.h"
da6ee469 60#include "http.h"
da6ee469
JF
61 /*}}}*/
62using namespace std;
63
9439ca0e
JF
64static CFStringRef Firmware_;
65static const char *Machine_;
66static CFStringRef UniqueID_;
ca652fea 67
fe23a696 68void CfrsError(const char *name, CFReadStreamRef rs) {
985ed269
JF
69 CFStreamError se = CFReadStreamGetError(rs);
70
71 if (se.domain == kCFStreamErrorDomainCustom) {
72 } else if (se.domain == kCFStreamErrorDomainPOSIX) {
73 _error->Error("POSIX: %s", strerror(se.error));
74 } else if (se.domain == kCFStreamErrorDomainMacOSStatus) {
75 _error->Error("MacOSStatus: %ld", se.error);
76 } else if (se.domain == kCFStreamErrorDomainNetDB) {
fe23a696 77 _error->Error("NetDB: %s %s", name, gai_strerror(se.error));
985ed269
JF
78 } else if (se.domain == kCFStreamErrorDomainMach) {
79 _error->Error("Mach: %ld", se.error);
80 } else if (se.domain == kCFStreamErrorDomainHTTP) {
81 switch (se.error) {
82 case kCFStreamErrorHTTPParseFailure:
83 _error->Error("Parse failure");
84 break;
85
86 case kCFStreamErrorHTTPRedirectionLoop:
87 _error->Error("Redirection loop");
88 break;
89
90 case kCFStreamErrorHTTPBadURL:
91 _error->Error("Bad URL");
92 break;
93
94 default:
95 _error->Error("Unknown HTTP error: %ld", se.error);
96 break;
97 }
98 } else if (se.domain == kCFStreamErrorDomainSOCKS) {
99 _error->Error("SOCKS: %ld", se.error);
100 } else if (se.domain == kCFStreamErrorDomainSystemConfiguration) {
101 _error->Error("SystemConfiguration: %ld", se.error);
102 } else if (se.domain == kCFStreamErrorDomainSSL) {
103 _error->Error("SSL: %ld", se.error);
104 } else {
b7a89731 105 _error->Error("Domain #%ld: %ld", se.domain, se.error);
985ed269
JF
106 }
107}
108
da6ee469
JF
109string HttpMethod::FailFile;
110int HttpMethod::FailFd = -1;
111time_t HttpMethod::FailTime = 0;
112unsigned long PipelineDepth = 10;
113unsigned long TimeOut = 120;
0e5943eb 114bool AllowRedirect = false;
da6ee469 115bool Debug = false;
00ec24d0 116URI Proxy;
da6ee469 117
788c5b99
JF
118static const CFOptionFlags kNetworkEvents =
119 kCFStreamEventOpenCompleted |
120 kCFStreamEventHasBytesAvailable |
121 kCFStreamEventEndEncountered |
122 kCFStreamEventErrorOccurred |
1230;
124
125static void CFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType event, void *arg) {
126 switch (event) {
127 case kCFStreamEventOpenCompleted:
128 break;
129
130 case kCFStreamEventHasBytesAvailable:
131 case kCFStreamEventEndEncountered:
132 *reinterpret_cast<int *>(arg) = 1;
133 CFRunLoopStop(CFRunLoopGetCurrent());
134 break;
135
136 case kCFStreamEventErrorOccurred:
137 *reinterpret_cast<int *>(arg) = -1;
138 CFRunLoopStop(CFRunLoopGetCurrent());
139 break;
140 }
141}
142
143/* http://lists.apple.com/archives/Macnetworkprog/2006/Apr/msg00014.html */
144int CFReadStreamOpen(CFReadStreamRef stream, double timeout) {
145 CFStreamClientContext context;
146 int value(0);
147
148 memset(&context, 0, sizeof(context));
149 context.info = &value;
150
151 if (CFReadStreamSetClient(stream, kNetworkEvents, CFReadStreamCallback, &context)) {
152 CFReadStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
153 if (CFReadStreamOpen(stream))
154 CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, false);
155 else
156 value = -1;
157 CFReadStreamSetClient(stream, kCFStreamEventNone, NULL, NULL);
158 }
159
160 return value;
161}
162
da6ee469
JF
163// HttpMethod::SigTerm - Handle a fatal signal /*{{{*/
164// ---------------------------------------------------------------------
165/* This closes and timestamps the open file. This is neccessary to get
166 resume behavoir on user abort */
167void HttpMethod::SigTerm(int)
168{
169 if (FailFd == -1)
170 _exit(100);
171 close(FailFd);
172
173 // Timestamp
174 struct utimbuf UBuf;
175 UBuf.actime = FailTime;
176 UBuf.modtime = FailTime;
177 utime(FailFile.c_str(),&UBuf);
178
179 _exit(100);
180}
181 /*}}}*/
da6ee469
JF
182// HttpMethod::Configuration - Handle a configuration message /*{{{*/
183// ---------------------------------------------------------------------
184/* We stash the desired pipeline depth */
185bool HttpMethod::Configuration(string Message)
186{
187 if (pkgAcqMethod::Configuration(Message) == false)
188 return false;
189
0e5943eb 190 AllowRedirect = _config->FindB("Acquire::http::AllowRedirect",true);
da6ee469
JF
191 TimeOut = _config->FindI("Acquire::http::Timeout",TimeOut);
192 PipelineDepth = _config->FindI("Acquire::http::Pipeline-Depth",
193 PipelineDepth);
194 Debug = _config->FindB("Debug::Acquire::http",false);
195
196 return true;
197}
198 /*}}}*/
199// HttpMethod::Loop - Main loop /*{{{*/
200// ---------------------------------------------------------------------
201/* */
202int HttpMethod::Loop()
203{
0e5943eb
JF
204 typedef vector<string> StringVector;
205 typedef vector<string>::iterator StringVectorIterator;
206 map<string, StringVector> Redirected;
207
da6ee469
JF
208 signal(SIGTERM,SigTerm);
209 signal(SIGINT,SigTerm);
210
788c5b99 211 std::set<std::string> cached;
da6ee469
JF
212
213 int FailCounter = 0;
214 while (1)
215 {
216 // We have no commands, wait for some to arrive
217 if (Queue == 0)
218 {
219 if (WaitFd(STDIN_FILENO) == false)
220 return 0;
221 }
222
223 /* Run messages, we can accept 0 (no message) if we didn't
224 do a WaitFd above.. Otherwise the FD is closed. */
225 int Result = Run(true);
226 if (Result != -1 && (Result != 0 || Queue == 0))
227 return 100;
228
229 if (Queue == 0)
230 continue;
0e5943eb 231
253ba34a
JF
232 CFStringEncoding se = kCFStringEncodingUTF8;
233
b7a89731
JF
234 char *url = strdup(Queue->Uri.c_str());
235 url:
ca652fea 236 URI uri = std::string(url);
fe23a696
JF
237 std::string hs = uri.Host;
238
788c5b99
JF
239 if (cached.find(hs) != cached.end()) {
240 _error->Error("Cached Failure");
241 Fail(true);
242 free(url);
243 FailCounter = 0;
244 continue;
245 }
246
fe23a696
JF
247 std::string urs = uri;
248
bdccc0e5
JF
249 for (;;) {
250 size_t bad = urs.find_first_of("+");
251 if (bad == std::string::npos)
252 break;
253 // XXX: generalize
254 urs = urs.substr(0, bad) + "%2b" + urs.substr(bad + 1);
255 }
256
fe23a696 257 CFStringRef sr = CFStringCreateWithCString(kCFAllocatorDefault, urs.c_str(), se);
253ba34a
JF
258 CFURLRef ur = CFURLCreateWithString(kCFAllocatorDefault, sr, NULL);
259 CFRelease(sr);
260 CFHTTPMessageRef hm = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), ur, kCFHTTPVersion1_1);
261 CFRelease(ur);
262
263 struct stat SBuf;
264 if (stat(Queue->DestFile.c_str(), &SBuf) >= 0 && SBuf.st_size > 0) {
265 sr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("bytes=%li-"), (long) SBuf.st_size - 1);
266 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Range"), sr);
267 CFRelease(sr);
268
269 sr = CFStringCreateWithCString(kCFAllocatorDefault, TimeRFC1123(SBuf.st_mtime).c_str(), se);
270 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("If-Range"), sr);
271 CFRelease(sr);
78741bff
JF
272
273 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Cache-Control"), CFSTR("no-cache"));
253ba34a 274 } else if (Queue->LastModified != 0) {
fc9b22c7 275 sr = CFStringCreateWithCString(kCFAllocatorDefault, TimeRFC1123(Queue->LastModified).c_str(), se);
253ba34a
JF
276 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("If-Modified-Since"), sr);
277 CFRelease(sr);
78741bff
JF
278
279 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Cache-Control"), CFSTR("no-cache"));
280 } else
281 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Cache-Control"), CFSTR("max-age=0"));
253ba34a 282
032f992c
JF
283 if (Firmware_ != NULL)
284 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("X-Firmware"), Firmware_);
285
ca652fea
JF
286 sr = CFStringCreateWithCString(kCFAllocatorDefault, Machine_, se);
287 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("X-Machine"), sr);
288 CFRelease(sr);
289
4db93271
JF
290 if (UniqueID_ != NULL)
291 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("X-Unique-ID"), UniqueID_);
ca652fea 292
acdafb44 293 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("User-Agent"), CFSTR("Telesphoreo APT-HTTP/1.0.592"));
fe23a696 294
253ba34a
JF
295 CFReadStreamRef rs = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, hm);
296 CFRelease(hm);
297
788c5b99
JF
298#define _kCFStreamPropertyReadTimeout CFSTR("_kCFStreamPropertyReadTimeout")
299#define _kCFStreamPropertyWriteTimeout CFSTR("_kCFStreamPropertyWriteTimeout")
300#define _kCFStreamPropertySocketImmediateBufferTimeOut CFSTR("_kCFStreamPropertySocketImmediateBufferTimeOut")
301
302 /*SInt32 to(TimeOut);
303 CFNumberRef nm(CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &to));*/
304 double to(TimeOut);
305 CFNumberRef nm(CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &to));
306
307 CFReadStreamSetProperty(rs, _kCFStreamPropertyReadTimeout, nm);
308 CFReadStreamSetProperty(rs, _kCFStreamPropertyWriteTimeout, nm);
309 CFReadStreamSetProperty(rs, _kCFStreamPropertySocketImmediateBufferTimeOut, nm);
310 CFRelease(nm);
311
985ed269
JF
312 CFDictionaryRef dr = SCDynamicStoreCopyProxies(NULL);
313 CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPProxy, dr);
314 CFRelease(dr);
315
b7a89731 316 //CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue);
253ba34a
JF
317 CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
318
910f26ec 319 FetchResult Res;
b7a89731
JF
320 CFIndex rd;
321 UInt32 sc;
910f26ec
JF
322
323 uint8_t data[10240];
324 size_t offset = 0;
325
fe23a696 326 Status("Connecting to %s", hs.c_str());
253ba34a 327
788c5b99
JF
328 switch (CFReadStreamOpen(rs, to)) {
329 case -1:
330 CfrsError("Open", rs);
331 goto fail;
332
333 case 0:
334 _error->Error("Host Unreachable");
335 cached.insert(hs);
336 goto fail;
337
338 case 1:
339 /* success */
340 break;
341
342 fail:
343 Fail(true);
910f26ec 344 goto done;
da6ee469
JF
345 }
346
b7a89731 347 rd = CFReadStreamRead(rs, data, sizeof(data));
da6ee469 348
910f26ec 349 if (rd == -1) {
fe23a696 350 CfrsError(uri.Host.c_str(), rs);
788c5b99 351 cached.insert(hs);
910f26ec
JF
352 Fail(true);
353 goto done;
354 }
355
da6ee469 356 Res.Filename = Queue->DestFile;
da6ee469 357
253ba34a 358 hm = (CFHTTPMessageRef) CFReadStreamCopyProperty(rs, kCFStreamPropertyHTTPResponseHeader);
b7a89731
JF
359 sc = CFHTTPMessageGetResponseStatusCode(hm);
360
415938a5 361 if (sc == 301 || sc == 302) {
b7a89731
JF
362 sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Location"));
363 if (sr == NULL) {
364 Fail();
365 goto done_;
366 } else {
367 size_t ln = CFStringGetLength(sr) + 1;
368 free(url);
369 url = static_cast<char *>(malloc(ln));
370
371 if (!CFStringGetCString(sr, url, ln, se)) {
372 Fail();
373 goto done_;
374 }
375
376 CFRelease(sr);
377 goto url;
378 }
379 }
253ba34a 380
253ba34a
JF
381 sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Content-Range"));
382 if (sr != NULL) {
383 size_t ln = CFStringGetLength(sr) + 1;
384 char cr[ln];
385
386 if (!CFStringGetCString(sr, cr, ln, se)) {
387 Fail();
985ed269 388 goto done_;
253ba34a
JF
389 }
390
391 CFRelease(sr);
392
393 if (sscanf(cr, "bytes %lu-%*u/%lu", &offset, &Res.Size) != 2) {
394 _error->Error(_("The HTTP server sent an invalid Content-Range header"));
395 Fail();
985ed269 396 goto done_;
253ba34a
JF
397 }
398
399 if (offset > Res.Size) {
400 _error->Error(_("This HTTP server has broken range support"));
401 Fail();
985ed269 402 goto done_;
253ba34a
JF
403 }
404 } else {
405 sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Content-Length"));
406 if (sr != NULL) {
407 Res.Size = CFStringGetIntValue(sr);
408 CFRelease(sr);
409 }
410 }
da6ee469 411
253ba34a
JF
412 time(&Res.LastModified);
413
414 sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Last-Modified"));
415 if (sr != NULL) {
416 size_t ln = CFStringGetLength(sr) + 1;
417 char cr[ln];
418
419 if (!CFStringGetCString(sr, cr, ln, se)) {
420 Fail();
985ed269 421 goto done_;
253ba34a
JF
422 }
423
424 CFRelease(sr);
425
426 if (!StrToTime(cr, Res.LastModified)) {
427 _error->Error(_("Unknown date format"));
428 Fail();
985ed269 429 goto done_;
253ba34a
JF
430 }
431 }
432
788c5b99
JF
433 if (sc < 200 || sc >= 300 && sc != 304) {
434 sr = CFHTTPMessageCopyResponseStatusLine(hm);
435
436 size_t ln = CFStringGetLength(sr) + 1;
437 char cr[ln];
438
439 if (!CFStringGetCString(sr, cr, ln, se)) {
440 Fail();
441 goto done;
442 }
443
444 CFRelease(sr);
445
446 _error->Error("%s", cr);
447
448 Fail();
449 goto done_;
450 }
451
253ba34a
JF
452 CFRelease(hm);
453
454 if (sc == 304) {
455 unlink(Queue->DestFile.c_str());
456 Res.IMSHit = true;
457 Res.LastModified = Queue->LastModified;
458 URIDone(Res);
788c5b99 459 } else {
253ba34a
JF
460 Hashes hash;
461
462 File = new FileFd(Queue->DestFile, FileFd::WriteAny);
463 if (_error->PendingError() == true) {
464 delete File;
465 File = NULL;
466 Fail();
467 goto done;
468 }
469
470 FailFile = Queue->DestFile;
471 FailFile.c_str(); // Make sure we dont do a malloc in the signal handler
472 FailFd = File->Fd();
473 FailTime = Res.LastModified;
474
475 Res.ResumePoint = offset;
476 ftruncate(File->Fd(), offset);
477
478 if (offset != 0) {
479 lseek(File->Fd(), 0, SEEK_SET);
480 if (!hash.AddFD(File->Fd(), offset)) {
481 _error->Errno("read", _("Problem hashing file"));
482 delete File;
483 File = NULL;
484 Fail();
485 goto done;
486 }
487 }
488
489 lseek(File->Fd(), 0, SEEK_END);
490
491 URIStart(Res);
492
910f26ec 493 read: if (rd == -1) {
fe23a696 494 CfrsError("rd", rs);
253ba34a 495 Fail(true);
910f26ec 496 } else if (rd == 0) {
da6ee469
JF
497 if (Res.Size == 0)
498 Res.Size = File->Size();
253ba34a 499
da6ee469
JF
500 struct utimbuf UBuf;
501 time(&UBuf.actime);
253ba34a
JF
502 UBuf.actime = Res.LastModified;
503 UBuf.modtime = Res.LastModified;
504 utime(Queue->DestFile.c_str(), &UBuf);
da6ee469 505
253ba34a 506 Res.TakeHashes(hash);
da6ee469 507 URIDone(Res);
253ba34a
JF
508 } else {
509 hash.Add(data, rd);
da6ee469 510
253ba34a
JF
511 uint8_t *dt = data;
512 while (rd != 0) {
513 int sz = write(File->Fd(), dt, rd);
da6ee469 514
253ba34a
JF
515 if (sz == -1) {
516 delete File;
517 File = NULL;
518 Fail();
519 goto done;
520 }
521
522 dt += sz;
523 rd -= sz;
524 }
525
526 rd = CFReadStreamRead(rs, data, sizeof(data));
527 goto read;
528 }
da6ee469 529 }
253ba34a 530
985ed269
JF
531 goto done;
532 done_:
533 CFRelease(hm);
253ba34a 534 done:
985ed269 535 CFReadStreamClose(rs);
253ba34a 536 CFRelease(rs);
b7a89731 537 free(url);
253ba34a 538
da6ee469
JF
539 FailCounter = 0;
540 }
541
542 return 0;
543}
544 /*}}}*/
545
4c8eb365 546int main()
da6ee469
JF
547{
548 setlocale(LC_ALL, "");
0e5943eb
JF
549 // ignore SIGPIPE, this can happen on write() if the socket
550 // closes the connection (this is dealt with via ServerDie())
551 signal(SIGPIPE, SIG_IGN);
da6ee469
JF
552
553 HttpMethod Mth;
ca652fea
JF
554
555 size_t size;
556 sysctlbyname("hw.machine", NULL, &size, NULL, 0);
557 char *machine = new char[size];
558 sysctlbyname("hw.machine", machine, &size, NULL, 0);
559 Machine_ = machine;
560
032f992c
JF
561 const char *path = "/System/Library/CoreServices/SystemVersion.plist";
562 CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (uint8_t *) path, strlen(path), false);
563
564 CFPropertyListRef plist; {
565 CFReadStreamRef stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
566 CFReadStreamOpen(stream);
567 plist = CFPropertyListCreateFromStream(kCFAllocatorDefault, stream, 0, kCFPropertyListImmutable, NULL, NULL);
568 CFReadStreamClose(stream);
569 }
570
571 CFRelease(url);
572
573 if (plist != NULL) {
574 Firmware_ = (CFStringRef) CFRetain(CFDictionaryGetValue((CFDictionaryRef) plist, CFSTR("ProductVersion")));
575 CFRelease(plist);
576 }
577
acc2d4c9
JF
578 if (UniqueID_ == NULL)
579 if (void *libMobileGestalt = dlopen("/usr/lib/libMobileGestalt.dylib", RTLD_GLOBAL | RTLD_LAZY))
580 if (CFStringRef (*$MGCopyAnswer)(CFStringRef) = (CFStringRef (*)(CFStringRef)) dlsym(libMobileGestalt, "MGCopyAnswer"))
581 UniqueID_ = $MGCopyAnswer(CFSTR("UniqueDeviceID"));
582
583 if (UniqueID_ == NULL)
4db93271
JF
584 if (void *lockdown = lockdown_connect()) {
585 UniqueID_ = lockdown_copy_value(lockdown, NULL, kLockdownUniqueDeviceIDKey);
586 lockdown_disconnect(lockdown);
587 }
0e5943eb 588
da6ee469
JF
589 return Mth.Loop();
590}