diff --git a/Makefile.am b/Makefile.am index 0da4b8edc..0d78f723a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -231,14 +231,22 @@ pkgfilter_PROGRAMS += \ endif check_PROGRAMS = \ - test-external + test-external \ + test-rastertopclx-format-string \ + test-rastertoescpx-page-size + +TESTS = \ + test-rastertopclx-format-string \ + test-rastertoescpx-page-size # Not reliable bash script #TESTS += filter/test.sh EXTRA_DIST += \ $(genfilterscripts) \ - filter/test.sh + filter/test.sh \ + filter/test-rastertopclx-format-string.c \ + filter/test-rastertoescpx-page-size.c bannertopdf_SOURCES = \ filter/bannertopdf.c @@ -561,6 +569,31 @@ test_external_LDADD = \ $(LIBPPD_LIBS) \ $(CUPS_LIBS) +test_rastertopclx_format_string_SOURCES = \ + filter/test-rastertopclx-format-string.c \ + filter/pcl-common.c +test_rastertopclx_format_string_CFLAGS = \ + $(CUPS_CFLAGS) \ + $(LIBPNG_CFLAGS) \ + $(LIBCUPSFILTERS_CFLAGS) \ + $(LIBPPD_CFLAGS) +test_rastertopclx_format_string_LDADD = \ + $(CUPS_LIBS) \ + $(LIBPNG_LIBS) \ + $(LIBCUPSFILTERS_LIBS) \ + $(LIBPPD_LIBS) + +test_rastertoescpx_page_size_SOURCES = \ + filter/test-rastertoescpx-page-size.c +test_rastertoescpx_page_size_CFLAGS = \ + $(CUPS_CFLAGS) \ + $(LIBCUPSFILTERS_CFLAGS) \ + $(LIBPPD_CFLAGS) +test_rastertoescpx_page_size_LDADD = \ + $(CUPS_LIBS) \ + $(LIBCUPSFILTERS_LIBS) \ + $(LIBPPD_LIBS) + # ========= # Man pages # ========= diff --git a/filter/pcl-common.c b/filter/pcl-common.c index dc162b535..a3f5dccdf 100644 --- a/filter/pcl-common.c +++ b/filter/pcl-common.c @@ -10,6 +10,8 @@ // Contents: // // pcl_set_media_size() - Set media size using the page size command. +// pcl_write() - Write a PCL command string, performing safe +// page-count substitutions as needed. // pjl_write() - Write a PJL command string, performing // substitutions as needed. // @@ -22,6 +24,7 @@ #include #include #include "pcl-common.h" +#include #include @@ -192,6 +195,92 @@ pcl_set_media_size(ppd_file_t *ppd, // I - PPD file } +// +// 'pcl_write()' - Write a PCL command string, performing safe page-count +// substitutions as needed. +// + +void +pcl_write(const char *format, // I - Format string + int page_count) // I - Page count +{ + const char *spec; // Start of format specifier + int width; // Field width + int zero_pad; // Zero-pad output? + int have_width; // Width was specified? + + + if (!format) + return; + + while (*format) + { + if (*format != '%') + { + putchar(*format++); + continue; + } + + format ++; + + if (!*format) + { + putchar('%'); + break; + } + + if (*format == '%') + { + putchar('%'); + format ++; + continue; + } + + spec = format; + width = 0; + zero_pad = 0; + have_width = 0; + + if (*format == '0') + { + zero_pad = 1; + format ++; + } + + while (isdigit((unsigned char)*format)) + { + have_width = 1; + width = width * 10 + (*format - '0'); + format ++; + } + + // Only integer page-count substitutions are supported here. Emit any + // other format sequence literally instead of interpreting PPD data as a + // general printf format string. + if (*format == 'd' || *format == 'i' || *format == 'u') + { + if (zero_pad && have_width) + printf("%0*d", width, page_count); + else if (have_width) + printf("%*d", width, page_count); + else + printf("%d", page_count); + + format ++; + continue; + } + + putchar('%'); + + while (spec < format) + putchar(*spec++); + + if (*format) + putchar(*format++); + } +} + + // // 'pjl_write()' - Write a PJL command string, performing substitutions as // needed. diff --git a/filter/pcl-common.h b/filter/pcl-common.h index 490d7cb5b..3a4accf8e 100644 --- a/filter/pcl-common.h +++ b/filter/pcl-common.h @@ -58,8 +58,8 @@ printf("@PJL ENTER LANGUAGE=%s\r\n", (lang)) extern void pcl_set_media_size(ppd_file_t *ppd, float width, float length); +extern void pcl_write(const char *format, int page_count); extern void pjl_write(const char *format, const char *value, int job_id, const char *user, const char *title, int num_options, cups_option_t *options); - diff --git a/filter/rastertoescpx.c b/filter/rastertoescpx.c index f060db0b3..0916ca82c 100644 --- a/filter/rastertoescpx.c +++ b/filter/rastertoescpx.c @@ -94,6 +94,7 @@ void Setup(ppd_file_t *); void StartPage(ppd_file_t *, cups_page_header2_t *); void EndPage(ppd_file_t *, cups_page_header2_t *); void Shutdown(ppd_file_t *); +static int GetPrinterTop(ppd_file_t *, cups_page_header2_t *); void AddBand(cups_weave_t *band); void CancelJob(int sig); @@ -123,6 +124,30 @@ Setup(ppd_file_t *ppd) // I - PPD file } +// +// 'GetPrinterTop()' - Get the selected page size's top margin in device +// units. +// + +static int +GetPrinterTop(ppd_file_t *ppd, // I - PPD file + cups_page_header2_t *header) // I - Page header +{ + ppd_size_t *size; // Selected page size + + + if (!ppd || !header) + return (0); + + // Use the currently selected/default PageSize instead of assuming a + // particular entry exists in ppd->sizes[]. + if ((size = ppdPageSize(ppd, NULL)) == NULL) + return (0); + + return ((int)((size->length - size->top) * header->HWResolution[1] / 72.0)); +} + + // // 'StartPage()' - Start a page of graphics. // @@ -729,8 +754,7 @@ StartPage(ppd_file_t *ppd, // I - PPD file // Set the top and bottom margins... // - PrinterTop = (int)((ppd->sizes[1].length - ppd->sizes[1].top) * - header->HWResolution[1] / 72.0); + PrinterTop = GetPrinterTop(ppd, header); if (ppd->model_number & ESCP_EXT_MARGINS) { diff --git a/filter/rastertopclx.c b/filter/rastertopclx.c index ded86f114..1c90a8f0e 100644 --- a/filter/rastertopclx.c +++ b/filter/rastertopclx.c @@ -925,7 +925,9 @@ Shutdown(ppd_file_t *ppd, // I - PPD file // putchar(0x1b); - printf(attr->value, Page); + // EndJob comes from the PPD, so only restricted page-count formatting + // is allowed here. + pcl_write(attr->value, Page); } else { diff --git a/filter/test-rastertoescpx-page-size.c b/filter/test-rastertoescpx-page-size.c new file mode 100644 index 000000000..2afda0d96 --- /dev/null +++ b/filter/test-rastertoescpx-page-size.c @@ -0,0 +1,204 @@ +// +// Regression test for rastertoescpx page size selection. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include +#include +#include +#include + +#define main rastertoescpx_filter_main +#include "rastertoescpx.c" +#undef main + +static void +init_header(cups_page_header2_t *header, int y_resolution) +{ + memset(header, 0, sizeof(*header)); + header->HWResolution[1] = y_resolution; +} + +static void +init_size(ppd_size_t *size, int marked, float length, float top) +{ + memset(size, 0, sizeof(*size)); + size->marked = marked; + size->length = length; + size->top = top; +} + +static int +expect_printer_top(const char *label, + ppd_file_t *ppd, + cups_page_header2_t *header, + int expected) +{ + int actual = GetPrinterTop(ppd, header); + + if (actual != expected) + { + fprintf(stderr, "%s: expected %d, got %d\n", label, expected, actual); + return (0); + } + + return (1); +} + +static int +write_test_ppd(char *path, + size_t path_size) +{ + static const char *ppd_data = + "*PPD-Adobe: \"4.3\"\n" + "*FormatVersion: \"4.3\"\n" + "*FileVersion: \"1.0\"\n" + "*LanguageVersion: English\n" + "*LanguageEncoding: ISOLatin1\n" + "*PCFileName: \"TEST.PPD\"\n" + "*Manufacturer: \"Test\"\n" + "*Product: \"(Test RastertoESCPX)\"\n" + "*ModelName: \"Test RastertoESCPX\"\n" + "*ShortNickName: \"Test RastertoESCPX\"\n" + "*NickName: \"Test RastertoESCPX\"\n" + "*PSVersion: \"(3010.000) 0\"\n" + "*LanguageLevel: \"3\"\n" + "*ColorDevice: False\n" + "*DefaultColorSpace: Gray\n" + "*FileSystem: False\n" + "*Throughput: \"1\"\n" + "*LandscapeOrientation: Plus90\n" + "*TTRasterizer: Type42\n" + "*cupsModelNumber: 0\n" + "*OpenUI *PageSize/Page Size: PickOne\n" + "*DefaultPageSize: Letter\n" + "*PageSize Letter/US Letter: \"<>setpagedevice\"\n" + "*PageSize Legal/US Legal: \"<>setpagedevice\"\n" + "*CloseUI: *PageSize\n" + "*DefaultImageableArea: Letter\n" + "*ImageableArea Letter/US Letter: \"0 36 612 756\"\n" + "*ImageableArea Legal/US Legal: \"0 72 612 900\"\n" + "*DefaultPaperDimension: Letter\n" + "*PaperDimension Letter/US Letter: \"612 792\"\n" + "*PaperDimension Legal/US Legal: \"612 1008\"\n"; + int fd; + FILE *fp; + + + snprintf(path, path_size, "/tmp/testrastertoescpx-ppd-XXXXXX"); + + fd = mkstemp(path); + if (fd < 0) + { + perror("mkstemp"); + return (0); + } + + fp = fdopen(fd, "w"); + if (!fp) + { + perror("fdopen"); + close(fd); + unlink(path); + return (0); + } + + if (fputs(ppd_data, fp) == EOF || fclose(fp) == EOF) + { + perror("write test ppd"); + unlink(path); + return (0); + } + + return (1); +} + +static int +expect_marked_page_size(const char *label, + const char *option_string, + int y_resolution, + int expected) +{ + char ppd_path[] = "/tmp/testrastertoescpx-ppd-XXXXXX"; + cups_page_header2_t header; + cups_option_t *options = NULL; + ppd_file_t *ppd; + int num_options; + int status; + + + if (!write_test_ppd(ppd_path, sizeof(ppd_path))) + return (0); + + ppd = ppdOpenFile(ppd_path); + unlink(ppd_path); + + if (!ppd) + { + fputs("Unable to open generated test PPD.\n", stderr); + return (0); + } + + num_options = cupsParseOptions(option_string, 0, &options); + ppdMarkDefaults(ppd); + ppdMarkOptions(ppd, num_options, options); + + init_header(&header, y_resolution); + status = expect_printer_top(label, ppd, &header, expected); + + cupsFreeOptions(num_options, options); + ppdClose(ppd); + + return (status); +} + +int +main(void) +{ + cups_page_header2_t header; + ppd_file_t ppd; + ppd_size_t sizes[2]; + + memset(&ppd, 0, sizeof(ppd)); + memset(sizes, 0, sizeof(sizes)); + + ppd.sizes = sizes; + + init_header(&header, 72); + + init_size(&sizes[0], 1, 792.0f, 756.0f); + init_size(&sizes[1], 0, 1008.0f, 936.0f); + ppd.num_sizes = 2; + + if (!expect_printer_top("marked size", &ppd, &header, 36)) + return (1); + + // A canary second entry must not affect a single-size queue. + init_header(&header, 144); + init_size(&sizes[0], 1, 792.0f, 756.0f); + init_size(&sizes[1], 0, 1600.0f, 1400.0f); + ppd.num_sizes = 1; + + if (!expect_printer_top("single size", &ppd, &header, 72)) + return (1); + + sizes[0].marked = 0; + if (!expect_printer_top("no marked size", &ppd, &header, 0)) + return (1); + + if (!expect_printer_top("null ppd", NULL, &header, 0)) + return (1); + + if (!expect_printer_top("null header", &ppd, NULL, 0)) + return (1); + + if (!expect_marked_page_size("default page size", "", 72, 36)) + return (1); + + if (!expect_marked_page_size("selected page size", "PageSize=Legal", 72, 108)) + return (1); + + return (0); +} diff --git a/filter/test-rastertopclx-format-string.c b/filter/test-rastertopclx-format-string.c new file mode 100644 index 000000000..c296d0a72 --- /dev/null +++ b/filter/test-rastertopclx-format-string.c @@ -0,0 +1,247 @@ +// +// Regression test for rastertopclx EndJob format string handling. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include +#include +#include +#include +#include +#include + +#define main rastertopclx_filter_main +#include "rastertopclx.c" +#undef main + +static int +write_test_ppd(char *path, size_t path_size, const char *endjob_value) +{ + static const char *ppd_data = + "*PPD-Adobe: \"4.3\"\n" + "*FormatVersion: \"4.3\"\n" + "*FileVersion: \"1.0\"\n" + "*LanguageVersion: English\n" + "*LanguageEncoding: ISOLatin1\n" + "*PCFileName: \"TEST.PPD\"\n" + "*Manufacturer: \"Test\"\n" + "*Product: \"(Test RastertoPCLX)\"\n" + "*ModelName: \"Test RastertoPCLX\"\n" + "*ShortNickName: \"Test RastertoPCLX\"\n" + "*NickName: \"Test RastertoPCLX\"\n" + "*PSVersion: \"(3010.000) 0\"\n" + "*LanguageLevel: \"3\"\n" + "*ColorDevice: False\n" + "*DefaultColorSpace: Gray\n" + "*FileSystem: False\n" + "*Throughput: \"1\"\n" + "*LandscapeOrientation: Plus90\n" + "*TTRasterizer: Type42\n" + "*OpenUI *PageSize/Page Size: PickOne\n" + "*DefaultPageSize: Letter\n" + "*PageSize Letter/US Letter: \"<>setpagedevice\"\n" + "*CloseUI: *PageSize\n" + "*cupsPCL EndJob: \"%s\"\n"; + int fd; + FILE *fp; + + snprintf(path, path_size, "/tmp/rastertopclx-ppd-XXXXXX"); + + fd = mkstemp(path); + if (fd < 0) + { + perror("mkstemp"); + return (0); + } + + fp = fdopen(fd, "w"); + if (!fp) + { + perror("fdopen"); + close(fd); + unlink(path); + return (0); + } + + if (fprintf(fp, ppd_data, endjob_value) < 0 || fclose(fp) == EOF) + { + perror("write test ppd"); + unlink(path); + return (0); + } + + return (1); +} + +static int +capture_shutdown_output(ppd_file_t *ppd, char *output, size_t output_size) +{ + int pipefds[2]; + int saved_stdout; + ssize_t bytes_read; + size_t total = 0; + + if (pipe(pipefds) != 0) + { + perror("pipe"); + return (-1); + } + + saved_stdout = dup(STDOUT_FILENO); + if (saved_stdout < 0) + { + perror("dup"); + close(pipefds[0]); + close(pipefds[1]); + return (-1); + } + + fflush(stdout); + + if (dup2(pipefds[1], STDOUT_FILENO) < 0) + { + perror("dup2"); + close(saved_stdout); + close(pipefds[0]); + close(pipefds[1]); + return (-1); + } + + close(pipefds[1]); + + Page = 7; + Shutdown(ppd, 1, "user", "title", 0, NULL); + + fflush(stdout); + + if (dup2(saved_stdout, STDOUT_FILENO) < 0) + { + perror("dup2 restore"); + close(saved_stdout); + close(pipefds[0]); + return (-1); + } + + close(saved_stdout); + + while ((bytes_read = read(pipefds[0], output + total, + output_size - 1 - total)) > 0) + { + total += (size_t)bytes_read; + if (total >= output_size - 1) + break; + } + + close(pipefds[0]); + output[total] = '\0'; + + return ((int)total); +} + +static int +expect_literal_percent_s(void) +{ + char ppd_path[] = "/tmp/rastertopclx-ppd-XXXXXX"; + char output[256]; + ppd_file_t *ppd; + int output_len; + + if (!write_test_ppd(ppd_path, sizeof(ppd_path), "%s")) + return (1); + + ppd = ppdOpenFile(ppd_path); + unlink(ppd_path); + + if (!ppd) + { + fputs("Unable to open generated test PPD.\n", stderr); + return (1); + } + + output_len = capture_shutdown_output(ppd, output, sizeof(output)); + ppdClose(ppd); + + if (output_len < 0) + return (1); + + if (output_len < 3 || + output[0] != '\033' || + output[1] != '%' || + output[2] != 's') + { + fprintf(stderr, "Unexpected Shutdown output prefix (%d bytes): ", output_len); + for (int i = 0; i < output_len; i ++) + fprintf(stderr, "%02x ", (unsigned char)output[i]); + fputc('\n', stderr); + return (1); + } + + if (strstr(output, "%s") == NULL) + { + fputs("Expected literal \"%s\" in Shutdown output.\n", stderr); + return (1); + } + + return (0); +} + +static int +expect_page_count_substitution(void) +{ + char ppd_path[] = "/tmp/rastertopclx-ppd-XXXXXX"; + char output[256]; + ppd_file_t *ppd; + int output_len; + + if (!write_test_ppd(ppd_path, sizeof(ppd_path), "%02d")) + return (1); + + ppd = ppdOpenFile(ppd_path); + unlink(ppd_path); + + if (!ppd) + { + fputs("Unable to open generated substitution test PPD.\n", stderr); + return (1); + } + + output_len = capture_shutdown_output(ppd, output, sizeof(output)); + ppdClose(ppd); + + if (output_len < 0) + return (1); + + if (output_len < 3 || + output[0] != '\033' || + output[1] != '0' || + output[2] != '7') + { + fprintf(stderr, "Unexpected substituted output prefix (%d bytes): ", output_len); + for (int i = 0; i < output_len; i ++) + fprintf(stderr, "%02x ", (unsigned char)output[i]); + fputc('\n', stderr); + return (1); + } + + if (strstr(output, "%02d") != NULL) + { + fputs("Expected page count substitution for \"%02d\".\n", stderr); + return (1); + } + + return (0); +} + +int +main(void) +{ + if (expect_literal_percent_s()) + return (1); + + if (expect_page_count_substitution()) + return (1); + + return (0); +}