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 #include <unistd.h>
33
34 #ifdef PATH_MAX
35 # undef PATH_MAX
36 #endif
37
38 #define PATH_MAX 4096
39
40 struct downloading
41 {
42 char *memory;
43 size_t size;
44 };
45
46 static const char settings[] =
47 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" \
48 "<scan:ScanSettings xmlns:pwg=\"http://www.pwg.org/schemas/2010/12/sm\" xmlns:scan=\"http://schemas.hp.com/imaging/escl/2011/05/03\">" \
49 " <pwg:Version>%.2f</pwg:Version>" \
50 " <pwg:ScanRegions>" \
51 " <pwg:ScanRegion>" \
52 " <pwg:ContentRegionUnits>escl:ThreeHundredthsOfInches</pwg:ContentRegionUnits>" \
53 " <pwg:Height>%d</pwg:Height>" \
54 " <pwg:Width>%d</pwg:Width>" \
55 " <pwg:XOffset>%d</pwg:XOffset>" \
56 " <pwg:YOffset>%d</pwg:YOffset>" \
57 " </pwg:ScanRegion>" \
58 " </pwg:ScanRegions>" \
59 "%s" \
60 " <scan:ColorMode>%s</scan:ColorMode>" \
61 " <scan:XResolution>%d</scan:XResolution>" \
62 " <scan:YResolution>%d</scan:YResolution>" \
63 " <pwg:InputSource>%s</pwg:InputSource>" \
64 "%s" \
65 "%s" \
66 "</scan:ScanSettings>";
67
68 /**
69 * \fn static size_t download_callback(void *str, size_t size, size_t nmemb, void *userp)
70 * \brief Callback function that stocks in memory the content of the 'job'. Example below :
71 * "Trying 192.168.14.150...
72 * TCP_NODELAY set
73 * Connected to 192.168.14.150 (192.168.14.150) port 80
74 * POST /eSCL/ScanJobs HTTP/1.1
75 * Host: 192.168.14.150
76 * User-Agent: curl/7.55.1
77 * Accept: /
78 * Content-Length: 605
79 * Content-Type: application/x-www-form-urlencoded
80 * upload completely sent off: 605 out of 605 bytes
81 * < HTTP/1.1 201 Created
82 * < MIME-Version: 1.0
83 * < Location: http://192.168.14.150/eSCL/ScanJobs/22b54fd0-027b-1000-9bd0-f4a99726e2fa
84 * < Content-Length: 0
85 * < Connection: close
86 * <
87 * Closing connection 0"
88 *
89 * \return realsize (size of the content needed -> the 'job')
90 */
91 static size_t
download_callback(void * str,size_t size,size_t nmemb,void * userp)92 download_callback(void *str, size_t size, size_t nmemb, void *userp)
93 {
94 struct downloading *download = (struct downloading *)userp;
95 size_t realsize = size * nmemb;
96 char *content = realloc(download->memory, download->size + realsize + 1);
97
98 if (content == NULL) {
99 DBG( 1, "Not enough memory (realloc returned NULL)\n");
100 return (0);
101 }
102 download->memory = content;
103 memcpy(&(download->memory[download->size]), str, realsize);
104 download->size = download->size + realsize;
105 download->memory[download->size] = 0;
106 return (realsize);
107 }
108
109 static char*
add_support_option(char * key,int val)110 add_support_option(char *key, int val)
111 {
112 int size = (strlen(key) * 3) + 10;
113 char *tmp = (char*)calloc(1, size);
114 snprintf (tmp, size, "<scan:%s>%d</scan:%s>\n", key, val, key);
115 return tmp;
116 }
117
118 /**
119 * \fn char *escl_newjob (capabilities_t *scanner, const ESCL_Device *device, SANE_Status *status)
120 * \brief Function that, using curl, uploads the data (composed by the scanner capabilities) to the
121 * server to download the 'job' and recover the 'new job' (char *result), in LOCATION.
122 * This function is called in the 'sane_start' function and it's the equivalent of the
123 * following curl command : "curl -v POST -d cap.xml http(s)://'ip':'port'/eSCL/ScanJobs".
124 *
125 * \return result (the 'new job', situated in LOCATION)
126 */
127 char *
escl_newjob(capabilities_t * scanner,const ESCL_Device * device,SANE_Status * status)128 escl_newjob (capabilities_t *scanner, const ESCL_Device *device, SANE_Status *status)
129 {
130 CURL *curl_handle = NULL;
131 int off_x = 0, off_y = 0;
132 struct downloading *upload = NULL;
133 struct downloading *download = NULL;
134 const char *scan_jobs = "/eSCL/ScanJobs";
135 char cap_data[PATH_MAX] = { 0 };
136 char *location = NULL;
137 char *result = NULL;
138 char *temporary = NULL;
139 char *format_ext = NULL;
140 char f_ext_tmp[1024];
141 char duplex_mode[1024] = { 0 };
142 int wakup_count = 0;
143
144 *status = SANE_STATUS_GOOD;
145 if (device == NULL || scanner == NULL) {
146 *status = SANE_STATUS_NO_MEM;
147 DBG( 1, "Create NewJob : the name or the scan are invalid.\n");
148 return (NULL);
149 }
150 upload = (struct downloading *)calloc(1, sizeof(struct downloading));
151 if (upload == NULL) {
152 *status = SANE_STATUS_NO_MEM;
153 DBG( 1, "Create NewJob : memory allocation failure\n");
154 return (NULL);
155 }
156 download = (struct downloading *)calloc(1, sizeof(struct downloading));
157 if (download == NULL) {
158 free(upload);
159 DBG( 1, "Create NewJob : memory allocation failure\n");
160 *status = SANE_STATUS_NO_MEM;
161 return (NULL);
162 }
163 if (scanner->caps[scanner->source].default_format)
164 free(scanner->caps[scanner->source].default_format);
165 scanner->caps[scanner->source].default_format = NULL;
166 int have_png = scanner->caps[scanner->source].have_png;
167 int have_jpeg = scanner->caps[scanner->source].have_jpeg;
168 int have_tiff = scanner->caps[scanner->source].have_tiff;
169 int have_pdf = scanner->caps[scanner->source].have_pdf;
170
171 if ((scanner->source == PLATEN && have_pdf == -1) ||
172 (scanner->source > PLATEN)) {
173 if (have_tiff != -1) {
174 scanner->caps[scanner->source].default_format =
175 strdup(scanner->caps[scanner->source].DocumentFormats[have_tiff]);
176 }
177 else if (have_png != -1) {
178 scanner->caps[scanner->source].default_format =
179 strdup(scanner->caps[scanner->source].DocumentFormats[have_png]);
180 }
181 else if (have_jpeg != -1) {
182 scanner->caps[scanner->source].default_format =
183 strdup(scanner->caps[scanner->source].DocumentFormats[have_jpeg]);
184 }
185 }
186 else {
187 scanner->caps[scanner->source].default_format =
188 strdup(scanner->caps[scanner->source].DocumentFormats[have_pdf]);
189 }
190 if (device->version <= 2.0)
191 {
192 // For eSCL 2.0 and older clients
193 snprintf(f_ext_tmp, sizeof(f_ext_tmp),
194 " <pwg:DocumentFormat>%s</pwg:DocumentFormat>",
195 scanner->caps[scanner->source].default_format);
196 }
197 else
198 {
199 // For eSCL 2.1 and newer clients
200 snprintf(f_ext_tmp, sizeof(f_ext_tmp),
201 " <scan:DocumentFormatExt>%s</scan:DocumentFormatExt>",
202 scanner->caps[scanner->source].default_format);
203 }
204 format_ext = f_ext_tmp;
205
206 if(scanner->source > PLATEN && scanner->Sources[ADFDUPLEX]) {
207 snprintf(duplex_mode, sizeof(duplex_mode),
208 " <scan:Duplex>%s</scan:Duplex>",
209 scanner->source == ADFDUPLEX ? "true" : "false");
210 }
211 DBG( 1, "Create NewJob : %s\n", scanner->caps[scanner->source].default_format);
212 if (scanner->caps[scanner->source].pos_x > scanner->caps[scanner->source].width)
213 off_x = (scanner->caps[scanner->source].pos_x > scanner->caps[scanner->source].width) / 2;
214 if (scanner->caps[scanner->source].pos_y > scanner->caps[scanner->source].height)
215 off_y = (scanner->caps[scanner->source].pos_y > scanner->caps[scanner->source].height) / 2;
216
217 char support_options[1024];
218 memset(support_options, 0, 1024);
219 char *source = (scanner->source == PLATEN ? "Platen" : "Feeder");
220 if (scanner->use_threshold)
221 {
222 if (scanner->val_threshold != scanner->threshold->value)
223 {
224 char *tmp = add_support_option("ThresholdSupport", scanner->val_threshold);
225 if (support_options[0])
226 strcat(support_options, tmp);
227 else
228 strcpy(support_options, tmp);
229 free(tmp);
230 }
231 }
232 if (scanner->use_sharpen)
233 {
234 if (scanner->val_sharpen != scanner->sharpen->value)
235 {
236 char *tmp = add_support_option("SharpenSupport", scanner->val_sharpen);
237 if (support_options[0])
238 strcat(support_options, tmp);
239 else
240 strcpy(support_options, tmp);
241 free(tmp);
242 }
243 }
244 if (scanner->use_contrast)
245 {
246 if (scanner->val_contrast != scanner->contrast->value)
247 {
248 char *tmp = add_support_option("ContrastSupport", scanner->val_contrast);
249 if (support_options[0])
250 strcat(support_options, tmp);
251 else
252 strcpy(support_options, tmp);
253 free(tmp);
254 }
255 }
256 if (scanner->use_brightness)
257 {
258 if (scanner->val_brightness != scanner->brightness->value)
259 {
260 char *tmp = add_support_option("BrightnessSupport", scanner->val_brightness);
261 if (support_options[0])
262 strcat(support_options, tmp);
263 else
264 strcpy(support_options, tmp);
265 free(tmp);
266 }
267 }
268 snprintf(cap_data, sizeof(cap_data), settings,
269 device->version,
270 scanner->caps[scanner->source].height,
271 scanner->caps[scanner->source].width,
272 off_x,
273 off_y,
274 format_ext,
275 scanner->caps[scanner->source].default_color,
276 scanner->caps[scanner->source].default_resolution,
277 scanner->caps[scanner->source].default_resolution,
278 source,
279 duplex_mode[0] == 0 ? " " : duplex_mode,
280 support_options[0] == 0 ? " " : support_options);
281 upload->memory = strdup(cap_data);
282 upload->size = strlen(cap_data);
283 wake_up_device:
284 DBG( 1, "Create NewJob : %s\n", cap_data);
285 download->memory = malloc(1);
286 download->size = 0;
287 curl_handle = curl_easy_init();
288 if (curl_handle != NULL) {
289 escl_curl_url(curl_handle, device, scan_jobs);
290 curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
291 curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, (const char*)upload->memory);
292 curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, upload->size);
293 curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, download_callback);
294 curl_easy_setopt(curl_handle, CURLOPT_HEADERDATA, (void *)download);
295 curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L);
296 curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 3L);
297 CURLcode res = curl_easy_perform(curl_handle);
298 if (res != CURLE_OK) {
299 DBG( 1, "Create NewJob : the scanner responded incorrectly: %s\n", curl_easy_strerror(res));
300 *status = SANE_STATUS_INVAL;
301 }
302 else {
303 if (download->memory != NULL) {
304 char *tmp_location = strstr(download->memory, "Location:");
305 if (tmp_location) {
306 temporary = strchr(tmp_location, '\r');
307 if (temporary == NULL)
308 temporary = strchr(tmp_location, '\n');
309 if (temporary != NULL) {
310 *temporary = '\0';
311 location = strrchr(tmp_location,'/');
312 if (location) {
313 result = strdup(location);
314 DBG( 1, "Create NewJob : %s\n", result);
315 *temporary = '\n';
316 *location = '\0';
317 location = strrchr(tmp_location,'/');
318 wakup_count = 0;
319 if (location) {
320 location++;
321 scanner->scanJob = strdup(location);
322 DBG( 1, "Full location header [%s]\n", scanner->scanJob);
323 }
324 else
325 scanner->scanJob = strdup("ScanJobs");
326 *location = '/';
327 }
328 }
329 if (result == NULL) {
330 DBG( 1, "Error : Create NewJob, no location: %s\n", download->memory);
331 *status = SANE_STATUS_INVAL;
332 }
333 free(download->memory);
334 download->memory = NULL;
335 }
336 else {
337 DBG( 1, "Create NewJob : The creation of the failed job: %s\n", download->memory);
338 // If "409 Conflict" appear it means that there is no paper in feeder
339 if (strstr(download->memory, "409 Conflict") != NULL)
340 *status = SANE_STATUS_NO_DOCS;
341 // If "503 Service Unavailable" appear, it means that device is busy (scanning in progress)
342 else if (strstr(download->memory, "503 Service Unavailable") != NULL) {
343 wakup_count += 1;
344 *status = SANE_STATUS_DEVICE_BUSY;
345 }
346 else
347 *status = SANE_STATUS_INVAL;
348 }
349 }
350 else {
351 *status = SANE_STATUS_NO_MEM;
352 DBG( 1, "Create NewJob : The creation of the failed job\n");
353 return (NULL);
354 }
355 }
356 curl_easy_cleanup(curl_handle);
357 }
358 if (wakup_count > 0 && wakup_count < 4) {
359 free(download->memory);
360 download->memory = NULL;
361 download->size = 0;
362 *status = SANE_STATUS_GOOD;
363 usleep(250);
364 goto wake_up_device;
365 }
366 if (upload != NULL) {
367 free(upload->memory);
368 free(upload);
369 }
370 if (download != NULL)
371 free(download);
372 return (result);
373 }
374