]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - cups/ppd.c
Update ipp documentation to reflect the behavior of configuring WiFi on IPP USB printers.
[thirdparty/cups.git] / cups / ppd.c
index 2da53d5c001d6e7ce552fd7f0a5f7a624cd7192b..ac5dbc62bb8d78aa8de022f8f9bb854d4525eaa8 100644 (file)
@@ -1,84 +1,28 @@
 /*
- * "$Id: ppd.c 7906 2008-09-03 20:19:43Z mike $"
+ * PPD file routines for CUPS.
  *
- *   PPD file routines for the Common UNIX Printing System (CUPS).
+ * Copyright © 2007-2019 by Apple Inc.
+ * Copyright © 1997-2007 by Easy Software Products, all rights reserved.
  *
- *   Copyright 2007-2009 by Apple Inc.
- *   Copyright 1997-2007 by Easy Software Products, all rights reserved.
+ * Licensed under Apache License v2.0.  See the file "LICENSE" for more
+ * information.
  *
- *   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/".
- *
- *   PostScript is a trademark of Adobe Systems, Inc.
- *
- *   This code and any derivative of it may be used and distributed
- *   freely under the terms of the GNU General Public License when
- *   used with GNU Ghostscript or its derivatives.  Use of the code
- *   (or any derivative of it) with software other than GNU
- *   GhostScript (or its derivatives) is governed by the CUPS license
- *   agreement.
- *
- *   This file is subject to the Apple OS-Developed Software exception.
- *
- * Contents:
- *
- *   ppdClose()             - Free all memory used by the PPD file.
- *   ppdErrorString()       - Returns the text assocated with a status.
- *   _ppdGetEncoding()      - Get the CUPS encoding value for the given
- *                            LanguageEncoding.
- *   ppdLastError()         - Return the status from the last ppdOpen*().
- *   ppdOpen()              - Read a PPD file into memory.
- *   ppdOpen2()             - Read a PPD file into memory.
- *   ppdOpenFd()            - Read a PPD file into memory.
- *   ppdOpenFile()          - Read a PPD file into memory.
- *   ppdSetConformance()    - Set the conformance level for PPD files.
- *   ppd_add_attr()         - Add an attribute to the PPD data.
- *   ppd_add_choice()       - Add a choice to an option.
- *   ppd_add_size()         - Add a page size.
- *   ppd_compare_attrs()    - Compare two attributes.
- *   ppd_compare_choices()  - Compare two choices...
- *   ppd_compare_coptions() - Compare two custom options.
- *   ppd_compare_cparams()  - Compare two custom parameters.
- *   ppd_compare_options()  - Compare two options.
- *   ppd_decode()           - Decode a string value...
- *   ppd_free_group()       - Free a single UI group.
- *   ppd_free_option()      - Free a single option.
- *   ppd_get_coption()      - Get a custom option record.
- *   ppd_get_cparam()       - Get a custom parameter record.
- *   ppd_get_group()        - Find or create the named group as needed.
- *   ppd_get_option()       - Find or create the named option as needed.
- *   ppd_hash_option()      - Generate a hash of the option name...
- *   ppd_read()             - Read a line from a PPD file, skipping comment
- *                            lines as necessary.
+ * PostScript is a trademark of Adobe Systems, Inc.
  */
 
 /*
  * Include necessary headers.
  */
 
+#include "cups-private.h"
 #include "ppd-private.h"
-#include "globals.h"
-#include "debug.h"
-#include <stdlib.h>
+#include "debug-internal.h"
 
 
 /*
  * Definitions...
  */
 
-#if defined(WIN32) || defined(__EMX__)
-#  define READ_BINARY  "rb"            /* Open a binary file for reading */
-#  define WRITE_BINARY "wb"            /* Open a binary file for writing */
-#else
-#  define READ_BINARY  "r"             /* Open a binary file for reading */
-#  define WRITE_BINARY "w"             /* Open a binary file for writing */
-#endif /* WIN32 || __EMX__ */
-
-#define ppd_free(p)    if (p) free(p)  /* Safe free macro */
-
 #define PPD_KEYWORD    1               /* Line contained a keyword */
 #define PPD_OPTION     2               /* Line contained an option name */
 #define PPD_TEXT       4               /* Line contained human-readable text */
@@ -98,6 +42,18 @@ typedef struct _ppd_line_s
 } _ppd_line_t;
 
 
+/*
+ * Local globals...
+ */
+
+static _cups_threadkey_t ppd_globals_key = _CUPS_THREADKEY_INITIALIZER;
+                                       /* Thread local storage key */
+#ifdef HAVE_PTHREAD_H
+static pthread_once_t  ppd_globals_key_once = PTHREAD_ONCE_INIT;
+                                       /* One-time initialization object */
+#endif /* HAVE_PTHREAD_H */
+
+
 /*
  * Local functions...
  */
@@ -111,9 +67,9 @@ static int           ppd_compare_attrs(ppd_attr_t *a, ppd_attr_t *b);
 static int             ppd_compare_choices(ppd_choice_t *a, ppd_choice_t *b);
 static int             ppd_compare_coptions(ppd_coption_t *a,
                                             ppd_coption_t *b);
-static int             ppd_compare_cparams(ppd_cparam_t *a, ppd_cparam_t *b);
 static int             ppd_compare_options(ppd_option_t *a, ppd_option_t *b);
 static int             ppd_decode(char *string);
+static void            ppd_free_filters(ppd_file_t *ppd);
 static void            ppd_free_group(ppd_group_t *group);
 static void            ppd_free_option(ppd_option_t *option);
 static ppd_coption_t   *ppd_get_coption(ppd_file_t *ppd, const char *name);
@@ -121,14 +77,23 @@ static ppd_cparam_t        *ppd_get_cparam(ppd_coption_t *opt,
                                        const char *param,
                                        const char *text);
 static ppd_group_t     *ppd_get_group(ppd_file_t *ppd, const char *name,
-                                      const char *text, _cups_globals_t *cg,
+                                      const char *text, _ppd_globals_t *pg,
                                       cups_encoding_t encoding);
 static ppd_option_t    *ppd_get_option(ppd_group_t *group, const char *name);
+static _ppd_globals_t  *ppd_globals_alloc(void);
+#if defined(HAVE_PTHREAD_H) || defined(_WIN32)
+static void            ppd_globals_free(_ppd_globals_t *g);
+#endif /* HAVE_PTHREAD_H || _WIN32 */
+#ifdef HAVE_PTHREAD_H
+static void            ppd_globals_init(void);
+#endif /* HAVE_PTHREAD_H */
 static int             ppd_hash_option(ppd_option_t *option);
 static int             ppd_read(cups_file_t *fp, _ppd_line_t *line,
                                 char *keyword, char *option, char *text,
                                 char **string, int ignoreblank,
-                                _cups_globals_t *cg);
+                                _ppd_globals_t *pg);
+static int             ppd_update_filters(ppd_file_t *ppd,
+                                          _ppd_globals_t *pg);
 
 
 /*
@@ -139,10 +104,8 @@ void
 ppdClose(ppd_file_t *ppd)              /* I - PPD file record */
 {
   int                  i;              /* Looping var */
-  ppd_emul_t           *emul;          /* Current emulation */
   ppd_group_t          *group;         /* Current group */
   char                 **font;         /* Current font */
-  char                 **filter;       /* Current filter */
   ppd_attr_t           **attr;         /* Current attribute */
   ppd_coption_t                *coption;       /* Current custom option */
   ppd_cparam_t         *cparam;        /* Current custom parameter */
@@ -159,28 +122,13 @@ ppdClose(ppd_file_t *ppd)         /* I - PPD file record */
   * Free all strings at the top level...
   */
 
-  _cupsStrFree(ppd->lang_encoding);
-  _cupsStrFree(ppd->nickname);
-  if (ppd->patches)
-    free(ppd->patches);
-  _cupsStrFree(ppd->jcl_begin);
-  _cupsStrFree(ppd->jcl_end);
-  _cupsStrFree(ppd->jcl_ps);
-
- /*
-  * Free any emulations...
-  */
-
-  if (ppd->num_emulations > 0)
-  {
-    for (i = ppd->num_emulations, emul = ppd->emulations; i > 0; i --, emul ++)
-    {
-      _cupsStrFree(emul->start);
-      _cupsStrFree(emul->stop);
-    }
-
-    ppd_free(ppd->emulations);
-  }
+  free(ppd->lang_encoding);
+  free(ppd->nickname);
+  free(ppd->patches);
+  free(ppd->emulations);
+  free(ppd->jcl_begin);
+  free(ppd->jcl_end);
+  free(ppd->jcl_ps);
 
  /*
   * Free any UI groups, subgroups, and options...
@@ -191,7 +139,7 @@ ppdClose(ppd_file_t *ppd)           /* I - PPD file record */
     for (i = ppd->num_groups, group = ppd->groups; i > 0; i --, group ++)
       ppd_free_group(group);
 
-    ppd_free(ppd->groups);
+    free(ppd->groups);
   }
 
   cupsArrayDelete(ppd->options);
@@ -202,26 +150,20 @@ ppdClose(ppd_file_t *ppd)         /* I - PPD file record */
   */
 
   if (ppd->num_sizes > 0)
-    ppd_free(ppd->sizes);
+    free(ppd->sizes);
 
  /*
   * Free any constraints...
   */
 
   if (ppd->num_consts > 0)
-    ppd_free(ppd->consts);
+    free(ppd->consts);
 
  /*
   * Free any filters...
   */
 
-  if (ppd->num_filters > 0)
-  {
-    for (i = ppd->num_filters, filter = ppd->filters; i > 0; i --, filter ++)
-      _cupsStrFree(*filter);
-
-    ppd_free(ppd->filters);
-  }
+  ppd_free_filters(ppd);
 
  /*
   * Free any fonts...
@@ -230,9 +172,9 @@ ppdClose(ppd_file_t *ppd)           /* I - PPD file record */
   if (ppd->num_fonts > 0)
   {
     for (i = ppd->num_fonts, font = ppd->fonts; i > 0; i --, font ++)
-      _cupsStrFree(*font);
+      free(*font);
 
-    ppd_free(ppd->fonts);
+    free(ppd->fonts);
   }
 
  /*
@@ -240,7 +182,7 @@ ppdClose(ppd_file_t *ppd)           /* I - PPD file record */
   */
 
   if (ppd->num_profiles > 0)
-    ppd_free(ppd->profiles);
+    free(ppd->profiles);
 
  /*
   * Free any attributes...
@@ -250,11 +192,11 @@ ppdClose(ppd_file_t *ppd)         /* I - PPD file record */
   {
     for (i = ppd->num_attrs, attr = ppd->attrs; i > 0; i --, attr ++)
     {
-      _cupsStrFree((*attr)->value);
-      ppd_free(*attr);
+      free((*attr)->value);
+      free(*attr);
     }
 
-    ppd_free(ppd->attrs);
+    free(ppd->attrs);
   }
 
   cupsArrayDelete(ppd->sorted_attrs);
@@ -276,7 +218,7 @@ ppdClose(ppd_file_t *ppd)           /* I - PPD file record */
         case PPD_CUSTOM_PASSCODE :
         case PPD_CUSTOM_PASSWORD :
         case PPD_CUSTOM_STRING :
-            _cupsStrFree(cparam->current.custom_string);
+            free(cparam->current.custom_string);
            break;
 
        default :
@@ -313,18 +255,25 @@ ppdClose(ppd_file_t *ppd)         /* I - PPD file record */
     cupsArrayDelete(ppd->cups_uiconstraints);
   }
 
+ /*
+  * Free any PPD cache/mapping data...
+  */
+
+  if (ppd->cache)
+    _ppdCacheDestroy(ppd->cache);
+
  /*
   * Free the whole record...
   */
 
-  ppd_free(ppd);
+  free(ppd);
 }
 
 
 /*
- * 'ppdErrorString()' - Returns the text assocated with a status.
+ * 'ppdErrorString()' - Returns the text associated with a status.
  *
- * @since CUPS 1.1.19/Mac OS X 10.3@
+ * @since CUPS 1.1.19/macOS 10.3@
  */
 
 const char *                           /* O - Status string */
@@ -352,11 +301,16 @@ ppdErrorString(ppd_status_t status)       /* I - PPD status */
                  _("Illegal option keyword string"),
                  _("Illegal translation string"),
                  _("Illegal whitespace character"),
-                 _("Bad custom parameter")
+                 _("Bad custom parameter"),
+                 _("Missing option keyword"),
+                 _("Bad value string"),
+                 _("Missing CloseGroup"),
+                 _("Bad CloseUI/JCLCloseUI"),
+                 _("Missing CloseUI/JCLCloseUI")
                };
 
 
