/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#if !defined(NO_GNU_SOURCE_THIS_TIME)
#define NO_GNU_SOURCE_THIS_TIME
#endif
#if !defined(_DARWIN_C_SOURCE)
#define _DARWIN_C_SOURCE
#endif

#include "private-lib-core.h"
#include <string.h>
#include <stdio.h>

#include <sys/stat.h>
#if defined(WIN32)
#include <direct.h>
#define read _read
#define open _open
#define close _close
#define write _write
#define mkdir(x,y) _mkdir(x)
#define rmdir _rmdir
#define unlink _unlink
#define HAVE_STRUCT_TIMESPEC
#if defined(pid_t)
#undef pid_t
#endif
#endif /* win32 */

#define COMBO_SIZEOF 512

#if !defined(LWS_PLAT_FREERTOS)

#if defined(WIN32)
#include "../../win32port/dirent/dirent-win32.h"
#else
#include <dirent.h>
#endif

static int filter(const struct dirent *ent)
{
	if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
		return 0;

	return 1;
}


#if !defined(WIN32)
static char csep = '/';
#else
static char csep = '\\';
#endif

static void
lws_dir_via_stat(char *combo, size_t l, const char *path, struct lws_dir_entry *lde)
{
        struct stat s;

        lws_strncpy(combo + l, path, COMBO_SIZEOF - l);

        lde->type = LDOT_UNKNOWN;

        if (!stat(combo, &s)) {
		switch (s.st_mode & S_IFMT) {
		case S_IFBLK:
			lde->type = LDOT_BLOCK;
			break;
		case S_IFCHR:
			lde->type = LDOT_CHAR;
			break;
		case S_IFDIR:
			lde->type = LDOT_DIR;
			break;
		case S_IFIFO:
			lde->type = LDOT_FIFO;
			break;
#if !defined(WIN32)
		case S_IFLNK:
			lde->type = LDOT_LINK;
			break;
#endif
		case S_IFREG:
			lde->type = LDOT_FILE;
			break;
		default:
			break;
		}
        }
}

static void
_fill_lde(char *combo, size_t l, const char *name, unsigned int type, struct lws_dir_entry *lde)
{
	lde->name = name;

	/*
	 * some filesystems don't report this (ZFS) and tell that
	 * files are LDOT_UNKNOWN
	 */

#if defined(__sun) || defined(__QNX__)
	lws_dir_via_stat(combo, l, name, lde);
#else
	/*
	 * XFS on Linux doesn't fill in d_type at all, always zero.
	 */

	     if (DT_BLK  != DT_UNKNOWN && type == DT_BLK)
		lde->type = LDOT_BLOCK;
	else if (DT_CHR  != DT_UNKNOWN && type == DT_CHR)
		lde->type = LDOT_CHAR;
	else if (DT_DIR  != DT_UNKNOWN && type == DT_DIR)
		lde->type = LDOT_DIR;
	else if (DT_FIFO != DT_UNKNOWN && type == DT_FIFO)
		lde->type = LDOT_FIFO;
	else if (DT_LNK  != DT_UNKNOWN && type == DT_LNK)
		lde->type = LDOT_LINK;
	else if (DT_REG  != DT_UNKNOWN && type == DT_REG)
		lde->type = LDOT_FILE;
	else if (DT_SOCK != DT_UNKNOWN && type == DT_SOCK)
		lde->type = LDOTT_SOCKET;
	else {
		lde->type = LDOT_UNKNOWN;
		lws_dir_via_stat(combo, l, name, lde);
	}
#endif
}

int
lws_dir_via_info(struct lws_dir_info *info)
{
	struct dirent **namelist = NULL;
	struct lws_dir_entry lde;
	char combo[COMBO_SIZEOF];
	unsigned int type = 0;
	int n, i, ret = 1;
	size_t l;

	assert(info->dirpath);
	assert(info->cb);

	l = (size_t)(ssize_t)lws_snprintf(combo, COMBO_SIZEOF - 2, "%s", info->dirpath);
	combo[l++] = csep;
	combo[l] = '\0';

	n = scandir((char *)info->dirpath, &namelist, filter, alphasort);
	if (n < 0) {
		lwsl_info("Scandir on '%s' failed, errno %d\n", info->dirpath, LWS_ERRNO);
		return 1;
	}

	for (i = 0; i < n; i++) {
#if !defined(__sun) && !defined(__QNX__)
		type = namelist[i]->d_type;
#endif
		if ((char *)strchr(namelist[i]->d_name, '~'))
			goto skip;

		_fill_lde(combo, l, namelist[i]->d_name, type, &lde);

		if (info->cb(info->dirpath, info->user, &lde)) {
			while (i < n)
				free(namelist[i++]);
			ret = 0; /* told to stop by cb */
			goto bail;
		}
skip:
		free(namelist[i]);
	}

	if (info->do_toplevel_cb) {
		lde.name = "";
		lws_dir_via_stat(combo, l, "", &lde);

		if (info->cb(info->dirpath, info->user, &lde))
			ret = 0; /* told to stop by cb */
	}

bail:
	free(namelist);

	return ret;
}

