1 /*
2  * CUPS destination API test program for CUPS.
3  *
4  * Copyright 2012-2017 by Apple Inc.
5  *
6  * These coded instructions, statements, and computer programs are the
7  * property of Apple Inc. and are protected by Federal copyright
8  * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
9  * which should have been included with this file.  If this file is
10  * missing or damaged, see the license at "http://www.cups.org/".
11  *
12  * This file is subject to the Apple OS-Developed Software exception.
13  */
14 
15 /*
16  * Include necessary headers...
17  */
18 
19 #include <stdio.h>
20 #include <errno.h>
21 #include "cups.h"
22 
23 
24 /*
25  * Local functions...
26  */
27 
28 static int	enum_cb(void *user_data, unsigned flags, cups_dest_t *dest);
29 static void	localize(http_t *http, cups_dest_t *dest, cups_dinfo_t *dinfo, const char *option, const char *value);
30 static void	print_file(http_t *http, cups_dest_t *dest, cups_dinfo_t *dinfo, const char *filename, int num_options, cups_option_t *options);
31 static void	show_conflicts(http_t *http, cups_dest_t *dest, cups_dinfo_t *dinfo, int num_options, cups_option_t *options);
32 static void	show_default(http_t *http, cups_dest_t *dest, cups_dinfo_t *dinfo, const char *option);
33 static void	show_media(http_t *http, cups_dest_t *dest, cups_dinfo_t *dinfo, unsigned flags, const char *name);
34 static void	show_supported(http_t *http, cups_dest_t *dest, cups_dinfo_t *dinfo, const char *option, const char *value);
35 static void	usage(const char *arg) __attribute__((noreturn));
36 
37 
38 /*
39  * 'main()' - Main entry.
40  */
41 
42 int					/* O - Exit status */
main(int argc,char * argv[])43 main(int  argc,				/* I - Number of command-line arguments */
44      char *argv[])			/* I - Command-line arguments */
45 {
46   http_t	*http;			/* Connection to destination */
47   cups_dest_t	*dest = NULL;		/* Destination */
48   cups_dinfo_t	*dinfo;			/* Destination info */
49 
50 
51   if (argc < 2)
52     usage(NULL);
53 
54   if (!strcmp(argv[1], "--enum"))
55   {
56     int			i;		/* Looping var */
57     cups_ptype_t	type = 0,	/* Printer type filter */
58 			mask = 0;	/* Printer type mask */
59 
60 
61     for (i = 2; i < argc; i ++)
62     {
63       if (!strcmp(argv[i], "grayscale"))
64       {
65         type |= CUPS_PRINTER_BW;
66 	mask |= CUPS_PRINTER_BW;
67       }
68       else if (!strcmp(argv[i], "color"))
69       {
70         type |= CUPS_PRINTER_COLOR;
71 	mask |= CUPS_PRINTER_COLOR;
72       }
73       else if (!strcmp(argv[i], "duplex"))
74       {
75         type |= CUPS_PRINTER_DUPLEX;
76 	mask |= CUPS_PRINTER_DUPLEX;
77       }
78       else if (!strcmp(argv[i], "staple"))
79       {
80         type |= CUPS_PRINTER_STAPLE;
81 	mask |= CUPS_PRINTER_STAPLE;
82       }
83       else if (!strcmp(argv[i], "small"))
84       {
85         type |= CUPS_PRINTER_SMALL;
86 	mask |= CUPS_PRINTER_SMALL;
87       }
88       else if (!strcmp(argv[i], "medium"))
89       {
90         type |= CUPS_PRINTER_MEDIUM;
91 	mask |= CUPS_PRINTER_MEDIUM;
92       }
93       else if (!strcmp(argv[i], "large"))
94       {
95         type |= CUPS_PRINTER_LARGE;
96 	mask |= CUPS_PRINTER_LARGE;
97       }
98       else
99         usage(argv[i]);
100     }
101 
102     cupsEnumDests(CUPS_DEST_FLAGS_NONE, 5000, NULL, type, mask, enum_cb, NULL);
103 
104     return (0);
105   }
106   else if (!strncmp(argv[1], "ipp://", 6) || !strncmp(argv[1], "ipps://", 7))
107     dest = cupsGetDestWithURI(NULL, argv[1]);
108   else if (!strcmp(argv[1], "default"))
109   {
110     dest = cupsGetNamedDest(CUPS_HTTP_DEFAULT, NULL, NULL);
111     if (dest && dest->instance)
112       printf("default is \"%s/%s\".\n", dest->name, dest->instance);
113     else
114       printf("default is \"%s\".\n", dest->name);
115   }
116   else
117     dest = cupsGetNamedDest(CUPS_HTTP_DEFAULT, argv[1], NULL);
118 
119   if (!dest)
120   {
121     printf("testdest: Unable to get destination \"%s\": %s\n", argv[1], cupsLastErrorString());
122     return (1);
123   }
124 
125   if ((http = cupsConnectDest(dest, CUPS_DEST_FLAGS_NONE, 30000, NULL, NULL, 0, NULL, NULL)) == NULL)
126   {
127     printf("testdest: Unable to connect to destination \"%s\": %s\n", argv[1], cupsLastErrorString());
128     return (1);
129   }
130 
131   if ((dinfo = cupsCopyDestInfo(http, dest)) == NULL)
132   {
133     printf("testdest: Unable to get information for destination \"%s\": %s\n", argv[1], cupsLastErrorString());
134     return (1);
135   }
136 
137   if (argc == 2 || (!strcmp(argv[2], "supported") && argc < 6))
138   {
139     if (argc > 3)
140       show_supported(http, dest, dinfo, argv[3], argv[4]);
141     else if (argc > 2)
142       show_supported(http, dest, dinfo, argv[3], NULL);
143     else
144       show_supported(http, dest, dinfo, NULL, NULL);
145   }
146   else if (!strcmp(argv[2], "conflicts") && argc > 3)
147   {
148     int			i,		/* Looping var */
149 			num_options = 0;/* Number of options */
150     cups_option_t	*options = NULL;/* Options */
151 
152     for (i = 3; i < argc; i ++)
153       num_options = cupsParseOptions(argv[i], num_options, &options);
154 
155     show_conflicts(http, dest, dinfo, num_options, options);
156   }
157   else if (!strcmp(argv[2], "default") && argc == 4)
158   {
159     show_default(http, dest, dinfo, argv[3]);
160   }
161   else if (!strcmp(argv[2], "localize") && argc < 6)
162   {
163     if (argc > 3)
164       localize(http, dest, dinfo, argv[3], argv[4]);
165     else if (argc > 2)
166       localize(http, dest, dinfo, argv[3], NULL);
167     else
168       localize(http, dest, dinfo, NULL, NULL);
169   }
170   else if (!strcmp(argv[2], "media"))
171   {
172     int		i;			/* Looping var */
173     const char	*name = NULL;		/* Media name, if any */
174     unsigned	flags = CUPS_MEDIA_FLAGS_DEFAULT;
175 					/* Media selection flags */
176 
177     for (i = 3; i < argc; i ++)
178     {
179       if (!strcmp(argv[i], "borderless"))
180 	flags = CUPS_MEDIA_FLAGS_BORDERLESS;
181       else if (!strcmp(argv[i], "duplex"))
182 	flags = CUPS_MEDIA_FLAGS_DUPLEX;
183       else if (!strcmp(argv[i], "exact"))
184 	flags = CUPS_MEDIA_FLAGS_EXACT;
185       else if (!strcmp(argv[i], "ready"))
186 	flags = CUPS_MEDIA_FLAGS_READY;
187       else if (name)
188         usage(argv[i]);
189       else
190         name = argv[i];
191     }
192 
193     show_media(http, dest, dinfo, flags, name);
194   }
195   else if (!strcmp(argv[2], "print") && argc > 3)
196   {
197     int			i,		/* Looping var */
198 			num_options = 0;/* Number of options */
199     cups_option_t	*options = NULL;/* Options */
200 
201     for (i = 4; i < argc; i ++)
202       num_options = cupsParseOptions(argv[i], num_options, &options);
203 
204     print_file(http, dest, dinfo, argv[3], num_options, options);
205   }
206   else
207     usage(argv[2]);
208 
209   return (0);
210 }
211 
212 
213 /*
214  * 'enum_cb()' - Print the results from the enumeration of destinations.
215  */
216 
217 static int				/* O - 1 to continue */
enum_cb(void * user_data,unsigned flags,cups_dest_t * dest)218 enum_cb(void        *user_data,		/* I - User data (unused) */
219         unsigned    flags,		/* I - Flags */
220 	cups_dest_t *dest)		/* I - Destination */
221 {
222   int	i;				/* Looping var */
223 
224 
225   (void)user_data;
226   (void)flags;
227 
228   if (dest->instance)
229     printf("%s%s/%s:\n", (flags & CUPS_DEST_FLAGS_REMOVED) ? "REMOVE " : "", dest->name, dest->instance);
230   else
231     printf("%s%s:\n", (flags & CUPS_DEST_FLAGS_REMOVED) ? "REMOVE " : "", dest->name);
232 
233   for (i = 0; i < dest->num_options; i ++)
234     printf("    %s=\"%s\"\n", dest->options[i].name, dest->options[i].value);
235 
236   return (1);
237 }
238 
239 
240 /*
241  * 'localize()' - Localize an option and value.
242  */
243 
244 static void
localize(http_t * http,cups_dest_t * dest,cups_dinfo_t * dinfo,const char * option,const char * value)245 localize(http_t       *http,		/* I - Connection to destination */
246          cups_dest_t  *dest,		/* I - Destination */
247 	 cups_dinfo_t *dinfo,		/* I - Destination information */
248          const char   *option,		/* I - Option */
249 	 const char   *value)		/* I - Value, if any */
250 {
251   ipp_attribute_t	*attr;		/* Attribute */
252   int			i,		/* Looping var */
253 			count;		/* Number of values */
254 
255 
256   if (!option)
257   {
258     attr = cupsFindDestSupported(http, dest, dinfo, "job-creation-attributes");
259     if (attr)
260     {
261       count = ippGetCount(attr);
262       for (i = 0; i < count; i ++)
263         localize(http, dest, dinfo, ippGetString(attr, i, NULL), NULL);
264     }
265     else
266     {
267       static const char * const options[] =
268       {					/* List of standard options */
269         CUPS_COPIES,
270 	CUPS_FINISHINGS,
271 	CUPS_MEDIA,
272 	CUPS_NUMBER_UP,
273 	CUPS_ORIENTATION,
274 	CUPS_PRINT_COLOR_MODE,
275 	CUPS_PRINT_QUALITY,
276 	CUPS_SIDES
277       };
278 
279       puts("No job-creation-attributes-supported attribute, probing instead.");
280 
281       for (i = 0; i < (int)(sizeof(options) / sizeof(options[0])); i ++)
282         if (cupsCheckDestSupported(http, dest, dinfo, options[i], NULL))
283 	  localize(http, dest, dinfo, options[i], NULL);
284     }
285   }
286   else if (!value)
287   {
288     printf("%s (%s)\n", option, cupsLocalizeDestOption(http, dest, dinfo, option));
289 
290     if ((attr = cupsFindDestSupported(http, dest, dinfo, option)) != NULL)
291     {
292       count = ippGetCount(attr);
293 
294       switch (ippGetValueTag(attr))
295       {
296         case IPP_TAG_INTEGER :
297 	    for (i = 0; i < count; i ++)
298               printf("  %d\n", ippGetInteger(attr, i));
299 	    break;
300 
301         case IPP_TAG_ENUM :
302 	    for (i = 0; i < count; i ++)
303               printf("  %s\n", ippEnumString(option, ippGetInteger(attr, i)));
304 	    break;
305 
306         case IPP_TAG_RANGE :
307 	    for (i = 0; i < count; i ++)
308 	    {
309 	      int upper, lower = ippGetRange(attr, i, &upper);
310 
311               printf("  %d-%d\n", lower, upper);
312 	    }
313 	    break;
314 
315         case IPP_TAG_RESOLUTION :
316 	    for (i = 0; i < count; i ++)
317 	    {
318 	      int xres, yres;
319 	      ipp_res_t units;
320 	      xres = ippGetResolution(attr, i, &yres, &units);
321 
322               if (xres == yres)
323                 printf("  %d%s\n", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
324 	      else
325                 printf("  %dx%d%s\n", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
326 	    }
327 	    break;
328 
329 	case IPP_TAG_TEXTLANG :
330 	case IPP_TAG_NAMELANG :
331 	case IPP_TAG_TEXT :
332 	case IPP_TAG_NAME :
333 	case IPP_TAG_KEYWORD :
334 	case IPP_TAG_URI :
335 	case IPP_TAG_URISCHEME :
336 	case IPP_TAG_CHARSET :
337 	case IPP_TAG_LANGUAGE :
338 	case IPP_TAG_MIMETYPE :
339 	    for (i = 0; i < count; i ++)
340               printf("  %s (%s)\n", ippGetString(attr, i, NULL), cupsLocalizeDestValue(http, dest, dinfo, option, ippGetString(attr, i, NULL)));
341 	    break;
342 
343         case IPP_TAG_STRING :
344 	    for (i = 0; i < count; i ++)
345 	    {
346 	      int j, len;
347 	      unsigned char *data = ippGetOctetString(attr, i, &len);
348 
349               fputs("  ", stdout);
350 	      for (j = 0; j < len; j ++)
351 	      {
352 	        if (data[j] < ' ' || data[j] >= 0x7f)
353 		  printf("<%02X>", data[j]);
354 		else
355 		  putchar(data[j]);
356               }
357               putchar('\n');
358 	    }
359 	    break;
360 
361         case IPP_TAG_BOOLEAN :
362 	    break;
363 
364         default :
365 	    printf("  %s\n", ippTagString(ippGetValueTag(attr)));
366 	    break;
367       }
368     }
369 
370   }
371   else
372     puts(cupsLocalizeDestValue(http, dest, dinfo, option, value));
373 }
374 
375 
376 /*
377  * 'print_file()' - Print a file.
378  */
379 
380 static void
print_file(http_t * http,cups_dest_t * dest,cups_dinfo_t * dinfo,const char * filename,int num_options,cups_option_t * options)381 print_file(http_t        *http,		/* I - Connection to destination */
382            cups_dest_t   *dest,		/* I - Destination */
383 	   cups_dinfo_t  *dinfo,	/* I - Destination information */
384            const char    *filename,	/* I - File to print */
385 	   int           num_options,	/* I - Number of options */
386 	   cups_option_t *options)	/* I - Options */
387 {
388   cups_file_t	*fp;			/* File to print */
389   int		job_id;			/* Job ID */
390   ipp_status_t	status;			/* Submission status */
391   const char	*title;			/* Title of job */
392   char		buffer[32768];		/* File buffer */
393   ssize_t	bytes;			/* Bytes read/to write */
394 
395 
396   if ((fp = cupsFileOpen(filename, "r")) == NULL)
397   {
398     printf("Unable to open \"%s\": %s\n", filename, strerror(errno));
399     return;
400   }
401 
402   if ((title = strrchr(filename, '/')) != NULL)
403     title ++;
404   else
405     title = filename;
406 
407   if ((status = cupsCreateDestJob(http, dest, dinfo, &job_id, title, num_options, options)) > IPP_STATUS_OK_IGNORED_OR_SUBSTITUTED)
408   {
409     printf("Unable to create job: %s\n", cupsLastErrorString());
410     cupsFileClose(fp);
411     return;
412   }
413 
414   printf("Created job ID: %d\n", job_id);
415 
416   if (cupsStartDestDocument(http, dest, dinfo, job_id, title, CUPS_FORMAT_AUTO, 0, NULL, 1) != HTTP_STATUS_CONTINUE)
417   {
418     printf("Unable to send document: %s\n", cupsLastErrorString());
419     cupsFileClose(fp);
420     return;
421   }
422 
423   while ((bytes = cupsFileRead(fp, buffer, sizeof(buffer))) > 0)
424   {
425     if (cupsWriteRequestData(http, buffer, (size_t)bytes) != HTTP_STATUS_CONTINUE)
426     {
427       printf("Unable to write document data: %s\n", cupsLastErrorString());
428       break;
429     }
430   }
431 
432   cupsFileClose(fp);
433 
434   if ((status = cupsFinishDestDocument(http, dest, dinfo)) > IPP_STATUS_OK_IGNORED_OR_SUBSTITUTED)
435   {
436     printf("Unable to send document: %s\n", cupsLastErrorString());
437     return;
438   }
439 
440   puts("Job queued.");
441 }
442 
443 
444 /*
445  * 'show_conflicts()' - Show conflicts for selected options.
446  */
447 
448 static void
show_conflicts(http_t * http,cups_dest_t * dest,cups_dinfo_t * dinfo,int num_options,cups_option_t * options)449 show_conflicts(
450     http_t        *http,		/* I - Connection to destination */
451     cups_dest_t   *dest,		/* I - Destination */
452     cups_dinfo_t  *dinfo,		/* I - Destination information */
453     int           num_options,		/* I - Number of options */
454     cups_option_t *options)		/* I - Options */
455 {
456   (void)http;
457   (void)dest;
458   (void)dinfo;
459   (void)num_options;
460   (void)options;
461 }
462 
463 
464 /*
465  * 'show_default()' - Show default value for option.
466  */
467 
468 static void
show_default(http_t * http,cups_dest_t * dest,cups_dinfo_t * dinfo,const char * option)469 show_default(http_t       *http,	/* I - Connection to destination */
470 	     cups_dest_t  *dest,	/* I - Destination */
471 	     cups_dinfo_t *dinfo,	/* I - Destination information */
472 	     const char  *option)	/* I - Option */
473 {
474   if (!strcmp(option, "media"))
475   {
476    /*
477     * Show default media option...
478     */
479 
480     cups_size_t size;                   /* Media size information */
481 
482     if (cupsGetDestMediaDefault(http, dest, dinfo, CUPS_MEDIA_FLAGS_DEFAULT, &size))
483       printf("%s (%.2fx%.2fmm, margins=[%.2f %.2f %.2f %.2f])\n", size.media, size.width * 0.01, size.length * 0.01, size.left * 0.01, size.bottom * 0.01, size.right * 0.01, size.top * 0.01);
484      else
485        puts("FAILED");
486   }
487   else
488   {
489    /*
490     * Show default other option...
491     */
492 
493     ipp_attribute_t *defattr;           /* Default attribute */
494 
495     if ((defattr = cupsFindDestDefault(http, dest, dinfo, option)) != NULL)
496     {
497       char value[1024];                 /* Value of default attribute */
498 
499       ippAttributeString(defattr, value, sizeof(value));
500       puts(value);
501     }
502     else
503       puts("FAILED");
504   }
505 }
506 
507 
508 /*
509  * 'show_media()' - Show available media.
510  */
511 
512 static void
show_media(http_t * http,cups_dest_t * dest,cups_dinfo_t * dinfo,unsigned flags,const char * name)513 show_media(http_t       *http,		/* I - Connection to destination */
514 	   cups_dest_t  *dest,		/* I - Destination */
515 	   cups_dinfo_t *dinfo,		/* I - Destination information */
516 	   unsigned     flags,		/* I - Media flags */
517 	   const char   *name)		/* I - Size name */
518 {
519   int		i,			/* Looping var */
520 		count;			/* Number of sizes */
521   cups_size_t	size;			/* Media size info */
522 
523 
524   if (name)
525   {
526     double	dw, dl;			/* Width and length from name */
527     char	units[32];		/* Units */
528     int		width,			/* Width in 100ths of millimeters */
529 		length;			/* Length in 100ths of millimeters */
530 
531 
532     if (sscanf(name, "%lfx%lf%31s", &dw, &dl, units) == 3)
533     {
534       if (!strcmp(units, "in"))
535       {
536         width  = (int)(dw * 2540.0);
537 	length = (int)(dl * 2540.0);
538       }
539       else if (!strcmp(units, "mm"))
540       {
541         width  = (int)(dw * 100.0);
542         length = (int)(dl * 100.0);
543       }
544       else
545       {
546         puts("  bad units in size");
547 	return;
548       }
549 
550       if (cupsGetDestMediaBySize(http, dest, dinfo, width, length, flags, &size))
551       {
552 	printf("  %s (%s) %dx%d B%d L%d R%d T%d\n", size.media, cupsLocalizeDestMedia(http, dest, dinfo, flags, &size), size.width, size.length, size.bottom, size.left, size.right, size.top);
553       }
554       else
555       {
556 	puts("  not supported");
557       }
558     }
559     else if (cupsGetDestMediaByName(http, dest, dinfo, name, flags, &size))
560     {
561       printf("  %s (%s) %dx%d B%d L%d R%d T%d\n", size.media, cupsLocalizeDestMedia(http, dest, dinfo, flags, &size), size.width, size.length, size.bottom, size.left, size.right, size.top);
562     }
563     else
564     {
565       puts("  not supported");
566     }
567   }
568   else
569   {
570     count = cupsGetDestMediaCount(http, dest, dinfo, flags);
571     printf("%d size%s:\n", count, count == 1 ? "" : "s");
572 
573     for (i = 0; i < count; i ++)
574     {
575       if (cupsGetDestMediaByIndex(http, dest, dinfo, i, flags, &size))
576         printf("  %s (%s) %dx%d B%d L%d R%d T%d\n", size.media, cupsLocalizeDestMedia(http, dest, dinfo, flags, &size), size.width, size.length, size.bottom, size.left, size.right, size.top);
577       else
578         puts("  error");
579     }
580   }
581 }
582 
583 
584 /*
585  * 'show_supported()' - Show supported options, values, etc.
586  */
587 
588 static void
show_supported(http_t * http,cups_dest_t * dest,cups_dinfo_t * dinfo,const char * option,const char * value)589 show_supported(http_t       *http,	/* I - Connection to destination */
590 	       cups_dest_t  *dest,	/* I - Destination */
591 	       cups_dinfo_t *dinfo,	/* I - Destination information */
592 	       const char   *option,	/* I - Option, if any */
593 	       const char   *value)	/* I - Value, if any */
594 {
595   ipp_attribute_t	*attr;		/* Attribute */
596   int			i,		/* Looping var */
597 			count;		/* Number of values */
598 
599 
600   if (!option)
601   {
602     attr = cupsFindDestSupported(http, dest, dinfo, "job-creation-attributes");
603     if (attr)
604     {
605       count = ippGetCount(attr);
606       for (i = 0; i < count; i ++)
607         show_supported(http, dest, dinfo, ippGetString(attr, i, NULL), NULL);
608     }
609     else
610     {
611       static const char * const options[] =
612       {					/* List of standard options */
613         CUPS_COPIES,
614 	CUPS_FINISHINGS,
615 	CUPS_MEDIA,
616 	CUPS_NUMBER_UP,
617 	CUPS_ORIENTATION,
618 	CUPS_PRINT_COLOR_MODE,
619 	CUPS_PRINT_QUALITY,
620 	CUPS_SIDES
621       };
622 
623       puts("No job-creation-attributes-supported attribute, probing instead.");
624 
625       for (i = 0; i < (int)(sizeof(options) / sizeof(options[0])); i ++)
626         if (cupsCheckDestSupported(http, dest, dinfo, options[i], NULL))
627 	  show_supported(http, dest, dinfo, options[i], NULL);
628     }
629   }
630   else if (!value)
631   {
632     printf("%s (%s - %s)\n", option, cupsLocalizeDestOption(http, dest, dinfo, option), cupsCheckDestSupported(http, dest, dinfo, option, NULL) ? "supported" : "not-supported");
633 
634     if ((attr = cupsFindDestSupported(http, dest, dinfo, option)) != NULL)
635     {
636       count = ippGetCount(attr);
637 
638       switch (ippGetValueTag(attr))
639       {
640         case IPP_TAG_INTEGER :
641 	    for (i = 0; i < count; i ++)
642               printf("  %d\n", ippGetInteger(attr, i));
643 	    break;
644 
645         case IPP_TAG_ENUM :
646 	    for (i = 0; i < count; i ++)
647 	    {
648 	      int val = ippGetInteger(attr, i);
649 	      char valstr[256];
650 
651               snprintf(valstr, sizeof(valstr), "%d", val);
652               printf("  %s (%s)\n", ippEnumString(option, ippGetInteger(attr, i)), cupsLocalizeDestValue(http, dest, dinfo, option, valstr));
653             }
654 	    break;
655 
656         case IPP_TAG_RANGE :
657 	    for (i = 0; i < count; i ++)
658 	    {
659 	      int upper, lower = ippGetRange(attr, i, &upper);
660 
661               printf("  %d-%d\n", lower, upper);
662 	    }
663 	    break;
664 
665         case IPP_TAG_RESOLUTION :
666 	    for (i = 0; i < count; i ++)
667 	    {
668 	      int xres, yres;
669 	      ipp_res_t units;
670 	      xres = ippGetResolution(attr, i, &yres, &units);
671 
672               if (xres == yres)
673                 printf("  %d%s\n", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
674 	      else
675                 printf("  %dx%d%s\n", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
676 	    }
677 	    break;
678 
679 	case IPP_TAG_KEYWORD :
680 	    for (i = 0; i < count; i ++)
681               printf("  %s (%s)\n", ippGetString(attr, i, NULL), cupsLocalizeDestValue(http, dest, dinfo, option, ippGetString(attr, i, NULL)));
682 	    break;
683 
684 	case IPP_TAG_TEXTLANG :
685 	case IPP_TAG_NAMELANG :
686 	case IPP_TAG_TEXT :
687 	case IPP_TAG_NAME :
688 	case IPP_TAG_URI :
689 	case IPP_TAG_URISCHEME :
690 	case IPP_TAG_CHARSET :
691 	case IPP_TAG_LANGUAGE :
692 	case IPP_TAG_MIMETYPE :
693 	    for (i = 0; i < count; i ++)
694               printf("  %s\n", ippGetString(attr, i, NULL));
695 	    break;
696 
697         case IPP_TAG_STRING :
698 	    for (i = 0; i < count; i ++)
699 	    {
700 	      int j, len;
701 	      unsigned char *data = ippGetOctetString(attr, i, &len);
702 
703               fputs("  ", stdout);
704 	      for (j = 0; j < len; j ++)
705 	      {
706 	        if (data[j] < ' ' || data[j] >= 0x7f)
707 		  printf("<%02X>", data[j]);
708 		else
709 		  putchar(data[j]);
710               }
711               putchar('\n');
712 	    }
713 	    break;
714 
715         case IPP_TAG_BOOLEAN :
716 	    break;
717 
718         default :
719 	    printf("  %s\n", ippTagString(ippGetValueTag(attr)));
720 	    break;
721       }
722     }
723 
724   }
725   else if (cupsCheckDestSupported(http, dest, dinfo, option, value))
726     puts("YES");
727   else
728     puts("NO");
729 }
730 
731 
732 /*
733  * 'usage()' - Show program usage.
734  */
735 
736 static void
usage(const char * arg)737 usage(const char *arg)			/* I - Argument for usage message */
738 {
739   if (arg)
740     printf("testdest: Unknown option \"%s\".\n", arg);
741 
742   puts("Usage:");
743   puts("  ./testdest name [operation ...]");
744   puts("  ./testdest ipp://... [operation ...]");
745   puts("  ./testdest ipps://... [operation ...]");
746   puts("  ./testdest --enum [grayscale] [color] [duplex] [staple] [small]\n"
747        "                    [medium] [large]");
748   puts("");
749   puts("Operations:");
750   puts("  conflicts options");
751   puts("  default option");
752   puts("  localize option [value]");
753   puts("  media [borderless] [duplex] [exact] [ready] [name or size]");
754   puts("  print filename [options]");
755   puts("  supported [option [value]]");
756 
757   exit(arg != NULL);
758 }
759