]> git.saurik.com Git - apt-legacy.git/blame_incremental - methods/http.cc
Fix compilation of http when embedding into Cydia.
[apt-legacy.git] / methods / http.cc
... / ...
CommitLineData
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
3// $Id: http.cc,v 1.59 2004/05/08 19:42:35 mdz Exp $
4/* ######################################################################
5
6 HTTP Acquire Method - This is the HTTP aquire method for APT.
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>
32#include <apt-pkg/netrc.h>
33
34#include <sys/sysctl.h>
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>
44#include <map>
45#include <set>
46#include <apti18n.h>
47
48
49// Internet stuff
50#include <netdb.h>
51#include <arpa/inet.h>
52
53#include <dlfcn.h>
54#include <lockdown.h>
55#include <CoreFoundation/CoreFoundation.h>
56#include <CFNetwork/CFNetwork.h>
57#include <SystemConfiguration/SystemConfiguration.h>
58
59#include "config.h"
60#include "http.h"
61 /*}}}*/
62using namespace std;
63
64static CFStringRef Firmware_;
65static const char *Machine_;
66static CFStringRef UniqueID_;
67
68void CfrsError(const char *name, CFReadStreamRef rs) {
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) {
77 _error->Error("NetDB: %s %s", name, gai_strerror(se.error));
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 {
105 _error->Error("Domain #%ld: %ld", se.domain, se.error);
106 }
107}
108
109string HttpMethod::FailFile;
110int HttpMethod::FailFd = -1;
111time_t HttpMethod::FailTime = 0;
112unsigned long PipelineDepth = 10;
113unsigned long TimeOut = 120;
114bool AllowRedirect = false;
115bool Debug = false;
116URI Proxy;
117
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
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 /*}}}*/
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
190 AllowRedirect = _config->FindB("Acquire::http::AllowRedirect",true);
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{
204 typedef vector<string> StringVector;
205 typedef vector<string>::iterator StringVectorIterator;
206 map<string, StringVector> Redirected;
207
208 signal(SIGTERM,SigTerm);
209 signal(SIGINT,SigTerm);
210
211 std::set<std::string> cached;
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;
231
232 CFStringEncoding se = kCFStringEncodingUTF8;
233
234 char *url = strdup(Queue->Uri.c_str());
235 url:
236 URI uri = std::string(url);
237 std::string hs = uri.Host;
238
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
247 std::string urs = uri;
248
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
257 CFStringRef sr = CFStringCreateWithCString(kCFAllocatorDefault, urs.c_str(), se);
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);
272
273 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Cache-Control"), CFSTR("no-cache"));
274 } else if (Queue->LastModified != 0) {
275 sr = CFStringCreateWithCString(kCFAllocatorDefault, TimeRFC1123(Queue->LastModified).c_str(), se);
276 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("If-Modified-Since"), sr);
277 CFRelease(sr);
278
279 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Cache-Control"), CFSTR("no-cache"));
280 } else
281 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Cache-Control"), CFSTR("max-age=0"));
282
283 if (Firmware_ != NULL)
284 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("X-Firmware"), Firmware_);
285
286 sr = CFStringCreateWithCString(kCFAllocatorDefault, Machine_, se);
287 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("X-Machine"), sr);
288 CFRelease(sr);
289
290 if (UniqueID_ != NULL)
291 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("X-Unique-ID"), UniqueID_);
292
293 CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("User-Agent"), CFSTR("Telesphoreo APT-HTTP/1.0.592"));
294
295 CFReadStreamRef rs = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, hm);
296 CFRelease(hm);
297
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
312 CFDictionaryRef dr = SCDynamicStoreCopyProxies(NULL);
313 CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPProxy, dr);
314 CFRelease(dr);
315
316 //CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue);
317 CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
318
319 FetchResult Res;
320 CFIndex rd;
321 UInt32 sc;
322
323 uint8_t data[10240];
324 size_t offset = 0;
325
326 Status("Connecting to %s", hs.c_str());
327
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);
344 goto done;
345 }
346
347 rd = CFReadStreamRead(rs, data, sizeof(data));
348
349 if (rd == -1) {
350 CfrsError(uri.Host.c_str(), rs);
351 cached.insert(hs);
352 Fail(true);
353 goto done;
354 }
355
356 Res.Filename = Queue->DestFile;
357
358 hm = (CFHTTPMessageRef) CFReadStreamCopyProperty(rs, kCFStreamPropertyHTTPResponseHeader);
359 sc = CFHTTPMessageGetResponseStatusCode(hm);
360
361 if (sc == 301 || sc == 302) {
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 }
380
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();
388 goto done_;
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();
396 goto done_;
397 }
398
399 if (offset > Res.Size) {
400 _error->Error(_("This HTTP server has broken range support"));
401 Fail();
402 goto done_;
403 }
404 } else {
405 sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Content-Length"));
406 if (sr != NULL) {
407 Res.Size = CFStringGetIntValue(sr);
408 CFRelease(sr);
409 }
410 }
411
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();
421 goto done_;
422 }
423
424 CFRelease(sr);
425
426 if (!StrToTime(cr, Res.LastModified)) {
427 _error->Error(_("Unknown date format"));
428 Fail();
429 goto done_;
430 }
431 }
432
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
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);
459 } else {
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
493 read: if (rd == -1) {
494 CfrsError("rd", rs);
495 Fail(true);
496 } else if (rd == 0) {
497 if (Res.Size == 0)
498 Res.Size = File->Size();
499
500 struct utimbuf UBuf;
501 time(&UBuf.actime);
502 UBuf.actime = Res.LastModified;
503 UBuf.modtime = Res.LastModified;
504 utime(Queue->DestFile.c_str(), &UBuf);
505
506 Res.TakeHashes(hash);
507 URIDone(Res);
508 } else {
509 hash.Add(data, rd);
510
511 uint8_t *dt = data;
512 while (rd != 0) {
513 int sz = write(File->Fd(), dt, rd);
514
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 }
529 }
530
531 goto done;
532 done_:
533 CFRelease(hm);
534 done:
535 CFReadStreamClose(rs);
536 CFRelease(rs);
537 free(url);
538
539 FailCounter = 0;
540 }
541
542 return 0;
543}
544 /*}}}*/
545
546int main()
547{
548 setlocale(LC_ALL, "");
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);
552
553 HttpMethod Mth;
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
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
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)
584 if (void *lockdown = lockdown_connect()) {
585 UniqueID_ = lockdown_copy_value(lockdown, NULL, kLockdownUniqueDeviceIDKey);
586 lockdown_disconnect(lockdown);
587 }
588
589 return Mth.Loop();
590}