| /* |
| * Option routines for CUPS. |
| * |
| * Copyright 2007-2017 by Apple Inc. |
| * Copyright 1997-2007 by Easy Software Products. |
| * |
| * Licensed under Apache License v2.0. See the file "LICENSE" for more information. |
| */ |
| |
| /* |
| * Include necessary headers... |
| */ |
| |
| #include "cups-private.h" |
| #include "debug-internal.h" |
| |
| |
| /* |
| * Local functions... |
| */ |
| |
| static int cups_compare_options(cups_option_t *a, cups_option_t *b); |
| static int cups_find_option(const char *name, int num_options, |
| cups_option_t *option, int prev, int *rdiff); |
| |
| |
| /* |
| * 'cupsAddIntegerOption()' - Add an integer option to an option array. |
| * |
| * New option arrays can be initialized simply by passing 0 for the |
| * "num_options" parameter. |
| * |
| * @since CUPS 2.2.4/macOS 10.13@ |
| */ |
| |
| int /* O - Number of options */ |
| cupsAddIntegerOption( |
| const char *name, /* I - Name of option */ |
| int value, /* I - Value of option */ |
| int num_options, /* I - Number of options */ |
| cups_option_t **options) /* IO - Pointer to options */ |
| { |
| char strvalue[32]; /* String value */ |
| |
| |
| snprintf(strvalue, sizeof(strvalue), "%d", value); |
| |
| return (cupsAddOption(name, strvalue, num_options, options)); |
| } |
| |
| |
| /* |
| * 'cupsAddOption()' - Add an option to an option array. |
| * |
| * New option arrays can be initialized simply by passing 0 for the |
| * "num_options" parameter. |
| */ |
| |
| int /* O - Number of options */ |
| cupsAddOption(const char *name, /* I - Name of option */ |
| const char *value, /* I - Value of option */ |
| int num_options,/* I - Number of options */ |
| cups_option_t **options) /* IO - Pointer to options */ |
| { |
| cups_option_t *temp; /* Pointer to new option */ |
| int insert, /* Insertion point */ |
| diff; /* Result of search */ |
| |
| |
| DEBUG_printf(("2cupsAddOption(name=\"%s\", value=\"%s\", num_options=%d, options=%p)", name, value, num_options, (void *)options)); |
| |
| if (!name || !name[0] || !value || !options || num_options < 0) |
| { |
| DEBUG_printf(("3cupsAddOption: Returning %d", num_options)); |
| return (num_options); |
| } |
| |
| if (!_cups_strcasecmp(name, "cupsPrintQuality")) |
| num_options = cupsRemoveOption("print-quality", num_options, options); |
| else if (!_cups_strcasecmp(name, "print-quality")) |
| num_options = cupsRemoveOption("cupsPrintQuality", num_options, options); |
| |
| /* |
| * Look for an existing option with the same name... |
| */ |
| |
| if (num_options == 0) |
| { |
| insert = 0; |
| diff = 1; |
| } |
| else |
| { |
| insert = cups_find_option(name, num_options, *options, num_options - 1, |
| &diff); |
| |
| if (diff > 0) |
| insert ++; |
| } |
| |
| if (diff) |
| { |
| /* |
| * No matching option name... |
| */ |
| |
| DEBUG_printf(("4cupsAddOption: New option inserted at index %d...", |
| insert)); |
| |
| if (num_options == 0) |
| temp = (cups_option_t *)malloc(sizeof(cups_option_t)); |
| else |
| temp = (cups_option_t *)realloc(*options, sizeof(cups_option_t) * (size_t)(num_options + 1)); |
| |
| if (!temp) |
| { |
| DEBUG_puts("3cupsAddOption: Unable to expand option array, returning 0"); |
| return (0); |
| } |
| |
| *options = temp; |
| |
| if (insert < num_options) |
| { |
| DEBUG_printf(("4cupsAddOption: Shifting %d options...", |
| (int)(num_options - insert))); |
| memmove(temp + insert + 1, temp + insert, (size_t)(num_options - insert) * sizeof(cups_option_t)); |
| } |
| |
| temp += insert; |
| temp->name = _cupsStrAlloc(name); |
| num_options ++; |
| } |
| else |
| { |
| /* |
| * Match found; free the old value... |
| */ |
| |
| DEBUG_printf(("4cupsAddOption: Option already exists at index %d...", |
| insert)); |
| |
| temp = *options + insert; |
| _cupsStrFree(temp->value); |
| } |
| |
| temp->value = _cupsStrAlloc(value); |
| |
| DEBUG_printf(("3cupsAddOption: Returning %d", num_options)); |
| |
| return (num_options); |
| } |
| |
| |
| /* |
| * 'cupsFreeOptions()' - Free all memory used by options. |
| */ |
| |
| void |
| cupsFreeOptions( |
| int num_options, /* I - Number of options */ |
| cups_option_t *options) /* I - Pointer to options */ |
| { |
| int i; /* Looping var */ |
| |
| |
| DEBUG_printf(("cupsFreeOptions(num_options=%d, options=%p)", num_options, (void *)options)); |
| |
| if (num_options <= 0 || !options) |
| return; |
| |
| for (i = 0; i < num_options; i ++) |
| { |
| _cupsStrFree(options[i].name); |
| _cupsStrFree(options[i].value); |
| } |
| |
| free(options); |
| } |
| |
| |
| /* |
| * 'cupsGetIntegerOption()' - Get an integer option value. |
| * |
| * INT_MIN is returned when the option does not exist, is not an integer, or |
| * exceeds the range of values for the "int" type. |
| * |
| * @since CUPS 2.2.4/macOS 10.13@ |
| */ |
| |
| int /* O - Option value or @code INT_MIN@ */ |
| cupsGetIntegerOption( |
| const char *name, /* I - Name of option */ |
| int num_options, /* I - Number of options */ |
| cups_option_t *options) /* I - Options */ |
| { |
| const char *value = cupsGetOption(name, num_options, options); |
| /* String value of option */ |
| char *ptr; /* Pointer into string value */ |
| long intvalue; /* Integer value */ |
| |
| |
| if (!value || !*value) |
| return (INT_MIN); |
| |
| intvalue = strtol(value, &ptr, 10); |
| if (intvalue < INT_MIN || intvalue > INT_MAX || *ptr) |
| return (INT_MIN); |
| |
| return ((int)intvalue); |
| } |
| |
| |
| /* |
| * 'cupsGetOption()' - Get an option value. |
| */ |
| |
| const char * /* O - Option value or @code NULL@ */ |
| cupsGetOption(const char *name, /* I - Name of option */ |
| int num_options,/* I - Number of options */ |
| cups_option_t *options) /* I - Options */ |
| { |
| int diff, /* Result of comparison */ |
| match; /* Matching index */ |
| |
| |
| DEBUG_printf(("2cupsGetOption(name=\"%s\", num_options=%d, options=%p)", name, num_options, (void *)options)); |
| |
| if (!name || num_options <= 0 || !options) |
| { |
| DEBUG_puts("3cupsGetOption: Returning NULL"); |
| return (NULL); |
| } |
| |
| match = cups_find_option(name, num_options, options, -1, &diff); |
| |
| if (!diff) |
| { |
| DEBUG_printf(("3cupsGetOption: Returning \"%s\"", options[match].value)); |
| return (options[match].value); |
| } |
| |
| DEBUG_puts("3cupsGetOption: Returning NULL"); |
| return (NULL); |
| } |
| |
| |
| /* |
| * 'cupsParseOptions()' - Parse options from a command-line argument. |
| * |
| * This function converts space-delimited name/value pairs according |
| * to the PAPI text option ABNF specification. Collection values |
| * ("name={a=... b=... c=...}") are stored with the curley brackets |
| * intact - use @code cupsParseOptions@ on the value to extract the |
| * collection attributes. |
| */ |
| |
| int /* O - Number of options found */ |
| cupsParseOptions( |
| const char *arg, /* I - Argument to parse */ |
| int num_options, /* I - Number of options */ |
| cups_option_t **options) /* O - Options found */ |
| { |
| char *copyarg, /* Copy of input string */ |
| *ptr, /* Pointer into string */ |
| *name, /* Pointer to name */ |
| *value, /* Pointer to value */ |
| sep, /* Separator character */ |
| quote; /* Quote character */ |
| |
| |
| DEBUG_printf(("cupsParseOptions(arg=\"%s\", num_options=%d, options=%p)", arg, num_options, (void *)options)); |
| |
| /* |
| * Range check input... |
| */ |
| |
| if (!arg) |
| { |
| DEBUG_printf(("1cupsParseOptions: Returning %d", num_options)); |
| return (num_options); |
| } |
| |
| if (!options || num_options < 0) |
| { |
| DEBUG_puts("1cupsParseOptions: Returning 0"); |
| return (0); |
| } |
| |
| /* |
| * Make a copy of the argument string and then divide it up... |
| */ |
| |
| if ((copyarg = strdup(arg)) == NULL) |
| { |
| DEBUG_puts("1cupsParseOptions: Unable to copy arg string"); |
| DEBUG_printf(("1cupsParseOptions: Returning %d", num_options)); |
| return (num_options); |
| } |
| |
| if (*copyarg == '{') |
| { |
| /* |
| * Remove surrounding {} so we can parse "{name=value ... name=value}"... |
| */ |
| |
| if ((ptr = copyarg + strlen(copyarg) - 1) > copyarg && *ptr == '}') |
| { |
| *ptr = '\0'; |
| ptr = copyarg + 1; |
| } |
| else |
| ptr = copyarg; |
| } |
| else |
| ptr = copyarg; |
| |
| /* |
| * Skip leading spaces... |
| */ |
| |
| while (_cups_isspace(*ptr)) |
| ptr ++; |
| |
| /* |
| * Loop through the string... |
| */ |
| |
| while (*ptr != '\0') |
| { |
| /* |
| * Get the name up to a SPACE, =, or end-of-string... |
| */ |
| |
| name = ptr; |
| while (!strchr("\f\n\r\t\v =", *ptr) && *ptr) |
| ptr ++; |
| |
| /* |
| * Avoid an empty name... |
| */ |
| |
| if (ptr == name) |
| break; |
| |
| /* |
| * Skip trailing spaces... |
| */ |
| |
| while (_cups_isspace(*ptr)) |
| *ptr++ = '\0'; |
| |
| if ((sep = *ptr) == '=') |
| *ptr++ = '\0'; |
| |
| DEBUG_printf(("2cupsParseOptions: name=\"%s\"", name)); |
| |
| if (sep != '=') |
| { |
| /* |
| * Boolean option... |
| */ |
| |
| if (!_cups_strncasecmp(name, "no", 2)) |
| num_options = cupsAddOption(name + 2, "false", num_options, |
| options); |
| else |
| num_options = cupsAddOption(name, "true", num_options, options); |
| |
| continue; |
| } |
| |
| /* |
| * Remove = and parse the value... |
| */ |
| |
| value = ptr; |
| |
| while (*ptr && !_cups_isspace(*ptr)) |
| { |
| if (*ptr == ',') |
| ptr ++; |
| else if (*ptr == '\'' || *ptr == '\"') |
| { |
| /* |
| * Quoted string constant... |
| */ |
| |
| quote = *ptr; |
| _cups_strcpy(ptr, ptr + 1); |
| |
| while (*ptr != quote && *ptr) |
| { |
| if (*ptr == '\\' && ptr[1]) |
| _cups_strcpy(ptr, ptr + 1); |
| |
| ptr ++; |
| } |
| |
| if (*ptr) |
| _cups_strcpy(ptr, ptr + 1); |
| } |
| else if (*ptr == '{') |
| { |
| /* |
| * Collection value... |
| */ |
| |
| int depth; |
| |
| for (depth = 0; *ptr; ptr ++) |
| { |
| if (*ptr == '{') |
| depth ++; |
| else if (*ptr == '}') |
| { |
| depth --; |
| if (!depth) |
| { |
| ptr ++; |
| break; |
| } |
| } |
| else if (*ptr == '\\' && ptr[1]) |
| _cups_strcpy(ptr, ptr + 1); |
| } |
| } |
| else |
| { |
| /* |
| * Normal space-delimited string... |
| */ |
| |
| while (*ptr && !_cups_isspace(*ptr)) |
| { |
| if (*ptr == '\\' && ptr[1]) |
| _cups_strcpy(ptr, ptr + 1); |
| |
| ptr ++; |
| } |
| } |
| } |
| |
| if (*ptr != '\0') |
| *ptr++ = '\0'; |
| |
| DEBUG_printf(("2cupsParseOptions: value=\"%s\"", value)); |
| |
| /* |
| * Skip trailing whitespace... |
| */ |
| |
| while (_cups_isspace(*ptr)) |
| ptr ++; |
| |
| /* |
| * Add the string value... |
| */ |
| |
| num_options = cupsAddOption(name, value, num_options, options); |
| } |
| |
| /* |
| * Free the copy of the argument we made and return the number of options |
| * found. |
| */ |
| |
| free(copyarg); |
| |
| DEBUG_printf(("1cupsParseOptions: Returning %d", num_options)); |
| |
| return (num_options); |
| } |
| |
| |
| /* |
| * 'cupsRemoveOption()' - Remove an option from an option array. |
| * |
| * @since CUPS 1.2/macOS 10.5@ |
| */ |
| |
| int /* O - New number of options */ |
| cupsRemoveOption( |
| const char *name, /* I - Option name */ |
| int num_options, /* I - Current number of options */ |
| cups_option_t **options) /* IO - Options */ |
| { |
| int i; /* Looping var */ |
| cups_option_t *option; /* Current option */ |
| |
| |
| DEBUG_printf(("2cupsRemoveOption(name=\"%s\", num_options=%d, options=%p)", name, num_options, (void *)options)); |
| |
| /* |
| * Range check input... |
| */ |
| |
| if (!name || num_options < 1 || !options) |
| { |
| DEBUG_printf(("3cupsRemoveOption: Returning %d", num_options)); |
| return (num_options); |
| } |
| |
| /* |
| * Loop for the option... |
| */ |
| |
| for (i = num_options, option = *options; i > 0; i --, option ++) |
| if (!_cups_strcasecmp(name, option->name)) |
| break; |
| |
| if (i) |
| { |
| /* |
| * Remove this option from the array... |
| */ |
| |
| DEBUG_puts("4cupsRemoveOption: Found option, removing it..."); |
| |
| num_options --; |
| i --; |
| |
| _cupsStrFree(option->name); |
| _cupsStrFree(option->value); |
| |
| if (i > 0) |
| memmove(option, option + 1, (size_t)i * sizeof(cups_option_t)); |
| } |
| |
| /* |
| * Return the new number of options... |
| */ |
| |
| DEBUG_printf(("3cupsRemoveOption: Returning %d", num_options)); |
| return (num_options); |
| } |
| |
| |
| /* |
| * '_cupsGet1284Values()' - Get 1284 device ID keys and values. |
| * |
| * The returned dictionary is a CUPS option array that can be queried with |
| * cupsGetOption and freed with cupsFreeOptions. |
| */ |
| |
| int /* O - Number of key/value pairs */ |
| _cupsGet1284Values( |
| const char *device_id, /* I - IEEE-1284 device ID string */ |
| cups_option_t **values) /* O - Array of key/value pairs */ |
| { |
| int num_values; /* Number of values */ |
| char key[256], /* Key string */ |
| value[256], /* Value string */ |
| *ptr; /* Pointer into key/value */ |
| |
| |
| /* |
| * Range check input... |
| */ |
| |
| if (values) |
| *values = NULL; |
| |
| if (!device_id || !values) |
| return (0); |
| |
| /* |
| * Parse the 1284 device ID value into keys and values. The format is |
| * repeating sequences of: |
| * |
| * [whitespace]key:value[whitespace]; |
| */ |
| |
| num_values = 0; |
| while (*device_id) |
| { |
| while (_cups_isspace(*device_id)) |
| device_id ++; |
| |
| if (!*device_id) |
| break; |
| |
| for (ptr = key; *device_id && *device_id != ':'; device_id ++) |
| if (ptr < (key + sizeof(key) - 1)) |
| *ptr++ = *device_id; |
| |
| if (!*device_id) |
| break; |
| |
| while (ptr > key && _cups_isspace(ptr[-1])) |
| ptr --; |
| |
| *ptr = '\0'; |
| device_id ++; |
| |
| while (_cups_isspace(*device_id)) |
| device_id ++; |
| |
| if (!*device_id) |
| break; |
| |
| for (ptr = value; *device_id && *device_id != ';'; device_id ++) |
| if (ptr < (value + sizeof(value) - 1)) |
| *ptr++ = *device_id; |
| |
| if (!*device_id) |
| break; |
| |
| while (ptr > value && _cups_isspace(ptr[-1])) |
| ptr --; |
| |
| *ptr = '\0'; |
| device_id ++; |
| |
| num_values = cupsAddOption(key, value, num_values, values); |
| } |
| |
| return (num_values); |
| } |
| |
| |
| /* |
| * 'cups_compare_options()' - Compare two options. |
| */ |
| |
| static int /* O - Result of comparison */ |
| cups_compare_options(cups_option_t *a, /* I - First option */ |
| cups_option_t *b) /* I - Second option */ |
| { |
| return (_cups_strcasecmp(a->name, b->name)); |
| } |
| |
| |
| /* |
| * 'cups_find_option()' - Find an option using a binary search. |
| */ |
| |
| static int /* O - Index of match */ |
| cups_find_option( |
| const char *name, /* I - Option name */ |
| int num_options, /* I - Number of options */ |
| cups_option_t *options, /* I - Options */ |
| int prev, /* I - Previous index */ |
| int *rdiff) /* O - Difference of match */ |
| { |
| int left, /* Low mark for binary search */ |
| right, /* High mark for binary search */ |
| current, /* Current index */ |
| diff; /* Result of comparison */ |
| cups_option_t key; /* Search key */ |
| |
| |
| DEBUG_printf(("7cups_find_option(name=\"%s\", num_options=%d, options=%p, prev=%d, rdiff=%p)", name, num_options, (void *)options, prev, (void *)rdiff)); |
| |
| #ifdef DEBUG |
| for (left = 0; left < num_options; left ++) |
| DEBUG_printf(("9cups_find_option: options[%d].name=\"%s\", .value=\"%s\"", |
| left, options[left].name, options[left].value)); |
| #endif /* DEBUG */ |
| |
| key.name = (char *)name; |
| |
| if (prev >= 0) |
| { |
| /* |
| * Start search on either side of previous... |
| */ |
| |
| if ((diff = cups_compare_options(&key, options + prev)) == 0 || |
| (diff < 0 && prev == 0) || |
| (diff > 0 && prev == (num_options - 1))) |
| { |
| *rdiff = diff; |
| return (prev); |
| } |
| else if (diff < 0) |
| { |
| /* |
| * Start with previous on right side... |
| */ |
| |
| left = 0; |
| right = prev; |
| } |
| else |
| { |
| /* |
| * Start wih previous on left side... |
| */ |
| |
| left = prev; |
| right = num_options - 1; |
| } |
| } |
| else |
| { |
| /* |
| * Start search in the middle... |
| */ |
| |
| left = 0; |
| right = num_options - 1; |
| } |
| |
| do |
| { |
| current = (left + right) / 2; |
| diff = cups_compare_options(&key, options + current); |
| |
| if (diff == 0) |
| break; |
| else if (diff < 0) |
| right = current; |
| else |
| left = current; |
| } |
| while ((right - left) > 1); |
| |
| if (diff != 0) |
| { |
| /* |
| * Check the last 1 or 2 elements... |
| */ |
| |
| if ((diff = cups_compare_options(&key, options + left)) <= 0) |
| current = left; |
| else |
| { |
| diff = cups_compare_options(&key, options + right); |
| current = right; |
| } |
| } |
| |
| /* |
| * Return the closest destination and the difference... |
| */ |
| |
| *rdiff = diff; |
| |
| return (current); |
| } |