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