-  if (status < PPD_OK || status > PPD_ILLEGAL_WHITESPACE)
+  if (status < PPD_OK || status >= PPD_MAX_STATUS)
     return (_cupsLangString(cupsLangDefault(), _("Unknown")));
   else
     return (_cupsLangString(cupsLangDefault(), messages[status]));
@@ -371,17 +325,17 @@ ppdErrorString(ppd_status_t status)       /* I - PPD status */
 cups_encoding_t                                /* O - CUPS encoding value */
 _ppdGetEncoding(const char *name)      /* I - LanguageEncoding string */
 {
-  if (!strcasecmp(name, "ISOLatin1"))
+  if (!_cups_strcasecmp(name, "ISOLatin1"))
     return (CUPS_ISO8859_1);
-  else if (!strcasecmp(name, "ISOLatin2"))
+  else if (!_cups_strcasecmp(name, "ISOLatin2"))
     return (CUPS_ISO8859_2);
-  else if (!strcasecmp(name, "ISOLatin5"))
+  else if (!_cups_strcasecmp(name, "ISOLatin5"))
     return (CUPS_ISO8859_5);
-  else if (!strcasecmp(name, "JIS83-RKSJ"))
+  else if (!_cups_strcasecmp(name, "JIS83-RKSJ"))
     return (CUPS_JIS_X0213);
-  else if (!strcasecmp(name, "MacStandard"))
+  else if (!_cups_strcasecmp(name, "MacStandard"))
     return (CUPS_MAC_ROMAN);
-  else if (!strcasecmp(name, "WindowsANSI"))
+  else if (!_cups_strcasecmp(name, "WindowsANSI"))
     return (CUPS_WINDOWS_1252);
   else
     return (CUPS_UTF8);
@@ -389,70 +343,77 @@ _ppdGetEncoding(const char *name) /* I - LanguageEncoding string */
 
 
 /*
- * 'ppdLastError()' - Return the status from the last ppdOpen*().
- *
- * @since CUPS 1.1.19/Mac OS X 10.3@
+ * '_ppdGlobals()' - Return a pointer to thread local storage
  */
 
-ppd_status_t                           /* O - Status code */
-ppdLastError(int *line)                        /* O - Line number */
+_ppd_globals_t *                       /* O - Pointer to global data */
+_ppdGlobals(void)
 {
-  _cups_globals_t      *cg = _cupsGlobals();
-                                       /* Global data */
-
+  _ppd_globals_t *pg;                  /* Pointer to global data */
 
-  if (line)
-    *line = cg->ppd_line;
 
-  return (cg->ppd_status);
-}
+#ifdef HAVE_PTHREAD_H
+ /*
+  * Initialize the global data exactly once...
+  */
 
+  pthread_once(&ppd_globals_key_once, ppd_globals_init);
+#endif /* HAVE_PTHREAD_H */
 
-/*
* 'ppdOpen()' - Read a PPD file into memory.
- */
+ /*
 * See if we have allocated the data yet...
 */
 
-ppd_file_t *                           /* O - PPD file record */
-ppdOpen(FILE *fp)                      /* I - File to read from */
-{
-  ppd_file_t   *ppd;                   /* PPD file record */
-  cups_file_t  *cf;                    /* CUPS file */
+  if ((pg = (_ppd_globals_t *)_cupsThreadGetData(ppd_globals_key)) == NULL)
+  {
+   /*
+    * No, allocate memory as set the pointer for the key...
+    */
 
+    if ((pg = ppd_globals_alloc()) != NULL)
+      _cupsThreadSetData(ppd_globals_key, pg);
+  }
 
  /*
-  * Reopen the stdio file as a CUPS file...
+  * Return the pointer to the data...
   */
 
-  if ((cf = cupsFileOpenFd(fileno(fp), "r")) == NULL)
-    return (NULL);
+  return (pg);
+}
 
- /*
-  * Load the PPD file using the newer API...
-  */
 
-  ppd = ppdOpen2(cf);
+/*
+ * 'ppdLastError()' - Return the status from the last ppdOpen*().
+ *
+ * @since CUPS 1.1.19/macOS 10.3@
+ */
+
+ppd_status_t                           /* O - Status code */
+ppdLastError(int *line)                        /* O - Line number */
+{
+  _ppd_globals_t       *pg = _ppdGlobals();
+                                       /* Global data */
 
- /*
-  * Close the CUPS file and return the PPD...
-  */
 
-  cupsFileClose(cf);
+  if (line)
+    *line = pg->ppd_line;
 
-  return (ppd);
+  return (pg->ppd_status);
 }
 
 
 /*
- * 'ppdOpen2()' - Read a PPD file into memory.
+ * '_ppdOpen()' - Read a PPD file into memory.
  *
- * @since CUPS 1.2/Mac OS X 10.5@
+ * @since CUPS 1.2/macOS 10.5@
  */
 
 ppd_file_t *                           /* O - PPD file record or @code NULL@ if the PPD file could not be opened. */
-ppdOpen2(cups_file_t *fp)              /* I - File to read from */
+_ppdOpen(
+    cups_file_t                *fp,            /* I - File to read from */
+    _ppd_localization_t        localization)   /* I - Localization to load */
 {
   int                  i, j, k;        /* Looping vars */
-  int                  count;          /* Temporary count */
   _ppd_line_t          line;           /* Line buffer */
   ppd_file_t           *ppd;           /* PPD file record */
   ppd_group_t          *group,         /* Current group */
@@ -470,22 +431,25 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
                                        /* Human-readable text from file */
                        *string,        /* Code/text from file */
                        *sptr,          /* Pointer into string */
-                       *nameptr,       /* Pointer into name */
                        *temp,          /* Temporary string pointer */
                        **tempfonts;    /* Temporary fonts pointer */
   float                        order;          /* Order dependency number */
   ppd_section_t                section;        /* Order dependency section */
   ppd_profile_t                *profile;       /* Pointer to color profile */
   char                 **filter;       /* Pointer to filter */
-  cups_lang_t          *language;      /* Default language */
   struct lconv         *loc;           /* Locale data */
   int                  ui_keyword;     /* Is this line a UI keyword? */
+  cups_lang_t          *lang;          /* Language data */
   cups_encoding_t      encoding;       /* Encoding of PPD file */
-  _cups_globals_t      *cg = _cupsGlobals();
+  _ppd_globals_t       *pg = _ppdGlobals();
                                        /* Global data */
   char                 custom_name[PPD_MAX_NAME];
                                        /* CustomFoo attribute name */
   ppd_attr_t           *custom_attr;   /* CustomFoo attribute */
+  char                 ll[7],          /* Base language + '.' */
+                       ll_CC[7];       /* Language w/country + '.' */
+  size_t               ll_len = 0,     /* Base language length */
+                       ll_CC_len = 0;  /* Language w/country length */
   static const char * const ui_keywords[] =
                        {
 #ifdef CUPS_USE_FULL_UI_KEYWORDS_LIST
@@ -547,16 +511,21 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
                          "PageSize"
 #endif /* CUPS_USE_FULL_UI_KEYWORDS_LIST */
                        };
+  static const char * const color_keywords[] = /* Keywords associated with color profiles */
+                       {
+                         ".cupsICCProfile",
+                         ".ColorModel",
+                       };
 
 
-  DEBUG_printf(("ppdOpen2(fp=%p)", fp));
+  DEBUG_printf(("_ppdOpen(fp=%p)", fp));
 
  /*
   * Default to "OK" status...
   */
 
-  cg->ppd_status = PPD_OK;
-  cg->ppd_line   = 0;
+  pg->ppd_status = PPD_OK;
+  pg->ppd_line   = 0;
 
  /*
   * Range check input...
@@ -564,10 +533,55 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
 
   if (fp == NULL)
   {
-    cg->ppd_status = PPD_NULL_FILE;
+    pg->ppd_status = PPD_NULL_FILE;
     return (NULL);
   }
 
+ /*
+  * If only loading a single localization set up the strings to match...
+  */
+
+  if (localization == _PPD_LOCALIZATION_DEFAULT)
+  {
+    if ((lang = cupsLangDefault()) == NULL)
+      return (NULL);
+
+    snprintf(ll_CC, sizeof(ll_CC), "%s.", lang->language);
+
+   /*
+    * <rdar://problem/22130168>
+    * <rdar://problem/27245567>
+    *
+    * Need to use a different base language for some locales...
+    */
+
+    if (!strcmp(lang->language, "zh_HK"))
+    {                                  /* Traditional Chinese + variants */
+      strlcpy(ll_CC, "zh_TW.", sizeof(ll_CC));
+      strlcpy(ll, "zh_", sizeof(ll));
+    }
+    else if (!strncmp(lang->language, "zh", 2))
+      strlcpy(ll, "zh_", sizeof(ll));  /* Any Chinese variant */
+    else if (!strncmp(lang->language, "jp", 2))
+    {                                  /* Any Japanese variant */
+      strlcpy(ll_CC, "ja", sizeof(ll_CC));
+      strlcpy(ll, "jp", sizeof(ll));
+    }
+    else if (!strncmp(lang->language, "nb", 2) || !strncmp(lang->language, "no", 2))
+    {                                  /* Any Norwegian variant */
+      strlcpy(ll_CC, "nb", sizeof(ll_CC));
+      strlcpy(ll, "no", sizeof(ll));
+    }
+    else
+      snprintf(ll, sizeof(ll), "%2.2s.", lang->language);
+
+    ll_CC_len = strlen(ll_CC);
+    ll_len    = strlen(ll);
+
+    DEBUG_printf(("2_ppdOpen: Loading localizations matching \"%s\" and \"%s\"",
+                  ll_CC, ll));
+  }
+
  /*
   * Grab the first line and make sure it reads '*PPD-Adobe: "major.minor"'...
   */
@@ -575,9 +589,9 @@ ppdOpen2(cups_file_t *fp)           /* I - File to read from */
   line.buffer  = NULL;
   line.bufsize = 0;
 
-  mask = ppd_read(fp, &line, keyword, name, text, &string, 0, cg);
+  mask = ppd_read(fp, &line, keyword, name, text, &string, 0, pg);
 
-  DEBUG_printf(("2ppdOpen2: mask=%x, keyword=\"%s\"...", mask, keyword));
+  DEBUG_printf(("2_ppdOpen: mask=%x, keyword=\"%s\"...", mask, keyword));
 
   if (mask == 0 ||
       strcmp(keyword, "PPD-Adobe") ||
@@ -587,18 +601,16 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
     * Either this is not a PPD file, or it is not a 4.x PPD file.
     */
 
-    if (cg->ppd_status == PPD_OK)
-      cg->ppd_status = PPD_MISSING_PPDADOBE4;
+    if (pg->ppd_status == PPD_OK)
+      pg->ppd_status = PPD_MISSING_PPDADOBE4;
 
-    _cupsStrFree(string);
-    ppd_free(line.buffer);
+    free(string);
+    free(line.buffer);
 
     return (NULL);
   }
 
-  DEBUG_printf(("2ppdOpen2: keyword=%s, string=%p", keyword, string));
-
-  _cupsStrFree(string);
+  DEBUG_printf(("2_ppdOpen: keyword=%s, string=%p", keyword, string));
 
  /*
   * Allocate memory for the PPD file record...
@@ -606,27 +618,22 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
 
   if ((ppd = calloc(1, sizeof(ppd_file_t))) == NULL)
   {
-    cg->ppd_status = PPD_ALLOC_ERROR;
+    pg->ppd_status = PPD_ALLOC_ERROR;
 
-    _cupsStrFree(string);
-    ppd_free(line.buffer);
+    free(string);
+    free(line.buffer);
 
     return (NULL);
   }
 
+  free(string);
+  string = NULL;
+
   ppd->language_level = 2;
   ppd->color_device   = 0;
   ppd->colorspace     = PPD_CS_N;
   ppd->landscape      = -90;
-  ppd->coptions       = cupsArrayNew((cups_array_func_t)ppd_compare_coptions,
-                                     NULL);
-
- /*
-  * Get the default language for the user...
-  */
-
-  language = cupsLangDefault();
-  loc      = localeconv();
+  ppd->coptions       = cupsArrayNew((cups_array_func_t)ppd_compare_coptions, NULL);
 
  /*
   * Read lines from the PPD file and add them to the file record...
@@ -638,24 +645,27 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
   choice     = NULL;
   ui_keyword = 0;
   encoding   = CUPS_ISO8859_1;
+  loc        = localeconv();
 
-  while ((mask = ppd_read(fp, &line, keyword, name, text, &string, 1, cg)) != 0)
+  while ((mask = ppd_read(fp, &line, keyword, name, text, &string, 1, pg)) != 0)
   {
-    DEBUG_printf(("2ppdOpen2: mask=%x, keyword=\"%s\", name=\"%s\", "
+    DEBUG_printf(("2_ppdOpen: mask=%x, keyword=\"%s\", name=\"%s\", "
                   "text=\"%s\", string=%d chars...", mask, keyword, name, text,
                  string ? (int)strlen(string) : 0));
 
     if (strncmp(keyword, "Default", 7) && !string &&
-        cg->ppd_conform != PPD_CONFORM_RELAXED)
+        pg->ppd_conform != PPD_CONFORM_RELAXED)
     {
      /*
       * Need a string value!
       */
 
-      cg->ppd_status = PPD_MISSING_VALUE;
+      pg->ppd_status = PPD_MISSING_VALUE;
 
       goto error;
     }
+    else if (!string)
+      continue;
 
    /*
     * Certain main keywords (as defined by the PPD spec) may be used
@@ -676,6 +686,54 @@ ppdOpen2(cups_file_t *fp)          /* I - File to read from */
       ui_keyword = 0;
     }
 
