75cb164ea9b355ec6ea333c814a99c52502b9873
[src/app-framework-main.git] / src / wgtpkg-zip.c
1 /*
2  Copyright 2015 IoT.bzh
3
4  author: José Bollo <jose.bollo@iot.bzh>
5
6  Licensed under the Apache License, Version 2.0 (the "License");
7  you may not use this file except in compliance with the License.
8  You may obtain a copy of the License at
9
10      http://www.apache.org/licenses/LICENSE-2.0
11
12  Unless required by applicable law or agreed to in writing, software
13  distributed under the License is distributed on an "AS IS" BASIS,
14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  See the License for the specific language governing permissions and
16  limitations under the License.
17 */
18
19 #define _BSD_SOURCE /* see readdir */
20
21 #include <limits.h>
22 #include <zip.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <errno.h>
27 #include <assert.h>
28 #include <dirent.h>
29 #include <string.h>
30 #include <unistd.h>
31
32 #include "verbose.h"
33 #include "wgtpkg.h"
34
35
36 #if !defined(MODE_OF_FILE_CREATION)
37 #define MODE_OF_FILE_CREATION 0640
38 #endif
39 #if !defined(MODE_OF_DIRECTORY_CREATION)
40 #define MODE_OF_DIRECTORY_CREATION 0750
41 #endif
42
43 static int is_valid_filename(const char *filename)
44 {
45         int lastsp = 0;
46         int index = 0;
47         unsigned char c;
48
49         c = (unsigned char)filename[index];
50         while (c) {
51                 if ((c < 0x1f)
52                  || ((lastsp = (c == 0x20)) && index == 0)
53                  || c == 0x7f || c == 0x3c || c == 0x3e
54                  || c == 0x3a || c == 0x22 
55                  || c == 0x5c || c == 0x7c || c == 0x3f
56                  || c == 0x2a || c == 0x5e || c == 0x60
57                  || c == 0x7b || c == 0x7d || c == 0x21)
58                         return 0;
59                 c = (unsigned char)filename[++index];
60         }
61         return !lastsp;
62 }
63
64 static int create_directory(char *file, int mode)
65 {
66         int rc;
67         char *last = strrchr(file, '/');
68         if (last != NULL)
69                 *last = 0;
70         rc = mkdirat(workdirfd, file, mode);
71         if (rc) {
72                 if (errno == EEXIST)
73                         rc = 0;
74                 else if (errno == ENOENT) {
75                         rc = create_directory(file, mode);
76                         if (!rc)
77                                 rc = mkdirat(workdirfd, file, mode);
78                 }
79         }
80         if (rc)
81                 ERROR("can't create directory %s", file);
82         if (last != NULL)
83                 *last = '/';
84         return rc;
85 }
86
87 static int create_file(char *file, int fmode, int dmode)
88 {
89         int fd = openat(workdirfd, file, O_CREAT|O_WRONLY|O_TRUNC, fmode);
90         if (fd < 0 && errno == ENOENT) {
91                 if (!create_directory(file, dmode))
92                         fd = openat(workdirfd, file, O_CREAT|O_WRONLY|O_TRUNC, fmode);
93         }
94         if (fd < 0)
95                 ERROR("can't create file %s", file);
96         return fd;
97 }
98
99 /* read (extract) 'zipfile' in current directory */
100 int zread(const char *zipfile, unsigned long long maxsize)
101 {
102         struct filedesc *fdesc;
103         int err, fd, len;
104         struct zip *zip;
105         zip_int64_t z64;
106         unsigned int count, index;
107         struct zip_file *zfile;
108         struct zip_stat zstat;
109         char buffer[32768];
110         ssize_t sizr, sizw;
111         size_t esize;
112
113         /* open the zip file */
114         zip = zip_open(zipfile, ZIP_CHECKCONS, &err);
115         if (!zip) {
116                 ERROR("Can't connect to file %s", zipfile);
117                 return -1;
118         }
119
120         z64 = zip_get_num_entries(zip, 0);
121         if (z64 < 0 || z64 > UINT_MAX) {
122                 ERROR("too many entries in %s", zipfile);
123                 goto error;
124         }
125         count = (unsigned int)z64;
126
127         /* records the files */
128         file_reset();
129         esize = 0;
130         for (index = 0 ; index < count ; index++) {
131                 err = zip_stat_index(zip, index, ZIP_FL_ENC_GUESS, &zstat);
132                 /* check the file name */
133                 if (!is_valid_filename(zstat.name)) {
134                         ERROR("invalid entry %s found in %s", zstat.name, zipfile);
135                         goto error;
136                 }
137                 if (zstat.name[0] == '/') {
138                         ERROR("absolute entry %s found in %s", zstat.name, zipfile);
139                         goto error;
140                 }
141                 len = strlen(zstat.name);
142                 if (len == 0) {
143                         ERROR("empty entry found in %s", zipfile);
144                         goto error;
145                 }
146                 if (zstat.size == 0) {
147                         /* directory name */
148                         if (zstat.name[len - 1] != '/') {
149                                 ERROR("bad directory name %s in %s", zstat.name, zipfile);
150                                 goto error;
151                         }
152                         /* record */
153                         fdesc = file_add_directory(zstat.name);
154                 } else {
155                         /* directory name */
156                         if (zstat.name[len - 1] == '/') {
157                                 ERROR("bad file name %s in %s", zstat.name, zipfile);
158                                 goto error;
159                         }
160                         /* get the size */
161                         esize += zstat.size;
162                         /* record */
163                         fdesc = file_add_file(zstat.name);
164                 }
165                 if (!fdesc)
166                         goto error;
167                 fdesc->zindex = index;
168         }
169
170         /* check the size */
171         if (maxsize && esize > maxsize) {
172                 ERROR("extracted size %zu greater than allowed size %llu", esize, maxsize);
173                 goto error;
174         }
175
176         /* unpack the recorded files */
177         assert(count == file_count());
178         for (index = 0 ; index < count ; index++) {
179                 fdesc = file_of_index(index);
180                 assert(fdesc != NULL);
181                 err = zip_stat_index(zip, fdesc->zindex, ZIP_FL_ENC_GUESS, &zstat);
182                 assert(zstat.name[0] != '/');
183                 if (zstat.size == 0) {
184                         /* directory name */
185                         err = create_directory((char*)zstat.name, MODE_OF_DIRECTORY_CREATION);
186                         if (err && errno != EEXIST)
187                                 goto error;
188                 } else {
189                         /* file name */
190                         zfile = zip_fopen_index(zip, fdesc->zindex, 0);
191                         if (!zfile) {
192                                 ERROR("Can't open %s in %s", zstat.name, zipfile);
193                                 goto error;
194                         }
195                         fd = create_file((char*)zstat.name, MODE_OF_FILE_CREATION, MODE_OF_DIRECTORY_CREATION);
196                         if (fd < 0)
197                                 goto errorz;
198                         /* extract */
199                         z64 = zstat.size;
200                         while (z64) {
201                                 sizr = zip_fread(zfile, buffer, sizeof buffer);
202                                 if (sizr < 0) {
203                                         ERROR("error while reading %s in %s", zstat.name, zipfile);
204                                         goto errorzf;
205                                 }
206                                 sizw = write(fd, buffer, sizr);
207                                 if (sizw < 0) {
208                                         ERROR("error while writing %s", zstat.name);
209                                         goto errorzf;
210                                 }
211                                 z64 -= sizw;
212                         }
213                         close(fd);
214                         zip_fclose(zfile);
215                 }
216         }
217
218         zip_close(zip);
219         return 0;
220
221 errorzf:
222         close(fd);
223 errorz:
224         zip_fclose(zfile);
225 error:
226         zip_close(zip);
227         return -1;
228 }
229
230 struct zws {
231         struct zip *zip;
232         char name[PATH_MAX];
233         char buffer[32768];
234 };
235
236 static int zwr(struct zws *zws, int offset)
237 {
238         int len, err, fd;
239         DIR *dir;
240         struct dirent *ent;
241         zip_int64_t z64;
242         struct zip_source *zsrc;
243         FILE *fp;
244
245         fd = openat(workdirfd, offset ? zws->name : ".", O_DIRECTORY|O_RDONLY);
246         if (fd < 0) {
247                 ERROR("opendir %.*s failed in zwr", offset, zws->name);
248                 return -1;
249         }
250         dir = fdopendir(fd);
251         if (!dir) {
252                 close(fd);
253                 ERROR("opendir %.*s failed in zwr", offset, zws->name);
254                 return -1;
255         }
256
257         if (offset != 0)
258                 zws->name[offset++] = '/';
259
260         ent = readdir(dir);
261         while (ent != NULL) {
262                 len = strlen(ent->d_name);
263                 if (ent->d_name[0] == '.' && (len == 1 || 
264                         (ent->d_name[1] == '.' && len == 2)))
265                         ;
266                 else if (offset + len >= sizeof(zws->name)) {
267                         ERROR("name too long in zwr");
268                         errno = ENAMETOOLONG;
269                         goto error;
270                 } else {
271                         memcpy(zws->name + offset, ent->d_name, 1+len);
272                         if (!is_valid_filename(ent->d_name)) {
273                                 ERROR("invalid name %s", zws->name);
274                                 goto error;
275                         }
276                         switch (ent->d_type) {
277                         case DT_DIR:
278                                 z64 = zip_dir_add(zws->zip, zws->name, ZIP_FL_ENC_UTF_8);
279                                 if (z64 < 0) {
280                                         ERROR("zip_dir_add of %s failed", zws->name);
281                                         goto error;
282                                 }
283                                 err = zwr(zws, offset + len);
284                                 if (err)
285                                         goto error;
286                                 break;
287                         case DT_REG:
288                                 fd = openat(workdirfd, zws->name, O_RDONLY);
289                                 if (fd < 0) {
290                                         ERROR("openat of %s failed", zws->name);
291                                         goto error;
292                                 }
293                                 fp = fdopen(fd, "r");
294                                 if (fp == NULL) {
295                                         ERROR("fdopen of %s failed", zws->name);
296                                         close(fd);
297                                         goto error;
298                                 }
299                                 zsrc = zip_source_filep(zws->zip, fp, 0, 0);
300                                 if (zsrc == NULL) {
301                                         ERROR("zip_source_file of %s failed", zws->name);
302                                         fclose(fp);
303                                         goto error;
304                                 }
305                                 z64 = zip_file_add(zws->zip, zws->name, zsrc, ZIP_FL_ENC_UTF_8);
306                                 if (z64 < 0) {
307                                         ERROR("zip_file_add of %s failed", zws->name);
308                                         zip_source_free(zsrc);
309                                         goto error;
310                                 }
311                                 break;
312                         default:
313                                 break;
314                         }
315                 }
316                 ent = readdir(dir);
317         }
318
319         closedir(dir);
320         return 0;
321 error:
322         closedir(dir);
323         return -1;
324 }
325
326 /* write (pack) content of the current directory in 'zipfile' */
327 int zwrite(const char *zipfile)
328 {
329         int err;
330         struct zws zws;
331
332         zws.zip = zip_open(zipfile, ZIP_CREATE|ZIP_TRUNCATE, &err);
333         if (!zws.zip) {
334                 ERROR("Can't open %s for write", zipfile);
335                 return -1;
336         }
337
338         err = zwr(&zws, 0);
339         zip_close(zws.zip);
340         return err;
341 }
342
343
344 #if defined(TEST_READ)
345 int main(int ac, char **av)
346 {
347         for(av++ ; *av ; av++)
348                 zread(*av);
349         return 0;
350 }
351 #endif
352
353 #if defined(TEST_WRITE)
354 int main(int ac, char **av)
355 {
356         for(av++ ; *av ; av++)
357                 zwrite(*av);
358         return 0;
359 }
360 #endif
361