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