+   /*
+    * If we are filtering out keyword localizations, see if this line needs to
+    * be used...
+    */
+
+    if (localization != _PPD_LOCALIZATION_ALL &&
+        (temp = strchr(keyword, '.')) != NULL &&
+        ((temp - keyword) == 2 || (temp - keyword) == 5) &&
+        _cups_isalpha(keyword[0]) &&
+        _cups_isalpha(keyword[1]) &&
+        (keyword[2] == '.' ||
+         (keyword[2] == '_' && _cups_isalpha(keyword[3]) &&
+          _cups_isalpha(keyword[4]) && keyword[5] == '.')))
+    {
+      if (localization == _PPD_LOCALIZATION_NONE ||
+         (localization == _PPD_LOCALIZATION_DEFAULT &&
+          strncmp(ll_CC, keyword, ll_CC_len) &&
+          strncmp(ll, keyword, ll_len)))
+      {
+       DEBUG_printf(("2_ppdOpen: Ignoring localization: \"%s\"\n", keyword));
+       free(string);
+       string = NULL;
+       continue;
+      }
+      else if (localization == _PPD_LOCALIZATION_ICC_PROFILES)
+      {
+       /*
+        * Only load localizations for the color profile related keywords...
+        */
+
+       for (i = 0;
+            i < (int)(sizeof(color_keywords) / sizeof(color_keywords[0]));
+            i ++)
+       {
+         if (!_cups_strcasecmp(temp, color_keywords[i]))
+           break;
+       }
+
+       if (i >= (int)(sizeof(color_keywords) / sizeof(color_keywords[0])))
+       {
+         DEBUG_printf(("2_ppdOpen: Ignoring localization: \"%s\"\n", keyword));
+         free(string);
+         string = NULL;
+         continue;
+       }
+      }
+    }
+
     if (option == NULL &&
         (mask & (PPD_KEYWORD | PPD_OPTION | PPD_STRING)) ==
            (PPD_KEYWORD | PPD_OPTION | PPD_STRING))
@@ -692,16 +750,16 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
 
         ui_keyword = 1;
 
-        DEBUG_printf(("2ppdOpen2: FOUND ADOBE UI KEYWORD %s WITHOUT OPENUI!",
+        DEBUG_printf(("2_ppdOpen: FOUND ADOBE UI KEYWORD %s WITHOUT OPENUI!",
                      keyword));
 
         if (!group)
        {
-          if ((group = ppd_get_group(ppd, "General", _("General"), cg,
+          if ((group = ppd_get_group(ppd, "General", _("General"), pg,
                                     encoding)) == NULL)
            goto error;
 
-          DEBUG_printf(("2ppdOpen2: Adding to group %s...", group->text));
+          DEBUG_printf(("2_ppdOpen: Adding to group %s...", group->text));
           option = ppd_get_option(group, keyword);
          group  = NULL;
        }
@@ -710,7 +768,7 @@ ppdOpen2(cups_file_t *fp)           /* I - File to read from */
 
        if (option == NULL)
        {
-          cg->ppd_status = PPD_ALLOC_ERROR;
+          pg->ppd_status = PPD_ALLOC_ERROR;
 
           goto error;
        }
@@ -736,7 +794,7 @@ ppdOpen2(cups_file_t *fp)           /* I - File to read from */
              !strcmp(ppd->attrs[j]->name + 7, keyword) &&
              ppd->attrs[j]->value)
          {
-           DEBUG_printf(("2ppdOpen2: Setting Default%s to %s via attribute...",
+           DEBUG_printf(("2_ppdOpen: Setting Default%s to %s via attribute...",
                          option->keyword, ppd->attrs[j]->value));
            strlcpy(option->defchoice, ppd->attrs[j]->value,
                    sizeof(option->defchoice));
@@ -766,7 +824,7 @@ ppdOpen2(cups_file_t *fp)           /* I - File to read from */
       * Say all PPD files are UTF-8, since we convert to UTF-8...
       */
 
-      ppd->lang_encoding = _cupsStrAlloc("UTF-8");
+      ppd->lang_encoding = strdup("UTF-8");
       encoding           = _ppdGetEncoding(string);
     }
     else if (!strcmp(keyword, "LanguageVersion"))
@@ -787,10 +845,10 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
 
 
         cupsCharsetToUTF8(utf8, string, sizeof(utf8), encoding);
-       ppd->nickname = _cupsStrAlloc((char *)utf8);
+       ppd->nickname = strdup((char *)utf8);
       }
       else
-        ppd->nickname = _cupsStrAlloc(string);
+        ppd->nickname = strdup(string);
     }
     else if (!strcmp(keyword, "Product"))
       ppd->product = string;
@@ -800,29 +858,29 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       ppd->ttrasterizer = string;
     else if (!strcmp(keyword, "JCLBegin"))
     {
-      ppd->jcl_begin = _cupsStrAlloc(string);
+      ppd->jcl_begin = strdup(string);
       ppd_decode(ppd->jcl_begin);      /* Decode quoted string */
     }
     else if (!strcmp(keyword, "JCLEnd"))
     {
-      ppd->jcl_end = _cupsStrAlloc(string);
+      ppd->jcl_end = strdup(string);
       ppd_decode(ppd->jcl_end);                /* Decode quoted string */
     }
     else if (!strcmp(keyword, "JCLToPSInterpreter"))
     {
-      ppd->jcl_ps = _cupsStrAlloc(string);
+      ppd->jcl_ps = strdup(string);
       ppd_decode(ppd->jcl_ps);         /* Decode quoted string */
     }
     else if (!strcmp(keyword, "AccurateScreensSupport"))
-      ppd->accurate_screens = !strcmp(string, "True");
+      ppd->accurate_screens = !strcasecmp(string, "True");
     else if (!strcmp(keyword, "ColorDevice"))
-      ppd->color_device = !strcmp(string, "True");
+      ppd->color_device = !strcasecmp(string, "True");
     else if (!strcmp(keyword, "ContoneOnly"))
-      ppd->contone_only = !strcmp(string, "True");
+      ppd->contone_only = !strcasecmp(string, "True");
     else if (!strcmp(keyword, "cupsFlipDuplex"))
-      ppd->flip_duplex = !strcmp(string, "True");
+      ppd->flip_duplex = !strcasecmp(string, "True");
     else if (!strcmp(keyword, "cupsManualCopies"))
-      ppd->manual_copies = !strcmp(string, "True");
+      ppd->manual_copies = !strcasecmp(string, "True");
     else if (!strcmp(keyword, "cupsModelNumber"))
       ppd->model_number = atoi(string);
     else if (!strcmp(keyword, "cupsColorProfile"))
@@ -830,12 +888,11 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       if (ppd->num_profiles == 0)
         profile = malloc(sizeof(ppd_profile_t));
       else
-        profile = realloc(ppd->profiles, sizeof(ppd_profile_t) *
-                                        (ppd->num_profiles + 1));
+        profile = realloc(ppd->profiles, sizeof(ppd_profile_t) * (size_t)(ppd->num_profiles + 1));
 
       if (!profile)
       {
-        cg->ppd_status = PPD_ALLOC_ERROR;
+        pg->ppd_status = PPD_ALLOC_ERROR;
 
        goto error;
       }
@@ -865,11 +922,11 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       if (ppd->num_filters == 0)
         filter = malloc(sizeof(char *));
       else
-        filter = realloc(ppd->filters, sizeof(char *) * (ppd->num_filters + 1));
+        filter = realloc(ppd->filters, sizeof(char *) * (size_t)(ppd->num_filters + 1));
 
       if (filter == NULL)
       {
-        cg->ppd_status = PPD_ALLOC_ERROR;
+        pg->ppd_status = PPD_ALLOC_ERROR;
 
        goto error;
       }
@@ -879,11 +936,10 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       ppd->num_filters ++;
 
      /*
-      * Copy filter string and prevent it from being freed below...
+      * Make a copy of the filter string...
       */
 
-      *filter = string;
-      string  = NULL;
+      *filter = strdup(string);
     }
     else if (!strcmp(keyword, "Throughput"))
       ppd->throughput = atoi(string);
@@ -896,18 +952,17 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       if (ppd->num_fonts == 0)
         tempfonts = (char **)malloc(sizeof(char *));
       else
-        tempfonts = (char **)realloc(ppd->fonts,
-                                    sizeof(char *) * (ppd->num_fonts + 1));
+        tempfonts = (char **)realloc(ppd->fonts, sizeof(char *) * (size_t)(ppd->num_fonts + 1));
 
       if (tempfonts == NULL)
       {
-        cg->ppd_status = PPD_ALLOC_ERROR;
+        pg->ppd_status = PPD_ALLOC_ERROR;
 
        goto error;
       }
-      
+
       ppd->fonts                 = tempfonts;
-      ppd->fonts[ppd->num_fonts] = _cupsStrAlloc(name);
+      ppd->fonts[ppd->num_fonts] = strdup(name);
       ppd->num_fonts ++;
     }
     else if (!strncmp(keyword, "ParamCustom", 11))
@@ -926,18 +981,25 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
 
       if ((coption = ppd_get_coption(ppd, keyword + 11)) == NULL)
       {
-        cg->ppd_status = PPD_ALLOC_ERROR;
+        pg->ppd_status = PPD_ALLOC_ERROR;
 
        goto error;
       }
 
       if ((cparam = ppd_get_cparam(coption, name, text)) == NULL)
       {
-        cg->ppd_status = PPD_ALLOC_ERROR;
+        pg->ppd_status = PPD_ALLOC_ERROR;
 
        goto error;
       }
 
+      if (cparam->type != PPD_CUSTOM_UNKNOWN)
+      {
+        pg->ppd_status = PPD_BAD_CUSTOM_PARAM;
+
+        goto error;
+      }
+
      /*
       * Get the parameter data...
       */
@@ -946,7 +1008,7 @@ ppdOpen2(cups_file_t *fp)          /* I - File to read from */
           sscanf(string, "%d%32s%64s%64s", &corder, ctype, cminimum,
                  cmaximum) != 4)
       {
-        cg->ppd_status = PPD_BAD_CUSTOM_PARAM;
+        pg->ppd_status = PPD_BAD_CUSTOM_PARAM;
 
        goto error;
       }
@@ -1003,7 +1065,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       }
       else
       {
-        cg->ppd_status = PPD_BAD_CUSTOM_PARAM;
+        pg->ppd_status = PPD_BAD_CUSTOM_PARAM;
 
        goto error;
       }
