]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - cups/options.c
Save work on documentation.
[thirdparty/cups.git] / cups / options.c
index cd38c185bc585972f2479a5f04be805c34b0516e..9aa20f8954ec5fe0acdb0acd0ed38317ac44c77a 100644 (file)
 /*
- * "$Id: options.c 6649 2007-07-11 21:46:42Z mike $"
+ * Option routines for CUPS.
  *
- *   Option routines for the Common UNIX Printing System (CUPS).
+ * Copyright 2007-2017 by Apple Inc.
+ * Copyright 1997-2007 by Easy Software Products.
  *
- *   Copyright 2007 by Apple Inc.
- *   Copyright 1997-2007 by Easy Software Products.
+ * These coded instructions, statements, and computer programs are the
+ * property of Apple Inc. and are protected by Federal copyright
+ * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
+ * which should have been included with this file.  If this file is
+ * missing or damaged, see the license at "http://www.cups.org/".
  *
- *   These coded instructions, statements, and computer programs are the
- *   property of Apple Inc. and are protected by Federal copyright
- *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
- *   which should have been included with this file.  If this file is
- *   file is missing or damaged, see the license at "http://www.cups.org/".
- *
- *   This file is subject to the Apple OS-Developed Software exception.
- *
- * Contents:
- *
- *   cupsAddOption()     - Add an option to an option array.
- *   cupsFreeOptions()   - Free all memory used by options.
- *   cupsGetOption()     - Get an option value.
- *   cupsMarkOptions()   - Mark command-line options in a PPD file.
- *   cupsParseOptions()  - Parse options from a command-line argument.
- *   cupsRemoveOptions() - Remove an option from an option array.
- *   ppd_mark_choices()  - Mark one or more option choices from a string.
+ * This file is subject to the Apple OS-Developed Software exception.
  */
 
 /*
  * Include necessary headers...
  */
 
-#include "cups.h"
-#include <stdlib.h>
-#include <ctype.h>
-#include "string.h"
-#include "debug.h"
+#include "cups-private.h"
 
 
 /*
  * Local functions...
  */
 
-static int     ppd_mark_choices(ppd_file_t *ppd, const char *options);
+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 */
+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 */
 {
-  int          i;                      /* Looping var */
   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 == NULL || !name[0] || value == NULL ||
-      options == NULL || num_options < 0)
+  if (!name || !name[0] || !value || !options || num_options < 0)
+  {
+    DEBUG_printf(("3cupsAddOption: Returning %d", num_options));
     return (num_options);
+  }
 
  /*
   * Look for an existing option with the same name...
   */
 
-  for (i = 0, temp = *options; i < num_options; i ++, temp ++)
-    if (strcasecmp(temp->name, name) == 0)
-      break;
+  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 (i >= num_options)
+  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) *
-                                               (num_options + 1));
+      temp = (cups_option_t *)realloc(*options, sizeof(cups_option_t) * (size_t)(num_options + 1));
 
-    if (temp == NULL)
+    if (!temp)
+    {
+      DEBUG_puts("3cupsAddOption: Unable to expand option array, returning 0");
       return (0);
+    }
+
+    *options = temp;
 
-    *options    = temp;
-    temp        += num_options;
-    temp->name  = strdup(name);
+    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
@@ -95,10 +137,16 @@ cupsAddOption(const char    *name, /* I - Name of option */
     * Match found; free the old value...
     */
 
-    free(temp->value);
+    DEBUG_printf(("4cupsAddOption: Option already exists at index %d...",
+                  insert));
+
+    temp = *options + insert;
+    _cupsStrFree(temp->value);
   }
 
-  temp->value = strdup(value);
+  temp->value = _cupsStrAlloc(value);
+
+  DEBUG_printf(("3cupsAddOption: Returning %d", num_options));
 
   return (num_options);
 }
@@ -116,13 +164,15 @@ cupsFreeOptions(
   int  i;                              /* Looping var */
 
 
-  if (num_options <= 0 || options == NULL)
+  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 ++)
   {
-    free(options[i].name);
-    free(options[i].value);
+    _cupsStrFree(options[i].name);
+    _cupsStrFree(options[i].value);
   }
 
   free(options);
