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