@@ -1035,7 +1097,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
     {
       ppd_option_t     *custom_option; /* Custom option */
 
-      DEBUG_puts("2ppdOpen2: Processing Custom option...");
+      DEBUG_puts("2_ppdOpen: Processing Custom option...");
 
      /*
       * Get the option and custom option...
@@ -1043,12 +1105,12 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
 
       if (!ppd_get_coption(ppd, keyword + 6))
       {
-        cg->ppd_status = PPD_ALLOC_ERROR;
+        pg->ppd_status = PPD_ALLOC_ERROR;
 
        goto error;
       }
 
-      if (option && !strcasecmp(option->keyword, keyword + 6))
+      if (option && !_cups_strcasecmp(option->keyword, keyword + 6))
         custom_option = option;
       else
         custom_option = ppdFindOption(ppd, keyword + 6);
@@ -1062,9 +1124,9 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
         if ((choice = ppdFindChoice(custom_option, "Custom")) == NULL)
          if ((choice = ppd_add_choice(custom_option, "Custom")) == NULL)
          {
-           DEBUG_puts("1ppdOpen2: Unable to add Custom choice!");
+           DEBUG_puts("1_ppdOpen: Unable to add Custom choice!");
 
-           cg->ppd_status = PPD_ALLOC_ERROR;
+           pg->ppd_status = PPD_ALLOC_ERROR;
 
            goto error;
          }
@@ -1072,7 +1134,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
        strlcpy(choice->text, text[0] ? text : _("Custom"),
                sizeof(choice->text));
 
-       choice->code = _cupsStrAlloc(string);
+       choice->code = strdup(string);
 
        if (custom_option->section == PPD_ORDER_JCL)
          ppd_decode(choice->code);
@@ -1092,7 +1154,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
 
        ppd_add_size(ppd, "Custom");
 
-       if (option && !strcasecmp(option->keyword, "PageRegion"))
+       if (option && !_cups_strcasecmp(option->keyword, "PageRegion"))
          custom_option = option;
        else
          custom_option = ppdFindOption(ppd, "PageRegion");
@@ -1102,9 +1164,9 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
          if ((choice = ppdFindChoice(custom_option, "Custom")) == NULL)
            if ((choice = ppd_add_choice(custom_option, "Custom")) == NULL)
            {
-             DEBUG_puts("1ppdOpen2: Unable to add Custom choice!");
+             DEBUG_puts("1_ppdOpen: Unable to add Custom choice!");
 
-             cg->ppd_status = PPD_ALLOC_ERROR;
+             pg->ppd_status = PPD_ALLOC_ERROR;
 
              goto error;
            }
@@ -1121,62 +1183,57 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
       else if (!strcmp(string, "Plus90"))
         ppd->landscape = 90;
     }
-    else if (!strcmp(keyword, "Emulators") && string)
+    else if (!strcmp(keyword, "Emulators") && string && ppd->num_emulations == 0)
     {
-      for (count = 1, sptr = string; sptr != NULL;)
-        if ((sptr = strchr(sptr, ' ')) != NULL)
-       {
-         count ++;
-         while (*sptr == ' ')
-           sptr ++;
-       }
+     /*
+      * Issue #5562: Samsung printer drivers incorrectly use Emulators keyword
+      *              to configure themselves
+      *
+      * The Emulators keyword was loaded but never used by anything in CUPS,
+      * and has no valid purpose in CUPS.  The old code was removed due to a
+      * memory leak (Issue #5475), so the following (new) code supports a single
+      * name for the Emulators keyword, allowing these drivers to work until we
+      * remove PPD and driver support entirely in a future version of CUPS.
+      */
+
+      ppd->num_emulations = 1;
+      ppd->emulations     = calloc(1, sizeof(ppd_emul_t));
+
+      strlcpy(ppd->emulations[0].name, string, sizeof(ppd->emulations[0].name));
+    }
+    else if (!strcmp(keyword, "JobPatchFile"))
+    {
+     /*
+      * CUPS STR #3421: Check for "*JobPatchFile: int: string"
+      */
 
-      ppd->num_emulations = count;
-      if ((ppd->emulations = calloc(count, sizeof(ppd_emul_t))) == NULL)
+      if (isdigit(*string & 255))
       {
-        cg->ppd_status = PPD_ALLOC_ERROR;
+        for (sptr = string + 1; isdigit(*sptr & 255); sptr ++);
 
-       goto error;
+        if (*sptr == ':')
+        {
+         /*
+          * Found "*JobPatchFile: int: string"...
+          */
+
+          pg->ppd_status = PPD_BAD_VALUE;
+
+         goto error;
+        }
       }
 
-      for (i = 0, sptr = string; i < count; i ++)
+      if (!name[0] && pg->ppd_conform == PPD_CONFORM_STRICT)
       {
-        for (nameptr = ppd->emulations[i].name;
-            *sptr != '\0' && *sptr != ' ';
-            sptr ++)
-         if (nameptr < (ppd->emulations[i].name + sizeof(ppd->emulations[i].name) - 1))
-           *nameptr++ = *sptr;
+       /*
+        * Found "*JobPatchFile: string"...
+        */
 
-       *nameptr = '\0';
+        pg->ppd_status = PPD_MISSING_OPTION_KEYWORD;
 
-       while (*sptr == ' ')
-         sptr ++;
+       goto error;
       }
-    }
-    else if (!strncmp(keyword, "StartEmulator_", 14))
-    {
-      ppd_decode(string);
 
-      for (i = 0; i < ppd->num_emulations; i ++)
-        if (!strcmp(keyword + 14, ppd->emulations[i].name))
-       {
-         ppd->emulations[i].start = string;
-         string = NULL;
-       }
-    }
-    else if (!strncmp(keyword, "StopEmulator_", 13))
-    {
-      ppd_decode(string);
-
-      for (i = 0; i < ppd->num_emulations; i ++)
-        if (!strcmp(keyword + 13, ppd->emulations[i].name))
-       {
-         ppd->emulations[i].stop = string;
-         string = NULL;
-       }
-    }
-    else if (!strcmp(keyword, "JobPatchFile"))
-    {
       if (ppd->patches == NULL)
         ppd->patches = strdup(string);
       else
@@ -1185,14 +1242,14 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
                                     strlen(string) + 1);
         if (temp == NULL)
        {
-          cg->ppd_status = PPD_ALLOC_ERROR;
+          pg->ppd_status = PPD_ALLOC_ERROR;
 
          goto error;
        }
 
         ppd->patches = temp;
 
-        strcpy(ppd->patches + strlen(ppd->patches), string);
+        memcpy(ppd->patches + strlen(ppd->patches), string, strlen(string) + 1);
       }
     }
     else if (!strcmp(keyword, "OpenUI"))
@@ -1201,9 +1258,9 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       * Don't allow nesting of options...
       */
 
-      if (option && cg->ppd_conform == PPD_CONFORM_STRICT)
+      if (option && pg->ppd_conform == PPD_CONFORM_STRICT)
       {
-        cg->ppd_status = PPD_NESTED_OPEN_UI;
+        pg->ppd_status = PPD_NESTED_OPEN_UI;
 
        goto error;
       }
@@ -1212,26 +1269,26 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
       * Add an option record to the current sub-group, group, or file...
       */
 
-      DEBUG_printf(("2ppdOpen2: name=\"%s\" (%d)", name, (int)strlen(name)));
+      DEBUG_printf(("2_ppdOpen: name=\"%s\" (%d)", name, (int)strlen(name)));
 
       if (name[0] == '*')
         _cups_strcpy(name, name + 1); /* Eliminate leading asterisk */
 
-      for (i = (int)strlen(name) - 1; i > 0 && isspace(name[i] & 255); i --)
+      for (i = (int)strlen(name) - 1; i > 0 && _cups_isspace(name[i]); i --)
         name[i] = '\0'; /* Eliminate trailing spaces */
 
-      DEBUG_printf(("2ppdOpen2: OpenUI of %s in group %s...", name,
+      DEBUG_printf(("2_ppdOpen: OpenUI of %s in group %s...", name,
                     group ? group->text : "(null)"));
 
       if (subgroup != NULL)
         option = ppd_get_option(subgroup, name);
       else if (group == NULL)
       {
-       if ((group = ppd_get_group(ppd, "General", _("General"), cg,
+       if ((group = ppd_get_group(ppd, "General", _("General"), pg,
                                   encoding)) == NULL)
          goto error;
 
-        DEBUG_printf(("2ppdOpen2: Adding to group %s...", group->text));
+        DEBUG_printf(("2_ppdOpen: Adding to group %s...", group->text));
         option = ppd_get_option(group, name);
        group  = NULL;
       }
@@ -1240,7 +1297,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
 
       if (option == NULL)
       {
-        cg->ppd_status = PPD_ALLOC_ERROR;
+        pg->ppd_status = PPD_ALLOC_ERROR;
 
        goto error;
       }
@@ -1255,9 +1312,9 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
         option->ui = PPD_UI_BOOLEAN;
       else if (string && !strcmp(string, "PickOne"))
         option->ui = PPD_UI_PICKONE;
-      else if (cg->ppd_conform == PPD_CONFORM_STRICT)
+      else if (pg->ppd_conform == PPD_CONFORM_STRICT)
       {
-        cg->ppd_status = PPD_BAD_OPEN_UI;
+        pg->ppd_status = PPD_BAD_OPEN_UI;
 
        goto error;
       }
@@ -1269,7 +1326,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
            !strcmp(ppd->attrs[j]->name + 7, name) &&
            ppd->attrs[j]->value)
        {
-         DEBUG_printf(("2ppdOpen2: Setting Default%s to %s via attribute...",
+         DEBUG_printf(("2_ppdOpen: Setting Default%s to %s via attribute...",
                        option->keyword, ppd->attrs[j]->value));
          strlcpy(option->defchoice, ppd->attrs[j]->value,
                  sizeof(option->defchoice));
@@ -1297,7 +1354,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
 
       option->section = PPD_ORDER_ANY;
 
-      _cupsStrFree(string);
+      free(string);
       string = NULL;
 
      /*
@@ -1305,8 +1362,8 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       * attribute...
       */
 
-      if (!strcasecmp(name, "PageRegion"))
-        strcpy(custom_name, "CustomPageSize");
+      if (!_cups_strcasecmp(name, "PageRegion"))
+        strlcpy(custom_name, "CustomPageSize", sizeof(custom_name));
       else
         snprintf(custom_name, sizeof(custom_name), "Custom%s", name);
 
@@ -1315,9 +1372,9 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
         if ((choice = ppdFindChoice(option, "Custom")) == NULL)
          if ((choice = ppd_add_choice(option, "Custom")) == NULL)
          {
-           DEBUG_puts("1ppdOpen2: Unable to add Custom choice!");
+           DEBUG_puts("1_ppdOpen: Unable to add Custom choice!");
 
-           cg->ppd_status = PPD_ALLOC_ERROR;
+           pg->ppd_status = PPD_ALLOC_ERROR;
 
            goto error;
          }
@@ -1325,7 +1382,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
        strlcpy(choice->text,
                custom_attr->text[0] ? custom_attr->text : _("Custom"),
                sizeof(choice->text));
-        choice->code = _cupsStrRetain(custom_attr->value);
+        choice->code = strdup(custom_attr->value);
       }
     }
     else if (!strcmp(keyword, "JCLOpenUI"))
@@ -1334,9 +1391,9 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       * Don't allow nesting of options...
       */
 
-      if (option && cg->ppd_conform == PPD_CONFORM_STRICT)
+      if (option && pg->ppd_conform == PPD_CONFORM_STRICT)
       {
-        cg->ppd_status = PPD_NESTED_OPEN_UI;
+        pg->ppd_status = PPD_NESTED_OPEN_UI;
 
        goto error;
       }
@@ -1345,7 +1402,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       * Find the JCL group, and add if needed...
       */
 
-      group = ppd_get_group(ppd, "JCL", _("JCL"), cg, encoding);
+      group = ppd_get_group(ppd, "JCL", _("JCL"), pg, encoding);
 
       if (group == NULL)
        goto error;
@@ -1361,7 +1418,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
 
       if (option == NULL)
       {
-        cg->ppd_status = PPD_ALLOC_ERROR;
+        pg->ppd_status = PPD_ALLOC_ERROR;
 
        goto error;
       }
@@ -1378,7 +1435,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
         option->ui = PPD_UI_PICKONE;
       else
       {
-        cg->ppd_status = PPD_BAD_OPEN_UI;
+        pg->ppd_status = PPD_BAD_OPEN_UI;
 
        goto error;
       }
@@ -1388,7 +1445,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
            !strcmp(ppd->attrs[j]->name + 7, name) &&
            ppd->attrs[j]->value)
        {
-         DEBUG_printf(("2ppdOpen2: Setting Default%s to %s via attribute...",
+         DEBUG_printf(("2_ppdOpen: Setting Default%s to %s via attribute...",
                        option->keyword, ppd->attrs[j]->value));
          strlcpy(option->defchoice, ppd->attrs[j]->value,
                  sizeof(option->defchoice));
@@ -1404,7 +1461,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       option->section = PPD_ORDER_JCL;
       group = NULL;
 
-      _cupsStrFree(string);
+      free(string);
       string = NULL;
 
      /*
@@ -1418,9 +1475,9 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       {
        if ((choice = ppd_add_choice(option, "Custom")) == NULL)
        {
-         DEBUG_puts("1ppdOpen2: Unable to add Custom choice!");
+         DEBUG_puts("1_ppdOpen: Unable to add Custom choice!");
 
-         cg->ppd_status = PPD_ALLOC_ERROR;
+         pg->ppd_status = PPD_ALLOC_ERROR;
 
          goto error;
        }
@@ -1428,14 +1485,77 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
        strlcpy(choice->text,
                custom_attr->text[0] ? custom_attr->text : _("Custom"),
                sizeof(choice->text));
-        choice->code = _cupsStrRetain(custom_attr->value);
+        choice->code = strdup(custom_attr->value);
       }
     }
-    else if (!strcmp(keyword, "CloseUI") || !strcmp(keyword, "JCLCloseUI"))
+    else if (!strcmp(keyword, "CloseUI"))
     {
+      if ((!option || option->section == PPD_ORDER_JCL) && pg->ppd_conform == PPD_CONFORM_STRICT)
+      {
+        pg->ppd_status = PPD_BAD_CLOSE_UI;
+
+       goto error;
+      }
+
+      if (option && (!_cups_strcasecmp(option->defchoice, "custom") || !_cups_strncasecmp(option->defchoice, "custom.", 7)))
+      {
+       /*
+       * "*DefaultOption: Custom..." may set the default to a custom value
+       * or (for a very small number of incompatible PPD files) select a
+       * standard choice for the option, which CUPS renames to "_Custom..."
+       * to avoid compatibility issues.  See which this is...
+       */
+
+        char tchoice[PPD_MAX_NAME];    /* Temporary choice name */
+
+       snprintf(tchoice, sizeof(tchoice), "_%s", option->defchoice);
+
+       if (ppdFindChoice(option, tchoice))
+       {
+         strlcpy(option->defchoice, tchoice, sizeof(option->defchoice));
+
+         DEBUG_printf(("2_ppdOpen: Reset Default%s to %s...", option->keyword, tchoice));
+       }
+      }
+
       option = NULL;
 
-      _cupsStrFree(string);
+      free(string);
+      string = NULL;
+    }
+    else if (!strcmp(keyword, "JCLCloseUI"))
+    {
+      if ((!option || option->section != PPD_ORDER_JCL) && pg->ppd_conform == PPD_CONFORM_STRICT)
+      {
+        pg->ppd_status = PPD_BAD_CLOSE_UI;
+
+       goto error;
+      }
+
+      if (option && (!_cups_strcasecmp(option->defchoice, "custom") || !_cups_strncasecmp(option->defchoice, "custom.", 7)))
+      {
+       /*
+       * "*DefaultOption: Custom..." may set the default to a custom value
+       * or (for a very small number of incompatible PPD files) select a
+       * standard choice for the option, which CUPS renames to "_Custom..."
+       * to avoid compatibility issues.  See which this is...
+       */
+
+        char tchoice[PPD_MAX_NAME];    /* Temporary choice name */
+
+       snprintf(tchoice, sizeof(tchoice), "_%s", option->defchoice);
+
+       if (ppdFindChoice(option, tchoice))
+       {
+         strlcpy(option->defchoice, tchoice, sizeof(option->defchoice));
+
+         DEBUG_printf(("2_ppdOpen: Reset Default%s to %s...", option->keyword, tchoice));
+       }
+      }
+
+      option = NULL;
+
+      free(string);
       string = NULL;
     }
     else if (!strcmp(keyword, "OpenGroup"))
@@ -1446,14 +1566,14 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
 
       if (group != NULL)
       {
-        cg->ppd_status = PPD_NESTED_OPEN_GROUP;
+        pg->ppd_status = PPD_NESTED_OPEN_GROUP;
 
        goto error;
       }
 
       if (!string)
       {
-        cg->ppd_status = PPD_BAD_OPEN_GROUP;
+        pg->ppd_status = PPD_BAD_OPEN_GROUP;
 
        goto error;
       }
@@ -1477,19 +1597,19 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
       * Find/add the group...
       */
 
-      group = ppd_get_group(ppd, string, sptr, cg, encoding);
+      group = ppd_get_group(ppd, string, sptr, pg, encoding);
 
       if (group == NULL)
        goto error;
 
-      _cupsStrFree(string);
+      free(string);
       string = NULL;
     }
     else if (!strcmp(keyword, "CloseGroup"))
     {
       group = NULL;
 
-      _cupsStrFree(string);
+      free(string);
       string = NULL;
     }
     else if (!strcmp(keyword, "OrderDependency"))
@@ -1498,7 +1618,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
 
       if (!sptr || sscanf(sptr, "%40s%40s", name, keyword) != 2)
       {
-        cg->ppd_status = PPD_BAD_ORDER_DEPENDENCY;
+        pg->ppd_status = PPD_BAD_ORDER_DEPENDENCY;
 
        goto error;
       }
@@ -1547,7 +1667,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
        option->order   = order;
       }
 
-      _cupsStrFree(string);
+      free(string);
       string = NULL;
     }
     else if (!strncmp(keyword, "Default", 7))
@@ -1591,11 +1711,9 @@ ppdOpen2(cups_file_t *fp)                /* I - File to read from */
         * Set the default as part of the current option...
        */
 
-        DEBUG_printf(("2ppdOpen2: Setting %s to %s...", keyword, string));
-
-        strlcpy(option->defchoice, string, sizeof(option->defchoice));
+       strlcpy(option->defchoice, string, sizeof(option->defchoice));
 
-        DEBUG_printf(("2ppdOpen2: %s is now %s...", keyword, option->defchoice));
+        DEBUG_printf(("2_ppdOpen: Set %s to %s...", keyword, option->defchoice));
       }
       else
       {
@@ -1605,11 +1723,27 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
 
         ppd_option_t   *toption;       /* Temporary option */
 
-
         if ((toption = ppdFindOption(ppd, keyword + 7)) != NULL)
        {
-         DEBUG_printf(("2ppdOpen2: Setting %s to %s...", keyword, string));
-         strlcpy(toption->defchoice, string, sizeof(toption->defchoice));
+         if (!_cups_strcasecmp(string, "custom") || !_cups_strncasecmp(string, "custom.", 7))
+         {
+          /*
+           * "*DefaultOption: Custom..." may set the default to a custom value
+           * or (for a very small number of incompatible PPD files) select a
+           * standard choice for the option, which CUPS renames to "_Custom..."
+           * to avoid compatibility issues.  See which this is...
+           */
+
+           snprintf(toption->defchoice, sizeof(toption->defchoice), "_%s", string);
+           if (!ppdFindChoice(toption, toption->defchoice))
+             strlcpy(toption->defchoice, string, sizeof(toption->defchoice));
+         }
+         else
+         {
+           strlcpy(toption->defchoice, string, sizeof(toption->defchoice));
+         }
+
+         DEBUG_printf(("2_ppdOpen: Set %s to %s...", keyword, toption->defchoice));
        }
       }
     }
