]> git.saurik.com Git - apt-legacy.git/blobdiff - methods/http.cc
Ported to APT 0.7.25.3.
[apt-legacy.git] / methods / http.cc
index 012d4506b021b0f9fd030426165cd2cb0de4ec8f..08504218ab089f987793eb35f1988772038dda7e 100644 (file)
@@ -1,13 +1,9 @@
-extern "C" {
-    #include <mach-o/nlist.h>
-}
-
 // -*- mode: cpp; mode: fold -*-
 // Description                                                         /*{{{*/
 // $Id: http.cc,v 1.59 2004/05/08 19:42:35 mdz Exp $
 /* ######################################################################
 
-   HTTP Aquire Method - This is the HTTP aquire method for APT.
+   HTTP Acquire Method - This is the HTTP aquire method for APT.
    
    It uses HTTP/1.1 and many of the fancy options there-in, such as
    pipelining, range, if-range and so on. 
@@ -33,7 +29,9 @@ extern "C" {
 #include <apt-pkg/acquire-method.h>
 #include <apt-pkg/error.h>
 #include <apt-pkg/hashes.h>
+#include <apt-pkg/netrc.h>
 
+#include <sys/sysctl.h>
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <utime.h>
@@ -43,27 +41,80 @@ extern "C" {
 #include <errno.h>
 #include <string.h>
 #include <iostream>
+#include <map>
+#include <set>
 #include <apti18n.h>
 
+
 // Internet stuff
 #include <netdb.h>
+#include <arpa/inet.h>
 
+#include <lockdown.h>
 #include <CoreFoundation/CoreFoundation.h>
 #include <CoreServices/CoreServices.h>
+#include <SystemConfiguration/SystemConfiguration.h>
 
+#include "config.h"
 #include "connect.h"
 #include "rfc2553emu.h"
 #include "http.h"
-
                                                                        /*}}}*/
 using namespace std;
 
+CFStringRef Firmware_;
+const char *Machine_;
+CFStringRef UniqueID_;
+
+void CfrsError(const char *name, CFReadStreamRef rs) {
+    CFStreamError se = CFReadStreamGetError(rs);
+
+    if (se.domain == kCFStreamErrorDomainCustom) {
+    } else if (se.domain == kCFStreamErrorDomainPOSIX) {
+        _error->Error("POSIX: %s", strerror(se.error));
+    } else if (se.domain == kCFStreamErrorDomainMacOSStatus) {
+        _error->Error("MacOSStatus: %ld", se.error);
+    } else if (se.domain == kCFStreamErrorDomainNetDB) {
+        _error->Error("NetDB: %s %s", name, gai_strerror(se.error));
+    } else if (se.domain == kCFStreamErrorDomainMach) {
+        _error->Error("Mach: %ld", se.error);
+    } else if (se.domain == kCFStreamErrorDomainHTTP) {
+        switch (se.error) {
+            case kCFStreamErrorHTTPParseFailure:
+                _error->Error("Parse failure");
+            break;
+
+            case kCFStreamErrorHTTPRedirectionLoop:
+                _error->Error("Redirection loop");
+            break;
+
+            case kCFStreamErrorHTTPBadURL:
+                _error->Error("Bad URL");
+            break;
+
+            default:
+                _error->Error("Unknown HTTP error: %ld", se.error);
+            break;
+        }
+    } else if (se.domain == kCFStreamErrorDomainSOCKS) {
+        _error->Error("SOCKS: %ld", se.error);
+    } else if (se.domain == kCFStreamErrorDomainSystemConfiguration) {
+        _error->Error("SystemConfiguration: %ld", se.error);
+    } else if (se.domain == kCFStreamErrorDomainSSL) {
+        _error->Error("SSL: %ld", se.error);
+    } else {
+        _error->Error("Domain #%ld: %ld", se.domain, se.error);
+    }
+}
+
 string HttpMethod::FailFile;
 int HttpMethod::FailFd = -1;
 time_t HttpMethod::FailTime = 0;
 unsigned long PipelineDepth = 10;
 unsigned long TimeOut = 120;
