]>
Commit | Line | Data |
---|---|---|
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 | /*}}}*/ | |
62 | using namespace std; | |
63 | ||
64 | static CFStringRef Firmware_; | |
65 | static const char *Machine_; | |
66 | static CFStringRef UniqueID_; | |
67 | ||
68 | void 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 | ||
109 | string HttpMethod::FailFile; | |
110 | int HttpMethod::FailFd = -1; | |
111 | time_t HttpMethod::FailTime = 0; | |
112 | unsigned long PipelineDepth = 10; | |
113 | unsigned long TimeOut = 120; | |
114 | bool AllowRedirect = false; | |
115 | bool Debug = false; | |
116 | URI Proxy; | |
117 | ||
118 | static const CFOptionFlags kNetworkEvents = | |
119 | kCFStreamEventOpenCompleted | | |
120 | kCFStreamEventHasBytesAvailable | | |
121 | kCFStreamEventEndEncountered | | |
122 | kCFStreamEventErrorOccurred | | |
123 | 0; | |
124 | ||
125 | static 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 */ | |
144 | int 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 */ | |
167 | void 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 */ | |
185 | bool 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 | /* */ | |
202 | int 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 | ||
546 | int 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 | } |