@@ -1618,19 +1752,18 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
     {
       if (!string)
       {
-       cg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
+       pg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
        goto error;
       }
 
       if (ppd->num_consts == 0)
        constraint = calloc(2, sizeof(ppd_const_t));
       else
-       constraint = realloc(ppd->consts,
-                            (ppd->num_consts + 2) * sizeof(ppd_const_t));
+       constraint = realloc(ppd->consts, (size_t)(ppd->num_consts + 2) * sizeof(ppd_const_t));
 
       if (constraint == NULL)
       {
-        cg->ppd_status = PPD_ALLOC_ERROR;
+        pg->ppd_status = PPD_ALLOC_ERROR;
 
        goto error;
       }
@@ -1643,9 +1776,8 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
                      constraint->choice1, constraint->option2,
                     constraint->choice2))
       {
-        case 0 : /* Error */
-       case 1 : /* Error */
-           cg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
+        default : /* Error */
+           pg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
            goto error;
 
        case 2 : /* Two options... */
@@ -1653,11 +1785,11 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
            * Check for broken constraints like "* Option"...
            */
 
-           if (cg->ppd_conform == PPD_CONFORM_STRICT &&
+           if (pg->ppd_conform == PPD_CONFORM_STRICT &&
                (!strcmp(constraint->option1, "*") ||
                 !strcmp(constraint->choice1, "*")))
            {
-             cg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
+             pg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
              goto error;
            }
 
@@ -1668,35 +1800,35 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
 
            if (constraint->option1[0] == '*')
              _cups_strcpy(constraint->option1, constraint->option1 + 1);
-           else if (cg->ppd_conform == PPD_CONFORM_STRICT)
+           else if (pg->ppd_conform == PPD_CONFORM_STRICT)
            {
-             cg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
+             pg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
              goto error;
            }
 
            if (constraint->choice1[0] == '*')
              _cups_strcpy(constraint->option2, constraint->choice1 + 1);
-           else if (cg->ppd_conform == PPD_CONFORM_STRICT)
+           else if (pg->ppd_conform == PPD_CONFORM_STRICT)
            {
-             cg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
+             pg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
              goto error;
            }
 
             constraint->choice1[0] = '\0';
             constraint->choice2[0] = '\0';
            break;
-           
+
        case 3 : /* Two options, one choice... */
           /*
            * Check for broken constraints like "* Option"...
            */
 
-           if (cg->ppd_conform == PPD_CONFORM_STRICT &&
+           if (pg->ppd_conform == PPD_CONFORM_STRICT &&
                (!strcmp(constraint->option1, "*") ||
                 !strcmp(constraint->choice1, "*") ||
                 !strcmp(constraint->option2, "*")))
            {
-             cg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
+             pg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
              goto error;
            }
 
@@ -1707,18 +1839,18 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
 
            if (constraint->option1[0] == '*')
              _cups_strcpy(constraint->option1, constraint->option1 + 1);
-           else if (cg->ppd_conform == PPD_CONFORM_STRICT)
+           else if (pg->ppd_conform == PPD_CONFORM_STRICT)
            {
-             cg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
+             pg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
              goto error;
            }
 
            if (constraint->choice1[0] == '*')
            {
-             if (cg->ppd_conform == PPD_CONFORM_STRICT &&
+             if (pg->ppd_conform == PPD_CONFORM_STRICT &&
                  constraint->option2[0] == '*')
              {
-               cg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
+               pg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
                goto error;
              }
 
@@ -1730,58 +1862,58 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
            {
              if (constraint->option2[0] == '*')
                _cups_strcpy(constraint->option2, constraint->option2 + 1);
-             else if (cg->ppd_conform == PPD_CONFORM_STRICT)
+             else if (pg->ppd_conform == PPD_CONFORM_STRICT)
              {
-               cg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
+               pg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
                goto error;
              }
 
               constraint->choice2[0] = '\0';
            }
            break;
-           
+
        case 4 : /* Two options, two choices... */
           /*
            * Check for broken constraints like "* Option"...
            */
 
-           if (cg->ppd_conform == PPD_CONFORM_STRICT &&
+           if (pg->ppd_conform == PPD_CONFORM_STRICT &&
                (!strcmp(constraint->option1, "*") ||
                 !strcmp(constraint->choice1, "*") ||
                 !strcmp(constraint->option2, "*") ||
                 !strcmp(constraint->choice2, "*")))
            {
-             cg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
+             pg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
              goto error;
            }
 
            if (constraint->option1[0] == '*')
              _cups_strcpy(constraint->option1, constraint->option1 + 1);
-           else if (cg->ppd_conform == PPD_CONFORM_STRICT)
+           else if (pg->ppd_conform == PPD_CONFORM_STRICT)
            {
-             cg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
+             pg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
              goto error;
            }
 
-            if (cg->ppd_conform == PPD_CONFORM_STRICT &&
+            if (pg->ppd_conform == PPD_CONFORM_STRICT &&
                constraint->choice1[0] == '*')
            {
-             cg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
+             pg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
              goto error;
            }
 
            if (constraint->option2[0] == '*')
              _cups_strcpy(constraint->option2, constraint->option2 + 1);
-           else if (cg->ppd_conform == PPD_CONFORM_STRICT)
+           else if (pg->ppd_conform == PPD_CONFORM_STRICT)
            {
-             cg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
+             pg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
              goto error;
            }
 
-            if (cg->ppd_conform == PPD_CONFORM_STRICT &&
+            if (pg->ppd_conform == PPD_CONFORM_STRICT &&
                constraint->choice2[0] == '*')
            {
-             cg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
+             pg->ppd_status = PPD_BAD_UI_CONSTRAINTS;
              goto error;
            }
            break;
@@ -1791,11 +1923,18 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
       * Don't add this one as an attribute...
       */
 
-      _cupsStrFree(string);
+      free(string);
       string = NULL;
     }
     else if (!strcmp(keyword, "PaperDimension"))
     {
+      if (!_cups_strcasecmp(name, "custom") || !_cups_strncasecmp(name, "custom.", 7))
+      {
+        char cname[PPD_MAX_NAME];      /* Rewrite with a leading underscore */
+        snprintf(cname, sizeof(cname), "_%s", name);
+        strlcpy(name, cname, sizeof(name));
+      }
+
       if ((size = ppdPageSize(ppd, name)) == NULL)
        size = ppd_add_size(ppd, name);
 
@@ -1805,7 +1944,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
         * Unable to add or find size!
        */
 
-        cg->ppd_status = PPD_ALLOC_ERROR;
+        pg->ppd_status = PPD_ALLOC_ERROR;
 
        goto error;
       }
@@ -1813,11 +1952,18 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
       size->width  = (float)_cupsStrScand(string, &sptr, loc);
       size->length = (float)_cupsStrScand(sptr, NULL, loc);
 
-      _cupsStrFree(string);
+      free(string);
       string = NULL;
     }
     else if (!strcmp(keyword, "ImageableArea"))
     {
+      if (!_cups_strcasecmp(name, "custom") || !_cups_strncasecmp(name, "custom.", 7))
+      {
+        char cname[PPD_MAX_NAME];      /* Rewrite with a leading underscore */
+        snprintf(cname, sizeof(cname), "_%s", name);
+        strlcpy(name, cname, sizeof(name));
+      }
+
       if ((size = ppdPageSize(ppd, name)) == NULL)
        size = ppd_add_size(ppd, name);
 
@@ -1827,7 +1973,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
         * Unable to add or find size!
        */
 
-        cg->ppd_status = PPD_ALLOC_ERROR;
+        pg->ppd_status = PPD_ALLOC_ERROR;
 
        goto error;
       }
@@ -1837,7 +1983,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
       size->right  = (float)_cupsStrScand(sptr, &sptr, loc);
       size->top    = (float)_cupsStrScand(sptr, NULL, loc);
 
-      _cupsStrFree(string);
+      free(string);
       string = NULL;
     }
     else if (option != NULL &&
@@ -1845,7 +1991,14 @@ ppdOpen2(cups_file_t *fp)                /* I - File to read from */
                 (PPD_KEYWORD | PPD_OPTION | PPD_STRING) &&
             !strcmp(keyword, option->keyword))
     {
-      DEBUG_printf(("2ppdOpen2: group=%p, subgroup=%p", group, subgroup));
+      DEBUG_printf(("2_ppdOpen: group=%p, subgroup=%p", group, subgroup));
+
+      if (!_cups_strcasecmp(name, "custom") || !_cups_strncasecmp(name, "custom.", 7))
+      {
+        char cname[PPD_MAX_NAME];      /* Rewrite with a leading underscore */
+        snprintf(cname, sizeof(cname), "_%s", name);
+        strlcpy(name, cname, sizeof(name));
+      }
 
       if (!strcmp(keyword, "PageSize"))
       {
@@ -1863,7 +2016,7 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
 
       if ((choice = ppd_add_choice(option, name)) == NULL)
       {
-        cg->ppd_status = PPD_ALLOC_ERROR;
+        pg->ppd_status = PPD_ALLOC_ERROR;
 
        goto error;
       }
@@ -1872,9 +2025,9 @@ ppdOpen2(cups_file_t *fp)         /* I - File to read from */
         cupsCharsetToUTF8((cups_utf8_t *)choice->text, text,
                           sizeof(choice->text), encoding);
       else if (!strcmp(name, "True"))
-        strcpy(choice->text, _("Yes"));
+        strlcpy(choice->text, _("Yes"), sizeof(choice->text));
       else if (!strcmp(name, "False"))
-        strcpy(choice->text, _("No"));
+        strlcpy(choice->text, _("No"), sizeof(choice->text));
       else
         strlcpy(choice->text, name, sizeof(choice->text));
 
@@ -1893,24 +2046,42 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
         (mask & (PPD_KEYWORD | PPD_STRING)) == (PPD_KEYWORD | PPD_STRING))
       ppd_add_attr(ppd, keyword, name, text, string);
     else
-      _cupsStrFree(string);
+      free(string);
   }
 
-  ppd_free(line.buffer);
+ /*
+  * Check for a missing CloseUI/JCLCloseUI...
+  */
+
+  if (option && pg->ppd_conform == PPD_CONFORM_STRICT)
+  {
+    pg->ppd_status = PPD_MISSING_CLOSE_UI;
+    goto error;
+  }
 
  /*
-  * Reset language preferences...
+  * Check for a missing CloseGroup...
   */
 
-  cupsLangFree(language);
+  if (group && pg->ppd_conform == PPD_CONFORM_STRICT)
+  {
+    pg->ppd_status = PPD_MISSING_CLOSE_GROUP;
+    goto error;
+  }
+
+  free(line.buffer);
+
+ /*
+  * Reset language preferences...
+  */
 
 #ifdef DEBUG
   if (!cupsFileEOF(fp))
-    DEBUG_printf(("1ppdOpen2: Premature EOF at %lu...\n",
+    DEBUG_printf(("1_ppdOpen: Premature EOF at %lu...\n",
                   (unsigned long)cupsFileTell(fp)));
 #endif /* DEBUG */
 
-  if (cg->ppd_status != PPD_OK)
+  if (pg->ppd_status != PPD_OK)
   {
    /*
     * Had an error reading the PPD file, cannot continue!
@@ -1921,6 +2092,17 @@ ppdOpen2(cups_file_t *fp)                /* I - File to read from */
     return (NULL);
   }
 
+ /*
+  * Update the filters array as needed...
+  */
+
+  if (!ppd_update_filters(ppd, pg))
+  {
+    ppdClose(ppd);
+
+    return (NULL);
+  }
+
  /*
   * Create the sorted options array and set the option back-pointer for
   * each choice and custom option...
@@ -1969,17 +2151,62 @@ ppdOpen2(cups_file_t *fp)               /* I - File to read from */
 
   error:
 
-  _cupsStrFree(string);
-  ppd_free(line.buffer);
+  free(string);
+  free(line.buffer);
 
   ppdClose(ppd);
 
-  cupsLangFree(language);
-
   return (NULL);
 }
 
 
+/*
+ * 'ppdOpen()' - Read a PPD file into memory.
+ */
+
+ppd_file_t *                           /* O - PPD file record */
+ppdOpen(FILE *fp)                      /* I - File to read from */
+{
+  ppd_file_t   *ppd;                   /* PPD file record */
+  cups_file_t  *cf;                    /* CUPS file */
+
+
+ /*
+  * Reopen the stdio file as a CUPS file...
+  */
+
+  if ((cf = cupsFileOpenFd(fileno(fp), "r")) == NULL)
+    return (NULL);
+
+ /*
+  * Load the PPD file using the newer API...
+  */
+
+  ppd = _ppdOpen(cf, _PPD_LOCALIZATION_DEFAULT);
+
+ /*
+  * Close the CUPS file and return the PPD...
+  */
+
+  cupsFileClose(cf);
+
+  return (ppd);
+}
+
+
+/*
+ * 'ppdOpen2()' - Read a PPD file into memory.
+ *
+ * @since CUPS 1.2/macOS 10.5@
+ */
+
+ppd_file_t *                           /* O - PPD file record or @code NULL@ if the PPD file could not be opened. */
+ppdOpen2(cups_file_t *fp)              /* I - File to read from */
+{
+  return _ppdOpen(fp, _PPD_LOCALIZATION_DEFAULT);
+}
+
+
 /*
  * 'ppdOpenFd()' - Read a PPD file into memory.
  */
@@ -1989,7 +2216,7 @@ ppdOpenFd(int fd)                 /* I - File to read from */
 {
   cups_file_t          *fp;            /* CUPS file pointer */
   ppd_file_t           *ppd;           /* PPD file record */
-  _cups_globals_t      *cg = _cupsGlobals();
+  _ppd_globals_t       *pg = _ppdGlobals();
                                        /* Global data */
 
 
@@ -1997,7 +2224,7 @@ ppdOpenFd(int fd)                 /* I - File to read from */
   * Set the line number to 0...
   */
 
-  cg->ppd_line = 0;
+  pg->ppd_line = 0;
 
  /*
   * Range check input...
@@ -2005,7 +2232,7 @@ ppdOpenFd(int fd)                 /* I - File to read from */
 
   if (fd < 0)
   {
-    cg->ppd_status = PPD_NULL_FILE;
+    pg->ppd_status = PPD_NULL_FILE;
 
     return (NULL);
   }
@@ -2022,7 +2249,7 @@ ppdOpenFd(int fd)                 /* I - File to read from */
   }
   else
   {
-    cg->ppd_status = PPD_FILE_OPEN_ERROR;
+    pg->ppd_status = PPD_FILE_OPEN_ERROR;
     ppd            = NULL;
   }
 
@@ -2031,15 +2258,16 @@ ppdOpenFd(int fd)                       /* I - File to read from */
 
 
 /*
- * 'ppdOpenFile()' - Read a PPD file into memory.
+ * '_ppdOpenFile()' - Read a PPD file into memory.
  */
 
 ppd_file_t *                           /* O - PPD file record or @code NULL@ if the PPD file could not be opened. */
-ppdOpenFile(const char *filename)      /* I - File to read from */
+_ppdOpenFile(const char                  *filename,    /* I - File to read from */
+            _ppd_localization_t  localization) /* I - Localization to load */
 {
   cups_file_t          *fp;            /* File pointer */
   ppd_file_t           *ppd;           /* PPD file record */
-  _cups_globals_t      *cg = _cupsGlobals();
+  _ppd_globals_t       *pg = _ppdGlobals();
                                        /* Global data */
 
 
@@ -2047,7 +2275,7 @@ ppdOpenFile(const char *filename) /* I - File to read from */
   * Set the line number to 0...
   */
 
-  cg->ppd_line = 0;
+  pg->ppd_line = 0;
 
  /*
   * Range check input...
@@ -2055,7 +2283,7 @@ ppdOpenFile(const char *filename) /* I - File to read from */
 
   if (filename == NULL)
   {
-    cg->ppd_status = PPD_NULL_FILE;
+    pg->ppd_status = PPD_NULL_FILE;
 
     return (NULL);
   }
@@ -2066,13 +2294,13 @@ ppdOpenFile(const char *filename)       /* I - File to read from */
 
   if ((fp = cupsFileOpen(filename, "r")) != NULL)
   {
-    ppd = ppdOpen2(fp);
+    ppd = _ppdOpen(fp, localization);
 
     cupsFileClose(fp);
   }
   else
   {
-    cg->ppd_status = PPD_FILE_OPEN_ERROR;
+    pg->ppd_status = PPD_FILE_OPEN_ERROR;
     ppd            = NULL;
   }
 
@@ -2080,20 +2308,31 @@ ppdOpenFile(const char *filename)       /* I - File to read from */
 }
 
 
+/*
+ * 'ppdOpenFile()' - Read a PPD file into memory.
+ */
+
+ppd_file_t *                           /* O - PPD file record or @code NULL@ if the PPD file could not be opened. */
+ppdOpenFile(const char *filename)      /* I - File to read from */
+{
+  return _ppdOpenFile(filename, _PPD_LOCALIZATION_DEFAULT);
+}
+
+
 /*
  * 'ppdSetConformance()' - Set the conformance level for PPD files.
  *
- * @since CUPS 1.1.20/Mac OS X 10.4@
+ * @since CUPS 1.1.20/macOS 10.4@
  */
 
 void
 ppdSetConformance(ppd_conform_t c)     /* I - Conformance level */
 {
-  _cups_globals_t      *cg = _cupsGlobals();
+  _ppd_globals_t       *pg = _ppdGlobals();
                                        /* Global data */
 
 
-  cg->ppd_conform = c;
+  pg->ppd_conform = c;
 }
 
 
@@ -2134,7 +2373,7 @@ ppd_add_attr(ppd_file_t *ppd,             /* I - PPD file data */
   if (ppd->num_attrs == 0)
     ptr = malloc(sizeof(ppd_attr_t *));
   else
-    ptr = realloc(ppd->attrs, (ppd->num_attrs + 1) * sizeof(ppd_attr_t *));
+    ptr = realloc(ppd->attrs, (size_t)(ppd->num_attrs + 1) * sizeof(ppd_attr_t *));
 
   if (ptr == NULL)
     return (NULL);
@@ -2153,8 +2392,16 @@ ppd_add_attr(ppd_file_t *ppd,            /* I - PPD file data */
   * Copy data over...
   */
 
+  if (!_cups_strcasecmp(spec, "custom") || !_cups_strncasecmp(spec, "custom.", 7))
+  {
+    temp->spec[0] = '_';
+    strlcpy(temp->spec + 1, spec, sizeof(temp->spec) - 1);
+  }
+  else {
+      strlcpy(temp->spec, spec, sizeof(temp->spec));
+  }
+
   strlcpy(temp->name, name, sizeof(temp->name));
-  strlcpy(temp->spec, spec, sizeof(temp->spec));
   strlcpy(temp->text, text, sizeof(temp->text));
   temp->value = (char *)value;
 
@@ -2186,8 +2433,7 @@ ppd_add_choice(ppd_option_t *option,      /* I - Option */
   if (option->num_choices == 0)
     choice = malloc(sizeof(ppd_choice_t));
   else
-    choice = realloc(option->choices,
-                    sizeof(ppd_choice_t) * (option->num_choices + 1));
+    choice = realloc(option->choices, sizeof(ppd_choice_t) * (size_t)(option->num_choices + 1));
 
   if (choice == NULL)
     return (NULL);
@@ -2217,7 +2463,7 @@ ppd_add_size(ppd_file_t *ppd,             /* I - PPD file */
   if (ppd->num_sizes == 0)
     size = malloc(sizeof(ppd_size_t));
   else
-    size = realloc(ppd->sizes, sizeof(ppd_size_t) * (ppd->num_sizes + 1));
+    size = realloc(ppd->sizes, sizeof(ppd_size_t) * (size_t)(ppd->num_sizes + 1));
 
   if (size == NULL)
     return (NULL);
@@ -2241,7 +2487,7 @@ static int                                /* O - Result of comparison */
 ppd_compare_attrs(ppd_attr_t *a,       /* I - First attribute */
                   ppd_attr_t *b)       /* I - Second attribute */
 {
-  return (strcasecmp(a->name, b->name));
+  return (_cups_strcasecmp(a->name, b->name));
 }
 
 
@@ -2265,19 +2511,7 @@ static int                               /* O - Result of comparison */
 ppd_compare_coptions(ppd_coption_t *a, /* I - First option */
                      ppd_coption_t *b) /* I - Second option */
 {
-  return (strcasecmp(a->keyword, b->keyword));
-}
-
-
-/*
- * 'ppd_compare_cparams()' - Compare two custom parameters.
- */
-
-static int                             /* O - Result of comparison */
-ppd_compare_cparams(ppd_cparam_t *a,   /* I - First parameter */
-                    ppd_cparam_t *b)   /* I - Second parameter */
-{
-  return (strcasecmp(a->name, b->name));
+  return (_cups_strcasecmp(a->keyword, b->keyword));
 }
 
 
@@ -2289,7 +2523,7 @@ static int                                /* O - Result of comparison */
 ppd_compare_options(ppd_option_t *a,   /* I - First option */
                     ppd_option_t *b)   /* I - Second option */
 {
-  return (strcasecmp(a->keyword, b->keyword));
+  return (_cups_strcasecmp(a->keyword, b->keyword));
 }
 
 
@@ -2317,20 +2551,20 @@ ppd_decode(char *string)                /* I - String to decode */
       inptr ++;
       while (isxdigit(*inptr & 255))
       {
-       if (isalpha(*inptr))
-         *outptr = (tolower(*inptr) - 'a' + 10) << 4;
+       if (_cups_isalpha(*inptr))
+         *outptr = (char)((tolower(*inptr) - 'a' + 10) << 4);
        else
-         *outptr = (*inptr - '0') << 4;
+         *outptr = (char)((*inptr - '0') << 4);
 
        inptr ++;
 
         if (!isxdigit(*inptr & 255))
          break;
 
-       if (isalpha(*inptr))
-         *outptr |= tolower(*inptr) - 'a' + 10;
+       if (_cups_isalpha(*inptr))
+         *outptr |= (char)(tolower(*inptr) - 'a' + 10);
        else
-         *outptr |= *inptr - '0';
+         *outptr |= (char)(*inptr - '0');
 
        inptr ++;
        outptr ++;
@@ -2350,6 +2584,30 @@ ppd_decode(char *string)         /* I - String to decode */
 }
 
 
+/*
+ * 'ppd_free_filters()' - Free the filters array.
+ */
+
+static void
+ppd_free_filters(ppd_file_t *ppd)      /* I - PPD file */
+{
+  int  i;                              /* Looping var */
+  char **filter;                       /* Current filter */
+
+
+  if (ppd->num_filters > 0)
+  {
+    for (i = ppd->num_filters, filter = ppd->filters; i > 0; i --, filter ++)
+      free(*filter);
+
+    free(ppd->filters);
+
+    ppd->num_filters = 0;
+    ppd->filters     = NULL;
+  }
+}
+
+
 /*
  * 'ppd_free_group()' - Free a single UI group.
  */
@@ -2369,7 +2627,7 @@ ppd_free_group(ppd_group_t *group)        /* I - Group to free */
         i --, option ++)
       ppd_free_option(option);
 
-    ppd_free(group->options);
+    free(group->options);
   }
 
   if (group->num_subgroups > 0)
@@ -2379,7 +2637,7 @@ ppd_free_group(ppd_group_t *group)        /* I - Group to free */
         i --, subgroup ++)
       ppd_free_group(subgroup);
 
-    ppd_free(group->subgroups);
+    free(group->subgroups);
   }
 }
 
@@ -2401,10 +2659,10 @@ ppd_free_option(ppd_option_t *option)   /* I - Option to free */
          i > 0;
          i --, choice ++)
     {
-      _cupsStrFree(choice->code);
+      free(choice->code);
     }
 
-    ppd_free(option->choices);
+    free(option->choices);
   }
 }
 
@@ -2436,7 +2694,7 @@ ppd_get_coption(ppd_file_t *ppd,  /* I - PPD file */
 
   strlcpy(copt->keyword, name, sizeof(copt->keyword));
 
-  copt->params = cupsArrayNew((cups_array_func_t)ppd_compare_cparams, NULL);
+  copt->params = cupsArrayNew((cups_array_func_t)NULL, NULL);
 
   cupsArrayAdd(ppd->coptions, copt);
 
@@ -2474,6 +2732,7 @@ ppd_get_cparam(ppd_coption_t *opt,        /* I - PPD file */
   if ((cparam = calloc(1, sizeof(ppd_cparam_t))) == NULL)
     return (NULL);
 
+  cparam->type = PPD_CUSTOM_UNKNOWN;
   strlcpy(cparam->name, param, sizeof(cparam->name));
   strlcpy(cparam->text, text[0] ? text : param, sizeof(cparam->text));
 
@@ -2499,7 +2758,7 @@ static ppd_group_t *                      /* O - Named group */
 ppd_get_group(ppd_file_t      *ppd,    /* I - PPD file */
               const char      *name,   /* I - Name of group */
              const char      *text,    /* I - Text for group */
-              _cups_globals_t *cg,     /* I - Global data */
+              _ppd_globals_t  *pg,     /* I - Global data */
              cups_encoding_t encoding) /* I - Encoding of text */
 {
   int          i;                      /* Looping var */
@@ -2507,7 +2766,7 @@ ppd_get_group(ppd_file_t      *ppd,       /* I - PPD file */
 
 
   DEBUG_printf(("7ppd_get_group(ppd=%p, name=\"%s\", text=\"%s\", cg=%p)",
-                ppd, name, text, cg));
+                ppd, name, text, pg));
 
   for (i = ppd->num_groups, group = ppd->groups; i > 0; i --, group ++)
     if (!strcmp(group->name, name))
@@ -2517,22 +2776,21 @@ ppd_get_group(ppd_file_t      *ppd,     /* I - PPD file */
   {
     DEBUG_printf(("8ppd_get_group: Adding group %s...", name));
 
-    if (cg->ppd_conform == PPD_CONFORM_STRICT && strlen(text) >= sizeof(group->text))
+    if (pg->ppd_conform == PPD_CONFORM_STRICT && strlen(text) >= sizeof(group->text))
     {
-      cg->ppd_status = PPD_ILLEGAL_TRANSLATION;
+      pg->ppd_status = PPD_ILLEGAL_TRANSLATION;
 
       return (NULL);
     }
-           
+
     if (ppd->num_groups == 0)
       group = malloc(sizeof(ppd_group_t));
     else
-      group = realloc(ppd->groups,
-                     (ppd->num_groups + 1) * sizeof(ppd_group_t));
+      group = realloc(ppd->groups, (size_t)(ppd->num_groups + 1) * sizeof(ppd_group_t));
 
     if (group == NULL)
     {
-      cg->ppd_status = PPD_ALLOC_ERROR;
+      pg->ppd_status = PPD_ALLOC_ERROR;
 
       return (NULL);
     }
@@ -2576,8 +2834,7 @@ ppd_get_option(ppd_group_t *group,        /* I - Group */
     if (group->num_options == 0)
       option = malloc(sizeof(ppd_option_t));
     else
-      option = realloc(group->options,
-                      (group->num_options + 1) * sizeof(ppd_option_t));
+      option = realloc(group->options, (size_t)(group->num_options + 1) * sizeof(ppd_option_t));
 
     if (option == NULL)
       return (NULL);
@@ -2594,6 +2851,47 @@ ppd_get_option(ppd_group_t *group,       /* I - Group */
 }
 
 
+/*
+ * 'ppd_globals_alloc()' - Allocate and initialize global data.
+ */
+
+static _ppd_globals_t *                /* O - Pointer to global data */
+ppd_globals_alloc(void)
+{
+  return ((_ppd_globals_t *)calloc(1, sizeof(_ppd_globals_t)));
+}
+
+
+/*
+ * 'ppd_globals_free()' - Free global data.
+ */
+
+#if defined(HAVE_PTHREAD_H) || defined(_WIN32)
+static void
+ppd_globals_free(_ppd_globals_t *pg)   /* I - Pointer to global data */
+{
+  free(pg);
+}
+#endif /* HAVE_PTHREAD_H || _WIN32 */
+
+
+#ifdef HAVE_PTHREAD_H
+/*
+ * 'ppd_globals_init()' - Initialize per-thread globals...
+ */
+
+static void
+ppd_globals_init(void)
+{
+ /*
+  * Register the global data for this thread...
+  */
+
+  pthread_key_create(&ppd_globals_key, (void (*)(void *))ppd_globals_free);
+}
+#endif /* HAVE_PTHREAD_H */
+
+
 /*
  * 'ppd_hash_option()' - Generate a hash of the option name...
  */
@@ -2606,7 +2904,7 @@ ppd_hash_option(ppd_option_t *option)     /* I - Option */
 
 
   for (hash = option->keyword[0], k = option->keyword + 1; *k;)
-    hash = 33 * hash + *k++;
+    hash = (int)(33U * (unsigned)hash) + *k++;
 
   return (hash & 511);
 }
@@ -2625,7 +2923,7 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
          char           *text,         /* O - Human-readable text from line */
         char           **string,       /* O - Code/string data */
          int            ignoreblank,   /* I - Ignore blank lines? */
-        _cups_globals_t *cg)           /* I - Global data */
+        _ppd_globals_t *pg)            /* I - Global data */
 {
   int          ch,                     /* Character from file */
                col,                    /* Column in line */
@@ -2647,7 +2945,7 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
 
   *string   = NULL;
   col       = 0;
-  startline = cg->ppd_line + 1;
+  startline = pg->ppd_line + 1;
 
   if (!line->buffer)
   {
@@ -2686,8 +2984,8 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
          * Don't allow lines longer than 256k!
          */
 
-          cg->ppd_line   = startline;
-          cg->ppd_status = PPD_LINE_TOO_LONG;
+          pg->ppd_line   = startline;
+          pg->ppd_status = PPD_LINE_TOO_LONG;
 
          return (0);
        }
@@ -2695,8 +2993,8 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
         temp = realloc(line->buffer, line->bufsize);
        if (!temp)
        {
-          cg->ppd_line   = startline;
-          cg->ppd_status = PPD_LINE_TOO_LONG;
+          pg->ppd_line   = startline;
+          pg->ppd_status = PPD_LINE_TOO_LONG;
 
          return (0);
        }
@@ -2711,7 +3009,7 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
        * Line feed or carriage return...
        */
 
-        cg->ppd_line ++;
+        pg->ppd_line ++;
        col = 0;
 
        if (ch == '\r')
@@ -2740,14 +3038,14 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
 
        *lineptr++ = '\n';
       }
-      else if (ch < ' ' && ch != '\t' && cg->ppd_conform == PPD_CONFORM_STRICT)
+      else if (ch < ' ' && ch != '\t' && pg->ppd_conform == PPD_CONFORM_STRICT)
       {
        /*
         * Other control characters...
        */
 
-        cg->ppd_line   = startline;
-        cg->ppd_status = PPD_ILLEGAL_CHARACTER;
+        pg->ppd_line   = startline;
+        pg->ppd_status = PPD_ILLEGAL_CHARACTER;
 
         return (0);
       }
@@ -2757,7 +3055,7 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
        * Any other character...
        */
 
-       *lineptr++ = ch;
+       *lineptr++ = (char)ch;
        col ++;
 
        if (col > (PPD_MAX_LINE - 1))
@@ -2766,8 +3064,8 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
           * Line is too long...
          */
 
-          cg->ppd_line   = startline;
-          cg->ppd_status = PPD_LINE_TOO_LONG;
+          pg->ppd_line   = startline;
+          pg->ppd_status = PPD_LINE_TOO_LONG;
 
           return (0);
        }
@@ -2791,7 +3089,7 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
          break;
        else if (ch == '\r' || ch == '\n')
        {
-         cg->ppd_line ++;
+         pg->ppd_line ++;
          col = 0;
 
          if (ch == '\r')
@@ -2806,14 +3104,14 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
              cupsFileGetChar(fp);
          }
        }
-       else if (ch < ' ' && ch != '\t' && cg->ppd_conform == PPD_CONFORM_STRICT)
+       else if (ch < ' ' && ch != '\t' && pg->ppd_conform == PPD_CONFORM_STRICT)
        {
         /*
           * Other control characters...
          */
 
-          cg->ppd_line   = startline;
-          cg->ppd_status = PPD_ILLEGAL_CHARACTER;
+          pg->ppd_line   = startline;
+          pg->ppd_status = PPD_ILLEGAL_CHARACTER;
 
           return (0);
        }
@@ -2827,8 +3125,8 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
             * Line is too long...
            */
 
-            cg->ppd_line   = startline;
-            cg->ppd_status = PPD_LINE_TOO_LONG;
+            pg->ppd_line   = startline;
+            pg->ppd_status = PPD_LINE_TOO_LONG;
 
             return (0);
          }
@@ -2848,7 +3146,7 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
          * Line feed or carriage return...
          */
 
-          cg->ppd_line ++;
+          pg->ppd_line ++;
          col = 0;
 
          if (ch == '\r')
@@ -2865,14 +3163,14 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
 
          break;
        }
