aboutsummaryrefslogtreecommitdiffstats
path: root/src/wgt.c
blob: c2a31f2269241ac72a239054d781fe85f51730dd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
/*
 Copyright (C) 2015-2019 IoT.bzh

 author: José Bollo <jose.bollo@iot.bzh>

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <limits.h>
#include <string.h>
#include <ctype.h>


#include "wgt.h"

struct wgt {
	int refcount;
	int rootfd;
	unsigned int nrlocales;
	char **locales;
};

/* a valid subpath is a relative path not looking deeper than root using .. */
static int validsubpath(const char *subpath)
{
	int l = 0, i = 0;

	/* absolute path is not valid */
	if (subpath[i] == '/')
		return 0;

	/* inspect the path */
	while(subpath[i]) {
		switch(subpath[i++]) {
		case '.':
			if (!subpath[i])
				break;
			if (subpath[i] == '/') {
				i++;
				break;
			}
			if (subpath[i++] == '.') {
				if (!subpath[i]) {
					l--;
					break;
				}
				if (subpath[i++] == '/') {
					l--;
					break;
				}
			}
		default:
			while(subpath[i] && subpath[i] != '/')
				i++;
			if (l >= 0)
				l++;
		case '/':
			break;
		}
	}
	return l >= 0;
}

/*
 * Normalizes and checks the 'subpath'.
 * Removes any starting '/' and checks that 'subpath'
 * does not contains sequence of '..' going deeper than
 * root.
 * Returns the normalized subpath or NULL in case of
 * invalid subpath.
 */
static const char *normalsubpath(const char *subpath)
{
	while(*subpath == '/')
		subpath++;
	return validsubpath(subpath) ? subpath : NULL;
}

/*
 * Creates a wgt handler and returns it or return NULL
 * in case of memory depletion.
 */
struct wgt *wgt_create()
{
	struct wgt *wgt = malloc(sizeof * wgt);
	if (!wgt)
		errno = ENOMEM;
	else {
		wgt->refcount = 1;
		wgt->rootfd = -1;
		wgt->nrlocales = 0;
		wgt->locales = NULL;
	}
	return wgt;
}

/*
 * Adds a reference to 'wgt'
 */
void wgt_addref(struct wgt *wgt)
{
	assert(wgt);
	wgt->refcount++;
}

/*
 * Drops a reference to 'wgt' and destroys it
 * if not more referenced
 */
void wgt_unref(struct wgt *wgt)
{
	assert(wgt);
	if (!--wgt->refcount) {
		wgt_disconnect(wgt);
		wgt_locales_reset(wgt);
		free(wgt->locales);
		free(wgt);
	}
}

/*
 * Creates a wgt handle and connect it to 'dirfd' and 'pathname'.
 *
 * Returns the created and connected wgt handle on success
 * or returns NULL if allocation failed or connecting had
 * error.
 */
struct wgt *wgt_createat(int dirfd, const char *pathname)
{
	struct wgt *wgt = wgt_create();
	if (wgt) {
		if (wgt_connectat(wgt, dirfd, pathname)) {
			wgt_unref(wgt);
			wgt = NULL;
		}
	}
	return wgt;
}

/*
 * Connect 'wgt' to the directory of 'pathname' relative
 * to the directory handled by 'dirfd'.
 *
 * Use AT_FDCWD for connecting relatively to the current directory.
 *
 * Use 'pathname' == NULL or "" for connecting to 'dirfd'. In
 * that case, 'dirfd' is duplicated and can safely be used later
 * by the client.
 *
 * If 'wgt' is already connected, it will be diconnected before.
 *
 * The languages settings are not changed.
 *
 * Returns 0 in case of success or -1 in case or error.
 */
