788 lines
24 KiB
C++
Executable File
788 lines
24 KiB
C++
Executable File
// Filename: p3dPackage.cxx
|
|
// Created by: drose (12Jun09)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PANDA 3D SOFTWARE
|
|
// Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
//
|
|
// All use of this software is subject to the terms of the revised BSD
|
|
// license. You should have received a copy of this license along
|
|
// with this source code in a file named "LICENSE."
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#include "p3dPackage.h"
|
|
#include "p3dInstanceManager.h"
|
|
#include "p3dMultifileReader.h"
|
|
|
|
#include "openssl/md5.h"
|
|
#include "zlib.h"
|
|
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <sys/utime.h>
|
|
#include <direct.h>
|
|
#define stat _stat
|
|
#define utime _utime
|
|
#define utimbuf _utimbuf
|
|
|
|
#else
|
|
#include <utime.h>
|
|
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
P3DPackage::
|
|
P3DPackage(const string &package_name, const string &package_version) :
|
|
_package_name(package_name),
|
|
_package_version(package_version)
|
|
{
|
|
_package_fullname = _package_name + "_" + _package_version;
|
|
_ready = false;
|
|
_failed = false;
|
|
_active_download = NULL;
|
|
_partial_download = false;
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
|
|
// Ensure the package directory exists; create it if it does not.
|
|
_package_dir = inst_mgr->get_root_dir() + string("/") + _package_name;
|
|
inst_mgr->mkdir_public(_package_dir);
|
|
|
|
_package_dir += string("/") + _package_version;
|
|
inst_mgr->mkdir_public(_package_dir);
|
|
|
|
_desc_file_basename = _package_fullname + ".xml";
|
|
_desc_file_pathname = _package_dir + "/" + _desc_file_basename;
|
|
|
|
// TODO: we should check the desc file for updates with the server.
|
|
// Perhaps this should be done in a parent class.
|
|
|
|
// TODO: if the desc file exists, and is consistent with the server
|
|
// contents file, don't re-download it.
|
|
/*
|
|
// Load the desc file, if it exists.
|
|
TiXmlDocument doc(_desc_file_pathname.c_str());
|
|
if (!doc.LoadFile()) {
|
|
download_desc_file();
|
|
} else {
|
|
got_desc_file(&doc, false);
|
|
}
|
|
*/
|
|
|
|
download_desc_file();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::Destructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
P3DPackage::
|
|
~P3DPackage() {
|
|
// Tell any pending callbacks that we're no good any more.
|
|
report_done(false);
|
|
|
|
// Cancel any pending download.
|
|
if (_active_download != NULL) {
|
|
_active_download->cancel();
|
|
delete _active_download;
|
|
_active_download = NULL;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::set_callback
|
|
// Access: Public
|
|
// Description: Registers a callback on the package. The callback
|
|
// object will be notified when the package is ready for
|
|
// use (or when it has failed to download properly).
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
set_callback(Callback *callback) {
|
|
if (_ready || _failed) {
|
|
// Actually, we're already done. Signal the callback immediately.
|
|
callback->package_ready(this, _ready);
|
|
delete callback;
|
|
} else {
|
|
// Bootstrap still in progress. Save the callback.
|
|
_callbacks.push_back(callback);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::cancel_callback
|
|
// Access: Public
|
|
// Description: Unregisters a particular callback object. This
|
|
// object will no longer be notified when the package is
|
|
// ready.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
cancel_callback(Callback *callback) {
|
|
Callbacks::iterator ci;
|
|
ci = find(_callbacks.begin(), _callbacks.end(), callback);
|
|
if (ci != _callbacks.end()) {
|
|
_callbacks.erase(ci);
|
|
} else {
|
|
cerr << "Canceling unknown callback on " << _package_fullname << "\n";
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::download_desc_file
|
|
// Access: Private
|
|
// Description: Starts downloading the desc file for the package.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
download_desc_file() {
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
string url = inst_mgr->get_download_url();
|
|
url += _package_name + "/" + _package_version + "/" + _desc_file_basename;
|
|
|
|
start_download(DT_desc_file, url, _desc_file_pathname, false);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::desc_file_download_finished
|
|
// Access: Private
|
|
// Description: Called when the desc file has been fully downloaded.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
desc_file_download_finished(bool success) {
|
|
if (!success) {
|
|
report_done(false);
|
|
return;
|
|
}
|
|
|
|
TiXmlDocument doc(_desc_file_pathname.c_str());
|
|
if (!doc.LoadFile()) {
|
|
cerr << "Couldn't read " << _desc_file_pathname << "\n";
|
|
report_done(false);
|
|
return;
|
|
}
|
|
|
|
got_desc_file(&doc, true);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::got_desc_file
|
|
// Access: Private
|
|
// Description: Reads the desc file and begins verifying the files.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) {
|
|
cerr << "got desc file\n";
|
|
|
|
TiXmlElement *xpackage = doc->FirstChildElement("package");
|
|
TiXmlElement *uncompressed_archive = NULL;
|
|
TiXmlElement *compressed_archive = NULL;
|
|
|
|
if (xpackage != NULL) {
|
|
uncompressed_archive = xpackage->FirstChildElement("uncompressed_archive");
|
|
compressed_archive = xpackage->FirstChildElement("compressed_archive");
|
|
}
|
|
|
|
if (uncompressed_archive == NULL || compressed_archive == NULL) {
|
|
// The desc file didn't include the archive file itself, weird.
|
|
if (!freshly_downloaded) {
|
|
download_desc_file();
|
|
return;
|
|
}
|
|
report_done(false);
|
|
return;
|
|
}
|
|
|
|
_uncompressed_archive.load_xml(uncompressed_archive);
|
|
_compressed_archive.load_xml(compressed_archive);
|
|
|
|
// Now get all the components.
|
|
_components.clear();
|
|
TiXmlElement *component = xpackage->FirstChildElement("component");
|
|
while (component != NULL) {
|
|
FileSpec file;
|
|
file.load_xml(component);
|
|
_components.push_back(file);
|
|
component = component->NextSiblingElement("component");
|
|
}
|
|
|
|
cerr << "got " << _components.size() << " components\n";
|
|
|
|
// Verify all of the components.
|
|
bool all_components_ok = true;
|
|
Components::iterator ci;
|
|
for (ci = _components.begin(); ci != _components.end(); ++ci) {
|
|
if (!(*ci).quick_verify(_package_dir)) {
|
|
all_components_ok = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (all_components_ok) {
|
|
// Great, we're ready to begin.
|
|
report_done(true);
|
|
|
|
} else if (_uncompressed_archive.quick_verify(_package_dir)) {
|
|
// We need to re-extract the archive.
|
|
extract_archive();
|
|
|
|
} else if (_compressed_archive.quick_verify(_package_dir)) {
|
|
// We need to uncompress the archive.
|
|
uncompress_archive();
|
|
|
|
} else {
|
|
// Shoot, we need to download the archive.
|
|
download_compressed_archive(true);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::download_compressed_archive
|
|
// Access: Private
|
|
// Description: Starts downloading the archive file for the package.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
download_compressed_archive(bool allow_partial) {
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
string url = inst_mgr->get_download_url();
|
|
url += _package_name + "/" + _package_version + "/" + _compressed_archive._filename;
|
|
|
|
string target_pathname = _package_dir + "/" + _compressed_archive._filename;
|
|
|
|
start_download(DT_compressed_archive, url, target_pathname, allow_partial);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::compressed_archive_download_finished
|
|
// Access: Private
|
|
// Description: Called when the desc file has been fully downloaded.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
compressed_archive_download_finished(bool success) {
|
|
if (!success) {
|
|
report_done(false);
|
|
return;
|
|
}
|
|
|
|
if (_compressed_archive.full_verify(_package_dir)) {
|
|
// Go on to uncompress the archive.
|
|
uncompress_archive();
|
|
return;
|
|
}
|
|
|
|
// Oof, didn't download it correctly.
|
|
if (_partial_download) {
|
|
// Go back and get the whole file this time.
|
|
download_compressed_archive(false);
|
|
}
|
|
|
|
cerr << _compressed_archive._filename
|
|
<< " failed hash check after download\n";
|
|
report_done(false);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::uncompress_archive
|
|
// Access: Private
|
|
// Description: Uncompresses the archive file.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
uncompress_archive() {
|
|
cerr << "uncompressing " << _compressed_archive._filename << "\n";
|
|
|
|
string source_pathname = _package_dir + "/" + _compressed_archive._filename;
|
|
string target_pathname = _package_dir + "/" + _uncompressed_archive._filename;
|
|
|
|
ifstream source(source_pathname.c_str(), ios::in | ios::binary);
|
|
if (!source) {
|
|
cerr << "Couldn't open " << source_pathname << "\n";
|
|
report_done(false);
|
|
return;
|
|
}
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
if (!inst_mgr->mkfile_public(target_pathname)) {
|
|
cerr << "Unable to create " << target_pathname << "\n";
|
|
report_done(false);
|
|
return;
|
|
}
|
|
|
|
ofstream target(target_pathname.c_str(), ios::out | ios::binary);
|
|
if (!target) {
|
|
cerr << "Couldn't write to " << target_pathname << "\n";
|
|
report_done(false);
|
|
return;
|
|
}
|
|
|
|
static const int decompress_buffer_size = 1024;
|
|
char decompress_buffer[decompress_buffer_size];
|
|
static const int write_buffer_size = 1024;
|
|
char write_buffer[write_buffer_size];
|
|
|
|
z_stream z;
|
|
z.next_in = Z_NULL;
|
|
z.avail_in = 0;
|
|
z.next_out = Z_NULL;
|
|
z.avail_out = 0;
|
|
z.zalloc = Z_NULL;
|
|
z.zfree = Z_NULL;
|
|
z.opaque = Z_NULL;
|
|
z.msg = (char *)"no error message";
|
|
|
|
bool eof = false;
|
|
int flush = 0;
|
|
|
|
source.read(decompress_buffer, decompress_buffer_size);
|
|
size_t read_count = source.gcount();
|
|
eof = (read_count == 0 || source.eof() || source.fail());
|
|
|
|
z.next_in = (Bytef *)decompress_buffer;
|
|
z.avail_in = read_count;
|
|
|
|
int result = inflateInit(&z);
|
|
if (result < 0) {
|
|
cerr << z.msg << "\n";
|
|
report_done(false);
|
|
return;
|
|
}
|
|
|
|
while (true) {
|
|
if (z.avail_in == 0 && !eof) {
|
|
source.read(decompress_buffer, decompress_buffer_size);
|
|
size_t read_count = source.gcount();
|
|
eof = (read_count == 0 || source.eof() || source.fail());
|
|
|
|
z.next_in = (Bytef *)decompress_buffer;
|
|
z.avail_in = read_count;
|
|
}
|
|
|
|
z.next_out = (Bytef *)write_buffer;
|
|
z.avail_out = write_buffer_size;
|
|
int result = inflate(&z, flush);
|
|
if (z.avail_out < write_buffer_size) {
|
|
target.write(write_buffer, write_buffer_size - z.avail_out);
|
|
if (!target) {
|
|
cerr << "Couldn't write entire file to " << target_pathname << "\n";
|
|
report_done(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (result == Z_STREAM_END) {
|
|
// Here's the end of the file.
|
|
break;
|
|
|
|
} else if (result == Z_BUF_ERROR && flush == 0) {
|
|
// We might get this if no progress is possible, for instance if
|
|
// the input stream is truncated. In this case, tell zlib to
|
|
// dump everything it's got.
|
|
flush = Z_FINISH;
|
|
|
|
} else if (result < 0) {
|
|
cerr << z.msg << "\n";
|
|
inflateEnd(&z);
|
|
report_done(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
result = inflateEnd(&z);
|
|
if (result < 0) {
|
|
cerr << z.msg << "\n";
|
|
report_done(false);
|
|
return;
|
|
}
|
|
|
|
source.close();
|
|
target.close();
|
|
|
|
if (!_uncompressed_archive.full_verify(_package_dir)) {
|
|
cerr << "after uncompressing " << target_pathname
|
|
<< ", failed hash check\n";
|
|
report_done(false);
|
|
return;
|
|
}
|
|
|
|
unlink(source_pathname.c_str());
|
|
|
|
// All done uncompressing.
|
|
extract_archive();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::extract_archive
|
|
// Access: Private
|
|
// Description: Extracts the components from the archive file.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
extract_archive() {
|
|
cerr << "extracting " << _uncompressed_archive._filename << "\n";
|
|
|
|
string source_pathname = _package_dir + "/" + _uncompressed_archive._filename;
|
|
P3DMultifileReader reader;
|
|
if (!reader.extract(source_pathname, _package_dir)) {
|
|
cerr << "Failure extracting " << _uncompressed_archive._filename
|
|
<< "\n";
|
|
report_done(false);
|
|
return;
|
|
}
|
|
|
|
cerr << "done extracting\n";
|
|
report_done(true);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::report_done
|
|
// Access: Private
|
|
// Description: Transitions the package to "ready" or "failure"
|
|
// state, and reports this change to all the listening
|
|
// callbacks.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
report_done(bool success) {
|
|
cerr << "report_done(" << success << "), "
|
|
<< _callbacks.size() << " callbacks\n";
|
|
if (success) {
|
|
_ready = true;
|
|
_failed = false;
|
|
} else {
|
|
_ready = false;
|
|
_failed = true;
|
|
}
|
|
|
|
Callbacks orig_callbacks;
|
|
orig_callbacks.swap(_callbacks);
|
|
Callbacks::iterator ci;
|
|
for (ci = orig_callbacks.begin(); ci != orig_callbacks.end(); ++ci) {
|
|
(*ci)->package_ready(this, _ready);
|
|
delete (*ci);
|
|
}
|
|
|
|
// We shouldn't have added any more callbacks during the above loop.
|
|
assert(_callbacks.empty());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::start_download
|
|
// Access: Private
|
|
// Description: Initiates a download of the indicated file.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
start_download(P3DPackage::DownloadType dtype, const string &url,
|
|
const string &pathname, bool allow_partial) {
|
|
// Only one download should be active at a time
|
|
assert(_active_download == NULL);
|
|
|
|
if (!allow_partial) {
|
|
unlink(pathname.c_str());
|
|
}
|
|
|
|
Download *download = new Download(this, dtype);
|
|
download->set_url(url);
|
|
download->set_filename(pathname);
|
|
|
|
// TODO: implement partial file re-download.
|
|
allow_partial = false;
|
|
|
|
_active_download = download;
|
|
_partial_download = false;
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
inst_mgr->get_command_instance()->start_download(download);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::decode_hex
|
|
// Access: Private, Static
|
|
// Description: Decodes the hex string in source into the character
|
|
// array in dest. dest must have has least size bytes;
|
|
// source must have size * 2 bytes.
|
|
//
|
|
// Returns true on success, false if there was a non-hex
|
|
// digit in the string.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool P3DPackage::
|
|
decode_hex(unsigned char *dest, const char *source, size_t size) {
|
|
for (size_t i = 0; i < size; ++i) {
|
|
int high = decode_hexdigit(source[i * 2]);
|
|
int low = decode_hexdigit(source[i * 2 + 1]);
|
|
if (high < 0 || low < 0) {
|
|
return false;
|
|
}
|
|
dest[i] = (high << 4) | low;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::encode_hex
|
|
// Access: Private, Static
|
|
// Description: Encodes a character array into a hex string for
|
|
// output. dest must have at least size * 2 bytes;
|
|
// source must have size bytes. The result is not
|
|
// null-terminated.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
encode_hex(char *dest, const unsigned char *source, size_t size) {
|
|
for (size_t i = 0; i < size; ++i) {
|
|
int high = (source[i] >> 4) & 0xf;
|
|
int low = source[i] & 0xf;
|
|
dest[2 * i] = encode_hexdigit(high);
|
|
dest[2 * i + 1] = encode_hexdigit(low);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::stream_hex
|
|
// Access: Private, Static
|
|
// Description: Writes the indicated buffer as a string of hex
|
|
// characters to the given ostream.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::
|
|
stream_hex(ostream &out, const unsigned char *source, size_t size) {
|
|
for (size_t i = 0; i < size; ++i) {
|
|
int high = (source[i] >> 4) & 0xf;
|
|
int low = source[i] & 0xf;
|
|
out.put(encode_hexdigit(high));
|
|
out.put(encode_hexdigit(low));
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::Callback::package_ready
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::Callback::
|
|
package_ready(P3DPackage *package, bool success) {
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::Download::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
P3DPackage::Download::
|
|
Download(P3DPackage *package, DownloadType dtype) :
|
|
_package(package),
|
|
_dtype(dtype)
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::Download::download_finished
|
|
// Access: Protected, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::Download::
|
|
download_finished(bool success) {
|
|
P3DFileDownload::download_finished(success);
|
|
assert(_package->_active_download == this);
|
|
_package->_active_download = NULL;
|
|
|
|
switch (_dtype) {
|
|
case DT_desc_file:
|
|
_package->desc_file_download_finished(success);
|
|
break;
|
|
|
|
case DT_compressed_archive:
|
|
_package->compressed_archive_download_finished(success);
|
|
break;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::FileSpec::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
P3DPackage::FileSpec::
|
|
FileSpec() {
|
|
_size = 0;
|
|
_timestamp = 0;
|
|
memset(_hash, 0, sizeof(_hash));
|
|
_got_hash = false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::FileSpec::load_xml
|
|
// Access: Public
|
|
// Description: Reads the data from the indicated XML file.
|
|
////////////////////////////////////////////////////////////////////
|
|
void P3DPackage::FileSpec::
|
|
load_xml(TiXmlElement *element) {
|
|
const char *filename = element->Attribute("filename");
|
|
if (filename != NULL) {
|
|
_filename = filename;
|
|
}
|
|
|
|
const char *size = element->Attribute("size");
|
|
if (size != NULL) {
|
|
char *endptr;
|
|
_size = strtoul(size, &endptr, 10);
|
|
}
|
|
|
|
const char *timestamp = element->Attribute("timestamp");
|
|
if (timestamp != NULL) {
|
|
char *endptr;
|
|
_timestamp = strtoul(timestamp, &endptr, 10);
|
|
}
|
|
|
|
_got_hash = false;
|
|
const char *hash = element->Attribute("hash");
|
|
if (hash != NULL && strlen(hash) == (hash_size * 2)) {
|
|
// Decode the hex hash string.
|
|
_got_hash = decode_hex(_hash, hash, hash_size);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::FileSpec::quick_verify
|
|
// Access: Public
|
|
// Description: Performs a quick test to ensure the file has not been
|
|
// modified. This test is vulnerable to people
|
|
// maliciously attempting to fool the program (by
|
|
// setting datestamps etc.).
|
|
//
|
|
// Returns true if it is intact, false if it needs to be
|
|
// redownloaded.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool P3DPackage::FileSpec::
|
|
quick_verify(const string &package_dir) const {
|
|
string pathname = package_dir + "/" + _filename;
|
|
struct stat st;
|
|
if (stat(pathname.c_str(), &st) != 0) {
|
|
cerr << "file not found: " << _filename << "\n";
|
|
return false;
|
|
}
|
|
|
|
if (st.st_size != _size) {
|
|
// If the size is wrong, the file fails.
|
|
cerr << "size wrong: " << _filename << "\n";
|
|
return false;
|
|
}
|
|
|
|
if (st.st_mtime == _timestamp) {
|
|
// If the size is right and the timestamp is right, the file passes.
|
|
cerr << "file ok: " << _filename << "\n";
|
|
return true;
|
|
}
|
|
|
|
cerr << "modification time wrong: " << _filename << "\n";
|
|
|
|
// If the size is right but the timestamp is wrong, the file
|
|
// soft-fails. We follow this up with a hash check.
|
|
if (!check_hash(pathname)) {
|
|
// Hard fail, the hash is wrong.
|
|
cerr << "hash check wrong: " << _filename << "\n";
|
|
return false;
|
|
}
|
|
|
|
cerr << "hash check ok: " << _filename << "\n";
|
|
|
|
// The hash is OK after all. Change the file's timestamp back to
|
|
// what we expect it to be, so we can quick-verify it successfully
|
|
// next time.
|
|
utimbuf utb;
|
|
utb.actime = st.st_atime;
|
|
utb.modtime = _timestamp;
|
|
utime(pathname.c_str(), &utb);
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::FileSpec::quick_verify
|
|
// Access: Public
|
|
// Description: Performs a more thorough test to ensure the file has
|
|
// not been modified. This test is less vulnerable to
|
|
// malicious attacks, since it reads and verifies the
|
|
// entire file.
|
|
//
|
|
// Returns true if it is intact, false if it needs to be
|
|
// redownloaded.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool P3DPackage::FileSpec::
|
|
full_verify(const string &package_dir) const {
|
|
string pathname = package_dir + "/" + _filename;
|
|
struct stat st;
|
|
if (stat(pathname.c_str(), &st) != 0) {
|
|
cerr << "file not found: " << _filename << "\n";
|
|
return false;
|
|
}
|
|
|
|
if (st.st_size != _size) {
|
|
// If the size is wrong, the file fails.
|
|
cerr << "size wrong: " << _filename << "\n";
|
|
return false;
|
|
}
|
|
|
|
// If the size is right but the timestamp is wrong, the file
|
|
// soft-fails. We follow this up with a hash check.
|
|
if (!check_hash(pathname)) {
|
|
// Hard fail, the hash is wrong.
|
|
cerr << "hash check wrong: " << _filename << "\n";
|
|
return false;
|
|
}
|
|
|
|
cerr << "hash check ok: " << _filename << "\n";
|
|
|
|
// The hash is OK. If the timestamp is wrong, change it back to
|
|
// what we expect it to be, so we can quick-verify it successfully
|
|
// next time.
|
|
|
|
if (st.st_mtime != _timestamp) {
|
|
utimbuf utb;
|
|
utb.actime = st.st_atime;
|
|
utb.modtime = _timestamp;
|
|
utime(pathname.c_str(), &utb);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: P3DPackage::FileSpec::check_hash
|
|
// Access: Public
|
|
// Description: Returns true if the file has the expected md5 hash,
|
|
// false otherwise.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool P3DPackage::FileSpec::
|
|
check_hash(const string &pathname) const {
|
|
ifstream stream(pathname.c_str(), ios::in | ios::binary);
|
|
if (!stream) {
|
|
cerr << "unable to read " << pathname << "\n";
|
|
return false;
|
|
}
|
|
|
|
unsigned char md[hash_size];
|
|
|
|
MD5_CTX ctx;
|
|
MD5_Init(&ctx);
|
|
|
|
static const int buffer_size = 1024;
|
|
char buffer[buffer_size];
|
|
|
|
stream.read(buffer, buffer_size);
|
|
size_t count = stream.gcount();
|
|
while (count != 0) {
|
|
MD5_Update(&ctx, buffer, count);
|
|
stream.read(buffer, buffer_size);
|
|
count = stream.gcount();
|
|
}
|
|
|
|
MD5_Final(md, &ctx);
|
|
|
|
return (memcmp(md, _hash, hash_size) == 0);
|
|
}
|