]> git.ipfire.org Git - thirdparty/cups.git/blame - cgi-bin/ipp-var.c
Merge changes from CUPS 1.4svn-r8227.
[thirdparty/cups.git] / cgi-bin / ipp-var.c
CommitLineData
ef416fc2 1/*
b19ccc9e 2 * "$Id: ipp-var.c 7940 2008-09-16 00:45:16Z mike $"
ef416fc2 3 *
4 * CGI <-> IPP variable routines for the Common UNIX Printing System (CUPS).
5 *
91c84a35 6 * Copyright 2007-2008 by Apple Inc.
f7deaa1a 7 * Copyright 1997-2007 by Easy Software Products.
ef416fc2 8 *
9 * These coded instructions, statements, and computer programs are the
bc44d920 10 * property of Apple Inc. and are protected by Federal copyright
11 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
12 * which should have been included with this file. If this file is
13 * file is missing or damaged, see the license at "http://www.cups.org/".
ef416fc2 14 *
15 * Contents:
16 *
58dc1933
MS
17 * cgiGetAttributes() - Get the list of attributes that are needed by the
18 * template file.
fa73b229 19 * cgiGetIPPObjects() - Get the objects in an IPP response.
20 * cgiMoveJobs() - Move one or more jobs.
58dc1933 21 * cgiPrintCommand() - Print a CUPS command job.
fa73b229 22 * cgiPrintTestPage() - Print a test page.
ef416fc2 23 * cgiRewriteURL() - Rewrite a printer URI into a web browser URL...
24 * cgiSetIPPObjectVars() - Set CGI variables from an IPP object.
25 * cgiSetIPPVars() - Set CGI variables from an IPP response.
fa73b229 26 * cgiShowIPPError() - Show the last IPP error message.
27 * cgiShowJobs() - Show print jobs.
28 * cgiText() - Return localized text.
ef416fc2 29 */
30
31/*
32 * Include necessary headers...
33 */
34
35#include "cgi-private.h"
36
37
38/*
39 * 'cgiGetAttributes()' - Get the list of attributes that are needed
40 * by the template file.
41 */
42
43void
44cgiGetAttributes(ipp_t *request, /* I - IPP request */
45 const char *tmpl) /* I - Base filename */
46{
47 int num_attrs; /* Number of attributes */
48 char *attrs[1000]; /* Attributes */
49 int i; /* Looping var */
50 char filename[1024], /* Filename */
51 locale[16]; /* Locale name */
52 const char *directory, /* Directory */
53 *lang; /* Language */
54 FILE *in; /* Input file */
55 int ch; /* Character from file */
56 char name[255], /* Name of variable */
57 *nameptr; /* Pointer into name */
58
59
60 /*
61 * Convert the language to a locale name...
62 */
63
64 if ((lang = getenv("LANG")) != NULL)
65 {
66 for (i = 0; lang[i] && i < 15; i ++)
67 if (isalnum(lang[i] & 255))
68 locale[i] = tolower(lang[i]);
69 else
70 locale[i] = '_';
71
72 locale[i] = '\0';
73 }
74 else
75 locale[0] = '\0';
76
77 /*
78 * See if we have a template file for this language...
79 */
80
81 directory = cgiGetTemplateDir();
82
83 snprintf(filename, sizeof(filename), "%s/%s/%s", directory, locale, tmpl);
84 if (access(filename, 0))
85 {
86 locale[2] = '\0';
87
88 snprintf(filename, sizeof(filename), "%s/%s/%s", directory, locale, tmpl);
89 if (access(filename, 0))
90 snprintf(filename, sizeof(filename), "%s/%s", directory, tmpl);
91 }
92
93 /*
94 * Open the template file...
95 */
96
97 if ((in = fopen(filename, "r")) == NULL)
98 return;
99
100 /*
101 * Loop through the file adding attribute names as needed...
102 */
103
104 num_attrs = 0;
d09495fa 105 attrs[0] = NULL; /* Eliminate compiler warning */
ef416fc2 106
107 while ((ch = getc(in)) != EOF)
108 if (ch == '\\')
109 getc(in);
110 else if (ch == '{' && num_attrs < (sizeof(attrs) / sizeof(attrs[0])))
111 {
112 /*
113 * Grab the name...
114 */
115
116 for (nameptr = name; (ch = getc(in)) != EOF;)
58dc1933 117 if (strchr("}]<>=!~ \t\n", ch))
ef416fc2 118 break;
119 else if (nameptr > name && ch == '?')
120 break;
121 else if (nameptr < (name + sizeof(name) - 1))
122 {
123 if (ch == '_')
124 *nameptr++ = '-';
125 else
126 *nameptr++ = ch;
127 }
128
129 *nameptr = '\0';
130
131 if (!strncmp(name, "printer_state_history", 21))
132 strcpy(name, "printer_state_history");
133
134 /*
135 * Possibly add it to the list of attributes...
136 */
137
138 for (i = 0; i < num_attrs; i ++)
139 if (!strcmp(attrs[i], name))
140 break;
141
142 if (i >= num_attrs)
143 {
144 attrs[num_attrs] = strdup(name);
145 num_attrs ++;
146 }
147 }
148
149 /*
150 * If we have attributes, add a requested-attributes attribute to the
151 * request...
152 */
153
154 if (num_attrs > 0)
155 {
156 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
157 "requested-attributes", num_attrs, NULL, (const char **)attrs);
158
159 for (i = 0; i < num_attrs; i ++)
160 free(attrs[i]);
161 }
91c84a35
MS
162
163 fclose(in);
ef416fc2 164}
165
166
167/*
fa73b229 168 * 'cgiGetIPPObjects()' - Get the objects in an IPP response.
ef416fc2 169 */
170
171cups_array_t * /* O - Array of objects */
172cgiGetIPPObjects(ipp_t *response, /* I - IPP response */
173 void *search) /* I - Search filter */
174{
175 int i; /* Looping var */
176 cups_array_t *objs; /* Array of objects */
177 ipp_attribute_t *attr, /* Current attribute */
178 *first; /* First attribute for object */
179 ipp_tag_t group; /* Current group tag */
180 int add; /* Add this object to the array? */
181
182
183 if (!response)
184 return (0);
185
186 for (add = 0, first = NULL, objs = cupsArrayNew(NULL, NULL),
187 group = IPP_TAG_ZERO, attr = response->attrs;
188 attr;
189 attr = attr->next)
190 {
191 if (attr->group_tag != group)
192 {
193 group = attr->group_tag;
194
195 if (group != IPP_TAG_ZERO && group != IPP_TAG_OPERATION)
196 {
197 first = attr;
198 add = 0;
199 }
200 else if (add && first)
201 {
202 cupsArrayAdd(objs, first);
203
204 add = 0;
205 first = NULL;
206 }
207 }
208
209 if (attr->name && attr->group_tag != IPP_TAG_OPERATION && !add)
210 {
211 if (!search)
212 {
213 /*
214 * Add all objects if there is no search...
215 */
216
217 add = 1;
218 }
219 else
220 {
221 /*
222 * Check the search string against the string and integer values.
223 */
224
225 switch (attr->value_tag)
226 {
227 case IPP_TAG_TEXTLANG :
228 case IPP_TAG_NAMELANG :
229 case IPP_TAG_TEXT :
230 case IPP_TAG_NAME :
231 case IPP_TAG_KEYWORD :
232 case IPP_TAG_URI :
233 case IPP_TAG_MIMETYPE :
234 for (i = 0; !add && i < attr->num_values; i ++)
235 if (cgiDoSearch(search, attr->values[i].string.text))
236 add = 1;
237 break;
238
239 case IPP_TAG_INTEGER :
240 for (i = 0; !add && i < attr->num_values; i ++)
241 {
242 char buf[255]; /* Number buffer */
243
244
245 sprintf(buf, "%d", attr->values[i].integer);
246
247 if (cgiDoSearch(search, buf))
248 add = 1;
249 }
250 break;
251
252 default :
253 break;
254 }
255 }
256 }
257 }
258
259 if (add && first)
260 cupsArrayAdd(objs, first);
261
262 return (objs);
263}
264
265
fa73b229 266/*
267 * 'cgiMoveJobs()' - Move one or more jobs.
268 *
269 * At least one of dest or job_id must be non-zero/NULL.
270 */
271
272void
273cgiMoveJobs(http_t *http, /* I - Connection to server */
274 const char *dest, /* I - Destination or NULL */
275 int job_id) /* I - Job ID or 0 for all */
276{
277 int i; /* Looping var */
278 const char *user; /* Username */
279 ipp_t *request, /* IPP request */
280 *response; /* IPP response */
281 ipp_attribute_t *attr; /* Current attribute */
282 const char *name; /* Destination name */
283 const char *job_printer_uri; /* JOB_PRINTER_URI form variable */
284 char current_dest[1024]; /* Current destination */
285
286
287 /*
288 * See who is logged in...
289 */
290
291 if ((user = getenv("REMOTE_USER")) == NULL)
292 user = "guest";
293
294 /*
295 * See if the user has already selected a new destination...
296 */
297
298 if ((job_printer_uri = cgiGetVariable("JOB_PRINTER_URI")) == NULL)
299 {
300 /*
301 * Make sure necessary form variables are set...
302 */
303
304 if (job_id)
305 {
306 char temp[255]; /* Temporary string */
307
308
309 sprintf(temp, "%d", job_id);
310 cgiSetVariable("JOB_ID", temp);
311 }
312
313 if (dest)
314 cgiSetVariable("PRINTER_NAME", dest);
315
316 /*
317 * No new destination specified, show the user what the available
318 * printers/classes are...
319 */
320
321 if (!dest)
322 {
323 /*
324 * Get the current destination for job N...
325 */
326
327 char job_uri[1024]; /* Job URI */
328
329
330 request = ippNewRequest(IPP_GET_JOB_ATTRIBUTES);
331
332 snprintf(job_uri, sizeof(job_uri), "ipp://localhost/jobs/%d", job_id);
333 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri",
334 NULL, job_uri);
335 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
336 "requested-attributes", NULL, "job-printer-uri");
337
338 if ((response = cupsDoRequest(http, request, "/")) != NULL)
339 {
340 if ((attr = ippFindAttribute(response, "job-printer-uri",
341 IPP_TAG_URI)) != NULL)
342 {
343 /*
344 * Pull the name from the URI...
345 */
346
347 strlcpy(current_dest, strrchr(attr->values[0].string.text, '/') + 1,
348 sizeof(current_dest));
349 dest = current_dest;
350 }
351
352 ippDelete(response);
353 }
354
355 if (!dest)
356 {
357 /*
358 * Couldn't get the current destination...
359 */
360
361 cgiStartHTML(cgiText(_("Move Job")));
362 cgiShowIPPError(_("Unable to find destination for job!"));
363 cgiEndHTML();
364 return;
365 }
366 }
367
368 /*
369 * Get the list of available destinations...
370 */
371
372 request = ippNewRequest(CUPS_GET_PRINTERS);
373
374 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
375 "requested-attributes", NULL, "printer-uri-supported");
376
377 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
378 "requesting-user-name", NULL, user);
379
380 if ((response = cupsDoRequest(http, request, "/")) != NULL)
381 {
382 for (i = 0, attr = ippFindAttribute(response, "printer-uri-supported",
383 IPP_TAG_URI);
384 attr;
385 attr = ippFindNextAttribute(response, "printer-uri-supported",
386 IPP_TAG_URI))
387 {
388 /*
389 * Pull the name from the URI...
390 */
391
392 name = strrchr(attr->values[0].string.text, '/') + 1;
393
394 /*
395 * If the name is not the same as the current destination, add it!
396 */
397
398 if (strcasecmp(name, dest))
399 {
400 cgiSetArray("JOB_PRINTER_URI", i, attr->values[0].string.text);
401 cgiSetArray("JOB_PRINTER_NAME", i, name);
402 i ++;
403 }
404 }
405
406 ippDelete(response);
407 }
408
409 /*
410 * Show the form...
411 */
412
413 if (job_id)
414 cgiStartHTML(cgiText(_("Move Job")));
415 else
416 cgiStartHTML(cgiText(_("Move All Jobs")));
417
418 cgiCopyTemplateLang("job-move.tmpl");
419 }
420 else
421 {
422 /*
423 * Try moving the job or jobs...
424 */
425
426 char uri[1024], /* Job/printer URI */
427 resource[1024], /* Post resource */
428 refresh[1024]; /* Refresh URL */
429 const char *job_printer_name; /* New printer name */
430
431
432 request = ippNewRequest(CUPS_MOVE_JOB);
433
434 if (job_id)
435 {
436 /*
437 * Move 1 job...
438 */
439
440 snprintf(resource, sizeof(resource), "/jobs/%d", job_id);
441
442 snprintf(uri, sizeof(uri), "ipp://localhost/jobs/%d", job_id);
443 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri",
444 NULL, uri);
445 }
446 else
447 {
448 /*
449 * Move all active jobs on a destination...
450 */
451
452 snprintf(resource, sizeof(resource), "/%s/%s",
453 cgiGetVariable("SECTION"), dest);
454
a4d04587 455 httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
456 "localhost", ippPort(), "/%s/%s",
457 cgiGetVariable("SECTION"), dest);
fa73b229 458 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
459 NULL, uri);
460 }
461
462 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-printer-uri",
463 NULL, job_printer_uri);
464
465 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
466 "requesting-user-name", NULL, user);
467
468 ippDelete(cupsDoRequest(http, request, resource));
469
470 /*
471 * Show the results...
472 */
473
474 job_printer_name = strrchr(job_printer_uri, '/') + 1;
475
476 if (cupsLastError() <= IPP_OK_CONFLICT)
477 {
a0f6818e
MS
478 const char *path = strstr(job_printer_uri, "/printers/");
479 if (!path)
480 path = strstr(job_printer_uri, "/classes/");
481
482 if (path)
483 {
484 cgiFormEncode(uri, path, sizeof(uri));
485 snprintf(refresh, sizeof(refresh), "2;URL=%s", uri);
486 cgiSetVariable("refresh_page", refresh);
487 }
fa73b229 488 }
489
490 if (job_id)
491 cgiStartHTML(cgiText(_("Move Job")));
492 else
493 cgiStartHTML(cgiText(_("Move All Jobs")));
494
495 if (cupsLastError() > IPP_OK_CONFLICT)
496 {
497 if (job_id)
498 cgiShowIPPError(_("Unable to move job"));
499 else
500 cgiShowIPPError(_("Unable to move jobs"));
501 }
502 else
503 {
504 cgiSetVariable("JOB_PRINTER_NAME", job_printer_name);
505 cgiCopyTemplateLang("job-moved.tmpl");
506 }
507 }
508
509 cgiEndHTML();
510}
511
512
58dc1933
MS
513/*
514 * 'cgiPrintCommand()' - Print a CUPS command job.
515 */
516
517void
518cgiPrintCommand(http_t *http, /* I - Connection to server */
519 const char *dest, /* I - Destination printer */
520 const char *command, /* I - Command to send */
521 const char *title) /* I - Page/job title */
522{
523 int job_id; /* Command file job */
524 char uri[HTTP_MAX_URI], /* Job URI */
525 resource[1024], /* Printer resource path */
526 refresh[1024], /* Refresh URL */
527 command_file[1024]; /* Command "file" */
528 http_status_t status; /* Document status */
529 cups_option_t hold_option; /* job-hold-until option */
530 const char *user; /* User name */
531 ipp_t *request, /* Get-Job-Attributes request */
532 *response; /* Get-Job-Attributes response */
533 ipp_attribute_t *attr; /* Current job attribute */
534 static const char const *job_attrs[] =/* Job attributes we want */
535 {
536 "job-state",
537 "job-printer-state-message"
538 };
539
540
541 /*
542 * Create the CUPS command file...
543 */
544
545 snprintf(command_file, sizeof(command_file), "#CUPS-COMMAND\n%s\n", command);
546
547 /*
548 * Show status...
549 */
550
551 cgiStartMultipart();
552 cgiStartHTML(title);
553 cgiCopyTemplateLang("command.tmpl");
554 cgiEndHTML();
555 fflush(stdout);
556
557 /*
558 * Send the command file job...
559 */
560
561 hold_option.name = "job-hold-until";
562 hold_option.value = "no-hold";
563
564 if ((user = getenv("REMOTE_USER")) != NULL)
565 cupsSetUser(user);
566 else
567 cupsSetUser("anonymous");
568
569 if ((job_id = cupsCreateJob(http, dest, title,
570 1, &hold_option)) < 1)
571 {
572 cgiSetVariable("MESSAGE", cgiText(_("Unable to send command to printer driver!")));
573 cgiSetVariable("ERROR", cupsLastErrorString());
574 cgiStartHTML(title);
575 cgiCopyTemplateLang("error.tmpl");
576 cgiEndHTML();
577 cgiEndMultipart();
578 return;
579 }
580
581 status = cupsStartDocument(http, dest, job_id, NULL, CUPS_FORMAT_COMMAND, 1);
582 if (status == HTTP_CONTINUE)
583 status = cupsWriteRequestData(http, command_file,
584 strlen(command_file));
585 if (status == HTTP_CONTINUE)
586 cupsFinishDocument(http, dest);
587
588 if (cupsLastError() >= IPP_REDIRECTION_OTHER_SITE)
589 {
590 cgiSetVariable("MESSAGE", cgiText(_("Unable to send command to printer driver!")));
591 cgiSetVariable("ERROR", cupsLastErrorString());
592 cgiStartHTML(title);
593 cgiCopyTemplateLang("error.tmpl");
594 cgiEndHTML();
595 cgiEndMultipart();
596
597 cupsCancelJob(dest, job_id);
598 return;
599 }
600
601 /*
602 * Wait for the job to complete...
603 */
604
605 for (;;)
606 {
607 /*
608 * Get the current job state...
609 */
610
611 snprintf(uri, sizeof(uri), "ipp://localhost/jobs/%d", job_id);
612 request = ippNewRequest(IPP_GET_JOB_ATTRIBUTES);
613 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri",
614 NULL, uri);
615 if (user)
616 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
617 "requesting-user-name", NULL, user);
618 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
619 "requested-attributes", 2, NULL, job_attrs);
620
621 if ((response = cupsDoRequest(http, request, "/")) != NULL)
622 cgiSetIPPVars(response, NULL, NULL, NULL, 0);
623
624 attr = ippFindAttribute(response, "job-state", IPP_TAG_ENUM);
625 if (!attr || attr->values[0].integer >= IPP_JOB_STOPPED)
626 {
627 ippDelete(response);
628 break;
629 }
630
631 /*
632 * Job not complete, so update the status...
633 */
634
635 ippDelete(response);
636
637 cgiStartHTML(title);
638 cgiCopyTemplateLang("command.tmpl");
639 cgiEndHTML();
640 fflush(stdout);
641
642 sleep(5);
643 }
644
645 /*
646 * Send the final page that reloads the printer's page...
647 */
648
649 snprintf(resource, sizeof(resource), "/printers/%s", dest);
650
651 cgiFormEncode(uri, resource, sizeof(uri));
652 snprintf(refresh, sizeof(refresh), "5;URL=%s", uri);
653 cgiSetVariable("refresh_page", refresh);
654
655 cgiStartHTML(title);
656 cgiCopyTemplateLang("command.tmpl");
657 cgiEndHTML();
658 cgiEndMultipart();
659}
660
661
fa73b229 662/*
663 * 'cgiPrintTestPage()' - Print a test page.
664 */
665
666void
667cgiPrintTestPage(http_t *http, /* I - Connection to server */
668 const char *dest) /* I - Destination printer/class */
669{
670 ipp_t *request, /* IPP request */
671 *response; /* IPP response */
672 char uri[HTTP_MAX_URI], /* Printer URI */
673 resource[1024], /* POST resource path */
674 refresh[1024], /* Refresh URL */
675 filename[1024]; /* Test page filename */
676 const char *datadir; /* CUPS_DATADIR env var */
677 const char *user; /* Username */
678
679
680 /*
681 * See who is logged in...
682 */
683
5a738aea 684 user = getenv("REMOTE_USER");
fa73b229 685
686 /*
687 * Locate the test page file...
688 */
689
690 if ((datadir = getenv("CUPS_DATADIR")) == NULL)
691 datadir = CUPS_DATADIR;
692
6e8b116d 693 snprintf(filename, sizeof(filename), "%s/data/testprint", datadir);
fa73b229 694
695 /*
696 * Point to the printer/class...
697 */
698
699 snprintf(resource, sizeof(resource), "/%s/%s", cgiGetVariable("SECTION"),
700 dest);
701
a4d04587 702 httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
703 "localhost", ippPort(), "/%s/%s", cgiGetVariable("SECTION"),
704 dest);
fa73b229 705
706 /*
707 * Build an IPP_PRINT_JOB request, which requires the following
708 * attributes:
709 *
710 * attributes-charset
711 * attributes-natural-language
712 * printer-uri
713 * requesting-user-name
fa73b229 714 */
715
716 request = ippNewRequest(IPP_PRINT_JOB);
717
718 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
719 NULL, uri);
720
5a738aea
MS
721 if (user)
722 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
723 "requesting-user-name", NULL, user);
fa73b229 724
725 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name",
726 NULL, "Test Page");
727
fa73b229 728 /*
729 * Do the request and get back a response...
730 */
731
732 if ((response = cupsDoFileRequest(http, request, resource,
733 filename)) != NULL)
734 {
735 cgiSetIPPVars(response, NULL, NULL, NULL, 0);
736
737 ippDelete(response);
738 }
739
740 if (cupsLastError() <= IPP_OK_CONFLICT)
741 {
742 /*
743 * Automatically reload the printer status page...
744 */
745
746 cgiFormEncode(uri, resource, sizeof(uri));
f301802f 747 snprintf(refresh, sizeof(refresh), "2;URL=%s", uri);
fa73b229 748 cgiSetVariable("refresh_page", refresh);
749 }
5bd77a73
MS
750 else if (cupsLastError() == IPP_NOT_AUTHORIZED)
751 {
752 puts("Status: 401\n");
753 exit(0);
754 }
fa73b229 755
756 cgiStartHTML(cgiText(_("Print Test Page")));
757
758 if (cupsLastError() > IPP_OK_CONFLICT)
759 cgiShowIPPError(_("Unable to print test page:"));
760 else
761 {
762 cgiSetVariable("PRINTER_NAME", dest);
763
764 cgiCopyTemplateLang("test-page.tmpl");
765 }
766
767 cgiEndHTML();
768}
769
770
ef416fc2 771/*
772 * 'cgiRewriteURL()' - Rewrite a printer URI into a web browser URL...
773 */
774
775char * /* O - New URL */
776cgiRewriteURL(const char *uri, /* I - Current URI */
777 char *url, /* O - New URL */
778 int urlsize, /* I - Size of URL buffer */
779 const char *newresource) /* I - Replacement resource */
780{
c168a833 781 char scheme[HTTP_MAX_URI],
ef416fc2 782 userpass[HTTP_MAX_URI],
783 hostname[HTTP_MAX_URI],
784 rawresource[HTTP_MAX_URI],
785 resource[HTTP_MAX_URI],
786 /* URI components... */
787 *rawptr, /* Pointer into rawresource */
788 *resptr; /* Pointer into resource */
789 int port; /* Port number */
790 static int ishttps = -1; /* Using encryption? */
791 static const char *server; /* Name of server */
792 static char servername[1024];
793 /* Local server name */
794 static const char hexchars[] = "0123456789ABCDEF";
795 /* Hexadecimal conversion characters */
796
797
798 /*
799 * Check if we have been called before...
800 */
801
802 if (ishttps < 0)
803 {
804 /*
805 * No, initialize static vars for the conversion...
806 *
807 * First get the server name associated with the client interface as
808 * well as the locally configured hostname. We'll check *both* of
809 * these to see if the printer URL is local...
810 */
811
812 if ((server = getenv("SERVER_NAME")) == NULL)
813 server = "";
814
757d2cad 815 httpGetHostname(NULL, servername, sizeof(servername));
ef416fc2 816
817 /*
818 * Then flag whether we are using SSL on this connection...
819 */
820
821 ishttps = getenv("HTTPS") != NULL;
822 }
823
824 /*
825 * Convert the URI to a URL...
826 */
827
c168a833 828 httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), userpass,
a4d04587 829 sizeof(userpass), hostname, sizeof(hostname), &port,
ef416fc2 830 rawresource, sizeof(rawresource));
831
c168a833
MS
832 if (!strcmp(scheme, "ipp") ||
833 !strcmp(scheme, "http") ||
834 !strcmp(scheme, "https"))
ef416fc2 835 {
836 if (newresource)
837 {
838 /*
839 * Force the specified resource name instead of the one in the URL...
840 */
841
842 strlcpy(resource, newresource, sizeof(resource));
843 }
844 else
845 {
846 /*
847 * Rewrite the resource string so it doesn't contain any
848 * illegal chars...
849 */
850
851 for (rawptr = rawresource, resptr = resource; *rawptr; rawptr ++)
852 if ((*rawptr & 128) || *rawptr == '%' || *rawptr == ' ' ||
853 *rawptr == '#' || *rawptr == '?' ||
854 *rawptr == '.') /* For MSIE */
855 {
856 if (resptr < (resource + sizeof(resource) - 3))
857 {
858 *resptr++ = '%';
859 *resptr++ = hexchars[(*rawptr >> 4) & 15];
860 *resptr++ = hexchars[*rawptr & 15];
861 }
862 }
863 else if (resptr < (resource + sizeof(resource) - 1))
864 *resptr++ = *rawptr;
865
866 *resptr = '\0';
867 }
868
869 /*
870 * Map local access to a local URI...
871 */
872
c168a833
MS
873 if (!strcasecmp(hostname, "127.0.0.1") ||
874 !strcasecmp(hostname, "[::1]") ||
875 !strcasecmp(hostname, "localhost") ||
ef416fc2 876 !strncasecmp(hostname, "localhost.", 10) ||
877 !strcasecmp(hostname, server) ||
878 !strcasecmp(hostname, servername))
879 {
880 /*
881 * Make URI relative to the current server...
882 */
883
884 strlcpy(url, resource, urlsize);
885 }
886 else
887 {
888 /*
889 * Rewrite URI with HTTP/HTTPS scheme...
890 */
891
892 if (userpass[0])
893 snprintf(url, urlsize, "%s://%s@%s:%d%s",
894 ishttps ? "https" : "http",
895 userpass, hostname, port, resource);
896 else
897 snprintf(url, urlsize, "%s://%s:%d%s",
898 ishttps ? "https" : "http",
899 hostname, port, resource);
900 }
901 }
902 else
903 strlcpy(url, uri, urlsize);
904
905 return (url);
906}
907
908
909/*
910 * 'cgiSetIPPObjectVars()' - Set CGI variables from an IPP object.
911 */
912
913ipp_attribute_t * /* O - Next object */
914cgiSetIPPObjectVars(
915 ipp_attribute_t *obj, /* I - Response data to be copied... */
916 const char *prefix, /* I - Prefix for name or NULL */
917 int element) /* I - Parent element number */
918{
919 ipp_attribute_t *attr; /* Attribute in response... */
920 int i; /* Looping var */
921 char name[1024], /* Name of attribute */
922 *nameptr, /* Pointer into name */
923 value[16384], /* Value(s) */
924 *valptr; /* Pointer into value */
925 struct tm *date; /* Date information */
926
927
928 fprintf(stderr, "DEBUG2: cgiSetIPPObjectVars(obj=%p, prefix=\"%s\", "
929 "element=%d)\n",
f301802f 930 obj, prefix ? prefix : "(null)", element);
ef416fc2 931
932 /*
933 * Set common CGI template variables...
934 */
935
936 if (!prefix)
937 cgiSetServerVersion();
938
939 /*
940 * Loop through the attributes and set them for the template...
941 */
942
943 for (attr = obj; attr && attr->group_tag != IPP_TAG_ZERO; attr = attr->next)
944 {
945 /*
946 * Copy the attribute name, substituting "_" for "-"...
947 */
948
949 if (!attr->name)
950 continue;
951
952 if (prefix)
953 {
954 snprintf(name, sizeof(name), "%s.", prefix);
955 nameptr = name + strlen(name);
956 }
957 else
958 nameptr = name;
959
960 for (i = 0; attr->name[i] && nameptr < (name + sizeof(name) - 1); i ++)
961 if (attr->name[i] == '-')
962 *nameptr++ = '_';
963 else
964 *nameptr++ = attr->name[i];
965
966 *nameptr = '\0';
967
968 /*
969 * Add "job_printer_name" variable if we have a "job_printer_uri"
970 * attribute...
971 */
972
973 if (!strcmp(name, "job_printer_uri"))
974 {
975 if ((valptr = strrchr(attr->values[0].string.text, '/')) == NULL)
976 valptr = "unknown";
977 else
978 valptr ++;
979
980 cgiSetArray("job_printer_name", element, valptr);
981 }
982
f7deaa1a 983 /*
984 * Localize event names in "notify_events" variable...
985 */
986
987 if (!strcmp(name, "notify_events"))
988 {
989 size_t remaining; /* Remaining bytes in buffer */
990
991
992 value[0] = '\0';
993 valptr = value;
994
995 for (i = 0; i < attr->num_values; i ++)
996 {
997 if (valptr >= (value + sizeof(value) - 3))
998 break;
999
1000 if (i)
1001 {
1002 *valptr++ = ',';
1003 *valptr++ = ' ';
1004 }
1005
1006 remaining = sizeof(value) - (valptr - value);
1007
1008 if (!strcmp(attr->values[i].string.text, "printer-stopped"))
080811b1 1009 strlcpy(valptr, _("Printer Paused"), remaining);
f7deaa1a 1010 else if (!strcmp(attr->values[i].string.text, "printer-added"))
1011 strlcpy(valptr, _("Printer Added"), remaining);
1012 else if (!strcmp(attr->values[i].string.text, "printer-modified"))
1013 strlcpy(valptr, _("Printer Modified"), remaining);
1014 else if (!strcmp(attr->values[i].string.text, "printer-deleted"))
1015 strlcpy(valptr, _("Printer Deleted"), remaining);
1016 else if (!strcmp(attr->values[i].string.text, "job-created"))
1017 strlcpy(valptr, _("Job Created"), remaining);
1018 else if (!strcmp(attr->values[i].string.text, "job-completed"))
1019 strlcpy(valptr, _("Job Completed"), remaining);
1020 else if (!strcmp(attr->values[i].string.text, "job-stopped"))
1021 strlcpy(valptr, _("Job Stopped"), remaining);
1022 else if (!strcmp(attr->values[i].string.text, "job-config-changed"))
1023 strlcpy(valptr, _("Job Options Changed"), remaining);
1024 else if (!strcmp(attr->values[i].string.text, "server-restarted"))
1025 strlcpy(valptr, _("Server Restarted"), remaining);
1026 else if (!strcmp(attr->values[i].string.text, "server-started"))
1027 strlcpy(valptr, _("Server Started"), remaining);
1028 else if (!strcmp(attr->values[i].string.text, "server-stopped"))
1029 strlcpy(valptr, _("Server Stopped"), remaining);
1030 else if (!strcmp(attr->values[i].string.text, "server-audit"))
1031 strlcpy(valptr, _("Server Security Auditing"), remaining);
1032 else
1033 strlcpy(valptr, attr->values[i].string.text, remaining);
1034
1035 valptr += strlen(valptr);
1036 }
1037
1038 cgiSetArray("notify_events", element, value);
1039 continue;
1040 }
1041
1042 /*
1043 * Add "notify_printer_name" variable if we have a "notify_printer_uri"
1044 * attribute...
1045 */
1046
1047 if (!strcmp(name, "notify_printer_uri"))
1048 {
1049 if ((valptr = strrchr(attr->values[0].string.text, '/')) == NULL)
1050 valptr = "unknown";
1051 else
1052 valptr ++;
1053
1054 cgiSetArray("notify_printer_name", element, valptr);
1055 }
1056
1057 /*
1058 * Add "notify_recipient_name" variable if we have a "notify_recipient_uri"
1059 * attribute, and rewrite recipient URI...
1060 */
1061
1062 if (!strcmp(name, "notify_recipient_uri"))
1063 {
1064 char uri[1024], /* New URI */
1065 scheme[32], /* Scheme portion of URI */
1066 userpass[256], /* Username/password portion of URI */
1067 host[1024], /* Hostname portion of URI */
1068 resource[1024], /* Resource portion of URI */
1069 *options; /* Options in URI */
1070 int port; /* Port number */
1071
1072
1073 httpSeparateURI(HTTP_URI_CODING_ALL, attr->values[0].string.text,
1074 scheme, sizeof(scheme), userpass, sizeof(userpass),
1075 host, sizeof(host), &port, resource, sizeof(resource));
1076
1077 if (!strcmp(scheme, "rss"))
1078 {
1079 /*
1080 * RSS notification...
1081 */
1082
1083 if ((options = strchr(resource, '?')) != NULL)
1084 *options = '\0';
1085
1086 if (host[0])
1087 {
1088 /*
1089 * Link to remote feed...
1090 */
1091
1092 httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), "http",
1093 userpass, host, port, resource);
1094 strlcpy(name, uri, sizeof(name));
1095 }
1096 else
1097 {
1098 /*
1099 * Link to local feed...
1100 */
1101
1102 snprintf(uri, sizeof(uri), "/rss%s", resource);
1103 strlcpy(name, resource + 1, sizeof(name));
1104 }
1105 }
1106 else
1107 {
1108 /*
1109 * Other...
1110 */
1111
1112 strlcpy(uri, attr->values[0].string.text, sizeof(uri));
1113 strlcpy(name, resource, sizeof(name));
1114 }
1115
1116 cgiSetArray("notify_recipient_uri", element, uri);
1117 cgiSetArray("notify_recipient_name", element, name);
1118 continue;
1119 }
1120
ef416fc2 1121 /*
1122 * Add "admin_uri" variable if we have a "printer_uri_supported"
1123 * attribute...
1124 */
1125
1126 if (!strcmp(name, "printer_uri_supported"))
1127 {
1128 cgiRewriteURL(attr->values[0].string.text, value, sizeof(value),
1129 "/admin/");
1130
1131 cgiSetArray("admin_uri", element, value);
1132 }
1133
1134 /*
1135 * Copy values...
1136 */
1137
1138 value[0] = '\0'; /* Initially an empty string */
1139 valptr = value; /* Start at the beginning */
1140
1141 for (i = 0; i < attr->num_values; i ++)
1142 {
1143 if (i)
2e4ff8af 1144 strlcat(valptr, ", ", sizeof(value) - (valptr - value));
ef416fc2 1145
1146 valptr += strlen(valptr);
1147
1148 switch (attr->value_tag)
1149 {
1150 case IPP_TAG_INTEGER :
1151 case IPP_TAG_ENUM :
1152 if (strncmp(name, "time_at_", 8) == 0)
1153 {
1154 time_t t; /* Temporary time value */
1155
1156 t = (time_t)attr->values[i].integer;
1157 date = localtime(&t);
1158
1159 strftime(valptr, sizeof(value) - (valptr - value), "%c", date);
1160 }
1161 else
1162 snprintf(valptr, sizeof(value) - (valptr - value),
1163 "%d", attr->values[i].integer);
1164 break;
1165
1166 case IPP_TAG_BOOLEAN :
1167 snprintf(valptr, sizeof(value) - (valptr - value),
1168 "%d", attr->values[i].boolean);
1169 break;
1170
1171 case IPP_TAG_NOVALUE :
1172 strlcat(valptr, "novalue", sizeof(value) - (valptr - value));
1173 break;
1174
1175 case IPP_TAG_RANGE :
1176 snprintf(valptr, sizeof(value) - (valptr - value),
1177 "%d-%d", attr->values[i].range.lower,
1178 attr->values[i].range.upper);
1179 break;
1180
1181 case IPP_TAG_RESOLUTION :
1182 snprintf(valptr, sizeof(value) - (valptr - value),
1183 "%dx%d%s", attr->values[i].resolution.xres,
1184 attr->values[i].resolution.yres,
1185 attr->values[i].resolution.units == IPP_RES_PER_INCH ?
1186 "dpi" : "dpc");
1187 break;
1188
1189 case IPP_TAG_URI :
b423cd4c 1190 if (strchr(attr->values[i].string.text, ':') &&
1191 strcmp(name, "device_uri"))
ef416fc2 1192 {
1193 /*
1194 * Rewrite URIs...
1195 */
1196
1197 if (!strcmp(name, "member_uris"))
1198 {
1199 char url[1024]; /* URL for class member... */
1200
1201
1202 cgiRewriteURL(attr->values[i].string.text, url,
1203 sizeof(url), NULL);
1204
1205 snprintf(valptr, sizeof(value) - (valptr - value),
1206 "<A HREF=\"%s\">%s</A>", url,
b86bc4cf 1207 strrchr(attr->values[i].string.text, '/') + 1);
ef416fc2 1208 }
1209 else
1210 cgiRewriteURL(attr->values[i].string.text, valptr,
1211 sizeof(value) - (valptr - value), NULL);
1212 break;
1213 }
1214
1215 case IPP_TAG_STRING :
1216 case IPP_TAG_TEXT :
1217 case IPP_TAG_NAME :
1218 case IPP_TAG_KEYWORD :
1219 case IPP_TAG_CHARSET :
1220 case IPP_TAG_LANGUAGE :
1221 case IPP_TAG_MIMETYPE :
1222 strlcat(valptr, attr->values[i].string.text,
1223 sizeof(value) - (valptr - value));
1224 break;
1225
1226 case IPP_TAG_BEGIN_COLLECTION :
1227 snprintf(value, sizeof(value), "%s%d", name, i + 1);
1228 cgiSetIPPVars(attr->values[i].collection, NULL, NULL, value,
1229 element);
1230 break;
1231
1232 default :
1233 break; /* anti-compiler-warning-code */
1234 }
1235 }
1236
1237 /*
1238 * Add the element...
1239 */
1240
1241 if (attr->value_tag != IPP_TAG_BEGIN_COLLECTION)
1242 {
1243 cgiSetArray(name, element, value);
1244
1245 fprintf(stderr, "DEBUG2: %s[%d]=\"%s\"\n", name, element, value);
1246 }
1247 }
1248
1249 return (attr ? attr->next : NULL);
1250}
1251
1252
1253/*
1254 * 'cgiSetIPPVars()' - Set CGI variables from an IPP response.
1255 */
1256
1257int /* O - Maximum number of elements */
1258cgiSetIPPVars(ipp_t *response, /* I - Response data to be copied... */
1259 const char *filter_name, /* I - Filter name */
1260 const char *filter_value, /* I - Filter value */
1261 const char *prefix, /* I - Prefix for name or NULL */
1262 int parent_el) /* I - Parent element number */
1263{
1264 int element; /* Element in CGI array */
1265 ipp_attribute_t *attr, /* Attribute in response... */
1266 *filter; /* Filtering attribute */
1267
1268
1269 fprintf(stderr, "DEBUG2: cgiSetIPPVars(response=%p, filter_name=\"%s\", "
1270 "filter_value=\"%s\", prefix=\"%s\", parent_el=%d)\n",
f301802f 1271 response, filter_name ? filter_name : "(null)",
1272 filter_value ? filter_value : "(null)",
1273 prefix ? prefix : "(null)", parent_el);
ef416fc2 1274
1275 /*
1276 * Set common CGI template variables...
1277 */
1278
1279 if (!prefix)
1280 cgiSetServerVersion();
1281
1282 /*
1283 * Loop through the attributes and set them for the template...
1284 */
1285
1286 attr = response->attrs;
1287
1288 if (!prefix)
1289 while (attr && attr->group_tag == IPP_TAG_OPERATION)
1290 attr = attr->next;
1291
1292 for (element = parent_el; attr; element ++)
1293 {
1294 /*
1295 * Copy attributes to a separator...
1296 */
1297
1298 while (attr && attr->group_tag == IPP_TAG_ZERO)
1299 attr= attr->next;
1300
1301 if (!attr)
1302 break;
1303
1304 if (filter_name)
1305 {
1306 for (filter = attr;
1307 filter != NULL && filter->group_tag != IPP_TAG_ZERO;
1308 filter = filter->next)
1309 if (filter->name && !strcmp(filter->name, filter_name) &&
1310 (filter->value_tag == IPP_TAG_STRING ||
1311 (filter->value_tag >= IPP_TAG_TEXTLANG &&
1312 filter->value_tag <= IPP_TAG_MIMETYPE)) &&
1313 filter->values[0].string.text != NULL &&
1314 !strcasecmp(filter->values[0].string.text, filter_value))
1315 break;
1316
1317 if (!filter)
1318 return (element + 1);
1319
1320 if (filter->group_tag == IPP_TAG_ZERO)
1321 {
1322 attr = filter;
1323 element --;
1324 continue;
1325 }
1326 }
1327
1328 attr = cgiSetIPPObjectVars(attr, prefix, element);
1329 }
1330
89d46774 1331 fprintf(stderr, "DEBUG2: Returing %d from cgiSetIPPVars()...\n", element);
ef416fc2 1332
89d46774 1333 return (element);
ef416fc2 1334}
1335
1336
fa73b229 1337/*
1338 * 'cgiShowIPPError()' - Show the last IPP error message.
1339 *
1340 * The caller must still call cgiStartHTML() and cgiEndHTML().
1341 */
1342
1343void
1344cgiShowIPPError(const char *message) /* I - Contextual message */
1345{
1346 cgiSetVariable("MESSAGE", cgiText(message));
1347 cgiSetVariable("ERROR", cupsLastErrorString());
1348 cgiCopyTemplateLang("error.tmpl");
1349}
1350
1351
ef416fc2 1352/*
1353 * 'cgiShowJobs()' - Show print jobs.
1354 */
1355
1356void
1357cgiShowJobs(http_t *http, /* I - Connection to server */
1358 const char *dest) /* I - Destination name or NULL */
1359{
1360 int i; /* Looping var */
1361 const char *which_jobs; /* Which jobs to show */
1362 ipp_t *request, /* IPP request */
1363 *response; /* IPP response */
1364 cups_array_t *jobs; /* Array of job objects */
1365 ipp_attribute_t *job; /* Job object */
1366 int ascending, /* Order of jobs (0 = descending) */
1367 first, /* First job to show */
1368 count; /* Number of jobs */
1369 const char *var; /* Form variable */
1370 void *search; /* Search data */
2e4ff8af
MS
1371 char url[1024], /* Printer URI */
1372 val[1024]; /* Form variable */
ef416fc2 1373
1374
1375 /*
1376 * Build an IPP_GET_JOBS request, which requires the following
1377 * attributes:
1378 *
1379 * attributes-charset
1380 * attributes-natural-language
1381 * printer-uri
1382 */
1383
fa73b229 1384 request = ippNewRequest(IPP_GET_JOBS);
ef416fc2 1385
1386 if (dest)
1387 {
a4d04587 1388 httpAssembleURIf(HTTP_URI_CODING_ALL, url, sizeof(url), "ipp", NULL,
1389 "localhost", ippPort(), "/printers/%s", dest);
ef416fc2 1390 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
1391 NULL, url);
1392 }
1393 else
1394 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL,
1395 "ipp://localhost/jobs");
1396
1397 if ((which_jobs = cgiGetVariable("which_jobs")) != NULL)
1398 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "which-jobs",
1399 NULL, which_jobs);
1400
1401 cgiGetAttributes(request, "jobs.tmpl");
1402
1403 /*
1404 * Do the request and get back a response...
1405 */
1406
1407 if ((response = cupsDoRequest(http, request, "/")) != NULL)
1408 {
1409 /*
1410 * Get a list of matching job objects.
1411 */
1412
2e4ff8af
MS
1413 if ((var = cgiGetVariable("QUERY")) != NULL &&
1414 !cgiGetVariable("CLEAR"))
ef416fc2 1415 search = cgiCompileSearch(var);
1416 else
1417 search = NULL;
1418
1419 jobs = cgiGetIPPObjects(response, search);
1420 count = cupsArrayCount(jobs);
1421
1422 if (search)
1423 cgiFreeSearch(search);
1424
1425 /*
1426 * Figure out which jobs to display...
1427 */
1428
1429 if ((var = cgiGetVariable("FIRST")) != NULL)
1430 first = atoi(var);
1431 else
1432 first = 0;
1433
1434 if (first >= count)
1435 first = count - CUPS_PAGE_MAX;
1436
1437 first = (first / CUPS_PAGE_MAX) * CUPS_PAGE_MAX;
1438
1439 if (first < 0)
1440 first = 0;
1441
2e4ff8af
MS
1442 sprintf(val, "%d", count);
1443 cgiSetVariable("TOTAL", val);
ef416fc2 1444
1445 if ((var = cgiGetVariable("ORDER")) != NULL)
1446 ascending = !strcasecmp(var, "asc");
1447 else
b423cd4c 1448 {
1449 ascending = !which_jobs || !strcasecmp(which_jobs, "not-completed");
1450 cgiSetVariable("ORDER", ascending ? "asc" : "dec");
1451 }
ef416fc2 1452
1453 if (ascending)
1454 {
1455 for (i = 0, job = (ipp_attribute_t *)cupsArrayIndex(jobs, first);
1456 i < CUPS_PAGE_MAX && job;
1457 i ++, job = (ipp_attribute_t *)cupsArrayNext(jobs))
1458 cgiSetIPPObjectVars(job, NULL, i);
1459 }
1460 else
1461 {
1462 for (i = 0, job = (ipp_attribute_t *)cupsArrayIndex(jobs, count - first - 1);
1463 i < CUPS_PAGE_MAX && job;
1464 i ++, job = (ipp_attribute_t *)cupsArrayPrev(jobs))
1465 cgiSetIPPObjectVars(job, NULL, i);
1466 }
1467
1468 /*
1469 * Save navigation URLs...
1470 */
1471
2e4ff8af
MS
1472 if (dest)
1473 snprintf(val, sizeof(val), "/%s/%s", cgiGetVariable("SECTION"), dest);
ef416fc2 1474 else
2e4ff8af 1475 strlcpy(val, "/jobs/", sizeof(val));
ef416fc2 1476
2e4ff8af 1477 cgiSetVariable("THISURL", val);
ef416fc2 1478
1479 if (first > 0)
1480 {
2e4ff8af
MS
1481 sprintf(val, "%d", first - CUPS_PAGE_MAX);
1482 cgiSetVariable("PREV", val);
ef416fc2 1483 }
1484
1485 if ((first + CUPS_PAGE_MAX) < count)
1486 {
2e4ff8af
MS
1487 sprintf(val, "%d", first + CUPS_PAGE_MAX);
1488 cgiSetVariable("NEXT", val);
ef416fc2 1489 }
1490
1491 /*
1492 * Then show everything...
1493 */
1494
fa73b229 1495 if (dest)
1496 cgiSetVariable("SEARCH_DEST", dest);
1497
1498 cgiCopyTemplateLang("search.tmpl");
ef416fc2 1499
1500 cgiCopyTemplateLang("jobs-header.tmpl");
1501
b19ccc9e 1502 if (count > CUPS_PAGE_MAX)
fa73b229 1503 cgiCopyTemplateLang("pager.tmpl");
ef416fc2 1504
1505 cgiCopyTemplateLang("jobs.tmpl");
1506
b19ccc9e 1507 if (count > CUPS_PAGE_MAX)
fa73b229 1508 cgiCopyTemplateLang("pager.tmpl");
ef416fc2 1509
bd7854cb 1510 cupsArrayDelete(jobs);
ef416fc2 1511 ippDelete(response);
1512 }
1513}
1514
1515
fa73b229 1516/*
1517 * 'cgiText()' - Return localized text.
1518 */
1519
1520const char * /* O - Localized message */
1521cgiText(const char *message) /* I - Message */
1522{
1523 static cups_lang_t *language = NULL;
1524 /* Language */
1525
1526
1527 if (!language)
1528 language = cupsLangDefault();
1529
1530 return (_cupsLangString(language, message));
1531}
1532
ef416fc2 1533
1534/*
b19ccc9e 1535 * End of "$Id: ipp-var.c 7940 2008-09-16 00:45:16Z mike $".
ef416fc2 1536 */