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