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