• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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