/* $XTermId: xstrings.c,v 1.81 2024/12/01 20:27:00 tom Exp $ */

/*
 * Copyright 2000-2023,2024 by Thomas E. Dickey
 *
 *                         All Rights Reserved
 *
 * 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 ABOVE LISTED COPYRIGHT HOLDER(S) 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.
 *
 * Except as contained in this notice, the name(s) of the above copyright
 * holders shall not be used in advertising or otherwise to promote the
 * sale, use or other dealings in this Software without prior written
 * authorization.
 */

#include <xterm.h>

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include <xstrings.h>

static void
alloc_pw(struct passwd *target, struct passwd *source)
{
    *target = *source;
    /* we care only about these strings */
    target->pw_dir = x_strdup(source->pw_dir);
    target->pw_name = x_strdup(source->pw_name);
    target->pw_shell = x_strdup(source->pw_shell);
}

static void
free_pw(struct passwd *source)
{
    free(source->pw_dir);
    free(source->pw_name);
    free(source->pw_shell);
}

void
x_appendargv(char **target, char **source)
{
    if (target && source) {
	target += x_countargv(target);
	while ((*target++ = *source++) != NULL) ;
    }
}

char *
x_basename(char *name)
{
    char *cp;

    cp = strrchr(name, '/');
    return (cp ? cp + 1 : name);
}

unsigned
x_countargv(char **argv)
{
    unsigned result = 0;
    if (argv) {
	while (*argv++) {
	    ++result;
	}
    }
    return result;
}

/*
 * Decode a hexadecimal string, returning the decoded string.
 * On return, 'next' points to the first character not part of the input.
 * The caller must free the result.
 */
char *
x_decode_hex(const char *source, const char **next)
{
    char *result = NULL;
    int pass;
    size_t j, k;

    for (pass = 0; pass < 2; ++pass) {
	for (j = k = 0; isxdigit(CharOf(source[j])); ++j) {
	    if ((pass != 0) && (j & 1) != 0) {
		result[k++] = (char) ((CharOf(x_hex2int(source[j - 1])) << 4)
				      | CharOf(x_hex2int(source[j])));
	    }
	}
	*next = (source + j);
	if ((j & 1) == 0) {
	    if (pass) {
		result[k] = '\0';
	    } else {
		result = malloc(++j);
		if (result == NULL)
		    break;	/* not enough memory */
	    }
	} else {
	    break;		/* must have an even number of digits */
	}
    }
    return result;
}

/*
 * Encode a string into hexadecimal, returning the encoded string.
 * The caller must free the result.
 */
char *
x_encode_hex(const char *source)
{
    size_t need = (strlen(source) * 2) + 1;
    char *result = malloc(need);

    if (result != NULL) {
	unsigned j, k;
	result[0] = '\0';
	for (j = k = 0; source[j] != '\0'; ++j) {
	    sprintf(result + k, "%02X", CharOf(source[j]));
	    k += 2;
	}
    }
    return result;
}

char *
x_getenv(const char *name)
{
    char *result;
    result = x_strdup(x_nonempty(getenv(name)));
    TRACE2(("getenv(%s) %s\n", name, result));
    return result;
}

static char *
login_alias(char *login_name, uid_t uid, struct passwd *in_out)
{
    /*
     * If the logon-name differs from the value we get by looking in the
     * password file, check if it does correspond to the same uid.  If so,
     * allow that as an alias for the uid.
     */
    if (!IsEmpty(login_name)
	&& strcmp(login_name, in_out->pw_name)) {
	struct passwd pw2;

	if (x_getpwnam(login_name, &pw2)) {
	    uid_t uid2 = pw2.pw_uid;
	    struct passwd pw3;
	    Boolean ok3;

	    if ((ok3 = x_getpwuid(uid, &pw3))
		&& ((uid_t) pw3.pw_uid == uid2)) {
		/* use the other passwd-data including shell */
		alloc_pw(in_out, &pw2);
	    } else {
		FreeAndNull(login_name);
	    }
	    free_pw(&pw2);
	    if (ok3)
		free_pw(&pw3);
	}
    }
    return login_name;
}

/*
 * Call this with in_out pointing to data filled in by x_getpwnam() or by
 * x_getpwnam().  It finds the user's logon name, if possible.  As a side
 * effect, it updates in_out to fill in possibly more-relevant data, i.e.,
 * in case there is more than one alias for the same uid.
 */