+bool AllowRedirect = false;
 bool Debug = false;
+URI Proxy;
 
 unsigned long CircleBuf::BwReadLimit=0;
 unsigned long CircleBuf::BwTickReadData=0;
@@ -314,22 +365,27 @@ bool ServerState::Open()
    Persistent = true;
    
    // Determine the proxy setting
-   if (getenv("http_proxy") == 0)
+   string SpecificProxy = _config->Find("Acquire::http::Proxy::" + ServerName.Host);
+   if (!SpecificProxy.empty())
    {
-      string DefProxy = _config->Find("Acquire::http::Proxy");
-      string SpecificProxy = _config->Find("Acquire::http::Proxy::" + ServerName.Host);
-      if (SpecificProxy.empty() == false)
-      {
-        if (SpecificProxy == "DIRECT")
-           Proxy = "";
-        else
-           Proxy = SpecificProxy;
-      }   
-      else
-        Proxy = DefProxy;
+          if (SpecificProxy == "DIRECT")
+                  Proxy = "";
+          else
+                  Proxy = SpecificProxy;
    }
    else
-      Proxy = getenv("http_proxy");
+   {
+          string DefProxy = _config->Find("Acquire::http::Proxy");
+          if (!DefProxy.empty())
+          {
+                  Proxy = DefProxy;
+          }
+          else
+          {
+                  char* result = getenv("http_proxy");
+                  Proxy = result ? result : "";
+          }
+   }
    
    // Parse no_proxy, a , separated list of domains
    if (getenv("no_proxy") != 0)
@@ -373,8 +429,8 @@ bool ServerState::Close()
                                                                        /*}}}*/
 // ServerState::RunHeaders - Get the headers before the data           /*{{{*/
 // ---------------------------------------------------------------------
-/* Returns 0 if things are OK, 1 if an IO error occursed and 2 if a header
-   parse error occured */
+/* Returns 0 if things are OK, 1 if an IO error occurred and 2 if a header
+   parse error occurred */
 int ServerState::RunHeaders()
 {
    State = Header;
@@ -550,7 +606,7 @@ bool ServerState::HeaderLine(string Line)
       // Evil servers return no version
       if (Line[4] == '/')
       {
-        if (sscanf(Line.c_str(),"HTTP/%u.%u %u %[^\n]",&Major,&Minor,
+        if (sscanf(Line.c_str(),"HTTP/%u.%u %u%[^\n]",&Major,&Minor,
                    &Result,Code) != 4)
            return _error->Error(_("The HTTP server sent an invalid reply header"));
       }
@@ -558,7 +614,7 @@ bool ServerState::HeaderLine(string Line)
       {
         Major = 0;
         Minor = 9;
-        if (sscanf(Line.c_str(),"HTTP %u %[^\n]",&Result,Code) != 2)
+        if (sscanf(Line.c_str(),"HTTP %u%[^\n]",&Result,Code) != 2)
            return _error->Error(_("The HTTP server sent an invalid reply header"));
       }
 
@@ -633,10 +689,61 @@ bool ServerState::HeaderLine(string Line)
       return true;
    }
 
+   if (stringcasecmp(Tag,"Location:") == 0)
+   {
+      Location = Val;
+      return true;
+   }
+
    return true;
 }
                                                                        /*}}}*/
 
+static const CFOptionFlags kNetworkEvents =
+    kCFStreamEventOpenCompleted |
+    kCFStreamEventHasBytesAvailable |
+    kCFStreamEventEndEncountered |
+    kCFStreamEventErrorOccurred |
+0;
+
+static void CFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType event, void *arg) {
+    switch (event) {
+        case kCFStreamEventOpenCompleted:
+        break;
+
+        case kCFStreamEventHasBytesAvailable:
+        case kCFStreamEventEndEncountered:
+            *reinterpret_cast<int *>(arg) = 1;
+            CFRunLoopStop(CFRunLoopGetCurrent());
+        break;
+
+        case kCFStreamEventErrorOccurred:
+            *reinterpret_cast<int *>(arg) = -1;
+            CFRunLoopStop(CFRunLoopGetCurrent());
+        break;
+    }
+}
+
+/* http://lists.apple.com/archives/Macnetworkprog/2006/Apr/msg00014.html */
+int CFReadStreamOpen(CFReadStreamRef stream, double timeout) {
+    CFStreamClientContext context;
+    int value(0);
+
+    memset(&context, 0, sizeof(context));
+    context.info = &value;
+
+    if (CFReadStreamSetClient(stream, kNetworkEvents, CFReadStreamCallback, &context)) {
+        CFReadStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+        if (CFReadStreamOpen(stream))
+            CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, false);
+        else
+            value = -1;
+        CFReadStreamSetClient(stream, kCFStreamEventNone, NULL, NULL);
+    }
+
+    return value;
+}
+
 // HttpMethod::SendReq - Send the HTTP request                         /*{{{*/
 // ---------------------------------------------------------------------
 /* This places the http request in the outbound buffer */
