From 28ac2415edd04d2e5c8fbc9d8b04c7ad18839d78 Mon Sep 17 00:00:00 2001
From: Daira Hopwood <daira@jacaranda.org>
Date: Sat, 25 Oct 2014 21:04:56 +0100
Subject: [PATCH] WIP

Signed-off-by: Daira Hopwood <daira@jacaranda.org>
---
 .../windows/installer/installer/installer.cpp | 115 +++++++++++++++++-
 1 file changed, 110 insertions(+), 5 deletions(-)

diff --git a/misc/build_helpers/windows/installer/installer/installer.cpp b/misc/build_helpers/windows/installer/installer/installer.cpp
index e8c8e357..6fd38413 100644
--- a/misc/build_helpers/windows/installer/installer/installer.cpp
+++ b/misc/build_helpers/windows/installer/installer/installer.cpp
@@ -17,6 +17,7 @@
 
 int wmain(int argc, wchar_t *argv[]);
 void self_extract(wchar_t *destination_dir);
+unsigned __int64 read_uint64_le(unsigned char *b);
 bool have_acceptable_python();
 void install_python();
 wchar_t * get_default_destination_dir();
@@ -30,7 +31,16 @@ void fail(char *s);
 #define PYTHON_INSTALLER_32BIT (L"python-" INSTALL_PYTHON_VERSION L".msi")
 #define PYTHON_INSTALLER_64BIT (L"python-" INSTALL_PYTHON_VERSION L".amd64.msi")
 