char *
x_getlogin(uid_t uid, struct passwd *in_out)
{
    char *login_name;

    login_name = login_alias(x_getenv("LOGNAME"), uid, in_out);
    if (IsEmpty(login_name)) {
	free(login_name);
	login_name = login_alias(x_getenv("USER"), uid, in_out);
    }
#ifdef HAVE_GETLOGIN
    /*
     * Of course getlogin() will fail if we're started from a window-manager,
     * since there's no controlling terminal to fuss with.  For that reason, we
     * tried first to get something useful from the user's $LOGNAME or $USER
     * environment variables.
     */
    if (IsEmpty(login_name)) {
	TRACE2(("...try getlogin\n"));
	free(login_name);
	login_name = login_alias(x_strdup(getlogin()), uid, in_out);
    }
#endif

    if (IsEmpty(login_name)) {
	free(login_name);
	login_name = x_strdup(in_out->pw_name);
    }

    TRACE2(("x_getloginid ->%s\n", NonNull(login_name)));
    return login_name;
}

/*
 * Simpler than getpwnam_r, retrieves the passwd result by name and stores the
 * result via the given pointer.  On failure, wipes the data to prevent use.
 */
Boolean
x_getpwnam(const char *name, struct passwd *result)
{
    struct passwd *ptr = getpwnam(name);
    Boolean code;

    if (ptr != NULL && OkPasswd(ptr)) {
	code = True;
	alloc_pw(result, ptr);
    } else {
	code = False;
	memset(result, 0, sizeof(*result));
    }
    return code;
}

/*
 * Simpler than getpwuid_r, retrieves the passwd result by uid and stores the
 * result via the given pointer.  On failure, wipes the data to prevent use.
 */
Boolean
x_getpwuid(uid_t uid, struct passwd *result)
{
    struct passwd *ptr = getpwuid((uid_t) uid);
    Boolean code;

    if (ptr != NULL && OkPasswd(ptr)) {
	code = True;
	alloc_pw(result, ptr);
    } else {
	code = False;
	memset(result, 0, sizeof(*result));
    }
    TRACE2(("x_getpwuid(%d) %d\n", (int) uid, (int) code));
    return code;
}

/*
 * Decode a single hex "nibble", returning the nibble as 0-15, or -1 on error.
 */
int
x_hex2int(int c)
{
    if (c >= '0' && c <= '9')
	return c - '0';
    if (c >= 'a' && c <= 'f')
	return c - 'a' + 10;
    if (c >= 'A' && c <= 'F')
	return c - 'A' + 10;
    return -1;
}

/*
 * Check if the given string is nonnull/nonempty.  If so, return a pointer
 * to the beginning of its content, otherwise return null.
 */
String
x_nonempty(String s)
{
    if (s != NULL) {
	if (*s == '\0') {
	    s = NULL;
	} else {
	    s = x_skip_blanks(s);
	    if (*s == '\0')
		s = NULL;
	}
    }
    return s;
}

String
x_skip_blanks(String s)
{
    while (IsSpace(CharOf(*s)))
	++s;
    return s;
}

String
x_skip_nonblanks(String s)
{
    while (*s != '\0' && !IsSpace(CharOf(*s)))
	++s;
    return s;
}

static const char *
skip_blanks(const char *s)
{
    while (IsSpace(CharOf(*s)))
	++s;
    return s;
}

/*
 * Split a command-string into an argv[]-style array.
 */
char **
x_splitargs(const char *command)
{
    char **result = NULL;

    if (command != NULL) {
	const char *first = skip_blanks(command);
	char *blob = x_strdup(first);

	if (blob != NULL) {
	    int pass;

	    for (pass = 0; pass < 2; ++pass) {
		int state;
		size_t count;
		size_t n;

		for (n = count = 0, state = 0; first[n] != '\0'; ++n) {

		    switch (state) {
		    case 0:
			if (!IsSpace(CharOf(first[n]))) {
			    state = 1;
			    if (pass)
				result[count] = blob + n;
			    ++count;
			} else {
			    blob[n] = '\0';
			}
			break;
		    case 1:
			if (IsSpace(CharOf(first[n]))) {
			    blob[n] = '\0';
			    state = 0;
			}
			break;
		    }
		}
		if (!pass) {
		    result = TypeCallocN(char *, count + 1);
		    if (!result) {
			free(blob);
			break;
		    }
		}
	    }
	}
    } else {
	result = TypeCalloc(char *);
    }
    return result;
}

