1 /* AirScan (a.k.a. eSCL) backend for SANE
2  *
3  * Copyright (C) 2019 and up by Alexander Pevzner (pzz@apevzner.com)
4  * See LICENSE for license terms and conditions
5  *
6  * ESCL protocol handler
7  */
8 
9 #include "airscan.h"
10 
11 /******************** Protocol constants ********************/
12 /* If HTTP 503 reply is received, how many retry attempts
13  * to perform before giving up
14  *
15  *   ESCL_RETRY_ATTEMPTS_LOAD - for NextDocument request
16  *   ESCL_RETRY_ATTEMPTS      - for other requests
17  *
18  * Note, some printers (namely, HP LaserJet MFP M28w) require
19  * a lot of retry attempts when loading next page at high res
20  */
21 #define ESCL_RETRY_ATTEMPTS_LOAD        30
22 #define ESCL_RETRY_ATTEMPTS             10
23 
24 /* And pause between retries, in milliseconds
25  */
26 #define ESCL_RETRY_PAUSE                1000
27 
28 /* Some devices (namely, Brother MFC-L2710DW) erroneously returns
29  * HTTP 404 Not Found when scanning from ADF, if next LOAD request
30  * send immediately after completion the previous one, and ScannerStatus
31  * returns ScannerAdfEmpty at this case, which leads to premature
32  * scan job termination with SANE_STATUS_NO_DOCS status
33  *
34  * Introducing a small delay between subsequent LOAD requests solves
35  * this problem.
36  *
37  * To avoid performance regression on a very fast scanners, this
38  * delay is limited to some fraction of the preceding LOAD
39  * query time
40  *
41  *   ESCL_NEXT_LOAD_DELAY     - delay between LOAD requests, milliseconds
42  *   ESCL_NEXT_LOAD_DELAY_MAX - upper limit of this delay, as a fraction
43  *                              of a previous LOAD time
44  */
45 #define ESCL_NEXT_LOAD_DELAY           1000
46 #define ESCL_NEXT_LOAD_DELAY_MAX       0.5
47 
48 /* proto_handler_escl represents eSCL protocol handler
49  */
50 typedef struct {
51     proto_handler proto; /* Base class */
52 
53     /* Miscellaneous flags */
54     bool quirk_localhost;            /* Set Host: localhost in ScanJobs rq */
55     bool quirk_canon_mf410_series;   /* Canon MF410 Series */
56     bool quirk_port_in_host;         /* Always set port in Host: header */
57 } proto_handler_escl;
58 
59 /* XML namespace for XML writer
60  */
61 static const xml_ns escl_xml_wr_ns[] = {
62     {"pwg",  "http://www.pwg.org/schemas/2010/12/sm"},
63     {"scan", "http://schemas.hp.com/imaging/escl/2011/05/03"},
64     {NULL, NULL}
65 };
66 
67 
68 /******************** Miscellaneous types ********************/
69 /* escl_scanner_status represents decoded ScannerStatus response
70  */
71 typedef struct {
72     SANE_Status device_status; /* <pwg:State>XXX</pwg:State> */
73     SANE_Status adf_status;    /* <scan:AdfState>YYY</scan:AdfState> */
74 } escl_scanner_status;
75 
76 
77 /******************** Forward declarations ********************/
78 static error
79 escl_parse_scanner_status (const proto_ctx *ctx,
80         const char *xml_text, size_t xml_len, escl_scanner_status *out);
81 
82 /******************** HTTP utility functions ********************/
83 /* Create HTTP query
84  */
85 static http_query*
escl_http_query(const proto_ctx * ctx,const char * path,const char * method,char * body)86 escl_http_query (const proto_ctx *ctx, const char *path,
87         const char *method, char *body)
88 {
89     proto_handler_escl *escl = (proto_handler_escl*) ctx->proto;
90     http_query *query = http_query_new_relative(ctx->http, ctx->base_uri, path,
91         method, body, "text/xml");
92     if (escl->quirk_port_in_host) {
93         http_query_force_port(query, true);
94     }
95     return query;
96 }
97 
98 /* Create HTTP get query
99  */
100 static http_query*
escl_http_get(const proto_ctx * ctx,const char * path)101 escl_http_get (const proto_ctx *ctx, const char *path)
102 {
103     return escl_http_query(ctx, path, "GET", NULL);
104 }
105 
106 /******************** Device Capabilities ********************/
107 /* Parse color modes
108  */
109 static error
escl_devcaps_source_parse_color_modes(xml_rd * xml,devcaps_source * src)110 escl_devcaps_source_parse_color_modes (xml_rd *xml, devcaps_source *src)
111 {
112     src->colormodes = 0;
113 
114     xml_rd_enter(xml);
115     for (; !xml_rd_end(xml); xml_rd_next(xml)) {
116         if(xml_rd_node_name_match(xml, "scan:ColorMode")) {
117             const char *v = xml_rd_node_value(xml);
118             if (!strcmp(v, "BlackAndWhite1")) {
119                 src->colormodes |= 1 << ID_COLORMODE_BW1;
120             } else if (!strcmp(v, "Grayscale8")) {
121                 src->colormodes |= 1 << ID_COLORMODE_GRAYSCALE;
122             } else if (!strcmp(v, "RGB24")) {
123                 src->colormodes |= 1 << ID_COLORMODE_COLOR;
124             }
125         }
126     }
127     xml_rd_leave(xml);
128 
129     return NULL;
130 }
131 
132 /* Parse document formats
133  */
134 static error
escl_devcaps_source_parse_document_formats(xml_rd * xml,devcaps_source * src)135 escl_devcaps_source_parse_document_formats (xml_rd *xml, devcaps_source *src)
136 {
137     xml_rd_enter(xml);
138     for (; !xml_rd_end(xml); xml_rd_next(xml)) {
139         unsigned int flags = 0;
140 
141         if(xml_rd_node_name_match(xml, "pwg:DocumentFormat")) {
142             flags |= DEVCAPS_SOURCE_PWG_DOCFMT;
143         }
144 
145         if(xml_rd_node_name_match(xml, "scan:DocumentFormatExt")) {
146             flags |= DEVCAPS_SOURCE_SCAN_DOCFMT_EXT;
147         }
148 
149         if (flags != 0) {
150             const char *v = xml_rd_node_value(xml);
151             ID_FORMAT  fmt = id_format_by_mime_name(v);
152 
153             if (fmt != ID_FORMAT_UNKNOWN) {
154                 src->formats |= 1 << fmt;
155                 src->flags |= flags;
156             }
157         }
158     }
159     xml_rd_leave(xml);
160 
161     return NULL;
162 }
163 
164 /* Parse discrete resolutions.
165  */
166 static error
escl_devcaps_source_parse_discrete_resolutions(xml_rd * xml,devcaps_source * src)167 escl_devcaps_source_parse_discrete_resolutions (xml_rd *xml,
168         devcaps_source *src)
169 {
170     error err = NULL;
171 
172     sane_word_array_reset(&src->resolutions);
173 
174     xml_rd_enter(xml);
175     for (; err == NULL && !xml_rd_end(xml); xml_rd_next(xml)) {
176         if (xml_rd_node_name_match(xml, "scan:DiscreteResolution")) {
177             SANE_Word x = 0, y = 0;
178             xml_rd_enter(xml);
179             for (; err == NULL && !xml_rd_end(xml); xml_rd_next(xml)) {
180                 if (xml_rd_node_name_match(xml, "scan:XResolution")) {
181                     err = xml_rd_node_value_uint(xml, &x);
182                 } else if (xml_rd_node_name_match(xml,
183                         "scan:YResolution")) {
184                     err = xml_rd_node_value_uint(xml, &y);
185                 }
186             }
187             xml_rd_leave(xml);
188 
189             if (x && y && x == y) {
190                 src->resolutions = sane_word_array_append(src->resolutions, x);
191             }
192         }
193     }
194     xml_rd_leave(xml);
195 
196     if (sane_word_array_len(src->resolutions) > 0) {
197         src->flags |= DEVCAPS_SOURCE_RES_DISCRETE;
198         sane_word_array_sort(src->resolutions);
199     }
200 
201     return err;
202 }
203 
204 /* Parse resolutions range
205  */
206 static error
escl_devcaps_source_parse_resolutions_range(xml_rd * xml,devcaps_source * src)207 escl_devcaps_source_parse_resolutions_range (xml_rd *xml, devcaps_source *src)
208 {
209     error      err = NULL;
210     SANE_Range range_x = {0, 0, 0}, range_y = {0, 0, 0};
211 
212     xml_rd_enter(xml);
213     for (; err == NULL && !xml_rd_end(xml); xml_rd_next(xml)) {
214         SANE_Range *range = NULL;
215         if (xml_rd_node_name_match(xml, "scan:XResolution")) {
216             range = &range_x;
217         } else if (xml_rd_node_name_match(xml, "scan:XResolution")) {
218             range = &range_y;
219         }
220 
221         if (range != NULL) {
222             xml_rd_enter(xml);
223             for (; err == NULL && !xml_rd_end(xml); xml_rd_next(xml)) {
224                 if (xml_rd_node_name_match(xml, "scan:Min")) {
225                     err = xml_rd_node_value_uint(xml, &range->min);
226                 } else if (xml_rd_node_name_match(xml, "scan:Max")) {
227                     err = xml_rd_node_value_uint(xml, &range->max);
228                 } else if (xml_rd_node_name_match(xml, "scan:Step")) {
229                     err = xml_rd_node_value_uint(xml, &range->quant);
230                 }
231             }
232             xml_rd_leave(xml);
233         }
234     }
235     xml_rd_leave(xml);
236 
237     if (range_x.min > range_x.max) {
238         err = ERROR("Invalid scan:XResolution range");
239         goto DONE;
240     }
241 
242     if (range_y.min > range_y.max) {
243         err = ERROR("Invalid scan:YResolution range");
244         goto DONE;
245     }
246 
247     /* If no quantization value, SANE uses 0, not 1
248      */
249     if (range_x.quant == 1) {
250         range_x.quant = 0;
251     }
252 
253     if (range_y.quant == 1) {
254         range_y.quant = 0;
255     }
256 
257     /* Try to merge x/y ranges */
258     if (!math_range_merge(&src->res_range, &range_x, &range_y)) {
259         err = ERROR("Incompatible scan:XResolution and "
260                     "scan:YResolution ranges");
261         goto DONE;
262     }
263 
264     src->flags |= DEVCAPS_SOURCE_RES_RANGE;
265 
266 DONE:
267     return err;
268 }
269 
270 /* Parse supported resolutions.
271  */
272 static error
escl_devcaps_source_parse_resolutions(xml_rd * xml,devcaps_source * src)273 escl_devcaps_source_parse_resolutions (xml_rd *xml, devcaps_source *src)
274 {
275     error err = NULL;
276 
277     xml_rd_enter(xml);
278     for (; err == NULL && !xml_rd_end(xml); xml_rd_next(xml)) {
279         if (xml_rd_node_name_match(xml, "scan:DiscreteResolutions")) {
280             err = escl_devcaps_source_parse_discrete_resolutions(xml, src);
281         } else if (xml_rd_node_name_match(xml, "scan:ResolutionRange")) {
282             err = escl_devcaps_source_parse_resolutions_range(xml, src);
283         }
284     }
285     xml_rd_leave(xml);
286 
287     /* Prefer discrete resolution, if both are provided */
288     if (src->flags & DEVCAPS_SOURCE_RES_DISCRETE) {
289         src->flags &= ~DEVCAPS_SOURCE_RES_RANGE;
290     }
291 
292     return err;
293 }
294 
295 /* Parse setting profiles (color modes, document formats etc).
296  */
297 static error
escl_devcaps_source_parse_setting_profiles(xml_rd * xml,devcaps_source * src)298 escl_devcaps_source_parse_setting_profiles (xml_rd *xml, devcaps_source *src)
299 {
300     error err = NULL;
301 
302     /* Parse setting profiles */
303     xml_rd_enter(xml);
304     for (; err == NULL && !xml_rd_end(xml); xml_rd_next(xml)) {
305         if (xml_rd_node_name_match(xml, "scan:SettingProfile")) {
306             xml_rd_enter(xml);
307             for (; err == NULL && !xml_rd_end(xml); xml_rd_next(xml)) {
308                 if (xml_rd_node_name_match(xml, "scan:ColorModes")) {
309                     err = escl_devcaps_source_parse_color_modes(xml, src);
310                 } else if (xml_rd_node_name_match(xml,
311                         "scan:DocumentFormats")) {
312                     err = escl_devcaps_source_parse_document_formats(xml, src);
313                 } else if (xml_rd_node_name_match(xml,
314                         "scan:SupportedResolutions")) {
315                     err = escl_devcaps_source_parse_resolutions(xml, src);
316                 }
317             }
318             xml_rd_leave(xml);
319         }
320     }
321     xml_rd_leave(xml);
322 
323     /* Validate results */
324     if (err == NULL) {
325         src->colormodes &= DEVCAPS_COLORMODES_SUPPORTED;
326         if (src->colormodes == 0) {
327             return ERROR("no color modes detected");
328         }
329 
330         src->formats &= DEVCAPS_FORMATS_SUPPORTED;
331         if (src->formats == 0) {
332             return ERROR("no image formats detected");
333         }
334 
335         if (!(src->flags & (DEVCAPS_SOURCE_RES_DISCRETE|
336                             DEVCAPS_SOURCE_RES_RANGE))){
337             return ERROR("scan resolutions are not defined");
338         }
339     }
340 
341     return err;
342 }
343 
344 
345 /* Parse source capabilities. Returns NULL on success, error string otherwise
346  */
347 static error
escl_devcaps_source_parse(xml_rd * xml,devcaps_source ** out)348 escl_devcaps_source_parse (xml_rd *xml, devcaps_source **out)
349 {
350     devcaps_source *src = devcaps_source_new();
351     error          err = NULL;
352 
353     xml_rd_enter(xml);
354     for (; err == NULL && !xml_rd_end(xml); xml_rd_next(xml)) {
355         if(xml_rd_node_name_match(xml, "scan:MinWidth")) {
356             err = xml_rd_node_value_uint(xml, &src->min_wid_px);
357         } else if (xml_rd_node_name_match(xml, "scan:MaxWidth")) {
358             err = xml_rd_node_value_uint(xml, &src->max_wid_px);
359         } else if (xml_rd_node_name_match(xml, "scan:MinHeight")) {
360             err = xml_rd_node_value_uint(xml, &src->min_hei_px);
361         } else if (xml_rd_node_name_match(xml, "scan:MaxHeight")) {
362             err = xml_rd_node_value_uint(xml, &src->max_hei_px);
363         } else if (xml_rd_node_name_match(xml, "scan:SettingProfiles")) {
364             err = escl_devcaps_source_parse_setting_profiles(xml, src);
365         }
366     }
367     xml_rd_leave(xml);
368 
369     if (err != NULL) {
370         goto DONE;
371     }
372 
373     if (src->max_wid_px != 0 && src->max_hei_px != 0)
374     {
375         /* Validate window size */
376         if (src->min_wid_px > src->max_wid_px)
377         {
378             err = ERROR("Invalid scan:MinWidth or scan:MaxWidth");
379             goto DONE;
380         }
381 
382         if (src->min_hei_px > src->max_hei_px)
383         {
384             err = ERROR("Invalid scan:MinHeight or scan:MaxHeight");
385             goto DONE;
386         }
387 
388         src->flags |= DEVCAPS_SOURCE_HAS_SIZE;
389 
390         /* Set window ranges */
391         src->win_x_range_mm.min = src->win_y_range_mm.min = 0;
392         src->win_x_range_mm.max = math_px2mm_res(src->max_wid_px, 300);
393         src->win_y_range_mm.max = math_px2mm_res(src->max_hei_px, 300);
394     }
395 
396 DONE:
397     if (err != NULL) {
398         devcaps_source_free(src);
399     } else {
400         if (*out == NULL) {
401             *out = src;
402         } else {
403             /* Duplicate detected. Ignored for now */
404             devcaps_source_free(src);
405         }
406     }
407 
408     return err;
409 }
410 
411 /* Parse compression factor parameters
412  */
413 static error
escl_devcaps_compression_parse(xml_rd * xml,devcaps * caps)414 escl_devcaps_compression_parse (xml_rd *xml, devcaps *caps)
415 {
416     for (; !xml_rd_end(xml); xml_rd_next(xml)) {
417         error err = NULL;
418 
419         if (xml_rd_node_name_match(xml, "scan:Min")) {
420             err = xml_rd_node_value_uint(xml, &caps->compression_range.min);
421         } else if (xml_rd_node_name_match(xml, "scan:Max")) {
422             err = xml_rd_node_value_uint(xml, &caps->compression_range.max);
423         } else if (xml_rd_node_name_match(xml, "scan:Step")) {
424             err = xml_rd_node_value_uint(xml, &caps->compression_range.quant);
425         } else if (xml_rd_node_name_match(xml, "scan:Normal")) {
426             err = xml_rd_node_value_uint(xml, &caps->compression_norm);
427         }
428 
429         if (err != NULL) {
430             return err;
431         }
432     }
433 
434     /* Validate obtained parameters.
435      *
436      * Note, errors are silently ignored starting from this point
437      */
438     if (caps->compression_range.min > caps->compression_range.max) {
439         return NULL;
440     }
441 
442     if (caps->compression_norm < caps->compression_range.min ||
443         caps->compression_norm > caps->compression_range.max) {
444         return NULL;
445     }
446 
447     caps->compression_ok = true;
448 
449     return NULL;
450 }
451 
452 /* Parse device capabilities. devcaps structure must be initialized
453  * before calling this function.
454  */
455 static error
escl_devcaps_parse(proto_handler_escl * escl,devcaps * caps,const char * xml_text,size_t xml_len)456 escl_devcaps_parse (proto_handler_escl *escl,
457         devcaps *caps, const char *xml_text, size_t xml_len)
458 {
459     error     err = NULL;
460     xml_rd    *xml;
461     bool      quirk_canon_iR2625_2630 = false;
462     ID_SOURCE id_src;
463     bool      src_ok = false;
464 
465     /* Parse capabilities XML */
466     err = xml_rd_begin(&xml, xml_text, xml_len, NULL);
467     if (err != NULL) {
468         goto DONE;
469     }
470 
471     if (!xml_rd_node_name_match(xml, "scan:ScannerCapabilities")) {
472         err = ERROR("XML: missed scan:ScannerCapabilities");
473         goto DONE;
474     }
475 
476     xml_rd_enter(xml);
477     for (; !xml_rd_end(xml); xml_rd_next(xml)) {
478         if (xml_rd_node_name_match(xml, "pwg:MakeAndModel")) {
479             const char *m = xml_rd_node_value(xml);
480 
481             if (!strcmp(m, "Canon iR2625/2630")) {
482                 quirk_canon_iR2625_2630 = true;
483             } else if (!strcmp(m, "HP LaserJet MFP M630")) {
484                 escl->quirk_localhost = true;
485             } else if (!strcmp(m, "HP Color LaserJet FlowMFP M578")) {
486                 escl->quirk_localhost = true;
487             } else if (!strcmp(m, "MF410 Series")) {
488                 escl->quirk_canon_mf410_series = true;
489             } else if (!strncasecmp(m, "EPSON ", 6)) {
490                 escl->quirk_port_in_host = true;
491             }
492         } else if (xml_rd_node_name_match(xml, "scan:Manufacturer")) {
493             const char *m = xml_rd_node_value(xml);
494 
495             if (!strcasecmp(m, "EPSON")) {
496                 escl->quirk_port_in_host = true;
497             }
498         } else if (xml_rd_node_name_match(xml, "scan:Platen")) {
499             xml_rd_enter(xml);
500             if (xml_rd_node_name_match(xml, "scan:PlatenInputCaps")) {
501                 err = escl_devcaps_source_parse(xml,
502                     &caps->src[ID_SOURCE_PLATEN]);
503             }
504             xml_rd_leave(xml);
505         } else if (xml_rd_node_name_match(xml, "scan:Adf")) {
506             xml_rd_enter(xml);
507             while (!xml_rd_end(xml)) {
508                 if (xml_rd_node_name_match(xml, "scan:AdfSimplexInputCaps")) {
509                     err = escl_devcaps_source_parse(xml,
510                         &caps->src[ID_SOURCE_ADF_SIMPLEX]);
511                 } else if (xml_rd_node_name_match(xml,
512                         "scan:AdfDuplexInputCaps")) {
513                     err = escl_devcaps_source_parse(xml,
514                         &caps->src[ID_SOURCE_ADF_DUPLEX]);
515                 }
516                 xml_rd_next(xml);
517             }
518             xml_rd_leave(xml);
519         } else if (xml_rd_node_name_match(xml, "scan:CompressionFactorSupport")) {
520             xml_rd_enter(xml);
521             err = escl_devcaps_compression_parse(xml, caps);
522             xml_rd_leave(xml);
523         }
524 
525         if (err != NULL) {
526             goto DONE;
527         }
528     }
529 
530     /* Check that we have at least one source */
531     for (id_src = (ID_SOURCE) 0; id_src < NUM_ID_SOURCE; id_src ++) {
532         if (caps->src[id_src] != NULL) {
533             src_ok = true;
534         }
535     }
536 
537     if (!src_ok) {
538         err = ERROR("XML: neither platen nor ADF sources detected");
539         goto DONE;
540     }
541 
542     /* Apply quirks, if any */
543     if (quirk_canon_iR2625_2630) {
544         /* This device announces resolutions up to 600 DPI,
545          * but actually doesn't support more that 300
546          *
547          * https://oip.manual.canon/USRMA-4209-zz-CSL-2600-enUV/contents/devu-apdx-sys_spec-send.html?search=600
548          *
549          * See #57 for details
550          */
551         for (id_src = (ID_SOURCE) 0; id_src < NUM_ID_SOURCE; id_src ++) {
552             devcaps_source *src = caps->src[id_src];
553             if (src != NULL &&
554                 /* paranoia: array won't be empty after quirk applied */
555                 sane_word_array_len(src->resolutions) > 0 &&
556                 src->resolutions[1] <= 300) {
557                 sane_word_array_bound(src->resolutions, 0, 300);
558             }
559         }
560     }
561 
562 DONE:
563     if (err != NULL) {
564         devcaps_reset(caps);
565     }
566 
567     xml_rd_finish(&xml);
568 
569     return err;
570 }
571 
572 /* Query device capabilities
573  */
574 static http_query*
escl_devcaps_query(const proto_ctx * ctx)575 escl_devcaps_query (const proto_ctx *ctx)
576 {
577     return escl_http_get(ctx, "ScannerCapabilities");
578 }
579 
580 /* Decode device capabilities
581  */
582 static error
escl_devcaps_decode(const proto_ctx * ctx,devcaps * caps)583 escl_devcaps_decode (const proto_ctx *ctx, devcaps *caps)
584 {
585     proto_handler_escl *escl = (proto_handler_escl*) ctx->proto;
586     http_data          *data = http_query_get_response_data(ctx->query);
587     const char         *s;
588 
589     caps->units = 300;
590     caps->protocol = ctx->proto->name;
591 
592     /* Most of devices that have Server: HP_Compact_Server
593      * in their HTTP response header, require this quirk
594      * (see #116)
595      */
596     s = http_query_get_response_header(ctx->query, "server");
597     if (s != NULL && !strcmp(s, "HP_Compact_Server")) {
598         escl->quirk_localhost = true;
599     }
600 
601     return escl_devcaps_parse(escl, caps, data->bytes, data->size);
602 }
603 
604 /* Create pre-scan check query
605  */
606 static http_query*
escl_precheck_query(const proto_ctx * ctx)607 escl_precheck_query (const proto_ctx *ctx)
608 {
609     return escl_http_get(ctx, "ScannerStatus");
610 }
611 
612 /* Decode pre-scan check query results
613  */
614 static proto_result
escl_precheck_decode(const proto_ctx * ctx)615 escl_precheck_decode (const proto_ctx *ctx)
616 {
617     proto_handler_escl  *escl = (proto_handler_escl*) ctx->proto;
618     proto_result        result = {0};
619     error               err = NULL;
620     escl_scanner_status sts;
621     bool                adf = ctx->params.src == ID_SOURCE_ADF_SIMPLEX ||
622                               ctx->params.src == ID_SOURCE_ADF_DUPLEX;
623 
624     /* Initialize result to something optimistic :-) */
625     result.status = SANE_STATUS_GOOD;
626     result.next = PROTO_OP_SCAN;
627 
628     /* Decode status */
629     err = http_query_error(ctx->query);
630     if (err == NULL) {
631         http_data *data = http_query_get_response_data(ctx->query);
632         err = escl_parse_scanner_status(ctx, data->bytes, data->size, &sts);
633     }
634 
635     if (err != NULL) {
636         result.err = err;
637         result.status = SANE_STATUS_IO_ERROR;
638         result.next = PROTO_OP_FINISH;
639         return result;
640     }
641 
642     /* Note, the pre-check status is not always reliable, so normally
643      * we ignore it. Hoverer, with Canon MF410 Series attempt to
644      * scan from empty ADF causes ADF jam error (really, physical!),
645      * so we must take care
646      */
647     if (escl->quirk_canon_mf410_series) {
648         if (adf) {
649             switch (sts.adf_status) {
650             case SANE_STATUS_JAMMED:
651             case SANE_STATUS_NO_DOCS:
652                 result.status = sts.adf_status;
653                 result.next = PROTO_OP_FINISH;
654 
655             default:
656                 break;
657             }
658         }
659     }
660 
661     return result;
662 }
663 
664 /* Fix Location: URL
665  *
666  * Can be used as http_query_onredir() callback
667  */
668 static void
escl_scan_fix_location(void * p,http_uri * uri,const http_uri * orig_uri)669 escl_scan_fix_location (void *p, http_uri *uri, const http_uri *orig_uri)
670 {
671     (void) p;
672     http_uri_fix_host(uri, orig_uri, "localhost");
673 }
674 
675 /* Initiate scanning
676  */
677 static http_query*
escl_scan_query(const proto_ctx * ctx)678 escl_scan_query (const proto_ctx *ctx)
679 {
680     proto_handler_escl      *escl = (proto_handler_escl*) ctx->proto;
681     const proto_scan_params *params = &ctx->params;
682     const char              *source = NULL;
683     const char              *colormode = NULL;
684     const char              *mime = id_format_mime_name(ctx->params.format);
685     const devcaps_source    *src = ctx->devcaps->src[params->src];
686     bool                    duplex = false;
687     http_query              *query;
688 
689     /* Prepare parameters */
690     switch (params->src) {
691     case ID_SOURCE_PLATEN:      source = "Platen"; duplex = false; break;
692     case ID_SOURCE_ADF_SIMPLEX: source = "Feeder"; duplex = false; break;
693     case ID_SOURCE_ADF_DUPLEX:  source = "Feeder"; duplex = true; break;
694 
695     default:
696         log_internal_error(ctx->log);
697     }
698 
699     switch (params->colormode) {
700     case ID_COLORMODE_COLOR:     colormode = "RGB24"; break;
701     case ID_COLORMODE_GRAYSCALE: colormode = "Grayscale8"; break;
702     case ID_COLORMODE_BW1:       colormode = "BlackAndWhite1"; break;
703 
704     default:
705         log_internal_error(ctx->log);
706     }
707 
708     /* Build scan request */
709     xml_wr *xml = xml_wr_begin("scan:ScanSettings", escl_xml_wr_ns);
710 
711     xml_wr_add_text(xml, "pwg:Version", "2.0");
712 
713     xml_wr_enter(xml, "pwg:ScanRegions");
714     xml_wr_enter(xml, "pwg:ScanRegion");
715     xml_wr_add_text(xml, "pwg:ContentRegionUnits",
716             "escl:ThreeHundredthsOfInches");
717     xml_wr_add_uint(xml, "pwg:XOffset", params->x_off);
718     xml_wr_add_uint(xml, "pwg:YOffset", params->y_off);
719     xml_wr_add_uint(xml, "pwg:Width", params->wid);
720     xml_wr_add_uint(xml, "pwg:Height", params->hei);
721     xml_wr_leave(xml); /* pwg:ScanRegion */
722     xml_wr_leave(xml); /* pwg:ScanRegions */
723 
724     //xml_wr_add_text(xml, "scan:InputSource", source);
725     xml_wr_add_text(xml, "pwg:InputSource", source);
726     if (ctx->devcaps->compression_ok) {
727         xml_wr_add_uint(xml, "scan:CompressionFactor",
728             ctx->devcaps->compression_norm);
729     }
730     xml_wr_add_text(xml, "scan:ColorMode", colormode);
731     xml_wr_add_text(xml, "pwg:DocumentFormat", mime);
732     if ((src->flags & DEVCAPS_SOURCE_SCAN_DOCFMT_EXT) != 0) {
733         xml_wr_add_text(xml, "scan:DocumentFormatExt", mime);
734     }
735     xml_wr_add_uint(xml, "scan:XResolution", params->x_res);
736     xml_wr_add_uint(xml, "scan:YResolution", params->y_res);
737     if (params->src != ID_SOURCE_PLATEN) {
738         xml_wr_add_bool(xml, "scan:Duplex", duplex);
739     }
740 
741     /* Send request to device */
742     query = escl_http_query(ctx, "ScanJobs", "POST",
743         xml_wr_finish_compact(xml));
744 
745     /* It's a dirty hack
746      *
747      * HP LaserJet MFP M630, HP Color LaserJet FlowMFP M578 and
748      * probably some other HP devices don't allow eSCL scan, unless
749      * Host is set to "localhost". It is probably bad and naive attempt
750      * to enforce some access security.
751      *
752      * So here we forcibly set Host to "localhost".
753      *
754      * Note, this hack doesn't work with some other printers
755      * see #92, #98 for details
756      */
757     if (escl->quirk_localhost) {
758         http_query_set_request_header(query, "Host", "localhost");
759         http_query_onredir(query, escl_scan_fix_location);
760     }
761 
762     return query;
763 }
764 
765 /* Decode result of scan request
766  */
767 static proto_result
escl_scan_decode(const proto_ctx * ctx)768 escl_scan_decode (const proto_ctx *ctx)
769 {
770     proto_result result = {0};
771     error        err = NULL;
772     const char   *location;
773     http_uri     *uri;
774 
775     /* Check HTTP status */
776     if (http_query_status(ctx->query) != HTTP_STATUS_CREATED) {
777         err = eloop_eprintf("ScanJobs request: unexpected HTTP status %d",
778                 http_query_status(ctx->query));
779         result.next = PROTO_OP_CHECK;
780         result.err = err;
781         return result;
782     }
783 
784     /* Obtain location */
785     location = http_query_get_response_header(ctx->query, "Location");
786     if (location == NULL || *location == '\0') {
787         err = eloop_eprintf("ScanJobs request: empty location received");
788         goto ERROR;
789     }
790 
791     /* Validate and save location */
792     uri = http_uri_new_relative(ctx->base_uri, location, true, false);
793     if (uri == NULL) {
794         err = eloop_eprintf("ScanJobs request: invalid location received");
795         goto ERROR;
796     }
797 
798     escl_scan_fix_location(NULL, uri, http_query_uri(ctx->query));
799     result.data.location = str_dup(http_uri_str(uri));
800     http_uri_free(uri);
801 
802     result.next = PROTO_OP_LOAD;
803 
804     return result;
805 
806 ERROR:
807     result.next = PROTO_OP_FINISH;
808     result.status = SANE_STATUS_IO_ERROR;
809     result.err = err;
810     return result;
811 }
812 
813 /* Initiate image downloading
814  */
815 static http_query*
escl_load_query(const proto_ctx * ctx)816 escl_load_query (const proto_ctx *ctx)
817 {
818     char *url, *sep;
819     http_query *q;
820 
821     sep = str_has_suffix(ctx->location, "/") ? "" : "/";
822     url = str_concat(ctx->location, sep, "NextDocument", NULL);
823 
824     q = escl_http_get(ctx, url);
825     mem_free(url);
826 
827     return q;
828 }
829 
830 /* Decode result of image request
831  */
832 static proto_result
escl_load_decode(const proto_ctx * ctx)833 escl_load_decode (const proto_ctx *ctx)
834 {
835     proto_result result = {0};
836     error        err = NULL;
837     timestamp    t = 0;
838 
839     /* Check HTTP status */
840     err = http_query_error(ctx->query);
841     if (err != NULL) {
842         if (ctx->params.src == ID_SOURCE_PLATEN && ctx->images_received > 0) {
843             result.next = PROTO_OP_CLEANUP;
844         } else {
845             result.next = PROTO_OP_CHECK;
846             result.err = eloop_eprintf("HTTP: %s", ESTRING(err));
847         }
848 
849         return result;
850     }
851 
852     /* Compute delay until next load */
853     if (ctx->params.src != ID_SOURCE_PLATEN) {
854         t = timestamp_now() - http_query_timestamp(ctx->query);
855         t *= ESCL_NEXT_LOAD_DELAY_MAX;
856 
857         if (t > ESCL_NEXT_LOAD_DELAY) {
858             t = ESCL_NEXT_LOAD_DELAY;
859         }
860     }
861 
862     /* Fill proto_result */
863     result.next = PROTO_OP_LOAD;
864     result.delay = (int) t;
865     result.data.image = http_data_ref(http_query_get_response_data(ctx->query));
866 
867     return result;
868 }
869 
870 /* Request device status
871  */
872 static http_query*
escl_status_query(const proto_ctx * ctx)873 escl_status_query (const proto_ctx *ctx)
874 {
875     return escl_http_get(ctx, "ScannerStatus");
876 }
877 
878 /* Parse ScannerStatus response.
879  *
880  * Returned SANE_STATUS_UNSUPPORTED means status not understood
881  */
882 static error
escl_parse_scanner_status(const proto_ctx * ctx,const char * xml_text,size_t xml_len,escl_scanner_status * out)883 escl_parse_scanner_status (const proto_ctx *ctx,
884         const char *xml_text, size_t xml_len, escl_scanner_status *out)
885 {
886     error               err = NULL;
887     xml_rd              *xml;
888     const char          *opname = proto_op_name(ctx->op);
889     escl_scanner_status sts = {SANE_STATUS_UNSUPPORTED,
890             SANE_STATUS_UNSUPPORTED};
891 
892     /* Decode XML */
893     err = xml_rd_begin(&xml, xml_text, xml_len, NULL);
894     if (err != NULL) {
895         goto DONE;
896     }
897 
898     if (!xml_rd_node_name_match(xml, "scan:ScannerStatus")) {
899         err = ERROR("XML: missed scan:ScannerStatus");
900         goto DONE;
901     }
902 
903     xml_rd_enter(xml);
904     for (; !xml_rd_end(xml); xml_rd_next(xml)) {
905         if (xml_rd_node_name_match(xml, "pwg:State")) {
906             const char *state = xml_rd_node_value(xml);
907             if (!strcmp(state, "Idle")) {
908                 sts.device_status = SANE_STATUS_GOOD;
909             } else if (!strcmp(state, "Processing")) {
910                 sts.device_status = SANE_STATUS_DEVICE_BUSY;
911             } else if (!strcmp(state, "Testing")) {
912                 /* HP LaserJet MFP M630 warm up */
913                 sts.device_status = SANE_STATUS_DEVICE_BUSY;
914             } else {
915                 sts.device_status = SANE_STATUS_UNSUPPORTED;
916             }
917         } else if (xml_rd_node_name_match(xml, "scan:AdfState")) {
918             const char *state = xml_rd_node_value(xml);
919             if (!strcmp(state, "ScannerAdfLoaded")) {
920                 sts.adf_status = SANE_STATUS_GOOD;
921             } else if (!strcmp(state, "ScannerAdfJam")) {
922                 sts.adf_status = SANE_STATUS_JAMMED;
923             } else if (!strcmp(state, "ScannerAdfDoorOpen")) {
924                 sts.adf_status = SANE_STATUS_COVER_OPEN;
925             } else if (!strcmp(state, "ScannerAdfProcessing")) {
926                 /* Kyocera version */
927                 sts.adf_status = SANE_STATUS_NO_DOCS;
928             } else if (!strcmp(state, "ScannerAdfEmpty")) {
929                 /* Cannon TR4500, EPSON XP-7100 */
930                 sts.adf_status = SANE_STATUS_NO_DOCS;
931             } else {
932                 sts.adf_status = SANE_STATUS_UNSUPPORTED;
933             }
934         }
935     }
936 
937 DONE:
938     xml_rd_finish(&xml);
939 
940     if (err != NULL) {
941         log_debug(ctx->log, "%s: %s", opname, ESTRING(err));
942     } else {
943         log_debug(ctx->log, "%s: device status: %s",
944             opname, sane_strstatus(sts.device_status));
945         log_debug(ctx->log, "%s: ADF status: %s",
946             opname, sane_strstatus(sts.adf_status));
947     }
948 
949     *out = sts;
950     return err;
951 }
952 
953 /* Parse ScannerStatus response.
954  *
955  * Returned SANE_STATUS_UNSUPPORTED means status not understood
956  */
957 static SANE_Status
escl_decode_scanner_status(const proto_ctx * ctx,const char * xml_text,size_t xml_len)958 escl_decode_scanner_status (const proto_ctx *ctx,
959         const char *xml_text, size_t xml_len)
960 {
961     escl_scanner_status sts;
962     error               err;
963     SANE_Status         status;
964     const char          *opname = proto_op_name(ctx->op);
965 
966     err = escl_parse_scanner_status(ctx, xml_text, xml_len, &sts);
967     if (err != NULL) {
968         return SANE_STATUS_IO_ERROR;
969     }
970 
971     /* Decode Job status */
972     if (ctx->params.src != ID_SOURCE_PLATEN &&
973         sts.adf_status != SANE_STATUS_GOOD &&
974         sts.adf_status != SANE_STATUS_UNSUPPORTED) {
975         status = sts.adf_status;
976     } else {
977         status = sts.device_status;
978     }
979 
980     log_debug(ctx->log, "%s: job status: %s", opname, sane_strstatus(status));
981 
982     return status;
983 }
984 
985 /* Decode result of device status request
986  */
987 static proto_result
escl_status_decode(const proto_ctx * ctx)988 escl_status_decode (const proto_ctx *ctx)
989 {
990     proto_result result = {0};
991     error        err = NULL;
992     SANE_Status  status;
993     int          max_attempts;
994 
995     /* Decode status */
996     err = http_query_error(ctx->query);
997     if (err != NULL) {
998         status = SANE_STATUS_IO_ERROR;
999         goto FAIL;
1000     } else {
1001         http_data *data = http_query_get_response_data(ctx->query);
1002         status = escl_decode_scanner_status(ctx, data->bytes, data->size);
1003     }
1004 
1005     /* Now it's time to make a decision */
1006     max_attempts = ESCL_RETRY_ATTEMPTS;
1007     if (ctx->failed_op == PROTO_OP_LOAD) {
1008         max_attempts = ESCL_RETRY_ATTEMPTS_LOAD;
1009     }
1010 
1011     if (ctx->failed_http_status == HTTP_STATUS_SERVICE_UNAVAILABLE &&
1012         ctx->failed_attempt < max_attempts) {
1013 
1014         /* Note, some devices may return HTTP 503 error core, meaning
1015          * that it makes sense to come back after small delay
1016          *
1017          * So if status doesn't cleanly indicate any error, lets retry
1018          * several times
1019          */
1020         bool retry = false;
1021 
1022         switch (status) {
1023         case SANE_STATUS_GOOD:
1024         case SANE_STATUS_UNSUPPORTED:
1025         case SANE_STATUS_DEVICE_BUSY:
1026             retry = true;
1027             break;
1028 
1029         case SANE_STATUS_NO_DOCS:
1030             /* For some devices SANE_STATUS_NO_DOCS is not
1031              * reliable, if we have reached SANE_STATUS_NO_DOCS
1032              * operation: HTTP 503 may mean "I'm temporary not
1033              * ready, please try again", while ADF sensor
1034              * raises "ADF empty" signal.
1035              *
1036              * So retry at this case
1037              */
1038             if (ctx->failed_op == PROTO_OP_LOAD) {
1039                 retry = true;
1040             }
1041             break;
1042 
1043         default:
1044             break;
1045         }
1046 
1047         if (retry) {
1048             result.next = ctx->failed_op;
1049             result.delay = ESCL_RETRY_PAUSE;
1050             return result;
1051         }
1052     }
1053 
1054     /* If status cannot be cleanly decoded, look to HTTP status */
1055     if (status == SANE_STATUS_GOOD || status == SANE_STATUS_UNSUPPORTED) {
1056         status = SANE_STATUS_IO_ERROR;
1057         switch (ctx->failed_http_status) {
1058         case HTTP_STATUS_SERVICE_UNAVAILABLE:
1059             status = SANE_STATUS_DEVICE_BUSY;
1060             break;
1061 
1062         case HTTP_STATUS_NOT_FOUND:
1063             if (ctx->params.src != ID_SOURCE_PLATEN &&
1064                 ctx->failed_op == PROTO_OP_LOAD) {
1065                 status = SANE_STATUS_NO_DOCS;
1066             }
1067             break;
1068         }
1069     }
1070 
1071     /* Fill the result */
1072 FAIL:
1073     result.next = ctx->location ? PROTO_OP_CLEANUP : PROTO_OP_FINISH;
1074     result.status = status;
1075     result.err = err;
1076 
1077     return result;
1078 }
1079 
1080 /* Cancel scan in progress
1081  */
1082 static http_query*
escl_cancel_query(const proto_ctx * ctx)1083 escl_cancel_query (const proto_ctx *ctx)
1084 {
1085     return escl_http_query(ctx, ctx->location, "DELETE", NULL);
1086 }
1087 
1088 /******************** Constructor/destructor ********************/
1089 /* Free ESCL protocol handler
1090  */
1091 static void
escl_free(proto_handler * proto)1092 escl_free (proto_handler *proto)
1093 {
1094     mem_free(proto);
1095 }
1096 
1097 /* proto_handler_escl_new creates new eSCL protocol handler
1098  */
1099 proto_handler*
proto_handler_escl_new(void)1100 proto_handler_escl_new (void)
1101 {
1102     proto_handler_escl *escl = mem_new(proto_handler_escl, 1);
1103 
1104     escl->proto.name = "eSCL";
1105     escl->proto.free = escl_free;
1106 
1107     escl->proto.devcaps_query = escl_devcaps_query;
1108     escl->proto.devcaps_decode = escl_devcaps_decode;
1109 
1110     escl->proto.precheck_query = escl_precheck_query;
1111     escl->proto.precheck_decode = escl_precheck_decode;
1112 
1113     escl->proto.scan_query = escl_scan_query;
1114     escl->proto.scan_decode = escl_scan_decode;
1115 
1116     escl->proto.load_query = escl_load_query;
1117     escl->proto.load_decode = escl_load_decode;
1118 
1119     escl->proto.status_query = escl_status_query;
1120     escl->proto.status_decode = escl_status_decode;
1121 
1122     escl->proto.cleanup_query = escl_cancel_query;
1123     escl->proto.cancel_query = escl_cancel_query;
1124 
1125     return &escl->proto;
1126 }
1127 
1128 /* vim:ts=8:sw=4:et
1129  */
1130