int
lws_dir(const char *dirpath, void *user, lws_dir_callback_function cb)
{
	struct lws_dir_info info;

	memset(&info, 0, sizeof(info));

	info.dirpath = dirpath;
	info.user = user;
	info.cb = cb;

	return lws_dir_via_info(&info);
}

/*
 * Check filename against one globby filter
 *
 * We can support things like "*.rpm"
 */

static int
lws_dir_glob_check(const char *nm, const char *filt)
{
	while (*nm) {
		if (*filt == '*') {
			if (!strcmp(nm, filt + 1))
				return 1;
		} else {
			if (*nm != *filt)
				return 0;
			filt++;
		}
		nm++;
	}

	return 0;
}

int
lws_dir_du_cb(const char *dirpath, void *user, struct lws_dir_entry *lde)
{
	lws_dir_du_t *du = (lws_dir_du_t *)user;
	char path[384];
	struct stat s;

	if (!strcmp(lde->name, ".") || !strcmp(lde->name, ".."))
		return 0;

	lws_snprintf(path, sizeof(path), "%s%c%s", dirpath, csep, lde->name);

	if (lde->type == LDOT_DIR) {
		lws_dir(path, user, lws_dir_du_cb);

		return 0;
	}

	if (lde->type == LDOT_FILE) {
		if (stat(path, &s))
			lwsl_warn("%s: stat %s failed %d\n", __func__,
				  path, errno);
		else {
			du->size_in_bytes += (uint64_t)s.st_size;
			du->count_files++;
		}
	}

	return 0;
}

/*
 * We get passed a single filter string, like "*.txt" or "mydir/\*.rpm" or so.
 */

int
lws_dir_glob_cb(const char *dirpath, void *user, struct lws_dir_entry *lde)
{
	lws_dir_glob_t *filter = (lws_dir_glob_t*)user;
	char path[384];

	if (!strcmp(lde->name, ".") || !strcmp(lde->name, ".."))
		return 0;

	if (lde->type == LDOT_DIR)
		return 0;

	if (lws_dir_glob_check(lde->name, filter->filter)) {
		lws_snprintf(path, sizeof(path), "%s%c%s", dirpath, csep,
							   lde->name);
		filter->cb(filter->user, path);
	}

	return 0;
}

int
lws_dir_rm_rf_cb(const char *dirpath, void *user, struct lws_dir_entry *lde)
{
	char path[384];

	if (!strcmp(lde->name, ".") || !strcmp(lde->name, ".."))
		return 0;

	lws_snprintf(path, sizeof(path), "%s%c%s", dirpath, csep, lde->name);

	if (lde->type == LDOT_DIR) {
#if !defined(WIN32) && !defined(_WIN32) && !defined(__COVERITY__)
		char dummy[8];
		/*
		 * hm... eg, recursive dir symlinks can show up a LDOT_DIR
		 * here.  If it's a symlink, don't recurse into it.
		 *
		 * Notice we immediately discard dummy without looking in it.
		 * There is no way to get into trouble from its lack of NUL
		 * termination in dummy[].  We just wanted to know if it was
		 * a symlink at all.
		 *
		 * Hide this from Coverity since it flags any use of readlink()
		 * even if safe.
		 */
		if (readlink(path, dummy, sizeof(dummy)) < 0)
#endif
			lws_dir(path, NULL, lws_dir_rm_rf_cb);

		if (rmdir(path))
			lwsl_warn("%s: rmdir %s failed %d\n", __func__, path, errno);
	} else {
		if (unlink(path)) {
#if defined(WIN32)
			SetFileAttributesA(path, FILE_ATTRIBUTE_NORMAL);
			if (unlink(path))
#else
			if (rmdir(path))
#endif
			lwsl_warn("%s: unlink %s failed %d (type %d)\n",
					__func__, path, errno, lde->type);
		}
	}

	return 0;
}


#endif

struct lws_plugin *
lws_plugin_alloc(struct lws_plugin **pplugin)
{
	struct lws_plugin *pin = lws_malloc(sizeof(*pin), __func__);

	if (!pin)
		return NULL;

	pin->list = *pplugin;
	*pplugin = pin;

	return pin;
}

#if defined(LWS_WITH_PLUGINS_API)

#if defined(LWS_BUILTIN_PLUGIN_NAMES)

extern const lws_plugin_protocol_t *builtin_pcols[];

int
lws_plugins_handle_builtin(struct lws_plugin **pplugin,
			   each_plugin_cb_t each, void *each_user)
{
	size_t n = 0;

	while (builtin_pcols[n]) {
		struct lws_plugin *pin = lws_plugin_alloc(pplugin);
		if (!pin)
			return 1;

		pin->u.l = NULL;
		pin->hdr = &builtin_pcols[n]->hdr;

		if (each)
			each(pin, each_user);

		n++;
	}

	return 0;
}
#endif

#if defined(LWS_WITH_NETWORK) && defined(LWS_WITH_PLUGINS_API)
struct lws_plugins_args {
	struct lws_plugin	**pplugin;
	const char		*_class;
	const char		*filter;
	each_plugin_cb_t	each;
	void			*each_user;
};