@@ -716,11 +823,14 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out)
       Req += string("Proxy-Authorization: Basic ") + 
           Base64Encode(Proxy.User + ":" + Proxy.Password) + "\r\n";
 
+   maybe_add_auth (Uri, _config->FindFile("Dir::Etc::netrc"));
    if (Uri.User.empty() == false || Uri.Password.empty() == false)
+   {
       Req += string("Authorization: Basic ") + 
           Base64Encode(Uri.User + ":" + Uri.Password) + "\r\n";
-   
-   Req += "User-Agent: Debian APT-HTTP/1.3\r\n\r\n";
+   }
+   Req += "User-Agent: " + _config->Find("Acquire::http::User-Agent",
+               "Debian APT-HTTP/1.3 ("VERSION")") + "\r\n\r\n";
    
    if (Debug == true)
       cerr << Req << endl;
@@ -905,7 +1015,9 @@ bool HttpMethod::ServerDie(ServerState *Srv)
      1 - IMS hit
      3 - Unrecoverable error 
      4 - Error with error content page
-     5 - Unrecoverable non-server error (close the connection) */
+     5 - Unrecoverable non-server error (close the connection) 
+     6 - Try again with a new or changed URI
+ */
 int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv)
 {
    // Not Modified
@@ -917,6 +1029,27 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv)
       return 1;
    }
    
+   /* Redirect
+    *
+    * Note that it is only OK for us to treat all redirection the same
+    * because we *always* use GET, not other HTTP methods.  There are
+    * three redirection codes for which it is not appropriate that we
+    * redirect.  Pass on those codes so the error handling kicks in.
+    */
+   if (AllowRedirect
+       && (Srv->Result > 300 && Srv->Result < 400)
+       && (Srv->Result != 300       // Multiple Choices
+           && Srv->Result != 304    // Not Modified
+           && Srv->Result != 306))  // (Not part of HTTP/1.1, reserved)
+   {
+      if (!Srv->Location.empty())
+      {
+         NextURI = Srv->Location;
+         return 6;
+      }
+      /* else pass through for error message */
+   }
    /* We have a reply we dont handle. This should indicate a perm server
       failure */
    if (Srv->Result < 200 || Srv->Result >= 300)
@@ -946,7 +1079,8 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv)
    if (Srv->StartPos >= 0)
    {
       Res.ResumePoint = Srv->StartPos;
-      ftruncate(File->Fd(),Srv->StartPos);
+      if (ftruncate(File->Fd(),Srv->StartPos) < 0)
+        _error->Errno("ftruncate", _("Failed to truncate file"));
    }
       
    // Set the start point
@@ -1001,7 +1135,6 @@ bool HttpMethod::Fetch(FetchItem *)
 
    // Queue the requests
    int Depth = -1;
