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