static unsigned int
lws_plugin_get_priority(const lws_plugin_header_t *hdr)
{
	if (hdr->api_magic >= 192)
		return hdr->priority;

	return 0;
}

static void
lws_plugins_sort(struct lws_plugin **pplugin)
{
	struct lws_plugin *p1, *p2, *prev;
	int swapped;

	if (!pplugin || !*pplugin || !(*pplugin)->list)
		return;

	do {
		swapped = 0;
		p1 = *pplugin;
		prev = NULL;

		while (p1->list) {
			p2 = p1->list;
			if (lws_plugin_get_priority(p1->hdr) <
			    lws_plugin_get_priority(p2->hdr)) {
				/* swap */
				p1->list = p2->list;
				p2->list = p1;
				if (prev)
					prev->list = p2;
				else
					*pplugin = p2;

				swapped = 1;
				prev = p2;
			} else {
				prev = p1;
				p1 = p1->list;
			}
		}
	} while (swapped);
}

static int
lws_plugins_dir_cb(const char *dirpath, void *user, struct lws_dir_entry *lde)
{
	struct lws_plugins_args *pa = (struct lws_plugins_args *)user;
	char path[256], base[64], *q = base;
	const lws_plugin_header_t *pl;
	const char *p;

	if (strlen(lde->name) < 7)
		return 0; /* keep going */

	/*
	 * The actual plugin names for protocol plugins look like
	 * "libprotocol_lws_ssh_base.so" and for event libs
	 * "libwebsockets-evlib_ev.so"... to recover the base name of
	 * "lws_ssh_base" and "evlib_ev" we strip from the left to after the
	 * first _ or -, and then truncate at the first .
	 */

	p = lde->name;
	while (*p && *p != '_' && *p != '-')
		p++;
	if (!*p)
		return 0;
	p++;
	while (*p && *p != '.' && lws_ptr_diff(q, base) < (int)sizeof(base) - 1)
		*q++ = *p++;
	*q = '\0';

	/* if he's given a filter, only match if base matches it */
	if (pa->filter && strcmp(base, pa->filter))
		return 0; /* keep going */

	lws_snprintf(path, sizeof(path) - 1, "%s/%s", dirpath, lde->name);

	pl = lws_plat_dlopen(pa->pplugin, path, base, pa->_class,
			     NULL, NULL);

	/*
	 * If we were looking for a specific plugin, finding it should make
	 * us stop looking (eg, to account for directory precedence of the
	 * same plugin).  If scanning for plugins in a dir, we always keep
	 * going.
	 */

	return pa->filter && pl;
}

int
lws_plugins_init(struct lws_plugin **pplugin, const char * const *d,
		 const char *_class, const char *filter,
		 each_plugin_cb_t each, void *each_user)
{
	struct lws_plugins_args pa;
	char *ld_env;
	int ret = 1;

	pa.pplugin = pplugin;
	pa._class = _class;
	pa.each = each;
	pa.each_user = each_user;
	pa.filter = filter;

	/*
	 * Check LD_LIBRARY_PATH override path first if present
	 */

	ld_env = getenv("LD_LIBRARY_PATH");
	if (ld_env) {
		char temp[128];
		struct lws_tokenize ts;

		memset(&ts, 0, sizeof(ts));
		ts.start = ld_env;
		ts.len = strlen(ld_env);
		ts.flags = LWS_TOKENIZE_F_SLASH_NONTERM |
			   LWS_TOKENIZE_F_DOT_NONTERM |
			   LWS_TOKENIZE_F_MINUS_NONTERM |
			   LWS_TOKENIZE_F_NO_INTEGERS |
			   LWS_TOKENIZE_F_NO_FLOATS;

		do {
			ts.e = (int8_t)lws_tokenize(&ts);
			if (ts.e != LWS_TOKZE_TOKEN)
				continue;

			lws_strnncpy(temp, ts.token,
				     ts.token_len,
				     sizeof(temp));

			lwsl_info("%s: trying %s\n", __func__, temp);
			if (!lws_dir(temp, &pa, lws_plugins_dir_cb))
				ret = 0;

		} while (ts.e > 0);
	}

	while (d && *d) {
		lwsl_info("%s: trying %s\n", __func__, *d);
		if (!lws_dir(*d, &pa, lws_plugins_dir_cb))
			ret = 0;

		d++;
	}

	if (*pplugin) {
		struct lws_plugin *p = *pplugin;

		lws_plugins_sort(pplugin);

		if (each) {
			p = *pplugin;
			while (p) {
				each(p, each_user);
				p = p->list;
			}
		}
	}

	return ret;
}

int
lws_plugins_destroy(struct lws_plugin **pplugin, each_plugin_cb_t each,
		    void *each_user)
{
	struct lws_plugin *p = *pplugin, *p1;

	while (p) {
		if (each)
			each(p, each_user);
		if (p->u.l)
			lws_plat_destroy_dl(p);
		p1 = p->list;
		p->list = NULL;
		lws_free(p);
		p = p1;
	}

	*pplugin = NULL;

	return 0;
}
#endif
#endif
