refactoring sources
[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 "wgtpkg.h"
32
33
34 #if !defined(MODE_OF_FILE_CREATION)
35 #define MODE_OF_FILE_CREATION 0640
36 #endif
37 #if !defined(MODE_OF_DIRECTORY_CREATION)
38 #define MODE_OF_DIRECTORY_CREATION 0750
39 #endif
40
41 static int is_valid_filename(const char *filename)
42 {
43         int lastsp = 0;
44         int index = 0;
45         unsigned char c;
46
47         c = (unsigned char)filename[index];
48         while (c) {
49                 if ((c < 0x1f)
50                  || ((lastsp = (c == 0x20)) && index == 0)
51                  || c == 0x7f || c == 0x3c || c == 0x3e
52                  || c == 0x3a || c == 0x22 
53                  || c == 0x5c || c == 0x7c || c == 0x3f
54                  || c == 0x2a || c == 0x5e || c == 0x60
55                  || c == 0x7b || c == 0x7d || c == 0x21)
56                         return 0;
57                 c = (unsigned char)filename[++index];
58         }
59         return !lastsp;
60 }
61
62 static int create_directory(char *file, int mode)
63 {
64         int rc;
65         char *last = strrchr(file, '/');
66         if (last != NULL)
67                 *last = 0;
68         rc = mkdir(file, mode);
69         if (rc) {
70                 if (errno == EEXIST)
71                         rc = 0;
72                 else if (errno == ENOENT) {
73                         rc = create_directory(file, mode);
74                         if (!rc)
75                                 rc = mkdir(file, mode);
76                 }
77         }
78         if (rc)
79                 syslog(LOG_ERR, "can't create directory %s", file);
80         if (last != NULL)
81                 *last = '/';
82         return rc;
83 }
84
85 static int create_file(char *file, int fmode, int dmode)
86 {
87         int fd = creat(file, fmode);
88         if (fd < 0 && errno == ENOENT) {
89                 if (!create_directory(file, dmode))
90                         fd = creat(file, fmode);
91         }
92         if (fd < 0)
93                 syslog(LOG_ERR, "can't create file %s", file);
94         return fd;
95 }
96
97 /* read (extract) 'zipfile' in current directory */
98 int zread(const char *zipfile, unsigned long long maxsize)
99 {
100         struct filedesc *fdesc;
101         int err, fd, len;
102         struct zip *zip;
103         zip_int64_t z64;
104         unsigned int count, index;
105         struct zip_file *zfile;
106         struct zip_stat zstat;
107         char buffer[32768];
108         ssize_t sizr, sizw;
109         size_t esize;
110
111         /* open the zip file */
112         zip = zip_open(zipfile, ZIP_CHECKCONS, &err);
113         if (!zip) {
114                 syslog(LOG_ERR, "Can't connect to file %s", zipfile);
115                 return -1;
116         }
117
118         z64 = zip_get_num_entries(zip, 0);
119         if (z64 < 0 || z64 > UINT_MAX) {
120                 syslog(LOG_ERR, "too many entries in %s", zipfile);
121                 goto error;
122         }
123         count = (unsigned int)z64;
124
125         /* records the files */
126         file_reset();
127         esize = 0;
128         for (index = 0 ; index < count ; index++) {
129                 err = zip_stat_index(zip, index, ZIP_FL_ENC_GUESS, &zstat);
130                 /* check the file name */
131                 if (!is_valid_filename(zstat.name)) {
132                         syslog(LOG_ERR, "invalid entry %s found in %s", zstat.name, zipfile);
133                         goto error;
134                 }
135                 if (zstat.name[0] == '/') {
136                         syslog(LOG_ERR, "absolute entry %s found in %s", zstat.name, zipfile);
137                         goto error;
138                 }
139                 len = strlen(zstat.name);
140                 if (len == 0) {
141                         syslog(LOG_ERR, "empty entry found in %s", zipfile);
142                         goto error;
143                 }
144                 if (zstat.size == 0) {
145                         /* directory name */
146                         if (zstat.name[len - 1] != '/') {
147                                 syslog(LOG_ERR, "bad directory name %s in %s", zstat.name, zipfile);
148                                 goto error;
149                         }
150                         /* record */
151                         fdesc = file_add_directory(zstat.name);
152                 } else {
153                         /* directory name */
154                         if (zstat.name[len - 1] == '/') {
155                                 syslog(LOG_ERR, "bad file name %s in %s", zstat.name, zipfile);
156                                 goto error;
157                         }
158                         /* get the size */
159                         esize += zstat.size;
160                         /* record */
161                         fdesc = file_add_file(zstat.name);
162                 }
163                 if (!fdesc)
164                         goto error;
165                 fdesc->zindex = index;
166         }
167
168         /* check the size */
169         if (maxsize && esize > maxsize) {
170                 syslog(LOG_ERR, "extracted size %zu greater than allowed size %llu", esize, maxsize);
171                 goto error;
172         }
173
174         /* unpack the recorded files */
175         assert(count == file_count());
176         for (index = 0 ; index < count ; index++) {
177                 fdesc = file_of_index(index);
178                 assert(fdesc != NULL);
179                 err = zip_stat_index(zip, fdesc->zindex, ZIP_FL_ENC_GUESS, &zstat);
180                 assert(zstat.name[0] != '/');
181                 if (zstat.size == 0) {
182                         /* directory name */
183                         err = create_directory((char*)zstat.name, MODE_OF_DIRECTORY_CREATION);
184                         if (err && errno != EEXIST)
185                                 goto error;
186                 } else {
187                         /* file name */
188                         zfile = zip_fopen_index(zip, fdesc->zindex, 0);
189                         if (!zfile) {
190                                 syslog(LOG_ERR, "Can't open %s in %s", zstat.name, zipfile);
191                                 goto error;
192                         }
193                         fd = create_file((char*)zstat.name, MODE_OF_FILE_CREATION, MODE_OF_DIRECTORY_CREATION);
194                         if (fd < 0)
195                                 goto errorz;
196                         /* extract */
197                         z64 = zstat.size;
198                         while (z64) {
199                                 sizr = zip_fread(zfile, buffer, sizeof buffer);
200                                 if (sizr < 0) {
201                                         syslog(LOG_ERR, "error while reading %s in %s", zstat.name, zipfile);
202                                         goto errorzf;
203                                 }
204                                 sizw = write(fd, buffer, sizr);
205                                 if (sizw < 0) {
206                                         syslog(LOG_ERR, "error while writing %s", zstat.name);
207                                         goto errorzf;
208                                 }
209                                 z64 -= sizw;
210                         }
211                         close(fd);
212                         zip_fclose(zfile);
213                 }
214         }
215
216         zip_close(zip);
217         return 0;
218
219 errorzf:
220         close(fd);
221 errorz:
222         zip_fclose(zfile);
223 error:
224         zip_close(zip);
225         return -1;
226 }
227
228 struct zws {
229         struct zip *zip;
230         char name[PATH_MAX];
231         char buffer[32768];
232 };
233
234 static int zwr(struct zws *zws, int offset)
235 {
236         int len, err;
237         DIR *dir;
238         struct dirent *ent;
239         zip_int64_t z64;
240         struct zip_source *zsrc;
241
242         if (offset == 0)
243                 dir = opendir(".");
244         else {
245                 dir = opendir(zws->name);
246                 zws->name[offset++] = '/';
247         }
248         if (!dir) {
249                 syslog(LOG_ERR, "opendir %.*s failed in zwr", offset, zws->name);
250                 return -1;
251         }
252
253         ent = readdir(dir);
254         while (ent != NULL) {
255                 len = strlen(ent->d_name);
256                 if (ent->d_name[0] == '.' && (len == 1 || 
257                         (ent->d_name[1] == '.' && len == 2)))
258                         ;
259                 else if (offset + len >= sizeof(zws->name)) {
260                         syslog(LOG_ERR, "name too long in zwr");
261                         errno = ENAMETOOLONG;
262                         goto error;
263                 } else {
264                         memcpy(zws->name + offset, ent->d_name, 1+len);
265                         if (!is_valid_filename(ent->d_name)) {
266                                 syslog(LOG_ERR, "invalid name %s", zws->name);
267                                 goto error;
268                         }
269                         switch (ent->d_type) {
270                         case DT_DIR:
271                                 z64 = zip_dir_add(zws->zip, zws->name, ZIP_FL_ENC_UTF_8);
272                                 if (z64 < 0) {
273                                         syslog(LOG_ERR, "zip_dir_add of %s failed", zws->name);
274                                         goto error;
275                                 }
276                                 err = zwr(zws, offset + len);
277                                 if (err)
278                                         goto error;
279                                 break;
280                         case DT_REG:
281                                 zsrc = zip_source_file(zws->zip, zws->name, 0, 0);
282                                 if (zsrc == NULL) {
283                                         syslog(LOG_ERR, "zip_source_file of %s failed", zws->name);
284                                         goto error;
285                                 }
286                                 z64 = zip_file_add(zws->zip, zws->name, zsrc, ZIP_FL_ENC_UTF_8);
287                                 if (z64 < 0) {
288                                         syslog(LOG_ERR, "zip_file_add of %s failed", zws->name);
289                                         zip_source_free(zsrc);
290                                         goto error;
291                                 }
292                                 break;
293                         default:
294                                 break;
295                         }
296                 }
297                 ent = readdir(dir);
298         }
299
300         closedir(dir);
301         return 0;
302 error:
303         closedir(dir);
304         return -1;
305 }
306
307 /* write (pack) content of the current directory in 'zipfile' */
308 int zwrite(const char *zipfile)
309 {
310         int err;
311         struct zws zws;
312
313         zws.zip = zip_open(zipfile, ZIP_CREATE|ZIP_TRUNCATE, &err);
314         if (!zws.zip) {
315                 syslog(LOG_ERR, "Can't open %s for write", zipfile);
316                 return -1;
317         }
318
319         err = zwr(&zws, 0);
320         zip_close(zws.zip);
321         return err;
322 }
323
324
325 #if defined(TEST_READ)
326 int main(int ac, char **av)
327 {
328         for(av++ ; *av ; av++)
329                 zread(*av);
330         return 0;
331 }
332 #endif
333
334 #if defined(TEST_WRITE)
335 int main(int ac, char **av)
336 {
337         for(av++ ; *av ; av++)
338                 zwrite(*av);
339         return 0;
340 }
341 #endif
342