-   bool Tail = false;
    for (FetchItem *I = Queue; I != 0 && Depth < (signed)PipelineDepth; 
        I = I->Next, Depth++)
    {
@@ -1013,8 +1146,6 @@ bool HttpMethod::Fetch(FetchItem *)
       if (Server->Comp(I->Uri) == false)
         break;
       if (QueueBack == I)
-        Tail = true;
-      if (Tail == true)
       {
         QueueBack = I->Next;
         SendReq(I,Server->Out);
@@ -1033,6 +1164,7 @@ bool HttpMethod::Configuration(string Message)
    if (pkgAcqMethod::Configuration(Message) == false)
       return false;
    
+   AllowRedirect = _config->FindB("Acquire::http::AllowRedirect",true);
    TimeOut = _config->FindI("Acquire::http::Timeout",TimeOut);
    PipelineDepth = _config->FindI("Acquire::http::Pipeline-Depth",
                                  PipelineDepth);
@@ -1046,10 +1178,16 @@ bool HttpMethod::Configuration(string Message)
 /* */
 int HttpMethod::Loop()
 {
+   typedef vector<string> StringVector;
+   typedef vector<string>::iterator StringVectorIterator;
+   map<string, StringVector> Redirected;
+
    signal(SIGTERM,SigTerm);
    signal(SIGINT,SigTerm);
    
    Server = 0;
+
+   std::set<std::string> cached;
    
    int FailCounter = 0;
    while (1)
@@ -1072,7 +1210,30 @@ int HttpMethod::Loop()
 
       CFStringEncoding se = kCFStringEncodingUTF8;
 
-      CFStringRef sr = CFStringCreateWithCString(kCFAllocatorDefault, Queue->Uri.c_str(), se);
+      char *url = strdup(Queue->Uri.c_str());
+    url:
+      URI uri = std::string(url);
+      std::string hs = uri.Host;
+
+      if (cached.find(hs) != cached.end()) {
+         _error->Error("Cached Failure");
+         Fail(true);
+         free(url);
+         FailCounter = 0;
+         continue;
+      }
+
+      std::string urs = uri;
+
+      for (;;) {
+         size_t bad = urs.find_first_of("+");
+         if (bad == std::string::npos)
+            break;
+         // XXX: generalize
+         urs = urs.substr(0, bad) + "%2b" + urs.substr(bad + 1);
+      }
+
+      CFStringRef sr = CFStringCreateWithCString(kCFAllocatorDefault, urs.c_str(), se);
       CFURLRef ur = CFURLCreateWithString(kCFAllocatorDefault, sr, NULL);
       CFRelease(sr);
       CFHTTPMessageRef hm = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), ur, kCFHTTPVersion1_1);
@@ -1087,38 +1248,86 @@ int HttpMethod::Loop()
          sr = CFStringCreateWithCString(kCFAllocatorDefault, TimeRFC1123(SBuf.st_mtime).c_str(), se);
          CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("If-Range"), sr);
          CFRelease(sr);
+
+         CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Cache-Control"), CFSTR("no-cache"));
       } else if (Queue->LastModified != 0) {
-         sr = CFStringCreateWithCString(kCFAllocatorDefault, TimeRFC1123(SBuf.st_mtime).c_str(), se);
+         sr = CFStringCreateWithCString(kCFAllocatorDefault, TimeRFC1123(Queue->LastModified).c_str(), se);
          CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("If-Modified-Since"), sr);
          CFRelease(sr);
-      }
 
-      CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("User-Agent"), CFSTR("Telesphoreo APT-HTTP/1.0.98"));
+         CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Cache-Control"), CFSTR("no-cache"));
+      } else
+         CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("Cache-Control"), CFSTR("max-age=0"));
+
+      if (Firmware_ != NULL)
+         CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("X-Firmware"), Firmware_);
+
+      sr = CFStringCreateWithCString(kCFAllocatorDefault, Machine_, se);
+      CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("X-Machine"), sr);
+      CFRelease(sr);
+
+      if (UniqueID_ != NULL)
+         CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("X-Unique-ID"), UniqueID_);
+
+      CFHTTPMessageSetHeaderFieldValue(hm, CFSTR("User-Agent"), CFSTR("Telesphoreo APT-HTTP/1.0.592"));
+
       CFReadStreamRef rs = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, hm);
       CFRelease(hm);
 