/*
 * Free storage allocated by x_splitargs().
 */
void
x_freeargs(char **argv)
{
    if (argv != NULL) {
	free(*argv);
	free(argv);
    }
}

int
x_strcasecmp(const char *s1, const char *s2)
{
    size_t len1 = (s1 != NULL) ? strlen(s1) : 0;
    size_t len2 = (s2 != NULL) ? strlen(s2) : 0;

    return ((len1 != len2)
	    ? 1
	    : x_strncasecmp(s1, s2, (unsigned) len1));
}

int
x_strncasecmp(const char *s1, const char *s2, unsigned n)
{
    int result = 0;

    if (s1 != NULL && s2 != NULL) {
	while (n-- != 0) {
	    char c1 = x_toupper(*s1);
	    char c2 = x_toupper(*s2);
	    if (c1 != c2) {
		result = 1;
		break;
	    } else if (c1 == 0) {
		break;
	    }
	    s1++;
	    s2++;
	}
    } else if (s1 == NULL && s2 != NULL) {
	result = 1;
    } else if (s1 != NULL && s2 == NULL) {
	result = 1;
    }

    return result;
}

/*
 * Allocates a copy of a string
 */
char *
x_strdup(const char *s)
{
    char *result = NULL;

    if (s != NULL) {
	char *t = malloc(strlen(s) + 5);
	if (t != NULL) {
	    strcpy(t, s);
	}
	result = t;
    }
    return result;
}

/*
 * Returns a pointer to the first occurrence of s2 in s1,
 * or NULL if there are none.
 */
char *
x_strindex(char *s1, const char *s2)
{
    char *s3;
    size_t s2len = strlen(s2);

    while ((s3 = (strchr) (s1, *s2)) != NULL) {
	if (strncmp(s3, s2, s2len) == 0)
	    return (s3);
	s1 = ++s3;
    }
    return (NULL);
}

/*
 * Trims leading/trailing spaces from a copy of the string.
 */
char *
x_strtrim(const char *source)
{
    char *result;

    if (IsEmpty(source)) {
	result = x_strdup("");
    } else {
	char *t = x_strdup(source);
	if (t != NULL) {
	    char *s = t;
	    char *d = s;
	    while (IsSpace(CharOf(*s)))
		++s;
	    while ((*d++ = *s++) != '\0') {
		;
	    }
	    if (*t != '\0') {
		s = t + strlen(t);
		while (s != t && IsSpace(CharOf(s[-1]))) {
		    *--s = '\0';
		}
	    }
	}
	result = t;
    }
    return result;
}

/*
 * Trims trailing whitespace from a copy of the string.
 */
char *
x_strrtrim(const char *source)
{
    char *result;

    if (IsEmpty(source)) {
	result = x_strdup("");
    } else {
	char *t = x_strdup(source);
	if (t != NULL) {
	    if (*t != '\0') {
		char *s = t + strlen(t);
		while (s != t && IsSpace(CharOf(s[-1]))) {
		    *--s = '\0';
		}
	    }
	}
	result = t;
    }
    return result;
}

/*
 * Avoid using system locale for upper/lowercase conversion, since there are
 * a few locales where toupper(tolower(c)) != c.
 */
char
x_toupper(int ch)
{
    static char table[256];
    char result = table[CharOf(ch)];

    if (result == '\0') {
	unsigned n;
	static const char s[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

	for (n = 0; n < sizeof(table); ++n) {
	    table[n] = (char) n;
	}
	for (n = 0; s[n] != '\0'; ++n) {
	    table[CharOf(s[n])] = s[n % 26];
	}
	result = table[CharOf(ch)];
    }

    return result;
}

/*
 * Match strings ignoring case and allowing glob-like '*' and '?'
 */
int
x_wildstrcmp(const char *pattern, const char *actual)
{
    int result = 0;

    while (*pattern && *actual) {
	char c1 = x_toupper(*pattern);
	char c2 = x_toupper(*actual);

	if (c1 == '*') {
	    Boolean found = False;
	    pattern++;
	    while (*actual != '\0') {
		if (!x_wildstrcmp(pattern, actual++)) {
		    found = True;
		    break;
		}
	    }
	    if (!found) {
		result = 1;
		break;
	    }
	} else if (c1 == '?') {
	    ++pattern;
	    ++actual;
	} else if ((result = (c1 != c2)) == 0) {
	    ++pattern;
	    ++actual;
	} else {
	    break;
	}
    }
    return result;
}
