| /* |
| * 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; |
| } |
| } |
| } |