-       else if (ch < ' ' && ch != '\t' && cg->ppd_conform == PPD_CONFORM_STRICT)
+       else if (ch < ' ' && ch != '\t' && pg->ppd_conform == PPD_CONFORM_STRICT)
        {
         /*
           * Other control characters...
          */
 
-          cg->ppd_line   = startline;
-          cg->ppd_status = PPD_ILLEGAL_CHARACTER;
+          pg->ppd_line   = startline;
+          pg->ppd_status = PPD_ILLEGAL_CHARACTER;
 
           return (0);
        }
@@ -2886,8 +3184,8 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
             * Line is too long...
            */
 
-            cg->ppd_line   = startline;
-            cg->ppd_status = PPD_LINE_TOO_LONG;
+            pg->ppd_line   = startline;
+            pg->ppd_status = PPD_LINE_TOO_LONG;
 
             return (0);
          }
@@ -2902,7 +3200,7 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
     DEBUG_printf(("9ppd_read: LINE=\"%s\"", line->buffer));
 
    /*
-    * The dynamically created PPDs for older style Mac OS X
+    * The dynamically created PPDs for older style macOS
     * drivers include a large blob of data inserted as comments
     * at the end of the file.  As an optimization we can stop
     * reading the PPD when we get to the start of this data.
@@ -2931,21 +3229,21 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
          !strcmp(line->buffer, "*End")) && /* End of multi-line string */
         ignoreblank)                   /* Ignore these? */
     {
-      startline = cg->ppd_line + 1;
+      startline = pg->ppd_line + 1;
       continue;
     }
 
     if (!strcmp(line->buffer, "*"))    /* (Bad) comment line */
     {
-      if (cg->ppd_conform == PPD_CONFORM_RELAXED)
+      if (pg->ppd_conform == PPD_CONFORM_RELAXED)
       {
-       startline = cg->ppd_line + 1;
+       startline = pg->ppd_line + 1;
        continue;
       }
       else
       {
-        cg->ppd_line   = startline;
-        cg->ppd_status = PPD_ILLEGAL_MAIN_KEYWORD;
+        pg->ppd_line   = startline;
+        pg->ppd_status = PPD_ILLEGAL_MAIN_KEYWORD;
 
         return (0);
       }
@@ -2958,12 +3256,12 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
       */
 
       for (lineptr = line->buffer; *lineptr; lineptr ++)
-        if (!isspace(*lineptr & 255))
+        if (*lineptr && !_cups_isspace(*lineptr))
          break;
 
       if (*lineptr)
       {
-        cg->ppd_status = PPD_MISSING_ASTERISK;
+        pg->ppd_status = PPD_MISSING_ASTERISK;
         return (0);
       }
       else if (ignoreblank)
@@ -2978,12 +3276,12 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
 
     keyptr = keyword;
 
-    while (*lineptr != '\0' && *lineptr != ':' && !isspace(*lineptr & 255))
+    while (*lineptr && *lineptr != ':' && !_cups_isspace(*lineptr))
     {
       if (*lineptr <= ' ' || *lineptr > 126 || *lineptr == '/' ||
           (keyptr - keyword) >= (PPD_MAX_NAME - 1))
       {
-        cg->ppd_status = PPD_ILLEGAL_MAIN_KEYWORD;
+        pg->ppd_status = PPD_ILLEGAL_MAIN_KEYWORD;
        return (0);
       }
 
@@ -2997,24 +3295,24 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
 
     mask |= PPD_KEYWORD;
 
-    if (isspace(*lineptr & 255))
+    if (_cups_isspace(*lineptr))
     {
      /*
       * Get an option name...
       */
 
-      while (isspace(*lineptr & 255))
+      while (_cups_isspace(*lineptr))
         lineptr ++;
 
       optptr = option;
 
-      while (*lineptr != '\0' && !isspace(*lineptr & 255) && *lineptr != ':' &&
+      while (*lineptr && !_cups_isspace(*lineptr) && *lineptr != ':' &&
              *lineptr != '/')
       {
        if (*lineptr <= ' ' || *lineptr > 126 ||
            (optptr - option) >= (PPD_MAX_NAME - 1))
         {
-          cg->ppd_status = PPD_ILLEGAL_OPTION_KEYWORD;
+          pg->ppd_status = PPD_ILLEGAL_OPTION_KEYWORD;
          return (0);
        }
 
@@ -3023,13 +3321,13 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
 
       *optptr = '\0';
 
-      if (isspace(*lineptr & 255) && cg->ppd_conform == PPD_CONFORM_STRICT)
+      if (_cups_isspace(*lineptr) && pg->ppd_conform == PPD_CONFORM_STRICT)
       {
-        cg->ppd_status = PPD_ILLEGAL_WHITESPACE;
+        pg->ppd_status = PPD_ILLEGAL_WHITESPACE;
        return (0);
       }
 
-      while (isspace(*lineptr & 255))
+      while (_cups_isspace(*lineptr))
        lineptr ++;
 
       mask |= PPD_OPTION;
@@ -3041,7 +3339,7 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
        */
 
         lineptr ++;
-       
+
        textptr = text;
 
        while (*lineptr != '\0' && *lineptr != '\n' && *lineptr != ':')
@@ -3049,7 +3347,7 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
          if (((unsigned char)*lineptr < ' ' && *lineptr != '\t') ||
              (textptr - text) >= (PPD_MAX_LINE - 1))
          {
-           cg->ppd_status = PPD_ILLEGAL_TRANSLATION;
+           pg->ppd_status = PPD_ILLEGAL_TRANSLATION;
            return (0);
          }
 
@@ -3059,23 +3357,23 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
        *textptr = '\0';
        textlen  = ppd_decode(text);
 
-       if (textlen > PPD_MAX_TEXT && cg->ppd_conform == PPD_CONFORM_STRICT)
+       if (textlen > PPD_MAX_TEXT && pg->ppd_conform == PPD_CONFORM_STRICT)
        {
-         cg->ppd_status = PPD_ILLEGAL_TRANSLATION;
+         pg->ppd_status = PPD_ILLEGAL_TRANSLATION;
          return (0);
        }
-           
+
        mask |= PPD_TEXT;
       }
     }
 
-    if (isspace(*lineptr & 255) && cg->ppd_conform == PPD_CONFORM_STRICT)
+    if (_cups_isspace(*lineptr) && pg->ppd_conform == PPD_CONFORM_STRICT)
     {
-      cg->ppd_status = PPD_ILLEGAL_WHITESPACE;
+      pg->ppd_status = PPD_ILLEGAL_WHITESPACE;
       return (0);
     }
 
-    while (isspace(*lineptr & 255))
+    while (_cups_isspace(*lineptr))
       lineptr ++;
 
     if (*lineptr == ':')
@@ -3085,11 +3383,11 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
       */
 
       lineptr ++;
-      while (isspace(*lineptr & 255))
+      while (_cups_isspace(*lineptr))
         lineptr ++;
 
       strptr = lineptr + strlen(lineptr) - 1;
-      while (strptr >= lineptr && isspace(*strptr & 255))
+      while (strptr >= lineptr && _cups_isspace(*strptr))
         *strptr-- = '\0';
 
       if (*strptr == '\"')
@@ -3102,7 +3400,7 @@ ppd_read(cups_file_t    *fp,              /* I - File to read from */
        lineptr ++;
       }
 
-      *string = _cupsStrAlloc(lineptr);
+      *string = strdup(lineptr);
 
       mask |= PPD_STRING;
     }
@@ -3114,5 +3412,120 @@ ppd_read(cups_file_t    *fp,            /* I - File to read from */
 
 
 /*
- * End of "$Id: ppd.c 7906 2008-09-03 20:19:43Z mike $".
+ * 'ppd_update_filters()' - Update the filters array as needed.
+ *
+ * This function re-populates the filters array with cupsFilter2 entries that
+ * have been stripped of the destination MIME media types and any maxsize hints.
+ *
+ * (All for backwards-compatibility)
  */
+
+static int                             /* O - 1 on success, 0 on failure */
+ppd_update_filters(ppd_file_t     *ppd,        /* I - PPD file */
+                   _ppd_globals_t *pg) /* I - Global data */
+{
+  ppd_attr_t   *attr;                  /* Current cupsFilter2 value */
+  char         srcsuper[16],           /* Source MIME media type */
+               srctype[256],
+               dstsuper[16],           /* Destination MIME media type */
+               dsttype[256],
+               program[1024],          /* Command to run */
+               *ptr,                   /* Pointer into command to run */
+               buffer[1024],           /* Re-written cupsFilter value */
+               **filter;               /* Current filter */
+  int          cost;                   /* Cost of filter */
+
+
+  DEBUG_printf(("4ppd_update_filters(ppd=%p, cg=%p)", ppd, pg));
+
+ /*
+  * See if we have any cupsFilter2 lines...
+  */
+
+  if ((attr = ppdFindAttr(ppd, "cupsFilter2", NULL)) == NULL)
+  {
+    DEBUG_puts("5ppd_update_filters: No cupsFilter2 keywords present.");
+    return (1);
+  }
+
+ /*
+  * Yes, free the cupsFilter-defined filters and re-build...
+  */
+
+  ppd_free_filters(ppd);
+
+  do
+  {
+   /*
+    * Parse the cupsFilter2 string:
+    *
+    *   src/type dst/type cost program
+    *   src/type dst/type cost maxsize(n) program
+    */
+
+    DEBUG_printf(("5ppd_update_filters: cupsFilter2=\"%s\"", attr->value));
+
+    if (sscanf(attr->value, "%15[^/]/%255s%*[ \t]%15[^/]/%255s%d%*[ \t]%1023[^\n]",
+              srcsuper, srctype, dstsuper, dsttype, &cost, program) != 6)
+    {
+      DEBUG_puts("5ppd_update_filters: Bad cupsFilter2 line.");
+      pg->ppd_status = PPD_BAD_VALUE;
+
+      return (0);
+    }
+
+    DEBUG_printf(("5ppd_update_filters: srcsuper=\"%s\", srctype=\"%s\", "
+                  "dstsuper=\"%s\", dsttype=\"%s\", cost=%d, program=\"%s\"",
+                 srcsuper, srctype, dstsuper, dsttype, cost, program));
+
+    if (!strncmp(program, "maxsize(", 8) &&
+        (ptr = strchr(program + 8, ')')) != NULL)
+    {
+      DEBUG_puts("5ppd_update_filters: Found maxsize(nnn).");
+
+      ptr ++;
+      while (_cups_isspace(*ptr))
+       ptr ++;
+
+      _cups_strcpy(program, ptr);
+      DEBUG_printf(("5ppd_update_filters: New program=\"%s\"", program));
+    }
+
+   /*
+    * Convert to cupsFilter format:
+    *
+    *   src/type cost program
+    */
+
+    snprintf(buffer, sizeof(buffer), "%s/%s %d %s", srcsuper, srctype, cost,
+             program);
+    DEBUG_printf(("5ppd_update_filters: Adding \"%s\".", buffer));
+
+   /*
+    * Add a cupsFilter-compatible string to the filters array.
+    */
+
+    if (ppd->num_filters == 0)
+      filter = malloc(sizeof(char *));
+    else
+      filter = realloc(ppd->filters, sizeof(char *) * (size_t)(ppd->num_filters + 1));
+
+    if (filter == NULL)
+    {
+      DEBUG_puts("5ppd_update_filters: Out of memory.");
+      pg->ppd_status = PPD_ALLOC_ERROR;
+
+      return (0);
+    }
+
+    ppd->filters     = filter;
+    filter           += ppd->num_filters;
+    ppd->num_filters ++;
+
+    *filter = strdup(buffer);
+  }
+  while ((attr = ppdFindNextAttr(ppd, "cupsFilter2", NULL)) != NULL);
+
+  DEBUG_puts("5ppd_update_filters: Completed OK.");
+  return (1);
+}