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>%s</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 if (have_pdf != -1) {
188 scanner->caps[scanner->source].default_format =
189 strdup(scanner->caps[scanner->source].DocumentFormats[have_pdf]);
190 }
191 else if (have_tiff != -1) {
192 scanner->caps[scanner->source].default_format =
193 strdup(scanner->caps[scanner->source].DocumentFormats[have_tiff]);
194 }
195 else if (have_png != -1) {
196 scanner->caps[scanner->source].default_format =
197 strdup(scanner->caps[scanner->source].DocumentFormats[have_png]);
198 }
199 else if (have_jpeg != -1) {
200 scanner->caps[scanner->source].default_format =
201 strdup(scanner->caps[scanner->source].DocumentFormats[have_jpeg]);
202 }
203 }
204 if (atof ((const char *)device->version) <= 2.0)
205 {
206 // For eSCL 2.0 and older clients
207 snprintf(f_ext_tmp, sizeof(f_ext_tmp),
208 " <pwg:DocumentFormat>%s</pwg:DocumentFormat>",
209 scanner->caps[scanner->source].default_format);
210 }
211 else
212 {
213 // For eSCL 2.1 and newer clients
214 snprintf(f_ext_tmp, sizeof(f_ext_tmp),
215 " <scan:DocumentFormatExt>%s</scan:DocumentFormatExt>",
216 scanner->caps[scanner->source].default_format);
217 }
218 format_ext = f_ext_tmp;
219
220 if(scanner->source > PLATEN && scanner->Sources[ADFDUPLEX]) {
221 snprintf(duplex_mode, sizeof(duplex_mode),
222 " <scan:Duplex>%s</scan:Duplex>",
223 scanner->source == ADFDUPLEX ? "true" : "false");
224 }
225 DBG( 1, "Create NewJob : %s\n", scanner->caps[scanner->source].default_format);
226 if (scanner->caps[scanner->source].pos_x > scanner->caps[scanner->source].width)
227 off_x = (scanner->caps[scanner->source].pos_x > scanner->caps[scanner->source].width) / 2;
228 if (scanner->caps[scanner->source].pos_y > scanner->caps[scanner->source].height)
229 off_y = (scanner->caps[scanner->source].pos_y > scanner->caps[scanner->source].height) / 2;
230
231 char support_options[1024];
232 memset(support_options, 0, 1024);
233 char *source = (scanner->source == PLATEN ? "Platen" : "Feeder");
234 if (scanner->use_threshold)
235 {
236 if (scanner->val_threshold != scanner->threshold->value)
237 {
238 char *tmp = add_support_option("ThresholdSupport", scanner->val_threshold);
239 if (support_options[0])
240 strcat(support_options, tmp);
241 else
242 strcpy(support_options, tmp);
243 free(tmp);
244 }
245 }
246 if (scanner->use_sharpen)
247 {
248 if (scanner->val_sharpen != scanner->sharpen->value)
249 {
250 char *tmp = add_support_option("SharpenSupport", scanner->val_sharpen);
251 if (support_options[0])
252 strcat(support_options, tmp);
253 else
254 strcpy(support_options, tmp);
255 free(tmp);
256 }
257 }
258 if (scanner->use_contrast)
259 {
260 if (scanner->val_contrast != scanner->contrast->value)
261 {
262 char *tmp = add_support_option("ContrastSupport", scanner->val_contrast);
263 if (support_options[0])
264 strcat(support_options, tmp);
265 else
266 strcpy(support_options, tmp);
267 free(tmp);
268 }
269 }
270 if (scanner->use_brightness)
271 {
272 if (scanner->val_brightness != scanner->brightness->value)
273 {
274 char *tmp = add_support_option("BrightnessSupport", scanner->val_brightness);
275 if (support_options[0])
276 strcat(support_options, tmp);
277 else
278 strcpy(support_options, tmp);
279 free(tmp);
280 }
281 }
282 snprintf(cap_data, sizeof(cap_data), settings,
283 device->version,
284 scanner->caps[scanner->source].height,
285 scanner->caps[scanner->source].width,
286 off_x,
287 off_y,
288 format_ext,
289 scanner->caps[scanner->source].default_color,
290 scanner->caps[scanner->source].default_resolution,
291 scanner->caps[scanner->source].default_resolution,
292 source,
293 duplex_mode[0] == 0 ? " " : duplex_mode,
294 support_options[0] == 0 ? " " : support_options);
295 upload->memory = strdup(cap_data);
296 upload->size = strlen(cap_data);
297 wake_up_device:
298 DBG( 1, "Create NewJob : %s\n", cap_data);
299 download->memory = malloc(1);
300 download->size = 0;
301 curl_handle = curl_easy_init();
302 if (curl_handle != NULL) {
303 escl_curl_url(curl_handle, device, scan_jobs);
304 curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
305 curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, (const char*)upload->memory);
306 curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, upload->size);
307 curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, download_callback);
308 curl_easy_setopt(curl_handle, CURLOPT_HEADERDATA, (void *)download);
309 curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L);
310 curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 3L);
311 CURLcode res = curl_easy_perform(curl_handle);
312 if (res != CURLE_OK) {
313 DBG( 1, "Create NewJob : the scanner responded incorrectly: %s\n", curl_easy_strerror(res));
314 *status = SANE_STATUS_INVAL;
315 }
316 else {
317 if (download->memory != NULL) {
318 char *tmp_location = strstr(download->memory, "Location:");
319 if (tmp_location) {
320 temporary = strchr(tmp_location, '\r');
321 if (temporary == NULL)
322 temporary = strchr(tmp_location, '\n');
323 if (temporary != NULL) {
324 *temporary = '\0';
325 location = strrchr(tmp_location,'/');
326 if (location) {
327 result = strdup(location);
328 DBG( 1, "Create NewJob : %s\n", result);
329 *temporary = '\n';
330 *location = '\0';
331 location = strrchr(tmp_location,'/');
332 wakup_count = 0;
333 if (location) {
334 location++;
335 scanner->scanJob = strdup(location);
336 DBG( 1, "Full location header [%s]\n", scanner->scanJob);
337 }
338 else
339 scanner->scanJob = strdup("ScanJobs");
340 *location = '/';
341 }
342 }
343 if (result == NULL) {
344 DBG( 1, "Error : Create NewJob, no location: %s\n", download->memory);
345 *status = SANE_STATUS_INVAL;
346 }
347 free(download->memory);
348 download->memory = NULL;
349 }
350 else {
351 DBG( 1, "Create NewJob : The creation of the failed job: %s\n", download->memory);
352 // If "409 Conflict" appear it means that there is no paper in feeder
353 if (strstr(download->memory, "409 Conflict") != NULL)
354 *status = SANE_STATUS_NO_DOCS;
355 // If "503 Service Unavailable" appear, it means that device is busy (scanning in progress)
356 else if (strstr(download->memory, "503 Service Unavailable") != NULL) {
357 wakup_count += 1;
358 *status = SANE_STATUS_DEVICE_BUSY;
359 }
360 else
361 *status = SANE_STATUS_INVAL;
362 }
363 }
364 else {
365 *status = SANE_STATUS_NO_MEM;
366 DBG( 1, "Create NewJob : The creation of the failed job\n");
367 return (NULL);
368 }
369 }
370 curl_easy_cleanup(curl_handle);
371 }
372 if (wakup_count > 0 && wakup_count < 4) {
373 free(download->memory);
374 download->memory = NULL;
375 download->size = 0;
376 *status = SANE_STATUS_GOOD;
377 usleep(250);
378 goto wake_up_device;
379 }
380 if (upload != NULL) {
381 free(upload->memory);
382 free(upload);
383 }
384 if (download != NULL)
385 free(download);
386 return (result);
387 }
388