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 */
normpath(char * path)19 static void normpath(char *path)
20 {
21     while (path && *path) {
22         if (*path == '/')
23             *path = '\\';
24         ++path;
25     }
26 }
27 
ensure_directory(char * pathname,char * new_part,NOTIFYPROC notify)28 BOOL 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  */
map_new_file(DWORD flags,char * filename,char * pathname_part,int size,WORD wFatDate,WORD wFatTime,NOTIFYPROC notify)59 char *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 
150 BOOL
extract_file(char * dst,char * src,int method,int comp_size,int uncomp_size,NOTIFYPROC notify)151 extract_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  */
201 BOOL
unzip_archive(SCHEME * scheme,char * dirname,char * data,DWORD size,NOTIFYPROC notify)202 unzip_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