blob: 74cd0edebe61ccd91a19b60ee9465149a0f4000a [file] [log] [blame]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "parse.h"
#include "log.h"
char *
joinStrings (const char *const *strings, int count) {
char *string;
size_t length = 0;
size_t lengths[count];
for (unsigned int index=0; index<count; index+=1) {
length += lengths[index] = strlen(strings[index]);
}
if ((string = malloc(length+1))) {
char *target = string;
for (unsigned int index=0; index<count; index+=1) {
length = lengths[index];
memcpy(target, strings[index], length);
target += length;
}
*target = 0;
}
return string;
}
int
changeStringSetting (char **setting, const char *value) {
if (value == *setting) return 1;
char *string;
if (!value) {
string = NULL;
} else if (!(string = strdup(value))) {
logMallocError();
return 0;
}
if (*setting) free(*setting);
*setting = string;
return 1;
}
int
extendStringSetting (char **setting, const char *value, int prepend) {
if (!value) value = "";
if (*setting && **setting) {
if (*value) {
size_t newSize = strlen(*setting) + 1 + strlen(value) + 1;
char newSetting[newSize];
if (prepend) {
snprintf(newSetting, newSize, "%s%c%s", value, PARAMETER_SEPARATOR_CHARACTER, *setting);
} else {
snprintf(newSetting, newSize, "%s%c%s", *setting, PARAMETER_SEPARATOR_CHARACTER, value);
}
if (!changeStringSetting(setting, newSetting)) return 0;
}
} else if (!changeStringSetting(setting, value)) {
return 0;
}
return 1;
}
void
deallocateStrings (char **array) {
char **element = array;
while (*element) free(*element++);
free(array);
}
char **
splitString (const char *string, char delimiter, int *count) {
char **array = NULL;
if (!string) string = "";
if (string) {
while (1) {
const char *start = string;
int index = 0;
if (*start) {
while (1) {
const char *end = strchr(start, delimiter);
size_t length = end? (size_t)(end-start): strlen(start);
if (array) {
char *element = malloc(length+1);
if (!(array[index] = element)) {
logMallocError();
deallocateStrings(array);
array = NULL;
goto done;
}
memcpy(element, start, length);
element[length] = 0;
}
index += 1;
if (!end) break;
start = end + 1;
}
}
if (array) {
array[index] = NULL;
if (count) *count = index;
break;
}
if (!(array = malloc((index + 1) * sizeof(*array)))) {
logMallocError();
break;
}
}
}
done:
if (!array && count) *count = 0;
return array;
}
int
changeListSetting (char ***list, char **setting, const char *value) {
char **newList = splitString(value, PARAMETER_SEPARATOR_CHARACTER, NULL);
if (newList) {
if (changeStringSetting(setting, value)) {
char **oldList = *list;
*list = newList;
if (oldList) deallocateStrings(oldList);
return 1;
}
deallocateStrings(newList);
}
return 0;
}
int
rescaleInteger (int value, int from, int to) {
return (to * (value + (from / (to * 2)))) / from;
}
int
isInteger (int *value, const char *string) {
if (*string) {
char *end;
long l = strtol(string, &end, 0);
if (!*end) {
*value = l;
return 1;
}
}
return 0;
}
int
isUnsignedInteger (unsigned int *value, const char *string) {
if (*string) {
char *end;
unsigned long l = strtoul(string, &end, 0);
if (!*end) {
*value = l;
return 1;
}
}
return 0;
}
int
isLogLevel (unsigned int *level, const char *string) {
{
size_t length = strlen(string);
for (unsigned int index=0; index<logLevelCount; index+=1) {
if (strncasecmp(string, logLevelNames[index], length) == 0) {
*level = index;
return 1;
}
}
}
{
unsigned int value;
if (isUnsignedInteger(&value, string) && (value < logLevelCount)) {
*level = value;
return 1;
}
}
return 0;
}
int
isAbbreviation (const char *actual, const char *supplied) {
return strncasecmp(actual, supplied, strlen(supplied)) == 0;
}
int
isAbbreviatedPhrase (const char *actual, const char *supplied) {
while (1) {
if (!*supplied) return 1;
if (*supplied == '-') {
while (*actual != '-') {
if (!*actual) return 0;
actual += 1;
}
} else if (tolower(*supplied) != tolower(*actual)) {
return 0;
}
actual += 1;
supplied += 1;
}
}
int
validateInteger (int *value, const char *string, const int *minimum, const int *maximum) {
if (*string) {
int i;
if (!isInteger(&i, string)) return 0;
if (minimum && (i < *minimum)) return 0;
if (maximum && (i > *maximum)) return 0;
*value = i;
}
return 1;
}
int
validateChoiceEx (unsigned int *value, const char *string, const void *choices, size_t size) {
*value = 0;
if (!*string) return 1;
const void *choice = choices;
while (1) {
typedef struct {
const char *name;
} Entry;
const Entry *entry = choice;
const char *name = entry->name;
if (!name) break;
if (isAbbreviatedPhrase(name, string)) {
*value = (choice - choices) / size;
return 1;
}
choice += size;
}
return 0;
}
int
validateChoice (unsigned int *value, const char *string, const char *const *choices) {
return validateChoiceEx(value, string, choices, sizeof(*choices));
}
FlagKeywordPair fkpOnOff = {.on="on" , .off="off" };
FlagKeywordPair fkpTrueFalse = {.on="true", .off="false"};
FlagKeywordPair fkpYesNo = {.on="yes" , .off="no" };
FlagKeywordPair fkp10 = {.on="1" , .off="0" };
const FlagKeywordPair *const flagKeywordPairs[] = {
&fkpOnOff, &fkpTrueFalse, &fkpYesNo, &fkp10
};
int
validateFlagKeyword (unsigned int *value, const char *string) {
static const char **choices = NULL;
if (!choices) {
unsigned int count = ARRAY_COUNT(flagKeywordPairs);
size_t size = ARRAY_SIZE(choices, ((count * 2) + 1));
if (!(choices = malloc(size))) {
logMallocError();
return 0;
}
const FlagKeywordPair *const *fkp = flagKeywordPairs;
const FlagKeywordPair *const *end = fkp + count;
const char **choice = choices;
while (fkp < end) {
*choice++ = (*fkp)->off;
*choice++ = (*fkp)->on;
fkp += 1;
}
*choice = NULL;
}
if (!validateChoice(value, string, choices)) return 0;
*value %= 2;
return 1;
}
int
validateFlag (unsigned int *value, const char *string, const FlagKeywordPair *fkp) {
const char *choices[] = {fkp->off, fkp->on, NULL};
return validateChoice(value, string, choices);
}
int
validateOnOff (unsigned int *value, const char *string) {
return validateFlag(value, string, &fkpOnOff);
}
int
validateYesNo (unsigned int *value, const char *string) {
return validateFlag(value, string, &fkpYesNo);
}
#ifndef NO_FLOAT
int
isFloat (float *value, const char *string) {
if (*string) {
char *end;
double d = strtod(string, &end);
if (!*end) {
*value = d;
return 1;
}
}
return 0;
}
int
validateFloat (float *value, const char *string, const float *minimum, const float *maximum) {
if (*string) {
float f;
if (!isFloat(&f, string)) return 0;
if (minimum && (f < *minimum)) return 0;
if (maximum && (f > *maximum)) return 0;
*value = f;
}
return 1;
}
#endif /* NO_FLOAT */
#if defined(HAVE_PWD_H) && defined(HAVE_GRP_H)
#include <pwd.h>
#include <grp.h>
int
validateUser (uid_t *value, const char *string, gid_t *group) {
{
int integer = geteuid();
static const int minimum = 0;
if (validateInteger(&integer, string, &minimum, NULL)) {
*value = integer;
if (group) {
struct passwd *user = getpwuid(*value);
*group = user? user->pw_gid: 0;
}
return 1;
}
}
{
struct passwd *user = getpwnam(string);
if (user) {
*value = user->pw_uid;
if (group) *group = user->pw_gid;
return 1;
}
}
return 0;
}
int
validateGroup (gid_t *value, const char *string) {
{
int integer = geteuid();
static const int minimum = 0;
if (validateInteger(&integer, string, &minimum, NULL)) {
*value = integer;
return 1;
}
}
{
struct group *group = getgrnam(string);
if (group) {
*value = group->gr_gid;
return 1;
}
}
return 0;
}
#endif /* defined(HAVE_PWD_H) && defined(HAVE_GRP_H) */
int
hasQualifier (const char **identifier, const char *qualifier) {
const char *delimiter = strchr(*identifier, PARAMETER_QUALIFIER_CHARACTER);
if (!delimiter) return 0;
size_t count = delimiter - *identifier;
if (memchr(*identifier, PATH_SEPARATOR_CHARACTER, count)) return 0;
if (qualifier) {
if (count != strlen(qualifier)) return 0;
if (strncasecmp(*identifier, qualifier, count) != 0) return 0;
}
*identifier += count + 1;
return 1;
}
int
hasNoQualifier (const char *identifier) {
return !hasQualifier(&identifier, NULL);
}
static int
parseParameters (
char **values,
const char *const *names,
const char *qualifier,
const char *parameters
) {
if (parameters && *parameters) {
const char *parameter = parameters;
while (1) {
const char *parameterEnd = strchr(parameter, PARAMETER_SEPARATOR_CHARACTER);
int done = !parameterEnd;
if (done) parameterEnd = parameter + strlen(parameter);
int parameterLength = parameterEnd - parameter;
if (parameterLength > 0) {
const char *value = memchr(parameter, PARAMETER_ASSIGNMENT_CHARACTER, parameterLength);
if (!value) {
logMessage(LOG_WARNING, "%s: %.*s",
gettext("missing parameter value"),
parameterLength, parameter);
goto NEXT_PARAMETER;
}
{
const char *name = parameter;
size_t nameLength = value++ - name;
size_t valueLength = parameterEnd - value;
int isEligible = 1;
if (qualifier) {
const char *delimiter = memchr(name, PARAMETER_QUALIFIER_CHARACTER, nameLength);
if (delimiter) {
size_t qualifierLength = delimiter - name;
size_t nameAdjustment = qualifierLength + 1;
name += nameAdjustment;
nameLength -= nameAdjustment;
isEligible = 0;
if (!qualifierLength) {
logMessage(LOG_WARNING, "%s: %.*s",
gettext("missing parameter qualifier"),
parameterLength, parameter);
goto NEXT_PARAMETER;
}
if ((qualifierLength == strlen(qualifier)) &&
(memcmp(parameter, qualifier, qualifierLength) == 0)) {
isEligible = 1;
}
}
}
if (!nameLength) {
logMessage(LOG_WARNING, "%s: %.*s",
gettext("missing parameter name"),
parameterLength, parameter);
goto NEXT_PARAMETER;
}
if (isEligible) {
unsigned int index = 0;
while (names[index]) {
if (strncasecmp(name, names[index], nameLength) == 0) {
char *newValue = malloc(valueLength + 1);
if (!newValue) {
logMallocError();
return 0;
}
memcpy(newValue, value, valueLength);
newValue[valueLength] = 0;
free(values[index]);
values[index] = newValue;
goto NEXT_PARAMETER;
}
index += 1;
}
logMessage(LOG_WARNING, "%s: %.*s",
gettext("unsupported parameter"),
parameterLength, parameter);
goto NEXT_PARAMETER;
}
}
}
NEXT_PARAMETER:
if (done) break;
parameter = parameterEnd + 1;
}
}
return 1;
}
char **
getParameters (const char *const *names, const char *qualifier, const char *parameters) {
if (!names) {
static const char *const noNames[] = {NULL};
names = noNames;
}
{
char **values;
unsigned int count = 0;
while (names[count]) count += 1;
if ((values = malloc((count + 1) * sizeof(*values)))) {
unsigned int index = 0;
while (index < count) {
if (!(values[index] = strdup(""))) {
logMallocError();
break;
}
index += 1;
}
if (index == count) {
values[index] = NULL;
if (parseParameters(values, names, qualifier, parameters)) return values;
}
deallocateStrings(values);
} else {
logMallocError();
}
}
return NULL;
}
void
logParameters (const char *const *names, char **values, const char *description) {
if (names && values) {
while (*names) {
logMessage(LOG_INFO, "%s: %s=%s", description, *names, *values);
++names;
++values;
}
}
}