Re: [MOD] STR #4028: commandtops not robust against broken PostScript in PPD file
Till Kamppeter <[email protected]>
| Newsgroups | gmane.comp.printing.cups.bugs |
|---|---|
| Message-ID | <[email protected]> |
DO NOT REPLY TO THIS MESSAGE. INSTEAD, POST ANY RESPONSES TO THE LINK BELOW. [STR New] Attached file "commandtops_v3.c"... Link: http://www.cups.org/str.php?L4028 Version: 1.5.2 _______________________________________________ cups-bugs mailing list [email protected] http://lists.easysw.com/mailman/listinfo/cups-bugs
commandtops_v3.c
(text/plain, 10.8 KB)
/*
* "$Id: commandtops.c 9793 2011-05-20 03:49:49Z mike $"
*
* PostScript command filter for CUPS.
*
* Copyright 2008-2011 by Apple Inc.
*
* These coded instructions, statements, and computer programs are the
* property of Apple Inc. and are protected by Federal copyright
* law. Distribution and use rights are outlined in the file "LICENSE.txt"
* which should have been included with this file. If this file is
* file is missing or damaged, see the license at "http://www.cups.org/".
*
*
* Contents:
*
* main() - Process a CUPS command file.
* auto_configure() - Automatically configure the printer using
* PostScript query commands and/or SNMP lookups.
* begin_ps() - Send the standard PostScript prolog.
* end_ps() - Send the standard PostScript trailer.
* print_self_test_page() - Print a self-test page.
* report_levels() - Report supply levels.
*/
/*
* Include necessary headers...
*/
#include <cups/cups-private.h>
#include <cups/ppd.h>
#include <cups/sidechannel.h>
/*
* Local functions...
*/
static void auto_configure(ppd_file_t *ppd, const char *user);
static void begin_ps(ppd_file_t *ppd, const char *user);
static void end_ps(ppd_file_t *ppd);
static void print_self_test_page(ppd_file_t *ppd, const char *user);
static void report_levels(ppd_file_t *ppd, const char *user);
/*
* 'main()' - Process a CUPS command file.
*/
int /* O - Exit status */
main(int argc, /* I - Number of command-line arguments */
char *argv[]) /* I - Command-line arguments */
{
cups_file_t *fp; /* Command file */
char line[1024], /* Line from file */
*value; /* Value on line */
int linenum; /* Line number in file */
ppd_file_t *ppd; /* PPD file */
/*
* Check for valid arguments...
*/
if (argc < 6 || argc > 7)
{
/*
* We don't have the correct number of arguments; write an error message
* and return.
*/
_cupsLangPrintf(stderr,
_("Usage: %s job-id user title copies options file"),
argv[0]);
return (1);
}
/*
* Open the PPD file...
*/
if ((ppd = ppdOpenFile(getenv("PPD"))) == NULL)
{
fputs("ERROR: Unable to open PPD file!\n", stderr);
return (1);
}
/*
* Open the command file as needed...
*/
if (argc == 7)
{
if ((fp = cupsFileOpen(argv[6], "r")) == NULL)
{
perror("ERROR: Unable to open command file - ");
return (1);
}
}
else
fp = cupsFileStdin();
/*
* Read the commands from the file and send the appropriate commands...
*/
linenum = 0;
while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
{
/*
* Parse the command...
*/
if (!_cups_strcasecmp(line, "AutoConfigure"))
auto_configure(ppd, argv[2]);
else if (!_cups_strcasecmp(line, "PrintSelfTestPage"))
print_self_test_page(ppd, argv[2]);
else if (!_cups_strcasecmp(line, "ReportLevels"))
report_levels(ppd, argv[2]);
else
fprintf(stderr, "ERROR: Invalid printer command \"%s\"!\n", line);
}
return (0);
}
/*
* 'auto_configure()' - Automatically configure the printer using PostScript
* query commands and/or SNMP lookups.
*/
static void
auto_configure(ppd_file_t *ppd, /* I - PPD file */
const char *user) /* I - Printing user */
{
ppd_option_t *option; /* Current option in PPD */
ppd_attr_t *attr; /* Query command attribute */
char buffer[1024], /* String buffer */
*bufptr; /* Pointer into buffer */
ssize_t bytes; /* Number of bytes read */
int datalen; /* Side-channel data length */
int ch; /* single character equivalent */
char *tempptr; /* Pointer into buffer */
/*
* See if the backend supports bidirectional I/O...
*/
datalen = 1;
if (cupsSideChannelDoRequest(CUPS_SC_CMD_GET_BIDI, buffer, &datalen,
30.0) != CUPS_SC_STATUS_OK ||
buffer[0] != CUPS_SC_BIDI_SUPPORTED)
{
fputs("DEBUG: Unable to auto-configure PostScript Printer - no "
"bidirectional I/O available!\n", stderr);
return;
}
/*
* Put the printer in PostScript mode...
*/
begin_ps(ppd, user);
fflush(stdout);
/*
* Wait for the printer to become connected...
*/
do
{
sleep(1);
datalen = 1;
}
while (cupsSideChannelDoRequest(CUPS_SC_CMD_GET_CONNECTED, buffer, &datalen,
5.0) == CUPS_SC_STATUS_OK && !buffer[0]);
/*
* As a lot of printer vendor supplied PPDs have exhibited erroneous PostScript
* constructs in query code sequences, we make auto configuring (almost) bomb proof even
* against syntax errors in PostScript code.
* As a first step, we establish our own PS error handler.
*/
puts("/my_handleerror\n"
"{\n"
" $error/newerror false put\n"
" (:PostScript error in \")print query print (\": )print\n"
" $error/errorname get 128 string cvs print\n"
" (; offending command:)print $error/command get 128 string cvs print (\n)print flush\n"
"}bind def\n"
"errordict/timeout{}put\n"
"/query (?Unknown) def\n");
fflush(stdout);
/*
* Then loop through every option in the PPD file and ask for the current
* value...
*/
fputs("DEBUG: Auto-configuring PostScript printer...\n", stderr);
for (option = ppdFirstOption(ppd); option; option = ppdNextOption(ppd))
{
/*
* See if we have a query command for this option...
*/
snprintf(buffer, sizeof(buffer), "?%s", option->keyword);
if ((attr = ppdFindAttr(ppd, buffer, NULL)) == NULL || !attr->value)
{
fprintf(stderr, "DEBUG: Skipping %s option...\n", option->keyword);
continue;
}
/*
* Send the query code to the printer...
*/
fprintf(stderr, "DEBUG: Querying %s...\n", option->keyword);
fprintf(stdout, "/query (?%s)def\n", option->keyword); /* pass the keyword for error reporting */
puts("/this_query(");
/*
* To prevent our own construct from failing due to unpaired parantheses
* within the query code, we take pains to escape thr "(" and ")".
*/
fprintf(stderr, "DEBUG: %s\n", attr->value);
bufptr = attr->value;
while (*bufptr)
{
ch = *bufptr++;
if (ch == '(' || ch == ')') putchar('\\');
putchar(ch);
}
puts(")def\n{this_query cvx exec}stopped{userdict/my_handleerror get exec}if clear\n");
fflush(stdout);
datalen = 0;
cupsSideChannelDoRequest(CUPS_SC_CMD_DRAIN_OUTPUT, buffer, &datalen, 5.0);
/*
* Read the response data...
*/
bufptr = buffer;
buffer[0] = '\0';
while ((bytes = cupsBackChannelRead(bufptr,
sizeof(buffer) - (bufptr - buffer) - 1,
10.0)) > 0)
{
*(bufptr + bytes) = '\0';
/*
* No newline at the end? Go on reading ...
*/
bufptr += bytes;
if (bytes == 0 ||
(bufptr - buffer < sizeof(buffer) - 1 &&
*(bufptr - 1) != '\r' && *(bufptr - 1) != '\n'))
continue;
bytes = bufptr - buffer;
/*
* Trim whitespace and control characters from both ends...
*/
for (bufptr --; bufptr >= buffer; bufptr --)
if (isspace(*bufptr & 255) || iscntrl(*bufptr & 255))
*bufptr = '\0';
else
break;
for (bufptr = buffer; isspace(*bufptr & 255) || iscntrl(*bufptr & 255);
bufptr ++);
fprintf(stderr, "DEBUG: Got \"%s\" (%d bytes)\n", bufptr, (int)bytes);
/*
* Skip blank lines...
*/
if (!*bufptr)
{
bufptr = buffer;
buffer[0] = '\0';
continue;
}
/*
* PostScript code for this option in the PPD is broken...
*/
if ((tempptr = strchr(bufptr, ':')) != NULL)
{
fprintf(stderr, "DEBUG%s\n", tempptr); /* the interpreter's error message */
fprintf(stderr, "ERROR: PostScript error in query for option %s, default for this option not set.\n", option->keyword);
break;
}
/*
* Verify the result is a valid option choice...
*/
if (!ppdFindChoice(option, bufptr))
{
/* Consider "Unknown" as not configured in the printer */
if (!strcasecmp(bufptr, "Unknown"))
{
fprintf(stderr,
"WARNING: Printer answered query for the setting of option %s with \"Unknown\", default for this option not set.\n",
option->keyword);
break;
}
bufptr = buffer;
buffer[0] = '\0';
continue;
}
/*
* Write out the result and move on to the next option...
*/
fprintf(stderr, "DEBUG: Default%s=%s\n", option->keyword, bufptr);
fprintf(stderr, "PPD: Default%s=%s\n", option->keyword, bufptr);
break;
}
/*
* Printer did not answer this option's query
*/
if (bytes <= 0)
fprintf(stderr,
"ERROR: Printer timed out on option %s, default for this option not set.\n",
option->keyword);
}
/*
* Finish the job...
*/
fflush(stdout);
end_ps(ppd);
}
/*
* 'begin_ps()' - Send the standard PostScript prolog.
*/
static void
begin_ps(ppd_file_t *ppd, /* I - PPD file */
const char *user) /* I - Username */
{
(void)user;
if (ppd->jcl_begin)
{
fputs(ppd->jcl_begin, stdout);
fputs(ppd->jcl_ps, stdout);
}
puts("%!");
puts("userdict dup(\\004)cvn{}put (\\004\\004)cvn{}put\n");
fflush(stdout);
}
/*
* 'end_ps()' - Send the standard PostScript trailer.
*/
static void
end_ps(ppd_file_t *ppd) /* I - PPD file */
{
if (ppd->jcl_end)
fputs(ppd->jcl_end, stdout);
else
putchar(0x04);
fflush(stdout);
}
/*
* 'print_self_test_page()' - Print a self-test page.
*/
static void
print_self_test_page(ppd_file_t *ppd, /* I - PPD file */
const char *user) /* I - Printing user */
{
/*
* Put the printer in PostScript mode...
*/
begin_ps(ppd, user);
/*
* Send a simple file the draws a box around the imageable area and shows
* the product/interpreter information...
*/
puts("% You are using the wrong driver for your printer!\n"
"0 setgray\n"
"2 setlinewidth\n"
"initclip newpath clippath gsave stroke grestore pathbbox\n"
"exch pop exch pop exch 9 add exch 9 sub moveto\n"
"/Courier findfont 12 scalefont setfont\n"
"0 -12 rmoveto gsave product show grestore\n"
"0 -12 rmoveto gsave version show ( ) show revision 20 string cvs show "
"grestore\n"
"0 -12 rmoveto gsave serialnumber 20 string cvs show grestore\n"
"showpage");
/*
* Finish the job...
*/
end_ps(ppd);
}
/*
* 'report_levels()' - Report supply levels.
*/
static void
report_levels(ppd_file_t *ppd, /* I - PPD file */
const char *user) /* I - Printing user */
{
/*
* Put the printer in PostScript mode...
*/
begin_ps(ppd, user);
/*
* Don't bother sending any additional PostScript commands, since we just
* want the backend to have enough time to collect the supply info.
*/
/*
* Finish the job...
*/
end_ps(ppd);
}
/*
* End of "$Id: commandtops.c 9793 2011-05-20 03:49:49Z mike $".
*/