Browse Source

verify_contents

David Rose 16 years ago
parent
commit
df978c88b2

+ 1 - 1
direct/src/plugin/fileSpec.cxx

@@ -134,7 +134,7 @@ quick_verify(const string &package_dir) const {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: FileSpec::quick_verify
+//     Function: FileSpec::full_verify
 //       Access: Public
 //  Description: Performs a more thorough test to ensure the file has
 //               not been modified.  This test is less vulnerable to

+ 4 - 3
direct/src/plugin/load_plugin.cxx

@@ -123,8 +123,9 @@ static void unload_dso();
 //               path.
 ////////////////////////////////////////////////////////////////////
 bool
-load_plugin(const string &p3d_plugin_filename, const string &contents_filename,
-            const string &download_url, const string &platform,
+load_plugin(const string &p3d_plugin_filename, 
+            const string &contents_filename, const string &download_url, 
+            bool verify_contents, const string &platform,
             const string &log_directory, const string &log_basename) {
   string filename = p3d_plugin_filename;
   if (filename.empty()) {
@@ -295,7 +296,7 @@ load_plugin(const string &p3d_plugin_filename, const string &contents_filename,
   plugin_loaded = true;
 
   if (!P3D_initialize(P3D_API_VERSION, contents_filename.c_str(),
-                      download_url.c_str(), platform.c_str(),
+                      download_url.c_str(), verify_contents, platform.c_str(),
                       log_directory.c_str(), log_basename.c_str())) {
     // Oops, failure to initialize.
     cerr << "Failed to initialize plugin (wrong API version?)\n";

+ 3 - 2
direct/src/plugin/load_plugin.h

@@ -59,8 +59,9 @@ extern P3D_instance_handle_event_func *P3D_instance_handle_event;
 
 string get_plugin_basename();
 bool 
-load_plugin(const string &p3d_plugin_filename, const string &contents_filename,
-            const string &download_url, const string &platform,
+load_plugin(const string &p3d_plugin_filename, 
+            const string &contents_filename, const string &download_url,
+            bool verify_contents, const string &platform,
             const string &log_directory, const string &log_basename);
 void unload_plugin();
 bool is_plugin_loaded();

+ 14 - 0
direct/src/plugin/p3dHost.I

@@ -71,3 +71,17 @@ inline bool P3DHost::
 has_contents_file() const {
   return (_xcontents != NULL);
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DHost::encode_hexdigit
+//       Access: Private
+//  Description: Returns the hex digit corresponding to the
+//               indicated integer value.
+////////////////////////////////////////////////////////////////////
+inline char P3DHost::
+encode_hexdigit(int c) {
+  if (c >= 10) {
+    return c - 10 + 'a';
+  }
+  return c + '0';
+}

+ 94 - 4
direct/src/plugin/p3dHost.cxx

@@ -15,6 +15,7 @@
 #include "p3dHost.h"
 #include "p3dInstanceManager.h"
 #include "p3dPackage.h"
+#include "openssl/md5.h"
 
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DHost::Constructor
@@ -23,12 +24,9 @@
 //               P3DHost.
 ////////////////////////////////////////////////////////////////////
 P3DHost::
-P3DHost(P3DInstanceManager *inst_mgr, const string &host_url) :
+P3DHost(const string &host_url) :
   _host_url(host_url) 
 {
-  _host_dir = inst_mgr->get_root_dir();
-  _host_dir += "/host";  // TODO.
-
   // Ensure that the download URL ends with a slash.
   _host_url_prefix = _host_url;
   if (!_host_url_prefix.empty() && _host_url_prefix[_host_url_prefix.size() - 1] != '/') {
@@ -36,6 +34,8 @@ P3DHost(P3DInstanceManager *inst_mgr, const string &host_url) :
   }
 
   _xcontents = NULL;
+
+  fill_host_dir();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -56,6 +56,20 @@ P3DHost::
   _packages.clear();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DHost::read_contents_file
+//       Access: Public
+//  Description: Reads the contents.xml file in the standard
+//               filename, if possible.
+//
+//               Returns true on success, false on failure.
+////////////////////////////////////////////////////////////////////
+bool P3DHost::
+read_contents_file() {
+  string standard_filename = _host_dir + "/contents.xml";
+  return read_contents_file(standard_filename);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DHost::read_contents_file
 //       Access: Public
@@ -181,6 +195,82 @@ get_package_desc_file(FileSpec &desc_file,              // out
   return false;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DHost::fill_host_dir
+//       Access: Private
+//  Description: Hashes the host_url into a (mostly) unique directory
+//               string for this particular host.  Stores the result
+//               in _host_dir.
+////////////////////////////////////////////////////////////////////
+void P3DHost::
+fill_host_dir() {
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+  _host_dir = inst_mgr->get_root_dir();
+  _host_dir += "/";
+
+  string hostname;
+
+  // Look for a server name in the URL.  Including this string in the
+  // directory name makes it friendlier for people browsing the
+  // directory.
+  size_t p = _host_url.find("://");
+  if (p != string::npos) {
+    size_t start = p + 3;
+    size_t end = _host_url.find("/",  start);
+    // Now start .. end is something like "username@host:port".
+
+    size_t at = _host_url.find("@", start);
+    if (at < end) {
+      start = at + 1;
+    }
+
+    size_t colon = _host_url.find(":", start);
+    if (colon < end) {
+      end = colon;
+    }
+
+    // Now start .. end is just the hostname.
+    hostname = _host_url.substr(start, end - start);
+  }
+
+  // Now build a hash string of the whole URL.  We'll use MD5 to get a
+  // pretty good hash, with a minimum chance of collision.  Even if
+  // there is a hash collision, though, it's not the end of the world;
+  // it just means that both hosts will dump their packages into the
+  // same directory, and they'll fight over the toplevel contents.xml
+  // file.  Assuming they use different version numbers (which should
+  // be safe since they have the same hostname), there will be minimal
+  // redownloading.
+
+
+  static const size_t hash_size = 16;
+  unsigned char md[hash_size];
+
+  size_t keep_hash = hash_size;
+
+  if (!hostname.empty()) {
+    _host_dir += hostname;
+    _host_dir += "_";
+
+    // If we successfully got a hostname, we don't really need the
+    // full hash.  We'll keep half of it.
+    keep_hash = hash_size / 2;
+  }
+
+  MD5_CTX ctx;
+  MD5_Init(&ctx);
+  MD5_Update(&ctx, _host_url.data(), _host_url.size());
+  MD5_Final(md, &ctx);
+
+  for (size_t i = 0; i < keep_hash; ++i) {
+    int high = (md[i] >> 4) & 0xf;
+    int low = md[i] & 0xf;
+    _host_dir += encode_hexdigit(high);
+    _host_dir += encode_hexdigit(low);
+  }
+}
+
+
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DHost::standardize_filename
 //       Access: Private, Static

+ 5 - 1
direct/src/plugin/p3dHost.h

@@ -30,7 +30,7 @@ class P3DPackage;
 ////////////////////////////////////////////////////////////////////
 class P3DHost {
 private:
-  P3DHost(P3DInstanceManager *inst_mgr, const string &host_url);
+  P3DHost(const string &host_url);
   ~P3DHost();
 
 public:
@@ -40,6 +40,7 @@ public:
   inline const string &get_descriptive_name() const;
 
   inline bool has_contents_file() const;
+  bool read_contents_file();
   bool read_contents_file(const string &contents_filename);
 
   P3DPackage *get_package(const string &package_name, 
@@ -50,8 +51,11 @@ public:
                              const string &package_version);
 
 private:
+  void fill_host_dir();
+
   static string standardize_filename(const string &filename);
   static bool copy_file(const string &from_filename, const string &to_filename);
+  static inline char encode_hexdigit(int c);
 
 private:
   string _host_dir;

+ 5 - 0
direct/src/plugin/p3dInstance.cxx

@@ -778,6 +778,11 @@ start_download(P3DDownload *download) {
   assert(download->get_download_id() == 0);
 
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+
+  // Since we're downloading something, we might as well check all
+  // contents files from this point on.
+  inst_mgr->reset_verify_contents();
+
   int download_id = inst_mgr->get_unique_id();
   download->set_download_id(download_id);
 

+ 27 - 0
direct/src/plugin/p3dInstanceManager.I

@@ -24,6 +24,33 @@ is_initialized() const {
   return _is_initialized;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DInstanceManager::verify_contents
+//       Access: Public
+//  Description: Returns the verify_contents flag.  When this is set
+//               false, it indicates that we don't need to contact the
+//               server to verify that a contents.xml file is fresh
+//               before using it; we should just use it as it is.
+////////////////////////////////////////////////////////////////////
+inline bool P3DInstanceManager::
+get_verify_contents() const {
+  return _verify_contents;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DInstanceManager::reset_verify_contents
+//       Access: Public
+//  Description: Resets the verify_contents flag to true.  This should
+//               be done whenever we discover anything needs to be
+//               downloaded.  At this point, we might as well verify
+//               everything.
+////////////////////////////////////////////////////////////////////
+inline void P3DInstanceManager::
+reset_verify_contents() {
+  _verify_contents = true;
+}
+
+
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DInstanceManager::get_root_dir
 //       Access: Public

+ 5 - 3
direct/src/plugin/p3dInstanceManager.cxx

@@ -153,11 +153,12 @@ P3DInstanceManager::
 ////////////////////////////////////////////////////////////////////
 bool P3DInstanceManager::
 initialize(const string &contents_filename, const string &download_url,
+           bool verify_contents,
            const string &platform, const string &log_directory,
            const string &log_basename) {
 
   _root_dir = find_root_dir();
-
+  _verify_contents = verify_contents;
   _platform = platform;
   if (_platform.empty()) {
     _platform = DTOOL_PLATFORM;
@@ -252,7 +253,8 @@ initialize(const string &contents_filename, const string &download_url,
 
   _is_initialized = true;
 
-  if (!download_url.empty() && !contents_filename.empty()) {
+  if (!_verify_contents &&
+      !download_url.empty() && !contents_filename.empty()) {
     // Attempt to pre-read the supplied contents.xml file, to avoid an
     // unnecessary download later.
     P3DHost *host = get_host(download_url);
@@ -440,7 +442,7 @@ get_host(const string &host_url) {
     return (*pi).second;
   }
 
-  P3DHost *host = new P3DHost(this, host_url);
+  P3DHost *host = new P3DHost(host_url);
   bool inserted = _hosts.insert(Hosts::value_type(host_url, host)).second;
   assert(inserted);
 

+ 4 - 0
direct/src/plugin/p3dInstanceManager.h

@@ -45,11 +45,14 @@ private:
 public:
   bool initialize(const string &contents_filename,
                   const string &download_url,
+                  bool verify_contents,
                   const string &platform,
                   const string &log_directory,
                   const string &log_basename);
 
   inline bool is_initialized() const;
+  inline bool get_verify_contents() const;
+  inline void reset_verify_contents();
 
   inline const string &get_root_dir() const;
   inline const string &get_platform() const;
@@ -99,6 +102,7 @@ private:
 private:
   bool _is_initialized;
   string _root_dir;
+  bool _verify_contents;
   string _platform;
   string _log_directory;
   string _log_basename;

+ 7 - 0
direct/src/plugin/p3dPackage.cxx

@@ -162,6 +162,13 @@ begin_info_download() {
 ////////////////////////////////////////////////////////////////////
 void P3DPackage::
 download_contents_file() {
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+  if (!_host->has_contents_file() && !inst_mgr->get_verify_contents()) {
+    // If we're allowed to read a contents file without checking the
+    // server first, try it now.
+    _host->read_contents_file();
+  }
+
   if (_host->has_contents_file()) {
     // We've already got a contents.xml file; go straight to the
     // package desc file.

+ 4 - 2
direct/src/plugin/p3d_plugin.cxx

@@ -34,7 +34,8 @@ LOCK _api_lock;
 
 bool 
 P3D_initialize(int api_version, const char *contents_filename,
-               const char *download_url, const char *platform,
+               const char *download_url, bool verify_contents,
+               const char *platform,
                const char *log_directory, const char *log_basename) {
   if (api_version != P3D_API_VERSION) {
     // Can't accept an incompatible version.
@@ -69,7 +70,8 @@ P3D_initialize(int api_version, const char *contents_filename,
 
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   bool result = inst_mgr->initialize(contents_filename, download_url,
-                                     platform, log_directory, log_basename);
+                                     verify_contents, platform,
+                                     log_directory, log_basename);
   RELEASE_LOCK(_api_lock);
   return result;
 }

+ 10 - 2
direct/src/plugin/p3d_plugin.h

@@ -96,7 +96,14 @@ extern "C" {
    If this is NULL, a new file will be downloaded as needed.
 
    If download_url is not NULL or empty, it specifies the root URL of
-   the download server; otherwise, the compiled-in default is used.
+   the download server that provided the contents_filename.
+
+   If verify_contents is true, it means that the download server will
+   be contacted to verify that contents.xml is current, before
+   continuing.  If it is false, it means that contents.xml will be
+   loaded without checking the download server, if possible.  This can
+   be used to minimize unnecessary network operations for standalone
+   applications.  For a web plugin, it should be set true.
 
    If platform is not NULL or empty, it specifies the current platform
    string; otherwise, the compiled-in default is used.
@@ -119,7 +126,8 @@ extern "C" {
    immediately unload the DLL and (if possible) download a new one. */
 typedef bool 
 P3D_initialize_func(int api_version, const char *contents_filename,
-                    const char *download_url, const char *platform,
+                    const char *download_url, bool verify_contents,
+                    const char *platform,
                     const char *log_directory, const char *log_basename);
 
 /* This function should be called to unload the core API.  It will

+ 1 - 1
direct/src/plugin_npapi/ppInstance.cxx

@@ -928,7 +928,7 @@ do_load_plugin() {
 #endif  // P3D_PLUGIN_P3D_PLUGIN
 
   nout << "Attempting to load core API from " << pathname << "\n";
-  if (!load_plugin(pathname, "", "", "", "", "")) {
+  if (!load_plugin(pathname, "", "", true, "", "", "")) {
     nout << "Unable to launch core API in " << pathname << "\n";
     return;
   }

+ 23 - 13
direct/src/plugin_standalone/panda3d.cxx

@@ -66,7 +66,7 @@ run(int argc, char *argv[]) {
   bool allow_multiple = false;
   string download_url = PANDA_PACKAGE_HOST_URL;
   string this_platform = DTOOL_PLATFORM;
-  bool force_download = false;
+  bool verify_contents = false;
 
   P3D_window_type window_type = P3D_WT_toplevel;
   int win_x = 0, win_y = 0;
@@ -89,7 +89,7 @@ run(int argc, char *argv[]) {
       break;
 
     case 'f':
-      force_download = true;
+      verify_contents = true;
       break;
 
     case 't':
@@ -149,7 +149,7 @@ run(int argc, char *argv[]) {
     download_url += '/';
   }
 
-  if (!get_plugin(download_url, this_platform, force_download)) {
+  if (!get_plugin(download_url, this_platform, verify_contents)) {
     cerr << "Unable to load Panda3D plugin.\n";
     return 1;
   }
@@ -315,10 +315,11 @@ run(int argc, char *argv[]) {
 //               true on success, false on failure.
 ////////////////////////////////////////////////////////////////////
 bool Panda3D::
-get_plugin(const string &download_url, const string &this_platform, bool force_download) {
+get_plugin(const string &download_url, const string &this_platform, 
+           bool verify_contents) {
   // First, look for the existing contents.xml file.
   Filename contents_filename = Filename(Filename::from_os_specific(_root_dir), "contents.xml");
-  if (!force_download && read_contents_file(contents_filename, download_url, this_platform)) {
+  if (!verify_contents && read_contents_file(contents_filename, download_url, this_platform, verify_contents)) {
     // Got the file, and it's good.
     return true;
   }
@@ -345,7 +346,11 @@ get_plugin(const string &download_url, const string &this_platform, bool force_d
     tempfile.rename_to(contents_filename);
   }
 
-  return read_contents_file(contents_filename, download_url, this_platform);
+  // Since we had to download some of it, might as well ask the core
+  // API to check all of it.
+  verify_contents = true;
+
+  return read_contents_file(contents_filename, download_url, this_platform, verify_contents);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -357,7 +362,7 @@ get_plugin(const string &download_url, const string &this_platform, bool force_d
 ////////////////////////////////////////////////////////////////////
 bool Panda3D::
 read_contents_file(Filename contents_filename, const string &download_url, 
-                   const string &this_platform) {
+                   const string &this_platform, bool verify_contents) {
   ifstream in;
   contents_filename.set_text();
   if (!contents_filename.open_read(in)) {
@@ -376,8 +381,8 @@ read_contents_file(Filename contents_filename, const string &download_url,
       if (name != NULL && strcmp(name, "coreapi") == 0) {
         const char *xplatform = xplugin->Attribute("platform");
         if (xplatform != NULL && strcmp(xplatform, this_platform.c_str()) == 0) {
-          return get_core_api(contents_filename, download_url, this_platform,
-                              xplugin);
+          return get_core_api(contents_filename, download_url, 
+                              this_platform, verify_contents, xplugin);
         }
       }
       
@@ -401,7 +406,8 @@ read_contents_file(Filename contents_filename, const string &download_url,
 ////////////////////////////////////////////////////////////////////
 bool Panda3D::
 get_core_api(const Filename &contents_filename, const string &download_url,
-             const string &this_platform, TiXmlElement *xplugin) {
+             const string &this_platform, bool verify_contents,
+             TiXmlElement *xplugin) {
   _core_api_dll.load_xml(xplugin);
 
   if (!_core_api_dll.quick_verify(_root_dir)) {
@@ -418,10 +424,14 @@ get_core_api(const Filename &contents_filename, const string &download_url,
       return false;
     }
 
-    if (!_core_api_dll.quick_verify(_root_dir)) {
+    if (!_core_api_dll.full_verify(_root_dir)) {
       cerr << "Mismatched download for " << url << "\n";
       return false;
     }
+
+    // Since we had to download some of it, might as well ask the core
+    // API to check all of it.
+    verify_contents = true;
   }
 
   // Now we've got the DLL.  Load it.
@@ -440,7 +450,7 @@ get_core_api(const Filename &contents_filename, const string &download_url,
 #endif  // P3D_PLUGIN_P3D_PLUGIN
 
   if (!load_plugin(pathname, contents_filename.to_os_specific(),
-                   download_url, this_platform, _log_dirname,
+                   download_url, verify_contents, this_platform, _log_dirname,
                    _log_basename)) {
     cerr << "Unable to launch core API in " << pathname << "\n" << flush;
     return false;
@@ -746,7 +756,7 @@ usage() {
     << "    the application output to the console.\n\n"
 
     << "  -f\n"
-    << "    Force a HTTP contact to the Panda3D download server, to check\n"
+    << "    Force an initial contact of the Panda3D download server, to check\n"
     << "    if a new version is available.  Normally, this is done only\n"
     << "    if contents.xml cannot be read.\n\n"
 

+ 4 - 4
direct/src/plugin_standalone/panda3d.h

@@ -43,13 +43,13 @@ public:
 
 private:
   bool get_plugin(const string &download_url, const string &this_platform,
-                  bool force_download);
-  bool read_contents_file(Filename contents_filename, 
+                  bool verify_contents);
+  bool read_contents_file(Filename contents_filename,
                           const string &download_url,
-                          const string &this_platform);
+                          const string &this_platform, bool verify_contents);
   bool get_core_api(const Filename &contents_filename, 
                     const string &download_url, const string &this_platform,
-                    TiXmlElement *xplugin);
+                    bool verify_contents, TiXmlElement *xplugin);
   void run_getters();
   void handle_request(P3D_request *request);
   void make_parent_window(P3D_window_handle &parent_window, 

+ 1 - 1
direct/src/showutil/make_contents.py

@@ -105,7 +105,7 @@ class ContentsMaker:
 
         try:
             f = open(contentsFilePathname, 'r')
-        except OSError:
+        except IOError:
             return None
 
         for line in f.readlines():