1/*
2 * "$Id: xmltotest.c 3650 2012-02-13 18:13:46Z msweet $"
3 *
4 *   IANA XML registration to test file generator for CUPS.
5 *
6 *   Copyright 2011-2012 by Apple Inc.
7 *
8 *   These coded instructions, statements, and computer programs are the
9 *   property of Apple Inc. and are protected by Federal copyright
10 *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
11 *   which should have been included with this file.  If this file is
12 *   file is missing or damaged, see the license at "http://www.cups.org/".
13 *
14 *   This file is subject to the Apple OS-Developed Software exception.
15 *
16 * Usage:
17 *
18 *   ./xmltotest [--ref standard] {--job|--printer} [XML file/URL] >file.test
19 *
20 *   If not specified, loads the XML registrations from:
21 *
22 *     http://www.iana.org/assignments/ipp-registrations/ipp-registrations.xml
23 *
24 *   "Standard" is of the form "rfcNNNN" or "pwgNNNN.N".
25 *
26 * Contents:
27 *
28 *   main()	    - Process command-line arguments.
29 *   compare_reg()  - Compare two registrations.
30 *   load_xml()     - Load the XML registration file or URL.
31 *   match_xref()   - Compare the xref against the named standard.
32 *   new_reg()	    - Create a new registration record.
33 *   usage()	    - Show usage message.
34 *   write_expect() - Write an EXPECT test for an attribute.
35 */
36
37
38#include <config.h>
39#include <cups/cups.h>
40#include <unistd.h>
41#include <fcntl.h>
42
43#ifdef HAVE_MXML_H
44#  include <mxml.h>
45/*
46 * Local types...
47 */
48
49typedef struct _cups_reg_s		/**** Registration data ****/
50{
51  char	*name,				/* Attribute name */
52	*member,			/* Member attribute name */
53	*sub_member,			/* Sub-member attribute name */
54	*syntax;			/* Attribute syntax */
55} _cups_reg_t;
56
57
58/*
59 * Local functions...
60 */
61
62static int		compare_reg(_cups_reg_t *a, _cups_reg_t *b);
63static mxml_node_t	*load_xml(const char *reg_file);
64static int		match_xref(mxml_node_t *xref, const char *standard);
65static _cups_reg_t	*new_reg(mxml_node_t *name, mxml_node_t *member,
66			         mxml_node_t *sub_member, mxml_node_t *syntax);
67static int		usage(void);
68static void		write_expect(_cups_reg_t *reg, ipp_tag_t group);
69
70
71/*
72 * 'main()' - Process command-line arguments.
73 */
74
75int
76main(int  argc,				/* I - Number of command-line args */
77     char *argv[])			/* I - Command-line arguments */
78{
79  int		i;			/* Looping var */
80  const char	*reg_file = NULL,	/* Registration file/URL to use */
81		*reg_standard = NULL;	/* Which standard to extract */
82  mxml_node_t	*reg_xml,		/* Registration XML data */
83		*reg_2,			/* ipp-registrations-2 */
84		*reg_record,		/* <record> */
85		*reg_collection,	/* <collection> */
86		*reg_name,		/* <name> */
87		*reg_member,		/* <member_attribute> */
88		*reg_sub_member,	/* <sub-member_attribute> */
89		*reg_syntax,		/* <syntax> */
90		*reg_xref;		/* <xref> */
91  cups_array_t	*attrs;			/* Attribute registrations */
92  _cups_reg_t	*current;		/* Current attribute registration */
93  ipp_tag_t	group = IPP_TAG_ZERO,	/* Which attributes to test */
94		reg_group;		/* Group for registration */
95
96
97 /*
98  * Parse command-line...
99  */
100
101  for (i = 1; i < argc; i ++)
102  {
103    if (!strcmp(argv[i], "--job") && group == IPP_TAG_ZERO)
104      group = IPP_TAG_JOB;
105    else if (!strcmp(argv[i], "--ref"))
106    {
107      i ++;
108      if (i >= argc)
109        return (usage());
110
111      reg_standard = argv[i];
112    }
113    else if (!strcmp(argv[i], "--printer") && group == IPP_TAG_ZERO)
114      group = IPP_TAG_PRINTER;
115    else if (argv[i][0] == '-' || reg_file)
116      return (usage());
117    else
118      reg_file = argv[i];
119  }
120
121  if (group == IPP_TAG_ZERO)
122    return (usage());
123
124 /*
125  * Read registrations...
126  */
127
128  if (!reg_file)
129    reg_file = "http://www.iana.org/assignments/ipp-registrations/"
130	       "ipp-registrations.xml";
131
132  if ((reg_xml = load_xml(reg_file)) == NULL)
133    return (1);
134
135 /*
136  * Scan registrations for attributes...
137  */
138
139  if ((reg_2 = mxmlFindElement(reg_xml, reg_xml, "registry", "id",
140                               "ipp-registrations-2",
141                               MXML_DESCEND)) == NULL)
142  {
143    fprintf(stderr, "xmltotest: No IPP attribute registrations in \"%s\".\n",
144            reg_file);
145    return (1);
146  }
147
148  attrs = cupsArrayNew((cups_array_func_t)compare_reg, NULL);
149
150  for (reg_record = mxmlFindElement(reg_2, reg_2, "record", NULL, NULL,
151				    MXML_DESCEND);
152       reg_record;
153       reg_record = mxmlFindElement(reg_record, reg_2, "record", NULL, NULL,
154                                    MXML_NO_DESCEND))
155  {
156   /*
157    * Get the values from the current record...
158    */
159
160    reg_collection = mxmlFindElement(reg_record, reg_record, "collection",
161                                     NULL, NULL, MXML_DESCEND);
162    reg_name       = mxmlFindElement(reg_record, reg_record, "name", NULL, NULL,
163                                     MXML_DESCEND);
164    reg_member     = mxmlFindElement(reg_record, reg_record, "member_attribute",
165                                     NULL, NULL, MXML_DESCEND);
166    reg_sub_member = mxmlFindElement(reg_record, reg_record,
167                                     "sub-member_attribute", NULL, NULL,
168                                     MXML_DESCEND);
169    reg_syntax     = mxmlFindElement(reg_record, reg_record, "syntax", NULL,
170                                     NULL, MXML_DESCEND);
171    reg_xref       = mxmlFindElement(reg_record, reg_record, "xref", NULL, NULL,
172                                     MXML_DESCEND);
173
174    if (!reg_collection || !reg_name || !reg_syntax || !reg_xref)
175      continue;
176
177   /*
178    * Filter based on group and standard...
179    */
180
181    if (!strcmp(reg_collection->child->value.opaque, "Printer Description"))
182      reg_group = IPP_TAG_PRINTER;
183    else if (!strcmp(reg_collection->child->value.opaque, "Job Description"))
184      reg_group = IPP_TAG_JOB;
185    else if (!strcmp(reg_collection->child->value.opaque, "Job Template"))
186    {
187      if (strstr(reg_name->child->value.opaque, "-default") ||
188          strstr(reg_name->child->value.opaque, "-supported"))
189	reg_group = IPP_TAG_PRINTER;
190      else
191	reg_group = IPP_TAG_JOB;
192    }
193    else
194      reg_group = IPP_TAG_ZERO;
195
196    if (reg_group != group)
197      continue;
198
199    if (reg_standard && !match_xref(reg_xref, reg_standard))
200      continue;
201
202   /*
203    * Add the record to the array...
204    */
205
206    if ((current = new_reg(reg_name, reg_member, reg_sub_member,
207                           reg_syntax)) != NULL)
208      cupsArrayAdd(attrs, current);
209  }
210
211 /*
212  * Write out a test for all of the selected attributes...
213  */
214
215  puts("{");
216
217  if (group == IPP_TAG_PRINTER)
218  {
219    puts("\tOPERATION Get-Printer-Attributes");
220    puts("\tGROUP operation-attributes-tag");
221    puts("\tATTR charset attributes-charset utf-8");
222    puts("\tATTR naturalLanguage attributes-natural-language en");
223    puts("\tATTR uri printer-uri $uri");
224    puts("\tATTR name requesting-user-name $user");
225    puts("\tATTR keyword requested-attributes all,media-col-database");
226    puts("");
227    puts("\tSTATUS successful-ok");
228    puts("\tSTATUS successful-ok-ignored-or-substituted-attributes");
229    puts("");
230  }
231  else
232  {
233    puts("\tOPERATION Get-Job-Attributes");
234    puts("\tGROUP operation-attributes-tag");
235    puts("\tATTR charset attributes-charset utf-8");
236    puts("\tATTR naturalLanguage attributes-natural-language en");
237    puts("\tATTR uri printer-uri $uri");
238    puts("\tATTR integer job-id $job-id");
239    puts("\tATTR name requesting-user-name $user");
240    puts("");
241    puts("\tSTATUS successful-ok");
242    puts("");
243  }
244
245  for (current = cupsArrayFirst(attrs);
246       current;
247       current = cupsArrayNext(attrs))
248    write_expect(current, group);
249
250  puts("}");
251
252  return (0);
253}
254
255
256/*
257 * 'compare_reg()' - Compare two registrations.
258 */
259
260static int				/* O - Result of comparison */
261compare_reg(_cups_reg_t *a,		/* I - First registration */
262            _cups_reg_t *b)		/* I - Second registration */
263{
264  int	retval;				/* Return value */
265
266
267  if ((retval = strcmp(a->name, b->name)) != 0)
268    return (retval);
269
270  if (a->member && b->member)
271    retval = strcmp(a->member, b->member);
272  else if (a->member)
273    retval = 1;
274  else if (b->member)
275    retval = -1;
276
277  if (retval)
278    return (retval);
279
280  if (a->sub_member && b->sub_member)
281    retval = strcmp(a->sub_member, b->sub_member);
282  else if (a->sub_member)
283    retval = 1;
284  else if (b->sub_member)
285    retval = -1;
286
287  return (retval);
288}
289
290
291/*
292 * 'load_xml()' - Load the XML registration file or URL.
293 */
294
295static mxml_node_t *			/* O - XML file or NULL */
296load_xml(const char *reg_file)		/* I - Filename or URL */
297{
298  mxml_node_t		*xml;		/* XML file */
299  char			scheme[256],	/* Scheme */
300			userpass[256],	/* Username and password */
301			hostname[256],	/* Hostname */
302			resource[1024],	/* Resource path */
303			filename[1024];	/* Temporary file */
304  int			port,		/* Port number */
305			fd;		/* File descriptor */
306
307
308  if (httpSeparateURI(HTTP_URI_CODING_ALL, reg_file, scheme, sizeof(scheme),
309                      userpass, sizeof(userpass), hostname, sizeof(hostname),
310                      &port, resource, sizeof(resource)) < HTTP_URI_OK)
311  {
312    fprintf(stderr, "xmltotest: Bad URI or filename \"%s\".\n", reg_file);
313    return (NULL);
314  }
315
316  if (!strcmp(scheme, "file"))
317  {
318   /*
319    * Local file...
320    */
321
322    if ((fd = open(resource, O_RDONLY)) < 0)
323    {
324      fprintf(stderr, "xmltotest: Unable to open \"%s\": %s\n", resource,
325              strerror(errno));
326      return (NULL);
327    }
328
329    filename[0] = '\0';
330  }
331  else if (strcmp(scheme, "http") && strcmp(scheme, "https"))
332  {
333    fprintf(stderr, "xmltotest: Unsupported URI scheme \"%s\".\n", scheme);
334    return (NULL);
335  }
336  else
337  {
338    http_t		*http;		/* HTTP connection */
339    http_encryption_t	encryption;	/* Encryption to use */
340    http_status_t	status;		/* Status of HTTP GET */
341
342    if (!strcmp(scheme, "https") || port == 443)
343      encryption = HTTP_ENCRYPT_ALWAYS;
344    else
345      encryption = HTTP_ENCRYPT_IF_REQUESTED;
346
347    if ((http = httpConnectEncrypt(hostname, port, encryption)) == NULL)
348    {
349      fprintf(stderr, "xmltotest: Unable to connect to \"%s\": %s\n", hostname,
350              cupsLastErrorString());
351      return (NULL);
352    }
353
354    if ((fd = cupsTempFd(filename, sizeof(filename))) < 0)
355    {
356      fprintf(stderr, "xmltotest: Unable to create temporary file: %s\n",
357              strerror(errno));
358      httpClose(http);
359      return (NULL);
360    }
361
362    status = cupsGetFd(http, resource, fd);
363    httpClose(http);
364
365    if (status != HTTP_OK)
366    {
367      fprintf(stderr, "mxmltotest: Unable to get \"%s\": %d\n", reg_file,
368              status);
369      close(fd);
370      unlink(filename);
371      return (NULL);
372    }
373
374    lseek(fd, 0, SEEK_SET);
375  }
376
377 /*
378  * Load the XML file...
379  */
380
381  xml = mxmlLoadFd(NULL, fd, MXML_OPAQUE_CALLBACK);
382
383  close(fd);
384
385  if (filename[0])
386    unlink(filename);
387
388  return (xml);
389}
390
391
392/*
393 * 'match_xref()' - Compare the xref against the named standard.
394 */
395
396static int				/* O - 1 if match, 0 if not */
397match_xref(mxml_node_t *xref,		/* I - <xref> node */
398           const char  *standard)	/* I - Name of standard */
399{
400  const char	*data;			/* "data" attribute */
401  char		s[256];			/* String to look for */
402
403
404  if ((data = mxmlElementGetAttr(xref, "data")) == NULL)
405    return (1);
406
407  if (!strcmp(data, standard))
408    return (1);
409
410  if (!strncmp(standard, "pwg", 3))
411  {
412    snprintf(s, sizeof(s), "-%s.pdf", standard + 3);
413    return (strstr(data, s) != NULL);
414  }
415  else
416    return (0);
417}
418
419
420/*
421 * 'new_reg()' - Create a new registration record.
422 */
423
424static _cups_reg_t *			/* O - New record */
425new_reg(mxml_node_t *name,		/* I - Attribute name */
426        mxml_node_t *member,		/* I - Member attribute, if any */
427        mxml_node_t *sub_member,	/* I - Sub-member attribute, if any */
428        mxml_node_t *syntax)		/* I - Syntax */
429{
430  _cups_reg_t	*reg;			/* New record */
431
432
433  if ((reg = calloc(1, sizeof(_cups_reg_t))) != NULL)
434  {
435    reg->name   = name->child->value.opaque;
436    reg->syntax = syntax->child->value.opaque;
437
438    if (member)
439      reg->member = member->child->value.opaque;
440
441    if (sub_member)
442      reg->sub_member = sub_member->child->value.opaque;
443  }
444
445  return (reg);
446}
447
448
449/*
450 * 'usage()' - Show usage message.
451 */
452
453static int				/* O - Exit status */
454usage(void)
455{
456  puts("Usage ./xmltotest [--ref standard] {--job|--printer} [XML file/URL] "
457       ">file.test");
458  return (1);
459}
460
461
462/*
463 * 'write_expect()' - Write an EXPECT test for an attribute.
464 */
465
466static void
467write_expect(_cups_reg_t *reg,		/* I - Registration information */
468             ipp_tag_t   group)		/* I - Attribute group tag */
469{
470  const char	*syntax;		/* Pointer into syntax string */
471  int		single = 1,		/* Single valued? */
472		skip = 0;		/* Skip characters? */
473
474
475  printf("\tEXPECT ?%s OF-TYPE ", reg->name);
476
477  syntax = reg->syntax;
478
479  while (*syntax)
480  {
481    if (!strncmp(syntax, "1setOf", 6))
482    {
483      single = 0;
484      syntax += 6;
485
486      while (isspace(*syntax & 255))
487        syntax ++;
488
489      if (*syntax == '(')
490        syntax ++;
491    }
492    else if (!strncmp(syntax, "type1", 5) || !strncmp(syntax, "type2", 5) ||
493             !strncmp(syntax, "type3", 5))
494      syntax += 5;
495    else if (*syntax == '(')
496    {
497      skip = 1;
498      syntax ++;
499    }
500    else if (*syntax == ')')
501    {
502      skip = 0;
503      syntax ++;
504    }
505    else if (!skip && (*syntax == '|' || isalpha(*syntax & 255)))
506      putchar(*syntax++);
507    else
508      syntax ++;
509  }
510
511  if (single)
512    printf(" IN-GROUP %s COUNT 1\n", ippTagString(group));
513  else
514    printf(" IN-GROUP %s\n", ippTagString(group));
515}
516
517
518#else /* !HAVE_MXML */
519int
520main(void)
521{
522  return (1);
523}
524#endif /* HAVE_MXML */
525
526
527/*
528 * End of "$Id: xmltotest.c 3650 2012-02-13 18:13:46Z msweet $".
529 */
530