• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Option conflict management routines for CUPS.
3  *
4  * Copyright © 2020-2024 by OpenPrinting.
5  * Copyright 2007-2018 by Apple Inc.
6  * Copyright 1997-2007 by Easy Software Products, all rights reserved.
7  *
8  * Licensed under Apache License v2.0.  See the file "LICENSE" for more
9  * information.
10  *
11  * PostScript is a trademark of Adobe Systems, Inc.
12  */
13 
14 /*
15  * Include necessary headers...
16  */
17 
18 #include "cups-private.h"
19 #include "ppd-private.h"
20 #include "debug-internal.h"
21 
22 
23 /*
24  * Local constants...
25  */
26 
27 enum
28 {
29   _PPD_OPTION_CONSTRAINTS,
30   _PPD_INSTALLABLE_CONSTRAINTS,
31   _PPD_ALL_CONSTRAINTS
32 };
33 
34 
35 /*
36  * Local functions...
37  */
38 
39 static int		ppd_is_installable(ppd_group_t *installable,
40 			                   const char *option);
41 static void		ppd_load_constraints(ppd_file_t *ppd);
42 static cups_array_t	*ppd_test_constraints(ppd_file_t *ppd,
43 			                      const char *option,
44 					      const char *choice,
45 			                      int num_options,
46 			                      cups_option_t *options,
47 					      int which);
48 
49 
50 /*
51  * 'cupsGetConflicts()' - Get a list of conflicting options in a marked PPD.
52  *
53  * This function gets a list of options that would conflict if "option" and
54  * "choice" were marked in the PPD.  You would typically call this function
55  * after marking the currently selected options in the PPD in order to
56  * determine whether a new option selection would cause a conflict.
57  *
58  * The number of conflicting options are returned with "options" pointing to
59  * the conflicting options.  The returned option array must be freed using
60  * @link cupsFreeOptions@.
61  *
62  * @since CUPS 1.4/macOS 10.6@
63  */
64 
65 int					/* O - Number of conflicting options */
cupsGetConflicts(ppd_file_t * ppd,const char * option,const char * choice,cups_option_t ** options)66 cupsGetConflicts(
67     ppd_file_t    *ppd,			/* I - PPD file */
68     const char    *option,		/* I - Option to test */
69     const char    *choice,		/* I - Choice to test */
70     cups_option_t **options)		/* O - Conflicting options */
71 {
72   int			i,		/* Looping var */
73 			num_options;	/* Number of conflicting options */
74   cups_array_t		*active;	/* Active conflicts */
75   _ppd_cups_uiconsts_t	*c;		/* Current constraints */
76   _ppd_cups_uiconst_t	*cptr;		/* Current constraint */
77   ppd_choice_t		*marked;	/* Marked choice */
78 
79 
80  /*
81   * Range check input...
82   */
83 
84   if (options)
85     *options = NULL;
86 
87   if (!ppd || !option || !choice || !options)
88     return (0);
89 
90  /*
91   * Test for conflicts...
92   */
93 
94   active = ppd_test_constraints(ppd, option, choice, 0, NULL,
95                                 _PPD_ALL_CONSTRAINTS);
96 
97  /*
98   * Loop through all of the UI constraints and add any options that conflict...
99   */
100 
101   for (num_options = 0, c = (_ppd_cups_uiconsts_t *)cupsArrayFirst(active);
102        c;
103        c = (_ppd_cups_uiconsts_t *)cupsArrayNext(active))
104   {
105     for (i = c->num_constraints, cptr = c->constraints;
106          i > 0;
107 	 i --, cptr ++)
108       if (_cups_strcasecmp(cptr->option->keyword, option))
109       {
110         if (cptr->choice)
111 	  num_options = cupsAddOption(cptr->option->keyword,
112 	                              cptr->choice->choice, num_options,
113 				      options);
114         else if ((marked = ppdFindMarkedChoice(ppd,
115 	                                       cptr->option->keyword)) != NULL)
116 	  num_options = cupsAddOption(cptr->option->keyword, marked->choice,
117 				      num_options, options);
118       }
119   }
120 
121   cupsArrayDelete(active);
122 
123   return (num_options);
124 }
125 
126 
127 /*
128  * 'cupsResolveConflicts()' - Resolve conflicts in a marked PPD.
129  *
130  * This function attempts to resolve any conflicts in a marked PPD, returning
131  * a list of option changes that are required to resolve them.  On input,
132  * "num_options" and "options" contain any pending option changes that have
133  * not yet been marked, while "option" and "choice" contain the most recent
134  * selection which may or may not be in "num_options" or "options".
135  *
136  * On successful return, "num_options" and "options" are updated to contain
137  * "option" and "choice" along with any changes required to resolve conflicts
138  * specified in the PPD file and 1 is returned.
139  *
140  * If option conflicts cannot be resolved, "num_options" and "options" are not
141  * changed and 0 is returned.
142  *
143  * When resolving conflicts, @code cupsResolveConflicts@ does not consider
144  * changes to the current page size (@code media@, @code PageSize@, and
145  * @code PageRegion@) or to the most recent option specified in "option".
146  * Thus, if the only way to resolve a conflict is to change the page size
147  * or the option the user most recently changed, @code cupsResolveConflicts@
148  * will return 0 to indicate it was unable to resolve the conflicts.
149  *
150  * The @code cupsResolveConflicts@ function uses one of two sources of option
151  * constraint information.  The preferred constraint information is defined by
152  * @code cupsUIConstraints@ and @code cupsUIResolver@ attributes - in this
153  * case, the PPD file provides constraint resolution actions.
154  *
155  * The backup constraint information is defined by the
156  * @code UIConstraints@ and @code NonUIConstraints@ attributes.  These
157  * constraints are resolved algorithmically by first selecting the default
158  * choice for the conflicting option, then iterating over all possible choices
159  * until a non-conflicting option choice is found.
160  *
161  * @since CUPS 1.4/macOS 10.6@
162  */
163 
164 int					/* O  - 1 on success, 0 on failure */
cupsResolveConflicts(ppd_file_t * ppd,const char * option,const char * choice,int * num_options,cups_option_t ** options)165 cupsResolveConflicts(
166     ppd_file_t    *ppd,			/* I  - PPD file */
167     const char    *option,		/* I  - Newly selected option or @code NULL@ for none */
168     const char    *choice,		/* I  - Newly selected choice or @code NULL@ for none */
169     int           *num_options,		/* IO - Number of additional selected options */
170     cups_option_t **options)		/* IO - Additional selected options */
171 {
172   int			i,		/* Looping var */
173 			tries,		/* Number of tries */
174 			num_newopts;	/* Number of new options */
175   cups_option_t		*newopts;	/* New options */
176   cups_array_t		*active = NULL,	/* Active constraints */
177 			*pass,		/* Resolvers for this pass */
178 			*resolvers,	/* Resolvers we have used */
179 			*test;		/* Test array for conflicts */
180   _ppd_cups_uiconsts_t	*consts;	/* Current constraints */
181   _ppd_cups_uiconst_t	*constptr;	/* Current constraint */
182   ppd_attr_t		*resolver;	/* Current resolver */
183   const char		*resval;	/* Pointer into resolver value */
184   char			resoption[PPD_MAX_NAME],
185 					/* Current resolver option */
186 			reschoice[PPD_MAX_NAME],
187 					/* Current resolver choice */
188 			*resptr,	/* Pointer into option/choice */
189 			firstpage[255];	/* AP_FIRSTPAGE_Keyword string */
190   const char		*value;		/* Selected option value */
191   int			changed;	/* Did we change anything? */
192   ppd_choice_t		*marked;	/* Marked choice */
193 
194 
195  /*
196   * Range check input...
197   */
198 
199   if (!ppd || !num_options || !options || (option == NULL) != (choice == NULL))
200     return (0);
201 
202  /*
203   * Build a shadow option array...
204   */
205 
206   num_newopts = 0;
207   newopts     = NULL;
208 
209   for (i = 0; i < *num_options; i ++)
210     num_newopts = cupsAddOption((*options)[i].name, (*options)[i].value,
211                                 num_newopts, &newopts);
212   if (option && _cups_strcasecmp(option, "Collate"))
213     num_newopts = cupsAddOption(option, choice, num_newopts, &newopts);
214 
215  /*
216   * Loop until we have no conflicts...
217   */
218 
219   cupsArraySave(ppd->sorted_attrs);
220 
221   resolvers = NULL;
222   pass      = cupsArrayNew((cups_array_func_t)_cups_strcasecmp, NULL);
223   tries     = 0;
224 
225   while (tries < 100 &&
226          (active = ppd_test_constraints(ppd, NULL, NULL, num_newopts, newopts,
227                                         _PPD_ALL_CONSTRAINTS)) != NULL)
228   {
229     tries ++;
230 
231     if (!resolvers)
232       resolvers = cupsArrayNew((cups_array_func_t)_cups_strcasecmp, NULL);
233 
234     for (consts = (_ppd_cups_uiconsts_t *)cupsArrayFirst(active), changed = 0;
235          consts;
236 	 consts = (_ppd_cups_uiconsts_t *)cupsArrayNext(active))
237     {
238       if (consts->resolver[0])
239       {
240        /*
241         * Look up the resolver...
242 	*/
243 
244         if (cupsArrayFind(pass, consts->resolver))
245 	  continue;			/* Already applied this resolver... */
246 
247         if (cupsArrayFind(resolvers, consts->resolver))
248 	{
249 	 /*
250 	  * Resolver loop!
251 	  */
252 
253 	  DEBUG_printf(("1cupsResolveConflicts: Resolver loop with %s!",
254 	                consts->resolver));
255           goto error;
256 	}
257 
258         if ((resolver = ppdFindAttr(ppd, "cupsUIResolver",
259 	                            consts->resolver)) == NULL)
260         {
261 	  DEBUG_printf(("1cupsResolveConflicts: Resolver %s not found!",
262 	                consts->resolver));
263 	  goto error;
264 	}
265 
266         if (!resolver->value)
267 	{
268 	  DEBUG_printf(("1cupsResolveConflicts: Resolver %s has no value!",
269 	                consts->resolver));
270 	  goto error;
271 	}
272 
273        /*
274         * Add the options from the resolver...
275 	*/
276 
277         cupsArrayAdd(pass, consts->resolver);
278 	cupsArrayAdd(resolvers, consts->resolver);
279 
280         for (resval = resolver->value; *resval && !changed;)
281 	{
282 	  while (_cups_isspace(*resval))
283 	    resval ++;
284 
285 	  if (*resval != '*')
286 	    break;
287 
288 	  for (resval ++, resptr = resoption;
289 	       *resval && !_cups_isspace(*resval);
290 	       resval ++)
291             if (resptr < (resoption + sizeof(resoption) - 1))
292 	      *resptr++ = *resval;
293 
294           *resptr = '\0';
295 
296 	  while (_cups_isspace(*resval))
297 	    resval ++;
298 
299 	  for (resptr = reschoice;
300 	       *resval && !_cups_isspace(*resval);
301 	       resval ++)
302             if (resptr < (reschoice + sizeof(reschoice) - 1))
303 	      *resptr++ = *resval;
304 
305           *resptr = '\0';
306 
307           if (!resoption[0] || !reschoice[0])
308 	    break;
309 
310          /*
311 	  * Is this the option we are changing?
312 	  */
313 
314           snprintf(firstpage, sizeof(firstpage), "AP_FIRSTPAGE_%s", resoption);
315 
316 	  if (option &&
317 	      (!_cups_strcasecmp(resoption, option) ||
318 	       !_cups_strcasecmp(firstpage, option) ||
319 	       (!_cups_strcasecmp(option, "PageSize") &&
320 		!_cups_strcasecmp(resoption, "PageRegion")) ||
321 	       (!_cups_strcasecmp(option, "AP_FIRSTPAGE_PageSize") &&
322 		!_cups_strcasecmp(resoption, "PageSize")) ||
323 	       (!_cups_strcasecmp(option, "AP_FIRSTPAGE_PageSize") &&
324 		!_cups_strcasecmp(resoption, "PageRegion")) ||
325 	       (!_cups_strcasecmp(option, "PageRegion") &&
326 	        !_cups_strcasecmp(resoption, "PageSize")) ||
327 	       (!_cups_strcasecmp(option, "AP_FIRSTPAGE_PageRegion") &&
328 	        !_cups_strcasecmp(resoption, "PageSize")) ||
329 	       (!_cups_strcasecmp(option, "AP_FIRSTPAGE_PageRegion") &&
330 	        !_cups_strcasecmp(resoption, "PageRegion"))))
331 	    continue;
332 
333 	 /*
334 	  * Try this choice...
335 	  */
336 
337           if ((test = ppd_test_constraints(ppd, resoption, reschoice,
338 					   num_newopts, newopts,
339 					   _PPD_ALL_CONSTRAINTS)) == NULL)
340 	  {
341 	   /*
342 	    * That worked...
343 	    */
344 
345             changed = 1;
346 	  }
347 	  else
348             cupsArrayDelete(test);
349 
350 	 /*
351 	  * Add the option/choice from the resolver regardless of whether it
352 	  * worked; this makes sure that we can cascade several changes to
353 	  * make things resolve...
354 	  */
355 
356 	  num_newopts = cupsAddOption(resoption, reschoice, num_newopts,
357 				      &newopts);
358         }
359       }
360       else
361       {
362        /*
363         * Try resolving by choosing the default values for non-installable
364 	* options, then by iterating through the possible choices...
365 	*/
366 
367         int		j;		/* Looping var */
368 	ppd_choice_t	*cptr;		/* Current choice */
369         ppd_size_t	*size;		/* Current page size */
370 
371 
372         for (i = consts->num_constraints, constptr = consts->constraints;
373 	     i > 0 && !changed;
374 	     i --, constptr ++)
375 	{
376 	 /*
377 	  * Can't resolve by changing an installable option...
378 	  */
379 
380 	  if (constptr->installable)
381 	    continue;
382 
383          /*
384 	  * Is this the option we are changing?
385 	  */
386 
387 	  if (option &&
388 	      (!_cups_strcasecmp(constptr->option->keyword, option) ||
389 	       (!_cups_strcasecmp(option, "PageSize") &&
390 		!_cups_strcasecmp(constptr->option->keyword, "PageRegion")) ||
391 	       (!_cups_strcasecmp(option, "PageRegion") &&
392 		!_cups_strcasecmp(constptr->option->keyword, "PageSize"))))
393 	    continue;
394 
395          /*
396 	  * Get the current option choice...
397 	  */
398 
399           if ((value = cupsGetOption(constptr->option->keyword, num_newopts,
400 	                             newopts)) == NULL)
401           {
402 	    if (!_cups_strcasecmp(constptr->option->keyword, "PageSize") ||
403 	        !_cups_strcasecmp(constptr->option->keyword, "PageRegion"))
404 	    {
405 	      if ((value = cupsGetOption("PageSize", num_newopts,
406 	                                 newopts)) == NULL)
407                 value = cupsGetOption("PageRegion", num_newopts, newopts);
408 
409               if (!value)
410 	      {
411 	        if ((size = ppdPageSize(ppd, NULL)) != NULL)
412 		  value = size->name;
413 		else
414 		  value = "";
415 	      }
416 	    }
417 	    else
418 	    {
419 	      marked = ppdFindMarkedChoice(ppd, constptr->option->keyword);
420 	      value  = marked ? marked->choice : "";
421 	    }
422 	  }
423 
424 	  if (!_cups_strncasecmp(value, "Custom.", 7))
425 	    value = "Custom";
426 
427          /*
428 	  * Try the default choice...
429 	  */
430 
431           test = NULL;
432 
433           if (_cups_strcasecmp(value, constptr->option->defchoice) &&
434 	      (test = ppd_test_constraints(ppd, constptr->option->keyword,
435 	                                   constptr->option->defchoice,
436 					   num_newopts, newopts,
437 					   _PPD_OPTION_CONSTRAINTS)) == NULL)
438 	  {
439 	   /*
440 	    * That worked...
441 	    */
442 
443 	    num_newopts = cupsAddOption(constptr->option->keyword,
444 	                                constptr->option->defchoice,
445 					num_newopts, &newopts);
446             changed     = 1;
447 	  }
448 	  else
449 	  {
450 	   /*
451 	    * Try each choice instead...
452 	    */
453 
454             for (j = constptr->option->num_choices,
455 	             cptr = constptr->option->choices;
456 		 j > 0;
457 		 j --, cptr ++)
458             {
459 	      cupsArrayDelete(test);
460 	      test = NULL;
461 
462 	      if (_cups_strcasecmp(value, cptr->choice) &&
463 	          _cups_strcasecmp(constptr->option->defchoice, cptr->choice) &&
464 		  _cups_strcasecmp("Custom", cptr->choice) &&
465 	          (test = ppd_test_constraints(ppd, constptr->option->keyword,
466 	                                       cptr->choice, num_newopts,
467 					       newopts,
468 					       _PPD_OPTION_CONSTRAINTS)) == NULL)
469 	      {
470 	       /*
471 		* This choice works...
472 		*/
473 
474 		num_newopts = cupsAddOption(constptr->option->keyword,
475 					    cptr->choice, num_newopts,
476 					    &newopts);
477 		changed     = 1;
478 		break;
479 	      }
480 	    }
481 
482 	    cupsArrayDelete(test);
483           }
484         }
485       }
486     }
487 
488     if (!changed)
489     {
490       DEBUG_puts("1cupsResolveConflicts: Unable to automatically resolve "
491 		 "constraint!");
492       goto error;
493     }
494 
495     cupsArrayClear(pass);
496     cupsArrayDelete(active);
497     active = NULL;
498   }
499 
500   if (tries >= 100)
501     goto error;
502 
503  /*
504   * Free the caller's option array...
505   */
506 
507   cupsFreeOptions(*num_options, *options);
508 
509  /*
510   * If Collate is the option we are testing, add it here.  Otherwise, remove
511   * any Collate option from the resolve list since the filters automatically
512   * handle manual collation...
513   */
514 
515   if (option && !_cups_strcasecmp(option, "Collate"))
516     num_newopts = cupsAddOption(option, choice, num_newopts, &newopts);
517   else
518     num_newopts = cupsRemoveOption("Collate", num_newopts, &newopts);
519 
520  /*
521   * Return the new list of options to the caller...
522   */
523 
524   *num_options = num_newopts;
525   *options     = newopts;
526 
527   cupsArrayDelete(pass);
528   cupsArrayDelete(resolvers);
529 
530   cupsArrayRestore(ppd->sorted_attrs);
531 
532   DEBUG_printf(("1cupsResolveConflicts: Returning %d options:", num_newopts));
533 #ifdef DEBUG
534   for (i = 0; i < num_newopts; i ++)
535     DEBUG_printf(("1cupsResolveConflicts: options[%d]: %s=%s", i,
536                   newopts[i].name, newopts[i].value));
537 #endif /* DEBUG */
538 
539   return (1);
540 
541  /*
542   * If we get here, we failed to resolve...
543   */
544 
545   error:
546 
547   cupsFreeOptions(num_newopts, newopts);
548 
549   cupsArrayDelete(active);
550   cupsArrayDelete(pass);
551   cupsArrayDelete(resolvers);
552 
553   cupsArrayRestore(ppd->sorted_attrs);
554 
555   DEBUG_puts("1cupsResolveConflicts: Unable to resolve conflicts!");
556 
557   return (0);
558 }
559 
560 
561 /*
562  * 'ppdConflicts()' - Check to see if there are any conflicts among the
563  *                    marked option choices.
564  *
565  * The returned value is the same as returned by @link ppdMarkOption@.
566  */
567 
568 int					/* O - Number of conflicts found */
ppdConflicts(ppd_file_t * ppd)569 ppdConflicts(ppd_file_t *ppd)		/* I - PPD to check */
570 {
571   int			i,		/* Looping variable */
572 			conflicts;	/* Number of conflicts */
573   cups_array_t		*active;	/* Active conflicts */
574   _ppd_cups_uiconsts_t	*c;		/* Current constraints */
575   _ppd_cups_uiconst_t	*cptr;		/* Current constraint */
576   ppd_option_t	*o;			/* Current option */
577 
578 
579   if (!ppd)
580     return (0);
581 
582  /*
583   * Clear all conflicts...
584   */
585 
586   cupsArraySave(ppd->options);
587 
588   for (o = ppdFirstOption(ppd); o; o = ppdNextOption(ppd))
589     o->conflicted = 0;
590 
591   cupsArrayRestore(ppd->options);
592 
593  /*
594   * Test for conflicts...
595   */
596 
597   active    = ppd_test_constraints(ppd, NULL, NULL, 0, NULL,
598                                    _PPD_ALL_CONSTRAINTS);
599   conflicts = cupsArrayCount(active);
600 
601  /*
602   * Loop through all of the UI constraints and flag any options
603   * that conflict...
604   */
605 
606   for (c = (_ppd_cups_uiconsts_t *)cupsArrayFirst(active);
607        c;
608        c = (_ppd_cups_uiconsts_t *)cupsArrayNext(active))
609   {
610     for (i = c->num_constraints, cptr = c->constraints;
611          i > 0;
612 	 i --, cptr ++)
613       cptr->option->conflicted = 1;
614   }
615 
616   cupsArrayDelete(active);
617 
618  /*
619   * Return the number of conflicts found...
620   */
621 
622   return (conflicts);
623 }
624 
625 
626 /*
627  * 'ppdInstallableConflict()' - Test whether an option choice conflicts with
628  *                              an installable option.
629  *
630  * This function tests whether a particular option choice is available based
631  * on constraints against options in the "InstallableOptions" group.
632  *
633  * @since CUPS 1.4/macOS 10.6@
634  */
635 
636 int					/* O - 1 if conflicting, 0 if not conflicting */
ppdInstallableConflict(ppd_file_t * ppd,const char * option,const char * choice)637 ppdInstallableConflict(
638     ppd_file_t *ppd,			/* I - PPD file */
639     const char *option,			/* I - Option */
640     const char *choice)			/* I - Choice */
641 {
642   cups_array_t	*active;		/* Active conflicts */
643 
644 
645   DEBUG_printf(("2ppdInstallableConflict(ppd=%p, option=\"%s\", choice=\"%s\")",
646                 ppd, option, choice));
647 
648  /*
649   * Range check input...
650   */
651 
652   if (!ppd || !option || !choice)
653     return (0);
654 
655  /*
656   * Test constraints using the new option...
657   */
658 
659   active = ppd_test_constraints(ppd, option, choice, 0, NULL,
660 				_PPD_INSTALLABLE_CONSTRAINTS);
661 
662   cupsArrayDelete(active);
663 
664   return (active != NULL);
665 }
666 
667 
668 /*
669  * 'ppd_is_installable()' - Determine whether an option is in the
670  *                          InstallableOptions group.
671  */
672 
673 static int				/* O - 1 if installable, 0 if normal */
ppd_is_installable(ppd_group_t * installable,const char * name)674 ppd_is_installable(
675     ppd_group_t *installable,		/* I - InstallableOptions group */
676     const char  *name)			/* I - Option name */
677 {
678   if (installable)
679   {
680     int			i;		/* Looping var */
681     ppd_option_t	*option;	/* Current option */
682 
683 
684     for (i = installable->num_options, option = installable->options;
685          i > 0;
686 	 i --, option ++)
687       if (!_cups_strcasecmp(option->keyword, name))
688         return (1);
689   }
690 
691   return (0);
692 }
693 
694 
695 /*
696  * 'ppd_load_constraints()' - Load constraints from a PPD file.
697  */
698 
699 static void
ppd_load_constraints(ppd_file_t * ppd)700 ppd_load_constraints(ppd_file_t *ppd)	/* I - PPD file */
701 {
702   int		i;			/* Looping var */
703   ppd_const_t	*oldconst;		/* Current UIConstraints data */
704   ppd_attr_t	*constattr;		/* Current cupsUIConstraints attribute */
705   _ppd_cups_uiconsts_t	*consts;	/* Current cupsUIConstraints data */
706   _ppd_cups_uiconst_t	*constptr;	/* Current constraint */
707   ppd_group_t	*installable;		/* Installable options group */
708   const char	*vptr;			/* Pointer into constraint value */
709   char		option[PPD_MAX_NAME],	/* Option name/MainKeyword */
710 		choice[PPD_MAX_NAME],	/* Choice/OptionKeyword */
711 		*ptr;			/* Pointer into option or choice */
712 
713 
714   DEBUG_printf(("7ppd_load_constraints(ppd=%p)", ppd));
715 
716  /*
717   * Create an array to hold the constraint data...
718   */
719 
720   ppd->cups_uiconstraints = cupsArrayNew(NULL, NULL);
721 
722  /*
723   * Find the installable options group if it exists...
724   */
725 
726   for (i = ppd->num_groups, installable = ppd->groups;
727        i > 0;
728        i --, installable ++)
729     if (!_cups_strcasecmp(installable->name, "InstallableOptions"))
730       break;
731 
732   if (i <= 0)
733     installable = NULL;
734 
735  /*
736   * Load old-style [Non]UIConstraints data...
737   */
738 
739   for (i = ppd->num_consts, oldconst = ppd->consts; i > 0; i --, oldconst ++)
740   {
741    /*
742     * Weed out nearby duplicates, since the PPD spec requires that you
743     * define both "*Foo foo *Bar bar" and "*Bar bar *Foo foo"...
744     */
745 
746     if (i > 1 &&
747 	!_cups_strcasecmp(oldconst[0].option1, oldconst[1].option2) &&
748 	!_cups_strcasecmp(oldconst[0].choice1, oldconst[1].choice2) &&
749 	!_cups_strcasecmp(oldconst[0].option2, oldconst[1].option1) &&
750 	!_cups_strcasecmp(oldconst[0].choice2, oldconst[1].choice1))
751       continue;
752 
753    /*
754     * Allocate memory...
755     */
756 
757     if ((consts = calloc(1, sizeof(_ppd_cups_uiconsts_t))) == NULL)
758     {
759       DEBUG_puts("8ppd_load_constraints: Unable to allocate memory for "
760 		 "UIConstraints!");
761       return;
762     }
763 
764     if ((constptr = calloc(2, sizeof(_ppd_cups_uiconst_t))) == NULL)
765     {
766       free(consts);
767       DEBUG_puts("8ppd_load_constraints: Unable to allocate memory for "
768 		 "UIConstraints!");
769       return;
770     }
771 
772    /*
773     * Fill in the information...
774     */
775 
776     consts->num_constraints = 2;
777     consts->constraints     = constptr;
778 
779     if (!_cups_strncasecmp(oldconst->option1, "Custom", 6) &&
780 	!_cups_strcasecmp(oldconst->choice1, "True"))
781     {
782       constptr[0].option      = ppdFindOption(ppd, oldconst->option1 + 6);
783       constptr[0].choice      = ppdFindChoice(constptr[0].option, "Custom");
784       constptr[0].installable = 0;
785     }
786     else
787     {
788       constptr[0].option      = ppdFindOption(ppd, oldconst->option1);
789       constptr[0].choice      = ppdFindChoice(constptr[0].option,
790 					      oldconst->choice1);
791       constptr[0].installable = ppd_is_installable(installable,
792 						   oldconst->option1);
793     }
794 
795     if (!constptr[0].option || (!constptr[0].choice && oldconst->choice1[0]))
796     {
797       DEBUG_printf(("8ppd_load_constraints: Unknown option *%s %s!",
798 		    oldconst->option1, oldconst->choice1));
799       free(consts->constraints);
800       free(consts);
801       continue;
802     }
803 
804     if (!_cups_strncasecmp(oldconst->option2, "Custom", 6) &&
805 	!_cups_strcasecmp(oldconst->choice2, "True"))
806     {
807       constptr[1].option      = ppdFindOption(ppd, oldconst->option2 + 6);
808       constptr[1].choice      = ppdFindChoice(constptr[1].option, "Custom");
809       constptr[1].installable = 0;
810     }
811     else
812     {
813       constptr[1].option      = ppdFindOption(ppd, oldconst->option2);
814       constptr[1].choice      = ppdFindChoice(constptr[1].option,
815 					      oldconst->choice2);
816       constptr[1].installable = ppd_is_installable(installable,
817 						   oldconst->option2);
818     }
819 
820     if (!constptr[1].option || (!constptr[1].choice && oldconst->choice2[0]))
821     {
822       DEBUG_printf(("8ppd_load_constraints: Unknown option *%s %s!",
823 		    oldconst->option2, oldconst->choice2));
824       free(consts->constraints);
825       free(consts);
826       continue;
827     }
828 
829     consts->installable = constptr[0].installable || constptr[1].installable;
830 
831    /*
832     * Add it to the constraints array...
833     */
834 
835     cupsArrayAdd(ppd->cups_uiconstraints, consts);
836   }
837 
838  /*
839   * Then load new-style constraints...
840   */
841 
842   for (constattr = ppdFindAttr(ppd, "cupsUIConstraints", NULL);
843        constattr;
844        constattr = ppdFindNextAttr(ppd, "cupsUIConstraints", NULL))
845   {
846     if (!constattr->value)
847     {
848       DEBUG_puts("8ppd_load_constraints: Bad cupsUIConstraints value!");
849       continue;
850     }
851 
852     for (i = 0, vptr = strchr(constattr->value, '*');
853 	 vptr;
854 	 i ++, vptr = strchr(vptr + 1, '*'));
855 
856     if (i == 0)
857     {
858       DEBUG_puts("8ppd_load_constraints: Bad cupsUIConstraints value!");
859       continue;
860     }
861 
862     if ((consts = calloc(1, sizeof(_ppd_cups_uiconsts_t))) == NULL)
863     {
864       DEBUG_puts("8ppd_load_constraints: Unable to allocate memory for "
865 		 "cupsUIConstraints!");
866       return;
867     }
868 
869     if ((constptr = calloc((size_t)i, sizeof(_ppd_cups_uiconst_t))) == NULL)
870     {
871       free(consts);
872       DEBUG_puts("8ppd_load_constraints: Unable to allocate memory for "
873 		 "cupsUIConstraints!");
874       return;
875     }
876 
877     consts->num_constraints = i;
878     consts->constraints     = constptr;
879 
880     strlcpy(consts->resolver, constattr->spec, sizeof(consts->resolver));
881 
882     for (i = 0, vptr = strchr(constattr->value, '*');
883 	 vptr;
884 	 i ++, vptr = strchr(vptr, '*'), constptr ++)
885     {
886      /*
887       * Extract "*Option Choice" or just "*Option"...
888       */
889 
890       for (vptr ++, ptr = option; *vptr && !_cups_isspace(*vptr); vptr ++)
891 	if (ptr < (option + sizeof(option) - 1))
892 	  *ptr++ = *vptr;
893 
894       *ptr = '\0';
895 
896       while (_cups_isspace(*vptr))
897 	vptr ++;
898 
899       if (*vptr == '*')
900 	choice[0] = '\0';
901       else
902       {
903 	for (ptr = choice; *vptr && !_cups_isspace(*vptr); vptr ++)
904 	  if (ptr < (choice + sizeof(choice) - 1))
905 	    *ptr++ = *vptr;
906 
907 	*ptr = '\0';
908       }
909 
910       if (!_cups_strncasecmp(option, "Custom", 6) && !_cups_strcasecmp(choice, "True"))
911       {
912 	_cups_strcpy(option, option + 6);
913 	strlcpy(choice, "Custom", sizeof(choice));
914       }
915 
916       constptr->option      = ppdFindOption(ppd, option);
917       constptr->choice      = ppdFindChoice(constptr->option, choice);
918       constptr->installable = ppd_is_installable(installable, option);
919       consts->installable   |= constptr->installable;
920 
921       if (!constptr->option || (!constptr->choice && choice[0]))
922       {
923 	DEBUG_printf(("8ppd_load_constraints: Unknown option *%s %s!",
924 		      option, choice));
925 	break;
926       }
927     }
928 
929     if (!vptr)
930       cupsArrayAdd(ppd->cups_uiconstraints, consts);
931     else
932     {
933       free(consts->constraints);
934       free(consts);
935     }
936   }
937 }
938 
939 
940 /*
941  * 'ppd_test_constraints()' - See if any constraints are active.
942  */
943 
944 static cups_array_t *			/* O - Array of active constraints */
ppd_test_constraints(ppd_file_t * ppd,const char * option,const char * choice,int num_options,cups_option_t * options,int which)945 ppd_test_constraints(
946     ppd_file_t    *ppd,			/* I - PPD file */
947     const char    *option,		/* I - Current option */
948     const char    *choice,		/* I - Current choice */
949     int           num_options,		/* I - Number of additional options */
950     cups_option_t *options,		/* I - Additional options */
951     int           which)		/* I - Which constraints to test */
952 {
953   int			i;		/* Looping var */
954   _ppd_cups_uiconsts_t	*consts;	/* Current constraints */
955   _ppd_cups_uiconst_t	*constptr;	/* Current constraint */
956   ppd_choice_t		key,		/* Search key */
957 			*marked;	/* Marked choice */
958   cups_array_t		*active = NULL;	/* Active constraints */
959   const char		*value,		/* Current value */
960 			*firstvalue;	/* AP_FIRSTPAGE_Keyword value */
961   char			firstpage[255];	/* AP_FIRSTPAGE_Keyword string */
962 
963 
964   DEBUG_printf(("7ppd_test_constraints(ppd=%p, option=\"%s\", choice=\"%s\", "
965                 "num_options=%d, options=%p, which=%d)", ppd, option, choice,
966 		num_options, options, which));
967 
968   if (!ppd->cups_uiconstraints)
969     ppd_load_constraints(ppd);
970 
971   DEBUG_printf(("9ppd_test_constraints: %d constraints!",
972 	        cupsArrayCount(ppd->cups_uiconstraints)));
973 
974   cupsArraySave(ppd->marked);
975 
976   for (consts = (_ppd_cups_uiconsts_t *)cupsArrayFirst(ppd->cups_uiconstraints);
977        consts;
978        consts = (_ppd_cups_uiconsts_t *)cupsArrayNext(ppd->cups_uiconstraints))
979   {
980     DEBUG_printf(("9ppd_test_constraints: installable=%d, resolver=\"%s\", "
981                   "num_constraints=%d option1=\"%s\", choice1=\"%s\", "
982 		  "option2=\"%s\", choice2=\"%s\", ...",
983 		  consts->installable, consts->resolver, consts->num_constraints,
984 		  consts->constraints[0].option->keyword,
985 		  consts->constraints[0].choice ?
986 		      consts->constraints[0].choice->choice : "",
987 		  consts->constraints[1].option->keyword,
988 		  consts->constraints[1].choice ?
989 		      consts->constraints[1].choice->choice : ""));
990 
991     if (consts->installable && which < _PPD_INSTALLABLE_CONSTRAINTS)
992       continue;				/* Skip installable option constraint */
993 
994     if (!consts->installable && which == _PPD_INSTALLABLE_CONSTRAINTS)
995       continue;				/* Skip non-installable option constraint */
996 
997     if ((which == _PPD_OPTION_CONSTRAINTS || which == _PPD_INSTALLABLE_CONSTRAINTS) && option)
998     {
999      /*
1000       * Skip constraints that do not involve the current option...
1001       */
1002 
1003       for (i = consts->num_constraints, constptr = consts->constraints;
1004 	   i > 0;
1005 	   i --, constptr ++)
1006       {
1007         if (!_cups_strcasecmp(constptr->option->keyword, option))
1008 	  break;
1009 
1010         if (!_cups_strncasecmp(option, "AP_FIRSTPAGE_", 13) &&
1011 	    !_cups_strcasecmp(constptr->option->keyword, option + 13))
1012 	  break;
1013       }
1014 
1015       if (!i)
1016         continue;
1017     }
1018 
1019     DEBUG_puts("9ppd_test_constraints: Testing...");
1020 
1021     for (i = consts->num_constraints, constptr = consts->constraints;
1022          i > 0;
1023 	 i --, constptr ++)
1024     {
1025       DEBUG_printf(("9ppd_test_constraints: %s=%s?", constptr->option->keyword,
1026 		    constptr->choice ? constptr->choice->choice : ""));
1027 
1028       if (constptr->choice &&
1029           (!_cups_strcasecmp(constptr->option->keyword, "PageSize") ||
1030            !_cups_strcasecmp(constptr->option->keyword, "PageRegion")))
1031       {
1032        /*
1033         * PageSize and PageRegion are used depending on the selected input slot
1034 	* and manual feed mode.  Validate against the selected page size instead
1035 	* of an individual option...
1036 	*/
1037 
1038         if (option && choice &&
1039 	    (!_cups_strcasecmp(option, "PageSize") ||
1040 	     !_cups_strcasecmp(option, "PageRegion")))
1041 	{
1042 	  value = choice;
1043         }
1044 	else if ((value = cupsGetOption("PageSize", num_options,
1045 	                                options)) == NULL)
1046 	  if ((value = cupsGetOption("PageRegion", num_options,
1047 	                             options)) == NULL)
1048 	    if ((value = cupsGetOption("media", num_options, options)) == NULL)
1049 	    {
1050 	      ppd_size_t *size = ppdPageSize(ppd, NULL);
1051 
1052               if (size)
1053 	        value = size->name;
1054 	    }
1055 
1056         if (value && !_cups_strncasecmp(value, "Custom.", 7))
1057 	  value = "Custom";
1058 
1059         if (option && choice &&
1060 	    (!_cups_strcasecmp(option, "AP_FIRSTPAGE_PageSize") ||
1061 	     !_cups_strcasecmp(option, "AP_FIRSTPAGE_PageRegion")))
1062 	{
1063 	  firstvalue = choice;
1064         }
1065 	else if ((firstvalue = cupsGetOption("AP_FIRSTPAGE_PageSize",
1066 	                                     num_options, options)) == NULL)
1067 	  firstvalue = cupsGetOption("AP_FIRSTPAGE_PageRegion", num_options,
1068 	                             options);
1069 
1070         if (firstvalue && !_cups_strncasecmp(firstvalue, "Custom.", 7))
1071 	  firstvalue = "Custom";
1072 
1073         if ((!value || _cups_strcasecmp(value, constptr->choice->choice)) &&
1074 	    (!firstvalue || _cups_strcasecmp(firstvalue, constptr->choice->choice)))
1075 	{
1076 	  DEBUG_puts("9ppd_test_constraints: NO");
1077 	  break;
1078 	}
1079       }
1080       else if (constptr->choice)
1081       {
1082        /*
1083         * Compare against the constrained choice...
1084 	*/
1085 
1086         if (option && choice && !_cups_strcasecmp(option, constptr->option->keyword))
1087 	{
1088 	  if (!_cups_strncasecmp(choice, "Custom.", 7))
1089 	    value = "Custom";
1090 	  else
1091 	    value = choice;
1092 	}
1093         else if ((value = cupsGetOption(constptr->option->keyword, num_options,
1094 	                                options)) != NULL)
1095         {
1096 	  if (!_cups_strncasecmp(value, "Custom.", 7))
1097 	    value = "Custom";
1098 	}
1099         else if (constptr->choice->marked)
1100 	  value = constptr->choice->choice;
1101 	else
1102 	  value = NULL;
1103 
1104        /*
1105         * Now check AP_FIRSTPAGE_option...
1106 	*/
1107 
1108         snprintf(firstpage, sizeof(firstpage), "AP_FIRSTPAGE_%s",
1109 	         constptr->option->keyword);
1110 
1111         if (option && choice && !_cups_strcasecmp(option, firstpage))
1112 	{
1113 	  if (!_cups_strncasecmp(choice, "Custom.", 7))
1114 	    firstvalue = "Custom";
1115 	  else
1116 	    firstvalue = choice;
1117 	}
1118         else if ((firstvalue = cupsGetOption(firstpage, num_options,
1119 	                                     options)) != NULL)
1120         {
1121 	  if (!_cups_strncasecmp(firstvalue, "Custom.", 7))
1122 	    firstvalue = "Custom";
1123 	}
1124 	else
1125 	  firstvalue = NULL;
1126 
1127         DEBUG_printf(("9ppd_test_constraints: value=%s, firstvalue=%s", value,
1128 	              firstvalue));
1129 
1130         if ((!value || _cups_strcasecmp(value, constptr->choice->choice)) &&
1131 	    (!firstvalue || _cups_strcasecmp(firstvalue, constptr->choice->choice)))
1132 	{
1133 	  DEBUG_puts("9ppd_test_constraints: NO");
1134 	  break;
1135 	}
1136       }
1137       else if (option && choice &&
1138                !_cups_strcasecmp(option, constptr->option->keyword))
1139       {
1140 	if (!_cups_strcasecmp(choice, "None") || !_cups_strcasecmp(choice, "Off") ||
1141 	    !_cups_strcasecmp(choice, "False"))
1142 	{
1143 	  DEBUG_puts("9ppd_test_constraints: NO");
1144 	  break;
1145 	}
1146       }
1147       else if ((value = cupsGetOption(constptr->option->keyword, num_options,
1148 				      options)) != NULL)
1149       {
1150 	if (!_cups_strcasecmp(value, "None") || !_cups_strcasecmp(value, "Off") ||
1151 	    !_cups_strcasecmp(value, "False"))
1152 	{
1153 	  DEBUG_puts("9ppd_test_constraints: NO");
1154 	  break;
1155 	}
1156       }
1157       else
1158       {
1159 	key.option = constptr->option;
1160 
1161 	if ((marked = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key))
1162 		== NULL ||
1163 	    (!_cups_strcasecmp(marked->choice, "None") ||
1164 	     !_cups_strcasecmp(marked->choice, "Off") ||
1165 	     !_cups_strcasecmp(marked->choice, "False")))
1166 	{
1167 	  DEBUG_puts("9ppd_test_constraints: NO");
1168 	  break;
1169 	}
1170       }
1171     }
1172 
1173     if (i <= 0)
1174     {
1175       if (!active)
1176         active = cupsArrayNew(NULL, NULL);
1177 
1178       cupsArrayAdd(active, consts);
1179       DEBUG_puts("9ppd_test_constraints: Added...");
1180     }
1181   }
1182 
1183   cupsArrayRestore(ppd->marked);
1184 
1185   DEBUG_printf(("8ppd_test_constraints: Found %d active constraints!",
1186                 cupsArrayCount(active)));
1187 
1188   return (active);
1189 }
1190