Home | History | Annotate | Download | only in ipphelper
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  * Copyright (C) 2016 Mopria Alliance, Inc.
      4  * Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
      5  *
      6  * Licensed under the Apache License, Version 2.0 (the "License");
      7  * you may not use this file except in compliance with the License.
      8  * You may obtain a copy of the License at
      9  *
     10  *      http://www.apache.org/licenses/LICENSE-2.0
     11  *
     12  * Unless required by applicable law or agreed to in writing, software
     13  * distributed under the License is distributed on an "AS IS" BASIS,
     14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15  * See the License for the specific language governing permissions and
     16  * limitations under the License.
     17  */
     18 
     19 #include "ipp_print.h"
     20 #include <math.h>
     21 #include "ipphelper.h"
     22 #include "wprint_debug.h"
     23 
     24 #include "plugins/media.h"
     25 
     26 #define TAG "ipp_print"
     27 
     28 static status_t _init(const ifc_print_job_t *this_p, const char *printer_address, int port,
     29         const char *printer_uri, bool use_secure_uri);
     30 
     31 static status_t _validate_job(const ifc_print_job_t *this_p, wprint_job_params_t *job_params);
     32 
     33 static status_t _start_job(const ifc_print_job_t *this_p, const wprint_job_params_t *job_params);
     34 
     35 static int _send_data(const ifc_print_job_t *this_p, const char *buffer, size_t length);
     36 
     37 static status_t _end_job(const ifc_print_job_t *this_p);
     38 
     39 static void _destroy(const ifc_print_job_t *this_p);
     40 
     41 static const ifc_print_job_t _print_job_ifc = {
     42         .init = _init, .validate_job = _validate_job, .start_job = _start_job,
     43         .send_data = _send_data, .end_job = _end_job, .destroy = _destroy, .enable_timeout = NULL,
     44 };
     45 
     46 /*
     47  * Struct for handling an ipp print job
     48  */
     49 typedef struct {
     50     http_t *http;
     51     char printer_uri[1024];
     52     char http_resource[1024];
     53     http_status_t status;
     54     ifc_print_job_t ifc;
     55     const char *useragent;
     56 } ipp_print_job_t;
     57 
     58 /*
     59  * Returns a print job handle for an ipp print job
     60  */
     61 const ifc_print_job_t *ipp_get_print_ifc(const ifc_wprint_t *wprint_ifc) {
     62     LOGD("ipp_get_print_ifc: Enter");
     63     ipp_print_job_t *ipp_job = (ipp_print_job_t *) malloc(sizeof(ipp_print_job_t));
     64 
     65     if (ipp_job == NULL) {
     66         return NULL;
     67     }
     68 
     69     memset(ipp_job, 0, sizeof(ipp_print_job_t));
     70     ipp_job->status = HTTP_CONTINUE;
     71 
     72     memcpy(&ipp_job->ifc, &_print_job_ifc, sizeof(ifc_print_job_t));
     73 
     74     return &ipp_job->ifc;
     75 }
     76 
     77 static status_t _init(const ifc_print_job_t *this_p, const char *printer_address, int port,
     78         const char *printer_uri, bool use_secure_uri) {
     79     LOGD("_init: Enter");
     80     ipp_print_job_t *ipp_job;
     81     const char *ipp_scheme;
     82 
     83     if (this_p == NULL) {
     84         return ERROR;
     85     }
     86 
     87     ipp_job = IMPL(ipp_print_job_t, ifc, this_p);
     88     if (ipp_job->http != NULL) {
     89         httpClose(ipp_job->http);
     90     }
     91 
     92     if ((printer_uri == NULL) || (strlen(printer_uri) == 0)) {
     93         printer_uri = DEFAULT_IPP_URI_RESOURCE;
     94     }
     95 
     96     int ippPortNumber = ((port == IPP_PORT) ? ippPort() : port);
     97     LOGD("Normal URI for %s:%d", printer_address, ippPortNumber);
     98     ipp_scheme = (use_secure_uri) ? IPPS_PREFIX : IPP_PREFIX;
     99 
    100     httpAssembleURIf(HTTP_URI_CODING_ALL, ipp_job->printer_uri, sizeof(ipp_job->printer_uri),
    101             ipp_scheme, NULL, printer_address, ippPortNumber, printer_uri);
    102     getResourceFromURI(ipp_job->printer_uri, ipp_job->http_resource, 1024);
    103     if (use_secure_uri) {
    104         ipp_job->http = httpConnectEncrypt(printer_address, ippPortNumber, HTTP_ENCRYPTION_ALWAYS);
    105 
    106         // If ALWAYS doesn't work, fall back to REQUIRED
    107         if (ipp_job->http == NULL) {
    108             ipp_job->http = httpConnectEncrypt(printer_address, ippPortNumber, HTTP_ENCRYPT_REQUIRED);
    109         }
    110     } else {
    111         ipp_job->http = httpConnectEncrypt(printer_address, ippPortNumber, HTTP_ENCRYPTION_IF_REQUESTED);
    112     }
    113 
    114     httpSetTimeout(ipp_job->http, DEFAULT_IPP_TIMEOUT, NULL, 0);
    115 
    116     return OK;
    117 }
    118 
    119 static void _destroy(const ifc_print_job_t *this_p) {
    120     LOGD("_destroy: Enter");
    121     ipp_print_job_t *ipp_job;
    122     if (this_p == NULL) {
    123         return;
    124     }
    125 
    126     ipp_job = IMPL(ipp_print_job_t, ifc, this_p);
    127     if (ipp_job->http != NULL) {
    128         httpClose(ipp_job->http);
    129     }
    130 
    131     free(ipp_job);
    132 }
    133 
    134 /*
    135  * Outputs width, height, and name for a given media size
    136  */
    137 static void _get_pwg_media_size(media_size_t media_size, float *mediaWidth, float *mediaHeight,
    138         const char **mediaSizeName) {
    139     int i = 0;
    140 
    141     for (i = 0; i < SUPPORTED_MEDIA_SIZE_COUNT; i++) {
    142         if (media_size == SupportedMediaSizes[i].media_size) {
    143             // Get the dimensions in 100 mm
    144             if ((SupportedMediaSizes[i].WidthInMm == UNKNOWN_VALUE) ||
    145                     (SupportedMediaSizes[i].HeightInMm == UNKNOWN_VALUE)) {
    146                 *mediaWidth = floorf(_MI_TO_100MM(SupportedMediaSizes[i].WidthInInches));
    147                 *mediaHeight = floorf(_MI_TO_100MM(SupportedMediaSizes[i].HeightInInches));
    148             } else {
    149                 *mediaWidth = SupportedMediaSizes[i].WidthInMm * 100;
    150                 *mediaHeight = SupportedMediaSizes[i].HeightInMm * 100;
    151             }
    152             *mediaSizeName = (char *) SupportedMediaSizes[i].PWGName;
    153 
    154             LOGD("_get_pwg_media_size(): match found: %d, %s, width=%f, height=%f",
    155                     media_size, SupportedMediaSizes[i].PCL6Name, *mediaWidth, *mediaHeight);
    156             break;  // we found a match, so break out of loop
    157         }
    158     }
    159 
    160     if (i == SUPPORTED_MEDIA_SIZE_COUNT) {
    161         // media size not found, defaulting to letter
    162         LOGD("_get_pwg_media_size(): media size, %d, NOT FOUND, setting to letter", media_size);
    163         _get_pwg_media_size(US_LETTER, mediaWidth, mediaHeight, mediaSizeName);
    164     }
    165 }
    166 
    167 /*
    168  * Fills and returns an ipp request object with the given job parameters
    169  */
    170 static ipp_t *_fill_job(int ipp_op, char *printer_uri, const wprint_job_params_t *job_params) {
    171     LOGD("_fill_job: Enter");
    172     ipp_t *request = NULL; // IPP request object
    173     ipp_attribute_t *attrptr; // Attribute pointer
    174     ipp_t *col[2];
    175     int col_index = -1;
    176 
    177     if (job_params == NULL) return NULL;
    178 
    179     request = ippNewRequest(ipp_op);
    180     if (request == NULL) {
    181         return request;
    182     }
    183 
    184     if (set_ipp_version(request, printer_uri, NULL, IPP_VERSION_RESOLVED) != 0) {
    185         ippDelete(request);
    186         return NULL;
    187     }
    188     bool is_2_0_capable = job_params->ipp_2_0_supported;
    189     bool is_ePCL_ipp_capable = job_params->epcl_ipp_supported;
    190 
    191     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
    192             printer_uri);
    193 
    194     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL,
    195             job_params->job_originating_user_name);
    196     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL, job_params->job_name);
    197 
    198     // Fields for Document source application and source OS
    199     bool is_doc_format_details_supported = (
    200             job_params->accepts_app_name ||
    201                     job_params->accepts_app_version ||
    202                     job_params->accepts_os_name ||
    203                     job_params->accepts_os_version);
    204 
    205     if (is_doc_format_details_supported) {
    206         ipp_t *document_format_details = ippNew();
    207         if (job_params->accepts_app_name) {
    208             ippAddString(document_format_details, IPP_TAG_OPERATION, IPP_TAG_NAME,
    209                     "document-source-application-name", NULL, g_appName);
    210         }
    211         if (job_params->accepts_app_version) {
    212             ippAddString(document_format_details, IPP_TAG_OPERATION, IPP_TAG_TEXT,
    213                     "document-source-application-version", NULL, g_appVersion);
    214         }
    215         if (job_params->accepts_os_name) {
    216             ippAddString(document_format_details, IPP_TAG_OPERATION, IPP_TAG_NAME,
    217                     "document-source-os-name", NULL, g_osName);
    218         }
    219         if (job_params->accepts_os_version) {
    220             char version[40];
    221             sprintf(version, "%d", g_API_version);
    222             ippAddString(document_format_details, IPP_TAG_OPERATION, IPP_TAG_TEXT,
    223                     "document-source-os-version", NULL, version);
    224         }
    225 
    226         ippAddCollection(request, IPP_TAG_OPERATION, "document-format-details",
    227                 document_format_details);
    228         ippDelete(document_format_details);
    229     }
    230 
    231     LOGD("_fill_job: pcl_type(%d), print_format(%s)", job_params->pcl_type,
    232             job_params->print_format);
    233     if (strcmp(job_params->print_format, PRINT_FORMAT_PDF) == 0) {
    234         if (is_2_0_capable) {
    235             // document-format needs to be the very next attribute for some printers
    236             ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL,
    237                     PRINT_FORMAT_PDF);
    238             LOGD("_fill_job: setting document-format: %s", PRINT_FORMAT_PDF);
    239         } else {
    240             // some earlier devices don't print pdfs when we send the other PRINT_FORMAT_PDF
    241             ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL,
    242                     (job_params->accepts_pclm ? PRINT_FORMAT_PCLM : PRINT_FORMAT_PDF));
    243             LOGD("_fill_job: setting document-format: %s",
    244                     (job_params->accepts_pclm ? PRINT_FORMAT_PCLM : PRINT_FORMAT_PDF));
    245         }
    246 
    247         if (is_ePCL_ipp_capable) {
    248             if (job_params->render_flags & RENDER_FLAG_AUTO_SCALE) {
    249                 ippAddBoolean(request, IPP_TAG_JOB, "pdf-fit-to-page", 1); // true
    250             }
    251         }
    252 
    253         // Fix Orientation bug for PDF printers only.
    254         if (job_params->render_flags & RENDER_FLAG_PORTRAIT_MODE) {
    255             ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_ENUM, "orientation-requested",
    256                     IPP_PRINT_ORIENTATION_PORTRAIT);
    257         }
    258         if (job_params->render_flags & RENDER_FLAG_LANDSCAPE_MODE) {
    259             ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_ENUM, "orientation-requested",
    260                     IPP_PRINT_ORIENTATION_LANDSCAPE);
    261         }
    262     } else {
    263         switch (job_params->pcl_type) {
    264             case PCLm:
    265                 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL,
    266                         PRINT_FORMAT_PCLM);
    267                 LOGD("_fill_job: setting document-format: %s", PRINT_FORMAT_PCLM);
    268                 if (is_ePCL_ipp_capable) {
    269                     ippAddResolution(request, IPP_TAG_JOB, "pclm-source-resolution",
    270                             IPP_RES_PER_INCH, job_params->pixel_units,
    271                             job_params->pixel_units);
    272                 }
    273                 break;
    274             case PCLPWG:
    275                 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL,
    276                         PRINT_FORMAT_PWG);
    277                 LOGD("_fill_job: setting document-format: %s", PRINT_FORMAT_PWG);
    278                 break;
    279             default:
    280                 LOGD("_fill_job: unrecognized pcl_type: %d", job_params->pcl_type);
    281                 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL,
    282                         PRINT_FORMAT_AUTO);
    283                 break;
    284         }
    285 
    286         if (is_ePCL_ipp_capable) {
    287             ippAddBoolean(request, IPP_TAG_JOB, "margins-pre-applied", 1); // true
    288         }
    289     }
    290 
    291     // Add copies support if required and allowed
    292     if (job_params->copies_supported && (strcmp(job_params->print_format, PRINT_FORMAT_PDF) == 0)) {
    293         ippAddInteger(request, IPP_TAG_JOB, IPP_TAG_INTEGER, "copies", job_params->num_copies);
    294     }
    295 
    296     ippAddResolution(request, IPP_TAG_JOB, "printer-resolution", IPP_RES_PER_INCH,
    297             job_params->pixel_units, job_params->pixel_units);
    298     if (job_params->duplex == DUPLEX_MODE_BOOK) {
    299         ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, IPP_SIDES_TAG, NULL,
    300                 IPP_SIDES_TWO_SIDED_LONG_EDGE);
    301     } else if (job_params->duplex == DUPLEX_MODE_TABLET) {
    302         ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, IPP_SIDES_TAG, NULL,
    303                 IPP_SIDES_TWO_SIDED_SHORT_EDGE);
    304     } else {
    305         ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, IPP_SIDES_TAG, NULL,
    306                 IPP_SIDES_ONE_SIDED);
    307     }
    308 
    309     if (job_params->color_space == COLOR_SPACE_MONO) {
    310         ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, IPP_OUTPUT_MODE_TAG, NULL,
    311                 IPP_OUTPUT_MODE_MONO);
    312     } else {
    313         ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, IPP_OUTPUT_MODE_TAG, NULL,
    314                 IPP_OUTPUT_MODE_COLOR);
    315     }
    316 
    317     if (is_2_0_capable) {
    318         // Not friendly to 1.1 devices.
    319         if (job_params->media_tray != TRAY_SRC_AUTO_SELECT) {
    320             if (job_params->media_tray == TRAY_SOURCE_PHOTO_TRAY) {
    321                 col[++col_index] = ippNew();
    322                 ippAddString(col[col_index], IPP_TAG_JOB, IPP_TAG_KEYWORD, "media-source", NULL,
    323                         "main-tray");
    324             } else if (job_params->media_tray == TRAY_SOURCE_TRAY_1) {
    325                 col[++col_index] = ippNew();
    326                 ippAddString(col[col_index], IPP_TAG_JOB, IPP_TAG_KEYWORD, "media-source", NULL,
    327                         "main-tray");
    328             }
    329         }
    330 
    331         // MEDIA-Type is IPP 2.0 only
    332         // put margins in with media-type
    333         // Client-Error-Attribute-Or-Values-Not-Supported
    334         col[++col_index] = ippNew();
    335 
    336         // set margins - if negative margins, set to full-bleed; otherwise set calculated values
    337         if (job_params->borderless) {
    338             LOGD("Setting Up BORDERLESS");
    339             ippAddInteger(col[col_index], IPP_TAG_JOB, IPP_TAG_INTEGER, "media-bottom-margin", 0);
    340             ippAddInteger(col[col_index], IPP_TAG_JOB, IPP_TAG_INTEGER, "media-top-margin", 0);
    341             ippAddInteger(col[col_index], IPP_TAG_JOB, IPP_TAG_INTEGER, "media-left-margin", 0);
    342             ippAddInteger(col[col_index], IPP_TAG_JOB, IPP_TAG_INTEGER, "media-right-margin", 0);
    343         }
    344 
    345         switch (job_params->media_type) {
    346             case MEDIA_PHOTO_GLOSSY:
    347                 ippAddString(col[col_index], IPP_TAG_JOB, IPP_TAG_KEYWORD, "media-type", NULL,
    348                         "photographic-glossy");
    349                 break;
    350             case MEDIA_PHOTO:
    351             case MEDIA_ADVANCED_PHOTO:
    352             case MEDIA_PHOTO_MATTE:
    353             case MEDIA_PREMIUM_PHOTO:
    354             case MEDIA_OTHER_PHOTO:
    355                 ippAddString(col[col_index], IPP_TAG_JOB, IPP_TAG_KEYWORD, "media-type", NULL,
    356                         "photographic");
    357                 break;
    358             default:
    359                 ippAddString(col[col_index], IPP_TAG_JOB, IPP_TAG_KEYWORD, "media-type", NULL,
    360                         "stationery");
    361                 break;
    362         }
    363 
    364         float mediaWidth;
    365         float mediaHeight;
    366         const char *mediaSizeName = NULL;
    367         _get_pwg_media_size(job_params->media_size, &mediaWidth, &mediaHeight, &mediaSizeName);
    368         ipp_t *mediaSize = ippNew();
    369 
    370         if ((job_params->media_size_name) && (mediaSizeName != NULL)) {
    371             ippAddString(mediaSize, IPP_TAG_JOB, IPP_TAG_KEYWORD, "media-size-name", NULL,
    372                     mediaSizeName);
    373         } else {
    374             ippAddInteger(mediaSize, IPP_TAG_JOB, IPP_TAG_INTEGER, "x-dimension", (int) mediaWidth);
    375             ippAddInteger(mediaSize, IPP_TAG_JOB, IPP_TAG_INTEGER, "y-dimension",
    376                     (int) mediaHeight);
    377         }
    378         ippAddCollection(col[col_index], IPP_TAG_JOB, "media-size", mediaSize);
    379 
    380         // can either set media or media-col.
    381         // if both sent, device should return client-error-bad-request
    382         ippAddCollections(request, IPP_TAG_JOB, "media-col", col_index + 1, (const ipp_t **) col);
    383         while (col_index >= 0) {
    384             ippDelete(col[col_index--]);
    385         }
    386     } else {
    387         ippAddString(request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "media", NULL,
    388                 mapDFMediaToIPPKeyword(job_params->media_size));
    389     }
    390 
    391     LOGI("_fill_job (%d): request", ipp_op);
    392     for (attrptr = ippFirstAttribute(request); attrptr; attrptr = ippNextAttribute(request)) {
    393         print_attr(attrptr);
    394     }
    395 
    396     return request;
    397 }
    398 
    399 static status_t  _validate_job(const ifc_print_job_t *this_p, wprint_job_params_t *job_params) {
    400     LOGD("_validate_job: Enter");
    401     status_t result = ERROR;
    402     ipp_print_job_t *ipp_job;
    403     ipp_t *response;
    404     ipp_t *request = NULL;
    405     ipp_status_t ipp_status;
    406 
    407     LOGD("_validate_job: ** validatePrintJob:  Entry");
    408     do {
    409         if (this_p == NULL) {
    410             break;
    411         }
    412 
    413         if (job_params == NULL) {
    414             break;
    415         }
    416 
    417         ipp_job = IMPL(ipp_print_job_t, ifc, this_p);
    418         if (ipp_job->http == NULL) {
    419             break;
    420         }
    421 
    422         ipp_job->useragent = NULL;
    423         if ((job_params->useragent != NULL) && (strlen(job_params->useragent) > 0)) {
    424             ipp_job->useragent = job_params->useragent;
    425         }
    426 
    427         request = _fill_job(IPP_VALIDATE_JOB, ipp_job->printer_uri, job_params);
    428 
    429         if (ipp_job->useragent != NULL) {
    430             httpSetDefaultField(ipp_job->http, HTTP_FIELD_USER_AGENT, ipp_job->useragent);
    431         }
    432         if ((response = ipp_doCupsRequest(ipp_job->http, request, ipp_job->http_resource,
    433                 ipp_job->printer_uri))
    434                 == NULL) {
    435             ipp_status = cupsLastError();
    436             LOGE("_validate_job:  validatePrintJob:  response is null:  ipp_status %d %s",
    437                     ipp_status, ippErrorString(ipp_status));
    438         } else {
    439             ipp_status = cupsLastError();
    440             LOGI("_validate_job: %s ipp_status %d  %x received:", ippOpString(IPP_VALIDATE_JOB),
    441                     ipp_status, ipp_status);
    442             ipp_attribute_t *attrptr;
    443             for (attrptr = ippFirstAttribute(response); attrptr; attrptr = ippNextAttribute(
    444                     response)) {
    445                 print_attr(attrptr);
    446             }
    447 
    448             ippDelete(response);
    449         }
    450 
    451         LOGD("_validate_job : ipp_status: %d", ipp_status);
    452         if (strncmp(ippErrorString(ipp_status), ippErrorString(IPP_OK),
    453                 strlen(ippErrorString(IPP_OK))) == 0) {
    454             result = OK;
    455         } else {
    456             result = ERROR;
    457         }
    458     } while (0);
    459 
    460     ippDelete(request);
    461 
    462     LOGD("_validate_job: ** validate_job result: %d", result);
    463 
    464     return result;
    465 }
    466 
    467 static status_t _start_job(const ifc_print_job_t *this_p, const wprint_job_params_t *job_params) {
    468     LOGD("_start_job: Enter");
    469     status_t result;
    470     ipp_print_job_t *ipp_job;
    471     ipp_t *request = NULL;
    472     bool retry;
    473     int failed_count = 0;
    474 
    475     LOGD("_start_job entry");
    476     do {
    477         retry = false;
    478         if (this_p == NULL) {
    479             LOGE("_start_job; this_p == NULL");
    480             continue;
    481         }
    482 
    483         ipp_job = IMPL(ipp_print_job_t, ifc, this_p);
    484 
    485         ipp_job->useragent = NULL;
    486         if ((job_params->useragent != NULL) && (strlen(job_params->useragent) > 0)) {
    487             ipp_job->useragent = job_params->useragent;
    488         }
    489         request = _fill_job(IPP_PRINT_JOB, ipp_job->printer_uri, job_params);
    490 
    491         if (request == NULL) {
    492             continue;
    493         }
    494 
    495         if (ipp_job->useragent != NULL) {
    496             httpSetDefaultField(ipp_job->http, HTTP_FIELD_USER_AGENT, ipp_job->useragent);
    497         }
    498         ipp_job->status = cupsSendRequest(ipp_job->http, request, ipp_job->http_resource, 0);
    499         if (ipp_job->status != HTTP_CONTINUE) {
    500             failed_count++;
    501             if ((failed_count == 1) &&
    502                     ((ipp_job->status == HTTP_ERROR) || (ipp_job->status >= HTTP_BAD_REQUEST))) {
    503                 retry = true;
    504                 LOGI("_start_job retry due to internal error");
    505                 // We will retry for one of these failures since we could have just
    506                 // lost our connection to the server and cups will not always attempt
    507                 // a reconnect for us.
    508                 ippDelete(request);
    509                 continue;
    510             }
    511 
    512             _cupsSetHTTPError(ipp_job->status);
    513         }
    514         ippDelete(request);
    515         LOGI("_start_job httpPrint fd %d status %d ipp_status %d", ipp_job->http->fd,
    516                 ipp_job->status, cupsLastError());
    517 
    518         result = ((ipp_job->status == HTTP_CONTINUE) ? OK : ERROR);
    519     } while (retry);
    520 
    521     return result;
    522 }
    523 
    524 static int _send_data(const ifc_print_job_t *this_p, const char *buffer, size_t length) {
    525     ipp_print_job_t *ipp_job;
    526     if (this_p == NULL) {
    527         return ERROR;
    528     }
    529 
    530     ipp_job = IMPL(ipp_print_job_t, ifc, this_p);
    531     if (ipp_job->http == NULL) {
    532         return ERROR;
    533     }
    534 
    535     if (ipp_job->status != HTTP_CONTINUE) {
    536         return ERROR;
    537     }
    538 
    539     if (length != 0) {
    540         if (ipp_job->useragent != NULL) {
    541             httpSetDefaultField(ipp_job->http, HTTP_FIELD_USER_AGENT, ipp_job->useragent);
    542         }
    543         ipp_job->status = cupsWriteRequestData(ipp_job->http, buffer, length);
    544     }
    545     return ((ipp_job->status == HTTP_CONTINUE) ? length : (int) ERROR);
    546 }
    547 
    548 static status_t _end_job(const ifc_print_job_t *this_p) {
    549     LOGD("_end_job: Enter");
    550     status_t result = ERROR;
    551     ipp_t *response;
    552     ipp_attribute_t *attrptr;
    553     int op = IPP_PRINT_JOB;
    554     ipp_print_job_t *ipp_job;
    555     int job_id = -1;
    556 
    557     char buffer[1024];
    558 
    559     if (this_p == NULL) {
    560         return result;
    561     }
    562 
    563     ipp_job = IMPL(ipp_print_job_t, ifc, this_p);
    564 
    565     if (ipp_job->http == NULL) {
    566         return result;
    567     }
    568 
    569     LOGD("_end_job: entry httpPrint %d", ipp_job->http->fd);
    570 
    571     if (ipp_job->useragent != NULL) {
    572         httpSetDefaultField(ipp_job->http, HTTP_FIELD_USER_AGENT, ipp_job->useragent);
    573     }
    574     ipp_job->status = cupsWriteRequestData(ipp_job->http, buffer, 0);
    575 
    576     if (ipp_job->status != HTTP_CONTINUE) {
    577         LOGE("Error: from cupsWriteRequestData http.fd %d:  status %d",
    578                 ipp_job->http->fd, ipp_job->status);
    579     } else {
    580         result = OK;
    581         LOGD("0 length Bytes sent, status %d", ipp_job->status);
    582         response = cupsGetResponse(ipp_job->http, ipp_job->http_resource);
    583 
    584         if ((attrptr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) == NULL) {
    585             LOGE("sent cupsGetResponse %s job id is null; received", ippOpString(op));
    586         } else {
    587             job_id = ippGetInteger(attrptr, 0);
    588             LOGI("sent cupsGetResponse %s job_id %d; received", ippOpString(op), job_id);
    589         }
    590 
    591         if (response != NULL) {
    592             for (attrptr = ippFirstAttribute(response); attrptr; attrptr = ippNextAttribute(
    593                     response)) {
    594                 print_attr(attrptr);
    595                 if (strcmp(ippGetName(attrptr), "job-state-reasons") == 0) {
    596                     int i;
    597                     for (i = 0; i < ippGetCount(attrptr); i++) {
    598                         if (strcmp(ippGetString(attrptr, i, NULL), "job-canceled-at-device")
    599                                 == 0) {
    600                             result = CANCELLED;
    601                             break;
    602                         }
    603                     }
    604                 }
    605             }
    606             ippDelete(response);
    607         }
    608     }
    609     LOGD("_end_job: exit status %d job_id %d", ipp_job->status, job_id);
    610 
    611     return result;
    612 }