netsurf/utils/filepath.c
Vincent Sanders c56642819e add file operations table and make all frontends use it.
This rationalises the path construction and basename file
operations. The default implementation is POSIX which works for all
frontends except windows, riscos and amiga which have differeing path
separators and rules.

These implementations are significantly more robust than the previous
nine implementations and also do not use unsafe strncpy or buffers
with arbitrary length limits.

These implementations also carry full documentation comments.
2014-05-07 16:24:51 +01:00

385 lines
8.0 KiB
C

/*
* Copyright 2010 Vincent Sanders <vince@kyllikki.org>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/** \file
* Provides utility functions for finding readable files.
*
* These functions are intended to make finding resource files more straightforward.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "utils/utils.h"
#include "utils/config.h"
#include "utils/filepath.h"
/** maximum number of elements in the resource vector */
#define MAX_RESPATH 128
/* exported interface documented in filepath.h */
char *filepath_vsfindfile(char *str, const char *format, va_list ap)
{
char *realpathname;
char *pathname;
int len;
pathname = malloc(PATH_MAX);
if (pathname == NULL)
return NULL; /* unable to allocate memory */
len = vsnprintf(pathname, PATH_MAX, format, ap);
if ((len < 0) || (len >= PATH_MAX)) {
/* error or output exceeded PATH_MAX length so
* operation is doomed to fail.
*/
free(pathname);
return NULL;
}
realpathname = realpath(pathname, str);
free(pathname);
if (realpathname != NULL) {
/* sucessfully expanded pathname */
if (access(realpathname, R_OK) != 0) {
/* unable to read the file */
return NULL;
}
}
return realpathname;
}
/* exported interface documented in filepath.h */
char *filepath_sfindfile(char *str, const char *format, ...)
{
va_list ap;
char *ret;
va_start(ap, format);
ret = filepath_vsfindfile(str, format, ap);
va_end(ap);
return ret;
}
/* exported interface documented in filepath.h */
char *filepath_findfile(const char *format, ...)
{
char *ret;
va_list ap;
va_start(ap, format);
ret = filepath_vsfindfile(NULL, format, ap);
va_end(ap);
return ret;
}
/* exported interface documented in filepath.h */
char *filepath_sfind(char **respathv, char *filepath, const char *filename)
{
int respathc = 0;
if ((respathv == NULL) || (respathv[0] == NULL) || (filepath == NULL))
return NULL;
while (respathv[respathc] != NULL) {
if (filepath_sfindfile(filepath, "%s/%s", respathv[respathc], filename) != NULL) {
return filepath;
}
respathc++;
}
return NULL;
}
/* exported interface documented in filepath.h */
char *filepath_find(char **respathv, const char *filename)
{
char *ret;
char *filepath;
if ((respathv == NULL) || (respathv[0] == NULL))
return NULL;
filepath = malloc(PATH_MAX);
if (filepath == NULL)
return NULL;
ret = filepath_sfind(respathv, filepath, filename);
if (ret == NULL)
free(filepath);
return ret;
}
/* exported interface documented in filepath.h */
char *filepath_sfinddef(char **respathv, char *filepath, const char *filename, const char *def)
{
char t[PATH_MAX];
char *ret;
if ((respathv == NULL) || (respathv[0] == NULL) || (filepath == NULL))
return NULL;
ret = filepath_sfind(respathv, filepath, filename);
if ((ret == NULL) && (def != NULL)) {
/* search failed, return the path specified */
ret = filepath;
if (def[0] == '~') {
snprintf(t, PATH_MAX, "%s/%s/%s", getenv("HOME"), def + 1, filename);
} else {
snprintf(t, PATH_MAX, "%s/%s", def, filename);
}
if (realpath(t, ret) == NULL) {
strcpy(ret, t);
}
}
return ret;
}
/* exported interface documented in filepath.h */
char **
filepath_generate(char * const *pathv, const char * const *langv)
{
char **respath; /* resource paths vector */
int pathc = 0;
int langc = 0;
int respathc = 0;
struct stat dstat;
char tmppath[PATH_MAX];
respath = calloc(MAX_RESPATH, sizeof(char *));
while (pathv[pathc] != NULL) {
if ((stat(pathv[pathc], &dstat) == 0) &&
S_ISDIR(dstat.st_mode)) {
/* path element exists and is a directory */
langc = 0;
while (langv[langc] != NULL) {
snprintf(tmppath, sizeof tmppath, "%s/%s", pathv[pathc],langv[langc]);
if ((stat(tmppath, &dstat) == 0) &&
S_ISDIR(dstat.st_mode)) {
/* path element exists and is a directory */
respath[respathc++] = strdup(tmppath);
}
langc++;
}
respath[respathc++] = strdup(pathv[pathc]);
}
pathc++;
}
return respath;
}
/**
* expand ${} in a string into environment variables.
*
* @param path The pathname to expand.
* @param pathlen The length of the path element.
* @return A string with the expanded path or NULL on empty expansion or error.
*/
static char *
expand_path(const char *path, int pathlen)
{
char *exp;
int explen;
int cstart = -1;
int cloop = 0;
char *envv;
int envlen;
int replen; /* length of replacement */
exp = malloc(pathlen + 1);
if (exp == NULL)
return NULL;
memcpy(exp, path, pathlen);
exp[pathlen] = 0;
explen = pathlen;
while (exp[cloop] != 0) {
if ((exp[cloop] == '$') &&
(exp[cloop + 1] == '{')) {
cstart = cloop;
cloop++;
}
if ((cstart != -1) &&
(exp[cloop] == '}')) {
replen = cloop - cstart;
exp[cloop] = 0;
envv = getenv(exp + cstart + 2);
if (envv == NULL) {
memmove(exp + cstart,
exp + cloop + 1,
explen - cloop);
explen -= replen;
} else {
envlen = strlen(envv);
exp = realloc(exp, explen + envlen - replen);
memmove(exp + cstart + envlen,
exp + cloop + 1,
explen - cloop );
memmove(exp + cstart, envv, envlen);
explen += envlen - replen;
}
cloop -= replen;
cstart = -1;
}
cloop++;
}
if (explen == 1) {
free(exp);
exp = NULL;
}
return exp;
}
/* exported interface documented in filepath.h */
char **
filepath_path_to_strvec(const char *path)
{
char **strvec;
int strc = 0;
const char *estart; /* path element start */
const char *eend; /* path element end */
int elen;
strvec = calloc(MAX_RESPATH, sizeof(char *));
if (strvec == NULL)
return NULL;
estart = eend = path;
while (strc < (MAX_RESPATH - 2)) {
while ( (*eend != 0) && (*eend != ':') )
eend++;
elen = eend - estart;
if (elen > 1) {
/* more than an empty colon */
strvec[strc] = expand_path(estart, elen);
if (strvec[strc] != NULL) {
/* successfully expanded an element */
strc++;
}
}
/* skip colons */
while (*eend == ':')
eend++;
/* check for termination */
if (*eend == 0)
break;
estart = eend;
}
return strvec;
}
/* exported interface documented in filepath.h */
void filepath_free_strvec(char **pathv)
{
int p = 0;
while (pathv[p] != NULL) {
free(pathv[p++]);
}
free(pathv);
}
/* exported interface documented in filepath.h */
nserror filepath_mkdir_all(const char *fname)
{
char *dname;
char *sep;
struct stat sb;
dname = strdup(fname);
sep = strrchr(dname, '/');
if (sep == NULL) {
/* no directory separator path is just filename so its ok */
free(dname);
return NSERROR_OK;
}
*sep = 0; /* null terminate directory path */
if (stat(dname, &sb) == 0) {
free(dname);
if (S_ISDIR(sb.st_mode)) {
/* path to file exists and is a directory */
return NSERROR_OK;
}
return NSERROR_NOT_DIRECTORY;
}
*sep = '/'; /* restore separator */
sep = dname;
while (*sep == '/') {
sep++;
}
while ((sep = strchr(sep, '/')) != NULL) {
*sep = 0;
if (stat(dname, &sb) != 0) {
if (nsmkdir(dname, S_IRWXU) != 0) {
/* could not create path element */
free(dname);
return NSERROR_NOT_FOUND;
}
} else {
if (! S_ISDIR(sb.st_mode)) {
/* path element not a directory */
free(dname);
return NSERROR_NOT_DIRECTORY;
}
}
*sep = '/'; /* restore separator */
/* skip directory separators */
while (*sep == '/') {
sep++;
}
}
free(dname);
return NSERROR_OK;
}