int wgt_connectat(struct wgt *wgt, int dirfd, const char *pathname)
{
	int rfd;

	assert(wgt);

	rfd = (pathname && *pathname) ? openat(dirfd, pathname, O_PATH|O_DIRECTORY) : dup(dirfd);
	if (rfd < 0)
		return rfd;

	if (wgt->rootfd >= 0)
		close(wgt->rootfd);
	wgt->rootfd = rfd;
	return 0;
}

/*
 * Connect 'wgt' to the directory of 'pathname'.
 *
 * Acts like wgt_connectat(wgt, AT_FDCWD, pathname)
 */
int wgt_connect(struct wgt *wgt, const char *pathname)
{
	return wgt_connectat(wgt, AT_FDCWD, pathname);
}

/*
 * Disconnetcs 'wgt' if connected.
 */
void wgt_disconnect(struct wgt *wgt)
{
	assert(wgt);
	if (wgt->rootfd >= 0)
		close(wgt->rootfd);
	wgt->rootfd = -1;
}

/*
 * Checks if 'wgt' is connected and returns 1 if connected
 * or 0 if not connected.
 */
int wgt_is_connected(struct wgt *wgt)
{
	assert(wgt);
	return wgt->rootfd != -1;
}

/*
 * Tests wether the connected 'wgt' has the 'filename'.
 *
 * It is an error (with errno = EINVAL) to test an
 * invalid filename.
 *
 * Returns 0 if it hasn't it, 1 if it has it or
 * -1 if an error occured.
 */
int wgt_has(struct wgt *wgt, const char *filename)
{
	assert(wgt);
	assert(wgt_is_connected(wgt));

	filename = normalsubpath(filename);
	if (!filename) {
		errno = EINVAL;
		return -1;
	}
	return 0 == faccessat(wgt->rootfd, filename, F_OK, 0);
}

/*
 * Opens 'filename' for read from the connected 'wgt'.
 *
 * Returns the file descriptor as returned by openat
 * system call or -1 in case of error.
 */
int wgt_open_read(struct wgt *wgt, const char *filename)
{
	assert(wgt);
	assert(wgt_is_connected(wgt));
	filename = normalsubpath(filename);
	if (!filename) {
		errno = EINVAL;
		return -1;
	}
	return openat(wgt->rootfd, filename, O_RDONLY);
}

/*
 * Adds if needed the locale 'locstr' of 'length'
 * to the list of locales.
 */
static int locadd(struct wgt *wgt, const char *locstr, size_t length)
{
	unsigned int i;
	char *item, **ptr;

	item = strndup(locstr, length);
	if (item != NULL) {
		/* normalize in lower case */
		for (i = 0 ; item[i] ; i++)
			item[i] = (char)tolower(item[i]);

		/* search it (no duplication) */
		for (i = 0 ; i < wgt->nrlocales ; i++)
			if (!strcmp(item, wgt->locales[i])) {
				free(item);
				return 0;
			}

		/* append it to the list */
		ptr = realloc(wgt->locales, (1 + wgt->nrlocales) * sizeof(wgt->locales[0]));
		if (ptr) {
			wgt->locales = ptr;
			wgt->locales[wgt->nrlocales++] = item;
			return 0;
		}
		free(item);
	}
	errno = ENOMEM;
	return -1;
}

/*
 * clears the list of locales of 'wgt'
 */
void wgt_locales_reset(struct wgt *wgt)
{
	assert(wgt);
	while(wgt->nrlocales)
		free(wgt->locales[--wgt->nrlocales]);
}

/*
 * Adds to 'wgt' the locales defined by 'locstr'.
 *
 * Example: passing "fr-CH,en-GB" will add "fr-CH",
 * "fr", "en-GB" and then "en" to the list of locales.
 *
 * Returns 0 in case of success or -1 in case of memory
 * depletion.
 */
