/* IMPORTANT NOTE: IF THIS FILE IS CHANGED, WININST-6.EXE MUST BE RECOMPILED WITH THE MSVC6 WININST.DSW WORKSPACE FILE MANUALLY, AND WININST-7.1.EXE MUST BE RECOMPILED WITH THE MSVC 2003.NET WININST-7.1.VCPROJ FILE MANUALLY. IF CHANGES TO THIS FILE ARE CHECKED INTO PYTHON CVS, THE RECOMPILED BINARIES MUST BE CHECKED IN AS WELL! */ #include #include "zlib.h" #include #include #include "archive.h" /* Convert unix-path to dos-path */ static void normpath(char *path) { while (path && *path) { if (*path == '/') *path = '\\'; ++path; } } BOOL ensure_directory(char *pathname, char *new_part, NOTIFYPROC notify) { while (new_part && *new_part && (new_part = strchr(new_part, '\\'))) { DWORD attr; *new_part = '\0'; attr = GetFileAttributes(pathname); if (attr == -1) { /* nothing found */ if (!CreateDirectory(pathname, NULL) && notify) notify(SYSTEM_ERROR, "CreateDirectory (%s)", pathname); else notify(DIR_CREATED, pathname); } if (attr & FILE_ATTRIBUTE_DIRECTORY) { ; } else { SetLastError(183); if (notify) notify(SYSTEM_ERROR, "CreateDirectory (%s)", pathname); } *new_part = '\\'; ++new_part; } return TRUE; } /* XXX Should better explicitely specify * uncomp_size and file_times instead of pfhdr! */ char *map_new_file(DWORD flags, char *filename, char *pathname_part, int size, WORD wFatDate, WORD wFatTime, NOTIFYPROC notify) { HANDLE hFile, hFileMapping; char *dst; FILETIME ft; try_again: if (!flags) flags = CREATE_NEW; hFile = CreateFile(filename, GENERIC_WRITE | GENERIC_READ, 0, NULL, flags, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { DWORD x = GetLastError(); switch (x) { case ERROR_FILE_EXISTS: if (notify && notify(CAN_OVERWRITE, filename)) hFile = CreateFile(filename, GENERIC_WRITE|GENERIC_READ, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); else { if (notify) notify(FILE_OVERWRITTEN, filename); return NULL; } break; case ERROR_PATH_NOT_FOUND: if (ensure_directory(filename, pathname_part, notify)) goto try_again; else return FALSE; break; default: SetLastError(x); break; } } if (hFile == INVALID_HANDLE_VALUE) { if (notify) notify (SYSTEM_ERROR, "CreateFile (%s)", filename); return NULL; } if (notify) notify(FILE_CREATED, filename); DosDateTimeToFileTime(wFatDate, wFatTime, &ft); SetFileTime(hFile, &ft, &ft, &ft); if (size == 0) { /* We cannot map a zero-length file (Also it makes no sense */ CloseHandle(hFile); return NULL; } hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, size, NULL); CloseHandle(hFile); if (hFileMapping == INVALID_HANDLE_VALUE) { if (notify) notify(SYSTEM_ERROR, "CreateFileMapping (%s)", filename); return NULL; } dst = MapViewOfFile(hFileMapping, FILE_MAP_WRITE, 0, 0, 0); CloseHandle(hFileMapping); if (!dst) { if (notify) notify(SYSTEM_ERROR, "MapViewOfFile (%s)", filename); return NULL; } return dst; } BOOL extract_file(char *dst, char *src, int method, int comp_size, int uncomp_size, NOTIFYPROC notify) { z_stream zstream; int result; if (method == Z_DEFLATED) { int x; memset(&zstream, 0, sizeof(zstream)); zstream.next_in = src; zstream.avail_in = comp_size+1; zstream.next_out = dst; zstream.avail_out = uncomp_size; /* Apparently an undocumented feature of zlib: Set windowsize to negative values to supress the gzip header and be compatible with zip! */ result = TRUE; if (Z_OK != (x = inflateInit2(&zstream, -15))) { if (notify) notify(ZLIB_ERROR, "inflateInit2 returns %d", x); result = FALSE; goto cleanup; } if (Z_STREAM_END != (x = inflate(&zstream, Z_FINISH))) { if (notify) notify(ZLIB_ERROR, "inflate returns %d", x); result = FALSE; } cleanup: if (Z_OK != (x = inflateEnd(&zstream))) { if (notify) notify (ZLIB_ERROR, "inflateEnd returns %d", x); result = FALSE; } } else if (method == 0) { memcpy(dst, src, uncomp_size); result = TRUE; } else result = FALSE; UnmapViewOfFile(dst); return result; } /* Open a zip-compatible archive and extract all files * into the specified directory (which is assumed to exist) */ BOOL unzip_archive(SCHEME *scheme, char *dirname, char *data, DWORD size, NOTIFYPROC notify) { int n; char pathname[MAX_PATH]; char *new_part; /* read the end of central directory record */ struct eof_cdir *pe = (struct eof_cdir *)&data[size - sizeof (struct eof_cdir)]; int arc_start = size - sizeof (struct eof_cdir) - pe->nBytesCDir - pe->ofsCDir; /* set position to start of central directory */ int pos = arc_start + pe->ofsCDir; /* make sure this is a zip file */ if (pe->tag != 0x06054b50) return FALSE; /* Loop through the central directory, reading all entries */ for (n = 0; n < pe->nTotalCDir; ++n) { int i; char *fname; char *pcomp; char *dst; struct cdir *pcdir; struct fhdr *pfhdr; pcdir = (struct cdir *)&data[pos]; pfhdr = (struct fhdr *)&data[pcdir->ofs_local_header + arc_start]; if (pcdir->tag != 0x02014b50) return FALSE; if (pfhdr->tag != 0x04034b50) return FALSE; pos += sizeof(struct cdir); fname = (char *)&data[pos]; /* This is not null terminated! */ pos += pcdir->fname_length + pcdir->extra_length + pcdir->comment_length; pcomp = &data[pcdir->ofs_local_header + sizeof(struct fhdr) + arc_start + pfhdr->fname_length + pfhdr->extra_length]; /* dirname is the Python home directory (prefix) */ strcpy(pathname, dirname); if (pathname[strlen(pathname)-1] != '\\') strcat(pathname, "\\"); new_part = &pathname[lstrlen(pathname)]; /* we must now match the first part of the pathname * in the archive to a component in the installation * scheme (PURELIB, PLATLIB, HEADERS, SCRIPTS, or DATA) * and replace this part by the one in the scheme to use */ for (i = 0; scheme[i].name; ++i) { if (0 == strnicmp(scheme[i].name, fname, strlen(scheme[i].name))) { char *rest; int len; /* length of the replaced part */ int namelen = strlen(scheme[i].name); strcat(pathname, scheme[i].prefix); rest = fname + namelen; len = pfhdr->fname_length - namelen; if ((pathname[strlen(pathname)-1] != '\\') && (pathname[strlen(pathname)-1] != '/')) strcat(pathname, "\\"); /* Now that pathname ends with a separator, * we must make sure rest does not start with * an additional one. */ if ((rest[0] == '\\') || (rest[0] == '/')) { ++rest; --len; } strncat(pathname, rest, len); goto Done; } } /* no prefix to replace found, go unchanged */ strncat(pathname, fname, pfhdr->fname_length); Done: normpath(pathname); if (pathname[strlen(pathname)-1] != '\\') { /* * The local file header (pfhdr) does not always * contain the compressed and uncompressed sizes of * the data depending on bit 3 of the flags field. So * it seems better to use the data from the central * directory (pcdir). */ dst = map_new_file(0, pathname, new_part, pcdir->uncomp_size, pcdir->last_mod_file_date, pcdir->last_mod_file_time, notify); if (dst) { if (!extract_file(dst, pcomp, pfhdr->method, pcdir->comp_size, pcdir->uncomp_size, notify)) return FALSE; } /* else ??? */ } if (notify) notify(NUM_FILES, new_part, (int)pe->nTotalCDir, (int)n+1); } return TRUE; }