• 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>%.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