-      CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue);
-      CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
+#define _kCFStreamPropertyReadTimeout CFSTR("_kCFStreamPropertyReadTimeout")
+#define _kCFStreamPropertyWriteTimeout CFSTR("_kCFStreamPropertyWriteTimeout")
+#define _kCFStreamPropertySocketImmediateBufferTimeOut CFSTR("_kCFStreamPropertySocketImmediateBufferTimeOut")
+
+      /*SInt32 to(TimeOut);
+      CFNumberRef nm(CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &to));*/
+      double to(TimeOut);
+      CFNumberRef nm(CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &to));
+
+      CFReadStreamSetProperty(rs, _kCFStreamPropertyReadTimeout, nm);
+      CFReadStreamSetProperty(rs, _kCFStreamPropertyWriteTimeout, nm);
+      CFReadStreamSetProperty(rs, _kCFStreamPropertySocketImmediateBufferTimeOut, nm);
+      CFRelease(nm);
 
-      URI uri = Queue->Uri;
+      CFDictionaryRef dr = SCDynamicStoreCopyProxies(NULL);
+      CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPProxy, dr);
+      CFRelease(dr);
+
+      //CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue);
+      CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
 
       FetchResult Res;
+      CFIndex rd;
+      UInt32 sc;
 
       uint8_t data[10240];
       size_t offset = 0;
 
-      Status("Connecting to %s", uri.Host.c_str());
+      Status("Connecting to %s", hs.c_str());
 
-      if (!CFReadStreamOpen(rs)) {
-         _error->Error("Unable to open stream");
-         Fail(true);
+      switch (CFReadStreamOpen(rs, to)) {
+         case -1:
+            CfrsError("Open", rs);
+         goto fail;
+
+         case 0:
+            _error->Error("Host Unreachable");
+            cached.insert(hs);
+         goto fail;
+
+         case 1:
+            /* success */
+         break;
+
+         fail:
+            Fail(true);
          goto done;
       }
 
-      CFIndex rd = CFReadStreamRead(rs, data, sizeof(data));
+      rd = CFReadStreamRead(rs, data, sizeof(data));
 
       if (rd == -1) {
-         _error->Error("Stream read failure");
+         CfrsError(uri.Host.c_str(), rs);
+         cached.insert(hs);
          Fail(true);
          goto done;
       }
@@ -1126,7 +1335,27 @@ int HttpMethod::Loop()
       Res.Filename = Queue->DestFile;
 
       hm = (CFHTTPMessageRef) CFReadStreamCopyProperty(rs, kCFStreamPropertyHTTPResponseHeader);
-      UInt32 sc = CFHTTPMessageGetResponseStatusCode(hm);
+      sc = CFHTTPMessageGetResponseStatusCode(hm);
+
+      if (sc == 301 || sc == 302) {
+         sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Location"));
+         if (sr == NULL) {
+            Fail();
+            goto done_;
+         } else {
+            size_t ln = CFStringGetLength(sr) + 1;
+            free(url);
+            url = static_cast<char *>(malloc(ln));
+
+            if (!CFStringGetCString(sr, url, ln, se)) {
+               Fail();
+               goto done_;
+            }
+
+            CFRelease(sr);
+            goto url;
+         }
+      }
 
       sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Content-Range"));
       if (sr != NULL) {
@@ -1135,7 +1364,7 @@ int HttpMethod::Loop()
 
          if (!CFStringGetCString(sr, cr, ln, se)) {
             Fail();
-            goto done;
+            goto done_;
          }
 
          CFRelease(sr);
@@ -1143,13 +1372,13 @@ int HttpMethod::Loop()
          if (sscanf(cr, "bytes %lu-%*u/%lu", &offset, &Res.Size) != 2) {
            _error->Error(_("The HTTP server sent an invalid Content-Range header"));
             Fail();
-            goto done;
+            goto done_;
          }
 
          if (offset > Res.Size) {
            _error->Error(_("This HTTP server has broken range support"));
             Fail();
-            goto done;
+            goto done_;
          }
       } else {
          sr = CFHTTPMessageCopyHeaderFieldValue(hm, CFSTR("Content-Length"));
@@ -1168,16 +1397,35 @@ int HttpMethod::Loop()
 
          if (!CFStringGetCString(sr, cr, ln, se)) {
             Fail();
-            goto done;
+            goto done_;
          }
 
          CFRelease(sr);
 
          if (!StrToTime(cr, Res.LastModified)) {
            _error->Error(_("Unknown date format"));
+            Fail();
+            goto done_;
+         }
+      }
+
+      if (sc < 200 || sc >= 300 && sc != 304) {
+         sr = CFHTTPMessageCopyResponseStatusLine(hm);
+
+         size_t ln = CFStringGetLength(sr) + 1;
+         char cr[ln];
+
+         if (!CFStringGetCString(sr, cr, ln, se)) {
             Fail();
             goto done;
          }
+
+         CFRelease(sr);
+
+         _error->Error("%s", cr);
+
+         Fail();
+         goto done_;
       }
 
       CFRelease(hm);
@@ -1187,9 +1435,7 @@ int HttpMethod::Loop()
          Res.IMSHit = true;
          Res.LastModified = Queue->LastModified;
         URIDone(Res);
-      } else if (sc < 200 || sc >= 300)
-        Fail();
-      else {
+      } else {
          Hashes hash;
 
          File = new FileFd(Queue->DestFile, FileFd::WriteAny);
@@ -1224,7 +1470,7 @@ int HttpMethod::Loop()
         URIStart(Res);
 
          read: if (rd == -1) {
-            _error->Error("Stream read failure");
+            CfrsError("rd", rs);
             Fail(true);
          } else if (rd == 0) {
            if (Res.Size == 0)
@@ -1261,8 +1507,13 @@ int HttpMethod::Loop()
          }
       }
 
