diff --git a/direct/src/plugin/fileSpec.cxx b/direct/src/plugin/fileSpec.cxx index 146d87e03a..906fb3acbf 100755 --- a/direct/src/plugin/fileSpec.cxx +++ b/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 diff --git a/direct/src/plugin/load_plugin.cxx b/direct/src/plugin/load_plugin.cxx index 1dae223e75..a9c258d6e0 100755 --- a/direct/src/plugin/load_plugin.cxx +++ b/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"; diff --git a/direct/src/plugin/load_plugin.h b/direct/src/plugin/load_plugin.h index 9e4511b61c..84229b30ba 100755 --- a/direct/src/plugin/load_plugin.h +++ b/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(); diff --git a/direct/src/plugin/p3dHost.I b/direct/src/plugin/p3dHost.I index e4d36ec8fc..10041260d1 100644 --- a/direct/src/plugin/p3dHost.I +++ b/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'; +} diff --git a/direct/src/plugin/p3dHost.cxx b/direct/src/plugin/p3dHost.cxx index 7c791dd48e..0714d794cc 100644 --- a/direct/src/plugin/p3dHost.cxx +++ b/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 diff --git a/direct/src/plugin/p3dHost.h b/direct/src/plugin/p3dHost.h index 156bb0e863..f616c95ae7 100644 --- a/direct/src/plugin/p3dHost.h +++ b/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; diff --git a/direct/src/plugin/p3dInstance.cxx b/direct/src/plugin/p3dInstance.cxx index fa55ee5a7a..81cf024589 100644 --- a/direct/src/plugin/p3dInstance.cxx +++ b/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); diff --git a/direct/src/plugin/p3dInstanceManager.I b/direct/src/plugin/p3dInstanceManager.I index 886f95f5af..4cb0512a16 100644 --- a/direct/src/plugin/p3dInstanceManager.I +++ b/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 diff --git a/direct/src/plugin/p3dInstanceManager.cxx b/direct/src/plugin/p3dInstanceManager.cxx index 74d4f75009..2533128f9a 100644 --- a/direct/src/plugin/p3dInstanceManager.cxx +++ b/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); diff --git a/direct/src/plugin/p3dInstanceManager.h b/direct/src/plugin/p3dInstanceManager.h index ffb50484ef..3564b6bf8f 100644 --- a/direct/src/plugin/p3dInstanceManager.h +++ b/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; diff --git a/direct/src/plugin/p3dPackage.cxx b/direct/src/plugin/p3dPackage.cxx index 217a3bc144..75c9bf3b2f 100755 --- a/direct/src/plugin/p3dPackage.cxx +++ b/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. diff --git a/direct/src/plugin/p3d_plugin.cxx b/direct/src/plugin/p3d_plugin.cxx index 8d45422111..3c268c8ae9 100644 --- a/direct/src/plugin/p3d_plugin.cxx +++ b/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; } diff --git a/direct/src/plugin/p3d_plugin.h b/direct/src/plugin/p3d_plugin.h index 15c4048a32..9058c84951 100644 --- a/direct/src/plugin/p3d_plugin.h +++ b/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 diff --git a/direct/src/plugin_npapi/ppInstance.cxx b/direct/src/plugin_npapi/ppInstance.cxx index 0f84a95e37..01564a4ded 100644 --- a/direct/src/plugin_npapi/ppInstance.cxx +++ b/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; } diff --git a/direct/src/plugin_standalone/panda3d.cxx b/direct/src/plugin_standalone/panda3d.cxx index 39fdad61d9..3d9735c774 100644 --- a/direct/src/plugin_standalone/panda3d.cxx +++ b/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" diff --git a/direct/src/plugin_standalone/panda3d.h b/direct/src/plugin_standalone/panda3d.h index 8085337f9f..52a36a1b63 100755 --- a/direct/src/plugin_standalone/panda3d.h +++ b/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, diff --git a/direct/src/showutil/make_contents.py b/direct/src/showutil/make_contents.py index c46d085ff3..a6122543fc 100755 --- a/direct/src/showutil/make_contents.py +++ b/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():