Provision argument for handling language
[src/app-framework-main.git] / src / wgt.c
1 /*
2  Copyright 2015, 2016, 2017 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 _GNU_SOURCE
20
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <unistd.h>
24 #include <fcntl.h>
25 #include <errno.h>
26 #include <assert.h>
27 #include <limits.h>
28 #include <string.h>
29 #include <ctype.h>
30
31
32 #include "wgt.h"
33
34 struct wgt {
35         int refcount;
36         int rootfd;
37         unsigned int nrlocales;
38         char **locales;
39 };
40
41 /* a valid subpath is a relative path not looking deeper than root using .. */
42 static int validsubpath(const char *subpath)
43 {
44         int l = 0, i = 0;
45
46         /* absolute path is not valid */
47         if (subpath[i] == '/')
48                 return 0;
49
50         /* inspect the path */
51         while(subpath[i]) {
52                 switch(subpath[i++]) {
53                 case '.':
54                         if (!subpath[i])
55                                 break;
56                         if (subpath[i] == '/') {
57                                 i++;
58                                 break;
59                         }
60                         if (subpath[i++] == '.') {
61                                 if (!subpath[i]) {
62                                         l--;
63                                         break;
64                                 }
65                                 if (subpath[i++] == '/') {
66                                         l--;
67                                         break;
68                                 }
69                         }
70                 default:
71                         while(subpath[i] && subpath[i] != '/')
72                                 i++;
73                         if (l >= 0)
74                                 l++;
75                 case '/':
76                         break;
77                 }
78         }
79         return l >= 0;
80 }
81
82 /*
83  * Normalizes and checks the 'subpath'.
84  * Removes any starting '/' and checks that 'subpath'
85  * does not contains sequence of '..' going deeper than
86  * root.
87  * Returns the normalized subpath or NULL in case of
88  * invalid subpath.
89  */
90 static const char *normalsubpath(const char *subpath)
91 {
92         while(*subpath == '/')
93                 subpath++;
94         return validsubpath(subpath) ? subpath : NULL;
95 }
96
97 /*
98  * Creates a wgt handler and returns it or return NULL
99  * in case of memory depletion.
100  */
101 struct wgt *wgt_create()
102 {
103         struct wgt *wgt = malloc(sizeof * wgt);
104         if (!wgt)
105                 errno = ENOMEM;
106         else {
107                 wgt->refcount = 1;
108                 wgt->rootfd = -1;
109                 wgt->nrlocales = 0;
110                 wgt->locales = NULL;
111         }
112         return wgt;
113 }
114
115 /*
116  * Adds a reference to 'wgt'
117  */
118 void wgt_addref(struct wgt *wgt)
119 {
120         assert(wgt);
121         wgt->refcount++;
122 }
123
124 /*
125  * Drops a reference to 'wgt' and destroys it
126  * if not more referenced
127  */
128 void wgt_unref(struct wgt *wgt)
129 {
130         assert(wgt);
131         if (!--wgt->refcount) {
132                 wgt_disconnect(wgt);
133                 wgt_locales_reset(wgt);
134                 free(wgt->locales);
135                 free(wgt);
136         }
137 }
138
139 /*
140  * Creates a wgt handle and connect it to 'dirfd' and 'pathname'.
141  *
142  * Returns the created and connected wgt handle on success
143  * or returns NULL if allocation failed or connecting had
144  * error.
145  */
146 struct wgt *wgt_createat(int dirfd, const char *pathname)
147 {
148         struct wgt *wgt = wgt_create();
149         if (wgt) {
150                 if (wgt_connectat(wgt, dirfd, pathname)) {
151                         wgt_unref(wgt);
152                         wgt = NULL;
153                 }
154         }
155         return wgt;
156 }
157
158 /*
159  * Connect 'wgt' to the directory of 'pathname' relative
160  * to the directory handled by 'dirfd'.
161  *
162  * Use AT_FDCWD for connecting relatively to the current directory.
163  *
164  * Use 'pathname' == NULL or "" for connecting to 'dirfd'. In
165  * that case, 'dirfd' is duplicated and can safely be used later
166  * by the client.
167  *
168  * If 'wgt' is already connected, it will be diconnected before.
169  *
170  * The languages settings are not changed.
171  *
172  * Returns 0 in case of success or -1 in case or error.
173  */
174 int wgt_connectat(struct wgt *wgt, int dirfd, const char *pathname)
175 {
176         int rfd;
177
178         assert(wgt);
179
180         rfd = (pathname && *pathname) ? openat(dirfd, pathname, O_PATH|O_DIRECTORY) : dup(dirfd);
181         if (rfd < 0)
182                 return rfd;
183
184         if (wgt->rootfd >= 0)
185                 close(wgt->rootfd);
186         wgt->rootfd = rfd;
187         return 0;
188 }
189
190 /*
191  * Connect 'wgt' to the directory of 'pathname'.
192  *
193  * Acts like wgt_connectat(wgt, AT_FDCWD, pathname)
194  */
195 int wgt_connect(struct wgt *wgt, const char *pathname)
196 {
197         return wgt_connectat(wgt, AT_FDCWD, pathname);
198 }
199
200 /*
201  * Disconnetcs 'wgt' if connected.
202  */
203 void wgt_disconnect(struct wgt *wgt)
204 {
205         assert(wgt);
206         if (wgt->rootfd >= 0)
207                 close(wgt->rootfd);
208         wgt->rootfd = -1;
209 }
210
211 /*
212  * Checks if 'wgt' is connected and returns 1 if connected
213  * or 0 if not connected.
214  */
215 int wgt_is_connected(struct wgt *wgt)
216 {
217         assert(wgt);
218         return wgt->rootfd != -1;
219 }
220
221 /*
222  * Tests wether the connected 'wgt' has the 'filename'.
223  *
224  * It is an error (with errno = EINVAL) to test an
225  * invalid filename.
226  *
227  * Returns 0 if it hasn't it, 1 if it has it or
228  * -1 if an error occured.
229  */
230 int wgt_has(struct wgt *wgt, const char *filename)
231 {
232         assert(wgt);
233         assert(wgt_is_connected(wgt));
234
235         filename = normalsubpath(filename);
236         if (!filename) {
237                 errno = EINVAL;
238                 return -1;
239         }
240         return 0 == faccessat(wgt->rootfd, filename, F_OK, 0);
241 }
242
243 /*
244  * Opens 'filename' for read from the connected 'wgt'.
245  *
246  * Returns the file descriptor as returned by openat
247  * system call or -1 in case of error.
248  */
249 int wgt_open_read(struct wgt *wgt, const char *filename)
250 {
251         assert(wgt);
252         assert(wgt_is_connected(wgt));
253         filename = normalsubpath(filename);
254         if (!filename) {
255                 errno = EINVAL;
256                 return -1;
257         }
258         return openat(wgt->rootfd, filename, O_RDONLY);
259 }
260
261 /*
262  * Adds if needed the locale 'locstr' of 'length'
263  * to the list of locales.
264  */
265 static int locadd(struct wgt *wgt, const char *locstr, size_t length)
266 {
267         unsigned int i;
268         char *item, **ptr;
269
270         item = strndup(locstr, length);
271         if (item != NULL) {
272                 /* normalize in lower case */
273                 for (i = 0 ; item[i] ; i++)
274                         item[i] = (char)tolower(item[i]);
275
276                 /* search it (no duplication) */
277                 for (i = 0 ; i < wgt->nrlocales ; i++)
278                         if (!strcmp(item, wgt->locales[i])) {
279                                 free(item);
280                                 return 0;
281                         }
282
283                 /* append it to the list */
284                 ptr = realloc(wgt->locales, (1 + wgt->nrlocales) * sizeof(wgt->locales[0]));
285                 if (ptr) {
286                         wgt->locales = ptr;
287                         wgt->locales[wgt->nrlocales++] = item;
288                         return 0;
289                 }
290                 free(item);
291         }
292         errno = ENOMEM;
293         return -1;
294 }
295
296 /*
297  * clears the list of locales of 'wgt'
298  */
299 void wgt_locales_reset(struct wgt *wgt)
300 {
301         assert(wgt);
302         while(wgt->nrlocales)
303                 free(wgt->locales[--wgt->nrlocales]);
304 }
305
306 /*
307  * Adds to 'wgt' the locales defined by 'locstr'.
308  *
309  * Example: passing "fr-CH,en-GB" will add "fr-CH",
310  * "fr", "en-GB" and then "en" to the list of locales.
311  *
312  * Returns 0 in case of success or -1 in case of memory
313  * depletion.
314  */
315 int wgt_locales_add(struct wgt *wgt, const char *locstr)
316 {
317         const char *stop, *next;
318         assert(wgt);
319
320         /* iterate the comma separated languages */
321         while (*locstr) {
322                 stop = strchrnul(locstr, ',');
323                 next = stop + !!*stop;
324                 /* iterate variant of languages in reverse order */
325                 while (locstr != stop) {
326                         if (locadd(wgt, locstr, (size_t)(stop - locstr)))
327                                 return -1;
328                         do { stop--; } while(stop > locstr && *stop != '-');
329                 }
330                 locstr = next;
331         }
332         return 0;
333 }
334
335 /*
336  * Get the score of the language 'lang' for 'wgt'.
337  *
338  * The lower result means the higher priority of the language.
339  * The returned value of 0 is the top first priority.
340  */
341 unsigned int wgt_locales_score(struct wgt *wgt, const char *lang)
342 {
343         unsigned int i;
344
345         assert(wgt);
346         if (lang)
347                 for (i = 0 ; i < wgt->nrlocales ; i++)
348                         if (!strcasecmp(lang, wgt->locales[i]))
349                                 return i;
350
351         return UINT_MAX;
352 }
353
354 /*
355  * Applies the localisation algorithm of 'filename'
356  * within 'wgt'. Use the scratch buffer given by 'path'.
357  *
358  * Returns the filepath of the located file or NULL
359  * if not found. If not NULL, the returned value is either
360  * 'path' or the normalized version of 'filename'.
361  */
362 static const char *localize(struct wgt *wgt, const char *filename, char path[PATH_MAX])
363 {
364         unsigned int i;
365
366         /* get the normalized name */
367         filename = normalsubpath(filename);
368         if (!filename) {
369                 errno = EINVAL;
370                 return NULL;
371         }
372
373         /* search in locales */
374         for (i = 0 ; i < wgt->nrlocales ; i++) {
375                 if (snprintf(path, PATH_MAX, "locales/%s/%s", wgt->locales[i], filename) >= PATH_MAX) {
376                         errno = EINVAL;
377                         return NULL;
378                 }
379                 if (0 == faccessat(wgt->rootfd, path, F_OK, 0))
380                         return path;
381         }
382         if (0 == faccessat(wgt->rootfd, filename, F_OK, 0))
383                 return filename;
384         errno = ENOENT;
385         return NULL;
386 }
387
388 /*
389  * Gets the localized file of 'filename' within 'wgt'.
390  *
391  * Returns a fresh allocated string for the found 'filename'.
392  * Returns NULL if file is not found (ENOENT) or memory
393  * exhausted (ENOMEM).
394  */
395 char *wgt_locales_locate(struct wgt *wgt, const char *filename)
396 {
397         char path[PATH_MAX];
398         char * result;
399         const char * loc;
400
401         assert(wgt);
402         assert(wgt_is_connected(wgt));
403
404         loc = localize(wgt, filename, path);
405         if (!loc)
406                 result = NULL;
407         else {
408                 result = strdup(loc);
409                 if (!result)
410                         errno = ENOMEM;
411         }
412         return result;
413 }
414
415 /*
416  * Opens for read the localized version of 'filename'
417  * from the connected 'wgt'.
418  *
419  * Returns the file descriptor as returned by openat
420  * system call or -1 in case of error.
421  */
422 int wgt_locales_open_read(struct wgt *wgt, const char *filename)
423 {
424         char path[PATH_MAX];
425         const char *loc;
426
427         assert(wgt);
428         assert(wgt_is_connected(wgt));
429
430         loc = localize(wgt, filename, path);
431         if (!loc)
432                 return -1;
433
434         return openat(wgt->rootfd, loc, O_RDONLY);
435 }
436
437
438 #if defined(TEST_wgt_validsubpath)
439 #include <stdio.h>
440 void t(const char *subpath, int validity) {
441   printf("%s -> %d = %d, %s\n", subpath, validity, validsubpath(subpath), validsubpath(subpath)==validity ? "ok" : "NOT OK");
442 }
443 int main() {
444   t("/",0);
445   t("..",0);
446   t(".",1);
447   t("../a",0);
448   t("a/..",1);
449   t("a/../////..",0);
450   t("a/../b/..",1);
451   t("a/b/c/..",1);
452   t("a/b/c/../..",1);
453   t("a/b/c/../../..",1);
454   t("a/b/c/../../../.",1);
455   t("./..a/././..b/..c/./.././.././../.",1);
456   t("./..a/././..b/..c/./.././.././.././..",0);
457   t("./..a//.//./..b/..c/./.././/./././///.././.././a/a/a/a/a",1);
458   return 0;
459 }
460 #endif
461