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