open_toontown_panda3d/direct/src/plugin/fileSpec.cxx

482 lines
11 KiB
C++

/**
* 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."
*
* @file fileSpec.cxx
* @author drose
* @date 2009-06-29
*/
#include "fileSpec.h"
#include "wstring_encode.h"
#include "openssl/md5.h"
#include <fstream>
#include <fcntl.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#ifdef _WIN32
#include <sys/utime.h>
#include <direct.h>
#define utimbuf _utimbuf
#else
#include <utime.h>
#endif
/**
*
*/
FileSpec::
FileSpec() {
_size = 0;
_timestamp = 0;
memset(_hash, 0, hash_size);
_got_hash = false;
_actual_file = NULL;
}
/**
*
*/
FileSpec::
FileSpec(const FileSpec &copy) :
_filename(copy._filename),
_size(copy._size),
_timestamp(copy._timestamp),
_got_hash(copy._got_hash)
{
memcpy(_hash, copy._hash, hash_size);
_actual_file = NULL;
}
/**
*
*/
void FileSpec::
operator = (const FileSpec &copy) {
_filename = copy._filename;
_size = copy._size;
_timestamp = copy._timestamp;
memcpy(_hash, copy._hash, hash_size);
_got_hash = copy._got_hash;
}
/**
*
*/
FileSpec::
~FileSpec() {
if (_actual_file != NULL) {
delete _actual_file;
}
}
/**
* Reads the data from the indicated XML file.
*/
void FileSpec::
load_xml(TiXmlElement *xelement) {
const char *filename = xelement->Attribute("filename");
if (filename != NULL) {
_filename = filename;
}
const char *size = xelement->Attribute("size");
if (size != NULL) {
char *endptr;
_size = strtoul(size, &endptr, 10);
}
const char *timestamp = xelement->Attribute("timestamp");
if (timestamp != NULL) {
char *endptr;
_timestamp = strtoul(timestamp, &endptr, 10);
}
_got_hash = false;
const char *hash = xelement->Attribute("hash");
if (hash != NULL && strlen(hash) == (hash_size * 2)) {
// Decode the hex hash string.
_got_hash = decode_hex(_hash, hash, hash_size);
}
}
/**
* Stores the data to the indicated XML file.
*/
void FileSpec::
store_xml(TiXmlElement *xelement) {
if (!_filename.empty()) {
xelement->SetAttribute("filename", _filename);
}
if (_size != 0) {
xelement->SetAttribute("size", _size);
}
if (_timestamp != 0) {
xelement->SetAttribute("timestamp", (int)_timestamp);
}
if (_got_hash) {
char hash[hash_size * 2 + 1];
encode_hex(hash, _hash, hash_size);
hash[hash_size * 2] = '\0';
xelement->SetAttribute("hash", hash);
}
}
/**
* 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 FileSpec::
quick_verify(const string &package_dir) {
return quick_verify_pathname(get_pathname(package_dir));
}
/**
* Works like quick_verify(), above, with an explicit pathname. Useful for
* verifying the copy of a file in a temporary location.
*/
bool FileSpec::
quick_verify_pathname(const string &pathname) {
if (_actual_file != NULL) {
delete _actual_file;
_actual_file = NULL;
}
int result = 1;
#ifdef _WIN32
struct _stat st;
wstring pathname_w;
if (string_to_wstring(pathname_w, pathname)) {
result = _wstat(pathname_w.c_str(), &st);
}
#else // _WIN32
struct stat st;
result = stat(pathname.c_str(), &st);
#endif // _WIN32
if (result != 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 (!priv_check_hash(pathname, &st)) {
// 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;
#ifdef _WIN32
_wutime(pathname_w.c_str(), &utb);
#else // _WIN32
utime(pathname.c_str(), &utb);
#endif // _WIN32
return true;
}
/**
* 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 FileSpec::
full_verify(const string &package_dir) {
if (_actual_file != NULL) {
delete _actual_file;
_actual_file = NULL;
}
string pathname = get_pathname(package_dir);
int result = 1;
#ifdef _WIN32
struct _stat st;
wstring pathname_w;
if (string_to_wstring(pathname_w, pathname)) {
result = _wstat(pathname_w.c_str(), &st);
}
#else // _WIN32
struct stat st;
result = stat(pathname.c_str(), &st);
#endif // _WIN32
if (result != 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 (!priv_check_hash(pathname, &st)) {
// 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;
#ifdef _WIN32
_wutime(pathname_w.c_str(), &utb);
#else // _WIN32
utime(pathname.c_str(), &utb);
#endif // _WIN32
}
return true;
}
/**
* Returns a FileSpec that represents the actual data read on disk. This will
* read the disk to determine the data if necessary.
*/
const FileSpec *FileSpec::
force_get_actual_file(const string &pathname) {
if (_actual_file == NULL) {
#ifdef _WIN32
struct _stat st;
wstring pathname_w;
if (string_to_wstring(pathname_w, pathname)) {
_wstat(pathname_w.c_str(), &st);
}
#else // _WIN32
struct stat st;
stat(pathname.c_str(), &st);
#endif // _WIN32
priv_check_hash(pathname, &st);
}
return _actual_file;
}
/**
* Returns true if the file has the expected md5 hash, false otherwise.
*/
bool FileSpec::
check_hash(const string &pathname) const {
FileSpec other;
if (!other.read_hash(pathname)) {
return false;
}
return (memcmp(_hash, other._hash, hash_size) == 0);
}
/**
* Computes the hash from the indicated pathname and stores it within the
* FileSpec.
*/
bool FileSpec::
read_hash(const string &pathname) {
memset(_hash, 0, hash_size);
_got_hash = false;
ifstream stream;
#ifdef _WIN32
wstring pathname_w;
if (string_to_wstring(pathname_w, pathname)) {
stream.open(pathname_w.c_str(), ios::in | ios::binary);
}
#else // _WIN32
stream.open(pathname.c_str(), ios::in | ios::binary);
#endif // _WIN32
if (!stream) {
// cerr << "unable to read " << pathname << "\n";
return false;
}
MD5_CTX ctx;
MD5_Init(&ctx);
static const int buffer_size = 4096;
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(_hash, &ctx);
_got_hash = true;
return true;
}
/**
* Reads the hash from the next 16 bytes on the indicated istream, in the same
* unusual order observed by Panda's HashVal::read_stream() method.
*/
bool FileSpec::
read_hash_stream(istream &in) {
for (int i = 0; i < hash_size; i += 4) {
unsigned int a = in.get();
unsigned int b = in.get();
unsigned int c = in.get();
unsigned int d = in.get();
_hash[i + 0] = d;
_hash[i + 1] = c;
_hash[i + 2] = b;
_hash[i + 3] = a;
}
return !in.fail();
}
/**
* Returns < 0 if this hash sorts before the other hash, > 0 if it sorts
* after, 0 if they are the same.
*/
int FileSpec::
compare_hash(const FileSpec &other) const {
return memcmp(_hash, other._hash, hash_size);
}
/**
* Describes the data in the FileSpec.
*/
void FileSpec::
write(ostream &out) const {
out << "filename: " << _filename << ", " << _size << " bytes, "
<< asctime(localtime(&_timestamp));
// asctime includes a newline.
out << "hash: ";
stream_hex(out, _hash, hash_size);
out << "\n";
}
/**
* Writes just the hash code.
*/
void FileSpec::
output_hash(ostream &out) const {
stream_hex(out, _hash, hash_size);
}
/**
* Returns true if the file has the expected md5 hash, false otherwise.
* Updates _actual_file with the data read from disk, including the hash, for
* future reference.
*
* The parameter stp is a pointer to a stat structure. It's declared as a
* void * to get around issues with the nonstandard declaration of this
* structure in Windows.
*/
bool FileSpec::
priv_check_hash(const string &pathname, void *stp) {
const struct stat &st = *(const struct stat *)stp;
assert(_actual_file == NULL);
_actual_file = new FileSpec;
_actual_file->_filename = pathname;
_actual_file->_size = st.st_size;
_actual_file->_timestamp = st.st_mtime;
if (!_actual_file->read_hash(pathname)) {
return false;
}
return (memcmp(_hash, _actual_file->_hash, hash_size) == 0);
}
/**
* 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 FileSpec::
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;
}
/**
* 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 FileSpec::
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);
}
}
/**
* Writes the indicated buffer as a string of hex characters to the given
* ostream.
*/
void FileSpec::
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));
}
}