+     goto done;
+    done_:
+      CFRelease(hm);
     done:
+      CFReadStreamClose(rs);
       CFRelease(rs);
+      free(url);
 
       FailCounter = 0;
    }
@@ -1273,17 +1524,41 @@ int HttpMethod::Loop()
 
 int main()
 {
-   struct nlist nl[2];
-   memset(nl, 0, sizeof(nl));
-   nl[0].n_un.n_name = "_useMDNSResponder";
-   nlist("/usr/lib/libc.dylib", nl);
-   if (nl[0].n_type != N_UNDF)
-       *(int *) nl[0].n_value = 0;
-
    setlocale(LC_ALL, "");
+   // ignore SIGPIPE, this can happen on write() if the socket
+   // closes the connection (this is dealt with via ServerDie())
+   signal(SIGPIPE, SIG_IGN);
 
    HttpMethod Mth;
-   
+
+    size_t size;
+    sysctlbyname("hw.machine", NULL, &size, NULL, 0);
+    char *machine = new char[size];
+    sysctlbyname("hw.machine", machine, &size, NULL, 0);
+    Machine_ = machine;
+
+    const char *path = "/System/Library/CoreServices/SystemVersion.plist";
+    CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (uint8_t *) path, strlen(path), false);
+
+    CFPropertyListRef plist; {
+        CFReadStreamRef stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
+        CFReadStreamOpen(stream);
+        plist = CFPropertyListCreateFromStream(kCFAllocatorDefault, stream, 0, kCFPropertyListImmutable, NULL, NULL);
+        CFReadStreamClose(stream);
+    }
+
+    CFRelease(url);
+
+    if (plist != NULL) {
+        Firmware_ = (CFStringRef) CFRetain(CFDictionaryGetValue((CFDictionaryRef) plist, CFSTR("ProductVersion")));
+        CFRelease(plist);
+    }
+
+    if (void *lockdown = lockdown_connect()) {
+        UniqueID_ = lockdown_copy_value(lockdown, NULL, kLockdownUniqueDeviceIDKey);
+        lockdown_disconnect(lockdown);
+    }
+
    return Mth.Loop();
 }