@@ -130,312 +180,68 @@ cupsFreeOptions(
 
 
 /*
- * 'cupsGetOption()' - Get an option value.
+ * '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@
  */
 
-const char *                           /* O - Option value or NULL */
-cupsGetOption(const char    *name,     /* I - Name of option */
-              int           num_options,/* I - Number of options */
-              cups_option_t *options)  /* I - Options */
+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 */
 {
-  int  i;                              /* Looping var */
+  const char   *value = cupsGetOption(name, num_options, options);
+                                       /* String value of option */
+  char         *ptr;                   /* Pointer into string value */
+  long         intvalue;               /* Integer value */
 
 
-  if (name == NULL || num_options <= 0 || options == NULL)
-    return (NULL);
+  if (!value || !*value)
+    return (INT_MIN);
 
-  for (i = 0; i < num_options; i ++)
-    if (strcasecmp(options[i].name, name) == 0)
-      return (options[i].value);
+  intvalue = strtol(value, &ptr, 10);
+  if (intvalue < INT_MIN || intvalue > INT_MAX || *ptr)
+    return (INT_MIN);
 
-  return (NULL);
+  return ((int)intvalue);
 }
 
 
 /*
- * 'cupsMarkOptions()' - Mark command-line options in a PPD file.
+ * 'cupsGetOption()' - Get an option value.
  */
 
-int                                    /* O - 1 if conflicting */
-cupsMarkOptions(
-    ppd_file_t    *ppd,                        /* I - PPD file */
-    int           num_options,         /* I - Number of options */
-    cups_option_t *options)            /* I - Options */
+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          i, j, k;                /* Looping vars */
-  int          conflict;               /* Option conflicts */
-  char         *val,                   /* Pointer into value */
-               *ptr,                   /* Pointer into string */
-               s[255];                 /* Temporary string */
-  const char   *page_size;             /* PageSize option */
-  cups_option_t        *optptr;                /* Current option */
-  ppd_option_t *option;                /* PPD option */
-  ppd_attr_t   *attr;                  /* PPD attribute */
-  static const char * const duplex_options[] =
-               {                       /* Duplex option names */
-                 "Duplex",             /* Adobe */
-                 "EFDuplex",           /* EFI */
-                 "EFDuplexing",        /* EFI */
-                 "KD03Duplex",         /* Kodak */
-                 "JCLDuplex"           /* Samsung */
-               };
-  static const char * const duplex_one[] =
-               {                       /* one-sided names */
-                 "None",
-                 "False"
-               };
-  static const char * const duplex_two_long[] =
-               {                       /* two-sided-long-edge names */
-                 "DuplexNoTumble",     /* Adobe */
-                 "LongEdge",           /* EFI */
-                 "Top"                 /* EFI */
-               };
-  static const char * const duplex_two_short[] =
-               {                       /* two-sided-long-edge names */
-                 "DuplexTumble",       /* Adobe */
-                 "ShortEdge",          /* EFI */
-                 "Bottom"              /* EFI */
-               };
-
-
- /*
-  * Check arguments...
-  */
-
-  if (ppd == NULL || num_options <= 0 || options == NULL)
-    return (0);
-
- /*
-  * Mark options...
-  */
-
-  conflict  = 0;
-
-  for (i = num_options, optptr = options; i > 0; i --, optptr ++)
-    if (!strcasecmp(optptr->name, "media"))
-    {
-     /*
-      * Loop through the option string, separating it at commas and
-      * marking each individual option as long as the corresponding
-      * PPD option (PageSize, InputSlot, etc.) is not also set.
-      *
-      * For PageSize, we also check for an empty option value since
-      * some versions of MacOS X use it to specify auto-selection
-      * of the media based solely on the size.
-      */
-
-      page_size = cupsGetOption("PageSize", num_options, options);
-
-      for (val = optptr->value; *val;)
-      {
-       /*
-        * Extract the sub-option from the string...
-       */
-
-        for (ptr = s; *val && *val != ',' && (ptr - s) < (sizeof(s) - 1);)
-         *ptr++ = *val++;
-       *ptr++ = '\0';
-
-       if (*val == ',')
-         val ++;
-
-       /*
-        * Mark it...
-       */
-
-        if (!page_size || !page_size[0])
-         if (ppdMarkOption(ppd, "PageSize", s))
-            conflict = 1;
-
-        if (cupsGetOption("InputSlot", num_options, options) == NULL)
-         if (ppdMarkOption(ppd, "InputSlot", s))
-            conflict = 1;
+  int  diff,                           /* Result of comparison */
+       match;                          /* Matching index */
 
-        if (cupsGetOption("MediaType", num_options, options) == NULL)
-         if (ppdMarkOption(ppd, "MediaType", s))
-            conflict = 1;
 
-        if (cupsGetOption("EFMediaType", num_options, options) == NULL)
-         if (ppdMarkOption(ppd, "EFMediaType", s))
-            conflict = 1;
+  DEBUG_printf(("2cupsGetOption(name=\"%s\", num_options=%d, options=%p)", name, num_options, (void *)options));
 
-        if (cupsGetOption("EFMediaQualityMode", num_options, options) == NULL)
-         if (ppdMarkOption(ppd, "EFMediaQualityMode", s))      /* EFI */
-            conflict = 1;
-
-       if (strcasecmp(s, "manual") == 0 &&
-           cupsGetOption("ManualFeed", num_options, options) == NULL)
-          if (ppdMarkOption(ppd, "ManualFeed", "True"))
-           conflict = 1;
-      }
-    }
-    else if (!strcasecmp(optptr->name, "sides"))
-    {
-      for (j = 0; j < (int)(sizeof(duplex_options) / sizeof(duplex_options[0])); j ++)
-        if (cupsGetOption(duplex_options[j], num_options, options) != NULL)
-         break;
-
-      if (j < (int)(sizeof(duplex_options) / sizeof(duplex_options[0])))
-      {
-       /*
-        * Don't override the PPD option with the IPP attribute...
-       */
-
-        continue;
-      }
-
-      if (!strcasecmp(optptr->value, "one-sided"))
-      {
-       /*
-        * Mark the appropriate duplex option for one-sided output...
-       */
-
-        for (j = 0; j < (int)(sizeof(duplex_options) / sizeof(duplex_options[0])); j ++)
-         if ((option = ppdFindOption(ppd, duplex_options[j])) != NULL)
-           break;
-
-       if (j < (int)(sizeof(duplex_options) / sizeof(duplex_options[0])))
-       {
-          for (k = 0; k < (int)(sizeof(duplex_one) / sizeof(duplex_one[0])); k ++)
-            if (ppdFindChoice(option, duplex_one[k]))
-           {
-             if (ppdMarkOption(ppd, duplex_options[j], duplex_one[k]))
-               conflict = 1;
-
-             break;
-            }
-        }
-      }
-      else if (!strcasecmp(optptr->value, "two-sided-long-edge"))
-      {
-       /*
-        * Mark the appropriate duplex option for two-sided-long-edge output...
-       */
-
-        for (j = 0; j < (int)(sizeof(duplex_options) / sizeof(duplex_options[0])); j ++)
-         if ((option = ppdFindOption(ppd, duplex_options[j])) != NULL)
-           break;
-
-       if (j < (int)(sizeof(duplex_options) / sizeof(duplex_options[0])))
-       {
-          for (k = 0; k < (int)(sizeof(duplex_two_long) / sizeof(duplex_two_long[0])); k ++)
-            if (ppdFindChoice(option, duplex_two_long[k]))
-           {
-             if (ppdMarkOption(ppd, duplex_options[j], duplex_two_long[k]))
-               conflict = 1;
-
-             break;
-            }
-        }
-      }
-      else if (!strcasecmp(optptr->value, "two-sided-short-edge"))
-      {
-       /*
-        * Mark the appropriate duplex option for two-sided-short-edge output...
-       */
-
-        for (j = 0; j < (int)(sizeof(duplex_options) / sizeof(duplex_options[0])); j ++)
-         if ((option = ppdFindOption(ppd, duplex_options[j])) != NULL)
-           break;
-
-       if (j < (int)(sizeof(duplex_options) / sizeof(duplex_options[0])))
-       {
-          for (k = 0; k < (int)(sizeof(duplex_two_short) / sizeof(duplex_two_short[0])); k ++)
-            if (ppdFindChoice(option, duplex_two_short[k]))
-           {
-             if (ppdMarkOption(ppd, duplex_options[j], duplex_two_short[k]))
-               conflict = 1;
-
-             break;
-            }
-        }
-      }
-    }
-    else if (!strcasecmp(optptr->name, "resolution") ||
-             !strcasecmp(optptr->name, "printer-resolution"))
-    {
-      if (ppdMarkOption(ppd, "Resolution", optptr->value))
-        conflict = 1;
-      if (ppdMarkOption(ppd, "SetResolution", optptr->value))
-       /* Calcomp, Linotype, QMS, Summagraphics, Tektronix, Varityper */
-        conflict = 1;
-      if (ppdMarkOption(ppd, "JCLResolution", optptr->value))  /* HP */
-        conflict = 1;
-      if (ppdMarkOption(ppd, "CNRes_PGP", optptr->value))      /* Canon */
-        conflict = 1;
-    }
-    else if (!strcasecmp(optptr->name, "output-bin"))
-    {
-      if (!cupsGetOption("OutputBin", num_options, options))
-        if (ppdMarkOption(ppd, "OutputBin", optptr->value))
-          conflict = 1;
-    }
-    else if (!strcasecmp(optptr->name, "multiple-document-handling"))
-    {
-      if (!cupsGetOption("Collate", num_options, options) &&
-          ppdFindOption(ppd, "Collate"))
-      {
-        if (strcasecmp(optptr->value, "separate-documents-uncollated-copies"))
-       {
-         if (ppdMarkOption(ppd, "Collate", "True"))
-            conflict = 1;
-        }
-       else
-       {
-         if (ppdMarkOption(ppd, "Collate", "False"))
-            conflict = 1;
-        }
-      }
-    }
-    else if (!strcasecmp(optptr->name, "finishings"))
-    {
-     /*
-      * Lookup cupsIPPFinishings attributes for each value...
-      */
-
-      for (ptr = optptr->value; *ptr;)
-      {
-       /*
-        * Get the next finishings number...
-       */
-
-        if (!isdigit(*ptr & 255))
-         break;
-
-        if ((j = strtol(ptr, &ptr, 10)) < 3)
-         break;
-
-       /*
-        * Skip separator as needed...
-       */
-
-        if (*ptr == ',')
-         ptr ++;
-
-       /*
-        * Look it up in the PPD file...
-       */
-
-       sprintf(s, "%d", j);
-
-        if ((attr = ppdFindAttr(ppd, "cupsIPPFinishings", s)) == NULL)
-         continue;
+  if (!name || num_options <= 0 || !options)
+  {
+    DEBUG_puts("3cupsGetOption: Returning NULL");
+    return (NULL);
+  }
 
-       /*
-        * Apply "*Option Choice" settings from the attribute value...
-       */
+  match = cups_find_option(name, num_options, options, -1, &diff);
 
-        if (ppd_mark_choices(ppd, attr->value))
-         conflict = 1;
-      }
-    }
-    else if (!strcasecmp(optptr->name, "mirror") &&
-             ppdMarkOption(ppd, "MirrorPrint", optptr->value))
-      conflict = 1;
-    else if (ppdMarkOption(ppd, optptr->name, optptr->value))
-      conflict = 1;
+  if (!diff)
+  {
+    DEBUG_printf(("3cupsGetOption: Returning \"%s\"", options[match].value));
+    return (options[match].value);
+  }
 
-  return (conflict);
+  DEBUG_puts("3cupsGetOption: Returning NULL");
+  return (NULL);
 }
 
 
