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