int wgt_locales_add(struct wgt *wgt, const char *locstr)
{
	const char *stop, *next;
	assert(wgt);

	/* iterate the comma separated languages */
	while (*locstr) {
		stop = strchrnul(locstr, ',');
		next = stop + !!*stop;
		/* iterate variant of languages in reverse order */
		while (locstr != stop) {
			if (locadd(wgt, locstr, (size_t)(stop - locstr)))
				return -1;
			do { stop--; } while(stop > locstr && *stop != '-');
		}
		locstr = next;
	}
	return 0;
}

/*
 * Get the score of the language 'lang' for 'wgt'.
 *
 * The lower result means the higher priority of the language.
 * The returned value of 0 is the top first priority.
 */
unsigned int wgt_locales_score(struct wgt *wgt, const char *lang)
{
	unsigned int i;

	assert(wgt);
	if (lang)
		for (i = 0 ; i < wgt->nrlocales ; i++)
			if (!strcasecmp(lang, wgt->locales[i]))
				return i;

	return UINT_MAX;
}

/*
 * Applies the localisation algorithm of 'filename'
 * within 'wgt'. Use the scratch buffer given by 'path'.
 *
 * Returns the filepath of the located file or NULL
 * if not found. If not NULL, the returned value is either
 * 'path' or the normalized version of 'filename'.
 */
static const char *localize(struct wgt *wgt, const char *filename, char path[PATH_MAX])
{
	unsigned int i;

	/* get the normalized name */
	filename = normalsubpath(filename);
	if (!filename) {
		errno = EINVAL;
		return NULL;
	}

	/* search in locales */
	for (i = 0 ; i < wgt->nrlocales ; i++) {
		if (snprintf(path, PATH_MAX, "locales/%s/%s", wgt->locales[i], filename) >= PATH_MAX) {
			errno = EINVAL;
			return NULL;
		}
		if (0 == faccessat(wgt->rootfd, path, F_OK, 0))
			return path;
	}
	if (0 == faccessat(wgt->rootfd, filename, F_OK, 0))
		return filename;
	errno = ENOENT;
	return NULL;
}

/*
 * Gets the localized file of 'filename' within 'wgt'.
 *
 * Returns a fresh allocated string for the found 'filename'.
 * Returns NULL if file is not found (ENOENT) or memory
 * exhausted (ENOMEM).
 */
char *wgt_locales_locate(struct wgt *wgt, const char *filename)
{
	char path[PATH_MAX];
	char * result;
	const char * loc;

	assert(wgt);
	assert(wgt_is_connected(wgt));

	loc = localize(wgt, filename, path);
	if (!loc)
		result = NULL;
	else {
		result = strdup(loc);
		if (!result)
			errno = ENOMEM;
	}
	return result;
}

/*
 * Opens for read the localized version of 'filename'
 * from the connected 'wgt'.
 *
 * Returns the file descriptor as returned by openat
 * system call or -1 in case of error.
 */
int wgt_locales_open_read(struct wgt *wgt, const char *filename)
{
	char path[PATH_MAX];
	const char *loc;

	assert(wgt);
	assert(wgt_is_connected(wgt));

	loc = localize(wgt, filename, path);
	if (!loc)
		return -1;

	return openat(wgt->rootfd, loc, O_RDONLY);
}


#if defined(TEST_wgt_validsubpath)
#include <stdio.h>
void t(const char *subpath, int validity) {
  printf("%s -> %d = %d, %s\n", subpath, validity, validsubpath(subpath), validsubpath(subpath)==validity ? "ok" : "NOT OK");
}
int main() {
  t("/",0);
  t("..",0);
  t(".",1);
  t("../a",0);
  t("a/..",1);
  t("a/../////..",0);
  t("a/../b/..",1);
  t("a/b/c/..",1);
  t("a/b/c/../..",1);
  t("a/b/c/../../..",1);
  t("a/b/c/../../../.",1);
  t("./..a/././..b/..c/./.././.././../.",1);
  t("./..a/././..b/..c/./.././.././.././..",0);
  t("./..a//.//./..b/..c/./.././/./././///.././.././a/a/a/a/a",1);
  return 0;
}
#endif