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
28static 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
31static status_t _validate_job(const ifc_print_job_t *this_p, wprint_job_params_t *job_params);
32
33static status_t _start_job(const ifc_print_job_t *this_p, const wprint_job_params_t *job_params);
34
35static int _send_data(const ifc_print_job_t *this_p, const char *buffer, size_t length);
36
37static status_t _end_job(const ifc_print_job_t *this_p);
38
39static void _destroy(const ifc_print_job_t *this_p);
40
41static 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 */
49typedef 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 */
61const 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
77static 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
119static 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 */
137static 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 */
170static 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
399static 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
467static 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
524static 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
548static 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}