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