#include "lib/fs/path.h"
#include "lib/malloc/malloc.h"
#include "lib/log/log.h"
#include "lib/log/util_bug.h"
#include "lib/container/smartlist.h"
#include "lib/sandbox/sandbox.h"
#include "lib/string/printf.h"
#include "lib/string/util_string.h"
#include "lib/string/compat_ctype.h"
#include "lib/string/compat_string.h"
#include "lib/fs/files.h"
#include "lib/fs/dir.h"
#include "lib/fs/userdb.h"
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef _WIN32
#include <windows.h>
#include <shlwapi.h>
#else
#include <dirent.h>
#include <glob.h>
#endif
#include <errno.h>
#include <string.h>
char *
get_unquoted_path(const char *path)
{
size_t len = strlen(path);
if (len == 0) {
return tor_strdup("");
}
int has_start_quote = (path[0] == '\"');
int has_end_quote = (len > 0 && path[len-1] == '\"');
if (has_start_quote != has_end_quote || (len == 1 && has_start_quote)) {
return NULL;
}
char *unquoted_path = tor_malloc(len - has_start_quote - has_end_quote + 1);
char *s = unquoted_path;
size_t i;
for (i = has_start_quote; i < len - has_end_quote; i++) {
if (path[i] == '\"' && (i > 0 && path[i-1] == '\\')) {
*(s-1) = path[i];
} else if (path[i] != '\"') {
*s++ = path[i];
} else {
tor_free(unquoted_path);
return NULL;
}
}
*s = '\0';
return unquoted_path;
}
char *
expand_filename(const char *filename)
{
tor_assert(filename);
#ifdef _WIN32
return tor_strdup(filename);
#else
if (*filename == '~') {
char *home, *result=NULL;
const char *rest;
if (filename[1] == '/' || filename[1] == '\0') {
home = getenv("HOME");
if (!home) {
log_warn(LD_CONFIG, "Couldn't find $HOME environment variable while "
"expanding \"%s\"; defaulting to \"\".", filename);
home = tor_strdup("");
} else {
home = tor_strdup(home);
}
rest = strlen(filename)>=2?(filename+2):"";
} else {
#ifdef HAVE_PWD_H
char *username, *slash;
slash = strchr(filename, '/');
if (slash)
username = tor_strndup(filename+1,slash-filename-1);
else
username = tor_strdup(filename+1);
if (!(home = get_user_homedir(username))) {
log_warn(LD_CONFIG,"Couldn't get homedir for \"%s\"",username);
tor_free(username);
return NULL;
}
tor_free(username);
rest = slash ? (slash+1) : "";
#else
log_warn(LD_CONFIG, "Couldn't expand homedir on system without pwd.h");
return tor_strdup(filename);
#endif
}
tor_assert(home);
if (strlen(home)>1 && !strcmpend(home,PATH_SEPARATOR)) {
home[strlen(home)-1] = '\0';
}
tor_asprintf(&result,"%s"PATH_SEPARATOR"%s",home,rest);
tor_free(home);
return result;
} else {
return tor_strdup(filename);
}
#endif
}
int
path_is_relative(const char *filename)
{
if (filename && filename[0] == '/')
return 0;
#ifdef _WIN32
else if (filename && filename[0] == '\\')
return 0;
else if (filename && strlen(filename)>3 && TOR_ISALPHA(filename[0]) &&
filename[1] == ':' && filename[2] == '\\')
return 0;
#endif
else
return 1;
}
void
clean_fname_for_stat(char *name)
{
#ifdef _WIN32
size_t len = strlen(name);
if (!len)
return;
if (name[len-1]=='\\' || name[len-1]=='/') {
if (len == 1 || (len==3 && name[1]==':'))
return;
name[len-1]='\0';
}
#else
(void)name;
#endif
}
int
get_parent_directory(char *fname)
{
char *cp;
int at_end = 1;
tor_assert(fname);
#ifdef _WIN32
if (fname[0] && fname[1] == ':') {
fname += 2;
}
#endif
cp = fname + strlen(fname);
at_end = 1;
while (--cp >= fname) {
int is_sep = (*cp == '/'
#ifdef _WIN32
|| *cp == '\\'
#endif
);
if (is_sep) {
if (cp == fname) {
cp[1] = '\0';
return 0;
}
*cp = '\0';
if (! at_end)
return 0;
} else {
at_end = 0;
}
}
return -1;
}
#ifndef _WIN32
static char *
alloc_getcwd(void)
{
#ifdef HAVE_GET_CURRENT_DIR_NAME
char *cwd = get_current_dir_name();
char *result = NULL;
if (cwd) {
result = tor_strdup(cwd);
raw_free(cwd); }
return result;
#else
size_t size = 1024;
char *buf = NULL;
char *ptr = NULL;
while (ptr == NULL) {
buf = tor_realloc(buf, size);
ptr = getcwd(buf, size);
if (ptr == NULL && errno != ERANGE) {
tor_free(buf);
return NULL;
}
size *= 2;
}
return buf;
#endif
}
#endif
char *
make_path_absolute(const char *fname)
{
#ifdef _WIN32
char *absfname_malloced = _fullpath(NULL, fname, 1);
char *absfname = tor_strdup(absfname_malloced ? absfname_malloced : fname);
if (absfname_malloced) raw_free(absfname_malloced);
return absfname;
#else
char *absfname = NULL, *path = NULL;
tor_assert(fname);
if (fname[0] == '/') {
absfname = tor_strdup(fname);
} else {
path = alloc_getcwd();
if (path) {
tor_asprintf(&absfname, "%s/%s", path, fname);
tor_free(path);
} else {
log_warn(LD_GENERAL, "Unable to find current working directory: %s",
strerror(errno));
absfname = tor_strdup(fname);
}
}
return absfname;
#endif
}
static inline bool
is_glob_char(const char *pattern, int pos)
{
bool is_glob = pattern[pos] == '*' || pattern[pos] == '?';
#ifdef _WIN32
return is_glob;
#else
bool is_escaped = pos > 0 && pattern[pos-1] == '\\';
return is_glob && !is_escaped;
#endif
}
typedef struct smartlist_t * unglob_fn(const char *pattern, int prev_sep,
int next_sep);
static bool
add_non_glob_path(const char *path, struct smartlist_t *result)
{
file_status_t file_type = file_status(path);
if (file_type == FN_ERROR) {
return false;
} else if (file_type != FN_NOENT) {
char *to_add = tor_strdup(path);
clean_fname_for_stat(to_add);
smartlist_add(result, to_add);
}
return true;
}
static struct smartlist_t *
get_glob_paths(const char *pattern, unglob_fn unglob, bool final)
{
smartlist_t *result = smartlist_new();
int i, prev_sep = -1, next_sep = -1;
bool is_glob = false, error_found = false, is_sep = false, is_last = false;
for (i = 0; pattern[i]; i++) {
is_glob = is_glob || is_glob_char(pattern, i);
is_last = !pattern[i+1];
is_sep = pattern[i] == *PATH_SEPARATOR || pattern[i] == '/';
if (is_sep || is_last) {
prev_sep = next_sep;
next_sep = i; if (is_glob) {
break;
}
}
}
if (!is_glob) { if (final && !add_non_glob_path(pattern, result)) {
error_found = true;
goto end;
}
return result;
}
if (!final) {
int len = prev_sep < 1 ? prev_sep + 1 : prev_sep; char *path_until_glob = tor_strndup(pattern, len);
smartlist_add(result, path_until_glob);
}
smartlist_t *unglobbed_paths = unglob(pattern, prev_sep, next_sep);
if (!unglobbed_paths) {
error_found = true;
} else {
SMARTLIST_FOREACH_BEGIN(unglobbed_paths, char *, current_path) {
char *next_path;
tor_asprintf(&next_path, "%s"PATH_SEPARATOR"%s", current_path,
&pattern[next_sep+1]);
smartlist_t *opened_next = get_glob_paths(next_path, unglob, final);
tor_free(next_path);
if (!opened_next) {
error_found = true;
break;
}
smartlist_add_all(result, opened_next);
smartlist_free(opened_next);
} SMARTLIST_FOREACH_END(current_path);
SMARTLIST_FOREACH(unglobbed_paths, char *, p, tor_free(p));
smartlist_free(unglobbed_paths);
}
end:
if (error_found) {
SMARTLIST_FOREACH(result, char *, p, tor_free(p));
smartlist_free(result);
result = NULL;
}
return result;
}
#ifdef _WIN32
static struct smartlist_t *
unglob_win32(const char *pattern, int prev_sep, int next_sep)
{
smartlist_t *result = smartlist_new();
int len = prev_sep < 1 ? prev_sep + 1 : prev_sep; char *path_until_glob = tor_strndup(pattern, len);
if (!is_file(file_status(path_until_glob))) {
smartlist_t *filenames = tor_listdir(path_until_glob);
if (!filenames) {
smartlist_free(result);
result = NULL;
} else {
SMARTLIST_FOREACH_BEGIN(filenames, char *, filename) {
TCHAR tpattern[MAX_PATH] = {0};
TCHAR tfile[MAX_PATH] = {0};
char *full_path;
tor_asprintf(&full_path, "%s"PATH_SEPARATOR"%s",
path_until_glob, filename);
char *path_curr_glob = tor_strndup(pattern, next_sep + 1);
if (is_dir(file_status(full_path))) {
clean_fname_for_stat(path_curr_glob);
}
#ifdef UNICODE
mbstowcs(tpattern, path_curr_glob, MAX_PATH);
mbstowcs(tfile, full_path, MAX_PATH);
#else
strlcpy(tpattern, path_curr_glob, MAX_PATH);
strlcpy(tfile, full_path, MAX_PATH);
#endif
if (PathMatchSpec(tfile, tpattern)) {
smartlist_add(result, full_path);
} else {
tor_free(full_path);
}
tor_free(path_curr_glob);
} SMARTLIST_FOREACH_END(filename);
SMARTLIST_FOREACH(filenames, char *, p, tor_free(p));
smartlist_free(filenames);
}
}
tor_free(path_until_glob);
return result;
}
#elif HAVE_GLOB
static DIR *
prot_opendir(const char *name)
{
if (sandbox_interned_string_is_missing(name)) {
errno = EPERM;
return NULL;
}
return opendir(sandbox_intern_string(name));
}
static int
prot_stat(const char *pathname, struct stat *buf)
{
if (sandbox_interned_string_is_missing(pathname)) {
errno = EPERM;
return -1;
}
return stat(sandbox_intern_string(pathname), buf);
}
static int
prot_lstat(const char *pathname, struct stat *buf)
{
if (sandbox_interned_string_is_missing(pathname)) {
errno = EPERM;
return -1;
}
return lstat(sandbox_intern_string(pathname), buf);
}
static void
wrap_closedir(void *arg)
{
closedir(arg);
}
#endif
struct smartlist_t *
tor_glob(const char *pattern)
{
smartlist_t *result = NULL;
#ifdef _WIN32
char *pattern_normalized = tor_strdup(pattern);
tor_strreplacechar(pattern_normalized, '/', *PATH_SEPARATOR);
result = get_glob_paths(pattern_normalized, unglob_win32, true);
tor_free(pattern_normalized);
#elif HAVE_GLOB
glob_t matches;
int flags = GLOB_ERR | GLOB_NOSORT;
#ifdef GLOB_ALTDIRFUNC
flags |= GLOB_ALTDIRFUNC;
typedef void *(*gl_opendir)(const char * name);
typedef struct dirent *(*gl_readdir)(void *);
typedef void (*gl_closedir)(void *);
matches.gl_opendir = (gl_opendir) &prot_opendir;
matches.gl_readdir = (gl_readdir) &readdir;
matches.gl_closedir = (gl_closedir) &wrap_closedir;
matches.gl_stat = &prot_stat;
matches.gl_lstat = &prot_lstat;
#endif
int ret = glob(pattern, flags, NULL, &matches);
if (ret == GLOB_NOMATCH) {
return smartlist_new();
} else if (ret != 0) {
return NULL;
}
size_t pattern_len = strlen(pattern);
bool dir_only = has_glob(pattern) &&
pattern_len > 0 && pattern[pattern_len-1] == *PATH_SEPARATOR;
result = smartlist_new();
size_t i;
for (i = 0; i < matches.gl_pathc; i++) {
char *match = tor_strdup(matches.gl_pathv[i]);
size_t len = strlen(match);
if (len > 0 && match[len-1] == *PATH_SEPARATOR) {
match[len-1] = '\0';
}
if (!dir_only || (dir_only && is_dir(file_status(match)))) {
smartlist_add(result, match);
} else {
tor_free(match);
}
}
globfree(&matches);
#else
(void)pattern;
return result;
#endif
return result;
}
bool
has_glob(const char *s)
{
int i;
for (i = 0; s[i]; i++) {
if (is_glob_char(s, i)) {
return true;
}
}
return false;
}
static struct smartlist_t *
unglob_opened_files(const char *pattern, int prev_sep, int next_sep)
{
(void)prev_sep;
smartlist_t *result = smartlist_new();
if (has_glob(&pattern[next_sep+1])) {
char *glob_path = tor_strndup(pattern, next_sep);
smartlist_t *child_paths = tor_glob(glob_path);
tor_free(glob_path);
if (!child_paths) {
smartlist_free(result);
result = NULL;
} else {
smartlist_add_all(result, child_paths);
smartlist_free(child_paths);
}
}
return result;
}
struct smartlist_t *
get_glob_opened_files(const char *pattern)
{
return get_glob_paths(pattern, unglob_opened_files, false);
}