+void noop_handler(const wchar_t * expression,
+                  const wchar_t * function,
+                  const wchar_t * file,
+                  unsigned int line,
+                  uintptr_t pReserved) {
+}
+
 int wmain(int argc, wchar_t *argv[]) {
+	_set_invalid_parameter_handler(noop_handler);
+
 	if (argc >= 2 && wcscmp(argv[1], L"--help") == 0) {
 		printf("installer <destination_dir>\n");
 	}
@@ -54,7 +64,97 @@ void self_extract(wchar_t *destination_dir) {
     GetModuleFileNameW(hModule, executable_path, MAX_PATH); 
     fail_unless(GetLastError() == ERROR_SUCCESS, "Could not get the path of the current executable.");
 
-	unzip(executable_path, destination_dir);
+	// shell32's zipped folder implementation is strict about the zip format and
+	// does not support unzipping a self-extracting exe directly. So we copy the
+	// original zip file that was appended to the exe to a temporary directory,
+	// and use shell32 to unzip it from there. To get the length of the zip file,
+	// we look at its "end of central directory record" (not to be confused with
+	// a "Zip64 end of central directory record"), which is documented at
+	// <http://www.pkware.com/documents/casestudies/APPNOTE.TXT>.
+	// For simplicity we only handle the case of a zip file that has no archive
+	// comment. This code is based loosely on the _EndRecData function in
+	// <https://hg.python.org/cpython/file/2.7/Lib/zipfile.py>.
+
+	// APPNOTE.TXT sections 4.3.15 and 4.3.16.
+	const size_t sizeof_zip64eocdl = 20,
+		         sizeof_eocd = 22;
+	unsigned char end_data[sizeof_zip64eocdl + sizeof_eocd];
+	unsigned char zip64eocdl_signature[]  = {0x50, 0x4B, 0x06, 0x07};
+	unsigned char eocd_signature[]        = {0x50, 0x4B, 0x05, 0x06};
+	unsigned char comment_length[]        = {0x00, 0x00};
+	unsigned char zip64eocdl_disk_num[]   = {0x00, 0x00, 0x00, 0x00};
+	unsigned char zip64eocdl_disk_count[] = {0x01, 0x00, 0x00, 0x00};
+
+	errno = 0;
+	FILE *f = _wfopen(L"C:\\tahoe\\foo.zip", L"rb");
+	fail_unless(f != NULL && errno == 0 && ferror(f) == 0,
+		        "Could not open executable file.");
+
+	_fseeki64(f, -(__int64) sizeof(end_data), SEEK_END);
+	fail_unless(errno == 0 && ferror(f) == 0,
+		        "Could not seek to end records.");
+
+	__int64 zip64eocdl_offset = _ftelli64(f);
+	fail_unless(errno == 0 && ferror(f) == 0 && zip64eocdl_offset >= 0,
+		        "Could not read position of end records.");
+
+	printf("zip64eocdl_offset = %ld\n", zip64eocdl_offset);
+	size_t n = fread(end_data, sizeof(end_data), 1, f);
+	printf("n = %ld\n", n);
+	for (size_t i = 0; i < sizeof(end_data); i++) {
+		printf("%02X ", end_data[i]);
+	}
+	printf("\n");
+	fail_unless(n == 1 && errno == 0 && ferror(f) == 0,
+		        "Could not read end records.");
+
+	fail_unless(memcmp(end_data + sizeof(end_data) - sizeof(comment_length),
+		               comment_length, sizeof(comment_length)) == 0,
+		        "Cannot read a zip file that has an archive comment.");
+
+	unsigned char *eocd = end_data + sizeof_zip64eocdl;
+	fail_unless(memcmp(eocd, eocd_signature, sizeof(eocd_signature)) == 0,
+		        "Could not find the end-of-central-directory signature.");
+
+	fail_unless(memcmp(end_data, zip64eocdl_signature, sizeof(zip64eocdl_signature)) == 0,
+		        "Could not find the zip64-end-of-central-directory-locator signature.");
+
+	fail_unless(memcmp(eocd + 4, zip64eocdl_disk_num, sizeof(zip64eocdl_disk_num)) == 0 &&
+		        memcmp(eocd + 6, zip64eocdl_disk_num, sizeof(zip64eocdl_disk_num)) == 0 &&
+		        memcmp(end_data + 4, zip64eocdl_disk_count, sizeof(zip64eocdl_disk_count)) == 0,
+		        "Cannot read a zipfile that spans disks.");
+
+    unsigned __int64 eocd_relative_offset = read_uint64_le(end_data + 8);
+	unsigned __int64 eocd_offset = zip64eocdl_offset + sizeof_zip64eocdl;
+	fail_unless(eocd_relative_offset <= 0x7FFFFFFFFFFFFFFFi64 && eocd_offset <= 0x7FFFFFFFFFFFFFFFi64,
+		        "Could not calculate zipfile offset due to potential integer overflow.");
+
+	__int64 zipfile_offset = eocd_offset - eocd_relative_offset;
+	fail_unless(zipfile_offset >= 0 && zipfile_offset <= zip64eocdl_offset,
+		        "Unexpected result from zipfile offset calculation.");
+
+	printf("zipfile_offset = %ld\n", zipfile_offset);
+	_fseeki64(f, zipfile_offset, SEEK_SET); 
+	fail_unless(errno == 0 && ferror(f) == 0,
+		        "Could not seek to zipfile offset.");
+
+	printf("%ld\n", zipfile_offset);
+	//unzip(L"C:\\tahoe\\foo.zip", destination_dir);
+}
+
+// read unsigned little-endian 64-bit integer
+unsigned __int64 read_uint64_le(unsigned char *b) {
+	return ((unsigned __int64) b[0]      ) |
+		   ((unsigned __int64) b[1] <<  8) |
+		   ((unsigned __int64) b[2] << 16) |
+		   ((unsigned __int64) b[3] << 24) |
+		   ((unsigned __int64) b[4] << 32) |
+		   ((unsigned __int64) b[5] << 40) |
+		   ((unsigned __int64) b[6] << 48) |
+		   ((unsigned __int64) b[7] << 56);
+}
+
+void read_from_end(FILE *f, size_t offset, unsigned char *dest, size_t length) {
 }
 
 bool have_acceptable_python() {
@@ -76,6 +176,8 @@ wchar_t * get_default_destination_dir() {
 }
 
 void unzip(wchar_t *zip_path, wchar_t *destination_dir) {
+	// Based on <https://social.msdn.microsoft.com/Forums/vstudio/en-US/45668d18-2840-4887-87e1-4085201f4103/visual-c-to-unzip-a-zip-file-to-a-specific-directory?forum=vclanguage>
+
 	wprintf(L"Extracting %ls\nto %ls\n", zip_path, destination_dir);
 
 	// SysAllocString: <http://msdn.microsoft.com/en-gb/library/windows/desktop/ms221458(v=vs.85).aspx>
@@ -103,17 +205,20 @@ void unzip(wchar_t *zip_path, wchar_t *destination_dir) {
 	// Folder.NameSpace: <http://msdn.microsoft.com/en-gb/library/windows/desktop/gg537721(v=vs.85).aspx>
 	Folder *zip_folder = NULL;
 	res = shell->NameSpace(zip_path_var, &zip_folder);
-	printf("%d", res);
-	fail_unless(zip_folder != NULL, "Could not create zip Folder object.");
+	fail_unless(res == S_OK && zip_folder != NULL, "Could not create zip Folder object.");
 
 	Folder *destination_folder = NULL;
-	shell->NameSpace(destination_dir_var, &destination_folder);
-	fail_unless(destination_folder != NULL, "Could not create destination Folder object.");
+	res = shell->NameSpace(destination_dir_var, &destination_folder);
+	fail_unless(res == S_OK && destination_folder != NULL, "Could not create destination Folder object.");
 
 	FolderItems *zip_folderitems = NULL;
 	zip_folder->Items(&zip_folderitems);
 	fail_unless(zip_folderitems != NULL, "Could not create zip FolderItems object.");
 
+	long files_count = 0;
+	zip_folderitems->get_Count(&files_count);
+	printf("count %d\n", files_count);
+
 	VARIANT zip_idispatch_var;
 	zip_idispatch_var.vt = VT_DISPATCH;
 	zip_idispatch_var.pdispVal = NULL;
-- 
2.45.2