diff options
Diffstat (limited to 'filter/testclient.c')
-rw-r--r-- | filter/testclient.c | 839 |
1 files changed, 839 insertions, 0 deletions
diff --git a/filter/testclient.c b/filter/testclient.c new file mode 100644 index 000000000..d6ad4249c --- /dev/null +++ b/filter/testclient.c @@ -0,0 +1,839 @@ +/* + * Simulated client test program for CUPS. + * + * Copyright 2017 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 + * missing or damaged, see the license at "http://www.cups.org/". + * + * This file is subject to the Apple OS-Developed Software exception. + */ + +/* + * Include necessary headers... + */ + +#include <stdio.h> +#include <stdlib.h> +#include <cups/cups.h> +#include <cups/raster.h> +#include <cups/string-private.h> +#include <cups/thread-private.h> + + +/* + * Local types... + */ + +typedef struct _client_monitor_s +{ + const char *uri, /* Printer URI */ + *hostname, /* Hostname */ + *user, /* Username */ + *resource; /* Resource path */ + int port; /* Port number */ + http_encryption_t encryption; /* Use encryption? */ + ipp_pstate_t printer_state; /* Current printer state */ + char printer_state_reasons[1024]; + /* Current printer-state-reasons */ + int job_id; /* Job ID for submitted job */ + ipp_jstate_t job_state; /* Current job state */ + char job_state_reasons[1024]; + /* Current job-state-reasons */ +} _client_monitor_t; + + +/* + * Local functions... + */ + +static const char *make_raster_file(ipp_t *response, char *tempname, size_t tempsize, const char **format); +static void *monitor_printer(_client_monitor_t *monitor); +static void show_capabilities(ipp_t *response); +static void usage(void); + + +/* + * 'main()' - Main entry. + */ + +int /* O - Exit status */ +main(int argc, /* I - Number of command-line arguments */ + char *argv[]) /* I - Command-line arguments */ +{ + int i; /* Looping var */ + const char *opt, /* Current option */ + *uri = NULL, /* Printer URI */ + *printfile = NULL, + /* Print file */ + *printformat = NULL; + /* Print format */ + char tempfile[1024] = "", + /* Temporary file (if any) */ + scheme[32], /* URI scheme */ + userpass[256], /* Username:password */ + hostname[256], /* Hostname */ + resource[256]; /* Resource path */ + int port; /* Port number */ + http_encryption_t encryption; /* Encryption mode */ + _client_monitor_t monitor; /* Monitoring data */ + http_t *http; /* HTTP connection */ + ipp_t *request, /* IPP request */ + *response; /* IPP response */ + ipp_attribute_t *attr; /* IPP attribute */ + static const char * const pattrs[] = /* Printer attributes we are interested in */ + { + "job-template", + "printer-defaults", + "printer-description", + "media-col-database", + "media-col-ready" + }; + + + /* + * Parse command-line options... + */ + + for (i = 1; i < argc; i ++) + { + if (argv[i][0] == '-') + { + for (opt = argv[i] + 1; *opt; opt ++) + { + switch (*opt) + { + case 'f' : /* -f print-file */ + if (printfile) + { + puts("Print file can only be specified once."); + usage(); + return (1); + } + + i ++; + if (i >= argc) + { + puts("Expected print file after '-f'."); + usage(); + return (1); + } + + printfile = argv[i]; + break; + + default : + printf("Unknown option '-%c'.\n", *opt); + usage(); + return (1); + } + } + } + else if (uri || (strncmp(argv[i], "ipp://", 6) && strncmp(argv[i], "ipps://", 7))) + { + printf("Unknown command-line argument '%s'.\n", argv[i]); + usage(); + return (1); + } + else + uri = argv[i]; + } + + /* + * Make sure we have everything we need. + */ + + if (!uri) + { + puts("Expected printer URI."); + usage(); + return (1); + } + + /* + * Connect to the printer... + */ + + if (httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK) + { + printf("Bad printer URI '%s'.\n", uri); + return (1); + } + + if (!port) + port = IPP_PORT; + + if (!strcmp(scheme, "https") || !strcmp(scheme, "ipps")) + encryption = HTTP_ENCRYPTION_ALWAYS; + else + encryption = HTTP_ENCRYPTION_IF_REQUESTED; + + if ((http = httpConnect2(hostname, port, NULL, AF_UNSPEC, encryption, 1, 0, NULL)) == NULL) + { + printf("Unable to connect to '%s' on port %d: %s\n", hostname, port, cupsLastErrorString()); + return (1); + } + + /* + * Query printer status and capabilities... + */ + + request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); + ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", (int)(sizeof(pattrs) / sizeof(pattrs[0])), NULL, pattrs); + + response = cupsDoRequest(http, request, resource); + + show_capabilities(response); + + /* + * Now figure out what we will be printing... + */ + + if (printfile) + { + /* + * User specified a print file, figure out the format... + */ + + if ((opt = strrchr(printfile, '.')) != NULL) + { + /* + * Guess the format from the extension... + */ + + if (!strcmp(opt, ".jpg")) + printformat = "image/jpeg"; + else if (!strcmp(opt, ".pdf")) + printformat = "application/pdf"; + else if (!strcmp(opt, ".ps")) + printformat = "application/postscript"; + else if (!strcmp(opt, ".pwg")) + printformat = "image/pwg-raster"; + else if (!strcmp(opt, ".urf")) + printformat = "image/urf"; + else + printformat = "application/octet-stream"; + } + else + { + /* + * Tell the printer to auto-detect... + */ + + printformat = "application/octet-stream"; + } + } + else + { + /* + * No file specified, make something to test with... + */ + + if ((printfile = make_raster_file(response, tempfile, sizeof(tempfile), &printformat)) == NULL) + return (1); + } + + ippDelete(response); + + /* + * Start monitoring the printer in the background... + */ + + memset(&monitor, 0, sizeof(monitor)); + + monitor.uri = uri; + monitor.hostname = hostname; + monitor.resource = resource; + monitor.port = port; + monitor.encryption = encryption; + + _cupsThreadCreate((_cups_thread_func_t)monitor_printer, &monitor); + + /* + * Create the job and wait for completion... + */ + + request = ippNewRequest(IPP_OP_CREATE_JOB); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); + + if ((opt = strrchr(printfile, '/')) != NULL) + opt ++; + else + opt = printfile; + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL, opt); + + response = cupsDoRequest(http, request, resource); + + if (cupsLastError() >= IPP_STATUS_REDIRECTION_OTHER_SITE) + { + printf("Unable to create print job: %s\n", cupsLastErrorString()); + + monitor.job_state = IPP_JSTATE_ABORTED; + + goto cleanup; + } + + if ((attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) == NULL) + { + puts("No job-id returned in Create-Job request."); + + monitor.job_state = IPP_JSTATE_ABORTED; + + goto cleanup; + } + + monitor.job_id = ippGetInteger(attr, 0); + + ippDelete(response); + + request = ippNewRequest(IPP_OP_SEND_DOCUMENT); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); + ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", monitor.job_id); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL, printformat); + ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", 1); + + response = cupsDoFileRequest(http, request, resource, printfile); + + if (cupsLastError() >= IPP_STATUS_REDIRECTION_OTHER_SITE) + { + printf("Unable to print file: %s\n", cupsLastErrorString()); + + monitor.job_state = IPP_JSTATE_ABORTED; + + goto cleanup; + } + + while (monitor.job_state < IPP_JSTATE_CANCELED) + sleep(1); + + /* + * Cleanup after ourselves... + */ + + cleanup: + + httpClose(http); + + if (tempfile[0]) + unlink(tempfile); + + return (monitor.job_state == IPP_JSTATE_COMPLETED); +} + + +/* + * 'make_raster_file()' - Create a temporary raster file. + */ + +static const char * /* O - Print filename */ +make_raster_file(ipp_t *response, /* I - Printer attributes */ + char *tempname, /* I - Temporary filename buffer */ + size_t tempsize, /* I - Size of temp file buffer */ + const char **format) /* O - Print format */ +{ + int i, /* Looping var */ + count; /* Number of values */ + ipp_attribute_t *attr; /* Printer attribute */ + const char *type = NULL; /* Raster type (colorspace + bits) */ + pwg_media_t *media = NULL; /* Media size */ + int xdpi = 0, /* Horizontal resolution */ + ydpi = 0; /* Vertical resolution */ + int fd; /* Temporary file */ + cups_mode_t mode; /* Raster mode */ + cups_raster_t *ras; /* Raster stream */ + cups_page_header2_t header; /* Page header */ + unsigned char *line, /* Line of raster data */ + *lineptr; /* Pointer into line */ + unsigned y, /* Current position on page */ + xcount, ycount, /* Current count for X and Y */ + xrep, yrep, /* Repeat count for X and Y */ + xoff, yoff, /* Offsets for X and Y */ + yend; /* End Y value */ + int temprow, /* Row in template */ + tempcolor; /* Template color */ + const char *template; /* Pointer into template */ + const unsigned char *color; /* Current color */ + static const unsigned char colors[][3] = + { /* Colors for test */ + { 191, 191, 191 }, + { 127, 127, 127 }, + { 63, 63, 63 }, + { 0, 0, 0 }, + { 255, 0, 0 }, + { 255, 127, 0 }, + { 255, 255, 0 }, + { 127, 255, 0 }, + { 0, 255, 0 }, + { 0, 255, 127 }, + { 0, 255, 255 }, + { 0, 127, 255 }, + { 0, 0, 255 }, + { 127, 0, 255 }, + { 255, 0, 255 } + }; + static const char * const templates[] = + { /* Raster template */ + " CCC U U PPPP SSS TTTTT EEEEE SSS TTTTT 000 1 222 333 4 55555 66 77777 888 999 ", + "C C U U P P S S T E S S T 0 0 11 2 2 3 3 4 4 5 6 7 8 8 9 9 ", + "C U U P P S T E S T 0 0 1 2 3 4 4 5 6 7 8 8 9 9 ", + "C U U PPPP SSS ----- T EEEE SSS T 0 0 0 1 22 333 44444 555 6666 7 888 9999 ", + "C U U P S T E S T 0 0 1 2 3 4 5 6 6 7 8 8 9 ", + "C C U U P S S T E S S T 0 0 1 2 3 3 4 5 5 6 6 7 8 8 9 ", + " CCC UUU P SSS T EEEEE SSS T 000 111 22222 333 4 555 666 7 888 99 ", + " " + }; + + + /* + * Figure out the output format... + */ + + if ((attr = ippFindAttribute(response, "document-format-supported", IPP_TAG_MIMETYPE)) == NULL) + { + puts("No supported document formats, aborting."); + return (NULL); + } + + if (ippContainsString(attr, "image/urf")) + { + /* + * Apple Raster format... + */ + + *format = "image/urf"; + mode = CUPS_RASTER_WRITE_APPLE; + } + else if (ippContainsString(attr, "image/pwg-raster")) + { + /* + * PWG Raster format... + */ + + *format = "image/pwg-raster"; + mode = CUPS_RASTER_WRITE_PWG; + } + else + { + /* + * No supported raster format... + */ + + puts("Printer does not support Apple or PWG raster files, aborting."); + return (NULL); + } + + /* + * Figure out the the media, resolution, and color mode... + */ + + if ((attr = ippFindAttribute(response, "media-default", IPP_TAG_KEYWORD)) != NULL) + { + /* + * Use default media... + */ + + media = pwgMediaForPWG(ippGetString(attr, 0, NULL)); + } + else if ((attr = ippFindAttribute(response, "media-ready", IPP_TAG_KEYWORD)) != NULL) + { + /* + * Use ready media... + */ + + if (ippContainsString(attr, "na_letter_8.5x11in")) + media = pwgMediaForPWG("na_letter_8.5x11in"); + else if (ippContainsString(attr, "iso_a4_210x297mm")) + media = pwgMediaForPWG("iso_a4_210x297mm"); + else + media = pwgMediaForPWG(ippGetString(attr, 0, NULL)); + } + else + { + puts("No default or ready media reported by printer, aborting."); + return (NULL); + } + + if ((attr = ippFindAttribute(response, "urf-supported", IPP_TAG_KEYWORD)) != NULL) + { + for (i = 0, count = ippGetCount(attr); i < count; i ++) + { + const char *val = ippGetString(attr, i, NULL); + + if (val[0] == 'R') + xdpi = ydpi = atoi(val + 1); + else if (!strcmp(val, "W8") && !type) + type = "sgray_8"; + else if (!strcmp(val, "SRGB24")) + type = "srgb_8"; + } + } + else if ((attr = ippFindAttribute(response, "pwg-raster-document-resolution-supported", IPP_TAG_RESOLUTION)) != NULL) + { + for (i = 0, count = ippGetCount(attr); i < count; i ++) + { + int tempxdpi, tempydpi; + ipp_res_t tempunits; + + tempxdpi = ippGetResolution(attr, 0, &tempydpi, &tempunits); + + if (i == 0 || tempxdpi < xdpi || tempydpi < ydpi) + { + xdpi = tempxdpi; + ydpi = tempydpi; + } + } + + if ((attr = ippFindAttribute(response, "pwg-raster-document-type-supported", IPP_TAG_KEYWORD)) != NULL) + { + if (ippContainsString(attr, "srgb_8")) + type = "srgb_8"; + else if (ippContainsString(attr, "sgray_8")) + type = "sgray_8"; + } + } + + if (xdpi < 72 || ydpi < 72) + { + puts("No supported raster resolutions, aborting."); + return (NULL); + } + + if (!type) + { + puts("No supported color spaces or bit depths, aborting."); + return (NULL); + } + + /* + * Make the raster context and details... + */ + + if (!cupsRasterInitPWGHeader(&header, media, type, xdpi, ydpi, "one-sided", NULL)) + { + printf("Unable to initialize raster context: %s\n", cupsRasterErrorString()); + return (NULL); + } + + header.cupsInteger[CUPS_RASTER_PWG_TotalPageCount] = 1; + + if (header.cupsWidth > (4 * header.HWResolution[0])) + { + xoff = header.HWResolution[0] / 2; + yoff = header.HWResolution[1] / 2; + } + else + { + xoff = 0; + yoff = 0; + } + + xrep = (header.cupsWidth - 2 * xoff) / 140; + yrep = xrep * header.HWResolution[1] / header.HWResolution[0]; + yend = header.cupsHeight - yoff; + + /* + * Prepare the raster file... + */ + + if ((line = malloc(header.cupsBytesPerLine)) == NULL) + { + printf("Unable to allocate %u bytes for raster output: %s\n", header.cupsBytesPerLine, strerror(errno)); + return (NULL); + } + + if ((fd = cupsTempFd(tempname, (int)tempsize)) < 0) + { + printf("Unable to create temporary print file: %s\n", strerror(errno)); + return (NULL); + } + + if ((ras = cupsRasterOpen(fd, mode)) == NULL) + { + printf("Unable to open raster stream: %s\n", cupsRasterErrorString()); + close(fd); + return (NULL); + } + + /* + * Write a single page consisting of the template dots repeated over the page. + */ + + cupsRasterWriteHeader2(ras, &header); + + memset(line, 0xff, header.cupsBytesPerLine); + + for (y = 0; y < yoff; y ++) + cupsRasterWritePixels(ras, line, header.cupsBytesPerLine); + + for (temprow = 0, tempcolor = 0; y < yend; y ++) + { + template = templates[temprow]; + color = colors[tempcolor]; + + temprow ++; + if (temprow >= (int)(sizeof(templates) / sizeof(templates[0]))) + { + temprow = 0; + tempcolor ++; + if (tempcolor >= (int)(sizeof(colors) / sizeof(colors[0]))) + tempcolor = 0; + } + + memset(line, 0xff, header.cupsBytesPerLine); + + if (header.cupsColorSpace == CUPS_CSPACE_SW) + { + /* + * Do grayscale output... + */ + + for (lineptr = line + xoff; *template; template ++) + { + if (*template != ' ') + { + for (xcount = xrep; xcount > 0; xcount --) + *lineptr++ = *color; + } + else + { + lineptr += xrep; + } + } + } + else + { + /* + * Do color output... + */ + + for (lineptr = line + 3 * xoff; *template; template ++) + { + if (*template != ' ') + { + for (xcount = xrep; xcount > 0; xcount --, lineptr += 3) + memcpy(lineptr, color, 3); + } + else + { + lineptr += 3 * xrep; + } + } + } + + for (ycount = yrep; ycount > 0 && y < yend; ycount --, y ++) + cupsRasterWritePixels(ras, line, header.cupsBytesPerLine); + } + + memset(line, 0xff, header.cupsBytesPerLine); + + for (y = 0; y < header.cupsHeight; y ++) + cupsRasterWritePixels(ras, line, header.cupsBytesPerLine); + + cupsRasterClose(ras); + + close(fd); + + printf("PRINT FILE: %s\n", tempname); + + return (tempname); +} + + +/* + * 'monitor_printer()' - Monitor the job and printer states. + */ + +static void * /* O - Thread exit code */ +monitor_printer( + _client_monitor_t *monitor) /* I - Monitoring data */ +{ + http_t *http; /* Connection to printer */ + ipp_t *request, /* IPP request */ + *response; /* IPP response */ + ipp_attribute_t *attr; /* Attribute in response */ + ipp_pstate_t printer_state; /* Printer state */ + char printer_state_reasons[1024]; + /* Printer state reasons */ + ipp_jstate_t job_state; /* Job state */ + char job_state_reasons[1024];/* Printer state reasons */ + static const char * const jattrs[] = /* Job attributes we want */ + { + "job-state", + "job-state-reasons" + }; + static const char * const pattrs[] = /* Printer attributes we want */ + { + "printer-state", + "printer-state-reasons" + }; + + + /* + * Open a connection to the printer... + */ + + http = httpConnect2(monitor->hostname, monitor->port, NULL, AF_UNSPEC, monitor->encryption, 1, 0, NULL); + + /* + * Loop until the job is canceled, aborted, or completed. + */ + + printer_state = (ipp_pstate_t)0; + printer_state_reasons[0] = '\0'; + + job_state = (ipp_jstate_t)0; + job_state_reasons[0] = '\0'; + + while (monitor->job_state < IPP_JSTATE_CANCELED) + { + /* + * Reconnect to the printer as needed... + */ + + if (httpGetFd(http) < 0) + httpReconnect2(http, 30000, NULL); + + if (httpGetFd(http) >= 0) + { + /* + * Connected, so check on the printer state... + */ + + request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, monitor->uri); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); + ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", (int)(sizeof(pattrs) / sizeof(pattrs[0])), NULL, pattrs); + + response = cupsDoRequest(http, request, monitor->resource); + + if ((attr = ippFindAttribute(response, "printer-state", IPP_TAG_ENUM)) != NULL) + printer_state = (ipp_pstate_t)ippGetInteger(attr, 0); + + if ((attr = ippFindAttribute(response, "printer-state-reasons", IPP_TAG_KEYWORD)) != NULL) + ippAttributeString(attr, printer_state_reasons, sizeof(printer_state_reasons)); + + if (printer_state != monitor->printer_state || strcmp(printer_state_reasons, monitor->printer_state_reasons)) + { + printf("PRINTER: %s (%s)\n", ippEnumString("printer-state", printer_state), printer_state_reasons); + + monitor->printer_state = printer_state; + strlcpy(monitor->printer_state_reasons, printer_state_reasons, sizeof(monitor->printer_state_reasons)); + } + + ippDelete(response); + + if (monitor->job_id > 0) + { + /* + * Check the status of the job itself... + */ + + request = ippNewRequest(IPP_OP_GET_JOB_ATTRIBUTES); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, monitor->uri); + ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", monitor->job_id); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); + ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", (int)(sizeof(jattrs) / sizeof(jattrs[0])), NULL, jattrs); + + response = cupsDoRequest(http, request, monitor->resource); + + if ((attr = ippFindAttribute(response, "job-state", IPP_TAG_ENUM)) != NULL) + job_state = (ipp_jstate_t)ippGetInteger(attr, 0); + + if ((attr = ippFindAttribute(response, "job-state-reasons", IPP_TAG_KEYWORD)) != NULL) + ippAttributeString(attr, job_state_reasons, sizeof(job_state_reasons)); + + if (job_state != monitor->job_state || strcmp(job_state_reasons, monitor->job_state_reasons)) + { + printf("JOB %d: %s (%s)\n", monitor->job_id, ippEnumString("job-state", job_state), job_state_reasons); + + monitor->job_state = job_state; + strlcpy(monitor->job_state_reasons, job_state_reasons, sizeof(monitor->job_state_reasons)); + } + + ippDelete(response); + } + } + + if (monitor->job_state < IPP_JSTATE_CANCELED) + { + /* + * Sleep for 5 seconds... + */ + + sleep(5); + } + } + + /* + * Cleanup and return... + */ + + httpClose(http); + + return (NULL); +} + + +/* + * 'show_capabilities()' - Show printer capabilities. + */ + +static void +show_capabilities(ipp_t *response) /* I - Printer attributes */ +{ + int i; /* Looping var */ + ipp_attribute_t *attr; /* Attribute */ + char buffer[1024]; /* Attribute value buffer */ + static const char * const pattrs[] = /* Attributes we want to show */ + { + "copies-default", + "copies-supported", + "finishings-default", + "finishings-ready", + "finishings-supported", + "media-default", + "media-ready", + "media-supported", + "output-bin-default", + "output-bin-supported", + "print-color-mode-default", + "print-color-mode-supported", + "sides-default", + "sides-supported", + "document-format-default", + "document-format-supported", + "pwg-raster-document-resolution-supported", + "pwg-raster-document-type-supported", + "urf-supported" + }; + + + puts("CAPABILITIES:"); + for (i = 0; i < (int)(sizeof(pattrs) / sizeof(pattrs[0])); i ++) + { + if ((attr = ippFindAttribute(response, pattrs[i], IPP_TAG_ZERO)) != NULL) + { + ippAttributeString(attr, buffer, sizeof(buffer)); + printf(" %s=%s\n", pattrs[i], buffer); + } + } +} + + +/* + * 'usage()' - Show program usage... + */ + +static void +usage(void) +{ + puts("Usage: ./testclient printer-uri [-f print-file]"); +} |