1/* sane - Scanner Access Now Easy.
2
3   Copyright (C) 2019 Touboul Nathane
4   Copyright (C) 2019 Thierry HUCHARD <thierry@ordissimo.com>
5
6   This file is part of the SANE package.
7
8   SANE is free software; you can redistribute it and/or modify it under
9   the terms of the GNU General Public License as published by the Free
10   Software Foundation; either version 3 of the License, or (at your
11   option) any later version.
12
13   SANE is distributed in the hope that it will be useful, but WITHOUT
14   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16   for more details.
17
18   You should have received a copy of the GNU General Public License
19   along with sane; see the file COPYING.
20   If not, see <https://www.gnu.org/licenses/>.
21
22   This file implements a SANE backend for eSCL scanners.  */
23
24#define DEBUG_DECLARE_ONLY
25#include "../include/sane/config.h"
26
27#include "escl.h"
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32
33#include <libxml/parser.h>
34
35struct idle
36{
37    char *memory;
38    size_t size;
39};
40
41/**
42 * \fn static size_t memory_callback_s(void *contents, size_t size, size_t nmemb, void *userp)
43 * \brief Callback function that stocks in memory the content of the scanner status.
44 *
45 * \return realsize (size of the content needed -> the scanner status)
46 */
47static size_t
48memory_callback_s(void *contents, size_t size, size_t nmemb, void *userp)
49{
50    size_t realsize = size * nmemb;
51    struct idle *mem = (struct idle *)userp;
52
53    char *str = realloc(mem->memory, mem->size + realsize + 1);
54    if (str == NULL) {
55        DBG(1, "not enough memory (realloc returned NULL)\n");
56        return (0);
57    }
58    mem->memory = str;
59    memcpy(&(mem->memory[mem->size]), contents, realsize);
60    mem->size = mem->size + realsize;
61    mem->memory[mem->size] = 0;
62    return (realsize);
63}
64
65/**
66 * \fn static int find_nodes_s(xmlNode *node)
67 * \brief Function that browses the xml file and parses it, to find the xml children node.
68 *        --> to recover the scanner status.
69 *
70 * \return 0 if a xml child node is found, 1 otherwise
71 */
72static int
73find_nodes_s(xmlNode *node)
74{
75    xmlNode *child = node->children;
76
77    while (child) {
78        if (child->type == XML_ELEMENT_NODE)
79            return (0);
80        child = child->next;
81    }
82    return (1);
83}
84
85static void
86print_xml_job_status(xmlNode *node,
87                     SANE_Status *job,
88                     int *image)
89{
90    while (node) {
91        if (node->type == XML_ELEMENT_NODE) {
92            if (find_nodes_s(node)) {
93                if (strcmp((const char *)node->name, "JobState") == 0) {
94                    const char *state = (const char *)xmlNodeGetContent(node);
95                    if (!strcmp(state, "Processing")) {
96                        *job = SANE_STATUS_DEVICE_BUSY;
97                        DBG(10, "jobId Processing SANE_STATUS_DEVICE_BUSY\n");
98                    }
99                    else if (!strcmp(state, "Completed")) {
100                        *job = SANE_STATUS_GOOD;
101                        DBG(10, "jobId Completed SANE_STATUS_GOOD\n");
102                    }
103                    else if (strcmp((const char *)node->name, "ImagesToTransfer") == 0) {
104	                const char *state = (const char *)xmlNodeGetContent(node);
105	                *image = atoi(state);
106	            }
107                }
108            }
109        }
110        print_xml_job_status(node->children, job, image);
111        node = node->next;
112    }
113}
114
115static void
116print_xml_platen_and_adf_status(xmlNode *node,
117                                SANE_Status *platen,
118                                SANE_Status *adf,
119                                const char* jobId,
120                                SANE_Status *job,
121                                int *image)
122{
123    while (node) {
124        if (node->type == XML_ELEMENT_NODE) {
125            if (find_nodes_s(node)) {
126                if (strcmp((const char *)node->name, "State") == 0) {
127	            DBG(10, "State\t");
128                    const char *state = (const char *)xmlNodeGetContent(node);
129                    if (!strcmp(state, "Idle")) {
130			DBG(10, "Idle SANE_STATUS_GOOD\n");
131                        *platen = SANE_STATUS_GOOD;
132                    } else if (!strcmp(state, "Processing")) {
133			DBG(10, "Processing SANE_STATUS_DEVICE_BUSY\n");
134                        *platen = SANE_STATUS_DEVICE_BUSY;
135                    } else {
136			DBG(10, "%s SANE_STATUS_UNSUPPORTED\n", state);
137                        *platen = SANE_STATUS_UNSUPPORTED;
138                    }
139                }
140                // Thank's Alexander Pevzner (pzz@apevzner.com)
141                else if (adf && strcmp((const char *)node->name, "AdfState") == 0) {
142                    const char *state = (const char *)xmlNodeGetContent(node);
143                    if (!strcmp(state, "ScannerAdfLoaded")){
144			DBG(10, "ScannerAdfLoaded SANE_STATUS_GOOD\n");
145                        *adf = SANE_STATUS_GOOD;
146                    } else if (!strcmp(state, "ScannerAdfJam")) {
147                        DBG(10, "ScannerAdfJam SANE_STATUS_JAMMED\n");
148                        *adf = SANE_STATUS_JAMMED;
149                    } else if (!strcmp(state, "ScannerAdfDoorOpen")) {
150                        DBG(10, "ScannerAdfDoorOpen SANE_STATUS_COVER_OPEN\n");
151                        *adf = SANE_STATUS_COVER_OPEN;
152                    } else if (!strcmp(state, "ScannerAdfProcessing")) {
153                        /* Kyocera version */
154                        DBG(10, "ScannerAdfProcessing SANE_STATUS_NO_DOC\n");
155                        *adf = SANE_STATUS_NO_DOCS;
156                    } else if (!strcmp(state, "ScannerAdfEmpty")) {
157                        DBG(10, "ScannerAdfEmpty SANE_STATUS_NO_DOCS\n");
158                        /* Cannon TR4500, EPSON XP-7100 */
159                        *adf = SANE_STATUS_NO_DOCS;
160                    } else {
161                        DBG(10, "%s SANE_STATUS_NO_DOCS\n", state);
162                        *adf = SANE_STATUS_UNSUPPORTED;
163                    }
164                }
165                else if (jobId && job && strcmp((const char *)node->name, "JobUri") == 0) {
166                    if (strstr((const char *)xmlNodeGetContent(node), jobId)) {
167						print_xml_job_status(node, job, image);
168					}
169                }
170            }
171        }
172        print_xml_platen_and_adf_status(node->children,
173                                        platen,
174                                        adf,
175                                        jobId,
176                                        job,
177                                        image);
178        node = node->next;
179    }
180}
181
182/**
183 * \fn SANE_Status escl_status(const ESCL_Device *device)
184 * \brief Function that finally recovers the scanner status ('Idle', or not), using curl.
185 *        This function is called in the 'sane_open' function and it's the equivalent of
186 *        the following curl command : "curl http(s)://'ip':'port'/eSCL/ScannerStatus".
187 *
188 * \return status (if everything is OK, status = SANE_STATUS_GOOD, otherwise, SANE_STATUS_NO_MEM/SANE_STATUS_INVAL)
189 */
190SANE_Status
191escl_status(const ESCL_Device *device,
192            int source,
193            const char* jobId,
194            SANE_Status *job)
195{
196    SANE_Status status = SANE_STATUS_DEVICE_BUSY;
197    SANE_Status platen= SANE_STATUS_DEVICE_BUSY;
198    SANE_Status adf= SANE_STATUS_DEVICE_BUSY;
199    CURL *curl_handle = NULL;
200    struct idle *var = NULL;
201    xmlDoc *data = NULL;
202    xmlNode *node = NULL;
203    const char *scanner_status = "/eSCL/ScannerStatus";
204    int image = -1;
205    int pass = 0;
206reload:
207
208    if (device == NULL)
209        return (SANE_STATUS_NO_MEM);
210    status = SANE_STATUS_DEVICE_BUSY;
211    platen= SANE_STATUS_DEVICE_BUSY;
212    adf= SANE_STATUS_DEVICE_BUSY;
213    var = (struct idle*)calloc(1, sizeof(struct idle));
214    if (var == NULL)
215        return (SANE_STATUS_NO_MEM);
216    var->memory = malloc(1);
217    var->size = 0;
218    curl_handle = curl_easy_init();
219
220    escl_curl_url(curl_handle, device, scanner_status);
221    curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, memory_callback_s);
222    curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)var);
223    curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L);
224    curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 3L);
225    CURLcode res = curl_easy_perform(curl_handle);
226    if (res != CURLE_OK) {
227        DBG( 1, "The scanner didn't respond: %s\n", curl_easy_strerror(res));
228        status = SANE_STATUS_INVAL;
229        goto clean_data;
230    }
231    DBG( 10, "eSCL : Status : %s.\n", var->memory);
232    data = xmlReadMemory(var->memory, var->size, "file.xml", NULL, 0);
233    if (data == NULL) {
234        status = SANE_STATUS_NO_MEM;
235        goto clean_data;
236    }
237    node = xmlDocGetRootElement(data);
238    if (node == NULL) {
239        status = SANE_STATUS_NO_MEM;
240        goto clean;
241    }
242    /* Decode Job status */
243    // Thank's Alexander Pevzner (pzz@apevzner.com)
244    print_xml_platen_and_adf_status(node, &platen, &adf, jobId, job, &image);
245    if (platen != SANE_STATUS_GOOD &&
246        platen != SANE_STATUS_UNSUPPORTED) {
247        status = platen;
248    } else if (source == PLATEN) {
249        status = platen;
250    } else {
251        status = adf;
252    }
253    DBG (10, "STATUS : %s\n", sane_strstatus(status));
254clean:
255    xmlFreeDoc(data);
256clean_data:
257    xmlCleanupParser();
258    xmlMemoryDump();
259    curl_easy_cleanup(curl_handle);
260    free(var->memory);
261    free(var);
262    if (pass == 0 &&
263        source != PLATEN &&
264        image == 0 &&
265        (status == SANE_STATUS_GOOD ||
266         status == SANE_STATUS_UNSUPPORTED ||
267         status == SANE_STATUS_DEVICE_BUSY)) {
268       pass = 1;
269       goto reload;
270    }
271    return (status);
272}
273