@@ -445,8 +251,8 @@ cupsMarkOptions(
  * 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 cupsParseOptions() on the value to extract the collection
- * attributes.
+ * intact - use @code cupsParseOptions@ on the value to extract the
+ * collection attributes.
  */
 
 int                                    /* O - Number of options found */
@@ -458,24 +264,62 @@ cupsParseOptions(
   char *copyarg,                       /* Copy of input string */
        *ptr,                           /* Pointer into string */
        *name,                          /* Pointer to name */
-       *value;                         /* Pointer to value */
+       *value,                         /* Pointer to value */
+       sep,                            /* Separator character */
+       quote;                          /* Quote character */
 
 
-  if (arg == NULL || options == NULL || num_options < 0)
+  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...
   */
 
-  copyarg     = strdup(arg);
-  ptr         = copyarg;
+  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 (isspace(*ptr & 255))
+  while (_cups_isspace(*ptr))
     ptr ++;
 
  /*
@@ -489,7 +333,7 @@ cupsParseOptions(
     */
 
     name = ptr;
-    while (!isspace(*ptr & 255) && *ptr != '=' && *ptr != '\0')
+    while (!strchr("\f\n\r\t\v =", *ptr) && *ptr)
       ptr ++;
 
    /*
@@ -503,16 +347,21 @@ cupsParseOptions(
     * Skip trailing spaces...
     */
 
-    while (isspace(*ptr & 255))
+    while (_cups_isspace(*ptr))
+      *ptr++ = '\0';
+
+    if ((sep = *ptr) == '=')
       *ptr++ = '\0';
 
-    if (*ptr != '=')
+    DEBUG_printf(("2cupsParseOptions: name=\"%s\"", name));
+
+    if (sep != '=')
     {
      /*
-      * Start of another option...
+      * Boolean option...
       */
 
-      if (strncasecmp(name, "no", 2) == 0)
+      if (!_cups_strncasecmp(name, "no", 2))
         num_options = cupsAddOption(name + 2, "false", num_options,
                                    options);
       else
@@ -525,101 +374,84 @@ cupsParseOptions(
     * Remove = and parse the value...
     */
 
-    *ptr++ = '\0';
+    value = ptr;
 
-    if (*ptr == '\'')
+    while (*ptr && !_cups_isspace(*ptr))
     {
-     /*
-      * Quoted string constant...
-      */
-
-      ptr ++;
-      value = ptr;
-
-      while (*ptr != '\'' && *ptr != '\0')
+      if (*ptr == ',')
+        ptr ++;
+      else if (*ptr == '\'' || *ptr == '\"')
       {
-        if (*ptr == '\\')
-         _cups_strcpy(ptr, ptr + 1);
+       /*
+       * Quoted string constant...
+       */
 
-        ptr ++;
-      }
+       quote = *ptr;
+       _cups_strcpy(ptr, ptr + 1);
 
-      if (*ptr != '\0')
-        *ptr++ = '\0';
-    }
-    else if (*ptr == '\"')
-    {
-     /*
-      * Double-quoted string constant...
-      */
+       while (*ptr != quote && *ptr)
+       {
+         if (*ptr == '\\' && ptr[1])
+           _cups_strcpy(ptr, ptr + 1);
 
-      ptr ++;
-      value = ptr;
+         ptr ++;
+       }
 
-      while (*ptr != '\"' && *ptr != '\0')
-      {
-        if (*ptr == '\\')
+       if (*ptr)
          _cups_strcpy(ptr, ptr + 1);
-
-        ptr ++;
       }
+      else if (*ptr == '{')
+      {
+       /*
+       * Collection value...
+       */
 
-      if (*ptr != '\0')
-        *ptr++ = '\0';
-    }
-    else if (*ptr == '{')
-    {
-     /*
-      * Collection value...
-      */
-
-      int depth;
-
-      value = ptr;
+       int depth;
 
-      for (depth = 1; *ptr; ptr ++)
-        if (*ptr == '{')
-         depth ++;
-       else if (*ptr == '}')
+       for (depth = 0; *ptr; ptr ++)
        {
-         depth --;
-         if (!depth)
+         if (*ptr == '{')
+           depth ++;
+         else if (*ptr == '}')
          {
-           ptr ++;
-
-           if (*ptr != ',')
+           depth --;
+           if (!depth)
+           {
+             ptr ++;
              break;
+           }
          }
-        }
-        else if (*ptr == '\\')
-         _cups_strcpy(ptr, ptr + 1);
-
-      if (*ptr != '\0')
-        *ptr++ = '\0';
-    }
-    else
-    {
-     /*
-      * Normal space-delimited string...
-      */
-
-      value = ptr;
-
-      while (!isspace(*ptr & 255) && *ptr != '\0')
+         else if (*ptr == '\\' && ptr[1])
+           _cups_strcpy(ptr, ptr + 1);
+       }
+      }
+      else
       {
-        if (*ptr == '\\')
-         _cups_strcpy(ptr, ptr + 1);
+       /*
+       * Normal space-delimited string...
+       */
 
-        ptr ++;
+       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 (isspace(*ptr & 255))
-      *ptr++ = '\0';
+    while (_cups_isspace(*ptr))
+      ptr ++;
 
    /*
     * Add the string value...
@@ -635,6 +467,8 @@ cupsParseOptions(
 
   free(copyarg);
 
+  DEBUG_printf(("1cupsParseOptions: Returning %d", num_options));
+
   return (num_options);
 }
 
@@ -642,7 +476,7 @@ cupsParseOptions(
 /*
  * 'cupsRemoveOption()' - Remove an option from an option array.
  *
- * @since CUPS 1.2@
+ * @since CUPS 1.2/macOS 10.5@
  */
 
 int                                    /* O  - New number of options */
@@ -655,19 +489,24 @@ cupsRemoveOption(
   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 (!strcasecmp(name, option->name))
+    if (!_cups_strcasecmp(name, option->name))
       break;
 
   if (i)
@@ -676,107 +515,227 @@ cupsRemoveOption(
     * Remove this option from the array...
     */
 
+    DEBUG_puts("4cupsRemoveOption: Found option, removing it...");
+
     num_options --;
     i --;
 
-    free(option->name);
-    if (option->value)
-      free(option->value);
+    _cupsStrFree(option->name);
+    _cupsStrFree(option->value);
 
     if (i > 0)
-      memmove(option, option + 1, i * sizeof(cups_option_t));
+      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);
 }
 
 
 /*
- * 'ppd_mark_choices()' - Mark one or more option choices from a string.
+ * '_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.
  */
 
-static int                             /* O - 1 if there are conflicts, 0 otherwise */
-ppd_mark_choices(ppd_file_t *ppd,      /* I - PPD file */
-                 const char *options)  /* I - "*Option Choice ..." string */
+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 */
 {
-  char option[PPD_MAX_NAME],           /* Current option */
-       choice[PPD_MAX_NAME],           /* Current choice */
-       *ptr;                           /* Pointer into option or choice */
-  int  conflict = 0;                   /* Do we have a conflict? */
+  int          num_values;             /* Number of values */
+  char         key[256],               /* Key string */
+               value[256],             /* Value string */
+               *ptr;                   /* Pointer into key/value */
+
 
+ /*
+  * Range check input...
+  */
 
-  if (!options)
+  if (values)
+    *values = NULL;
+
+  if (!device_id || !values)
     return (0);
 
  /*
-  * Read all of the "*Option Choice" pairs from the string, marking PPD
-  * options as we go...
+  * Parse the 1284 device ID value into keys and values.  The format is
+  * repeating sequences of:
+  *
+  *   [whitespace]key:value[whitespace];
   */
 
-  while (*options)
+  num_values = 0;
+  while (*device_id)
   {
-   /*
-    * Skip leading whitespace...
-    */
+    while (_cups_isspace(*device_id))
+      device_id ++;
+
+    if (!*device_id)
+      break;
 
-    while (isspace(*options & 255))
-      options ++;
+    for (ptr = key; *device_id && *device_id != ':'; device_id ++)
+      if (ptr < (key + sizeof(key) - 1))
+        *ptr++ = *device_id;
 
-    if (*options != '*')
+    if (!*device_id)
       break;
 
-   /*
-    * Get the option name...
-    */
+    while (ptr > key && _cups_isspace(ptr[-1]))
+      ptr --;
 
-    options ++;
-    ptr = option;
-    while (*options && !isspace(*options & 255) &&
-              ptr < (option + sizeof(option) - 1))
-      *ptr++ = *options++;
+    *ptr = '\0';
+    device_id ++;
 
-    if (ptr == option)
+    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)
+  {
    /*
-    * Get the choice...
+    * Start search on either side of previous...
     */
 
-    while (isspace(*options & 255))
-      options ++;
+    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...
+      */
 
-    if (!*options)
-      break;
+      left  = prev;
+      right = num_options - 1;
+    }
+  }
+  else
+  {
+   /*
+    * Start search in the middle...
+    */
+
+    left  = 0;
+    right = num_options - 1;
+  }
 
-    ptr = choice;
-    while (*options && !isspace(*options & 255) &&
-              ptr < (choice + sizeof(choice) - 1))
-      *ptr++ = *options++;
+  do
+  {
+    current = (left + right) / 2;
+    diff    = cups_compare_options(&key, options + current);
 
-    *ptr = '\0';
+    if (diff == 0)
+      break;
+    else if (diff < 0)
+      right = current;
+    else
+      left = current;
+  }
+  while ((right - left) > 1);
 
+  if (diff != 0)
+  {
    /*
-    * Mark the option...
+    * Check the last 1 or 2 elements...
     */
 
-    if (ppdMarkOption(ppd, option, choice))
-      conflict = 1;
+    if ((diff = cups_compare_options(&key, options + left)) <= 0)
+      current = left;
+    else
+    {
+      diff    = cups_compare_options(&key, options + right);
+      current = right;
+    }
   }
 
  /*
-  * Return whether we had any conflicts...
+  * Return the closest destination and the difference...
   */
 
-  return (conflict);
-}
-
+  *rdiff = diff;
 
-/*
- * End of "$Id: options.c 6649 2007-07-11 21:46:42Z mike $".
- */
+  return (current);
+}