1/*
2  IMPORTANT NOTE: IF THIS FILE IS CHANGED, PCBUILD\BDIST_WININST.VCXPROJ MUST
3  BE REBUILT AS WELL.
4
5  IF CHANGES TO THIS FILE ARE CHECKED IN, THE RECOMPILED BINARIES MUST BE
6  CHECKED IN AS WELL!
7*/
8
9#include <windows.h>
10
11#include "zlib.h"
12
13#include <stdio.h>
14#include <stdarg.h>
15
16#include "archive.h"
17
18/* Convert unix-path to dos-path */
19static void normpath(char *path)
20{
21    while (path && *path) {
22        if (*path == '/')
23            *path = '\\';
24        ++path;
25    }
26}
27
28BOOL ensure_directory(char *pathname, char *new_part, NOTIFYPROC notify)
29{
30    while (new_part && *new_part && (new_part = strchr(new_part, '\\'))) {
31        DWORD attr;
32        *new_part = '\0';
33        attr = GetFileAttributes(pathname);
34        if (attr == -1) {
35            /* nothing found */
36            if (!CreateDirectory(pathname, NULL) && notify)
37                notify(SYSTEM_ERROR,
38                       "CreateDirectory (%s)", pathname);
39            else
40                notify(DIR_CREATED, pathname);
41        }
42        if (attr & FILE_ATTRIBUTE_DIRECTORY) {
43            ;
44        } else {
45            SetLastError(183);
46            if (notify)
47                notify(SYSTEM_ERROR,
48                       "CreateDirectory (%s)", pathname);
49        }
50        *new_part = '\\';
51        ++new_part;
52    }
53    return TRUE;
54}
55
56/* XXX Should better explicitly specify
57 * uncomp_size and file_times instead of pfhdr!
58 */
59char *map_new_file(DWORD flags, char *filename,
60                   char *pathname_part, int size,
61                   WORD wFatDate, WORD wFatTime,
62                   NOTIFYPROC notify)
63{
64    HANDLE hFile, hFileMapping;
65    char *dst;
66    FILETIME ft;
67
68  try_again:
69    if (!flags)
70        flags = CREATE_NEW;
71    hFile = CreateFile(filename,
72                       GENERIC_WRITE | GENERIC_READ,
73                       0, NULL,
74                       flags,
75                       FILE_ATTRIBUTE_NORMAL, NULL);
76    if (hFile == INVALID_HANDLE_VALUE) {
77        DWORD x = GetLastError();
78        switch (x) {
79        case ERROR_FILE_EXISTS:
80            if (notify && notify(CAN_OVERWRITE, filename))
81                hFile = CreateFile(filename,
82                                   GENERIC_WRITE|GENERIC_READ,
83                                   0, NULL,
84                                   CREATE_ALWAYS,
85                                   FILE_ATTRIBUTE_NORMAL,
86                                   NULL);
87            else {
88                if (notify)
89                    notify(FILE_OVERWRITTEN, filename);
90                return NULL;
91            }
92            break;
93        case ERROR_PATH_NOT_FOUND:
94            if (ensure_directory(filename, pathname_part, notify))
95                goto try_again;
96            else
97                return FALSE;
98            break;
99        default:
100            SetLastError(x);
101            break;
102        }
103    }
104    if (hFile == INVALID_HANDLE_VALUE) {
105        if (notify)
106            notify (SYSTEM_ERROR, "CreateFile (%s)", filename);
107        return NULL;
108    }
109
110    if (notify)
111        notify(FILE_CREATED, filename);
112
113    DosDateTimeToFileTime(wFatDate, wFatTime, &ft);
114    SetFileTime(hFile, &ft, &ft, &ft);
115
116
117    if (size == 0) {
118        /* We cannot map a zero-length file (Also it makes
119           no sense */
120        CloseHandle(hFile);
121        return NULL;
122    }
123
124    hFileMapping = CreateFileMapping(hFile,
125                                     NULL, PAGE_READWRITE, 0, size, NULL);
126
127    CloseHandle(hFile);
128
129    if (hFileMapping == NULL) {
130        if (notify)
131            notify(SYSTEM_ERROR,
132                   "CreateFileMapping (%s)", filename);
133        return NULL;
134    }
135
136    dst = MapViewOfFile(hFileMapping,
137                        FILE_MAP_WRITE, 0, 0, 0);
138
139    CloseHandle(hFileMapping);
140
141    if (!dst) {
142        if (notify)
143            notify(SYSTEM_ERROR, "MapViewOfFile (%s)", filename);
144        return NULL;
145    }
146    return dst;
147}
148
149
150BOOL
151extract_file(char *dst, char *src, int method, int comp_size,
152             int uncomp_size, NOTIFYPROC notify)
153{
154    z_stream zstream;
155    int result;
156
157    if (method == Z_DEFLATED) {
158        int x;
159        memset(&zstream, 0, sizeof(zstream));
160        zstream.next_in = src;
161        zstream.avail_in = comp_size+1;
162        zstream.next_out = dst;
163        zstream.avail_out = uncomp_size;
164
165/* Apparently an undocumented feature of zlib: Set windowsize
166   to negative values to suppress the gzip header and be compatible with
167   zip! */
168        result = TRUE;
169        if (Z_OK != (x = inflateInit2(&zstream, -15))) {
170            if (notify)
171                notify(ZLIB_ERROR,
172                       "inflateInit2 returns %d", x);
173            result = FALSE;
174            goto cleanup;
175        }
176        if (Z_STREAM_END != (x = inflate(&zstream, Z_FINISH))) {
177            if (notify)
178                notify(ZLIB_ERROR,
179                       "inflate returns %d", x);
180            result = FALSE;
181        }
182      cleanup:
183        if (Z_OK != (x = inflateEnd(&zstream))) {
184            if (notify)
185                notify (ZLIB_ERROR,
186                    "inflateEnd returns %d", x);
187            result = FALSE;
188        }
189    } else if (method == 0) {
190        memcpy(dst, src, uncomp_size);
191        result = TRUE;
192    } else
193        result = FALSE;
194    UnmapViewOfFile(dst);
195    return result;
196}
197
198/* Open a zip-compatible archive and extract all files
199 * into the specified directory (which is assumed to exist)
200 */
201BOOL
202unzip_archive(SCHEME *scheme, char *dirname, char *data, DWORD size,
203              NOTIFYPROC notify)
204{
205    int n;
206    char pathname[MAX_PATH];
207    char *new_part;
208
209    /* read the end of central directory record */
210    struct eof_cdir *pe = (struct eof_cdir *)&data[size - sizeof
211                                                   (struct eof_cdir)];
212
213    int arc_start = size - sizeof (struct eof_cdir) - pe->nBytesCDir -
214        pe->ofsCDir;
215
216    /* set position to start of central directory */
217    int pos = arc_start + pe->ofsCDir;
218
219    /* make sure this is a zip file */
220    if (pe->tag != 0x06054b50)
221        return FALSE;
222
223    /* Loop through the central directory, reading all entries */
224    for (n = 0; n < pe->nTotalCDir; ++n) {
225        int i;
226        char *fname;
227        char *pcomp;
228        char *dst;
229        struct cdir *pcdir;
230        struct fhdr *pfhdr;
231
232        pcdir = (struct cdir *)&data[pos];
233        pfhdr = (struct fhdr *)&data[pcdir->ofs_local_header +
234                                     arc_start];
235
236        if (pcdir->tag != 0x02014b50)
237            return FALSE;
238        if (pfhdr->tag != 0x04034b50)
239            return FALSE;
240        pos += sizeof(struct cdir);
241        fname = (char *)&data[pos]; /* This is not null terminated! */
242        pos += pcdir->fname_length + pcdir->extra_length +
243            pcdir->comment_length;
244
245        pcomp = &data[pcdir->ofs_local_header
246                      + sizeof(struct fhdr)
247                      + arc_start
248                      + pfhdr->fname_length
249                      + pfhdr->extra_length];
250
251        /* dirname is the Python home directory (prefix) */
252        strcpy(pathname, dirname);
253        if (pathname[strlen(pathname)-1] != '\\')
254            strcat(pathname, "\\");
255        new_part = &pathname[lstrlen(pathname)];
256        /* we must now match the first part of the pathname
257         * in the archive to a component in the installation
258         * scheme (PURELIB, PLATLIB, HEADERS, SCRIPTS, or DATA)
259         * and replace this part by the one in the scheme to use
260         */
261        for (i = 0; scheme[i].name; ++i) {
262            if (0 == strnicmp(scheme[i].name, fname,
263                              strlen(scheme[i].name))) {
264                char *rest;
265                int len;
266
267                /* length of the replaced part */
268                int namelen = strlen(scheme[i].name);
269
270                strcat(pathname, scheme[i].prefix);
271
272                rest = fname + namelen;
273                len = pfhdr->fname_length - namelen;
274
275                if ((pathname[strlen(pathname)-1] != '\\')
276                    && (pathname[strlen(pathname)-1] != '/'))
277                    strcat(pathname, "\\");
278                /* Now that pathname ends with a separator,
279                 * we must make sure rest does not start with
280                 * an additional one.
281                 */
282                if ((rest[0] == '\\') || (rest[0] == '/')) {
283                    ++rest;
284                    --len;
285                }
286
287                strncat(pathname, rest, len);
288                goto Done;
289            }
290        }
291        /* no prefix to replace found, go unchanged */
292        strncat(pathname, fname, pfhdr->fname_length);
293      Done:
294        normpath(pathname);
295        if (pathname[strlen(pathname)-1] != '\\') {
296            /*
297             * The local file header (pfhdr) does not always
298             * contain the compressed and uncompressed sizes of
299             * the data depending on bit 3 of the flags field.  So
300             * it seems better to use the data from the central
301             * directory (pcdir).
302             */
303            dst = map_new_file(0, pathname, new_part,
304                               pcdir->uncomp_size,
305                               pcdir->last_mod_file_date,
306                               pcdir->last_mod_file_time, notify);
307            if (dst) {
308                if (!extract_file(dst, pcomp, pfhdr->method,
309                                  pcdir->comp_size,
310                                  pcdir->uncomp_size,
311                                  notify))
312                    return FALSE;
313            } /* else ??? */
314        }
315        if (notify)
316            notify(NUM_FILES, new_part, (int)pe->nTotalCDir,
317                   (int)n+1);
318    }
319    return TRUE;
320}
321